aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-10 07:15:30 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-05-10 07:15:30 +0000
commit4a26545e3ac9dfab9e2f3460eb850cb719453e4e (patch)
tree7c4f13116f1e65927321fcebaab6de4b0b9c79bb
parent131694bccfc3b959b0a71de8056dd1125210cf8e (diff)
parent334f479455a12b619f6c240e634a14f9e16ce1b2 (diff)
downloadcrosvm-4a26545e3ac9dfab9e2f3460eb850cb719453e4e.tar.gz
Snap for 8564071 from 334f479455a12b619f6c240e634a14f9e16ce1b2 to mainline-art-releaseaml_art_331314010aml_art_331113000aml_art_331012050
Change-Id: I8feb18efc082659e9cd3a1f6506f855777b2b551
-rw-r--r--.cargo/config.toml47
-rw-r--r--.devcontainer/devcontainer.json20
-rw-r--r--.github/mistaken-pull-closer.yml13
-rw-r--r--.github/pull_request_template.md3
-rw-r--r--.github/workflows/gh-pages.yml36
-rw-r--r--.gitignore2
-rw-r--r--.gitmodules3
-rw-r--r--ARCHITECTURE.md234
-rw-r--r--Android.bp456
-rw-r--r--CONTRIBUTING.md184
-rw-r--r--Cargo.lock1289
-rw-r--r--Cargo.toml186
-rw-r--r--OWNERS13
-rw-r--r--OWNERS.android5
-rw-r--r--PRESUBMIT.cfg6
-rw-r--r--PREUPLOAD.cfg2
-rw-r--r--README.chromeos.md17
-rw-r--r--README.md306
-rw-r--r--TEST_MAPPING7
-rw-r--r--aarch64/Android.bp159
-rw-r--r--aarch64/Cargo.toml8
-rw-r--r--aarch64/TEST_MAPPING2
-rw-r--r--aarch64/cargo2android.json8
-rw-r--r--aarch64/cargo2android_arch.bp16
-rw-r--r--aarch64/src/fdt.rs244
-rw-r--r--aarch64/src/lib.rs481
-rw-r--r--acpi_tables/Android.bp43
-rw-r--r--acpi_tables/Cargo.toml6
-rw-r--r--acpi_tables/src/aml.rs132
-rw-r--r--acpi_tables/src/facs.rs46
-rw-r--r--acpi_tables/src/lib.rs1
-rw-r--r--acpi_tables/src/rsdp.rs2
-rw-r--r--acpi_tables/src/sdt.rs27
-rwxr-xr-xall2android.sh37
-rw-r--r--arch/Android.bp175
-rw-r--r--arch/Cargo.toml17
-rw-r--r--arch/cargo2android.json8
-rw-r--r--arch/cargo2android_gdb.bp10
-rw-r--r--arch/src/fdt.rs35
-rw-r--r--arch/src/lib.rs507
-rw-r--r--arch/src/pstore.rs73
-rw-r--r--arch/src/serial.rs392
-rw-r--r--base/Android.bp129
-rw-r--r--base/Cargo.toml26
-rw-r--r--base/base_poll_token_derive/Android.bp (renamed from sys_util/poll_token_derive/Android.bp)37
-rw-r--r--base/base_poll_token_derive/Cargo.toml16
-rw-r--r--base/base_poll_token_derive/cargo2android.json7
-rw-r--r--base/base_poll_token_derive/poll_token_derive.rs (renamed from sys_util/poll_token_derive/poll_token_derive.rs)4
-rw-r--r--base/base_poll_token_derive/tests.rs (renamed from sys_util/poll_token_derive/tests.rs)0
-rw-r--r--base/build.rs9
-rw-r--r--base/cargo2android.json9
-rw-r--r--base/patches/Android.bp.patch81
-rw-r--r--base/src/alloc.rs (renamed from sys_util/src/alloc.rs)96
-rw-r--r--base/src/async_types.rs25
-rw-r--r--base/src/descriptor.rs130
-rw-r--r--base/src/descriptor_reflection.rs550
-rw-r--r--base/src/errno.rs (renamed from sys_util/src/errno.rs)40
-rw-r--r--base/src/event.rs60
-rw-r--r--base/src/external_mapping.rs (renamed from sys_util/src/external_mapping.rs)34
-rw-r--r--base/src/ioctl.rs15
-rw-r--r--base/src/lib.rs102
-rw-r--r--base/src/mmap.rs32
-rw-r--r--base/src/notifiers.rs33
-rw-r--r--base/src/scoped_event_macro.rs52
-rw-r--r--base/src/shm.rs48
-rw-r--r--base/src/timer.rs23
-rw-r--r--base/src/tube.rs301
-rw-r--r--base/src/unix/acpi_event.rs109
-rw-r--r--base/src/unix/android/mod.rs (renamed from sys_util/src/android/mod.rs)0
-rw-r--r--base/src/unix/android/syslog.rs (renamed from sys_util/src/android/syslog.rs)12
-rw-r--r--base/src/unix/capabilities.rs (renamed from sys_util/src/capabilities.rs)2
-rw-r--r--base/src/unix/clock.rs (renamed from sys_util/src/clock.rs)8
-rw-r--r--base/src/unix/descriptor.rs237
-rw-r--r--base/src/unix/eventfd.rs (renamed from sys_util/src/eventfd.rs)67
-rw-r--r--base/src/unix/file_flags.rs (renamed from sys_util/src/file_flags.rs)8
-rw-r--r--base/src/unix/file_traits.rs505
-rw-r--r--base/src/unix/get_filesystem_type.rs29
-rw-r--r--base/src/unix/handle_eintr.rs259
-rw-r--r--base/src/unix/ioctl.rs204
-rw-r--r--base/src/unix/linux/mod.rs (renamed from sys_util/src/linux/mod.rs)0
-rw-r--r--base/src/unix/linux/syslog.rs (renamed from sys_util/src/linux/syslog.rs)26
-rw-r--r--base/src/unix/mmap.rs1159
-rw-r--r--base/src/unix/mod.rs685
-rw-r--r--base/src/unix/net.rs1091
-rw-r--r--base/src/unix/netlink.rs499
-rw-r--r--base/src/unix/poll.rs809
-rw-r--r--base/src/unix/priority.rs (renamed from sys_util/src/priority.rs)2
-rw-r--r--base/src/unix/rand.rs112
-rw-r--r--base/src/unix/raw_fd.rs (renamed from sys_util/src/raw_fd.rs)0
-rw-r--r--base/src/unix/read_dir.rs148
-rw-r--r--base/src/unix/sched.rs143
-rw-r--r--base/src/unix/scoped_path.rs (renamed from sys_util/src/scoped_path.rs)0
-rw-r--r--base/src/unix/scoped_signal_handler.rs (renamed from sys_util/src/scoped_signal_handler.rs)116
-rw-r--r--base/src/unix/shm.rs473
-rw-r--r--base/src/unix/signal.rs (renamed from sys_util/src/signal.rs)197
-rw-r--r--base/src/unix/signalfd.rs (renamed from sys_util/src/signalfd.rs)84
-rw-r--r--base/src/unix/sock_ctrl_msg.rs (renamed from sys_util/src/sock_ctrl_msg.rs)140
-rw-r--r--base/src/unix/syslog.rs585
-rw-r--r--base/src/unix/terminal.rs (renamed from sys_util/src/terminal.rs)6
-rw-r--r--base/src/unix/timerfd.rs (renamed from sys_util/src/timerfd.rs)51
-rw-r--r--base/src/unix/tube.rs146
-rw-r--r--base/src/unix/vsock.rs (renamed from sys_util/src/vsock.rs)38
-rw-r--r--base/src/unix/write_zeroes.rs (renamed from sys_util/src/write_zeroes.rs)17
-rw-r--r--base/src/wait_context.rs26
-rw-r--r--base/src/windows/clock.rs119
-rw-r--r--base/src/windows/descriptor.rs181
-rw-r--r--base/src/windows/eventfd.rs210
-rw-r--r--base/src/windows/events.rs191
-rw-r--r--base/src/windows/file_traits.rs430
-rw-r--r--base/src/windows/gmtime.rs38
-rw-r--r--base/src/windows/ioctl.rs251
-rw-r--r--base/src/windows/mmap.rs491
-rw-r--r--base/src/windows/mod.rs110
-rw-r--r--base/src/windows/notifiers.rs16
-rw-r--r--base/src/windows/poll.rs123
-rw-r--r--base/src/windows/priority.rs45
-rw-r--r--base/src/windows/rand.rs121
-rw-r--r--base/src/windows/shm.rs152
-rw-r--r--base/src/windows/stdio_fileno.c13
-rw-r--r--base/src/windows/stream_channel.rs131
-rw-r--r--base/src/windows/syslog.rs865
-rw-r--r--base/src/windows/thread.rs52
-rw-r--r--base/src/windows/timer.rs355
-rw-r--r--base/src/windows/tube.rs448
-rw-r--r--base/src/windows/win/console.rs35
-rw-r--r--base/src/windows/win/event.rs309
-rw-r--r--base/src/windows/win/file_traits.rs224
-rw-r--r--base/src/windows/win/file_util.rs27
-rw-r--r--base/src/windows/win/get_filesystem_type.rs11
-rw-r--r--base/src/windows/win/ioctl.rs476
-rw-r--r--base/src/windows/win/mmap.rs412
-rw-r--r--base/src/windows/win/mod.rs175
-rw-r--r--base/src/windows/win/named_pipes.rs1035
-rw-r--r--base/src/windows/win/platform_timer_utils.rs206
-rw-r--r--base/src/windows/win/priority.rs91
-rw-r--r--base/src/windows/win/punch_hole.rs53
-rw-r--r--base/src/windows/win/sched.rs73
-rw-r--r--base/src/windows/win/shm.rs71
-rw-r--r--base/src/windows/win/stream_channel.rs469
-rw-r--r--base/src/windows/win/syslog.rs38
-rw-r--r--base/src/windows/win/timer.rs159
-rw-r--r--base/src/windows/win/wait.rs262
-rw-r--r--base/src/windows/win/write_zeros.rs29
-rw-r--r--base/src/windows/write_zeroes.rs216
-rwxr-xr-xbin/clippy98
-rwxr-xr-xbin/crate_coverage36
-rwxr-xr-xbin/fmt28
-rwxr-xr-xbin/preupload-clippy8
-rwxr-xr-xbin/sync_ebuild_files155
-rw-r--r--bit_field/Android.bp64
-rw-r--r--bit_field/Cargo.toml2
-rw-r--r--bit_field/bit_field_derive/Android.bp29
-rw-r--r--bit_field/bit_field_derive/Cargo.toml2
-rw-r--r--bit_field/bit_field_derive/bit_field_derive.rs6
-rw-r--r--bit_field/bit_field_derive/cargo2android.json7
-rw-r--r--bit_field/cargo2android.json9
-rw-r--r--cargo2android.json15
-rw-r--r--cargo2android_defaults.bp27
-rw-r--r--cargo2android_module.bp39
-rw-r--r--ci/Makefile58
-rw-r--r--ci/README.md132
-rwxr-xr-xci/aarch64_builder9
-rw-r--r--ci/build_environment/Makefile100
-rw-r--r--ci/build_environment/pkgconfig/libminijail.pc4
-rw-r--r--ci/build_environment/pkgconfig/libtpm2.pc5
-rwxr-xr-xci/builder9
-rw-r--r--ci/crosvm_aarch64_builder/Dockerfile75
-rw-r--r--ci/crosvm_aarch64_builder/bashrc5
-rwxr-xr-xci/crosvm_aarch64_builder/entrypoint58
-rw-r--r--ci/crosvm_base/Dockerfile78
-rw-r--r--ci/crosvm_base/rust-toolchain1
-rw-r--r--ci/crosvm_builder/Dockerfile48
-rw-r--r--ci/crosvm_builder/bashrc5
-rwxr-xr-xci/crosvm_builder/entrypoint54
-rw-r--r--ci/crosvm_test_vm/Dockerfile78
-rw-r--r--ci/crosvm_test_vm/build/cloud_init_data.yaml54
-rwxr-xr-xci/crosvm_test_vm/build/first_boot.expect21
-rw-r--r--ci/crosvm_test_vm/runtime/ssh/config5
-rwxr-xr-xci/crosvm_test_vm/runtime/start_vm.amd6419
-rwxr-xr-xci/crosvm_test_vm/runtime/start_vm.arm6415
-rw-r--r--ci/image_tag1
-rwxr-xr-xci/kokoro/build-aarch64.sh4
-rwxr-xr-xci/kokoro/build-armhf.sh (renamed from ci/kokoro/simulate_all)8
-rwxr-xr-xci/kokoro/build-merge-into-chromeos.sh222
-rwxr-xr-xci/kokoro/build-push-to-github.sh10
-rwxr-xr-xci/kokoro/build-x86_64.sh12
-rwxr-xr-xci/kokoro/common.sh64
-rw-r--r--ci/kokoro/continuous-armhf.cfg3
-rw-r--r--ci/kokoro/merge-into-chromeos.cfg3
-rw-r--r--ci/kokoro/presubmit-cq-armhf.cfg3
-rw-r--r--ci/kokoro/presubmit-cr-armhf.cfg3
-rw-r--r--ci/kokoro/push-to-github.cfg13
-rwxr-xr-xci/kokoro/simulate32
-rwxr-xr-xci/kokoro/simulate.py56
-rw-r--r--ci/kokoro/windows/common.cfg9
-rw-r--r--ci/kokoro/windows/continuous-windows.cfg3
-rw-r--r--ci/kokoro/windows/crosvm_build.bat54
-rw-r--r--ci/kokoro/windows/presubmit-windows.cfg3
-rwxr-xr-xci/run_container.sh80
-rw-r--r--ci/test_runner.py629
-rw-r--r--ci/vm_tools/README.md8
-rwxr-xr-xci/vm_tools/exec_binary_in_vm28
-rwxr-xr-xci/vm_tools/sync_deps37
-rwxr-xr-xci/vm_tools/wait_for_vm17
-rw-r--r--common/README.md11
-rw-r--r--common/assertions/Android.bp (renamed from assertions/Android.bp)29
-rw-r--r--common/assertions/Cargo.toml (renamed from assertions/Cargo.toml)2
-rw-r--r--common/assertions/src/lib.rs (renamed from assertions/src/lib.rs)0
-rw-r--r--common/assertions/src/mechanism.rs (renamed from assertions/src/mechanism.rs)0
-rw-r--r--common/audio_streams/Android.bp57
-rw-r--r--common/audio_streams/Cargo.toml18
-rw-r--r--common/audio_streams/README.md5
-rw-r--r--common/audio_streams/src/async_api.rs83
-rw-r--r--common/audio_streams/src/audio_streams.rs919
-rw-r--r--common/audio_streams/src/capture.rs463
-rw-r--r--common/audio_streams/src/shm_streams.rs599
-rw-r--r--common/balloon_control/Android.bp25
-rw-r--r--common/balloon_control/Cargo.toml8
-rw-r--r--common/balloon_control/src/lib.rs58
-rw-r--r--common/cros-fuzz/Cargo.toml (renamed from syscall_defines/Cargo.toml)7
-rw-r--r--common/cros-fuzz/OWNERS (renamed from cros_async/.build_test_skip)0
-rw-r--r--common/cros-fuzz/README.md3
-rw-r--r--common/cros-fuzz/cargo2android.json1
-rw-r--r--common/cros-fuzz/src/lib.rs95
-rw-r--r--common/cros-fuzz/src/rand.rs213
-rw-r--r--common/cros_async/.build_test_skip (renamed from fuzz/.build_test_skip)0
-rw-r--r--common/cros_async/Cargo.toml36
-rw-r--r--common/cros_async/DEPRECATED.md4
-rw-r--r--common/cros_async/TEST_MAPPING (renamed from io_uring/TEST_MAPPING)4
-rw-r--r--common/cros_async/cargo2android.json1
-rw-r--r--common/cros_async/src/audio_streams_async.rs68
-rw-r--r--common/cros_async/src/blocking.rs9
-rw-r--r--common/cros_async/src/blocking/block_on.rs202
-rw-r--r--common/cros_async/src/blocking/pool.rs524
-rw-r--r--common/cros_async/src/complete.rs91
-rw-r--r--common/cros_async/src/event.rs85
-rw-r--r--common/cros_async/src/executor.rs344
-rw-r--r--common/cros_async/src/fd_executor.rs634
-rw-r--r--common/cros_async/src/io_ext.rs479
-rw-r--r--common/cros_async/src/lib.rs540
-rw-r--r--common/cros_async/src/mem.rs98
-rw-r--r--common/cros_async/src/poll_source.rs450
-rw-r--r--common/cros_async/src/queue.rs66
-rw-r--r--common/cros_async/src/select.rs92
-rw-r--r--common/cros_async/src/sync.rs12
-rw-r--r--common/cros_async/src/sync/cv.rs1179
-rw-r--r--common/cros_async/src/sync/mu.rs2305
-rw-r--r--common/cros_async/src/sync/spin.rs284
-rw-r--r--common/cros_async/src/sync/waiter.rs288
-rw-r--r--common/cros_async/src/timer.rs120
-rw-r--r--common/cros_async/src/uring_executor.rs1151
-rw-r--r--common/cros_async/src/uring_source.rs643
-rw-r--r--common/cros_async/src/waker.rs70
-rw-r--r--common/cros_asyncv2/Cargo.toml34
-rw-r--r--common/cros_asyncv2/cargo2android.json1
-rw-r--r--common/cros_asyncv2/src/blocking.rs9
-rw-r--r--common/cros_asyncv2/src/blocking/block_on.rs (renamed from cros_async/src/sync/blocking.rs)20
-rw-r--r--common/cros_asyncv2/src/blocking/pool.rs522
-rw-r--r--common/cros_asyncv2/src/enter.rs67
-rw-r--r--common/cros_asyncv2/src/event.rs108
-rw-r--r--common/cros_asyncv2/src/executor.rs893
-rw-r--r--common/cros_asyncv2/src/file.rs462
-rw-r--r--common/cros_asyncv2/src/iobuf.rs190
-rw-r--r--common/cros_asyncv2/src/lib.rs108
-rw-r--r--common/cros_asyncv2/src/sync.rs12
-rw-r--r--common/cros_asyncv2/src/sync/cv.rs1166
-rw-r--r--common/cros_asyncv2/src/sync/mu.rs2293
-rw-r--r--common/cros_asyncv2/src/sync/spin.rs278
-rw-r--r--common/cros_asyncv2/src/sync/waiter.rs281
-rw-r--r--common/cros_asyncv2/src/timer.rs309
-rw-r--r--common/cros_asyncv2/src/unix/descriptor.rs35
-rw-r--r--common/cros_asyncv2/src/unix/event.rs67
-rw-r--r--common/cros_asyncv2/src/unix/file.rs145
-rw-r--r--common/cros_asyncv2/src/unix/io_driver.rs350
-rw-r--r--common/cros_asyncv2/src/unix/io_driver/cmsg.rs203
-rw-r--r--common/cros_asyncv2/src/unix/io_driver/mio.rs747
-rw-r--r--common/cros_asyncv2/src/unix/io_driver/uring.rs1142
-rw-r--r--common/cros_asyncv2/src/unix/mod.rs21
-rw-r--r--common/cros_asyncv2/src/unix/seqpacket.rs840
-rw-r--r--common/data_model/Android.bp (renamed from data_model/Android.bp)47
-rw-r--r--common/data_model/Cargo.toml (renamed from data_model/Cargo.toml)11
-rw-r--r--common/data_model/src/endian.rs (renamed from data_model/src/endian.rs)0
-rw-r--r--common/data_model/src/flexible_array.rs (renamed from data_model/src/flexible_array.rs)48
-rw-r--r--common/data_model/src/lib.rs (renamed from data_model/src/lib.rs)31
-rw-r--r--common/data_model/src/sys.rs16
-rw-r--r--common/data_model/src/sys/unix.rs (renamed from data_model/src/sys.rs)45
-rw-r--r--common/data_model/src/sys/windows.rs133
-rw-r--r--common/data_model/src/volatile_memory.rs (renamed from data_model/src/volatile_memory.rs)54
-rw-r--r--common/io_uring/.build_test_skip (renamed from io_uring/.build_test_skip)0
-rw-r--r--common/io_uring/Cargo.toml18
-rw-r--r--common/io_uring/DEPRECATED.md4
-rw-r--r--common/io_uring/TEST_MAPPING (renamed from sys_util/TEST_MAPPING)4
-rwxr-xr-xcommon/io_uring/bindgen.sh19
-rw-r--r--common/io_uring/cargo2android.json1
-rw-r--r--common/io_uring/src/bindings.rs353
-rw-r--r--common/io_uring/src/lib.rs9
-rw-r--r--common/io_uring/src/syscalls.rs41
-rw-r--r--common/io_uring/src/uring.rs1564
-rw-r--r--common/p9/Android.bp (renamed from tempfile/Android.bp)44
-rw-r--r--common/p9/Cargo.toml15
-rw-r--r--common/p9/OWNERS3
-rw-r--r--common/p9/README.md19
-rw-r--r--common/p9/fuzz/Cargo.toml19
-rw-r--r--common/p9/fuzz/cargo2android.json1
-rw-r--r--common/p9/fuzz/tframe_decode.rs12
-rw-r--r--common/p9/src/fuzzing.rs13
-rw-r--r--common/p9/src/lib.rs16
-rw-r--r--common/p9/src/protocol/messages.rs840
-rw-r--r--common/p9/src/protocol/mod.rs9
-rw-r--r--common/p9/src/protocol/wire_format.rs699
-rw-r--r--common/p9/src/server/mod.rs994
-rw-r--r--common/p9/src/server/tests.rs1101
-rw-r--r--common/p9/wire_format_derive/Android.bp26
-rw-r--r--common/p9/wire_format_derive/Cargo.toml (renamed from enumn/Cargo.toml)14
-rw-r--r--common/p9/wire_format_derive/cargo2android.json7
-rw-r--r--common/p9/wire_format_derive/wire_format_derive.rs303
-rw-r--r--common/sync/Android.bp (renamed from sync/Android.bp)29
-rw-r--r--common/sync/Cargo.toml (renamed from sync/Cargo.toml)2
-rw-r--r--common/sync/src/condvar.rs (renamed from sync/src/condvar.rs)38
-rw-r--r--common/sync/src/lib.rs (renamed from sync/src/lib.rs)0
-rw-r--r--common/sync/src/mutex.rs (renamed from sync/src/mutex.rs)0
-rw-r--r--common/sys_util/Android.bp57
-rw-r--r--common/sys_util/Cargo.toml (renamed from sys_util/Cargo.toml)9
-rw-r--r--common/sys_util/TEST_MAPPING (renamed from cros_async/TEST_MAPPING)4
-rw-r--r--common/sys_util/cargo2android.json9
-rw-r--r--common/sys_util/cargo2android_target.bp21
-rw-r--r--common/sys_util/src/acpi_event.rs109
-rw-r--r--common/sys_util/src/android/mod.rs7
-rw-r--r--common/sys_util/src/android/syslog.rs107
-rw-r--r--common/sys_util/src/capabilities.rs41
-rw-r--r--common/sys_util/src/clock.rs120
-rw-r--r--common/sys_util/src/descriptor.rs (renamed from sys_util/src/descriptor.rs)114
-rw-r--r--common/sys_util/src/descriptor_reflection.rs (renamed from sys_util/src/descriptor_reflection.rs)42
-rw-r--r--common/sys_util/src/eventfd.rs227
-rw-r--r--common/sys_util/src/file_flags.rs55
-rw-r--r--common/sys_util/src/file_traits.rs (renamed from sys_util/src/file_traits.rs)28
-rw-r--r--common/sys_util/src/get_filesystem_type.rs28
-rw-r--r--common/sys_util/src/handle_eintr.rs (renamed from sys_util/src/handle_eintr.rs)5
-rw-r--r--common/sys_util/src/ioctl.rs (renamed from sys_util/src/ioctl.rs)3
-rw-r--r--common/sys_util/src/lib.rs (renamed from sys_util/src/lib.rs)364
-rw-r--r--common/sys_util/src/linux/mod.rs7
-rw-r--r--common/sys_util/src/linux/syslog.rs186
-rw-r--r--common/sys_util/src/mmap.rs (renamed from sys_util/src/mmap.rs)149
-rw-r--r--common/sys_util/src/net.rs (renamed from sys_util/src/net.rs)147
-rw-r--r--common/sys_util/src/netlink.rs501
-rw-r--r--common/sys_util/src/poll.rs (renamed from sys_util/src/poll.rs)36
-rw-r--r--common/sys_util/src/priority.rs38
-rw-r--r--common/sys_util/src/rand.rs (renamed from sys_util/src/rand.rs)8
-rw-r--r--common/sys_util/src/raw_fd.rs16
-rw-r--r--common/sys_util/src/read_dir.rs148
-rw-r--r--common/sys_util/src/sched.rs143
-rw-r--r--common/sys_util/src/scoped_path.rs138
-rw-r--r--common/sys_util/src/scoped_signal_handler.rs417
-rw-r--r--common/sys_util/src/shm.rs (renamed from sys_util/src/shm.rs)50
-rw-r--r--common/sys_util/src/signal.rs695
-rw-r--r--common/sys_util/src/signalfd.rs183
-rw-r--r--common/sys_util/src/sock_ctrl_msg.rs546
-rw-r--r--common/sys_util/src/syslog.rs (renamed from sys_util/src/syslog.rs)79
-rw-r--r--common/sys_util/src/terminal.rs94
-rw-r--r--common/sys_util/src/timerfd.rs296
-rw-r--r--common/sys_util/src/vsock.rs497
-rw-r--r--common/sys_util/src/write_zeroes.rs216
-rw-r--r--common/sys_util_core/Android.bp (renamed from syscall_defines/Android.bp)43
-rw-r--r--common/sys_util_core/Cargo.toml17
-rw-r--r--common/sys_util_core/poll_token_derive/Android.bp26
-rw-r--r--common/sys_util_core/poll_token_derive/Cargo.toml (renamed from sys_util/poll_token_derive/Cargo.toml)2
-rw-r--r--common/sys_util_core/poll_token_derive/cargo2android.json7
-rw-r--r--common/sys_util_core/poll_token_derive/poll_token_derive.rs172
-rw-r--r--common/sys_util_core/poll_token_derive/tests.rs65
-rw-r--r--common/sys_util_core/src/alloc.rs215
-rw-r--r--common/sys_util_core/src/errno.rs75
-rw-r--r--common/sys_util_core/src/external_mapping.rs133
-rw-r--r--common/sys_util_core/src/lib.rs24
-rw-r--r--common/sys_util_core/src/scoped_event_macro.rs52
-rw-r--r--cros_async/Android.bp98
-rw-r--r--cros_async/Cargo.toml19
-rw-r--r--cros_async/DEPRECATED.md4
-rw-r--r--cros_async/cargo2android.json8
-rw-r--r--cros_async/src/async_types.rs71
-rw-r--r--cros_async/src/audio_streams_async.rs68
-rw-r--r--cros_async/src/blocking.rs9
-rw-r--r--cros_async/src/blocking/block_on.rs202
-rw-r--r--cros_async/src/blocking/pool.rs524
-rw-r--r--cros_async/src/complete.rs8
-rw-r--r--cros_async/src/event.rs20
-rw-r--r--cros_async/src/executor.rs69
-rw-r--r--cros_async/src/fd_executor.rs102
-rw-r--r--cros_async/src/io_ext.rs161
-rw-r--r--cros_async/src/lib.rs67
-rw-r--r--cros_async/src/mem.rs29
-rw-r--r--cros_async/src/poll_source.rs181
-rw-r--r--cros_async/src/queue.rs6
-rw-r--r--cros_async/src/select.rs13
-rw-r--r--cros_async/src/sync.rs2
-rw-r--r--cros_async/src/sync/cv.rs74
-rw-r--r--cros_async/src/sync/mu.rs70
-rw-r--r--cros_async/src/sync/spin.rs23
-rw-r--r--cros_async/src/sync/waiter.rs33
-rw-r--r--cros_async/src/timer.rs51
-rw-r--r--cros_async/src/unix/async_types.rs42
-rw-r--r--cros_async/src/uring_executor.rs214
-rw-r--r--cros_async/src/uring_source.rs195
-rw-r--r--cros_async/src/waker.rs8
-rw-r--r--cros_async/src/win/async_types.rs58
-rw-r--r--crosvm-fuzz/.gitignore (renamed from fuzz/.gitignore)0
-rw-r--r--crosvm-fuzz/Cargo.toml (renamed from fuzz/Cargo.toml)7
-rw-r--r--crosvm-fuzz/OWNERS1
-rw-r--r--crosvm-fuzz/block_fuzzer.rs (renamed from fuzz/block_fuzzer.rs)7
-rw-r--r--crosvm-fuzz/cargo2android.json1
-rw-r--r--crosvm-fuzz/fs_server_fuzzer.rs51
-rw-r--r--crosvm-fuzz/qcow_fuzzer.rs (renamed from fuzz/qcow_fuzzer.rs)12
-rw-r--r--crosvm-fuzz/usb_descriptor_fuzzer.rs (renamed from fuzz/usb_descriptor_fuzzer.rs)0
-rw-r--r--crosvm-fuzz/virtqueue_fuzzer.rs (renamed from fuzz/virtqueue_fuzzer.rs)0
-rw-r--r--crosvm-fuzz/zimage_fuzzer.rs (renamed from fuzz/zimage_fuzzer.rs)1
-rw-r--r--crosvm_control/Android.bp28
-rw-r--r--crosvm_control/Cargo.toml (renamed from libcrosvm_control/Cargo.toml)4
-rw-r--r--crosvm_control/src/lib.rs (renamed from libcrosvm_control/src/lib.rs)9
-rw-r--r--crosvm_plugin/Android.bp63
-rw-r--r--crosvm_plugin/Cargo.toml2
-rw-r--r--crosvm_plugin/cargo2android.json1
-rw-r--r--crosvm_plugin/crosvm.h5
-rw-r--r--crosvm_plugin/src/lib.rs41
-rw-r--r--cuttlefish/Android.bp8
-rw-r--r--devices/.build_test_serial0
-rw-r--r--devices/Android.bp218
-rw-r--r--devices/Cargo.toml45
-rw-r--r--devices/TEST_MAPPING4
-rw-r--r--devices/cargo2android.json11
-rw-r--r--devices/cargo2android_libs.bp13
-rw-r--r--devices/patches/Android.bp.patch21
-rw-r--r--devices/src/acpi.rs859
-rw-r--r--devices/src/bat.rs54
-rw-r--r--devices/src/bus.rs236
-rw-r--r--devices/src/direct_io.rs133
-rw-r--r--devices/src/direct_irq.rs134
-rw-r--r--devices/src/irq_event.rs120
-rw-r--r--devices/src/irqchip/aarch64.rs16
-rw-r--r--devices/src/irqchip/ioapic.rs63
-rw-r--r--devices/src/irqchip/kvm/aarch64.rs46
-rw-r--r--devices/src/irqchip/kvm/mod.rs54
-rw-r--r--devices/src/irqchip/kvm/x86_64.rs321
-rw-r--r--devices/src/irqchip/mod.rs36
-rw-r--r--devices/src/irqchip/pic.rs35
-rw-r--r--devices/src/irqchip/x86_64.rs9
-rw-r--r--devices/src/lib.rs80
-rw-r--r--devices/src/pci/ac97.rs171
-rw-r--r--devices/src/pci/ac97_bus_master.rs124
-rw-r--r--devices/src/pci/coiommu.rs1615
-rw-r--r--devices/src/pci/mod.rs48
-rw-r--r--devices/src/pci/msix.rs227
-rw-r--r--devices/src/pci/pci_address.rs313
-rw-r--r--devices/src/pci/pci_configuration.rs433
-rw-r--r--devices/src/pci/pci_device.rs441
-rw-r--r--devices/src/pci/pci_root.rs335
-rw-r--r--devices/src/pci/pcie/mod.rs85
-rw-r--r--devices/src/pci/pcie/pci_bridge.rs528
-rw-r--r--devices/src/pci/pcie/pcie_device.rs267
-rw-r--r--devices/src/pci/pcie/pcie_host.rs462
-rw-r--r--devices/src/pci/pcie/pcie_rp.rs530
-rw-r--r--devices/src/pci/pvpanic.rs245
-rw-r--r--devices/src/pci/stub.rs187
-rw-r--r--devices/src/pci/vfio_pci.rs1648
-rw-r--r--devices/src/pit.rs46
-rw-r--r--devices/src/pl030.rs16
-rw-r--r--devices/src/platform/mod.rs9
-rw-r--r--devices/src/platform/vfio_platform.rs286
-rw-r--r--devices/src/proxy.rs99
-rw-r--r--devices/src/register_space/register.rs2
-rw-r--r--devices/src/serial.rs34
-rw-r--r--devices/src/serial_device.rs318
-rw-r--r--devices/src/software_tpm.rs33
-rw-r--r--devices/src/sys.rs13
-rw-r--r--devices/src/sys/unix.rs5
-rw-r--r--devices/src/sys/unix/serial_device.rs195
-rw-r--r--devices/src/sys/windows.rs5
-rw-r--r--devices/src/sys/windows/serial_device.rs80
-rw-r--r--devices/src/usb/host_backend/error.rs109
-rw-r--r--devices/src/usb/host_backend/host_backend_device_provider.rs27
-rw-r--r--devices/src/usb/host_backend/host_device.rs215
-rw-r--r--devices/src/usb/xhci/command_ring_controller.rs59
-rw-r--r--devices/src/usb/xhci/device_slot.rs57
-rw-r--r--devices/src/usb/xhci/event_ring.rs69
-rw-r--r--devices/src/usb/xhci/interrupter.rs30
-rw-r--r--devices/src/usb/xhci/intr_resample_handler.rs20
-rw-r--r--devices/src/usb/xhci/ring_buffer.rs45
-rw-r--r--devices/src/usb/xhci/ring_buffer_controller.rs58
-rw-r--r--devices/src/usb/xhci/ring_buffer_stop_cb.rs4
-rw-r--r--devices/src/usb/xhci/scatter_gather_buffer.rs45
-rw-r--r--devices/src/usb/xhci/transfer_ring_controller.rs12
-rw-r--r--devices/src/usb/xhci/usb_hub.rs42
-rw-r--r--devices/src/usb/xhci/xhci.rs73
-rw-r--r--devices/src/usb/xhci/xhci_abi.rs20
-rw-r--r--devices/src/usb/xhci/xhci_controller.rs69
-rw-r--r--devices/src/usb/xhci/xhci_transfer.rs57
-rw-r--r--devices/src/utils/async_job_queue.rs16
-rw-r--r--devices/src/utils/error.rs41
-rw-r--r--devices/src/utils/event_loop.rs89
-rw-r--r--devices/src/vfio.rs986
-rw-r--r--devices/src/virtio/async_utils.rs51
-rw-r--r--devices/src/virtio/balloon.rs467
-rw-r--r--devices/src/virtio/block/asynchronous.rs (renamed from devices/src/virtio/block_async.rs)359
-rw-r--r--devices/src/virtio/block/block.rs (renamed from devices/src/virtio/block.rs)482
-rw-r--r--devices/src/virtio/block/common.rs161
-rw-r--r--devices/src/virtio/block/mod.rs12
-rw-r--r--devices/src/virtio/block/sys/mod.rs13
-rw-r--r--devices/src/virtio/block/sys/unix.rs15
-rw-r--r--devices/src/virtio/block/sys/windows.rs8
-rw-r--r--devices/src/virtio/console.rs488
-rw-r--r--devices/src/virtio/descriptor_utils.rs306
-rw-r--r--devices/src/virtio/fs/mod.rs99
-rw-r--r--devices/src/virtio/fs/passthrough.rs775
-rw-r--r--devices/src/virtio/fs/worker.rs87
-rwxr-xr-xdevices/src/virtio/gpu/bindgen.sh18
-rw-r--r--devices/src/virtio/gpu/mod.rs513
-rw-r--r--devices/src/virtio/gpu/protocol.rs95
-rw-r--r--devices/src/virtio/gpu/udmabuf.rs64
-rw-r--r--devices/src/virtio/gpu/udmabuf_bindings.rs51
-rw-r--r--devices/src/virtio/gpu/virtio_gpu.rs639
-rw-r--r--devices/src/virtio/input/defaults.rs48
-rw-r--r--devices/src/virtio/input/evdev.rs10
-rw-r--r--devices/src/virtio/input/event_source.rs12
-rw-r--r--devices/src/virtio/input/mod.rs111
-rw-r--r--devices/src/virtio/interrupt.rs65
-rw-r--r--devices/src/virtio/iommu.rs976
-rw-r--r--devices/src/virtio/iommu/ipc_memory_mapper.rs145
-rw-r--r--devices/src/virtio/iommu/memory_mapper.rs729
-rw-r--r--devices/src/virtio/iommu/memory_util.rs137
-rw-r--r--devices/src/virtio/iommu/protocol.rs174
-rw-r--r--devices/src/virtio/iommu/vfio_wrapper.rs99
-rw-r--r--devices/src/virtio/mod.rs31
-rw-r--r--devices/src/virtio/net.rs515
-rw-r--r--devices/src/virtio/p9.rs71
-rw-r--r--devices/src/virtio/pmem.rs280
-rw-r--r--devices/src/virtio/queue.rs299
-rw-r--r--devices/src/virtio/resource_bridge.rs32
-rw-r--r--devices/src/virtio/rng.rs19
-rw-r--r--devices/src/virtio/snd/common.rs112
-rw-r--r--devices/src/virtio/snd/constants.rs146
-rw-r--r--devices/src/virtio/snd/cras_backend/async_funcs.rs767
-rw-r--r--devices/src/virtio/snd/cras_backend/mod.rs843
-rw-r--r--devices/src/virtio/snd/layout.rs12
-rw-r--r--devices/src/virtio/snd/mod.rs8
-rw-r--r--devices/src/virtio/snd/vios_backend/mod.rs216
-rw-r--r--devices/src/virtio/snd/vios_backend/shm_streams.rs249
-rw-r--r--devices/src/virtio/snd/vios_backend/shm_vios.rs865
-rw-r--r--devices/src/virtio/snd/vios_backend/streams.rs494
-rw-r--r--devices/src/virtio/snd/vios_backend/worker.rs607
-rw-r--r--devices/src/virtio/tpm.rs120
-rw-r--r--devices/src/virtio/vhost/mod.rs72
-rw-r--r--devices/src/virtio/vhost/net.rs59
-rw-r--r--devices/src/virtio/vhost/user/README.md30
-rw-r--r--devices/src/virtio/vhost/user/device/block.rs303
-rw-r--r--devices/src/virtio/vhost/user/device/console.rs352
-rw-r--r--devices/src/virtio/vhost/user/device/cras_snd.rs289
-rw-r--r--devices/src/virtio/vhost/user/device/fs.rs334
-rw-r--r--devices/src/virtio/vhost/user/device/gpu.rs460
-rw-r--r--devices/src/virtio/vhost/user/device/handler.rs1063
-rw-r--r--devices/src/virtio/vhost/user/device/mod.rs29
-rw-r--r--devices/src/virtio/vhost/user/device/net.rs181
-rw-r--r--devices/src/virtio/vhost/user/device/unix/net.rs373
-rw-r--r--devices/src/virtio/vhost/user/device/vsock.rs665
-rw-r--r--devices/src/virtio/vhost/user/device/vvu.rs13
-rw-r--r--devices/src/virtio/vhost/user/device/vvu/bus.rs62
-rw-r--r--devices/src/virtio/vhost/user/device/vvu/device.rs254
-rw-r--r--devices/src/virtio/vhost/user/device/vvu/doorbell.rs33
-rw-r--r--devices/src/virtio/vhost/user/device/vvu/pci.rs516
-rw-r--r--devices/src/virtio/vhost/user/device/vvu/queue.rs586
-rw-r--r--devices/src/virtio/vhost/user/device/windows/net.rs288
-rw-r--r--devices/src/virtio/vhost/user/device/wl.rs367
-rw-r--r--devices/src/virtio/vhost/user/mod.rs101
-rw-r--r--devices/src/virtio/vhost/user/proxy.rs1420
-rw-r--r--devices/src/virtio/vhost/user/vmm/block.rs (renamed from devices/src/virtio/vhost/user/block.rs)23
-rw-r--r--devices/src/virtio/vhost/user/vmm/console.rs154
-rw-r--r--devices/src/virtio/vhost/user/vmm/fs.rs (renamed from devices/src/virtio/vhost/user/fs.rs)27
-rw-r--r--devices/src/virtio/vhost/user/vmm/gpu.rs259
-rw-r--r--devices/src/virtio/vhost/user/vmm/handler.rs (renamed from devices/src/virtio/vhost/user/handler.rs)128
-rw-r--r--devices/src/virtio/vhost/user/vmm/mac80211_hwsim.rs166
-rw-r--r--devices/src/virtio/vhost/user/vmm/mod.rs127
-rw-r--r--devices/src/virtio/vhost/user/vmm/net.rs (renamed from devices/src/virtio/vhost/user/net.rs)32
-rw-r--r--devices/src/virtio/vhost/user/vmm/snd.rs153
-rw-r--r--devices/src/virtio/vhost/user/vmm/vsock.rs160
-rw-r--r--devices/src/virtio/vhost/user/vmm/wl.rs154
-rw-r--r--devices/src/virtio/vhost/user/vmm/worker.rs44
-rw-r--r--devices/src/virtio/vhost/user/worker.rs82
-rw-r--r--devices/src/virtio/vhost/vsock.rs210
-rw-r--r--devices/src/virtio/vhost/worker.rs15
-rw-r--r--devices/src/virtio/video/async_cmd_desc_map.rs10
-rw-r--r--devices/src/virtio/video/command.rs153
-rw-r--r--devices/src/virtio/video/control.rs26
-rw-r--r--devices/src/virtio/video/decoder/backend/mod.rs89
-rw-r--r--devices/src/virtio/video/decoder/backend/utils.rs282
-rw-r--r--devices/src/virtio/video/decoder/backend/vda.rs246
-rw-r--r--devices/src/virtio/video/decoder/capability.rs116
-rw-r--r--devices/src/virtio/video/decoder/mod.rs497
-rw-r--r--devices/src/virtio/video/device.rs4
-rw-r--r--devices/src/virtio/video/encoder/backend/mod.rs72
-rw-r--r--devices/src/virtio/video/encoder/backend/vda.rs (renamed from devices/src/virtio/video/encoder/libvda_encoder.rs)175
-rw-r--r--devices/src/virtio/video/encoder/encoder.rs121
-rw-r--r--devices/src/virtio/video/encoder/mod.rs720
-rw-r--r--devices/src/virtio/video/error.rs73
-rw-r--r--devices/src/virtio/video/event.rs1
-rw-r--r--devices/src/virtio/video/format.rs115
-rw-r--r--devices/src/virtio/video/mod.rs295
-rw-r--r--devices/src/virtio/video/params.rs40
-rw-r--r--devices/src/virtio/video/protocol.rs81
-rw-r--r--devices/src/virtio/video/resource.rs402
-rw-r--r--devices/src/virtio/video/response.rs16
-rw-r--r--devices/src/virtio/video/vda.rs65
-rw-r--r--devices/src/virtio/video/worker.rs186
-rw-r--r--devices/src/virtio/virtio_device.rs35
-rw-r--r--devices/src/virtio/virtio_pci_common_config.rs2
-rw-r--r--devices/src/virtio/virtio_pci_device.rs484
-rw-r--r--devices/src/virtio/wl.rs504
-rw-r--r--disk/Android.bp92
-rw-r--r--disk/Cargo.toml13
-rw-r--r--disk/cargo2android.json8
-rw-r--r--disk/patches/Android.bp.patch26
-rw-r--r--disk/src/android_sparse.rs21
-rw-r--r--disk/src/composite.rs564
-rw-r--r--disk/src/disk.rs237
-rw-r--r--disk/src/gpt.rs275
-rw-r--r--disk/src/qcow/mod.rs814
-rw-r--r--disk/src/qcow/refcount.rs68
-rw-r--r--disk/src/qcow/vec_cache.rs2
-rw-r--r--docs/architecture.md82
-rw-r--r--docs/book/.gitignore1
-rw-r--r--docs/book/book.toml16
-rw-r--r--docs/book/mermaid-init.js1
-rw-r--r--docs/book/mermaid.min.js32
-rw-r--r--docs/book/src/SUMMARY.md27
-rw-r--r--docs/book/src/api.md4
-rw-r--r--docs/book/src/appendix/index.md4
-rw-r--r--docs/book/src/appendix/minijail.md25
-rw-r--r--docs/book/src/appendix/rust-vmm.md (renamed from docs/rust-vmm.md)8
-rw-r--r--docs/book/src/appendix/sandboxing.md41
-rw-r--r--docs/book/src/appendix/seccomp.md30
-rw-r--r--docs/book/src/architecture.md1
-rw-r--r--docs/book/src/building_crosvm/chromium_os.md27
-rw-r--r--docs/book/src/building_crosvm/index.md3
-rw-r--r--docs/book/src/building_crosvm/linux.md190
-rw-r--r--docs/book/src/contributing.md1
-rw-r--r--docs/book/src/introduction.md22
-rw-r--r--docs/book/src/logo.svg10
-rw-r--r--docs/book/src/onboarding.md85
-rw-r--r--docs/book/src/running_crosvm/advanced_usage.md261
-rw-r--r--docs/book/src/running_crosvm/custom_kernel_rootfs.md67
-rw-r--r--docs/book/src/running_crosvm/devices.md55
-rw-r--r--docs/book/src/running_crosvm/example_desktop.pngbin0 -> 160378 bytes
-rw-r--r--docs/book/src/running_crosvm/example_usage.md121
-rw-r--r--docs/book/src/running_crosvm/features.md90
-rw-r--r--docs/book/src/running_crosvm/index.md12
-rw-r--r--docs/book/src/running_crosvm/requirements.md10
-rw-r--r--enumn/Android.bp48
-rw-r--r--enumn/src/lib.rs207
-rw-r--r--enumn/src/tests.rs71
-rw-r--r--fuse/Android.bp85
-rw-r--r--fuse/Cargo.toml8
-rw-r--r--fuse/src/lib.rs87
-rw-r--r--fuse/src/mount.rs13
-rw-r--r--fuse/src/server.rs37
-rw-r--r--fuse/src/sys.rs31
-rw-r--r--fuse/src/worker.rs127
-rw-r--r--fuzz/OWNERS1
-rw-r--r--fuzz/fs_server_fuzzer.rs48
-rw-r--r--gpu_display/Android.bp106
-rw-r--r--gpu_display/Cargo.toml9
-rw-r--r--gpu_display/build.rs27
-rw-r--r--gpu_display/cargo2android.json8
-rw-r--r--gpu_display/examples/simple.rs8
-rw-r--r--gpu_display/examples/simple_open.rs8
-rw-r--r--gpu_display/patches/Android.bp.patch121
-rw-r--r--gpu_display/protocol/virtio-gpu-metadata-v1.xml75
-rw-r--r--gpu_display/protocol/xdg-shell-unstable-v6.xml1044
-rw-r--r--gpu_display/src/display_wl.c680
-rw-r--r--gpu_display/src/dwl.rs53
-rw-r--r--gpu_display/src/event_device.rs108
-rw-r--r--gpu_display/src/generated/xlib.rs2
-rwxr-xr-xgpu_display/src/generated/xlib_generator.sh134
-rw-r--r--gpu_display/src/gpu_display_stub.rs151
-rw-r--r--gpu_display/src/gpu_display_wl.rs409
-rw-r--r--gpu_display/src/gpu_display_x.rs586
-rw-r--r--gpu_display/src/keycode_converter/mod.rs3
-rw-r--r--gpu_display/src/lib.rs525
-rw-r--r--hypervisor/Android.bp83
-rw-r--r--hypervisor/Cargo.toml9
-rw-r--r--hypervisor/TEST_MAPPING4
-rw-r--r--hypervisor/cargo2android.json8
-rw-r--r--hypervisor/src/aarch64.rs46
-rw-r--r--hypervisor/src/caps.rs3
-rw-r--r--hypervisor/src/kvm/aarch64.rs205
-rw-r--r--hypervisor/src/kvm/mod.rs131
-rw-r--r--hypervisor/src/kvm/x86_64.rs145
-rw-r--r--hypervisor/src/lib.rs39
-rw-r--r--hypervisor/src/x86_64.rs15
-rw-r--r--infra/config/generated/cr-buildbucket.cfg15
-rw-r--r--infra/config/generated/luci-logdog.cfg7
-rw-r--r--infra/config/generated/project.cfg13
-rw-r--r--infra/config/generated/realms.cfg44
-rwxr-xr-xinfra/config/main.star66
-rw-r--r--integration_tests/Cargo.toml4
-rw-r--r--integration_tests/README.md25
-rw-r--r--integration_tests/cargo2android.json1
-rw-r--r--integration_tests/tests/boot.rs10
-rw-r--r--integration_tests/tests/fixture.rs44
-rw-r--r--io_uring/Android.bp57
-rw-r--r--io_uring/Cargo.toml18
-rw-r--r--io_uring/DEPRECATED.md4
-rwxr-xr-xio_uring/bindgen.sh19
-rw-r--r--io_uring/cargo2android.json8
-rw-r--r--io_uring/src/bindings.rs567
-rw-r--r--io_uring/src/uring.rs199
-rw-r--r--kernel_cmdline/Android.bp38
-rw-r--r--kernel_cmdline/Cargo.toml4
-rw-r--r--kernel_cmdline/src/kernel_cmdline.rs40
-rw-r--r--kernel_loader/Android.bp88
-rw-r--r--kernel_loader/Cargo.toml9
-rwxr-xr-xkernel_loader/bindgen.sh21
-rw-r--r--kernel_loader/src/elf.rs47
-rw-r--r--kernel_loader/src/lib.rs93
-rw-r--r--kvm/Android.bp118
-rw-r--r--kvm/Cargo.toml6
-rw-r--r--kvm/TEST_MAPPING16
-rw-r--r--kvm/cargo2android.json9
-rw-r--r--kvm/src/cap.rs1
-rw-r--r--kvm/src/lib.rs97
-rw-r--r--kvm/tests/real_run_adder.rs10
-rw-r--r--kvm_sys/Android.bp97
-rw-r--r--kvm_sys/Cargo.toml4
-rw-r--r--kvm_sys/TEST_MAPPING8
-rwxr-xr-xkvm_sys/bindgen.sh46
-rw-r--r--kvm_sys/cargo2android.json10
-rw-r--r--kvm_sys/src/aarch64/bindings.rs7667
-rw-r--r--kvm_sys/src/lib.rs26
-rw-r--r--kvm_sys/src/x86/bindings.rs10367
-rw-r--r--kvm_sys/tests/basic.rs (renamed from kvm_sys/tests/sanity.rs)4
-rw-r--r--libcras_stub/Android.bp22
-rw-r--r--libcras_stub/Cargo.toml (renamed from rand_ish/Cargo.toml)7
-rw-r--r--libcras_stub/README.md11
-rw-r--r--libcras_stub/src/libcras.rs1
-rw-r--r--libcrosvm_control/Android.bp108
-rw-r--r--linux_input_sys/Android.bp74
-rw-r--r--linux_input_sys/Cargo.toml4
-rw-r--r--linux_input_sys/src/lib.rs105
-rw-r--r--logo/logo.svg10
-rw-r--r--logo/logo_512.pngbin0 -> 47527 bytes
-rw-r--r--media/libvda/Android.bp1
-rw-r--r--media/libvda/Cargo.toml13
-rw-r--r--media/libvda/README.md31
-rwxr-xr-xmedia/libvda/bindgen.sh46
-rw-r--r--media/libvda/build.rs18
-rw-r--r--media/libvda/cargo2android.json1
-rw-r--r--media/libvda/src/bindings.rs66
-rw-r--r--media/libvda/src/decode/bindings.rs205
-rw-r--r--media/libvda/src/decode/event.rs138
-rw-r--r--media/libvda/src/decode/format.rs39
-rw-r--r--media/libvda/src/decode/mod.rs14
-rw-r--r--media/libvda/src/decode/session.rs177
-rw-r--r--media/libvda/src/decode/vda_instance.rs113
-rw-r--r--media/libvda/src/encode/bindings.rs230
-rw-r--r--media/libvda/src/encode/event.rs110
-rw-r--r--media/libvda/src/encode/format.rs66
-rw-r--r--media/libvda/src/encode/mod.rs14
-rw-r--r--media/libvda/src/encode/session.rs186
-rw-r--r--media/libvda/src/encode/vea_instance.rs156
-rw-r--r--media/libvda/src/error.rs57
-rw-r--r--media/libvda/src/format.rs123
-rw-r--r--media/libvda/src/lib.rs13
-rw-r--r--media/libvda/tests/decode_tests.rs58
-rw-r--r--media/libvda/tests/encode_tests.rs115
-rw-r--r--navbar.md10
-rw-r--r--net_sys/Android.bp73
-rw-r--r--net_sys/Cargo.toml3
-rwxr-xr-xnet_sys/bindgen.sh55
-rw-r--r--net_sys/src/if_tun.rs611
-rw-r--r--net_sys/src/iff.rs1252
-rw-r--r--net_sys/src/inn.rs843
-rw-r--r--net_sys/src/lib.rs26
-rw-r--r--net_sys/src/sockios.rs173
-rw-r--r--net_util/Android.bp78
-rw-r--r--net_util/Cargo.toml7
-rw-r--r--net_util/TEST_MAPPING4
-rw-r--r--net_util/cargo2android.json8
-rw-r--r--net_util/src/lib.rs235
-rw-r--r--patches/Android.bp.patch38
-rw-r--r--power_monitor/Android.bp74
-rw-r--r--power_monitor/Cargo.toml6
-rw-r--r--power_monitor/build.rs13
-rw-r--r--power_monitor/src/powerd/mod.rs56
-rw-r--r--protos/Android.bp33
-rw-r--r--protos/Cargo.toml3
-rw-r--r--protos/build.rs22
-rw-r--r--protos/cargo2android.json9
-rw-r--r--protos/cargo2android_protobuf.bp19
-rw-r--r--protos/patches/Android.bp.patch14
-rw-r--r--protos/src/lib.rs3
-rw-r--r--protos/tests/trunks.rs24
-rw-r--r--qcow_utils/Android.bp3
-rw-r--r--qcow_utils/Cargo.toml7
-rw-r--r--qcow_utils/cargo2android.json1
-rw-r--r--qcow_utils/src/qcow_img.rs322
-rw-r--r--qcow_utils/src/qcow_utils.rs6
-rw-r--r--rand_ish/src/lib.rs85
-rw-r--r--resources/Android.bp75
-rw-r--r--resources/Cargo.toml4
-rw-r--r--resources/src/address_allocator.rs325
-rw-r--r--resources/src/lib.rs66
-rw-r--r--resources/src/system_allocator.rs370
-rwxr-xr-xrun_c2a.sh40
-rwxr-xr-xrun_tests92
-rw-r--r--[l---------]rust-toolchain4
-rw-r--r--rutabaga_gfx/Android.bp104
-rw-r--r--rutabaga_gfx/Cargo.toml13
-rw-r--r--rutabaga_gfx/TEST_MAPPING4
-rw-r--r--rutabaga_gfx/build.rs172
-rw-r--r--rutabaga_gfx/cargo2android.json9
-rw-r--r--rutabaga_gfx/ffi/Android.bp32
-rw-r--r--rutabaga_gfx/ffi/Cargo.toml28
-rw-r--r--rutabaga_gfx/ffi/src/.clang-format15
-rw-r--r--rutabaga_gfx/ffi/src/include/rutabaga_gfx_ffi.h258
-rw-r--r--rutabaga_gfx/ffi/src/lib.rs495
-rw-r--r--rutabaga_gfx/ffi/src/tests/Makefile24
-rw-r--r--rutabaga_gfx/ffi/src/tests/rutabaga_test.c400
-rw-r--r--rutabaga_gfx/ffi/src/tests/virtgpu_cross_domain_protocol.h111
-rw-r--r--rutabaga_gfx/patches/Android.bp.patch72
-rw-r--r--rutabaga_gfx/src/cross_domain/cross_domain.rs934
-rw-r--r--rutabaga_gfx/src/cross_domain/cross_domain_protocol.rs58
-rwxr-xr-xrutabaga_gfx/src/generated/generate.py47
-rw-r--r--rutabaga_gfx/src/generated/mod.rs1
-rw-r--r--rutabaga_gfx/src/generated/virgl_debug_callback_bindings.rs58
-rw-r--r--rutabaga_gfx/src/generated/virgl_renderer_bindings.rs76
-rw-r--r--rutabaga_gfx/src/gfxstream.rs176
-rw-r--r--rutabaga_gfx/src/renderer_utils.rs107
-rw-r--r--rutabaga_gfx/src/rutabaga_2d.rs68
-rw-r--r--rutabaga_gfx/src/rutabaga_core.rs213
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/Cargo.toml2
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/TEST_MAPPING4
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/cargo2android.json1
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/formats.rs60
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/gralloc.rs21
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/minigbm.rs12
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/minigbm_bindings.rs3
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/mod.rs2
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/rendernode.rs6
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/system_gralloc.rs2
-rw-r--r--rutabaga_gfx/src/rutabaga_gralloc/vulkano_gralloc.rs252
-rw-r--r--rutabaga_gfx/src/rutabaga_utils.rs268
-rw-r--r--rutabaga_gfx/src/virgl_renderer.rs180
-rw-r--r--seccomp/Android.bp140
-rw-r--r--seccomp/aarch64/block_device.policy1
-rw-r--r--seccomp/aarch64/coiommu.policy11
-rw-r--r--seccomp/aarch64/common_device.policy5
-rw-r--r--seccomp/aarch64/cras_audio_device.policy5
-rw-r--r--seccomp/aarch64/cras_snd_device.policy17
-rw-r--r--seccomp/aarch64/fs_device.policy9
-rw-r--r--seccomp/aarch64/gpu_common.policy83
-rw-r--r--seccomp/aarch64/gpu_device.policy81
-rw-r--r--seccomp/aarch64/gpu_render_server.policy18
-rw-r--r--seccomp/aarch64/null_audio_device.policy1
-rw-r--r--seccomp/aarch64/tpm_device.policy44
-rw-r--r--seccomp/aarch64/vios_audio_device.policy1
-rw-r--r--seccomp/aarch64/wl_device.policy4
-rw-r--r--seccomp/aarch64/xhci.policy5
-rw-r--r--seccomp/arm/9p_device.policy1
-rw-r--r--seccomp/arm/block_device.policy1
-rw-r--r--seccomp/arm/coiommu.policy11
-rw-r--r--seccomp/arm/common_device.policy5
-rw-r--r--seccomp/arm/cras_audio_device.policy7
-rw-r--r--seccomp/arm/cras_snd_device.policy20
-rw-r--r--seccomp/arm/fs_device.policy10
-rw-r--r--seccomp/arm/gpu_common.policy105
-rw-r--r--seccomp/arm/gpu_device.policy93
-rw-r--r--seccomp/arm/gpu_render_server.policy20
-rw-r--r--seccomp/arm/tpm_device.policy51
-rw-r--r--seccomp/arm/video_device.policy5
-rw-r--r--seccomp/arm/wl_device.policy4
-rw-r--r--seccomp/arm/xhci.policy5
-rw-r--r--seccomp/crosvm_seccomp_policy_product_packages.mk6
-rwxr-xr-xseccomp/gen_android.sh34
-rwxr-xr-xseccomp/policy-inliner.sh12
-rw-r--r--seccomp/x86_64/battery.policy2
-rw-r--r--seccomp/x86_64/block_device.policy1
-rw-r--r--seccomp/x86_64/coiommu.policy11
-rw-r--r--seccomp/x86_64/common_device.policy6
-rw-r--r--seccomp/x86_64/cras_audio_device.policy5
-rw-r--r--seccomp/x86_64/cras_snd_device.policy18
-rw-r--r--seccomp/x86_64/fs_device.policy9
-rw-r--r--seccomp/x86_64/gpu_common.policy104
-rw-r--r--seccomp/x86_64/gpu_device.policy102
-rw-r--r--seccomp/x86_64/gpu_render_server.policy18
-rw-r--r--seccomp/x86_64/iommu_device.policy13
-rw-r--r--seccomp/x86_64/tpm_device.policy45
-rw-r--r--seccomp/x86_64/vfio_device.policy1
-rw-r--r--seccomp/x86_64/video_device.policy58
-rw-r--r--seccomp/x86_64/vios_audio_device.policy1
-rw-r--r--seccomp/x86_64/vvu_proxy_device.policy14
-rw-r--r--seccomp/x86_64/wl_device.policy4
-rw-r--r--seccomp/x86_64/xhci.policy7
-rw-r--r--serde_keyvalue/Android.bp (renamed from rand_ish/Android.bp)41
-rw-r--r--serde_keyvalue/Cargo.toml19
-rw-r--r--serde_keyvalue/README.md19
-rw-r--r--serde_keyvalue/serde_keyvalue_derive/Android.bp27
-rw-r--r--serde_keyvalue/serde_keyvalue_derive/Cargo.toml13
-rw-r--r--serde_keyvalue/serde_keyvalue_derive/src/lib.rs24
-rw-r--r--serde_keyvalue/src/key_values.rs1048
-rw-r--r--serde_keyvalue/src/lib.rs295
-rwxr-xr-xsetup_cros_cargo.sh (renamed from ci/vm_tools/wait_for_vm_with_timeout)8
-rw-r--r--src/argument.rs285
-rw-r--r--src/crosvm.rs351
-rw-r--r--src/gdb.rs40
-rw-r--r--src/linux.rs3250
-rw-r--r--src/linux/android.rs41
-rw-r--r--src/linux/device_helpers.rs1250
-rw-r--r--src/linux/gpu.rs332
-rw-r--r--src/linux/jail_helpers.rs205
-rw-r--r--src/linux/mod.rs2330
-rw-r--r--src/linux/vcpu.rs744
-rw-r--r--src/main.rs2351
-rw-r--r--src/plugin/mod.rs382
-rw-r--r--src/plugin/process.rs120
-rw-r--r--src/plugin/vcpu.rs16
-rw-r--r--sys_util/.build_test_serial0
-rw-r--r--sys_util/Android.bp105
-rw-r--r--sys_util/src/fork.rs150
-rw-r--r--sys_util/src/passwd.rs122
-rw-r--r--sys_util/src/sched.rs80
-rw-r--r--sys_util/src/seek_hole.rs196
-rw-r--r--sys_util/src/struct_util.rs149
-rw-r--r--syscall_defines/src/lib.rs19
-rw-r--r--syscall_defines/src/linux-aarch64/mod.rs333
-rw-r--r--syscall_defines/src/linux-arm/mod.rs410
-rw-r--r--syscall_defines/src/linux-x86/mod.rs437
-rw-r--r--syscall_defines/src/linux-x86_64/mod.rs395
-rw-r--r--system_api_stub/Android.bp22
-rw-r--r--system_api_stub/Cargo.toml8
-rw-r--r--system_api_stub/README.md11
-rw-r--r--system_api_stub/src/system_api.rs1
-rw-r--r--tempfile/Cargo.toml8
-rw-r--r--tempfile/src/lib.rs265
-rwxr-xr-xtest_all6
-rw-r--r--tests/plugin_net_config.c3
-rw-r--r--tests/plugins.rs41
-rw-r--r--third_party/vmm_vhost/.buildkite/pipeline.yml17
-rw-r--r--third_party/vmm_vhost/.cargo/config5
-rw-r--r--third_party/vmm_vhost/.github/dependabot.yml7
-rw-r--r--third_party/vmm_vhost/.gitignore6
-rw-r--r--third_party/vmm_vhost/.gitmodules3
-rw-r--r--third_party/vmm_vhost/Android.bp100
-rw-r--r--third_party/vmm_vhost/CODEOWNERS2
-rw-r--r--third_party/vmm_vhost/Cargo.toml32
-rw-r--r--third_party/vmm_vhost/LICENSE202
-rw-r--r--third_party/vmm_vhost/LICENSE-BSD-3-Clause27
-rw-r--r--third_party/vmm_vhost/LICENSE-BSD-Chromium27
-rw-r--r--third_party/vmm_vhost/README.md17
-rw-r--r--third_party/vmm_vhost/cargo2android.json8
-rw-r--r--third_party/vmm_vhost/docs/vhost_architecture.drawio1
-rw-r--r--third_party/vmm_vhost/docs/vhost_architecture.pngbin0 -> 180227 bytes
-rw-r--r--third_party/vmm_vhost/src/backend.rs526
-rw-r--r--third_party/vmm_vhost/src/connection.rs475
-rw-r--r--third_party/vmm_vhost/src/connection/socket.rs486
-rw-r--r--third_party/vmm_vhost/src/connection/tube.rs319
-rw-r--r--third_party/vmm_vhost/src/connection/unix.rs74
-rw-r--r--third_party/vmm_vhost/src/connection/vfio.rs142
-rw-r--r--third_party/vmm_vhost/src/connection/windows.rs42
-rw-r--r--third_party/vmm_vhost/src/dummy_slave.rs273
-rw-r--r--third_party/vmm_vhost/src/lib.rs480
-rw-r--r--third_party/vmm_vhost/src/master.rs1100
-rw-r--r--third_party/vmm_vhost/src/master_req_handler.rs512
-rw-r--r--third_party/vmm_vhost/src/message.rs1272
-rw-r--r--third_party/vmm_vhost/src/slave.rs90
-rw-r--r--third_party/vmm_vhost/src/slave_fs_cache.rs221
-rw-r--r--third_party/vmm_vhost/src/slave_req_handler.rs957
-rw-r--r--third_party/vmm_vhost/src/sys.rs16
-rw-r--r--third_party/vmm_vhost/src/sys/unix.rs35
-rw-r--r--third_party/vmm_vhost/src/sys/windows.rs22
-rwxr-xr-xtools/aarch64vm2
-rwxr-xr-xtools/bindgen-all-the-things37
-rwxr-xr-xtools/cargo-doc43
-rwxr-xr-xtools/chromeos/create_merge112
-rwxr-xr-xtools/chromeos/setup_cargo41
-rwxr-xr-xtools/chromeos/uprev_ebuilds43
-rwxr-xr-xtools/clippy62
-rw-r--r--tools/contrib/cargo_refactor.py209
-rw-r--r--tools/contrib/refactor_use_references.py163
-rwxr-xr-xtools/dev_container107
-rw-r--r--tools/examples/.gitignore1
-rw-r--r--tools/examples/README.md6
-rwxr-xr-xtools/examples/example_desktop51
-rwxr-xr-xtools/examples/example_network44
-rwxr-xr-xtools/examples/example_simple36
-rw-r--r--tools/examples/guest/01-netcfg.yaml15
-rwxr-xr-xtools/examples/setup_network39
-rwxr-xr-xtools/fmt49
-rwxr-xr-xtools/impl/bindgen-common.sh74
-rw-r--r--tools/impl/check_code_hygiene.py146
-rw-r--r--tools/impl/common.py487
-rw-r--r--tools/impl/dev_container/Dockerfile48
-rw-r--r--tools/impl/dev_container/Makefile36
-rw-r--r--tools/impl/dev_container/version1
-rwxr-xr-xtools/impl/test_config.py151
-rw-r--r--tools/impl/test_runner.py456
-rwxr-xr-xtools/impl/test_target.py360
-rwxr-xr-xtools/impl/testvm.py438
-rw-r--r--tools/impl/testvm/Makefile81
-rw-r--r--tools/impl/testvm/cloud_init.yaml74
-rw-r--r--tools/impl/testvm/id_rsa (renamed from ci/crosvm_test_vm/runtime/ssh/id_rsa)0
-rw-r--r--tools/impl/testvm/version1
-rwxr-xr-xtools/install-aarch64-deps43
-rwxr-xr-xtools/install-armhf-deps39
-rwxr-xr-xtools/install-deps62
-rwxr-xr-xtools/presubmit118
-rwxr-xr-xtools/run_tests2
-rwxr-xr-xtools/set_test_target2
-rw-r--r--tools/windows/build_test1
-rw-r--r--tools/windows/build_test.bat5
-rw-r--r--tools/windows/build_test.py455
-rw-r--r--tools/windows/enabled_features.py7
-rw-r--r--tools/windows/files_to_include.py19
-rw-r--r--tools/windows/prepare_dlls.py12
-rwxr-xr-xtools/x86vm2
-rw-r--r--tpm2-sys/Android.bp3
-rw-r--r--tpm2-sys/Cargo.toml4
-rw-r--r--tpm2-sys/TEST_MAPPING4
-rw-r--r--tpm2-sys/build.rs87
-rw-r--r--tpm2-sys/cargo2android.json1
m---------tpm2-sys/libtpm20
-rw-r--r--tpm2/Android.bp3
-rw-r--r--tpm2/Cargo.toml2
-rw-r--r--tpm2/TEST_MAPPING4
-rw-r--r--tpm2/cargo2android.json1
-rw-r--r--unblocked_terms.txt25
-rw-r--r--usb_sys/Android.bp72
-rw-r--r--usb_sys/Cargo.toml2
-rw-r--r--usb_util/Android.bp75
-rw-r--r--usb_util/Cargo.toml7
-rw-r--r--usb_util/src/descriptor.rs294
-rw-r--r--usb_util/src/device.rs42
-rw-r--r--usb_util/src/error.rs39
-rw-r--r--usb_util/src/lib.rs6
-rw-r--r--usb_util/src/types.rs1
-rw-r--r--vfio_sys/Android.bp73
-rw-r--r--vfio_sys/Cargo.toml3
-rwxr-xr-xvfio_sys/bindgen.sh57
-rw-r--r--vfio_sys/src/lib.rs12
-rw-r--r--vfio_sys/src/plat.rs21
-rw-r--r--vfio_sys/src/vfio.rs576
-rw-r--r--vhost/Android.bp80
-rw-r--r--vhost/Cargo.toml6
-rw-r--r--vhost/src/lib.rs206
-rw-r--r--vhost/src/net.rs27
-rw-r--r--vhost/src/vsock.rs30
-rw-r--r--virtio_sys/Android.bp72
-rw-r--r--virtio_sys/Cargo.toml2
-rwxr-xr-xvirtio_sys/bindgen.sh41
-rw-r--r--virtio_sys/src/vhost.rs971
-rw-r--r--virtio_sys/src/virtio_net.rs912
-rw-r--r--virtio_sys/src/virtio_ring.rs501
-rw-r--r--vm_control/Android.bp112
-rw-r--r--vm_control/Cargo.toml14
-rw-r--r--vm_control/cargo2android.json8
-rw-r--r--vm_control/cargo2android_gdb.bp10
-rw-r--r--vm_control/src/client.rs56
-rw-r--r--vm_control/src/gdb.rs5
-rw-r--r--vm_control/src/lib.rs814
-rw-r--r--vm_memory/Android.bp78
-rw-r--r--vm_memory/Cargo.toml13
-rw-r--r--vm_memory/src/guest_address.rs7
-rw-r--r--vm_memory/src/guest_memory.rs331
-rw-r--r--win_util/Cargo.toml17
-rw-r--r--win_util/build.rs13
-rw-r--r--win_util/cargo2android.json1
-rw-r--r--win_util/src/large_integer.rs32
-rw-r--r--win_util/src/lib.rs317
-rw-r--r--win_util/src/security_attributes.rs458
-rw-r--r--x86_64/Android.bp171
-rw-r--r--x86_64/Cargo.toml14
-rw-r--r--x86_64/TEST_MAPPING4
-rw-r--r--x86_64/cargo2android.json9
-rw-r--r--x86_64/cargo2android_gdb.bp19
-rw-r--r--x86_64/src/acpi.rs596
-rw-r--r--x86_64/src/bootparam.rs2
-rw-r--r--x86_64/src/bzimage.rs43
-rw-r--r--x86_64/src/cpuid.rs102
-rw-r--r--x86_64/src/interrupts.rs22
-rw-r--r--x86_64/src/lib.rs952
-rw-r--r--x86_64/src/mpspec.rs2
-rw-r--r--x86_64/src/mptable.rs71
-rw-r--r--x86_64/src/regs.rs79
-rw-r--r--x86_64/src/smbios.rs55
-rw-r--r--x86_64/src/test_integration.rs72
1089 files changed, 132318 insertions, 51742 deletions
diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 000000000..98133d579
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,47 @@
+# Disable clippy lints project-wide.
+# This allows ./tools/clippy and IDE integrations to use the same configuration.
+# This should be replaced with a proper clippy config once available:
+# https://github.com/rust-lang/cargo/issues/5034
+[target.'cfg(all())']
+rustflags = [
+ # TODO(b/181763000): Fail builds on warnings
+ # "-Dwarnings",
+
+ # TODO(crbug/908640): To be resolved.
+ "-Aclippy::needless_return",
+ "-Aclippy::needless_doctest_main",
+ "-Aclippy::blocks_in_if_conditions",
+ "-Aclippy::missing_safety_doc", # 26 errors
+
+ # We don't care about these lints. Okay to remain suppressed globally.
+ "-Aclippy::bool_assert_comparison",
+ "-Aclippy::cast_lossless",
+ "-Aclippy::cognitive_complexity",
+ "-Aclippy::collapsible_if",
+ "-Aclippy::enum_variant_names",
+ "-Aclippy::identity_op",
+ "-Aclippy::len_without_is_empty",
+ "-Aclippy::len_zero",
+ "-Aclippy::match_bool",
+ "-Aclippy::match_wild_err_arm",
+ "-Aclippy::module_inception",
+ "-Aclippy::needless_bool",
+ "-Aclippy::new_without_default",
+ "-Aclippy::new-ret-no-self",
+ "-Aclippy::or_fun_call",
+ "-Aclippy::result-unit-err",
+ "-Aclippy::should_implement_trait",
+ "-Aclippy::single_char_pattern",
+ "-Aclippy::too_many_arguments",
+ "-Aclippy::trivially_copy_pass_by_ref",
+ "-Aclippy::type_complexity",
+ "-Aclippy::unreadable_literal",
+ "-Aclippy::useless_let_if_seq",
+ "-Aclippy::useless_transmute",
+]
+
+[target.armv7-unknown-linux-gnueabihf]
+linker = "arm-linux-gnueabihf-gcc"
+
+[target.x86_64-pc-windows-gnu]
+runner = "wine64"
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 000000000..13b8382f6
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,20 @@
+{
+ "image": "gcr.io/crosvm-packages/crosvm_dev:latest",
+ "extensions": [
+ "matklad.rust-analyzer",
+ "bungcip.better-toml",
+ "esbenp.prettier-vscode",
+ "ms-python.vscode-pylance",
+ "foxundermoon.shell-format",
+ "timonwong.shellcheck",
+ ],
+ "runArgs": [
+ "--privileged",
+ "--device=/dev/kvm",
+ "--volume=/dev/log:/dev/log",
+ "--device=/dev/net/tun",
+ "--device=/dev/vhost-net",
+ "--device=/dev/vhost-vsock",
+ "--mount=type=tmpfs,destination=/tmp",
+ ]
+}
diff --git a/.github/mistaken-pull-closer.yml b/.github/mistaken-pull-closer.yml
new file mode 100644
index 000000000..cbab2c436
--- /dev/null
+++ b/.github/mistaken-pull-closer.yml
@@ -0,0 +1,13 @@
+# `true` will close all PRs.
+filters:
+ - true
+
+# The message to post to the closed PR.
+commentBody: |
+ Thanks for your contribution! Unfortunately, we don't use GitHub pull
+ requests to manage code contributions to this repository. Instead, please
+ see the [contributing guide](https://google.github.io/crosvm/contributing.html)
+ which provides full instructions on how to get involved.
+
+# Whether to add a label to the closed PR.
+addLabel: false
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 000000000..04bcab8a7
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,3 @@
+We don't accept any GitHub pull requests to crosvm. We use
+[Chromium Gerrit](https://chromium-review.googlesource.com/) for the code review process. See
+[the contribution guide](https://google.github.io/crosvm/contributing.html) for the details.
diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml
new file mode 100644
index 000000000..02d9dec18
--- /dev/null
+++ b/.github/workflows/gh-pages.yml
@@ -0,0 +1,36 @@
+name: github pages
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+jobs:
+ deploy:
+ runs-on: ubuntu-20.04
+ concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Setup mdBook
+ uses: peaceiris/actions-mdbook@v1
+ with:
+ mdbook-version: 'latest'
+ - name: Install Dependencies
+ run: |
+ ./tools/install-deps
+ - name: Run mdbook
+ run: |
+ mkdir -p docs/target/
+ mdbook build docs/book/ --dest-dir ../target/
+ - name: Run cargo doc
+ run: |
+ git submodule update --init
+ ./tools/cargo-doc --target-dir ./docs/target/html/
+ - name: Deploy
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./docs/target/html/
diff --git a/.gitignore b/.gitignore
index bfe747156..536df2dad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,7 +4,7 @@ target/
**/*.sw[po]
**/*.orig
**/Cargo.lock
-!/Cargo.lock
lcov.info
.idea
.vscode
+.envrc
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index f04998daa..000000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "tpm2-sys/libtpm2"]
- path = tpm2-sys/libtpm2
- url = https://chromium.googlesource.com/chromiumos/third_party/tpm2
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
new file mode 100644
index 000000000..d8c7505e6
--- /dev/null
+++ b/ARCHITECTURE.md
@@ -0,0 +1,234 @@
+# Architecture
+
+The principle characteristics of crosvm are:
+
+- A process per virtual device, made using fork
+- Each process is sandboxed using [minijail]
+- Takes full advantage of KVM and low-level Linux syscalls, and so only runs on Linux
+- Written in Rust for security and safety
+
+A typical session of crosvm starts in `main.rs` where command line parsing is done to build up a
+`Config` structure. The `Config` is used by `run_config` in `linux/mod.rs` to setup and execute a
+VM. Broken down into rough steps:
+
+1. Load the linux kernel from an ELF file.
+1. Create a handful of control sockets used by the virtual devices.
+1. Invoke the architecture specific VM builder `Arch::build_vm` (located in `x86_64/src/lib.rs` or
+ `aarch64/src/lib.rs`).
+1. `Arch::build_vm` will itself invoke the provided `create_devices` function from `linux/mod.rs`
+1. `create_devices` creates every PCI device, including the virtio devices, that were configured in
+ `Config`, along with matching [minijail] configs for each.
+1. `Arch::generate_pci_root`, using a list of every PCI device with optional `Minijail`, will
+ finally jail the PCI devices and construct a `PciRoot` that communicates with them.
+1. Once the VM has been built, it's contained within a `RunnableLinuxVm` object that is used by the
+ VCPUs and control loop to service requests until shutdown.
+
+## Forking
+
+During the device creation routine, each device will be created and then wrapped in a `ProxyDevice`
+which will internally `fork` (but not `exec`) and [minijail] the device, while dropping it for the
+main process. The only interaction that the device is capable of having with the main process is via
+the proxied trait methods of `BusDevice`, shared memory mappings such as the guest memory, and file
+descriptors that were specifically allowed by that device's security policy. This can lead to some
+surprising behavior to be aware of such as why some file descriptors which were once valid are now
+invalid.
+
+## Sandboxing Policy
+
+Every sandbox is made with [minijail] and starts with `create_base_minijail` in
+`linux/jail_helpers.rs` which set some very restrictive settings. Linux namespaces and seccomp
+filters are used extensively. Each seccomp policy can be found under
+`seccomp/{arch}/{device}.policy` and should start by `@include`-ing the `common_device.policy`. With
+the exception of architecture specific devices (such as `Pl030` on ARM or `I8042` on x86_64), every
+device will need a different policy for each supported architecture.
+
+## The VM Control Sockets
+
+For the operations that devices need to perform on the global VM state, such as mapping into guest
+memory address space, there are the vm control sockets. There are a few kinds, split by the type of
+request and response that the socket will process. This also proves basic security privilege
+separation in case a device becomes compromised by a malicious guest. For example, a rogue device
+that is able to allocate MSI routes would not be able to use the same socket to (de)register guest
+memory. During the device initialization stage, each device that requires some aspect of VM control
+will have a constructor that requires the corresponding control socket. The control socket will get
+preserved when the device is sandboxed and the other side of the socket will be waited on in the
+main process's control loop.
+
+The socket exposed by crosvm with the `--socket` command line argument is another form of the VM
+control socket. Because the protocol of the control socket is internal and unstable, the only
+supported way of using that resulting named unix domain socket is via crosvm command line
+subcommands such as `crosvm stop`.
+
+## GuestMemory
+
+`GuestMemory` and its friends `VolatileMemory`, `VolatileSlice`, `MemoryMapping`, and
+`SharedMemory`, are common types used throughout crosvm to interact with guest memory. Know which
+one to use in what place using some guidelines
+
+- `GuestMemory` is for sending around references to all of the guest memory. It can be cloned
+ freely, but the underlying guest memory is always the same. Internally, it's implemented using
+ `MemoryMapping` and `SharedMemory`. Note that `GuestMemory` is mapped into the host address space,
+ but it is non-contiguous. Device memory, such as mapped DMA-Bufs, are not present in
+ `GuestMemory`.
+- `SharedMemory` wraps a `memfd` and can be mapped using `MemoryMapping` to access its data.
+ `SharedMemory` can't be cloned.
+- `VolatileMemory` is a trait that exposes generic access to non-contiguous memory. `GuestMemory`
+ implements this trait. Use this trait for functions that operate on a memory space but don't
+ necessarily need it to be guest memory.
+- `VolatileSlice` is analogous to a Rust slice, but unlike those, a `VolatileSlice` has data that
+ changes asynchronously by all those that reference it. Exclusive mutability and data
+ synchronization are not available when it comes to a `VolatileSlice`. This type is useful for
+ functions that operate on contiguous shared memory, such as a single entry from a scatter gather
+ table, or for safe wrappers around functions which operate on pointers, such as a `read` or
+ `write` syscall.
+- `MemoryMapping` is a safe wrapper around anonymous and file mappings. Provides RAII and does
+ munmap after use. Access via Rust references is forbidden, but indirect reading and writing is
+ available via `VolatileSlice` and several convenience functions. This type is most useful for
+ mapping memory unrelated to `GuestMemory`.
+
+### Device Model
+
+### `Bus`/`BusDevice`
+
+The root of the crosvm device model is the `Bus` structure and its friend the `BusDevice` trait. The
+`Bus` structure is a virtual computer bus used to emulate the memory-mapped I/O bus and also I/O
+ports for x86 VMs. On a read or write to an address on a VM's bus, the corresponding `Bus` object is
+queried for a `BusDevice` that occupies that address. `Bus` will then forward the read/write to the
+`BusDevice`. Because of this behavior, only one `BusDevice` may exist at any given address. However,
+a `BusDevice` may be placed at more than one address range. Depending on how a `BusDevice` was
+inserted into the `Bus`, the forwarded read/write will be relative to 0 or to the start of the
+address range that the `BusDevice` occupies (which would be ambiguous if the `BusDevice` occupied
+more than one range).
+
+Only the base address of a multi-byte read/write is used to search for a device, so a device
+implementation should be aware that the last address of a single read/write may be outside its
+address range. For example, if a `BusDevice` was inserted at base address 0x1000 with a length of
+0x40, a 4-byte read by a VCPU at 0x39 would be forwarded to that `BusDevice`.
+
+Each `BusDevice` is reference counted and wrapped in a mutex, so implementations of `BusDevice` need
+not worry about synchronizing their access across multiple VCPUs and threads. Each VCPU will get a
+complete copy of the `Bus`, so there is no contention for querying the `Bus` about an address. Once
+the `BusDevice` is found, the `Bus` will acquire an exclusive lock to the device and forward the
+VCPU's read/write. The implementation of the `BusDevice` will block execution of the VCPU that
+invoked it, as well as any other VCPU attempting access, until it returns from its method.
+
+Most devices in crosvm do not implement `BusDevice` directly, but some are examples are `i8042` and
+`Serial`. With the exception of PCI devices, all devices are inserted by architecture specific code
+(which may call into the architecture-neutral `arch` crate). A `BusDevice` can be proxied to a
+sandboxed process using `ProxyDevice`, which will create the second process using a fork, with no
+exec.
+
+### `PciConfigIo`/`PciConfigMmio`
+
+In order to use the more complex PCI bus, there are a couple adapters that implement `BusDevice` and
+call into a `PciRoot` with higher level calls to `config_space_read`/`config_space_write`. The
+`PciConfigMmio` is a `BusDevice` for insertion into the MMIO `Bus` for ARM devices. For x86_64,
+`PciConfigIo` is inserted into the I/O port `Bus`. There is only one implementation of `PciRoot`
+that is used by either of the `PciConfig*` structures. Because these devices are very simple, they
+have very little code or state. They aren't sandboxed and are run as part of the main process.
+
+### `PciRoot`/`PciDevice`/`VirtioPciDevice`
+
+The `PciRoot`, analogous to `BusDevice` for `Bus`s, contains all the `PciDevice` trait objects.
+Because of a shortcut (or hack), the `ProxyDevice` only supports jailing `BusDevice` traits.
+Therefore, `PciRoot` only contains `BusDevice`s, even though they also implement `PciDevice`. In
+fact, every `PciDevice` also implements `BusDevice` because of a blanket implementation
+(`impl<T: PciDevice> BusDevice for T { … }`). There are a few PCI related methods in `BusDevice` to
+allow the `PciRoot` to still communicate with the underlying `PciDevice` (yes, this abstraction is
+very leaky). Most devices will not implement `PciDevice` directly, instead using the
+`VirtioPciDevice` implementation for virtio devices, but the xHCI (USB) controller is an example
+that implements `PciDevice` directly. The `VirtioPciDevice` is an implementation of `PciDevice` that
+wraps a `VirtioDevice`, which is how the virtio specified PCI transport is adapted to a transport
+agnostic `VirtioDevice` implementation.
+
+### `VirtioDevice`
+
+The `VirtioDevice` is the most widely implemented trait among the device traits. Each of the
+different virtio devices (block, rng, net, etc.) implement this trait directly and they follow a
+similar pattern. Most of the trait methods are easily filled in with basic information about the
+specific device, but `activate` will be the heart of the implementation. It's called by the virtio
+transport after the guest's driver has indicated the device has been configured and is ready to run.
+The virtio device implementation will receive the run time related resources (`GuestMemory`,
+`Interrupt`, etc.) for processing virtio queues and associated interrupts via the arguments to
+`activate`, but `activate` can't spend its time actually processing the queues. A VCPU will be
+blocked as long as `activate` is running. Every device uses `activate` to launch a worker thread
+that takes ownership of run time resources to do the actual processing. There is some subtlety in
+dealing with virtio queues, so the smart thing to do is copy a simpler device and adapt it, such as
+the rng device (`rng.rs`).
+
+## Communication Framework
+
+Because of the multi-process nature of crosvm, communication is done over several IPC primitives.
+The common ones are shared memory pages, unix sockets, anonymous pipes, and various other file
+descriptor variants (DMA-buf, eventfd, etc.). Standard methods (`read`/`write`) of using these
+primitives may be used, but crosvm has developed some helpers which should be used where applicable.
+
+### `PollContext`/`EpollContext`
+
+Most threads in crosvm will have a wait loop using a `PollContext`, which is a wrapper around
+Linux's `epoll` primitive for selecting over file descriptors. `EpollContext` is very similar but
+has slightly fewer features, but is usable by multiple threads at once. In either case, each FD is
+added to the context along with an associated token, whose type is the type parameter of
+`PollContext`. This token must be convertible to and from a `u64`, which is a limitation imposed by
+how `epoll` works. There is a custom derive `#[derive(PollToken)]` which can be applied to an `enum`
+declaration that makes it easy to use your own enum in a `PollContext`.
+
+Note that the limitations of `PollContext` are the same as the limitations of `epoll`. The same FD
+can not be inserted more than once, and the FD will be automatically removed if the process runs out
+of references to that FD. A `dup`/`fork` call will increment that reference count, so closing the
+original FD will not actually remove it from the `PollContext`. It is possible to receive tokens
+from `PollContext` for an FD that was closed because of a race condition in which an event was
+registered in the background before the `close` happened. Best practice is to remove an FD before
+closing it so that events associated with it can be reliably eliminated.
+
+### `serde` with Descriptors.
+
+Using raw sockets and pipes to communicate is very inconvenient for rich data types. To help make
+this easier and less error prone, crosvm uses the `serde` crate. To allow transmitting types with
+embedded descriptors (FDs on Linux or HANDLEs on Windows), a module is provided for sending and
+receiving descriptors alongside the plain old bytes that serde consumes.
+
+## Code Map
+
+Source code is organized into crates, each with their own unit tests.
+
+- `./src/` - The top-level binary front-end for using crosvm.
+- `aarch64` - Support code specific to 64 bit ARM architectures.
+- `base` - Safe wrappers for small system facilities which provides cross-platform-compatible
+ interfaces. For Linux, this is basically a thin wrapper of `sys_util`.
+- `bin` - Scripts for code health such as wrappers of `rustfmt` and `clippy`.
+- `ci` - Scripts for continuous integration.
+- `cros_async` - Runtime for async/await programming. This crate provides a `Future` executor based
+ on `io_uring` and one based on `epoll`.
+- `devices` - Virtual devices exposed to the guest OS.
+- `disk` - Library to create and manipulate several types of disks such as raw disk, [qcow], etc.
+- `hypervisor` - Abstract layer to interact with hypervisors. For Linux, this crate is a wrapper of
+ `kvm`.
+- `integration_tests` - End-to-end tests that run a crosvm VM.
+- `kernel_loader` - Loads elf64 kernel files to a slice of memory.
+- `kvm_sys` - Low-level (mostly) auto-generated structures and constants for using KVM.
+- `kvm` - Unsafe, low-level wrapper code for using `kvm_sys`.
+- `media/libvda` - Safe wrapper of [libvda], a Chrome OS HW-accelerated video decoding/encoding
+ library.
+- `net_sys` - Low-level (mostly) auto-generated structures and constants for creating TUN/TAP
+ devices.
+- `net_util` - Wrapper for creating TUN/TAP devices.
+- `qcow_util` - A library and a binary to manipulate [qcow] disks.
+- `seccomp` - Contains minijail seccomp policy files for each sandboxed device. Because some
+ syscalls vary by architecture, the seccomp policies are split by architecture.
+- `sync` - Our version of `std::sync::Mutex` and `std::sync::Condvar`.
+- `sys_util` - Mostly safe wrappers for small system facilities such as `eventfd` or `syslog`.
+- `third_party` - Third-party libraries which we are maintaining on the Chrome OS tree or the AOSP
+ tree.
+- `vfio_sys` - Low-level (mostly) auto-generated structures, constants and ioctls for [VFIO].
+- `vhost` - Wrappers for creating vhost based devices.
+- `virtio_sys` - Low-level (mostly) auto-generated structures and constants for interfacing with
+ kernel vhost support.
+- `vm_control` - IPC for the VM.
+- `vm_memory` - Vm-specific memory objects.
+- `x86_64` - Support code specific to 64 bit intel machines.
+
+[libvda]: https://chromium.googlesource.com/chromiumos/platform2/+/refs/heads/main/arc/vm/libvda/
+[minijail]: https://android.googlesource.com/platform/external/minijail
+[qcow]: https://en.wikipedia.org/wiki/Qcow
+[vfio]: https://www.kernel.org/doc/html/latest/driver-api/vfio.html
diff --git a/Android.bp b/Android.bp
index 8c7b0f860..49251d69e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,16 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --no-subdir.
-// Most modules in this file are edited manually:
-// * global defaults defined in crosvm_defaults, enabled for linux_glibc_x86_64
-// * all modules use crosvm_defaults or other defaults derived from it
-// * root host binary is crosvm
-// * crosvm and libcrosvm have extra features:
-// * audio, gfxstream, gpu, virgl_renderer, virgl_renderer_next
-// * crosvm has extra flags and ld_flags
-// * crosvm_host_test_tests_boot and crosvm_host_test_tests_plugins
-// are not ready yet
-// We use cargo2android.py only to discover new changes.
-// * Use --no-subdir to suppress output of subdirectory Android.bp files.
-// * Run cargo2android.py in each subdirectory to generate .bp files.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
package {
default_applicable_licenses: ["external_crosvm_license"],
@@ -43,102 +32,28 @@ license {
],
}
-rust_defaults {
- name: "crosvm_defaults",
- edition: "2018",
- enabled: false,
- target: {
- linux_glibc_x86_64: {
- enabled: true,
- flags: [
- "-L device/google/cuttlefish_vmm/x86_64-linux-gnu/bin/",
- ],
- },
- android64: {
- compile_multilib: "64",
- enabled: true,
- },
- linux_bionic_arm64: {
- enabled: true,
- },
- darwin: {
- enabled: false,
- },
- },
- apex_available: [
- "//apex_available:platform",
- "com.android.virt",
- ],
-}
-
-rust_defaults {
- name: "crosvm_proc_macro_defaults",
- defaults: ["crosvm_defaults"],
- target: {
- darwin: {
- enabled: true,
- },
- },
-}
-
rust_binary {
name: "crosvm",
defaults: ["crosvm_defaults"],
host_supported: true,
prefer_rlib: true,
crate_name: "crosvm",
+ cargo_env_compat: true,
srcs: ["src/main.rs"],
-
- target: {
- linux_bionic_arm64: {
- relative_install_path: "aarch64-linux-bionic",
- },
- linux_glibc_x86_64: {
- features: [
- "gdb",
- "gdbstub",
- ],
- relative_install_path: "x86_64-linux-gnu",
- rustlibs: [
- "libgdbstub",
- "libthiserror",
- ],
- },
- darwin: {
- enabled: false,
- },
- },
- arch: {
- x86_64: {
- rustlibs: ["libx86_64_rust"],
- },
- arm64: {
- rustlibs: ["libaarch64"],
- },
- },
-
- edition: "2018",
+ edition: "2021",
features: [
"audio",
- "default",
- "gpu",
- "gfxstream",
- ],
-
- flags: [
- "-C overflow-checks=y",
- ],
- ld_flags: [
- "-Wl,--rpath,\\$$ORIGIN",
- "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+ "usb",
],
rustlibs: [
"libacpi_tables",
+ "libanyhow",
"libarch",
"libassertions",
"libaudio_streams",
"libbase_rust",
"libbit_field",
+ "libcfg_if",
"libcrosvm",
"libdata_model",
"libdevices",
@@ -151,11 +66,14 @@ rust_binary {
"libminijail_rust",
"libnet_util",
"libp9",
- "librand_ish",
"libresources",
"librutabaga_gfx",
+ "libserde_json",
+ "libserde_keyvalue",
"libsync_rust",
"libtempfile",
+ "libthiserror",
+ "libuuid",
"libvhost",
"libvm_control",
"libvm_memory",
@@ -164,16 +82,6 @@ rust_binary {
"libenumn",
"libremain",
],
-}
-
-rust_defaults {
- name: "crosvm_defaults_test",
- defaults: ["crosvm_defaults"],
- crate_name: "crosvm",
- srcs: ["src/crosvm.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
arch: {
x86_64: {
rustlibs: ["libx86_64_rust"],
@@ -183,41 +91,97 @@ rust_defaults {
},
},
target: {
- linux_glibc_x86_64: {
+ host: {
+ features: [
+ "gfxstream",
+ "gpu",
+ ],
+ },
+ android: {
+ shared_libs: [
+ "libprocessgroup",
+ ],
+ },
+ darwin: {
+ enabled: false,
+ },
+ host_linux: {
features: [
"gdb",
"gdbstub",
],
rustlibs: [
"libgdbstub",
+ "libgdbstub_arch",
"libthiserror",
],
+ shared_libs: [
+ "libprocessgroup",
+ ],
},
+ linux_bionic_arm64: {
+ relative_install_path: "aarch64-linux-bionic",
+ },
+ linux_glibc_x86_64: {
+ relative_install_path: "x86_64-linux-gnu",
+ },
+ linux_musl_x86_64: {
+ relative_install_path: "x86_64-linux-musl",
+ },
+ },
+ ld_flags: [
+ "-Wl,--rpath,\\$$ORIGIN",
+ "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+ ],
+
+}
+
+rust_test {
+ name: "crosvm_test_src_crosvm",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "crosvm",
+ cargo_env_compat: true,
+ srcs: ["src/crosvm.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ test_options: {
+ unit_test: true,
},
+ edition: "2021",
features: [
- "default",
+ "audio",
+ "usb",
],
rustlibs: [
"libacpi_tables",
+ "libanyhow",
"libarch",
"libassertions",
+ "libaudio_streams",
"libbase_rust",
"libbit_field",
+ "libcfg_if",
"libdata_model",
"libdevices",
"libdisk",
"libhypervisor",
"libkernel_cmdline",
"libkernel_loader",
+ "liblazy_static",
"liblibc",
+ "liblibcras",
"libminijail_rust",
"libnet_util",
"libp9",
- "librand_ish",
"libresources",
"librutabaga_gfx",
+ "libserde_json",
+ "libserde_keyvalue",
"libsync_rust",
"libtempfile",
+ "libthiserror",
+ "libuuid",
"libvhost",
"libvm_control",
"libvm_memory",
@@ -226,60 +190,66 @@ rust_defaults {
"libenumn",
"libremain",
],
-}
-
-rust_test_host {
- name: "crosvm_host_test_src_crosvm",
- defaults: ["crosvm_defaults_test"],
- features: [
- "audio",
- ],
- rustlibs: [
- "libaudio_streams",
- "liblibcras",
- ],
- test_options: {
- unit_test: true,
+ arch: {
+ x86_64: {
+ rustlibs: ["libx86_64_rust"],
+ },
+ arm64: {
+ rustlibs: ["libaarch64"],
+ },
},
-}
-
-rust_test {
- name: "crosvm_device_test_src_crosvm",
- defaults: ["crosvm_defaults_test"],
- features: [
- "audio",
- ],
- rustlibs: [
- "libaudio_streams",
- "liblibcras",
+ target: {
+ host: {
+ features: [
+ "gfxstream",
+ "gpu",
+ ],
+ },
+ host_linux: {
+ features: [
+ "gdb",
+ "gdbstub",
+ ],
+ rustlibs: [
+ "libgdbstub",
+ "libgdbstub_arch",
+ "libthiserror",
+ ],
+ },
+ },
+ ld_flags: [
+ "-Wl,--rpath,\\$$ORIGIN",
+ "-Wl,--rpath,\\$$ORIGIN/../../lib64",
],
+
}
-rust_defaults {
- name: "crosvm_defaults_crosvm",
+rust_test {
+ name: "crosvm_test_src_main",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "crosvm",
+ cargo_env_compat: true,
srcs: ["src/main.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
- arch: {
- x86_64: {
- rustlibs: ["libx86_64_rust"],
- },
- arm64: {
- rustlibs: ["libaarch64"],
- },
+ test_options: {
+ unit_test: true,
},
+ edition: "2021",
features: [
- "default",
+ "audio",
+ "usb",
],
rustlibs: [
"libacpi_tables",
+ "libanyhow",
"libarch",
"libassertions",
+ "libaudio_streams",
"libbase_rust",
"libbit_field",
+ "libcfg_if",
"libcrosvm",
"libdata_model",
"libdevices",
@@ -287,15 +257,20 @@ rust_defaults {
"libhypervisor",
"libkernel_cmdline",
"libkernel_loader",
+ "liblazy_static",
"liblibc",
+ "liblibcras",
"libminijail_rust",
"libnet_util",
"libp9",
- "librand_ish",
"libresources",
"librutabaga_gfx",
+ "libserde_json",
+ "libserde_keyvalue",
"libsync_rust",
"libtempfile",
+ "libthiserror",
+ "libuuid",
"libvhost",
"libvm_control",
"libvm_memory",
@@ -304,72 +279,61 @@ rust_defaults {
"libenumn",
"libremain",
],
-}
-
-rust_test_host {
- name: "crosvm_host_test_src_main",
- defaults: ["crosvm_defaults_crosvm"],
- features: [
- "audio",
- ],
- rustlibs: [
- "libaudio_streams",
- "liblibcras",
- ],
- test_options: {
- unit_test: true,
+ arch: {
+ x86_64: {
+ rustlibs: ["libx86_64_rust"],
+ },
+ arm64: {
+ rustlibs: ["libaarch64"],
+ },
},
-}
-
-rust_test {
- name: "crosvm_device_test_src_main",
- defaults: ["crosvm_defaults_crosvm"],
-}
-
-rust_library {
- name: "libcrosvm",
- defaults: ["crosvm_defaults"],
- host_supported: true,
- crate_name: "crosvm",
- srcs: ["src/crosvm.rs"],
- edition: "2018",
target: {
- linux_glibc: {
+ host: {
features: [
"gfxstream",
+ "gpu",
],
},
- linux_glibc_x86_64: {
+ host_linux: {
features: [
"gdb",
"gdbstub",
],
rustlibs: [
"libgdbstub",
+ "libgdbstub_arch",
"libthiserror",
],
},
},
- arch: {
- x86_64: {
- rustlibs: ["libx86_64_rust"],
- },
- arm64: {
- rustlibs: ["libaarch64"],
- },
- },
+ ld_flags: [
+ "-Wl,--rpath,\\$$ORIGIN",
+ "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+ ],
+
+}
+
+rust_library {
+ name: "libcrosvm",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "crosvm",
+ cargo_env_compat: true,
+ srcs: ["src/crosvm.rs"],
+ edition: "2021",
features: [
"audio",
- "default",
- "gpu",
+ "usb",
],
rustlibs: [
"libacpi_tables",
+ "libanyhow",
"libarch",
"libassertions",
"libaudio_streams",
"libbase_rust",
"libbit_field",
+ "libcfg_if",
"libdata_model",
"libdevices",
"libdisk",
@@ -381,11 +345,14 @@ rust_library {
"libminijail_rust",
"libnet_util",
"libp9",
- "librand_ish",
"libresources",
"librutabaga_gfx",
+ "libserde_json",
+ "libserde_keyvalue",
"libsync_rust",
"libtempfile",
+ "libthiserror",
+ "libuuid",
"libvhost",
"libvm_control",
"libvm_memory",
@@ -394,59 +361,72 @@ rust_library {
"libenumn",
"libremain",
],
+ arch: {
+ x86_64: {
+ rustlibs: ["libx86_64_rust"],
+ },
+ arm64: {
+ rustlibs: ["libaarch64"],
+ },
+ },
+ target: {
+ host: {
+ features: [
+ "gfxstream",
+ "gpu",
+ ],
+ },
+ android: {
+ shared_libs: [
+ "libprocessgroup",
+ ],
+ },
+ host_linux: {
+ features: [
+ "gdb",
+ "gdbstub",
+ ],
+ rustlibs: [
+ "libgdbstub",
+ "libgdbstub_arch",
+ "libthiserror",
+ ],
+ shared_libs: [
+ "libprocessgroup",
+ ],
+ },
+ },
+ ld_flags: [
+ "-Wl,--rpath,\\$$ORIGIN",
+ "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+ ],
+
}
-// dependent_library ["feature_list"]
-// ../adhd/audio_streams/src/audio_streams.rs
-// ../adhd/cras/client/cras-sys/src/lib.rs
-// ../adhd/cras/client/libcras/src/libcras.rs
-// ../libchromeos-rs/src/lib.rs
-// ../minijail/rust/minijail-sys/lib.rs
-// ../minijail/rust/minijail/src/lib.rs
-// ../vm_tools/p9/src/lib.rs
-// ../vm_tools/p9/wire_format_derive/wire_format_derive.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.45
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// cc-1.0.25
-// cfg-if-1.0.0
-// downcast-rs-1.2.0 "default,std"
-// futures-0.3.13 "alloc,async-await,default,executor,futures-executor,std"
-// futures-channel-0.3.13 "alloc,futures-sink,sink,std"
-// futures-core-0.3.13 "alloc,std"
-// futures-executor-0.3.13 "std"
-// futures-io-0.3.13 "std"
-// futures-macro-0.3.13
-// futures-sink-0.3.13 "alloc,std"
-// futures-task-0.3.13 "alloc,std"
-// futures-util-0.3.13 "alloc,async-await,async-await-macro,channel,futures-channel,futures-io,futures-macro,futures-sink,io,memchr,proc-macro-hack,proc-macro-nested,sink,slab,std"
-// getrandom-0.2.2 "std"
-// intrusive-collections-0.9.0 "alloc,default"
-// libc-0.2.87 "default,std"
-// log-0.4.14
-// memchr-2.3.4 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.4
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// pkg-config-0.3.19
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro-hack-0.5.19
-// proc-macro-nested-0.1.7
-// proc-macro2-1.0.24 "default,proc-macro"
-// protobuf-2.22.0
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.3 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.0 "std"
-// rand_core-0.6.2 "alloc,getrandom,std"
-// remain-0.2.2
-// remove_dir_all-0.5.3
-// serde-1.0.123 "default,derive,serde_derive,std"
-// serde_derive-1.0.123 "default"
-// slab-0.4.2
-// syn-1.0.61 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
+rust_defaults {
+ name: "crosvm_defaults",
+ edition: "2018",
+ enabled: false,
+ target: {
+ linux_glibc_x86_64: {
+ enabled: true,
+ },
+ linux_musl_x86_64: {
+ enabled: true,
+ },
+ android64: {
+ compile_multilib: "64",
+ enabled: true,
+ },
+ linux_bionic_arm64: {
+ enabled: true,
+ },
+ darwin: {
+ enabled: false,
+ },
+ },
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
+}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9281b40d0..859f40b82 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,66 +1,174 @@
+# Contributing
+
## Intro
-This article goes into detail about multiple areas of interest to contributors,
-which includes reviewers, developers, and integrators who each share an interest
-in guiding crosvm's direction.
+This article goes into detail about multiple areas of interest to contributors, which includes
+reviewers, developers, and integrators who each share an interest in guiding crosvm's direction.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License Agreement (CLA). You (or
+your employer) retain the copyright to your contribution; this simply gives us permission to use and
+redistribute your contributions as part of the project. Head over to
+<https://cla.developers.google.com/> to see your current agreements on file or to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one (even if it was for
+a different project), you probably don't need to do it again.
+
+## Bug Reports
+
+We use the Chromium issue tracker. Please use
+[`OS>Systems>Containers`](https://bugs.chromium.org/p/chromium/issues/list?q=component:OS%3ESystems%3EContainers)
+component.
-## Guidelines
+## Philosophy
The following is high level guidance for producing contributions to crosvm.
- Prefer mechanism to policy.
- Use existing protocols when they are adequate, such as virtio.
- Prefer security over code re-use and speed of development.
-- Only the version of Rust in use by the Chrome OS toolchain is supported. This
- is ordinarily the stable version of Rust, but can be behind a version for a
- few weeks.
+- Only the version of Rust in use by the Chrome OS toolchain is supported. This is ordinarily the
+ stable version of Rust, but can be behind a version for a few weeks.
- Avoid distribution specific code.
## Code Health
### Scripts
-In the `bin/` directory of the crosvm repository, there is the `clippy` script
-which lints the Rust code and the `fmt` script which will format the crosvm Rust
-code inplace.
+In the `bin/` directory of the crosvm repository, there is the `clippy` script which lints the Rust
+code and the `fmt` script which will format the crosvm Rust code inplace.
### Running tests
The `./test_all` script will use docker containers to run all tests for crosvm.
-When submitting changes, these tests will be run by Kokoro, the internal Google
-run cloud builder, and the results will be posted to the change. Kokoro is only
-informational, so if Kokoro rejects a change, it can still be submitted.
-For more details on using the docker containers for running tests locally,
-including faster, iterative test runs, see `ci/README.md`.
+For more details on using the docker containers for running tests locally, including faster,
+iterative test runs, see `ci/README.md`.
-### Submitting Code
+### Style guidelines
-See also,
-[Chrome OS Contributing Guide](https://chromium.googlesource.com/chromiumos/docs/+/HEAD/contributing.md)
+To format all code, crosvm defers to rustfmt. In addition, the code adheres to the following rules:
-When a change is approved, verified, and added to the
-[commit queue](https://chromium.googlesource.com/chromiumos/docs/+/HEAD/contributing.md#send-your-changes-to-the-commit-queue),
-crosvm will be built and the unit tests (with some exceptions) will be run by
-the Chrome OS infrastructure. Only if that passes, will the change be submitted.
-Failures here will cause the commit queue to reject the change until it is
-re-added (CQ+2). Unfortunately, it is extremely common for false negatives to
-cause a change to get rejected, so be ready to re-apply the CQ+2 label if you're
-the owner of a ready to submit change.
+The `use` statements for each module should be grouped in this order
-### Style guidelines
+1. `std`
+1. third-party crates
+1. chrome os crates
+1. crosvm crates
+1. `crate`
-To format all code, crosvm defers to rustfmt. In addition, the code adheres to
-the following rules:
+crosvm uses the [remain](https://github.com/dtolnay/remain) crate to keep error enums sorted, along
+with the `#[sorted]` attribute to keep their corresponding match statements in the same order.
-The `use` statements for each module should be grouped in this order
+## Submitting Code
+
+Since crosvm is one of Chromium OS projects, please read through [Chrome OS Contributing Guide]
+first. This section describes the crosvm-specific workflow.
+
+### Trying crosvm
+
+Please see [the book of crosvm].
+
+### Sending for code review
+
+We use [Chromium Gerrit](https://chromium-review.googlesource.com/) for code reviewing. All crosvm
+CLs are listed at the [crosvm component].
+
+> Note: We don't accept any pull requests on the [GitHub mirror].
+
+#### For Chromium OS Developers {#chromiumos-cl}
+
+If you have already set up the `chromiumos` repository and the `repo` command, you can simply create
+and upload your CL in a similar manner as other Chromium OS projects.
+
+`repo start` will create a branch tracking `cros/chromeos` so you can develop with the latest,
+CQ-tested code as a foundation.
+
+However, changes are not acceped to the `cros/chromeos` branch, and should be submitted to
+`cros/main` instead.
+
+Use `repo upload -D main` to upload changes to the main branch, which works fine in most cases where
+gerrit can rebase the commit cleanly. If not, please rebase to `cros/main` manually.
+
+#### For non-Chromium OS Developers
+
+If you are not interested in other Chromium OS components, you can simply
+[clone and contribute crosvm only](https://google.github.io/crosvm/building_crosvm/linux.html).
+Before you make a commit locally, please set up [Gerrit's Change-Id hook] on your system.
+
+```sh
+# Modify code and make a git commit with a commit message following this rule:
+# https://chromium.googlesource.com/chromiumos/docs/+/HEAD/contributing.md#Commit-messages
+git commit
+# Push your commit to Chromium Gerrit (https://chromium-review.googlesource.com/).
+git push origin HEAD:refs/for/main
+```
+
+### Code review
+
+Your change must be reviewed and approved by one of [crosvm owners].
+
+### Presubmit checking {#presubmit}
+
+Once your change is reviewed, it will need to go through two layers of presubmit checks.
+
+The review will trigger Kokoro to run crosvm specific tests. If you want to check kokoro results
+before a review, you can set 'Commit Queue +1' in gerrit to trigger a dry-run.
+
+If you upload further changes after the you were given 'Code Review +2', Kokoro will automatically
+trigger another test run. But you can also always comment 'kokoro rerun' to manually trigger another
+build if needed.
+
+When Kokoro passes, it will set Verified +1 and the change is ready to be sent to the
+[ChromeOS commit queue](https://chromium.googlesource.com/chromiumos/docs/+/HEAD/contributing.md#send-your-changes-to-the-commit-queue)
+by setting CQ+2.
+
+Note: This is different from other ChromeOS repositories, where Verified +1 bit is set by the
+developers to indicate that they successfully tested a change. The Verified bit can only be set by
+Kokoro in the crosvm repository.
+
+### Postsubmit merging to Chrome OS {#chromiumos-postsubmit}
+
+Crosvm has a unique setup to integrate with ChromeOS infrastructure.
+
+The chromeos checkout tracks the
+[cros/chromeos](https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/chromeos)
+branch of crosvm, not the
+[cros/main](https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main) branch.
+
+While upstream development is happening on the `main` branch, changes submitted to that branch are
+only tested by the crosvm kokoro CI system, not by the ChromeOS CQ.
+
+There is a
+[daily process](https://chromium-review.googlesource.com/q/project:chromiumos%252Fplatform%252Fcrosvm+branch:chromeos)
+that creates a commit to merge changes from `main` into the `chromeos` branch, which is then tested
+through the CQ and watched by the crosvm-uprev rotation.
+
+## Contributing to the documentation
+
+[The book of crosvm] is build with [mdBook]. Each markdown files must follow
+[Google Markdown style guide].
+
+To render the book locally, you need to install mdbook and [mdbook-mermaid], which should be
+installed when you run `./tools/install-deps`script.
+
+```sh
+cd crosvm/docs/book/
+mdbook build
+```
-1. `std`
-2. third-party crates
-3. chrome os crates
-4. crosvm crates
-5. `crate`
+> Note: If you make a certain size of changes, it's recommended to reinstall mdbook manually with
+> `cargo install mdbook`, as `./tools/install-deps` only installs a binary with some convenient
+> features disabled. For example, the full version of mdbook allows you to edit files while checking
+> rendered results.
-crosvm uses the [remain](https://github.com/dtolnay/remain) crate to keep error
-enums sorted, along with the `#[sorted]` attribute to keep their corresponding
-match statements in the same order.
+[chrome os contributing guide]: https://chromium.googlesource.com/chromiumos/docs/+/HEAD/contributing.md
+[crosvm component]: https://chromium-review.googlesource.com/q/project:chromiumos%252Fplatform%252Fcrosvm
+[crosvm owners]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/HEAD/OWNERS
+[gerrit's change-id hook]: https://gerrit-review.googlesource.com/Documentation/user-changeid.html
+[github mirror]: https://github.com/google/crosvm
+[google markdown style guide]: https://github.com/google/styleguide/blob/gh-pages/docguide/style.md
+[mdbook]: https://rust-lang.github.io/mdBook/
+[mdbook-mermaid]: https://github.com/badboy/mdbook-mermaid
+[the book of crosvm]: https://google.github.io/crosvm/
diff --git a/Cargo.lock b/Cargo.lock
deleted file mode 100644
index 87cdedda2..000000000
--- a/Cargo.lock
+++ /dev/null
@@ -1,1289 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-[[package]]
-name = "aarch64"
-version = "0.1.0"
-dependencies = [
- "arch",
- "base",
- "data_model",
- "devices",
- "hypervisor",
- "kernel_cmdline",
- "kvm",
- "kvm_sys",
- "libc",
- "minijail",
- "remain",
- "resources",
- "sync",
- "vm_control",
- "vm_memory",
-]
-
-[[package]]
-name = "acpi_tables"
-version = "0.1.0"
-dependencies = [
- "data_model",
- "tempfile",
-]
-
-[[package]]
-name = "android_log-sys"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e"
-
-[[package]]
-name = "anyhow"
-version = "1.0.35"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c0df63cb2955042487fad3aefd2c6e3ae7389ac5dc1beb28921de0b69f779d4"
-
-[[package]]
-name = "arch"
-version = "0.1.0"
-dependencies = [
- "acpi_tables",
- "base",
- "devices",
- "gdbstub",
- "hypervisor",
- "kernel_cmdline",
- "libc",
- "minijail",
- "power_monitor",
- "resources",
- "sync",
- "thiserror",
- "vm_control",
- "vm_memory",
-]
-
-[[package]]
-name = "assertions"
-version = "0.1.0"
-
-[[package]]
-name = "async-task"
-version = "4.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
-
-[[package]]
-name = "async-trait"
-version = "0.1.36"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a265e3abeffdce30b2e26b7a11b222fe37c6067404001b434101457d0385eb92"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "audio_streams"
-version = "0.1.0"
-dependencies = [
- "sync",
- "sys_util",
-]
-
-[[package]]
-name = "autocfg"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
-
-[[package]]
-name = "autocfg"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
-
-[[package]]
-name = "base"
-version = "0.1.0"
-dependencies = [
- "cros_async",
- "data_model",
- "libc",
- "serde",
- "serde_json",
- "smallvec",
- "sync",
- "sys_util",
- "thiserror",
-]
-
-[[package]]
-name = "bit_field"
-version = "0.1.0"
-dependencies = [
- "bit_field_derive",
-]
-
-[[package]]
-name = "bit_field_derive"
-version = "0.1.0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "bitflags"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
-
-[[package]]
-name = "cc"
-version = "1.0.25"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16"
-
-[[package]]
-name = "cfg-if"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
-
-[[package]]
-name = "cloudabi"
-version = "0.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
-dependencies = [
- "bitflags",
-]
-
-[[package]]
-name = "cras-sys"
-version = "0.1.0"
-dependencies = [
- "audio_streams",
- "data_model",
-]
-
-[[package]]
-name = "cros_async"
-version = "0.1.0"
-dependencies = [
- "async-task",
- "async-trait",
- "data_model",
- "futures",
- "intrusive-collections",
- "io_uring",
- "libc",
- "paste",
- "pin-utils",
- "slab",
- "sync",
- "sys_util",
- "thiserror",
-]
-
-[[package]]
-name = "cros_fuzz"
-version = "0.1.0"
-dependencies = [
- "rand_core 0.4.2",
-]
-
-[[package]]
-name = "crosvm"
-version = "0.1.0"
-dependencies = [
- "aarch64",
- "acpi_tables",
- "arch",
- "assertions",
- "audio_streams",
- "base",
- "bit_field",
- "crosvm_plugin",
- "data_model",
- "devices",
- "disk",
- "enumn",
- "gdbstub",
- "hypervisor",
- "kernel_cmdline",
- "kernel_loader",
- "kvm",
- "kvm_sys",
- "libc",
- "libcras",
- "minijail",
- "net_util",
- "p9",
- "protobuf",
- "protos",
- "rand_ish",
- "remain",
- "resources",
- "rutabaga_gfx",
- "sync",
- "tempfile",
- "thiserror",
- "vhost",
- "vm_control",
- "vm_memory",
- "x86_64",
-]
-
-[[package]]
-name = "crosvm-fuzz"
-version = "0.0.1"
-dependencies = [
- "base",
- "cros_fuzz",
- "data_model",
- "devices",
- "disk",
- "fuse",
- "kernel_loader",
- "libc",
- "rand",
- "tempfile",
- "usb_util",
- "vm_memory",
-]
-
-[[package]]
-name = "crosvm_plugin"
-version = "0.17.0"
-dependencies = [
- "base",
- "kvm",
- "kvm_sys",
- "libc",
- "protobuf",
- "protos",
-]
-
-[[package]]
-name = "data_model"
-version = "0.1.0"
-dependencies = [
- "assertions",
- "libc",
- "serde",
-]
-
-[[package]]
-name = "dbus"
-version = "0.6.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48b5f0f36f1eebe901b0e6bee369a77ed3396334bf3f09abd46454a576f71819"
-dependencies = [
- "libc",
- "libdbus-sys",
-]
-
-[[package]]
-name = "devices"
-version = "0.1.0"
-dependencies = [
- "acpi_tables",
- "audio_streams",
- "base",
- "bit_field",
- "cros_async",
- "data_model",
- "disk",
- "enumn",
- "fuse",
- "futures",
- "gpu_display",
- "hypervisor",
- "kvm_sys",
- "libc",
- "libcras",
- "libvda",
- "linux_input_sys",
- "minijail",
- "net_sys",
- "net_util",
- "p9",
- "power_monitor",
- "protos",
- "rand_ish",
- "remain",
- "resources",
- "rutabaga_gfx",
- "serde",
- "smallvec",
- "sync",
- "sys_util",
- "tempfile",
- "thiserror",
- "tpm2",
- "usb_util",
- "vfio_sys",
- "vhost",
- "virtio_sys",
- "vm_control",
- "vm_memory",
- "vmm_vhost",
-]
-
-[[package]]
-name = "disk"
-version = "0.1.0"
-dependencies = [
- "async-trait",
- "base",
- "cros_async",
- "data_model",
- "futures",
- "libc",
- "protobuf",
- "protos",
- "remain",
- "tempfile",
- "vm_memory",
-]
-
-[[package]]
-name = "downcast-rs"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
-
-[[package]]
-name = "enumn"
-version = "0.1.0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "fuchsia-cprng"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
-
-[[package]]
-name = "fuse"
-version = "0.1.0"
-dependencies = [
- "base",
- "bitflags",
- "data_model",
- "enumn",
- "libc",
- "thiserror",
-]
-
-[[package]]
-name = "futures"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6f16056ecbb57525ff698bb955162d0cd03bee84e6241c27ff75c08d8ca5987"
-dependencies = [
- "futures-channel",
- "futures-core",
- "futures-io",
- "futures-sink",
- "futures-task",
- "futures-util",
-]
-
-[[package]]
-name = "futures-channel"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86"
-dependencies = [
- "futures-core",
- "futures-sink",
-]
-
-[[package]]
-name = "futures-core"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866"
-
-[[package]]
-name = "futures-io"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff"
-
-[[package]]
-name = "futures-sink"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "171be33efae63c2d59e6dbba34186fe0d6394fb378069a76dfd80fdcffd43c16"
-
-[[package]]
-name = "futures-task"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9"
-
-[[package]]
-name = "futures-util"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76"
-dependencies = [
- "futures-channel",
- "futures-core",
- "futures-io",
- "futures-sink",
- "futures-task",
- "memchr",
- "pin-utils",
- "slab",
-]
-
-[[package]]
-name = "gdbstub"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "224c17cf54ffe7e084343f25c7f2881a399bea69862ecaf5bc97f0f6586ba0dc"
-dependencies = [
- "cfg-if",
- "log",
- "managed",
- "num-traits",
- "paste",
-]
-
-[[package]]
-name = "getopts"
-version = "0.2.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a7292d30132fb5424b354f5dc02512a86e4c516fe544bb7a25e7f266951b797"
-dependencies = [
- "unicode-width",
-]
-
-[[package]]
-name = "gpu_display"
-version = "0.1.0"
-dependencies = [
- "base",
- "cc",
- "data_model",
- "libc",
- "linux_input_sys",
-]
-
-[[package]]
-name = "hypervisor"
-version = "0.1.0"
-dependencies = [
- "base",
- "bit_field",
- "data_model",
- "downcast-rs",
- "enumn",
- "kvm",
- "kvm_sys",
- "libc",
- "serde",
- "sync",
- "vm_memory",
-]
-
-[[package]]
-name = "integration_tests"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "arch",
- "base",
- "crosvm",
- "libc",
- "tempfile",
-]
-
-[[package]]
-name = "intrusive-collections"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bca8c0bb831cd60d4dda79a58e3705ca6eb47efb65d665651a8d672213ec3db"
-dependencies = [
- "memoffset",
-]
-
-[[package]]
-name = "io_uring"
-version = "0.1.0"
-dependencies = [
- "data_model",
- "libc",
- "sync",
- "sys_util",
-]
-
-[[package]]
-name = "itoa"
-version = "0.4.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
-
-[[package]]
-name = "kernel_cmdline"
-version = "0.1.0"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "kernel_loader"
-version = "0.1.0"
-dependencies = [
- "base",
- "libc",
- "tempfile",
- "vm_memory",
-]
-
-[[package]]
-name = "kvm"
-version = "0.1.0"
-dependencies = [
- "base",
- "data_model",
- "kvm_sys",
- "libc",
- "sync",
- "vm_memory",
-]
-
-[[package]]
-name = "kvm_sys"
-version = "0.1.0"
-dependencies = [
- "base",
- "data_model",
- "libc",
-]
-
-[[package]]
-name = "libc"
-version = "0.2.93"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
-
-[[package]]
-name = "libchromeos"
-version = "0.1.0"
-dependencies = [
- "data_model",
- "futures",
- "libc",
- "log",
- "protobuf",
- "sys_util",
- "thiserror",
- "zeroize",
-]
-
-[[package]]
-name = "libcras"
-version = "0.1.0"
-dependencies = [
- "audio_streams",
- "cras-sys",
- "data_model",
- "libc",
- "sys_util",
-]
-
-[[package]]
-name = "libcrosvm_control"
-version = "0.1.0"
-dependencies = [
- "base",
- "libc",
- "vm_control",
-]
-
-[[package]]
-name = "libdbus-sys"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0"
-dependencies = [
- "pkg-config",
-]
-
-[[package]]
-name = "libvda"
-version = "0.1.0"
-dependencies = [
- "enumn",
- "libc",
- "pkg-config",
-]
-
-[[package]]
-name = "linux_input_sys"
-version = "0.1.0"
-dependencies = [
- "base",
- "data_model",
- "libc",
-]
-
-[[package]]
-name = "log"
-version = "0.4.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4fcce5fa49cc693c312001daf1d13411c4a5283796bac1084299ea3e567113f"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "managed"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
-
-[[package]]
-name = "memchr"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3197e20c7edb283f87c071ddfc7a2cca8f8e0b888c242959846a6fce03c72223"
-
-[[package]]
-name = "memoffset"
-version = "0.5.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
-dependencies = [
- "autocfg 1.0.1",
-]
-
-[[package]]
-name = "minijail"
-version = "0.2.1"
-dependencies = [
- "libc",
- "minijail-sys",
-]
-
-[[package]]
-name = "minijail-sys"
-version = "0.0.11"
-dependencies = [
- "libc",
- "pkg-config",
-]
-
-[[package]]
-name = "net_sys"
-version = "0.1.0"
-dependencies = [
- "base",
-]
-
-[[package]]
-name = "net_util"
-version = "0.1.0"
-dependencies = [
- "base",
- "data_model",
- "libc",
- "net_sys",
-]
-
-[[package]]
-name = "num-traits"
-version = "0.2.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
-dependencies = [
- "autocfg 1.0.1",
-]
-
-[[package]]
-name = "num_cpus"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a69d464bdc213aaaff628444e99578ede64e9c854025aa43b9796530afa9238"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "p9"
-version = "0.1.0"
-dependencies = [
- "libc",
- "libchromeos",
- "wire_format_derive",
-]
-
-[[package]]
-name = "paste"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba7ae1a2180ed02ddfdb5ab70c70d596a26dd642e097bb6fe78b1bde8588ed97"
-
-[[package]]
-name = "pin-utils"
-version = "0.1.0-alpha.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
-
-[[package]]
-name = "pkg-config"
-version = "0.3.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
-
-[[package]]
-name = "poll_token_derive"
-version = "0.1.0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "power_monitor"
-version = "0.1.0"
-dependencies = [
- "base",
- "dbus",
- "protobuf",
- "protoc-rust",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
-dependencies = [
- "unicode-xid",
-]
-
-[[package]]
-name = "protobuf"
-version = "2.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40361836defdd5871ff7e84096c6f6444af7fc157f8ef1789f54f147687caa20"
-
-[[package]]
-name = "protobuf-codegen"
-version = "2.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12c6abd78435445fc86898ebbd0521a68438063d4a73e23527b7134e6bf58b4a"
-dependencies = [
- "protobuf",
-]
-
-[[package]]
-name = "protoc"
-version = "2.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3998c4bc0af8ccbd3cc68245ee9f72663c5ae2fb78bc48ff7719aef11562edea"
-dependencies = [
- "log",
-]
-
-[[package]]
-name = "protoc-rust"
-version = "2.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "234c97039c32bb58a883d0deafa57db37e59428ce536f3bdfe1c46cffec04113"
-dependencies = [
- "protobuf",
- "protobuf-codegen",
- "protoc",
- "tempfile",
-]
-
-[[package]]
-name = "protos"
-version = "0.1.0"
-dependencies = [
- "kvm_sys",
- "protobuf",
- "protoc-rust",
-]
-
-[[package]]
-name = "qcow_utils"
-version = "0.1.0"
-dependencies = [
- "base",
- "disk",
- "getopts",
- "libc",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "rand"
-version = "0.6.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
-dependencies = [
- "autocfg 0.1.7",
- "libc",
- "rand_chacha",
- "rand_core 0.4.2",
- "rand_hc",
- "rand_isaac",
- "rand_jitter",
- "rand_os",
- "rand_pcg",
- "rand_xorshift",
- "winapi",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
-dependencies = [
- "autocfg 0.1.7",
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
-dependencies = [
- "rand_core 0.4.2",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
-
-[[package]]
-name = "rand_hc"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
-dependencies = [
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rand_isaac"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
-dependencies = [
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rand_ish"
-version = "0.1.0"
-
-[[package]]
-name = "rand_jitter"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
-dependencies = [
- "libc",
- "rand_core 0.4.2",
- "winapi",
-]
-
-[[package]]
-name = "rand_os"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
-dependencies = [
- "cloudabi",
- "fuchsia-cprng",
- "libc",
- "rand_core 0.4.2",
- "rdrand",
- "winapi",
-]
-
-[[package]]
-name = "rand_pcg"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
-dependencies = [
- "autocfg 0.1.7",
- "rand_core 0.4.2",
-]
-
-[[package]]
-name = "rand_xorshift"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
-dependencies = [
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rdrand"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
-dependencies = [
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "remain"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99c861227fc40c8da6fdaa3d58144ac84c0537080a43eb1d7d45c28f88dcb888"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "resources"
-version = "0.1.0"
-dependencies = [
- "base",
- "libc",
- "serde",
-]
-
-[[package]]
-name = "rutabaga_gfx"
-version = "0.1.0"
-dependencies = [
- "base",
- "data_model",
- "libc",
- "sync",
-]
-
-[[package]]
-name = "ryu"
-version = "1.0.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
-
-[[package]]
-name = "serde"
-version = "1.0.121"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6159e3c76cab06f6bc466244d43b35e77e9500cd685da87620addadc2a4c40b1"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.121"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3fcab8778dc651bc65cfab2e4eb64996f3c912b74002fb379c94517e1f27c46"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "serde_json"
-version = "1.0.64"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
-dependencies = [
- "itoa",
- "ryu",
- "serde",
-]
-
-[[package]]
-name = "slab"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
-
-[[package]]
-name = "smallvec"
-version = "1.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
-
-[[package]]
-name = "syn"
-version = "1.0.58"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-xid",
-]
-
-[[package]]
-name = "sync"
-version = "0.1.0"
-
-[[package]]
-name = "synstructure"
-version = "0.12.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "unicode-xid",
-]
-
-[[package]]
-name = "sys_util"
-version = "0.1.0"
-dependencies = [
- "android_log-sys",
- "data_model",
- "libc",
- "poll_token_derive",
- "serde",
- "serde_json",
- "sync",
- "tempfile",
-]
-
-[[package]]
-name = "tempfile"
-version = "3.0.7"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "thiserror"
-version = "1.0.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08"
-dependencies = [
- "thiserror-impl",
-]
-
-[[package]]
-name = "thiserror-impl"
-version = "1.0.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "tpm2"
-version = "0.1.0"
-dependencies = [
- "tpm2-sys",
-]
-
-[[package]]
-name = "tpm2-sys"
-version = "0.1.0"
-dependencies = [
- "num_cpus",
- "pkg-config",
-]
-
-[[package]]
-name = "unicode-width"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
-
-[[package]]
-name = "unicode-xid"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
-
-[[package]]
-name = "usb_sys"
-version = "0.1.0"
-dependencies = [
- "base",
-]
-
-[[package]]
-name = "usb_util"
-version = "0.1.0"
-dependencies = [
- "assertions",
- "base",
- "data_model",
- "libc",
- "remain",
- "usb_sys",
-]
-
-[[package]]
-name = "vfio_sys"
-version = "0.1.0"
-dependencies = [
- "base",
-]
-
-[[package]]
-name = "vhost"
-version = "0.1.0"
-dependencies = [
- "assertions",
- "base",
- "libc",
- "net_util",
- "virtio_sys",
- "vm_memory",
-]
-
-[[package]]
-name = "virtio_sys"
-version = "0.1.0"
-dependencies = [
- "base",
-]
-
-[[package]]
-name = "vm_control"
-version = "0.1.0"
-dependencies = [
- "base",
- "data_model",
- "gdbstub",
- "hypervisor",
- "libc",
- "resources",
- "rutabaga_gfx",
- "serde",
- "sync",
- "vm_memory",
-]
-
-[[package]]
-name = "vm_memory"
-version = "0.1.0"
-dependencies = [
- "base",
- "bitflags",
- "cros_async",
- "data_model",
- "libc",
-]
-
-[[package]]
-name = "vmm_vhost"
-version = "0.1.0"
-dependencies = [
- "bitflags",
- "libc",
- "sys_util",
- "tempfile",
-]
-
-[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-
-[[package]]
-name = "wire_format_derive"
-version = "0.1.0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "x86_64"
-version = "0.1.0"
-dependencies = [
- "acpi_tables",
- "arch",
- "assertions",
- "base",
- "data_model",
- "devices",
- "gdbstub",
- "hypervisor",
- "kernel_cmdline",
- "kernel_loader",
- "libc",
- "minijail",
- "remain",
- "resources",
- "sync",
- "vm_control",
- "vm_memory",
-]
-
-[[package]]
-name = "zeroize"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36"
-dependencies = [
- "zeroize_derive",
-]
-
-[[package]]
-name = "zeroize_derive"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "synstructure",
-]
diff --git a/Cargo.toml b/Cargo.toml
index 8c969aca8..3a3742b78 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,9 +1,13 @@
+cargo-features = ["named-profiles"]
+
[package]
name = "crosvm"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
default-run = "crosvm"
+# b:223855233
+resolver = "1"
[lib]
path = "src/crosvm.rs"
@@ -21,57 +25,147 @@ required-features = [ "direct" ]
panic = 'abort'
overflow-checks = true
+[profile.release-test]
+inherits = 'release'
+panic = 'unwind'
+
+# Reproduces the options used when building crosvm for Chrome OS.
+[profile.chromeos]
+inherits = "release"
+opt-level = "s"
+
+# Enables LTO to further reduce the size of the binary.
+[profile.lto]
+inherits = "chromeos"
+lto = true
+
+# We currently need to exclude some crates from the workspace to allow
+# these crates to be independently built by portage. These crates will
+# eventually be moved into separate repositories.
+# The only workspace members that need to be explicitly specified here are those
+# that are not dependencies of the crosvm root crate.
[workspace]
members = [
- "fuzz",
- "qcow_utils",
- "integration_tests",
- "libcrosvm_control",
-]
-exclude = [
- "assertions",
+ "aarch64",
+ "acpi_tables",
+ "arch",
"base",
+ "bit_field",
"cros_async",
- "data_model",
- "rand_ish",
- "sync",
- "sys_util",
- "tempfile",
+ "crosvm-fuzz",
+ "crosvm_control",
+ "crosvm_plugin",
+ "devices",
+ "disk",
+ "fuse",
+ "gpu_display",
+ "hypervisor",
+ "integration_tests",
+ "io_uring",
+ "kernel_cmdline",
+ "kernel_loader",
+ "kvm",
+ "kvm_sys",
+ "libcras_stub",
+ "linux_input_sys",
+ "media/libvda",
+ "net_sys",
+ "net_util",
+ "power_monitor",
+ "protos",
+ "qcow_utils",
+ "resources",
+ "rutabaga_gfx",
+ "serde_keyvalue",
+ "system_api_stub",
+ "tpm2",
+ "tpm2-sys",
+ "usb_sys",
+ "usb_util",
+ "vfio_sys",
+ "vhost",
+ "virtio_sys",
+ "vm_control",
"vm_memory",
+ "x86_64",
+ "third_party/vmm_vhost",
+]
+exclude = [
+ "common/assertions",
+ "common/audio_streams",
+ "common/balloon_control",
+ "common/cros-fuzz",
+ "common/cros_async",
+ "common/cros_asyncv2",
+ "common/data_model",
+ "common/io_uring",
+ "common/p9",
+ "common/sync",
+ "common/sys_util",
+ "common/sys_util_core",
+ "common/win_sys_util",
+ "win_util",
]
[features]
-default = ["audio", "gpu"]
-chromeos = ["base/chromeos"]
-default-no-sandbox = []
-direct = ["devices/direct"]
+all-linux = [
+ # TODO(b/203105868): Enable remaining features on linux builds.
+ "composite-disk",
+ "default",
+ "gdb",
+ "tpm",
+ "virgl_renderer_next",
+ "virgl_renderer",
+ "x",
+ ]
+win64 = []
audio = ["devices/audio"]
+audio_cras = ["devices/audio_cras"]
+chromeos = ["base/chromeos", "audio_cras", "devices/chromeos"]
+composite-disk = ["protos/composite-disk", "protobuf", "disk/composite-disk"]
+default = ["audio", "gpu", "usb"]
+default-no-sandbox = []
+direct = ["devices/direct", "arch/direct", "x86_64/direct"]
+gdb = ["gdbstub", "gdbstub_arch", "arch/gdb", "vm_control/gdb", "x86_64/gdb"]
+gfxstream = ["devices/gfxstream"]
gpu = ["devices/gpu"]
+libvda = ["devices/libvda"]
+linux-armhf = [
+ "composite-disk",
+ "default",
+ "gdb",
+ "tpm",
+ ]
+linux-x86_64 = ["all-linux", "plugin"]
+linux-aarch64 = ["all-linux"]
plugin = ["protos/plugin", "crosvm_plugin", "kvm", "kvm_sys", "protobuf"]
+plugin-render-server = []
power-monitor-powerd = ["arch/power-monitor-powerd"]
+slirp = ["devices/slirp"]
tpm = ["devices/tpm"]
+usb = ["devices/usb"]
video-decoder = ["devices/video-decoder"]
video-encoder = ["devices/video-encoder"]
+virgl_renderer = ["devices/virgl_renderer"]
+virgl_renderer_next = ["rutabaga_gfx/virgl_renderer_next"]
wl-dmabuf = ["devices/minigbm"]
x = ["devices/x"]
-virgl_renderer_next = ["rutabaga_gfx/virgl_renderer_next"]
-composite-disk = ["protos/composite-disk", "protobuf", "disk/composite-disk"]
-virgl_renderer = ["devices/virgl_renderer"]
-gfxstream = ["devices/gfxstream"]
-gdb = ["gdbstub", "thiserror", "arch/gdb", "vm_control/gdb", "x86_64/gdb"]
[dependencies]
+anyhow = "1.0.32"
arch = { path = "arch" }
-assertions = { path = "assertions" }
+assertions = { path = "common/assertions" }
audio_streams = "*"
-base = "*"
+base = { path = "base" }
bit_field = { path = "bit_field" }
+cfg-if = "1.0.0"
crosvm_plugin = { path = "crosvm_plugin", optional = true }
data_model = "*"
devices = { path = "devices" }
disk = { path = "disk" }
-enumn = { path = "enumn" }
-gdbstub = { version = "0.4.0", optional = true }
+enumn = "0.1.0"
+gdbstub = { version = "0.5.0", optional = true }
+gdbstub_arch = { version = "0.1.0", optional = true }
rutabaga_gfx = { path = "rutabaga_gfx"}
hypervisor = { path = "hypervisor" }
kernel_cmdline = { path = "kernel_cmdline" }
@@ -82,15 +176,18 @@ libc = "0.2.93"
libcras = "*"
minijail = "*" # provided by ebuild
net_util = { path = "net_util" }
-p9 = { path = "../vm_tools/p9" }
+p9 = "*"
protobuf = { version = "2.3", optional = true }
protos = { path = "protos", optional = true }
-rand_ish = { path = "rand_ish" }
remain = "*"
resources = { path = "resources" }
-sync = { path = "sync" }
-tempfile = "*"
-thiserror = { version = "1.0.20", optional = true }
+scudo = { version = "0.1", optional = true }
+serde_json = "*"
+serde_keyvalue = { path = "serde_keyvalue" }
+sync = { path = "common/sync" }
+tempfile = "3"
+thiserror = { version = "1.0.20" }
+uuid = { version = "0.8.2" }
vhost = { path = "vhost" }
vm_control = { path = "vm_control" }
acpi_tables = { path = "acpi_tables" }
@@ -103,20 +200,21 @@ x86_64 = { path = "x86_64" }
aarch64 = { path = "aarch64" }
[dev-dependencies]
-base = "*"
+base = { path = "base" }
+lazy_static = "*"
[patch.crates-io]
-assertions = { path = "assertions" }
-audio_streams = { path = "../adhd/audio_streams" } # ignored by ebuild
+assertions = { path = "common/assertions" }
+audio_streams = { path = "common/audio_streams" }
base = { path = "base" }
-cros_fuzz = { path = "../../platform2/cros-fuzz" } # ignored by ebuild
-data_model = { path = "data_model" }
-libchromeos = { path = "../libchromeos-rs" } # ignored by ebuild
-libcras = { path = "../adhd/cras/client/libcras" } # ignored by ebuild
+sys_util_core = { path = "common/sys_util_core" }
+cros_async = { path = "cros_async" }
+cros_fuzz = { path = "common/cros-fuzz" } # ignored by ebuild
+data_model = { path = "common/data_model" }
+libcras = { path = "libcras_stub" } # ignored by ebuild
+p9 = { path = "common/p9" } # ignored by ebuild
+sync = { path = "common/sync" }
+sys_util = { path = "common/sys_util" }
+system_api = { path = "system_api_stub" } # ignored by ebuild
+wire_format_derive = { path = "common/p9/wire_format_derive" } # ignored by ebuild
minijail = { path = "../minijail/rust/minijail" } # ignored by ebuild
-p9 = { path = "../vm_tools/p9" } # ignored by ebuild
-sync = { path = "sync" }
-sys_util = { path = "sys_util" }
-tempfile = { path = "tempfile" }
-wire_format_derive = { path = "../vm_tools/p9/wire_format_derive" } # ignored by ebuild
-vmm_vhost = { path = "../rust/crates/vhost", features = ["vhost-user-master"] } # ignored by ebuild
diff --git a/OWNERS b/OWNERS
index 6a326b06d..29ac295a2 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,5 +1,8 @@
-adelva@google.com
-chirantan@google.com
-dgreid@google.com
-dverkamp@google.com
-zachr@google.com
+acourbot@chromium.org
+dtor@chromium.org
+dverkamp@chromium.org
+keiichiw@chromium.org
+denniskempin@google.com
+paulhsia@chromium.org
+uekawa@chromium.org
+crosvm-bot@crosvm-packages.iam.gserviceaccount.com
diff --git a/OWNERS.android b/OWNERS.android
new file mode 100644
index 000000000..2493044c4
--- /dev/null
+++ b/OWNERS.android
@@ -0,0 +1,5 @@
+adelva@google.com
+denniskempin@google.com
+dverkamp@google.com
+qwandor@google.com
+smoreland@google.com
diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg
deleted file mode 100644
index 91b497341..000000000
--- a/PRESUBMIT.cfg
+++ /dev/null
@@ -1,6 +0,0 @@
-[Hook Overrides]
-cargo_clippy_check: true
-
-[Hook Overrides Options]
-cargo_clippy_check:
- --project=.:bin/preupload-clippy
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 000000000..321bab6f0
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,2 @@
+[Builtin Hooks]
+bpfmt = true
diff --git a/README.chromeos.md b/README.chromeos.md
new file mode 100644
index 000000000..9347a8c1f
--- /dev/null
+++ b/README.chromeos.md
@@ -0,0 +1,17 @@
+# Crosvm on ChromeOS
+
+Here are pointers to useful documentation for working on Crosvm on Chrome OS.
+
+## Uploading changes
+
+See [Creating a CL for Chromium OS Developers](CONTRIBUTING.md#chromiumos-cl).
+
+## Building
+
+See [Building for ChromeOS](https://google.github.io/crosvm/building_crosvm/chromium_os.html) in the
+crosvm mdbook.
+
+## Submitting
+
+See [presubmit checking](CONTRIBUTING.md#presubmit) and
+[postsubmit merging](CONTRIBUTING.md#chromiumos-postsubmit)
diff --git a/README.md b/README.md
index a015eda39..a64ad718e 100644
--- a/README.md
+++ b/README.md
@@ -1,290 +1,20 @@
# crosvm - The Chrome OS Virtual Machine Monitor
-This component, known as crosvm, runs untrusted operating systems along with
-virtualized devices. This only runs VMs through the Linux's KVM interface. What
-makes crosvm unique is a focus on safety within the programming language and a
-sandbox around the virtual devices to protect the kernel from attack in case of
-an exploit in the devices.
-
-## IRC
-
-The channel #crosvm on [freenode](https://webchat.freenode.net/#crosvm) is used
-for technical discussion related to crosvm development and integration.
-
-## Getting started
-
-### Building for CrOS
-
-crosvm on Chromium OS is built with Portage, so it follows the same general
-workflow as any `cros_workon` package. The full package name is
-`chromeos-base/crosvm`.
-
-See the [Chromium OS developer guide] for more on how to build and deploy with
-Portage.
-
-[Chromium OS developer guide]: https://chromium.googlesource.com/chromiumos/docs/+/HEAD/developer_guide.md
-
-### Building with Docker
-
-See the [README](ci/README.md) from the `ci` subdirectory to learn how
-to build and test crosvm in enviroments outside of the Chrome OS chroot.
-
-### Building for Linux
-
->**NOTE:** Building for Linux natively is new and not fully supported.
-
-First, [set up depot_tools] and use `repo` to sync down the crosvm source
-tree. This is a subset of the entire Chromium OS manifest with just enough repos
-to build crosvm.
-
-```sh
-mkdir crosvm
-cd crosvm
-repo init -g crosvm -u https://chromium.googlesource.com/chromiumos/manifest.git --repo-url=https://chromium.googlesource.com/external/repo.git
-repo sync
-```
-
-A basic crosvm build links against `libcap`. On a Debian-based system,
-you can install `libcap-dev`.
-
-Handy Debian one-liner for all build and runtime deps, particularly if you're
-running Crostini:
-```sh
-sudo apt install build-essential libcap-dev libgbm-dev libvirglrenderer-dev libwayland-bin libwayland-dev pkg-config protobuf-compiler python wayland-protocols
-```
-
-Known issues:
-* Seccomp policy files have hardcoded absolute paths. You can either fix up
- the paths locally, or set up an awesome hacky symlink: `sudo mkdir
- /usr/share/policy && sudo ln -s /path/to/crosvm/seccomp/x86_64
- /usr/share/policy/crosvm`. We'll eventually build the precompiled
- policies [into the crosvm binary](http://crbug.com/1052126).
-* Devices can't be jailed if `/var/empty` doesn't exist. `sudo mkdir -p
- /var/empty` to work around this for now.
-* You need read/write permissions for `/dev/kvm` to run tests or other crosvm
- instances. Usually it's owned by the `kvm` group, so `sudo usermod -a -G kvm
- $USER` and then log out and back in again to fix this.
-* Some other features (networking) require `CAP_NET_ADMIN` so those usually
- need to be run as root.
-
-And that's it! You should be able to `cargo build/run/test`.
-
-[set up depot_tools]: https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up
-
-## Usage
-
-To see the usage information for your version of crosvm, run `crosvm` or `crosvm
-run --help`.
-
-### Boot a Kernel
-
-To run a very basic VM with just a kernel and default devices:
-
-```bash
-$ crosvm run "${KERNEL_PATH}"
-```
-
-The uncompressed kernel image, also known as vmlinux, can be found in your kernel
-build directory in the case of x86 at `arch/x86/boot/compressed/vmlinux`.
-
-### Rootfs
-
-#### With a disk image
-
-In most cases, you will want to give the VM a virtual block device to use as a
-root file system:
-
-```bash
-$ crosvm run -r "${ROOT_IMAGE}" "${KERNEL_PATH}"
-```
-
-The root image must be a path to a disk image formatted in a way that the kernel
-can read. Typically this is a squashfs image made with `mksquashfs` or an ext4
-image made with `mkfs.ext4`. By using the `-r` argument, the kernel is
-automatically told to use that image as the root, and therefore can only be
-given once. More disks can be given with `-d` or `--rwdisk` if a writable disk
-is desired.
-
-To run crosvm with a writable rootfs:
-
->**WARNING:** Writable disks are at risk of corruption by a malicious or
-malfunctioning guest OS.
-```bash
-crosvm run --rwdisk "${ROOT_IMAGE}" -p "root=/dev/vda" vmlinux
-```
->**NOTE:** If more disks arguments are added prior to the desired rootfs image,
-the `root=/dev/vda` must be adjusted to the appropriate letter.
-
-#### With virtiofs
-
-Linux kernel 5.4+ is required for using virtiofs. This is convenient for testing.
-The file system must be named "mtd*" or "ubi*".
-
-```bash
-crosvm run --shared-dir "/:mtdfake:type=fs:cache=always" \
- -p "rootfstype=virtiofs root=mtdfake" vmlinux
-```
-
-### Control Socket
-
-If the control socket was enabled with `-s`, the main process can be controlled
-while crosvm is running. To tell crosvm to stop and exit, for example:
-
->**NOTE:** If the socket path given is for a directory, a socket name underneath
-that path will be generated based on crosvm's PID.
-```bash
-$ crosvm run -s /run/crosvm.sock ${USUAL_CROSVM_ARGS}
- <in another shell>
-$ crosvm stop /run/crosvm.sock
-```
->**WARNING:** The guest OS will not be notified or gracefully shutdown.
-
-This will cause the original crosvm process to exit in an orderly fashion,
-allowing it to clean up any OS resources that might have stuck around if crosvm
-were terminated early.
-
-### Multiprocess Mode
-
-By default crosvm runs in multiprocess mode. Each device that supports running
-inside of a sandbox will run in a jailed child process of crosvm. The
-appropriate minijail seccomp policy files must be present either in
-`/usr/share/policy/crosvm` or in the path specified by the
-`--seccomp-policy-dir` argument. The sandbox can be disabled for testing with
-the `--disable-sandbox` option.
-
-### Virtio Wayland
-
-Virtio Wayland support requires special support on the part of the guest and as
-such is unlikely to work out of the box unless you are using a Chrome OS kernel
-along with a `termina` rootfs.
-
-To use it, ensure that the `XDG_RUNTIME_DIR` enviroment variable is set and that
-the path `$XDG_RUNTIME_DIR/wayland-0` points to the socket of the Wayland
-compositor you would like the guest to use.
-
-### GDB Support
-
-crosvm supports [GDB Remote Serial Protocol] to allow developers to debug guest
-kernel via GDB.
-
-You can enable the feature by `--gdb` flag:
-
-```sh
-# Use uncompressed vmlinux
-$ crosvm run --gdb <port> ${USUAL_CROSVM_ARGS} vmlinux
-```
-
-Then, you can start GDB in another shell.
-
-```sh
-$ gdb vmlinux
-(gdb) target remote :<port>
-(gdb) hbreak start_kernel
-(gdb) c
-<start booting in the other shell>
-```
-
-For general techniques for debugging the Linux kernel via GDB, see this
-[kernel documentation].
-
-[GDB Remote Serial Protocol]: https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html
-[kernel documentation]: https://www.kernel.org/doc/html/latest/dev-tools/gdb-kernel-debugging.html
-
-## Defaults
-
-The following are crosvm's default arguments and how to override them.
-
-* 256MB of memory (set with `-m`)
-* 1 virtual CPU (set with `-c`)
-* no block devices (set with `-r`, `-d`, or `--rwdisk`)
-* no network (set with `--host_ip`, `--netmask`, and `--mac`)
-* virtio wayland support if `XDG_RUNTIME_DIR` enviroment variable is set (disable with `--no-wl`)
-* only the kernel arguments necessary to run with the supported devices (add more with `-p`)
-* run in multiprocess mode (run in single process mode with `--disable-sandbox`)
-* no control socket (set with `-s`)
-
-## System Requirements
-
-A Linux kernel with KVM support (check for `/dev/kvm`) is required to run
-crosvm. In order to run certain devices, there are additional system
-requirements:
-
-* `virtio-wayland` - The `memfd_create` syscall, introduced in Linux 3.17, and a Wayland compositor.
-* `vsock` - Host Linux kernel with vhost-vsock support, introduced in Linux 4.8.
-* `multiprocess` - Host Linux kernel with seccomp-bpf and Linux namespacing support.
-* `virtio-net` - Host Linux kernel with TUN/TAP support (check for `/dev/net/tun`) and running with `CAP_NET_ADMIN` privileges.
-
-## Emulated Devices
-
-| Device | Description |
-|------------------|------------------------------------------------------------------------------------|
-| `CMOS/RTC` | Used to get the current calendar time. |
-| `i8042` | Used by the guest kernel to exit crosvm. |
-| `serial` | x86 I/O port driven serial devices that print to stdout and take input from stdin. |
-| `virtio-block` | Basic read/write block device. |
-| `virtio-net` | Device to interface the host and guest networks. |
-| `virtio-rng` | Entropy source used to seed guest OS's entropy pool. |
-| `virtio-vsock` | Enabled VSOCKs for the guests. |
-| `virtio-wayland` | Allowed guest to use host Wayland socket. |
-
-## Contributing
-
-### Code Health
-
-#### `test_all`
-
-Crosvm provides docker containers to build and run tests for both x86_64 and
-aarch64, which can be run with the `./test_all` script.
-See `ci/README.md` for more details on how to use the containers for local
-development.
-
-#### `rustfmt`
-
-All code should be formatted with `rustfmt`. We have a script that applies
-rustfmt to all Rust code in the crosvm repo: please run `bin/fmt` before
-checking in a change. This is different from `cargo fmt --all` which formats
-multiple crates but a single workspace only; crosvm consists of multiple
-workspaces.
-
-#### `clippy`
-
-The `clippy` linter is used to check for common Rust problems. The crosvm
-project uses a specific set of `clippy` checks; please run `bin/clippy` before
-checking in a change.
-
-#### Dependencies
-
-With a few exceptions, external dependencies inside of the `Cargo.toml` files
-are not allowed. The reason being that community made crates tend to explode the
-binary size by including dozens of transitive dependencies. All these
-dependencies also must be reviewed to ensure their suitability to the crosvm
-project. Currently allowed crates are:
-
-* `cc` - Build time dependency needed to build C source code used in crosvm.
-* `libc` - Required to use the standard library, this crate is a simple wrapper around `libc`'s symbols.
-
-### Code Overview
-
-The crosvm source code is written in Rust and C. To build, crosvm generally
-requires the most recent stable version of rustc.
-
-Source code is organized into crates, each with their own unit tests. These
-crates are:
-
-* `crosvm` - The top-level binary front-end for using crosvm.
-* `devices` - Virtual devices exposed to the guest OS.
-* `kernel_loader` - Loads elf64 kernel files to a slice of memory.
-* `kvm_sys` - Low-level (mostly) auto-generated structures and constants for using KVM.
-* `kvm` - Unsafe, low-level wrapper code for using `kvm_sys`.
-* `net_sys` - Low-level (mostly) auto-generated structures and constants for creating TUN/TAP devices.
-* `net_util` - Wrapper for creating TUN/TAP devices.
-* `sys_util` - Mostly safe wrappers for small system facilities such as `eventfd` or `syslog`.
-* `syscall_defines` - Lists of syscall numbers in each architecture used to make syscalls not supported in `libc`.
-* `vhost` - Wrappers for creating vhost based devices.
-* `virtio_sys` - Low-level (mostly) auto-generated structures and constants for interfacing with kernel vhost support.
-* `vm_control` - IPC for the VM.
-* `x86_64` - Support code specific to 64 bit intel machines.
-
-The `seccomp` folder contains minijail seccomp policy files for each sandboxed
-device. Because some syscalls vary by architecture, the seccomp policies are
-split by architecture.
+crosvm is a virtual machine monitor (VMM) based on Linux’s KVM hypervisor, with a focus on
+simplicity, security, and speed. crosvm is intended to run Linux guests, originally as a security
+boundary for running native applications on the Chrome OS platform. Compared to QEMU, crosvm doesn’t
+emulate architectures or real hardware, instead concentrating on paravirtualized devices, such as
+the virtio standard.
+
+crosvm is currently used to run Linux/Android guests on Chrome OS devices.
+
+- [Documentation](https://google.github.io/crosvm/)
+- [Source code](https://chromium.googlesource.com/chromiumos/platform/crosvm/)
+ - [API doc](https://google.github.io/crosvm/doc/crosvm/), useful for searching API.
+ - For contribution, see
+ [the contributor guide](https://google.github.io/crosvm/contributing.html). Mirror repository is
+ available at [GitHub](https://github.com/google/crosvm) for your convenience, but we don't
+ accept bug reports or pull requests there.
+- [Issue tracker](https://bugs.chromium.org/p/chromium/issues/list?q=component:OS%3ESystems%3EContainers)
+
+![Logo](./logo/logo_512.png)
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 000000000..f6e1c4f28
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+ {
+ "path": "packages/modules/Virtualization"
+ }
+ ]
+}
diff --git a/aarch64/Android.bp b/aarch64/Android.bp
index ec4e6704c..1707ea1dd 100644
--- a/aarch64/Android.bp
+++ b/aarch64/Android.bp
@@ -1,4 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
package {
// See: http://go/android-license-faq
@@ -9,68 +10,15 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "aarch64_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "aarch64",
- // has rustc warnings
- srcs: ["src/lib.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libarch",
- "libbase_rust",
- "libdata_model",
- "libdevices",
- "libhypervisor",
- "libkernel_cmdline",
- "libkvm",
- "libkvm_sys",
- "liblibc",
- "libminijail_rust",
- "libresources",
- "libsync_rust",
- "libvm_control",
- "libvm_memory",
- ],
- proc_macros: ["libremain"],
- target: {
- // It is necessary to disable this specifically as well as the arch below, because
- // crosvm_defaults enables it and the more specific target apparently takes precedence over
- // the less specific arch.
- linux_glibc_x86_64: {
- enabled: false,
- },
- },
- arch: {
- x86_64: {
- enabled: false,
- },
- },
-}
-
-rust_test_host {
- name: "aarch64_host_test_src_lib",
- defaults: ["aarch64_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "aarch64_device_test_src_lib",
- defaults: ["aarch64_defaults"],
-}
-
rust_library {
name: "libaarch64",
defaults: ["crosvm_defaults"],
- // has rustc warnings
host_supported: true,
crate_name: "aarch64",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libarch",
"libbase_rust",
@@ -81,9 +29,11 @@ rust_library {
"libkvm",
"libkvm_sys",
"liblibc",
+ "libmemoffset",
"libminijail_rust",
"libresources",
"libsync_rust",
+ "libthiserror",
"libvm_control",
"libvm_memory",
],
@@ -95,101 +45,14 @@ rust_library {
linux_glibc_x86_64: {
enabled: false,
},
+ linux_musl_x86_64: {
+ enabled: false,
+ },
},
arch: {
x86_64: {
enabled: false,
},
},
-}
-// dependent_library ["feature_list"]
-// ../../adhd/audio_streams/src/audio_streams.rs
-// ../../adhd/cras/client/cras-sys/src/lib.rs
-// ../../adhd/cras/client/libcras/src/libcras.rs
-// ../../libchromeos-rs/src/lib.rs
-// ../../minijail/rust/minijail-sys/lib.rs
-// ../../minijail/rust/minijail/src/lib.rs
-// ../../vm_tools/p9/src/lib.rs
-// ../../vm_tools/p9/wire_format_derive/wire_format_derive.rs
-// ../acpi_tables/src/lib.rs
-// ../arch/src/lib.rs
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../bit_field/bit_field_derive/bit_field_derive.rs
-// ../bit_field/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../devices/src/lib.rs
-// ../disk/src/disk.rs
-// ../enumn/src/lib.rs
-// ../fuse/src/lib.rs
-// ../hypervisor/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../kernel_cmdline/src/kernel_cmdline.rs
-// ../kvm/src/lib.rs
-// ../kvm_sys/src/lib.rs
-// ../linux_input_sys/src/lib.rs
-// ../net_sys/src/lib.rs
-// ../net_util/src/lib.rs
-// ../power_monitor/src/lib.rs
-// ../rand_ish/src/lib.rs
-// ../resources/src/lib.rs
-// ../rutabaga_gfx/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../syscall_defines/src/lib.rs
-// ../tempfile/src/lib.rs
-// ../usb_sys/src/lib.rs
-// ../usb_util/src/lib.rs
-// ../vfio_sys/src/lib.rs
-// ../vhost/src/lib.rs
-// ../virtio_sys/src/lib.rs
-// ../vm_control/src/lib.rs
-// ../vm_memory/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.45
-// autocfg-1.0.1
-// base-0.1.0
-// bitflags-1.2.1 "default"
-// cfg-if-1.0.0
-// downcast-rs-1.2.0 "default,std"
-// futures-0.3.13 "alloc,async-await,default,executor,futures-executor,std"
-// futures-channel-0.3.13 "alloc,futures-sink,sink,std"
-// futures-core-0.3.13 "alloc,std"
-// futures-executor-0.3.13 "std"
-// futures-io-0.3.13 "std"
-// futures-macro-0.3.13
-// futures-sink-0.3.13 "alloc,std"
-// futures-task-0.3.13 "alloc,std"
-// futures-util-0.3.13 "alloc,async-await,async-await-macro,channel,futures-channel,futures-io,futures-macro,futures-sink,io,memchr,proc-macro-hack,proc-macro-nested,sink,slab,std"
-// getrandom-0.2.2 "std"
-// intrusive-collections-0.9.0 "alloc,default"
-// libc-0.2.87 "default,std"
-// log-0.4.14
-// memchr-2.3.4 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.4
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// pkg-config-0.3.19
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro-hack-0.5.19
-// proc-macro-nested-0.1.7
-// proc-macro2-1.0.24 "default,proc-macro"
-// protobuf-2.22.0
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.3 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.0 "std"
-// rand_core-0.6.2 "alloc,getrandom,std"
-// remain-0.2.2
-// remove_dir_all-0.5.3
-// serde-1.0.123 "default,derive,serde_derive,std"
-// serde_derive-1.0.123 "default"
-// slab-0.4.2
-// syn-1.0.61 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
+}
diff --git a/aarch64/Cargo.toml b/aarch64/Cargo.toml
index 9ab2c7ee5..c4fafcd62 100644
--- a/aarch64/Cargo.toml
+++ b/aarch64/Cargo.toml
@@ -2,21 +2,23 @@
name = "aarch64"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
arch = { path = "../arch" }
-data_model = { path = "../data_model" }
+data_model = { path = "../common/data_model" }
devices = { path = "../devices" }
hypervisor = { path = "../hypervisor" }
kernel_cmdline = { path = "../kernel_cmdline" }
kvm = { path = "../kvm" }
kvm_sys = { path = "../kvm_sys" }
libc = "*"
+memoffset = "0.6"
minijail = { path = "../../minijail/rust/minijail" } # ignored by ebuild
remain = "*"
resources = { path = "../resources" }
-sync = { path = "../sync" }
+sync = { path = "../common/sync" }
base = { path = "../base" }
+thiserror = "*"
vm_control = { path = "../vm_control" }
vm_memory = { path = "../vm_memory" }
diff --git a/aarch64/TEST_MAPPING b/aarch64/TEST_MAPPING
index 685bee59c..238ea36ee 100644
--- a/aarch64/TEST_MAPPING
+++ b/aarch64/TEST_MAPPING
@@ -2,7 +2,7 @@
{
"presubmit": [
{
- "name": "aarch64_device_test_src_lib"
+ "name": "aarch64_test_src_lib"
}
]
}
diff --git a/aarch64/cargo2android.json b/aarch64/cargo2android.json
new file mode 100644
index 000000000..42ec06fa6
--- /dev/null
+++ b/aarch64/cargo2android.json
@@ -0,0 +1,8 @@
+{
+ "add-module-block": "cargo2android_arch.bp",
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/aarch64/cargo2android_arch.bp b/aarch64/cargo2android_arch.bp
new file mode 100644
index 000000000..2a2fb981d
--- /dev/null
+++ b/aarch64/cargo2android_arch.bp
@@ -0,0 +1,16 @@
+target: {
+ // It is necessary to disable this specifically as well as the arch below, because
+ // crosvm_defaults enables it and the more specific target apparently takes precedence over
+ // the less specific arch.
+ linux_glibc_x86_64: {
+ enabled: false,
+ },
+ linux_musl_x86_64: {
+ enabled: false,
+ },
+},
+arch: {
+ x86_64: {
+ enabled: false,
+ },
+}
diff --git a/aarch64/src/fdt.rs b/aarch64/src/fdt.rs
index 4ae191d34..1f3347f1e 100644
--- a/aarch64/src/fdt.rs
+++ b/aarch64/src/fdt.rs
@@ -2,13 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+use std::collections::BTreeMap;
use std::fs::File;
use std::io::Read;
use arch::fdt::{Error, FdtWriter, Result};
use arch::SERIAL_ADDR;
use devices::{PciAddress, PciInterruptPin};
-use hypervisor::PsciVersion;
+use hypervisor::{PsciVersion, PSCI_0_2, PSCI_1_0};
use vm_memory::{GuestAddress, GuestMemory};
// This is the start of DRAM in the physical address space.
@@ -33,18 +34,16 @@ use crate::AARCH64_SERIAL_2_4_IRQ;
use crate::AARCH64_SERIAL_SIZE;
use crate::AARCH64_SERIAL_SPEED;
-// These are related to guest virtio devices.
-use crate::AARCH64_MMIO_BASE;
-use crate::AARCH64_MMIO_SIZE;
-use crate::AARCH64_PCI_CFG_BASE;
-use crate::AARCH64_PCI_CFG_SIZE;
-
use crate::AARCH64_PMU_IRQ;
// This is an arbitrary number to specify the node for the GIC.
// If we had a more complex interrupt architecture, then we'd need an enum for
// these.
const PHANDLE_GIC: u32 = 1;
+const PHANDLE_RESTRICTED_DMA_POOL: u32 = 2;
+
+// CPUs are assigned phandles starting with this number.
+const PHANDLE_CPU0: u32 = 0x100;
// These are specified by the Linux GIC bindings
const GIC_FDT_IRQ_NUM_CELLS: u32 = 3;
@@ -67,7 +66,33 @@ fn create_memory_node(fdt: &mut FdtWriter, guest_mem: &GuestMemory) -> Result<()
Ok(())
}
-fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> Result<()> {
+fn create_resv_memory_node(fdt: &mut FdtWriter, resv_size: Option<u64>) -> Result<Option<u32>> {
+ if let Some(resv_size) = resv_size {
+ let resv_memory_node = fdt.begin_node("reserved-memory")?;
+ fdt.property_u32("#address-cells", 0x2)?;
+ fdt.property_u32("#size-cells", 0x2)?;
+ fdt.property_null("ranges")?;
+
+ let restricted_dma_pool = fdt.begin_node("restricted_dma_reserved")?;
+ fdt.property_u32("phandle", PHANDLE_RESTRICTED_DMA_POOL)?;
+ fdt.property_string("compatible", "restricted-dma-pool")?;
+ fdt.property_u64("size", resv_size)?;
+ fdt.property_u64("alignment", base::pagesize() as u64)?;
+ fdt.end_node(restricted_dma_pool)?;
+
+ fdt.end_node(resv_memory_node)?;
+ Ok(Some(PHANDLE_RESTRICTED_DMA_POOL))
+ } else {
+ Ok(None)
+ }
+}
+
+fn create_cpu_nodes(
+ fdt: &mut FdtWriter,
+ num_cpus: u32,
+ cpu_clusters: Vec<Vec<usize>>,
+ cpu_capacity: BTreeMap<usize, u32>,
+) -> Result<()> {
let cpus_node = fdt.begin_node("cpus")?;
fdt.property_u32("#address-cells", 0x1)?;
fdt.property_u32("#size-cells", 0x0)?;
@@ -81,8 +106,29 @@ fn create_cpu_nodes(fdt: &mut FdtWriter, num_cpus: u32) -> Result<()> {
fdt.property_string("enable-method", "psci")?;
}
fdt.property_u32("reg", cpu_id)?;
+ fdt.property_u32("phandle", PHANDLE_CPU0 + cpu_id)?;
+
+ if let Some(capacity) = cpu_capacity.get(&(cpu_id as usize)) {
+ fdt.property_u32("capacity-dmips-mhz", *capacity)?;
+ }
+
fdt.end_node(cpu_node)?;
}
+
+ if !cpu_clusters.is_empty() {
+ let cpu_map_node = fdt.begin_node("cpu-map")?;
+ for (cluster_idx, cpus) in cpu_clusters.iter().enumerate() {
+ let cluster_node = fdt.begin_node(&format!("cluster{}", cluster_idx))?;
+ for (core_idx, cpu_id) in cpus.iter().enumerate() {
+ let core_node = fdt.begin_node(&format!("core{}", core_idx))?;
+ fdt.property_u32("cpu", PHANDLE_CPU0 + *cpu_id as u32)?;
+ fdt.end_node(core_node)?;
+ }
+ fdt.end_node(cluster_node)?;
+ }
+ fdt.end_node(cpu_map_node)?;
+ }
+
fdt.end_node(cpus_node)?;
Ok(())
}
@@ -177,15 +223,29 @@ fn create_serial_nodes(fdt: &mut FdtWriter) -> Result<()> {
Ok(())
}
-fn create_psci_node(fdt: &mut FdtWriter, version: &PsciVersion) -> Result<()> {
- let mut compatible = vec![format!("arm,psci-{}.{}", version.major, version.minor)];
- if version.major == 1 {
- // Put `psci-0.2` as well because PSCI 1.0 is compatible with PSCI 0.2.
- compatible.push(format!("arm,psci-0.2"))
- };
+fn psci_compatible(version: &PsciVersion) -> Vec<&str> {
+ // The PSCI kernel driver only supports compatible strings for the following
+ // backward-compatible versions.
+ let supported = [(PSCI_1_0, "arm,psci-1.0"), (PSCI_0_2, "arm,psci-0.2")];
+
+ let mut compatible: Vec<_> = supported
+ .iter()
+ .filter(|&(v, _)| *version >= *v)
+ .map(|&(_, c)| c)
+ .collect();
+ // The PSCI kernel driver also supports PSCI v0.1, which is NOT forward-compatible.
+ if compatible.is_empty() {
+ compatible = vec!["arm,psci"];
+ }
+
+ compatible
+}
+
+fn create_psci_node(fdt: &mut FdtWriter, version: &PsciVersion) -> Result<()> {
+ let compatible = psci_compatible(version);
let psci_node = fdt.begin_node("psci")?;
- fdt.property_string_list("compatible", compatible)?;
+ fdt.property_string_list("compatible", &compatible)?;
// Only support aarch64 guest
fdt.property_string("method", "hvc")?;
fdt.end_node(psci_node)?;
@@ -229,42 +289,85 @@ fn create_chosen_node(
Ok(())
}
+/// PCI host controller address range.
+///
+/// This represents a single entry in the "ranges" property for a PCI host controller.
+///
+/// See [PCI Bus Binding to Open Firmware](https://www.openfirmware.info/data/docs/bus.pci.pdf)
+/// and https://www.kernel.org/doc/Documentation/devicetree/bindings/pci/host-generic-pci.txt
+/// for more information.
+#[derive(Copy, Clone)]
+pub struct PciRange {
+ pub space: PciAddressSpace,
+ pub bus_address: u64,
+ pub cpu_physical_address: u64,
+ pub size: u64,
+ pub prefetchable: bool,
+}
+
+/// PCI address space.
+#[derive(Copy, Clone)]
+#[allow(dead_code)]
+pub enum PciAddressSpace {
+ /// PCI configuration space
+ Configuration = 0b00,
+ /// I/O space
+ Io = 0b01,
+ /// 32-bit memory space
+ Memory = 0b10,
+ /// 64-bit memory space
+ Memory64 = 0b11,
+}
+
+/// Location of memory-mapped PCI configuration space.
+#[derive(Copy, Clone)]
+pub struct PciConfigRegion {
+ /// Physical address of the base of the memory-mapped PCI configuration region.
+ pub base: u64,
+ /// Size of the PCI configuration region in bytes.
+ pub size: u64,
+}
+
fn create_pci_nodes(
fdt: &mut FdtWriter,
pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>,
- pci_device_base: u64,
- pci_device_size: u64,
+ cfg: PciConfigRegion,
+ ranges: &[PciRange],
+ dma_pool_phandle: Option<u32>,
) -> Result<()> {
// Add devicetree nodes describing a PCI generic host controller.
// See Documentation/devicetree/bindings/pci/host-generic-pci.txt in the kernel
// and "PCI Bus Binding to IEEE Std 1275-1994".
- let ranges = [
- // mmio addresses
- 0x3000000, // (ss = 11: 64-bit memory space)
- (AARCH64_MMIO_BASE >> 32) as u32, // PCI address
- AARCH64_MMIO_BASE as u32,
- (AARCH64_MMIO_BASE >> 32) as u32, // CPU address
- AARCH64_MMIO_BASE as u32,
- (AARCH64_MMIO_SIZE >> 32) as u32, // size
- AARCH64_MMIO_SIZE as u32,
- // device addresses
- 0x3000000, // (ss = 11: 64-bit memory space)
- (pci_device_base >> 32) as u32, // PCI address
- pci_device_base as u32,
- (pci_device_base >> 32) as u32, // CPU address
- pci_device_base as u32,
- (pci_device_size >> 32) as u32, // size
- pci_device_size as u32,
- ];
+ let ranges: Vec<u32> = ranges
+ .iter()
+ .map(|r| {
+ let ss = r.space as u32;
+ let p = r.prefetchable as u32;
+ [
+ // BUS_ADDRESS(3) encoded as defined in OF PCI Bus Binding
+ (ss << 24) | (p << 30),
+ (r.bus_address >> 32) as u32,
+ r.bus_address as u32,
+ // CPU_PHYSICAL(2)
+ (r.cpu_physical_address >> 32) as u32,
+ r.cpu_physical_address as u32,
+ // SIZE(2)
+ (r.size >> 32) as u32,
+ r.size as u32,
+ ]
+ })
+ .flatten()
+ .collect();
+
let bus_range = [0, 0]; // Only bus 0
- let reg = [AARCH64_PCI_CFG_BASE, AARCH64_PCI_CFG_SIZE];
+ let reg = [cfg.base, cfg.size];
let mut interrupts: Vec<u32> = Vec::new();
let mut masks: Vec<u32> = Vec::new();
for (address, irq_num, irq_pin) in pci_irqs.iter() {
// PCI_DEVICE(3)
- interrupts.push(address.to_config_address(0));
+ interrupts.push(address.to_config_address(0, 8));
interrupts.push(0);
interrupts.push(0);
@@ -302,6 +405,9 @@ fn create_pci_nodes(
fdt.property_array_u32("interrupt-map", &interrupts)?;
fdt.property_array_u32("interrupt-map-mask", &masks)?;
fdt.property_null("dma-coherent")?;
+ if let Some(dma_pool_phandle) = dma_pool_phandle {
+ fdt.property_u32("memory-region", dma_pool_phandle)?;
+ }
fdt.end_node(pci_node)?;
Ok(())
@@ -343,10 +449,10 @@ fn create_rtc_node(fdt: &mut FdtWriter) -> Result<()> {
/// * `fdt_max_size` - The amount of space reserved for the device tree
/// * `guest_mem` - The guest memory object
/// * `pci_irqs` - List of PCI device address to PCI interrupt number and pin mappings
+/// * `pci_cfg` - Location of the memory-mapped PCI configuration space.
+/// * `pci_ranges` - Memory ranges accessible via the PCI host controller.
/// * `num_cpus` - Number of virtual CPUs the guest will have
/// * `fdt_load_offset` - The offset into physical memory for the device tree
-/// * `pci_device_base` - The offset into physical memory for PCI device memory
-/// * `pci_device_size` - The size of PCI device memory
/// * `cmdline` - The kernel commandline
/// * `initrd` - An optional tuple of initrd guest physical address and size
/// * `android_fstab` - An optional file holding Android fstab entries
@@ -356,16 +462,19 @@ pub fn create_fdt(
fdt_max_size: usize,
guest_mem: &GuestMemory,
pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>,
+ pci_cfg: PciConfigRegion,
+ pci_ranges: &[PciRange],
num_cpus: u32,
+ cpu_clusters: Vec<Vec<usize>>,
+ cpu_capacity: BTreeMap<usize, u32>,
fdt_load_offset: u64,
- pci_device_base: u64,
- pci_device_size: u64,
cmdline: &str,
initrd: Option<(GuestAddress, usize)>,
android_fstab: Option<File>,
is_gicv3: bool,
use_pmu: bool,
psci_version: PsciVersion,
+ swiotlb: Option<u64>,
) -> Result<()> {
let mut fdt = FdtWriter::new(&[]);
@@ -380,7 +489,8 @@ pub fn create_fdt(
}
create_chosen_node(&mut fdt, cmdline, initrd)?;
create_memory_node(&mut fdt, guest_mem)?;
- create_cpu_nodes(&mut fdt, num_cpus)?;
+ let dma_pool_phandle = create_resv_memory_node(&mut fdt, swiotlb)?;
+ create_cpu_nodes(&mut fdt, num_cpus, cpu_clusters, cpu_capacity)?;
create_gic_node(&mut fdt, is_gicv3, num_cpus as u64)?;
create_timer_node(&mut fdt, num_cpus)?;
if use_pmu {
@@ -388,7 +498,7 @@ pub fn create_fdt(
}
create_serial_nodes(&mut fdt)?;
create_psci_node(&mut fdt, &psci_version)?;
- create_pci_nodes(&mut fdt, pci_irqs, pci_device_base, pci_device_size)?;
+ create_pci_nodes(&mut fdt, pci_irqs, pci_cfg, pci_ranges, dma_pool_phandle)?;
create_rtc_node(&mut fdt)?;
// End giant node
fdt.end_node(root_node)?;
@@ -404,3 +514,51 @@ pub fn create_fdt(
}
Ok(())
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn psci_compatible_v0_1() {
+ assert_eq!(
+ psci_compatible(&PsciVersion::new(0, 1).unwrap()),
+ vec!["arm,psci"]
+ );
+ }
+
+ #[test]
+ fn psci_compatible_v0_2() {
+ assert_eq!(
+ psci_compatible(&PsciVersion::new(0, 2).unwrap()),
+ vec!["arm,psci-0.2"]
+ );
+ }
+
+ #[test]
+ fn psci_compatible_v0_5() {
+ // Only the 0.2 version supported by the kernel should be added.
+ assert_eq!(
+ psci_compatible(&PsciVersion::new(0, 5).unwrap()),
+ vec!["arm,psci-0.2"]
+ );
+ }
+
+ #[test]
+ fn psci_compatible_v1_0() {
+ // Both 1.0 and 0.2 should be listed, in that order.
+ assert_eq!(
+ psci_compatible(&PsciVersion::new(1, 0).unwrap()),
+ vec!["arm,psci-1.0", "arm,psci-0.2"]
+ );
+ }
+
+ #[test]
+ fn psci_compatible_v1_5() {
+ // Only the 1.0 and 0.2 versions supported by the kernel should be listed.
+ assert_eq!(
+ psci_compatible(&PsciVersion::new(1, 5).unwrap()),
+ vec!["arm,psci-1.0", "arm,psci-0.2"]
+ );
+ }
+}
diff --git a/aarch64/src/lib.rs b/aarch64/src/lib.rs
index 4a3a35ecf..0d9130fcf 100644
--- a/aarch64/src/lib.rs
+++ b/aarch64/src/lib.rs
@@ -2,30 +2,34 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#![cfg(any(target_arch = "arm", target_arch = "aarch64"))]
+
use std::collections::BTreeMap;
-use std::error::Error as StdError;
-use std::fmt::{self, Display};
-use std::io::{self};
+use std::io;
use std::sync::Arc;
-use arch::{
- get_serial_cmdline, GetSerialCmdlineError, RunnableLinuxVm, SerialHardware, SerialParameters,
- VmComponents, VmImage,
+use arch::{get_serial_cmdline, GetSerialCmdlineError, RunnableLinuxVm, VmComponents, VmImage};
+use base::{Event, MemoryMappingBuilder};
+use devices::serial_device::{SerialHardware, SerialParameters};
+use devices::{
+ Bus, BusDeviceObj, BusError, IrqChip, IrqChipAArch64, PciAddress, PciConfigMmio, PciDevice,
+};
+use hypervisor::{
+ arm64_core_reg, DeviceKind, Hypervisor, HypervisorCap, ProtectionType, VcpuAArch64,
+ VcpuFeature, Vm, VmAArch64,
};
-use base::Event;
-use devices::{Bus, BusError, IrqChip, IrqChipAArch64, PciConfigMmio, PciDevice, ProtectionType};
-use hypervisor::{DeviceKind, Hypervisor, HypervisorCap, VcpuAArch64, VcpuFeature, VmAArch64};
use minijail::Minijail;
use remain::sorted;
-use resources::SystemAllocator;
+use resources::{range_inclusive_len, MemRegion, SystemAllocator, SystemAllocatorConfig};
use sync::Mutex;
+use thiserror::Error;
use vm_control::BatteryType;
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
mod fdt;
// We place the kernel at offset 8MB
-const AARCH64_KERNEL_OFFSET: u64 = 0x80000;
+const AARCH64_KERNEL_OFFSET: u64 = 0x800000;
const AARCH64_FDT_MAX_SIZE: u64 = 0x200000;
const AARCH64_INITRD_ALIGN: u64 = 0x1000000;
@@ -36,6 +40,7 @@ const AARCH64_GIC_CPUI_SIZE: u64 = 0x20000;
// This indicates the start of DRAM inside the physical address space.
const AARCH64_PHYS_MEM_START: u64 = 0x80000000;
const AARCH64_AXI_BASE: u64 = 0x40000000;
+const AARCH64_PLATFORM_MMIO_SIZE: u64 = 0x800000;
// FDT is placed at the front of RAM when booting in BIOS mode.
const AARCH64_FDT_OFFSET_IN_BIOS_MODE: u64 = 0x0;
@@ -47,6 +52,10 @@ const AARCH64_PROTECTED_VM_FW_MAX_SIZE: u64 = 0x200000;
const AARCH64_PROTECTED_VM_FW_START: u64 =
AARCH64_PHYS_MEM_START - AARCH64_PROTECTED_VM_FW_MAX_SIZE;
+const AARCH64_PVTIME_IPA_MAX_SIZE: u64 = 0x10000;
+const AARCH64_PVTIME_IPA_START: u64 = AARCH64_PROTECTED_VM_FW_START - AARCH64_PVTIME_IPA_MAX_SIZE;
+const AARCH64_PVTIME_SIZE: u64 = 64;
+
// These constants indicate the placement of the GIC registers in the physical
// address space.
const AARCH64_GIC_DIST_BASE: u64 = AARCH64_AXI_BASE - AARCH64_GIC_DIST_SIZE;
@@ -60,26 +69,6 @@ const PSR_I_BIT: u64 = 0x00000080;
const PSR_A_BIT: u64 = 0x00000100;
const PSR_D_BIT: u64 = 0x00000200;
-macro_rules! offset__of {
- ($str:ty, $($field:ident).+ $([$idx:expr])*) => {
- unsafe { &(*(0 as *const $str))$(.$field)* $([$idx])* as *const _ as usize }
- }
-}
-
-const KVM_REG_ARM64: u64 = 0x6000000000000000;
-const KVM_REG_SIZE_U64: u64 = 0x0030000000000000;
-const KVM_REG_ARM_COPROC_SHIFT: u64 = 16;
-const KVM_REG_ARM_CORE: u64 = 0x0010 << KVM_REG_ARM_COPROC_SHIFT;
-
-macro_rules! arm64_core_reg {
- ($reg: tt) => {
- KVM_REG_ARM64
- | KVM_REG_SIZE_U64
- | KVM_REG_ARM_CORE
- | ((offset__of!(kvm_sys::user_pt_regs, $reg) / 4) as u64)
- };
-}
-
fn get_kernel_addr() -> GuestAddress {
GuestAddress(AARCH64_PHYS_MEM_START + AARCH64_KERNEL_OFFSET)
}
@@ -109,9 +98,9 @@ const AARCH64_PCI_CFG_BASE: u64 = 0x10000;
// PCI MMIO configuration region size.
const AARCH64_PCI_CFG_SIZE: u64 = 0x1000000;
// This is the base address of MMIO devices.
-const AARCH64_MMIO_BASE: u64 = 0x1010000;
+const AARCH64_MMIO_BASE: u64 = 0x2000000;
// Size of the whole MMIO region.
-const AARCH64_MMIO_SIZE: u64 = 0x100000;
+const AARCH64_MMIO_SIZE: u64 = 0x2000000;
// Virtio devices start at SPI interrupt number 3
const AARCH64_IRQ_BASE: u32 = 3;
@@ -119,77 +108,74 @@ const AARCH64_IRQ_BASE: u32 = 3;
const AARCH64_PMU_IRQ: u32 = 7;
#[sorted]
-#[derive(Debug)]
+#[derive(Error, Debug)]
pub enum Error {
+ #[error("bios could not be loaded: {0}")]
BiosLoadFailure(arch::LoadImageError),
+ #[error("failed to build arm pvtime memory: {0}")]
+ BuildPvtimeError(base::MmapError),
+ #[error("unable to clone an Event: {0}")]
CloneEvent(base::Error),
+ #[error("failed to clone IRQ chip: {0}")]
+ CloneIrqChip(base::Error),
+ #[error("the given kernel command line was invalid: {0}")]
Cmdline(kernel_cmdline::Error),
- CreateDevices(Box<dyn StdError>),
+ #[error("unable to make an Event: {0}")]
CreateEvent(base::Error),
+ #[error("FDT could not be created: {0}")]
CreateFdt(arch::fdt::Error),
+ #[error("failed to create GIC: {0}")]
CreateGICFailure(base::Error),
- CreateIrqChip(Box<dyn StdError>),
+ #[error("failed to create a PCI root hub: {0}")]
CreatePciRoot(arch::DeviceRegistrationError),
+ #[error("failed to create platform bus: {0}")]
+ CreatePlatformBus(arch::DeviceRegistrationError),
+ #[error("unable to create serial devices: {0}")]
CreateSerialDevices(arch::DeviceRegistrationError),
+ #[error("failed to create socket: {0}")]
CreateSocket(io::Error),
+ #[error("failed to create VCPU: {0}")]
CreateVcpu(base::Error),
- CreateVm(Box<dyn StdError>),
+ #[error("vm created wrong kind of vcpu")]
DowncastVcpu,
+ #[error("failed to finalize IRQ chip: {0}")]
+ FinalizeIrqChip(base::Error),
+ #[error("failed to get PSCI version: {0}")]
GetPsciVersion(base::Error),
+ #[error("failed to get serial cmdline: {0}")]
GetSerialCmdline(GetSerialCmdlineError),
+ #[error("failed to initialize arm pvtime: {0}")]
+ InitPvtimeError(base::Error),
+ #[error("initrd could not be loaded: {0}")]
InitrdLoadFailure(arch::LoadImageError),
+ #[error("kernel could not be loaded: {0}")]
KernelLoadFailure(arch::LoadImageError),
+ #[error("failed to map arm pvtime memory: {0}")]
+ MapPvtimeError(base::Error),
+ #[error("failed to protect vm: {0}")]
ProtectVm(base::Error),
+ #[error("ramoops address is different from high_mmio_base: {0} vs {1}")]
+ RamoopsAddress(u64, u64),
+ #[error("failed to register irq fd: {0}")]
RegisterIrqfd(base::Error),
+ #[error("error registering PCI bus: {0}")]
RegisterPci(BusError),
+ #[error("error registering virtual socket device: {0}")]
RegisterVsock(arch::DeviceRegistrationError),
+ #[error("failed to set device attr: {0}")]
SetDeviceAttr(base::Error),
+ #[error("failed to set register: {0}")]
SetReg(base::Error),
+ #[error("failed to set up guest memory: {0}")]
SetupGuestMemory(GuestMemoryError),
+ #[error("this function isn't supported")]
+ Unsupported,
+ #[error("failed to initialize VCPU: {0}")]
VcpuInit(base::Error),
}
-impl Display for Error {
- #[remain::check]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- #[sorted]
- match self {
- BiosLoadFailure(e) => write!(f, "bios could not be loaded: {}", e),
- CloneEvent(e) => write!(f, "unable to clone an Event: {}", e),
- Cmdline(e) => write!(f, "the given kernel command line was invalid: {}", e),
- CreateDevices(e) => write!(f, "error creating devices: {}", e),
- CreateEvent(e) => write!(f, "unable to make an Event: {}", e),
- CreateFdt(e) => write!(f, "FDT could not be created: {}", e),
- CreateGICFailure(e) => write!(f, "failed to create GIC: {}", e),
- CreateIrqChip(e) => write!(f, "failed to create IRQ chip: {}", e),
- CreatePciRoot(e) => write!(f, "failed to create a PCI root hub: {}", e),
- CreateSerialDevices(e) => write!(f, "unable to create serial devices: {}", e),
- CreateSocket(e) => write!(f, "failed to create socket: {}", e),
- CreateVcpu(e) => write!(f, "failed to create VCPU: {}", e),
- CreateVm(e) => write!(f, "failed to create vm: {}", e),
- DowncastVcpu => write!(f, "vm created wrong kind of vcpu"),
- GetPsciVersion(e) => write!(f, "failed to get PSCI version: {}", e),
- GetSerialCmdline(e) => write!(f, "failed to get serial cmdline: {}", e),
- InitrdLoadFailure(e) => write!(f, "initrd could not be loaded: {}", e),
- KernelLoadFailure(e) => write!(f, "kernel could not be loaded: {}", e),
- ProtectVm(e) => write!(f, "failed to protect vm: {}", e),
- RegisterIrqfd(e) => write!(f, "failed to register irq fd: {}", e),
- RegisterPci(e) => write!(f, "error registering PCI bus: {}", e),
- RegisterVsock(e) => write!(f, "error registering virtual socket device: {}", e),
- SetDeviceAttr(e) => write!(f, "failed to set device attr: {}", e),
- SetReg(e) => write!(f, "failed to set register: {}", e),
- SetupGuestMemory(e) => write!(f, "failed to set up guest memory: {}", e),
- VcpuInit(e) => write!(f, "failed to initialize VCPU: {}", e),
- }
- }
-}
-
pub type Result<T> = std::result::Result<T, Error>;
-impl std::error::Error for Error {}
-
/// Returns a Vec of the valid memory addresses.
/// These should be used to configure the GuestMemory structure for the platfrom.
pub fn arch_memory_regions(size: u64) -> Vec<(GuestAddress, u64)> {
@@ -221,28 +207,30 @@ impl arch::LinuxArch for AArch64 {
Ok(arch_memory_regions(components.memory_size))
}
- fn build_vm<V, Vcpu, I, FD, FI, E1, E2>(
+ fn get_system_allocator_config<V: Vm>(vm: &V) -> SystemAllocatorConfig {
+ Self::get_resource_allocator_config(
+ vm.get_memory().memory_size(),
+ vm.get_guest_phys_addr_bits(),
+ )
+ }
+
+ fn build_vm<V, Vcpu>(
mut components: VmComponents,
+ _exit_evt: &Event,
+ _reset_evt: &Event,
+ system_allocator: &mut SystemAllocator,
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
serial_jail: Option<Minijail>,
_battery: (&Option<BatteryType>, Option<Minijail>),
mut vm: V,
- create_devices: FD,
- create_irq_chip: FI,
- ) -> std::result::Result<RunnableLinuxVm<V, Vcpu, I>, Self::Error>
+ ramoops_region: Option<arch::pstore::RamoopsRegion>,
+ devs: Vec<(Box<dyn BusDeviceObj>, Option<Minijail>)>,
+ irq_chip: &mut dyn IrqChipAArch64,
+ kvm_vcpu_ids: &mut Vec<usize>,
+ ) -> std::result::Result<RunnableLinuxVm<V, Vcpu>, Self::Error>
where
V: VmAArch64,
Vcpu: VcpuAArch64,
- I: IrqChipAArch64,
- FD: FnOnce(
- &GuestMemory,
- &mut V,
- &mut SystemAllocator,
- &Event,
- ) -> std::result::Result<Vec<(Box<dyn PciDevice>, Option<Minijail>)>, E1>,
- FI: FnOnce(&V, /* vcpu_count: */ usize) -> std::result::Result<I, E2>,
- E1: StdError + 'static,
- E2: StdError + 'static,
{
let has_bios = match components.vm_image {
VmImage::Bios(_) => true,
@@ -250,20 +238,44 @@ impl arch::LinuxArch for AArch64 {
};
let mem = vm.get_memory().clone();
- let mut resources = Self::get_resource_allocator(components.memory_size);
- if components.protected_vm == ProtectionType::Protected {
- vm.enable_protected_vm(
- GuestAddress(AARCH64_PROTECTED_VM_FW_START),
- AARCH64_PROTECTED_VM_FW_MAX_SIZE,
- )
- .map_err(Error::ProtectVm)?;
- }
+ // separate out image loading from other setup to get a specific error for
+ // image loading
+ let mut initrd = None;
+ let image_size = match components.vm_image {
+ VmImage::Bios(ref mut bios) => {
+ arch::load_image(&mem, bios, get_bios_addr(), AARCH64_BIOS_MAX_LEN)
+ .map_err(Error::BiosLoadFailure)?
+ }
+ VmImage::Kernel(ref mut kernel_image) => {
+ let kernel_size =
+ arch::load_image(&mem, kernel_image, get_kernel_addr(), u64::max_value())
+ .map_err(Error::KernelLoadFailure)?;
+ let kernel_end = get_kernel_addr().offset() + kernel_size as u64;
+ initrd = match components.initrd_image {
+ Some(initrd_file) => {
+ let mut initrd_file = initrd_file;
+ let initrd_addr =
+ (kernel_end + (AARCH64_INITRD_ALIGN - 1)) & !(AARCH64_INITRD_ALIGN - 1);
+ let initrd_max_size =
+ components.memory_size - (initrd_addr - AARCH64_PHYS_MEM_START);
+ let initrd_addr = GuestAddress(initrd_addr);
+ let initrd_size =
+ arch::load_image(&mem, &mut initrd_file, initrd_addr, initrd_max_size)
+ .map_err(Error::InitrdLoadFailure)?;
+ Some((initrd_addr, initrd_size))
+ }
+ None => None,
+ };
+ kernel_size
+ }
+ };
let mut use_pmu = vm
.get_hypervisor()
- .check_capability(&HypervisorCap::ArmPmuV3);
+ .check_capability(HypervisorCap::ArmPmuV3);
let vcpu_count = components.vcpu_count;
+ let mut has_pvtime = true;
let mut vcpus = Vec::with_capacity(vcpu_count);
for vcpu_id in 0..vcpu_count {
let vcpu: Vcpu = *vm
@@ -271,60 +283,117 @@ impl arch::LinuxArch for AArch64 {
.map_err(Error::CreateVcpu)?
.downcast::<Vcpu>()
.map_err(|_| Error::DowncastVcpu)?;
- Self::configure_vcpu_early(vm.get_memory(), &vcpu, vcpu_id, use_pmu, has_bios)?;
+ Self::configure_vcpu_early(
+ vm.get_memory(),
+ &vcpu,
+ vcpu_id,
+ use_pmu,
+ has_bios,
+ image_size,
+ components.protected_vm,
+ )?;
+ has_pvtime &= vcpu.has_pvtime_support();
vcpus.push(vcpu);
+ kvm_vcpu_ids.push(vcpu_id);
+ }
+
+ irq_chip.finalize().map_err(Error::FinalizeIrqChip)?;
+
+ if has_pvtime {
+ let pvtime_mem = MemoryMappingBuilder::new(AARCH64_PVTIME_IPA_MAX_SIZE as usize)
+ .build()
+ .map_err(Error::BuildPvtimeError)?;
+ vm.add_memory_region(
+ GuestAddress(AARCH64_PVTIME_IPA_START),
+ Box::new(pvtime_mem),
+ false,
+ false,
+ )
+ .map_err(Error::MapPvtimeError)?;
}
- let mut irq_chip =
- create_irq_chip(&vm, vcpu_count).map_err(|e| Error::CreateIrqChip(Box::new(e)))?;
+ if components.protected_vm == ProtectionType::Protected {
+ vm.load_protected_vm_firmware(
+ GuestAddress(AARCH64_PROTECTED_VM_FW_START),
+ AARCH64_PROTECTED_VM_FW_MAX_SIZE,
+ )
+ .map_err(Error::ProtectVm)?;
+ }
- for vcpu in &vcpus {
+ for (vcpu_id, vcpu) in vcpus.iter().enumerate() {
use_pmu &= vcpu.init_pmu(AARCH64_PMU_IRQ as u64 + 16).is_ok();
+ if has_pvtime {
+ vcpu.init_pvtime(AARCH64_PVTIME_IPA_START + (vcpu_id as u64 * AARCH64_PVTIME_SIZE))
+ .map_err(Error::InitPvtimeError)?;
+ }
}
- let mut mmio_bus = devices::Bus::new();
+ let mmio_bus = Arc::new(devices::Bus::new());
- let exit_evt = Event::new().map_err(Error::CreateEvent)?;
+ // ARM doesn't really use the io bus like x86, so just create an empty bus.
+ let io_bus = Arc::new(devices::Bus::new());
// Event used by PMDevice to notify crosvm that
// guest OS is trying to suspend.
let suspend_evt = Event::new().map_err(Error::CreateEvent)?;
- let pci_devices = create_devices(&mem, &mut vm, &mut resources, &exit_evt)
- .map_err(|e| Error::CreateDevices(Box::new(e)))?;
- let (pci, pci_irqs, pid_debug_label_map) = arch::generate_pci_root(
+ let (pci_devices, others): (Vec<_>, Vec<_>) = devs
+ .into_iter()
+ .partition(|(dev, _)| dev.as_pci_device().is_some());
+
+ let pci_devices = pci_devices
+ .into_iter()
+ .map(|(dev, jail_orig)| (dev.into_pci_device().unwrap(), jail_orig))
+ .collect();
+ let (pci, pci_irqs, mut pid_debug_label_map) = arch::generate_pci_root(
pci_devices,
- &mut irq_chip,
- &mut mmio_bus,
- &mut resources,
+ irq_chip.as_irq_chip_mut(),
+ mmio_bus.clone(),
+ io_bus.clone(),
+ system_allocator,
&mut vm,
- (devices::AARCH64_GIC_NR_IRQS - AARCH64_IRQ_BASE) as usize,
+ (devices::AARCH64_GIC_NR_SPIS - AARCH64_IRQ_BASE) as usize,
)
.map_err(Error::CreatePciRoot)?;
- let pci_bus = Arc::new(Mutex::new(PciConfigMmio::new(pci)));
- // ARM doesn't really use the io bus like x86, so just create an empty bus.
- let io_bus = devices::Bus::new();
+ let pci_root = Arc::new(Mutex::new(pci));
+ let pci_bus = Arc::new(Mutex::new(PciConfigMmio::new(pci_root.clone(), 8)));
+ let (platform_devices, _others): (Vec<_>, Vec<_>) = others
+ .into_iter()
+ .partition(|(dev, _)| dev.as_platform_device().is_some());
+
+ let platform_devices = platform_devices
+ .into_iter()
+ .map(|(dev, jail_orig)| (*(dev.into_platform_device().unwrap()), jail_orig))
+ .collect();
+ let mut platform_pid_debug_label_map = arch::generate_platform_bus(
+ platform_devices,
+ irq_chip.as_irq_chip_mut(),
+ &mmio_bus,
+ system_allocator,
+ )
+ .map_err(Error::CreatePlatformBus)?;
+ pid_debug_label_map.append(&mut platform_pid_debug_label_map);
- Self::add_arch_devs(&mut irq_chip, &mut mmio_bus)?;
+ Self::add_arch_devs(irq_chip.as_irq_chip_mut(), &mmio_bus)?;
- let com_evt_1_3 = Event::new().map_err(Error::CreateEvent)?;
- let com_evt_2_4 = Event::new().map_err(Error::CreateEvent)?;
+ let com_evt_1_3 = devices::IrqEdgeEvent::new().map_err(Error::CreateEvent)?;
+ let com_evt_2_4 = devices::IrqEdgeEvent::new().map_err(Error::CreateEvent)?;
arch::add_serial_devices(
components.protected_vm,
- &mut mmio_bus,
- &com_evt_1_3,
- &com_evt_2_4,
+ &mmio_bus,
+ &com_evt_1_3.get_trigger(),
+ &com_evt_2_4.get_trigger(),
serial_parameters,
serial_jail,
)
.map_err(Error::CreateSerialDevices)?;
irq_chip
- .register_irq_event(AARCH64_SERIAL_1_3_IRQ, &com_evt_1_3, None)
+ .register_edge_irq_event(AARCH64_SERIAL_1_3_IRQ, &com_evt_1_3)
.map_err(Error::RegisterIrqfd)?;
irq_chip
- .register_irq_event(AARCH64_SERIAL_2_4_IRQ, &com_evt_2_4, None)
+ .register_edge_irq_event(AARCH64_SERIAL_2_4_IRQ, &com_evt_2_4)
.map_err(Error::RegisterIrqfd)?;
mmio_bus
@@ -338,79 +407,74 @@ impl arch::LinuxArch for AArch64 {
cmdline.insert_str(&param).map_err(Error::Cmdline)?;
}
+ if let Some(ramoops_region) = ramoops_region {
+ arch::pstore::add_ramoops_kernel_cmdline(&mut cmdline, &ramoops_region)
+ .map_err(Error::Cmdline)?;
+ }
+
let psci_version = vcpus[0].get_psci_version().map_err(Error::GetPsciVersion)?;
- let (pci_device_base, pci_device_size) =
- Self::get_high_mmio_base_size(components.memory_size);
- let mut initrd = None;
- // separate out image loading from other setup to get a specific error for
- // image loading
- match components.vm_image {
- VmImage::Bios(ref mut bios) => {
- arch::load_image(&mem, bios, get_bios_addr(), AARCH64_BIOS_MAX_LEN)
- .map_err(Error::BiosLoadFailure)?;
- }
- VmImage::Kernel(ref mut kernel_image) => {
- let kernel_size =
- arch::load_image(&mem, kernel_image, get_kernel_addr(), u64::max_value())
- .map_err(Error::KernelLoadFailure)?;
- let kernel_end = get_kernel_addr().offset() + kernel_size as u64;
- initrd = match components.initrd_image {
- Some(initrd_file) => {
- let mut initrd_file = initrd_file;
- let initrd_addr =
- (kernel_end + (AARCH64_INITRD_ALIGN - 1)) & !(AARCH64_INITRD_ALIGN - 1);
- let initrd_max_size =
- components.memory_size - (initrd_addr - AARCH64_PHYS_MEM_START);
- let initrd_addr = GuestAddress(initrd_addr);
- let initrd_size =
- arch::load_image(&mem, &mut initrd_file, initrd_addr, initrd_max_size)
- .map_err(Error::InitrdLoadFailure)?;
- Some((initrd_addr, initrd_size))
- }
- None => None,
- };
- }
- }
+ let pci_cfg = fdt::PciConfigRegion {
+ base: AARCH64_PCI_CFG_BASE,
+ size: AARCH64_PCI_CFG_SIZE,
+ };
+
+ let pci_ranges: Vec<fdt::PciRange> = system_allocator
+ .mmio_pools()
+ .iter()
+ .map(|range| fdt::PciRange {
+ space: fdt::PciAddressSpace::Memory64,
+ bus_address: *range.start(),
+ cpu_physical_address: *range.start(),
+ size: range_inclusive_len(range).unwrap(),
+ prefetchable: false,
+ })
+ .collect();
fdt::create_fdt(
AARCH64_FDT_MAX_SIZE as usize,
&mem,
pci_irqs,
+ pci_cfg,
+ &pci_ranges,
vcpu_count as u32,
+ components.cpu_clusters,
+ components.cpu_capacity,
fdt_offset(components.memory_size, has_bios),
- pci_device_base,
- pci_device_size,
cmdline.as_str(),
initrd,
components.android_fstab,
irq_chip.get_vgic_version() == DeviceKind::ArmVgicV3,
use_pmu,
psci_version,
+ components.swiotlb,
)
.map_err(Error::CreateFdt)?;
Ok(RunnableLinuxVm {
vm,
- resources,
- exit_evt,
vcpu_count,
vcpus: Some(vcpus),
vcpu_affinity: components.vcpu_affinity,
no_smt: components.no_smt,
- irq_chip,
+ irq_chip: irq_chip.try_box_clone().map_err(Error::CloneIrqChip)?,
has_bios,
io_bus,
mmio_bus,
pid_debug_label_map,
suspend_evt,
rt_cpus: components.rt_cpus,
+ delay_rt: components.delay_rt,
bat_control: None,
+ pm: None,
+ resume_notify_devices: Vec::new(),
+ root_config: pci_root,
+ hotplug_bus: Vec::new(),
})
}
- fn configure_vcpu(
- _guest_mem: &GuestMemory,
+ fn configure_vcpu<V: Vm>(
+ _vm: &V,
_hypervisor: &dyn Hypervisor,
_irq_chip: &mut dyn IrqChipAArch64,
_vcpu: &mut dyn VcpuAArch64,
@@ -418,19 +482,24 @@ impl arch::LinuxArch for AArch64 {
_num_cpus: usize,
_has_bios: bool,
_no_smt: bool,
+ _host_cpu_topology: bool,
) -> std::result::Result<(), Self::Error> {
// AArch64 doesn't configure vcpus on the vcpu thread, so nothing to do here.
Ok(())
}
-}
-impl AArch64 {
- fn get_high_mmio_base_size(mem_size: u64) -> (u64, u64) {
- let base = AARCH64_PHYS_MEM_START + mem_size;
- let size = u64::max_value() - base;
- (base, size)
+ fn register_pci_device<V: VmAArch64, Vcpu: VcpuAArch64>(
+ _linux: &mut RunnableLinuxVm<V, Vcpu>,
+ _device: Box<dyn PciDevice>,
+ _minijail: Option<Minijail>,
+ _resources: &mut SystemAllocator,
+ ) -> std::result::Result<PciAddress, Self::Error> {
+ // hotplug function isn't verified on AArch64, so set it unsupported here.
+ Err(Error::Unsupported)
}
+}
+impl AArch64 {
/// This returns a base part of the kernel command for this architecture
fn get_base_linux_cmdline() -> kernel_cmdline::Cmdline {
let mut cmdline = kernel_cmdline::Cmdline::new(base::pagesize());
@@ -438,14 +507,46 @@ impl AArch64 {
cmdline
}
- /// Returns a system resource allocator.
- fn get_resource_allocator(mem_size: u64) -> SystemAllocator {
- let (high_mmio_base, high_mmio_size) = Self::get_high_mmio_base_size(mem_size);
- SystemAllocator::builder()
- .add_high_mmio_addresses(high_mmio_base, high_mmio_size)
- .add_low_mmio_addresses(AARCH64_MMIO_BASE, AARCH64_MMIO_SIZE)
- .create_allocator(AARCH64_IRQ_BASE)
- .unwrap()
+ /// Returns a system resource allocator configuration.
+ ///
+ /// # Arguments
+ ///
+ /// * `mem_size` - Size of guest memory (RAM) in bytes.
+ /// * `guest_phys_addr_bits` - Size of guest physical addresses (IPA) in bits.
+ fn get_resource_allocator_config(
+ mem_size: u64,
+ guest_phys_addr_bits: u8,
+ ) -> SystemAllocatorConfig {
+ let guest_phys_end = 1u64 << guest_phys_addr_bits;
+ // The platform MMIO region is immediately past the end of RAM.
+ let plat_mmio_base = AARCH64_PHYS_MEM_START + mem_size;
+ let plat_mmio_size = AARCH64_PLATFORM_MMIO_SIZE;
+ // The high MMIO region is the rest of the address space after the platform MMIO region.
+ let high_mmio_base = plat_mmio_base + plat_mmio_size;
+ let high_mmio_size = guest_phys_end
+ .checked_sub(high_mmio_base)
+ .unwrap_or_else(|| {
+ panic!(
+ "guest_phys_end {:#x} < high_mmio_base {:#x}",
+ guest_phys_end, high_mmio_base,
+ );
+ });
+ SystemAllocatorConfig {
+ io: None,
+ low_mmio: MemRegion {
+ base: AARCH64_MMIO_BASE,
+ size: AARCH64_MMIO_SIZE,
+ },
+ high_mmio: MemRegion {
+ base: high_mmio_base,
+ size: high_mmio_size,
+ },
+ platform_mmio: Some(MemRegion {
+ base: plat_mmio_base,
+ size: plat_mmio_size,
+ }),
+ first_irq: AARCH64_IRQ_BASE,
+ }
}
/// This adds any early platform devices for this architecture.
@@ -454,10 +555,10 @@ impl AArch64 {
///
/// * `irq_chip` - The IRQ chip to add irqs to.
/// * `bus` - The bus to add devices to.
- fn add_arch_devs(irq_chip: &mut dyn IrqChip, bus: &mut Bus) -> Result<()> {
- let rtc_evt = Event::new().map_err(Error::CreateEvent)?;
+ fn add_arch_devs(irq_chip: &mut dyn IrqChip, bus: &Bus) -> Result<()> {
+ let rtc_evt = devices::IrqEdgeEvent::new().map_err(Error::CreateEvent)?;
irq_chip
- .register_irq_event(AARCH64_RTC_IRQ, &rtc_evt, None)
+ .register_edge_irq_event(AARCH64_RTC_IRQ, &rtc_evt)
.map_err(Error::RegisterIrqfd)?;
let rtc = Arc::new(Mutex::new(devices::pl030::Pl030::new(rtc_evt)));
@@ -486,6 +587,8 @@ impl AArch64 {
vcpu_id: usize,
use_pmu: bool,
has_bios: bool,
+ image_size: usize,
+ protected_vm: ProtectionType,
) -> Result<()> {
let mut features = vec![VcpuFeature::PsciV0_2];
if use_pmu {
@@ -505,19 +608,29 @@ impl AArch64 {
// Other cpus are powered off initially
if vcpu_id == 0 {
let entry_addr = if has_bios {
- AARCH64_PHYS_MEM_START + AARCH64_BIOS_OFFSET
+ get_bios_addr()
+ } else {
+ get_kernel_addr()
+ };
+ let entry_addr_reg_id = if protected_vm == ProtectionType::Protected {
+ arm64_core_reg!(regs, 1)
} else {
- AARCH64_PHYS_MEM_START + AARCH64_KERNEL_OFFSET
+ arm64_core_reg!(pc)
};
- vcpu.set_one_reg(arm64_core_reg!(pc), entry_addr)
+ vcpu.set_one_reg(entry_addr_reg_id, entry_addr.offset())
.map_err(Error::SetReg)?;
/* X0 -- fdt address */
let mem_size = guest_mem.memory_size();
let fdt_addr = (AARCH64_PHYS_MEM_START + fdt_offset(mem_size, has_bios)) as u64;
- // hack -- can't get this to do offsetof(regs[0]) but luckily it's at offset 0
- vcpu.set_one_reg(arm64_core_reg!(regs), fdt_addr)
+ vcpu.set_one_reg(arm64_core_reg!(regs, 0), fdt_addr)
.map_err(Error::SetReg)?;
+
+ /* X2 -- image size */
+ if protected_vm == ProtectionType::Protected {
+ vcpu.set_one_reg(arm64_core_reg!(regs, 2), image_size as u64)
+ .map_err(Error::SetReg)?;
+ }
}
Ok(())
diff --git a/acpi_tables/Android.bp b/acpi_tables/Android.bp
index 7d8e9978d..83151a3ca 100644
--- a/acpi_tables/Android.bp
+++ b/acpi_tables/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -10,54 +10,37 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "acpi_tables_defaults",
+rust_test {
+ name: "acpi_tables_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "acpi_tables",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2021",
rustlibs: [
"libdata_model",
"libtempfile",
],
}
-rust_test_host {
- name: "acpi_tables_host_test_src_lib",
- defaults: ["acpi_tables_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "acpi_tables_device_test_src_lib",
- defaults: ["acpi_tables_defaults"],
-}
-
rust_library {
name: "libacpi_tables",
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "acpi_tables",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libdata_model",
"libtempfile",
],
}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../data_model/src/lib.rs
-// ../tempfile/src/lib.rs
-// libc-0.2.93 "default,std"
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// syn-1.0.70 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// unicode-xid-0.2.1 "default"
diff --git a/acpi_tables/Cargo.toml b/acpi_tables/Cargo.toml
index 8904fbb97..c6d591766 100644
--- a/acpi_tables/Cargo.toml
+++ b/acpi_tables/Cargo.toml
@@ -2,8 +2,8 @@
name = "acpi_tables"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
-data_model = { path = "../data_model" }
-tempfile = { path = "../tempfile" }
+data_model = { path = "../common/data_model" }
+tempfile = "3"
diff --git a/acpi_tables/src/aml.rs b/acpi_tables/src/aml.rs
index 7d16153bd..6365d22a4 100644
--- a/acpi_tables/src/aml.rs
+++ b/acpi_tables/src/aml.rs
@@ -55,10 +55,12 @@ const CONCATRESOP: u8 = 0x84;
const MODOP: u8 = 0x85;
const NOTIFYOP: u8 = 0x86;
const INDEXOP: u8 = 0x88;
+const CREATEDWFIELDOP: u8 = 0x8a;
const LEQUALOP: u8 = 0x93;
const LLESSOP: u8 = 0x95;
const TOSTRINGOP: u8 = 0x9c;
const IFOP: u8 = 0xa0;
+const ELSEOP: u8 = 0xa1;
const WHILEOP: u8 = 0xa2;
const RETURNOP: u8 = 0xa4;
const ONESOP: u8 = 0xff;
@@ -210,12 +212,20 @@ impl Name {
/// * `path` - The namestring.
/// * `inner` - AML objects contained in this namespace.
pub fn new(path: Path, inner: &dyn Aml) -> Self {
- let mut bytes = Vec::new();
- bytes.push(NAMEOP);
+ let mut bytes = vec![NAMEOP];
path.to_aml_bytes(&mut bytes);
inner.to_aml_bytes(&mut bytes);
Name { bytes }
}
+
+ /// Create Field name object
+ ///
+ /// * 'field_name' - name string
+ pub fn new_field_name(field_name: &str) -> Self {
+ let mut bytes: Vec<u8> = Vec::new();
+ bytes.extend_from_slice(field_name.as_bytes());
+ Name { bytes }
+ }
}
/// Package object. 'children' represents the ACPI objects contained in this package.
@@ -225,8 +235,7 @@ pub struct Package<'a> {
impl<'a> Aml for Package<'a> {
fn to_aml_bytes(&self, aml: &mut Vec<u8>) {
- let mut bytes = Vec::new();
- bytes.push(self.children.len() as u8);
+ let mut bytes = vec![self.children.len() as u8];
for child in &self.children {
child.to_aml_bytes(&mut bytes);
}
@@ -359,8 +368,7 @@ impl Aml for Usize {
}
fn create_aml_string(v: &str) -> Vec<u8> {
- let mut data = Vec::new();
- data.push(STRINGOP);
+ let mut data = vec![STRINGOP];
data.extend_from_slice(v.as_bytes());
data.push(0x0); /* NullChar */
data
@@ -943,6 +951,36 @@ impl<'a> Aml for If<'a> {
}
}
+/// Else object
+pub struct Else<'a> {
+ body: Vec<&'a dyn Aml>,
+}
+
+impl<'a> Else<'a> {
+ /// Create Else object.
+ pub fn new(body: Vec<&'a dyn Aml>) -> Self {
+ Else { body }
+ }
+}
+
+impl<'a> Aml for Else<'a> {
+ fn to_aml_bytes(&self, aml: &mut Vec<u8>) {
+ let mut bytes = Vec::new();
+ for child in self.body.iter() {
+ child.to_aml_bytes(&mut bytes);
+ }
+
+ let mut pkg_length = create_pkg_length(&bytes, true);
+ pkg_length.reverse();
+ for byte in pkg_length {
+ bytes.insert(0, byte);
+ }
+
+ bytes.insert(0, ELSEOP);
+ aml.append(&mut bytes)
+ }
+}
+
/// Equal object with its right part and left part, which are both ACPI objects.
pub struct Equal<'a> {
right: &'a dyn Aml,
@@ -1194,6 +1232,7 @@ binary_op!(ConcatRes, CONCATRESOP);
binary_op!(Mod, MODOP);
binary_op!(Index, INDEXOP);
binary_op!(ToString, TOSTRINGOP);
+binary_op!(CreateDWordField, CREATEDWFIELDOP);
/// MethodCall object with the method name and parameter objects.
pub struct MethodCall<'a> {
@@ -1247,6 +1286,81 @@ impl Aml for Buffer {
}
}
+pub struct Uuid {
+ name: Buffer,
+}
+
+fn hex2byte(v1: char, v2: char) -> u8 {
+ let hi = v1.to_digit(16).unwrap() as u8;
+ assert!(hi <= 15);
+ let lo = v2.to_digit(16).unwrap() as u8;
+ assert!(lo <= 15);
+
+ (hi << 4) | lo
+}
+
+impl Uuid {
+ // Create Uuid object
+ // eg. UUID: aabbccdd-eeff-gghh-iijj-kkllmmnnoopp
+ pub fn new(name: &str) -> Self {
+ let name_vec: Vec<char> = name.chars().collect();
+ let mut data = Vec::new();
+
+ assert_eq!(name_vec.len(), 36);
+ assert_eq!(name_vec[8], '-');
+ assert_eq!(name_vec[13], '-');
+ assert_eq!(name_vec[18], '-');
+ assert_eq!(name_vec[23], '-');
+
+ // dd - at offset 00
+ data.push(hex2byte(name_vec[6], name_vec[7]));
+ // cc - at offset 01
+ data.push(hex2byte(name_vec[4], name_vec[5]));
+ // bb - at offset 02
+ data.push(hex2byte(name_vec[2], name_vec[3]));
+ // aa - at offset 03
+ data.push(hex2byte(name_vec[0], name_vec[1]));
+
+ // ff - at offset 04
+ data.push(hex2byte(name_vec[11], name_vec[12]));
+ // ee - at offset 05
+ data.push(hex2byte(name_vec[9], name_vec[10]));
+
+ // hh - at offset 06
+ data.push(hex2byte(name_vec[16], name_vec[17]));
+ // gg - at offset 07
+ data.push(hex2byte(name_vec[14], name_vec[15]));
+
+ // ii - at offset 08
+ data.push(hex2byte(name_vec[19], name_vec[20]));
+ // jj - at offset 09
+ data.push(hex2byte(name_vec[21], name_vec[22]));
+
+ // kk - at offset 10
+ data.push(hex2byte(name_vec[24], name_vec[25]));
+ // ll - at offset 11
+ data.push(hex2byte(name_vec[26], name_vec[27]));
+ // mm - at offset 12
+ data.push(hex2byte(name_vec[28], name_vec[29]));
+ // nn - at offset 13
+ data.push(hex2byte(name_vec[30], name_vec[31]));
+ // oo - at offset 14
+ data.push(hex2byte(name_vec[32], name_vec[33]));
+ // pp - at offset 15
+ data.push(hex2byte(name_vec[34], name_vec[35]));
+
+ Uuid {
+ name: Buffer::new(data),
+ }
+ }
+}
+
+impl Aml for Uuid {
+ fn to_aml_bytes(&self, bytes: &mut Vec<u8>) {
+ self.name.to_aml_bytes(bytes)
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -1522,13 +1636,13 @@ mod tests {
#[test]
fn test_pkg_length() {
- assert_eq!(create_pkg_length(&[0u8; 62].to_vec(), true), vec![63]);
+ assert_eq!(create_pkg_length(&[0u8; 62], true), vec![63]);
assert_eq!(
- create_pkg_length(&[0u8; 64].to_vec(), true),
+ create_pkg_length(&[0u8; 64], true),
vec![1 << 6 | (66 & 0xf), 66 >> 4]
);
assert_eq!(
- create_pkg_length(&[0u8; 4096].to_vec(), true),
+ create_pkg_length(&[0u8; 4096], true),
vec![
2 << 6 | (4099 & 0xf) as u8,
(4099 >> 4) as u8,
diff --git a/acpi_tables/src/facs.rs b/acpi_tables/src/facs.rs
new file mode 100644
index 000000000..a5c1335b6
--- /dev/null
+++ b/acpi_tables/src/facs.rs
@@ -0,0 +1,46 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use data_model::DataInit;
+
+#[repr(packed)]
+#[derive(Clone, Copy, Default)]
+pub struct FACS {
+ pub signature: [u8; 4],
+ pub length: u32,
+ pub hardware_signature: u32,
+ pub waking: u32,
+ pub lock: u32,
+ pub flags: u32,
+ pub x_waking: u64,
+ pub version: u8,
+ pub _reserved1: [u8; 3],
+ pub ospm_flags: u32,
+ pub _reserved2: [u8; 24],
+}
+
+// Safe as FACS structure only contains raw data
+unsafe impl DataInit for FACS {}
+
+impl FACS {
+ pub fn new() -> Self {
+ FACS {
+ signature: *b"FACS",
+ length: std::mem::size_of::<FACS>() as u32,
+ hardware_signature: 0,
+ waking: 0,
+ lock: 0,
+ flags: 0,
+ x_waking: 0,
+ version: 1,
+ _reserved1: [0; 3],
+ ospm_flags: 0,
+ _reserved2: [0; 24],
+ }
+ }
+
+ pub fn len() -> usize {
+ std::mem::size_of::<FACS>()
+ }
+}
diff --git a/acpi_tables/src/lib.rs b/acpi_tables/src/lib.rs
index e45aa40dd..36e2e2c57 100644
--- a/acpi_tables/src/lib.rs
+++ b/acpi_tables/src/lib.rs
@@ -3,6 +3,7 @@
// found in the LICENSE file.
pub mod aml;
+pub mod facs;
pub mod rsdp;
pub mod sdt;
diff --git a/acpi_tables/src/rsdp.rs b/acpi_tables/src/rsdp.rs
index 4bf64c98a..6e2befbd9 100644
--- a/acpi_tables/src/rsdp.rs
+++ b/acpi_tables/src/rsdp.rs
@@ -36,7 +36,7 @@ impl RSDP {
};
rsdp.checksum = super::generate_checksum(&rsdp.as_slice()[0..19]);
- rsdp.extended_checksum = super::generate_checksum(&rsdp.as_slice());
+ rsdp.extended_checksum = super::generate_checksum(rsdp.as_slice());
rsdp
}
diff --git a/acpi_tables/src/sdt.rs b/acpi_tables/src/sdt.rs
index 8e7ad7337..e1f0f23eb 100644
--- a/acpi_tables/src/sdt.rs
+++ b/acpi_tables/src/sdt.rs
@@ -4,12 +4,13 @@
use std::fs::File;
use std::io::{ErrorKind, Read, Result};
-use std::path::PathBuf;
+use std::path::Path;
use data_model::DataInit;
/// SDT represents for System Description Table. The structure SDT is a
/// generic format for creating various ACPI tables like DSDT/FADT/MADT.
+#[derive(Clone)]
pub struct SDT {
data: Vec<u8>,
}
@@ -59,7 +60,7 @@ impl SDT {
}
/// Set up the ACPI table from file content. Verify file checksum.
- pub fn from_file(path: &PathBuf) -> Result<Self> {
+ pub fn from_file(path: &Path) -> Result<Self> {
let mut file = File::open(path)?;
let mut data = Vec::new();
file.read_to_end(&mut data)?;
@@ -82,7 +83,7 @@ impl SDT {
}
pub fn as_slice(&self) -> &[u8] {
- &self.data.as_slice()
+ self.data.as_slice()
}
pub fn append<T: DataInit>(&mut self, value: T) {
@@ -95,14 +96,24 @@ impl SDT {
self.write(LENGTH_OFFSET, self.data.len() as u32);
}
+ /// Read a value at the given offset
+ pub fn read<T: DataInit + Default>(&self, offset: usize) -> T {
+ let value_len = std::mem::size_of::<T>();
+ *T::from_slice(
+ self.as_slice()
+ .get(offset..offset + value_len)
+ .unwrap_or(T::default().as_slice()),
+ )
+ .unwrap()
+ }
+
/// Write a value at the given offset
pub fn write<T: DataInit>(&mut self, offset: usize, value: T) {
let value_len = std::mem::size_of::<T>();
if (offset + value_len) > self.data.len() {
return;
}
-
- self.data[offset..offset + value_len].copy_from_slice(&value.as_slice());
+ self.data[offset..offset + value_len].copy_from_slice(value.as_slice());
self.update_checksum();
}
@@ -125,7 +136,7 @@ mod tests {
.iter()
.fold(0u8, |acc, x| acc.wrapping_add(*x));
assert_eq!(sum, 0);
- sdt.write(36, 0x12345678 as u32);
+ sdt.write(36, 0x12345678_u32);
let sum: u8 = sdt
.as_slice()
.iter()
@@ -141,11 +152,11 @@ mod tests {
// Write SDT to file.
{
let mut writer = temp_file.as_file();
- writer.write_all(&expected_sdt.as_slice())?;
+ writer.write_all(expected_sdt.as_slice())?;
}
// Read it back and verify.
- let actual_sdt = SDT::from_file(&temp_file.path().to_path_buf())?;
+ let actual_sdt = SDT::from_file(temp_file.path())?;
assert!(actual_sdt.is_signature(b"TEST"));
assert_eq!(actual_sdt.as_slice(), expected_sdt.as_slice());
Ok(())
diff --git a/all2android.sh b/all2android.sh
index 53f13a7e8..229086f99 100755
--- a/all2android.sh
+++ b/all2android.sh
@@ -1,40 +1,7 @@
#!/bin/bash
-# Convenience script to run cargo2android.py with the appropriate arguments in the crosvm directory
-# and all subdirectories with Cargo.toml files.
+# Run cargo2android.py for every crate in crosvm.
set -e
-cargo2android() {
- cargo2android.py --run --device --tests --dependencies $@
- rm -r cargo.out target.tmp
-}
-
-# Run in the main crosvm directory.
-cargo2android --no-subdir
-
-for dir in */src
-do
- base=`dirname $dir`
- echo "$base"
- cd "$base"
- # If the subdirectory has more subdirectories with crates, then pass --no-subdir and run it in
- # each of them too.
- if compgen -G "*/Cargo.toml" > /dev/null
- then
- cargo2android --global_defaults=crosvm_defaults --add_workspace --no-subdir
-
- for dir in */Cargo.toml
- do
- sub_base=`dirname $dir`
- echo "$base/$sub_base"
- cd "$sub_base"
- cargo2android --global_defaults=crosvm_defaults --add_workspace
- cd ..
- done
- else
- cargo2android --global_defaults=crosvm_defaults --add_workspace
- fi
-
- cd ..
-done
+find . -type f -name Cargo.toml | xargs dirname | sort | xargs -L1 ./run_c2a.sh
diff --git a/arch/Android.bp b/arch/Android.bp
index ee2b572a0..b4635a2c0 100644
--- a/arch/Android.bp
+++ b/arch/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace --features=gdb.
-// NOTE: The --features=gdb should be applied only to the host (not the device) and there are inline changes to achieve this
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
package {
// See: http://go/android-license-faq
@@ -10,27 +10,23 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "arch_defaults",
+rust_test {
+ name: "arch_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "arch",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
- target: {
- linux_glibc_x86_64: {
- features: [
- "gdb",
- "gdbstub",
- ],
- rustlibs: [
- "libgdbstub",
- ],
- },
+ test_options: {
+ unit_test: true,
},
+ edition: "2021",
rustlibs: [
"libacpi_tables",
+ "libanyhow",
"libbase_rust",
"libdevices",
"libhypervisor",
@@ -44,22 +40,18 @@ rust_defaults {
"libvm_control",
"libvm_memory",
],
- shared_libs: [
- "libfdt", // added manually
- ],
-}
-
-rust_test_host {
- name: "arch_host_test_src_lib",
- defaults: ["arch_defaults"],
- test_options: {
- unit_test: true,
+ proc_macros: ["libremain"],
+ target: {
+ host_linux: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
},
-}
-rust_test {
- name: "arch_device_test_src_lib",
- defaults: ["arch_defaults"],
}
rust_library {
@@ -67,21 +59,13 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "arch",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
- target: {
- linux_glibc_x86_64: {
- features: [
- "gdb",
- "gdbstub",
- ],
- rustlibs: [
- "libgdbstub",
- ],
- },
- },
+ edition: "2021",
rustlibs: [
"libacpi_tables",
+ "libanyhow",
"libbase_rust",
"libdevices",
"libhypervisor",
@@ -95,101 +79,16 @@ rust_library {
"libvm_control",
"libvm_memory",
],
- shared_libs: [
- "libfdt", // added manually
- ],
-}
+ proc_macros: ["libremain"],
+ target: {
+ host_linux: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+ },
-// dependent_library ["feature_list"]
-// ../../adhd/audio_streams/src/audio_streams.rs
-// ../../adhd/cras/client/cras-sys/src/lib.rs
-// ../../adhd/cras/client/libcras/src/libcras.rs
-// ../../libchromeos-rs/src/lib.rs
-// ../../minijail/rust/minijail-sys/lib.rs
-// ../../minijail/rust/minijail/src/lib.rs
-// ../../vm_tools/p9/src/lib.rs
-// ../../vm_tools/p9/wire_format_derive/wire_format_derive.rs
-// ../acpi_tables/src/lib.rs
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../bit_field/bit_field_derive/bit_field_derive.rs
-// ../bit_field/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../devices/src/lib.rs
-// ../disk/src/disk.rs
-// ../enumn/src/lib.rs
-// ../fuse/src/lib.rs
-// ../hypervisor/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../kernel_cmdline/src/kernel_cmdline.rs
-// ../kvm/src/lib.rs
-// ../kvm_sys/src/lib.rs
-// ../linux_input_sys/src/lib.rs
-// ../net_sys/src/lib.rs
-// ../net_util/src/lib.rs
-// ../power_monitor/src/lib.rs
-// ../rand_ish/src/lib.rs
-// ../resources/src/lib.rs
-// ../rutabaga_gfx/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../syscall_defines/src/lib.rs
-// ../tempfile/src/lib.rs
-// ../usb_sys/src/lib.rs
-// ../usb_util/src/lib.rs
-// ../vfio_sys/src/lib.rs
-// ../vhost/src/lib.rs
-// ../virtio_sys/src/lib.rs
-// ../vm_control/src/lib.rs
-// ../vm_memory/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.48
-// autocfg-1.0.1
-// base-0.1.0
-// bitflags-1.2.1 "default"
-// cfg-if-0.1.10
-// cfg-if-1.0.0
-// downcast-rs-1.2.0 "default,std"
-// futures-0.3.13 "alloc,async-await,default,executor,futures-executor,std"
-// futures-channel-0.3.13 "alloc,futures-sink,sink,std"
-// futures-core-0.3.13 "alloc,std"
-// futures-executor-0.3.13 "std"
-// futures-io-0.3.13 "std"
-// futures-macro-0.3.13
-// futures-sink-0.3.13 "alloc,std"
-// futures-task-0.3.13 "alloc,std"
-// futures-util-0.3.13 "alloc,async-await,async-await-macro,channel,futures-channel,futures-io,futures-macro,futures-sink,io,memchr,proc-macro-hack,proc-macro-nested,sink,slab,std"
-// gdbstub-0.4.4 "alloc,default,std"
-// getrandom-0.2.2 "std"
-// intrusive-collections-0.9.0 "alloc,default"
-// libc-0.2.88 "default,std"
-// log-0.4.14
-// managed-0.8.0 "alloc"
-// memchr-2.3.4 "default,std"
-// memoffset-0.5.6 "default"
-// num-traits-0.2.14
-// paste-1.0.4
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// pkg-config-0.3.19
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro-hack-0.5.19
-// proc-macro-nested-0.1.7
-// proc-macro2-1.0.24 "default,proc-macro"
-// protobuf-2.22.0
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.3 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.0 "std"
-// rand_core-0.6.2 "alloc,getrandom,std"
-// remain-0.2.2
-// remove_dir_all-0.5.3
-// serde-1.0.124 "default,derive,serde_derive,std"
-// serde_derive-1.0.124 "default"
-// slab-0.4.2
-// syn-1.0.63 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
+}
diff --git a/arch/Cargo.toml b/arch/Cargo.toml
index 095cc480c..756ca32e2 100644
--- a/arch/Cargo.toml
+++ b/arch/Cargo.toml
@@ -2,24 +2,27 @@
name = "arch"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[features]
power-monitor-powerd = ["power_monitor/powerd"]
-gdb = ["gdbstub"]
+gdb = ["gdbstub_arch"]
+direct = []
[dependencies]
acpi_tables = { path = "../acpi_tables" }
+anyhow = "*"
+base = { path = "../base" }
devices = { path = "../devices" }
-gdbstub = { version = "0.4.0", optional = true }
+gdbstub_arch = { version = "0.1.0", optional = true }
hypervisor = { path = "../hypervisor" }
kernel_cmdline = { path = "../kernel_cmdline" }
libc = "*"
minijail = { path = "../../minijail/rust/minijail" } # ignored by ebuild
+power_monitor = { path = "../power_monitor" }
+remain = "*"
resources = { path = "../resources" }
-sync = { path = "../sync" }
-base = { path = "../base" }
+sync = { path = "../common/sync" }
+thiserror = "1.0.20"
vm_control = { path = "../vm_control" }
vm_memory = { path = "../vm_memory" }
-power_monitor = { path = "../power_monitor" }
-thiserror = "1.0.20"
diff --git a/arch/cargo2android.json b/arch/cargo2android.json
new file mode 100644
index 000000000..2ae887908
--- /dev/null
+++ b/arch/cargo2android.json
@@ -0,0 +1,8 @@
+{
+ "add-module-block": "cargo2android_gdb.bp",
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/arch/cargo2android_gdb.bp b/arch/cargo2android_gdb.bp
new file mode 100644
index 000000000..49b21a132
--- /dev/null
+++ b/arch/cargo2android_gdb.bp
@@ -0,0 +1,10 @@
+target: {
+ host_linux: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+}
diff --git a/arch/src/fdt.rs b/arch/src/fdt.rs
index 944e5025a..bbb2e434d 100644
--- a/arch/src/fdt.rs
+++ b/arch/src/fdt.rs
@@ -11,28 +11,30 @@ use std::ffi::CString;
use std::io;
use std::mem::size_of;
+use remain::sorted;
use thiserror::Error as ThisError;
+#[sorted]
#[derive(ThisError, Debug)]
pub enum Error {
+ #[error("Parse error reading FDT parameters")]
+ FdtFileParseError,
+ #[error("Error writing FDT to guest memory")]
+ FdtGuestMemoryWriteError,
+ #[error("I/O error reading FDT parameters code={0}")]
+ FdtIoError(io::Error),
+ #[error("Strings cannot contain NUL")]
+ InvalidString,
+ #[error("Attempted to end a node that was not the most recent")]
+ OutOfOrderEndNode,
#[error("Properties may not be added after a node has been ended")]
PropertyAfterEndNode,
#[error("Property value size must fit in 32 bits")]
PropertyValueTooLarge,
#[error("Total size must fit in 32 bits")]
TotalSizeTooLarge,
- #[error("Strings cannot contain NUL")]
- InvalidString,
- #[error("Attempted to end a node that was not the most recent")]
- OutOfOrderEndNode,
#[error("Attempted to call finish without ending all nodes")]
UnclosedNode,
- #[error("Error writing FDT to guest memory")]
- FdtGuestMemoryWriteError,
- #[error("Parse error reading FDT parameters")]
- FdtFileParseError,
- #[error("I/O error reading FDT parameters code={0}")]
- FdtIoError(io::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
@@ -257,11 +259,11 @@ impl FdtWriter {
}
/// Write a stringlist property.
- pub fn property_string_list(&mut self, name: &str, values: Vec<String>) -> Result<()> {
+ pub fn property_string_list(&mut self, name: &str, values: &[&str]) -> Result<()> {
let mut bytes = Vec::new();
- for s in values {
+ for &s in values {
let cstr = CString::new(s).map_err(|_| Error::InvalidString)?;
- bytes.extend_from_slice(&cstr.to_bytes_with_nul());
+ bytes.extend_from_slice(cstr.to_bytes_with_nul());
}
self.property(name, &bytes)
}
@@ -511,8 +513,7 @@ mod tests {
fdt.property_u32("u32", 0x12345678).unwrap();
fdt.property_u64("u64", 0x1234567887654321).unwrap();
fdt.property_string("str", "hello").unwrap();
- fdt.property_string_list("strlst", vec!["hi".into(), "bye".into()])
- .unwrap();
+ fdt.property_string_list("strlst", &["hi", "bye"]).unwrap();
fdt.property_array_u32("arru32", &[0x12345678, 0xAABBCCDD])
.unwrap();
fdt.property_array_u64("arru64", &[0x1234567887654321])
@@ -707,8 +708,8 @@ mod tests {
#[test]
fn invalid_prop_string_list_value_nul() {
let mut fdt = FdtWriter::new(&[]);
- let strs = vec!["test".into(), "abc\0def".into()];
- fdt.property_string_list("mystr", strs)
+ let strs = ["test", "abc\0def"];
+ fdt.property_string_list("mystr", &strs)
.expect_err("stringlist property value with embedded NUL");
}
diff --git a/arch/src/lib.rs b/arch/src/lib.rs
index 5070e728c..a92d2d491 100644
--- a/arch/src/lib.rs
+++ b/arch/src/lib.rs
@@ -9,7 +9,6 @@ pub mod serial;
use std::collections::BTreeMap;
use std::error::Error as StdError;
-use std::fmt::{self, Display};
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};
use std::path::PathBuf;
@@ -17,21 +16,24 @@ use std::sync::Arc;
use acpi_tables::aml::Aml;
use acpi_tables::sdt::SDT;
-use base::{syslog, AsRawDescriptor, Event, Tube};
+use base::{syslog, AsRawDescriptor, AsRawDescriptors, Event, Tube};
use devices::virtio::VirtioDevice;
use devices::{
- Bus, BusDevice, BusError, IrqChip, PciAddress, PciDevice, PciDeviceError, PciInterruptPin,
- PciRoot, ProtectionType, ProxyDevice,
+ BarRange, Bus, BusDevice, BusDeviceObj, BusError, BusResumeDevice, HotPlugBus, IrqChip,
+ PciAddress, PciBridge, PciDevice, PciDeviceError, PciInterruptPin, PciRoot, ProxyDevice,
+ SerialHardware, SerialParameters, VfioPlatformDevice,
};
-use hypervisor::{IoEventAddress, Vm};
+use hypervisor::{IoEventAddress, ProtectionType, Vm};
use minijail::Minijail;
-use resources::{MmioType, SystemAllocator};
+use remain::sorted;
+use resources::{MmioType, SystemAllocator, SystemAllocatorConfig};
use sync::Mutex;
-use vm_control::{BatControl, BatteryType};
+use thiserror::Error;
+use vm_control::{BatControl, BatteryType, PmResource};
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
-use gdbstub::arch::x86::reg::X86_64CoreRegs as GdbStubRegs;
+use gdbstub_arch::x86::reg::X86_64CoreRegs as GdbStubRegs;
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
use {
@@ -46,7 +48,7 @@ use {
pub use serial::{
add_serial_devices, get_serial_cmdline, set_default_serial_parameters, GetSerialCmdlineError,
- SerialHardware, SerialParameters, SerialType, SERIAL_ADDR,
+ SERIAL_ADDR,
};
pub enum VmImage {
@@ -76,8 +78,11 @@ pub enum VcpuAffinity {
/// create a `RunnableLinuxVm`.
pub struct VmComponents {
pub memory_size: u64,
+ pub swiotlb: Option<u64>,
pub vcpu_count: usize,
pub vcpu_affinity: Option<VcpuAffinity>,
+ pub cpu_clusters: Vec<Vec<usize>>,
+ pub cpu_capacity: BTreeMap<usize, u32>,
pub no_smt: bool,
pub hugepages: bool,
pub vm_image: VmImage,
@@ -85,36 +90,46 @@ pub struct VmComponents {
pub pstore: Option<Pstore>,
pub initrd_image: Option<File>,
pub extra_kernel_params: Vec<String>,
- pub wayland_dmabuf: bool,
pub acpi_sdts: Vec<SDT>,
pub rt_cpus: Vec<usize>,
+ pub delay_rt: bool,
pub protected_vm: ProtectionType,
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
pub gdb: Option<(u32, Tube)>, // port and control tube.
pub dmi_path: Option<PathBuf>,
+ pub no_legacy: bool,
+ pub host_cpu_topology: bool,
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ pub force_s2idle: bool,
+ #[cfg(feature = "direct")]
+ pub direct_gpe: Vec<u32>,
}
/// Holds the elements needed to run a Linux VM. Created by `build_vm`.
-pub struct RunnableLinuxVm<V: VmArch, Vcpu: VcpuArch, I: IrqChipArch> {
+pub struct RunnableLinuxVm<V: VmArch, Vcpu: VcpuArch> {
pub vm: V,
- pub resources: SystemAllocator,
- pub exit_evt: Event,
pub vcpu_count: usize,
/// If vcpus is None, then it's the responsibility of the vcpu thread to create vcpus.
/// If it's Some, then `build_vm` already created the vcpus.
pub vcpus: Option<Vec<Vcpu>>,
pub vcpu_affinity: Option<VcpuAffinity>,
pub no_smt: bool,
- pub irq_chip: I,
+ pub irq_chip: Box<dyn IrqChipArch>,
pub has_bios: bool,
- pub io_bus: Bus,
- pub mmio_bus: Bus,
+ pub io_bus: Arc<Bus>,
+ pub mmio_bus: Arc<Bus>,
pub pid_debug_label_map: BTreeMap<u32, String>,
pub suspend_evt: Event,
pub rt_cpus: Vec<usize>,
+ pub delay_rt: bool,
pub bat_control: Option<BatControl>,
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
pub gdb: Option<(u32, Tube)>,
+ pub pm: Option<Arc<Mutex<dyn PmResource>>>,
+ /// Devices to be notified before the system resumes from the S3 suspended state.
+ pub resume_notify_devices: Vec<Arc<Mutex<dyn BusResumeDevice>>>,
+ pub root_config: Arc<Mutex<PciRoot>>,
+ pub hotplug_bus: Vec<Arc<Mutex<dyn HotPlugBus>>>,
}
/// The device and optional jail.
@@ -138,51 +153,66 @@ pub trait LinuxArch {
components: &VmComponents,
) -> std::result::Result<Vec<(GuestAddress, u64)>, Self::Error>;
+ /// Gets the configuration for a new `SystemAllocator` that fits the given `Vm`'s memory layout.
+ ///
+ /// This is the per-architecture template for constructing the `SystemAllocator`. Platform
+ /// agnostic modifications may be made to this configuration, but the final `SystemAllocator`
+ /// will be at least as strict as this configuration.
+ ///
+ /// # Arguments
+ ///
+ /// * `vm` - The virtual machine to be used as a template for the `SystemAllocator`.
+ fn get_system_allocator_config<V: Vm>(vm: &V) -> SystemAllocatorConfig;
+
/// Takes `VmComponents` and generates a `RunnableLinuxVm`.
///
/// # Arguments
///
/// * `components` - Parts to use to build the VM.
- /// * `serial_parameters` - definitions for how the serial devices should be configured.
- /// * `battery` - defines what battery device will be created.
- /// * `create_devices` - Function to generate a list of devices.
- /// * `create_irq_chip` - Function to generate an IRQ chip.
- fn build_vm<V, Vcpu, I, FD, FI, E1, E2>(
+ /// * `exit_evt` - Event used by sub-devices to request that crosvm exit because guest
+ /// wants to stop/shut down.
+ /// * `reset_evt` - Event used by sub-devices to request that crosvm exit because guest
+ /// requested reset.
+ /// * `system_allocator` - Allocator created by this trait's implementation of
+ /// `get_system_allocator_config`.
+ /// * `serial_parameters` - Definitions for how the serial devices should be configured.
+ /// * `serial_jail` - Jail used for serial devices created here.
+ /// * `battery` - Defines what battery device will be created.
+ /// * `vm` - A VM implementation to build upon.
+ /// * `ramoops_region` - Region allocated for ramoops.
+ /// * `devices` - The devices to be built into the VM.
+ /// * `irq_chip` - The IRQ chip implemention for the VM.
+ fn build_vm<V, Vcpu>(
components: VmComponents,
+ exit_evt: &Event,
+ reset_evt: &Event,
+ system_allocator: &mut SystemAllocator,
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
serial_jail: Option<Minijail>,
battery: (&Option<BatteryType>, Option<Minijail>),
vm: V,
- create_devices: FD,
- create_irq_chip: FI,
- ) -> std::result::Result<RunnableLinuxVm<V, Vcpu, I>, Self::Error>
+ ramoops_region: Option<pstore::RamoopsRegion>,
+ devices: Vec<(Box<dyn BusDeviceObj>, Option<Minijail>)>,
+ irq_chip: &mut dyn IrqChipArch,
+ kvm_vcpu_ids: &mut Vec<usize>,
+ ) -> std::result::Result<RunnableLinuxVm<V, Vcpu>, Self::Error>
where
V: VmArch,
- Vcpu: VcpuArch,
- I: IrqChipArch,
- FD: FnOnce(
- &GuestMemory,
- &mut V,
- &mut SystemAllocator,
- &Event,
- ) -> std::result::Result<Vec<(Box<dyn PciDevice>, Option<Minijail>)>, E1>,
- FI: FnOnce(&V, /* vcpu_count: */ usize) -> std::result::Result<I, E2>,
- E1: StdError + 'static,
- E2: StdError + 'static;
+ Vcpu: VcpuArch;
/// Configures the vcpu and should be called once per vcpu from the vcpu's thread.
///
/// # Arguments
///
- /// * `guest_mem` - The memory to be used by the guest.
+ /// * `vm` - The virtual machine object.
/// * `hypervisor` - The `Hypervisor` that created the vcpu.
/// * `irq_chip` - The `IrqChip` associated with this vm.
/// * `vcpu` - The VCPU object to configure.
/// * `vcpu_id` - The id of the given `vcpu`.
/// * `num_cpus` - Number of virtual CPUs the guest will have.
/// * `has_bios` - Whether the `VmImage` is a `Bios` image
- fn configure_vcpu(
- guest_mem: &GuestMemory,
+ fn configure_vcpu<V: Vm>(
+ vm: &V,
hypervisor: &dyn HypervisorArch,
irq_chip: &mut dyn IrqChipArch,
vcpu: &mut dyn VcpuArch,
@@ -190,8 +220,17 @@ pub trait LinuxArch {
num_cpus: usize,
has_bios: bool,
no_smt: bool,
+ host_cpu_topology: bool,
) -> Result<(), Self::Error>;
+ /// Configures and add a pci device into vm
+ fn register_pci_device<V: VmArch, Vcpu: VcpuArch>(
+ linux: &mut RunnableLinuxVm<V, Vcpu>,
+ device: Box<dyn PciDevice>,
+ minijail: Option<Minijail>,
+ resources: &mut SystemAllocator,
+ ) -> Result<PciAddress, Self::Error>;
+
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
/// Reads vCPU's registers.
fn debug_read_registers<T: VcpuArch>(vcpu: &T) -> Result<GdbStubRegs, Self::Error>;
@@ -231,83 +270,236 @@ pub trait LinuxArch {
}
/// Errors for device manager.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum DeviceRegistrationError {
+ /// No more MMIO space available.
+ #[error("no more addresses are available")]
+ AddrsExhausted,
+ /// Could not allocate device address space for the device.
+ #[error("Allocating device addresses: {0}")]
+ AllocateDeviceAddrs(PciDeviceError),
/// Could not allocate IO space for the device.
+ #[error("Allocating IO addresses: {0}")]
AllocateIoAddrs(PciDeviceError),
/// Could not allocate MMIO or IO resource for the device.
+ #[error("Allocating IO resource: {0}")]
AllocateIoResource(resources::Error),
- /// Could not allocate device address space for the device.
- AllocateDeviceAddrs(PciDeviceError),
/// Could not allocate an IRQ number.
+ #[error("Allocating IRQ number")]
AllocateIrq,
+ /// Could not allocate IRQ resource for the device.
+ #[error("Allocating IRQ resource: {0}")]
+ AllocateIrqResource(devices::vfio::VfioError),
+ /// Unable to clone a jail for the device.
+ #[error("failed to clone jail: {0}")]
+ CloneJail(minijail::Error),
+ /// Appending to kernel command line failed.
+ #[error("unable to add device to kernel command line: {0}")]
+ Cmdline(kernel_cmdline::Error),
+ /// Configure window size failed.
+ #[error("failed to configure window size: {0}")]
+ ConfigureWindowSize(PciDeviceError),
// Unable to create a pipe.
+ #[error("failed to create pipe: {0}")]
CreatePipe(base::Error),
// Unable to create serial device from serial parameters
- CreateSerialDevice(serial::Error),
+ #[error("failed to create serial device: {0}")]
+ CreateSerialDevice(devices::SerialError),
// Unable to create tube
+ #[error("failed to create tube: {0}")]
CreateTube(base::TubeError),
/// Could not clone an event.
+ #[error("failed to clone event: {0}")]
EventClone(base::Error),
/// Could not create an event.
+ #[error("failed to create event: {0}")]
EventCreate(base::Error),
+ /// No more IRQs are available.
+ #[error("no more IRQs are available")]
+ IrqsExhausted,
/// Missing a required serial device.
+ #[error("missing required serial device {0}")]
MissingRequiredSerialDevice(u8),
/// Could not add a device to the mmio bus.
+ #[error("failed to add to mmio bus: {0}")]
MmioInsert(BusError),
- /// Failed to register ioevent with VM.
- RegisterIoevent(base::Error),
- /// Failed to register irq event with VM.
- RegisterIrqfd(base::Error),
/// Failed to initialize proxy device for jailed device.
+ #[error("failed to create proxy device: {0}")]
ProxyDeviceCreation(devices::ProxyError),
- /// Appending to kernel command line failed.
- Cmdline(kernel_cmdline::Error),
- /// No more IRQs are available.
- IrqsExhausted,
- /// No more MMIO space available.
- AddrsExhausted,
+ /// Failed to register battery device.
+ #[error("failed to register battery device to VM: {0}")]
+ RegisterBattery(devices::BatteryError),
/// Could not register PCI device capabilities.
+ #[error("could not register PCI device capabilities: {0}")]
RegisterDeviceCapabilities(PciDeviceError),
- // Failed to register battery device.
- RegisterBattery(devices::BatteryError),
+ /// Failed to register ioevent with VM.
+ #[error("failed to register ioevent to VM: {0}")]
+ RegisterIoevent(base::Error),
+ /// Failed to register irq event with VM.
+ #[error("failed to register irq event to VM: {0}")]
+ RegisterIrqfd(base::Error),
+ /// Could not setup VFIO platform IRQ for the device.
+ #[error("Setting up VFIO platform IRQ: {0}")]
+ SetupVfioPlatformIrq(anyhow::Error),
+}
+
+/// Config a PCI device for used by this vm.
+pub fn configure_pci_device<V: VmArch, Vcpu: VcpuArch>(
+ linux: &mut RunnableLinuxVm<V, Vcpu>,
+ mut device: Box<dyn PciDevice>,
+ jail: Option<Minijail>,
+ resources: &mut SystemAllocator,
+) -> Result<PciAddress, DeviceRegistrationError> {
+ // Allocate PCI device address before allocating BARs.
+ let pci_address = device
+ .allocate_address(resources)
+ .map_err(DeviceRegistrationError::AllocateDeviceAddrs)?;
+
+ // Allocate ranges that may need to be in the low MMIO region (MmioType::Low).
+ let mmio_ranges = device
+ .allocate_io_bars(resources)
+ .map_err(DeviceRegistrationError::AllocateIoAddrs)?;
+
+ // Allocate device ranges that may be in low or high MMIO after low-only ranges.
+ let device_ranges = device
+ .allocate_device_bars(resources)
+ .map_err(DeviceRegistrationError::AllocateDeviceAddrs)?;
+
+ // Do not suggest INTx for hot-plug devices.
+ let intx_event = devices::IrqLevelEvent::new().map_err(DeviceRegistrationError::EventCreate)?;
+
+ if let Some((gsi, _pin)) = device.assign_irq(&intx_event, None) {
+ resources.reserve_irq(gsi);
+
+ linux
+ .irq_chip
+ .as_irq_chip_mut()
+ .register_level_irq_event(gsi, &intx_event)
+ .map_err(DeviceRegistrationError::RegisterIrqfd)?;
+ }
+
+ let mut keep_rds = device.keep_rds();
+ syslog::push_descriptors(&mut keep_rds);
+
+ device
+ .register_device_capabilities()
+ .map_err(DeviceRegistrationError::RegisterDeviceCapabilities)?;
+ for (event, addr, datamatch) in device.ioevents() {
+ let io_addr = IoEventAddress::Mmio(addr);
+ linux
+ .vm
+ .register_ioevent(event, io_addr, datamatch)
+ .map_err(DeviceRegistrationError::RegisterIoevent)?;
+ keep_rds.push(event.as_raw_descriptor());
+ }
+ let arced_dev: Arc<Mutex<dyn BusDevice>> = if let Some(jail) = jail {
+ let proxy = ProxyDevice::new(device, &jail, keep_rds)
+ .map_err(DeviceRegistrationError::ProxyDeviceCreation)?;
+ linux
+ .pid_debug_label_map
+ .insert(proxy.pid() as u32, proxy.debug_label());
+ Arc::new(Mutex::new(proxy))
+ } else {
+ device.on_sandboxed();
+ Arc::new(Mutex::new(device))
+ };
+
+ linux
+ .root_config
+ .lock()
+ .add_device(pci_address, arced_dev.clone());
+
+ for range in &mmio_ranges {
+ linux
+ .mmio_bus
+ .insert(arced_dev.clone(), range.addr, range.size)
+ .map_err(DeviceRegistrationError::MmioInsert)?;
+ }
+
+ for range in &device_ranges {
+ linux
+ .mmio_bus
+ .insert(arced_dev.clone(), range.addr, range.size)
+ .map_err(DeviceRegistrationError::MmioInsert)?;
+ }
+
+ Ok(pci_address)
}
-impl Display for DeviceRegistrationError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::DeviceRegistrationError::*;
-
- match self {
- AllocateIoAddrs(e) => write!(f, "Allocating IO addresses: {}", e),
- AllocateIoResource(e) => write!(f, "Allocating IO resource: {}", e),
- AllocateDeviceAddrs(e) => write!(f, "Allocating device addresses: {}", e),
- AllocateIrq => write!(f, "Allocating IRQ number"),
- CreatePipe(e) => write!(f, "failed to create pipe: {}", e),
- CreateSerialDevice(e) => write!(f, "failed to create serial device: {}", e),
- CreateTube(e) => write!(f, "failed to create tube: {}", e),
- Cmdline(e) => write!(f, "unable to add device to kernel command line: {}", e),
- EventClone(e) => write!(f, "failed to clone event: {}", e),
- EventCreate(e) => write!(f, "failed to create event: {}", e),
- MissingRequiredSerialDevice(n) => write!(f, "missing required serial device {}", n),
- MmioInsert(e) => write!(f, "failed to add to mmio bus: {}", e),
- RegisterIoevent(e) => write!(f, "failed to register ioevent to VM: {}", e),
- RegisterIrqfd(e) => write!(f, "failed to register irq event to VM: {}", e),
- ProxyDeviceCreation(e) => write!(f, "failed to create proxy device: {}", e),
- IrqsExhausted => write!(f, "no more IRQs are available"),
- AddrsExhausted => write!(f, "no more addresses are available"),
- RegisterDeviceCapabilities(e) => {
- write!(f, "could not register PCI device capabilities: {}", e)
+/// Creates a platform device for use by this Vm.
+pub fn generate_platform_bus(
+ devices: Vec<(VfioPlatformDevice, Option<Minijail>)>,
+ irq_chip: &mut dyn IrqChip,
+ mmio_bus: &Bus,
+ resources: &mut SystemAllocator,
+) -> Result<BTreeMap<u32, String>, DeviceRegistrationError> {
+ let mut pid_labels = BTreeMap::new();
+
+ // Allocate ranges that may need to be in the Platform MMIO region (MmioType::Platform).
+ for (mut device, jail) in devices.into_iter() {
+ let ranges = device
+ .allocate_regions(resources)
+ .map_err(DeviceRegistrationError::AllocateIoResource)?;
+
+ let mut keep_rds = device.keep_rds();
+ syslog::push_descriptors(&mut keep_rds);
+
+ let irqs = device
+ .get_platform_irqs()
+ .map_err(DeviceRegistrationError::AllocateIrqResource)?;
+ for irq in irqs.into_iter() {
+ let irq_num = resources
+ .allocate_irq()
+ .ok_or(DeviceRegistrationError::AllocateIrq)?;
+
+ if device.irq_is_automask(&irq) {
+ let irq_evt =
+ devices::IrqLevelEvent::new().map_err(DeviceRegistrationError::EventCreate)?;
+ irq_chip
+ .register_level_irq_event(irq_num, &irq_evt)
+ .map_err(DeviceRegistrationError::RegisterIrqfd)?;
+ device
+ .assign_level_platform_irq(&irq_evt, irq.index)
+ .map_err(DeviceRegistrationError::SetupVfioPlatformIrq)?;
+ keep_rds.extend(irq_evt.as_raw_descriptors());
+ } else {
+ let irq_evt =
+ devices::IrqEdgeEvent::new().map_err(DeviceRegistrationError::EventCreate)?;
+ irq_chip
+ .register_edge_irq_event(irq_num, &irq_evt)
+ .map_err(DeviceRegistrationError::RegisterIrqfd)?;
+ device
+ .assign_edge_platform_irq(&irq_evt, irq.index)
+ .map_err(DeviceRegistrationError::SetupVfioPlatformIrq)?;
+ keep_rds.extend(irq_evt.as_raw_descriptors());
}
- RegisterBattery(e) => write!(f, "failed to register battery device to VM: {}", e),
+ }
+
+ let arced_dev: Arc<Mutex<dyn BusDevice>> = if let Some(jail) = jail {
+ let proxy = ProxyDevice::new(device, &jail, keep_rds)
+ .map_err(DeviceRegistrationError::ProxyDeviceCreation)?;
+ pid_labels.insert(proxy.pid() as u32, proxy.debug_label());
+ Arc::new(Mutex::new(proxy))
+ } else {
+ device.on_sandboxed();
+ Arc::new(Mutex::new(device))
+ };
+ for range in &ranges {
+ mmio_bus
+ .insert(arced_dev.clone(), range.0, range.1)
+ .map_err(DeviceRegistrationError::MmioInsert)?;
}
}
+ Ok(pid_labels)
}
/// Creates a root PCI device for use by this Vm.
pub fn generate_pci_root(
mut devices: Vec<(Box<dyn PciDevice>, Option<Minijail>)>,
- irq_chip: &mut impl IrqChip,
- mmio_bus: &mut Bus,
+ irq_chip: &mut dyn IrqChip,
+ mmio_bus: Arc<Bus>,
+ io_bus: Arc<Bus>,
resources: &mut SystemAllocator,
vm: &mut impl Vm,
max_irqs: usize,
@@ -319,46 +511,78 @@ pub fn generate_pci_root(
),
DeviceRegistrationError,
> {
- let mut root = PciRoot::new();
- let mut pci_irqs = Vec::new();
+ let mut root = PciRoot::new(Arc::downgrade(&mmio_bus), Arc::downgrade(&io_bus));
let mut pid_labels = BTreeMap::new();
-
- let mut irqs: Vec<Option<u32>> = vec![None; max_irqs];
+ // The map of (dev_idx, bus), find bus number through dev_idx in devices
+ let mut devid_buses: BTreeMap<usize, u8> = BTreeMap::new();
+ // The map of (bridge secondary bus number, Vec<sub device BarRange>)
+ let mut bridge_bar_ranges: BTreeMap<u8, Vec<BarRange>> = BTreeMap::new();
// Allocate PCI device address before allocating BARs.
let mut device_addrs = Vec::<PciAddress>::new();
- for (device, _jail) in devices.iter_mut() {
+ for (dev_idx, (device, _jail)) in devices.iter_mut().enumerate() {
let address = device
.allocate_address(resources)
.map_err(DeviceRegistrationError::AllocateDeviceAddrs)?;
device_addrs.push(address);
+
+ if address.bus > 0 {
+ devid_buses.insert(dev_idx, address.bus);
+ }
+
+ if PciBridge::is_pci_bridge(device) {
+ let sec_bus = PciBridge::get_secondary_bus_num(device);
+ bridge_bar_ranges.insert(sec_bus, Vec::<BarRange>::new());
+ }
}
// Allocate ranges that may need to be in the low MMIO region (MmioType::Low).
let mut io_ranges = BTreeMap::new();
for (dev_idx, (device, _jail)) in devices.iter_mut().enumerate() {
- let ranges = device
+ let mut ranges = device
.allocate_io_bars(resources)
.map_err(DeviceRegistrationError::AllocateIoAddrs)?;
- io_ranges.insert(dev_idx, ranges);
+ io_ranges.insert(dev_idx, ranges.clone());
+
+ if let Some(bus) = devid_buses.get(&dev_idx) {
+ if let Some(bridge_bar) = bridge_bar_ranges.get_mut(bus) {
+ bridge_bar.append(&mut ranges);
+ }
+ }
}
// Allocate device ranges that may be in low or high MMIO after low-only ranges.
let mut device_ranges = BTreeMap::new();
for (dev_idx, (device, _jail)) in devices.iter_mut().enumerate() {
- let ranges = device
+ let mut ranges = device
.allocate_device_bars(resources)
.map_err(DeviceRegistrationError::AllocateDeviceAddrs)?;
- device_ranges.insert(dev_idx, ranges);
+ device_ranges.insert(dev_idx, ranges.clone());
+
+ if let Some(bus) = devid_buses.get(&dev_idx) {
+ if let Some(bridge_bar) = bridge_bar_ranges.get_mut(bus) {
+ bridge_bar.append(&mut ranges);
+ }
+ }
}
- for (dev_idx, (mut device, jail)) in devices.into_iter().enumerate() {
- let address = device_addrs[dev_idx];
- let mut keep_rds = device.keep_rds();
- syslog::push_descriptors(&mut keep_rds);
+ for (device, _jail) in devices.iter_mut() {
+ if PciBridge::is_pci_bridge(device) {
+ let sec_bus = PciBridge::get_secondary_bus_num(device);
+ if let Some(bridge_bar) = bridge_bar_ranges.get(&sec_bus) {
+ device
+ .configure_bridge_window(resources, bridge_bar)
+ .map_err(DeviceRegistrationError::ConfigureWindowSize)?;
+ }
+ }
+ }
+
+ // Allocate legacy INTx
+ let mut pci_irqs = Vec::new();
+ let mut irqs: Vec<Option<u32>> = vec![None; max_irqs];
- let irqfd = Event::new().map_err(DeviceRegistrationError::EventCreate)?;
- let irq_resample_fd = Event::new().map_err(DeviceRegistrationError::EventCreate)?;
+ for (dev_idx, (device, _jail)) in devices.iter_mut().enumerate() {
+ // For default interrupt routing use next preallocated interrupt from the pool.
let irq_num = if let Some(irq) = irqs[dev_idx % max_irqs] {
irq
} else {
@@ -368,23 +592,30 @@ pub fn generate_pci_root(
irqs[dev_idx % max_irqs] = Some(irq);
irq
};
- // Rotate interrupt pins across PCI logical functions.
- let pci_irq_pin = match address.func % 4 {
- 0 => PciInterruptPin::IntA,
- 1 => PciInterruptPin::IntB,
- 2 => PciInterruptPin::IntC,
- 3 => PciInterruptPin::IntD,
- _ => unreachable!(), // Obviously not possible, but the compiler is not smart enough.
- };
- irq_chip
- .register_irq_event(irq_num, &irqfd, Some(&irq_resample_fd))
- .map_err(DeviceRegistrationError::RegisterIrqfd)?;
+ let intx_event =
+ devices::IrqLevelEvent::new().map_err(DeviceRegistrationError::EventCreate)?;
+
+ if let Some((gsi, pin)) = device.assign_irq(&intx_event, Some(irq_num)) {
+ // reserve INTx if needed and non-default.
+ if gsi != irq_num {
+ resources.reserve_irq(gsi);
+ };
+ irq_chip
+ .register_level_irq_event(gsi, &intx_event)
+ .map_err(DeviceRegistrationError::RegisterIrqfd)?;
+
+ pci_irqs.push((device_addrs[dev_idx], gsi, pin));
+ }
+ }
+
+ for (dev_idx, (mut device, jail)) in devices.into_iter().enumerate() {
+ let address = device_addrs[dev_idx];
+
+ let mut keep_rds = device.keep_rds();
+ syslog::push_descriptors(&mut keep_rds);
+ keep_rds.append(&mut vm.get_memory().as_raw_descriptors());
- keep_rds.push(irqfd.as_raw_descriptor());
- keep_rds.push(irq_resample_fd.as_raw_descriptor());
- device.assign_irq(irqfd, irq_resample_fd, irq_num, pci_irq_pin);
- pci_irqs.push((address, irq_num, pci_irq_pin));
let ranges = io_ranges.remove(&dev_idx).unwrap_or_default();
let device_ranges = device_ranges.remove(&dev_idx).unwrap_or_default();
device
@@ -392,10 +623,11 @@ pub fn generate_pci_root(
.map_err(DeviceRegistrationError::RegisterDeviceCapabilities)?;
for (event, addr, datamatch) in device.ioevents() {
let io_addr = IoEventAddress::Mmio(addr);
- vm.register_ioevent(&event, io_addr, datamatch)
+ vm.register_ioevent(event, io_addr, datamatch)
.map_err(DeviceRegistrationError::RegisterIoevent)?;
keep_rds.push(event.as_raw_descriptor());
}
+
let arced_dev: Arc<Mutex<dyn BusDevice>> = if let Some(jail) = jail {
let proxy = ProxyDevice::new(device, &jail, keep_rds)
.map_err(DeviceRegistrationError::ProxyDeviceCreation)?;
@@ -408,13 +640,13 @@ pub fn generate_pci_root(
root.add_device(address, arced_dev.clone());
for range in &ranges {
mmio_bus
- .insert(arced_dev.clone(), range.0, range.1)
+ .insert(arced_dev.clone(), range.addr, range.size)
.map_err(DeviceRegistrationError::MmioInsert)?;
}
for range in &device_ranges {
mmio_bus
- .insert(arced_dev.clone(), range.0, range.1)
+ .insert(arced_dev.clone(), range.addr, range.size)
.map_err(DeviceRegistrationError::MmioInsert)?;
}
}
@@ -435,8 +667,8 @@ pub fn generate_pci_root(
pub fn add_goldfish_battery(
amls: &mut Vec<u8>,
battery_jail: Option<Minijail>,
- mmio_bus: &mut Bus,
- irq_chip: &mut impl IrqChip,
+ mmio_bus: &Bus,
+ irq_chip: &mut dyn IrqChip,
irq_num: u32,
resources: &mut SystemAllocator,
) -> Result<Tube, DeviceRegistrationError> {
@@ -451,11 +683,10 @@ pub fn add_goldfish_battery(
)
.map_err(DeviceRegistrationError::AllocateIoResource)?;
- let irq_evt = Event::new().map_err(DeviceRegistrationError::EventCreate)?;
- let irq_resample_evt = Event::new().map_err(DeviceRegistrationError::EventCreate)?;
+ let irq_evt = devices::IrqLevelEvent::new().map_err(DeviceRegistrationError::EventCreate)?;
irq_chip
- .register_irq_event(irq_num, &irq_evt, Some(&irq_resample_evt))
+ .register_level_irq_event(irq_num, &irq_evt)
.map_err(DeviceRegistrationError::RegisterIrqfd)?;
let (control_tube, response_tube) =
@@ -468,16 +699,10 @@ pub fn add_goldfish_battery(
#[cfg(not(feature = "power-monitor-powerd"))]
let create_monitor = None;
- let goldfish_bat = devices::GoldfishBattery::new(
- mmio_base,
- irq_num,
- irq_evt,
- irq_resample_evt,
- response_tube,
- create_monitor,
- )
- .map_err(DeviceRegistrationError::RegisterBattery)?;
- Aml::to_aml_bytes(&goldfish_bat, amls);
+ let goldfish_bat =
+ devices::GoldfishBattery::new(mmio_base, irq_num, irq_evt, response_tube, create_monitor)
+ .map_err(DeviceRegistrationError::RegisterBattery)?;
+ goldfish_bat.to_aml_bytes(amls);
match battery_jail.as_ref() {
Some(jail) => {
@@ -486,7 +711,7 @@ pub fn add_goldfish_battery(
mmio_bus
.insert(
Arc::new(Mutex::new(
- ProxyDevice::new(goldfish_bat, &jail, keep_rds)
+ ProxyDevice::new(goldfish_bat, jail, keep_rds)
.map_err(DeviceRegistrationError::ProxyDeviceCreation)?,
)),
mmio_base,
@@ -509,25 +734,17 @@ pub fn add_goldfish_battery(
}
/// Errors for image loading.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum LoadImageError {
+ #[error("Alignment not a power of two: {0}")]
BadAlignment(u64),
- Seek(io::Error),
+ #[error("Image size too large: {0}")]
ImageSizeTooLarge(u64),
+ #[error("Reading image into memory failed: {0}")]
ReadToMemory(GuestMemoryError),
-}
-
-impl Display for LoadImageError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::LoadImageError::*;
-
- match self {
- BadAlignment(a) => write!(f, "Alignment not a power of two: {}", a),
- Seek(e) => write!(f, "Seek failed: {}", e),
- ImageSizeTooLarge(size) => write!(f, "Image size too large: {}", size),
- ReadToMemory(e) => write!(f, "Reading image into memory failed: {}", e),
- }
- }
+ #[error("Seek failed: {0}")]
+ Seek(io::Error),
}
/// Load an image from a file into guest memory.
diff --git a/arch/src/pstore.rs b/arch/src/pstore.rs
index 9335a1d46..ee7e0a7eb 100644
--- a/arch/src/pstore.rs
+++ b/arch/src/pstore.rs
@@ -2,42 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fmt::{self, Display};
use std::fs::OpenOptions;
-use std::io;
use crate::Pstore;
+use anyhow::{bail, Context, Result};
use base::MemoryMappingBuilder;
use hypervisor::Vm;
-use resources::SystemAllocator;
-use resources::{Alloc, MmioType};
+use resources::MemRegion;
use vm_memory::GuestAddress;
-/// Error for pstore.
-#[derive(Debug)]
-pub enum Error {
- IoError(io::Error),
- MmapError(base::MmapError),
- ResourcesError(resources::Error),
- SysUtilError(base::Error),
-}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- IoError(e) => write!(f, "failed to create pstore backend file: {}", e),
- MmapError(e) => write!(f, "failed to get file mapped address: {}", e),
- ResourcesError(e) => write!(f, "failed to allocate pstore region: {}", e),
- SysUtilError(e) => write!(f, "file to add pstore region to mmio: {}", e),
- }
- }
-}
-
-impl std::error::Error for Error {}
-type Result<T> = std::result::Result<T, Error>;
-
pub struct RamoopsRegion {
pub address: u64,
pub size: u32,
@@ -46,37 +19,55 @@ pub struct RamoopsRegion {
/// Creates a mmio memory region for pstore.
pub fn create_memory_region(
vm: &mut impl Vm,
- resources: &mut SystemAllocator,
+ region: &MemRegion,
pstore: &Pstore,
) -> Result<RamoopsRegion> {
+ if region.size < pstore.size.into() {
+ bail!("insufficient space for pstore {:?} {}", region, pstore.size);
+ }
+
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&pstore.path)
- .map_err(Error::IoError)?;
- file.set_len(pstore.size as u64).map_err(Error::IoError)?;
-
- let address = resources
- .mmio_allocator(MmioType::High)
- .allocate(pstore.size as u64, Alloc::Pstore, "pstore".to_owned())
- .map_err(Error::ResourcesError)?;
+ .context("failed to open pstore")?;
+ file.set_len(pstore.size as u64)
+ .context("failed to set pstore length")?;
let memory_mapping = MemoryMappingBuilder::new(pstore.size as usize)
.from_file(&file)
.build()
- .map_err(Error::MmapError)?;
+ .context("failed to mmap pstore")?;
vm.add_memory_region(
- GuestAddress(address),
+ GuestAddress(region.base),
Box::new(memory_mapping),
false,
false,
)
- .map_err(Error::SysUtilError)?;
+ .context("failed to add pstore region")?;
Ok(RamoopsRegion {
- address,
+ address: region.base,
size: pstore.size,
})
}
+
+pub fn add_ramoops_kernel_cmdline(
+ cmdline: &mut kernel_cmdline::Cmdline,
+ ramoops_region: &RamoopsRegion,
+) -> std::result::Result<(), kernel_cmdline::Error> {
+ // It seems that default record_size is only 4096 byte even if crosvm allocates
+ // more memory. It means that one crash can only 4096 byte.
+ // Set record_size and console_size to 1/4 of allocated memory size.
+ // This configulation is same as the host.
+ let ramoops_opts = [
+ ("mem_address", ramoops_region.address),
+ ("mem_size", ramoops_region.size as u64),
+ ];
+ for (name, val) in &ramoops_opts {
+ cmdline.insert_str(format!("ramoops.{}={:#x}", name, val))?;
+ }
+ Ok(())
+}
diff --git a/arch/src/serial.rs b/arch/src/serial.rs
index 1b9ec1c93..d8b2e1b80 100644
--- a/arch/src/serial.rs
+++ b/arch/src/serial.rs
@@ -2,350 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::borrow::Cow;
use std::collections::BTreeMap;
-use std::fmt::{self, Display};
-use std::fs::{File, OpenOptions};
-use std::io::{self, stdin, stdout, ErrorKind};
-use std::os::unix::net::UnixDatagram;
-use std::path::{Path, PathBuf};
-use std::str::FromStr;
use std::sync::Arc;
-use std::thread;
-use std::time::Duration;
-use base::{error, info, read_raw_stdin, syslog, AsRawDescriptor, Event, RawDescriptor};
-use devices::{Bus, ProtectionType, ProxyDevice, Serial, SerialDevice};
+use base::Event;
+use devices::serial_device::{SerialHardware, SerialParameters, SerialType};
+use devices::{Bus, ProxyDevice, Serial};
+use hypervisor::ProtectionType;
use minijail::Minijail;
+use remain::sorted;
use sync::Mutex;
+use thiserror::Error as ThisError;
use crate::DeviceRegistrationError;
-#[derive(Debug)]
-pub enum Error {
- CloneEvent(base::Error),
- FileError(std::io::Error),
- InvalidSerialHardware(String),
- InvalidSerialType(String),
- InvalidPath,
- PathRequired,
- SocketCreateFailed,
- Unimplemented(SerialType),
-}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- CloneEvent(e) => write!(f, "unable to clone an Event: {}", e),
- FileError(e) => write!(f, "unable to open/create file: {}", e),
- InvalidSerialHardware(e) => write!(f, "invalid serial hardware: {}", e),
- InvalidSerialType(e) => write!(f, "invalid serial type: {}", e),
- InvalidPath => write!(f, "serial device path is invalid"),
- PathRequired => write!(f, "serial device type file requires a path"),
- SocketCreateFailed => write!(f, "failed to create unbound socket"),
- Unimplemented(e) => write!(f, "serial device type {} not implemented", e.to_string()),
- }
- }
-}
-
-/// Enum for possible type of serial devices
-#[derive(Clone, Debug)]
-pub enum SerialType {
- File,
- Stdout,
- Sink,
- Syslog,
- UnixSocket,
-}
-
-impl Display for SerialType {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- let s = match &self {
- SerialType::File => "File".to_string(),
- SerialType::Stdout => "Stdout".to_string(),
- SerialType::Sink => "Sink".to_string(),
- SerialType::Syslog => "Syslog".to_string(),
- SerialType::UnixSocket => "UnixSocket".to_string(),
- };
-
- write!(f, "{}", s)
- }
-}
-
-impl FromStr for SerialType {
- type Err = Error;
- fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
- match s {
- "file" | "File" => Ok(SerialType::File),
- "stdout" | "Stdout" => Ok(SerialType::Stdout),
- "sink" | "Sink" => Ok(SerialType::Sink),
- "syslog" | "Syslog" => Ok(SerialType::Syslog),
- "unix" | "UnixSocket" => Ok(SerialType::UnixSocket),
- _ => Err(Error::InvalidSerialType(s.to_string())),
- }
- }
-}
-
-/// Serial device hardware types
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub enum SerialHardware {
- Serial, // Standard PC-style (8250/16550 compatible) UART
- VirtioConsole, // virtio-console device
-}
-
-impl Display for SerialHardware {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- let s = match &self {
- SerialHardware::Serial => "serial".to_string(),
- SerialHardware::VirtioConsole => "virtio-console".to_string(),
- };
-
- write!(f, "{}", s)
- }
-}
-
-impl FromStr for SerialHardware {
- type Err = Error;
- fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
- match s {
- "serial" => Ok(SerialHardware::Serial),
- "virtio-console" => Ok(SerialHardware::VirtioConsole),
- _ => Err(Error::InvalidSerialHardware(s.to_string())),
- }
- }
-}
-
-struct WriteSocket {
- sock: UnixDatagram,
- buf: String,
-}
-
-const BUF_CAPACITY: usize = 1024;
-
-impl WriteSocket {
- pub fn new(s: UnixDatagram) -> WriteSocket {
- WriteSocket {
- sock: s,
- buf: String::with_capacity(BUF_CAPACITY),
- }
- }
-
- pub fn send_buf(&self, buf: &[u8]) -> io::Result<usize> {
- const SEND_RETRY: usize = 2;
- let mut sent = 0;
- for _ in 0..SEND_RETRY {
- match self.sock.send(&buf[..]) {
- Ok(bytes_sent) => {
- sent = bytes_sent;
- break;
- }
- Err(e) => info!("Send error: {:?}", e),
- }
- }
- Ok(sent)
- }
-}
-
-impl io::Write for WriteSocket {
- fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
- let parsed_str = String::from_utf8_lossy(buf);
-
- let last_newline_idx = match parsed_str.rfind('\n') {
- Some(newline_idx) => Some(self.buf.len() + newline_idx),
- None => None,
- };
- self.buf.push_str(&parsed_str);
-
- match last_newline_idx {
- Some(last_newline_idx) => {
- for line in (self.buf[..last_newline_idx]).lines() {
- if self.send_buf(line.as_bytes()).is_err() {
- break;
- }
- }
- self.buf.drain(..=last_newline_idx);
- }
- None => {
- if self.buf.len() >= BUF_CAPACITY {
- if let Err(e) = self.send_buf(self.buf.as_bytes()) {
- info!("Couldn't send full buffer. {:?}", e);
- }
- self.buf.clear();
- }
- }
- }
- Ok(buf.len())
- }
-
- fn flush(&mut self) -> io::Result<()> {
- Ok(())
- }
-}
-
-/// Holds the parameters for a serial device
-#[derive(Clone, Debug)]
-pub struct SerialParameters {
- pub type_: SerialType,
- pub hardware: SerialHardware,
- pub path: Option<PathBuf>,
- pub input: Option<PathBuf>,
- pub num: u8,
- pub console: bool,
- pub earlycon: bool,
- pub stdin: bool,
-}
-
-// The maximum length of a path that can be used as the address of a
-// unix socket. Note that this includes the null-terminator.
-const MAX_SOCKET_PATH_LENGTH: usize = 108;
-
-impl SerialParameters {
- /// Helper function to create a serial device from the defined parameters.
- ///
- /// # Arguments
- /// * `evt` - event used for interrupt events
- /// * `keep_rds` - Vector of descriptors required by this device if it were sandboxed
- /// in a child process. `evt` will always be added to this vector by
- /// this function.
- pub fn create_serial_device<T: SerialDevice>(
- &self,
- protected_vm: ProtectionType,
- evt: &Event,
- keep_rds: &mut Vec<RawDescriptor>,
- ) -> std::result::Result<T, Error> {
- let evt = evt.try_clone().map_err(Error::CloneEvent)?;
- keep_rds.push(evt.as_raw_descriptor());
- let input: Option<Box<dyn io::Read + Send>> = if let Some(input_path) = &self.input {
- let input_file = File::open(input_path.as_path()).map_err(Error::FileError)?;
- keep_rds.push(input_file.as_raw_descriptor());
- Some(Box::new(input_file))
- } else if self.stdin {
- keep_rds.push(stdin().as_raw_descriptor());
- // This wrapper is used in place of the libstd native version because we don't want
- // buffering for stdin.
- struct StdinWrapper;
- impl io::Read for StdinWrapper {
- fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
- read_raw_stdin(out).map_err(|e| e.into())
- }
- }
- Some(Box::new(StdinWrapper))
- } else {
- None
- };
- let output: Option<Box<dyn io::Write + Send>> = match self.type_ {
- SerialType::Stdout => {
- keep_rds.push(stdout().as_raw_descriptor());
- Some(Box::new(stdout()))
- }
- SerialType::Sink => None,
- SerialType::Syslog => {
- syslog::push_descriptors(keep_rds);
- Some(Box::new(syslog::Syslogger::new(
- syslog::Priority::Info,
- syslog::Facility::Daemon,
- )))
- }
- SerialType::File => match &self.path {
- Some(path) => {
- let file = OpenOptions::new()
- .append(true)
- .create(true)
- .open(path.as_path())
- .map_err(Error::FileError)?;
- keep_rds.push(file.as_raw_descriptor());
- Some(Box::new(file))
- }
- None => return Err(Error::PathRequired),
- },
- SerialType::UnixSocket => {
- match &self.path {
- Some(path) => {
- // If the path is longer than 107 characters,
- // then we won't be able to connect directly
- // to it. Instead we can shorten the path by
- // opening the containing directory and using
- // /proc/self/fd/*/ to access it via a shorter
- // path.
- let mut path_cow = Cow::<Path>::Borrowed(path);
- let mut _dir_fd = None;
- if path.as_os_str().len() >= MAX_SOCKET_PATH_LENGTH {
- let mut short_path = PathBuf::with_capacity(MAX_SOCKET_PATH_LENGTH);
- short_path.push("/proc/self/fd/");
-
- // We don't actually want to open this
- // directory for reading, but the stdlib
- // requires all files be opened as at
- // least one of readable, writeable, or
- // appeandable.
- let dir = OpenOptions::new()
- .read(true)
- .open(path.parent().ok_or(Error::InvalidPath)?)
- .map_err(Error::FileError)?;
-
- short_path.push(dir.as_raw_descriptor().to_string());
- short_path.push(path.file_name().ok_or(Error::InvalidPath)?);
- path_cow = Cow::Owned(short_path);
- _dir_fd = Some(dir);
- }
-
- // The shortened path may still be too long,
- // in which case we must give up here.
- if path_cow.as_os_str().len() >= MAX_SOCKET_PATH_LENGTH {
- return Err(Error::InvalidPath);
- }
-
- // There's a race condition between
- // vmlog_forwarder making the logging socket and
- // crosvm starting up, so we loop here until it's
- // available.
- let sock = UnixDatagram::unbound().map_err(Error::FileError)?;
- loop {
- match sock.connect(&path_cow) {
- Ok(_) => break,
- Err(e) => {
- match e.kind() {
- ErrorKind::NotFound | ErrorKind::ConnectionRefused => {
- // logging socket doesn't
- // exist yet, sleep for 10 ms
- // and try again.
- thread::sleep(Duration::from_millis(10))
- }
- _ => {
- error!("Unexpected error connecting to logging socket: {:?}", e);
- return Err(Error::FileError(e));
- }
- }
- }
- };
- }
- keep_rds.push(sock.as_raw_descriptor());
- Some(Box::new(WriteSocket::new(sock)))
- }
- None => return Err(Error::PathRequired),
- }
- }
- };
- Ok(T::new(protected_vm, evt, input, output, keep_rds.to_vec()))
- }
-
- pub fn add_bind_mounts(&self, jail: &mut Minijail) -> Result<(), minijail::Error> {
- if let Some(path) = &self.path {
- if let SerialType::UnixSocket = self.type_ {
- if let Some(parent) = path.as_path().parent() {
- if parent.exists() {
- info!("Bind mounting dir {}", parent.display());
- jail.mount_bind(parent, parent, true)?;
- }
- }
- }
- }
- Ok(())
- }
-}
-
/// Add the default serial parameters for serial ports that have not already been specified.
///
/// This ensures that `serial_parameters` will contain parameters for each of the four PC-style
@@ -356,11 +26,12 @@ impl SerialParameters {
/// configured explicitly.
pub fn set_default_serial_parameters(
serial_parameters: &mut BTreeMap<(SerialHardware, u8), SerialParameters>,
+ is_vhost_user_console_enabled: bool,
) {
// If no console device exists and the first serial port has not been specified,
// set the first serial port as a stdout+stdin console.
let default_console = (SerialHardware::Serial, 1);
- if !serial_parameters.iter().any(|(_, p)| p.console) {
+ if !serial_parameters.iter().any(|(_, p)| p.console) && !is_vhost_user_console_enabled {
serial_parameters
.entry(default_console)
.or_insert(SerialParameters {
@@ -372,6 +43,7 @@ pub fn set_default_serial_parameters(
console: true,
earlycon: false,
stdin: true,
+ out_timestamp: false,
});
}
@@ -389,6 +61,7 @@ pub fn set_default_serial_parameters(
console: false,
earlycon: false,
stdin: false,
+ out_timestamp: false,
});
}
}
@@ -410,7 +83,7 @@ pub const SERIAL_ADDR: [u64; 4] = [0x3f8, 0x2f8, 0x3e8, 0x2e8];
/// All four of the traditional PC-style serial ports (COM1-COM4) must be specified.
pub fn add_serial_devices(
protected_vm: ProtectionType,
- io_bus: &mut Bus,
+ io_bus: &Bus,
com_evt_1_3: &Event,
com_evt_2_4: &Event,
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
@@ -431,14 +104,20 @@ pub fn add_serial_devices(
let mut preserved_fds = Vec::new();
let com = param
- .create_serial_device::<Serial>(protected_vm, &com_evt, &mut preserved_fds)
+ .create_serial_device::<Serial>(protected_vm, com_evt, &mut preserved_fds)
.map_err(DeviceRegistrationError::CreateSerialDevice)?;
match serial_jail.as_ref() {
Some(jail) => {
let com = Arc::new(Mutex::new(
- ProxyDevice::new(com, &jail, preserved_fds)
- .map_err(DeviceRegistrationError::ProxyDeviceCreation)?,
+ ProxyDevice::new(
+ com,
+ &jail
+ .try_clone()
+ .map_err(DeviceRegistrationError::CloneJail)?,
+ preserved_fds,
+ )
+ .map_err(DeviceRegistrationError::ProxyDeviceCreation)?,
));
io_bus
.insert(com.clone(), SERIAL_ADDR[x as usize], 0x8)
@@ -456,30 +135,21 @@ pub fn add_serial_devices(
Ok(())
}
-#[derive(Debug)]
+#[sorted]
+#[derive(ThisError, Debug)]
pub enum GetSerialCmdlineError {
+ #[error("Error appending to cmdline: {0}")]
KernelCmdline(kernel_cmdline::Error),
+ #[error("Hardware {0} not supported as earlycon")]
UnsupportedEarlyconHardware(SerialHardware),
}
-impl Display for GetSerialCmdlineError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::GetSerialCmdlineError::*;
-
- match self {
- KernelCmdline(e) => write!(f, "error appending to cmdline: {}", e),
- UnsupportedEarlyconHardware(hw) => {
- write!(f, "hardware {} not supported as earlycon", hw)
- }
- }
- }
-}
-
pub type GetSerialCmdlineResult<T> = std::result::Result<T, GetSerialCmdlineError>;
/// Add serial options to the provided `cmdline` based on `serial_parameters`.
/// `serial_io_type` should be "io" if the platform uses x86-style I/O ports for serial devices
/// or "mmio" if the serial ports are memory mapped.
+// TODO(b/227407433): Support cases where vhost-user console is specified.
pub fn get_serial_cmdline(
cmdline: &mut kernel_cmdline::Cmdline,
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
@@ -539,7 +209,7 @@ mod tests {
let mut cmdline = Cmdline::new(4096);
let mut serial_parameters = BTreeMap::new();
- set_default_serial_parameters(&mut serial_parameters);
+ set_default_serial_parameters(&mut serial_parameters, false);
get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
.expect("get_serial_cmdline failed");
@@ -564,10 +234,11 @@ mod tests {
console: true,
earlycon: false,
stdin: true,
+ out_timestamp: false,
},
);
- set_default_serial_parameters(&mut serial_parameters);
+ set_default_serial_parameters(&mut serial_parameters, false);
get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
.expect("get_serial_cmdline failed");
@@ -592,6 +263,7 @@ mod tests {
console: true,
earlycon: false,
stdin: true,
+ out_timestamp: false,
},
);
@@ -607,10 +279,11 @@ mod tests {
console: false,
earlycon: true,
stdin: false,
+ out_timestamp: false,
},
);
- set_default_serial_parameters(&mut serial_parameters);
+ set_default_serial_parameters(&mut serial_parameters, false);
get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
.expect("get_serial_cmdline failed");
@@ -636,10 +309,11 @@ mod tests {
console: false,
earlycon: true,
stdin: true,
+ out_timestamp: false,
},
);
- set_default_serial_parameters(&mut serial_parameters);
+ set_default_serial_parameters(&mut serial_parameters, false);
get_serial_cmdline(&mut cmdline, &serial_parameters, "io")
.expect_err("get_serial_cmdline succeeded");
}
diff --git a/base/Android.bp b/base/Android.bp
index 003ff41bc..f1b95b867 100644
--- a/base/Android.bp
+++ b/base/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -10,38 +10,57 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "base_defaults",
+rust_test {
+ name: "base_test_src_lib",
defaults: ["crosvm_defaults"],
crate_name: "base",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2021",
rustlibs: [
- "libcros_async",
+ "libaudio_streams",
+ "libcfg_if",
+ "libchrono",
"libdata_model",
"liblibc",
+ "librand",
+ "libregex",
"libserde",
"libserde_json",
"libsmallvec",
"libsync_rust",
- "libsys_util",
+ "libtempfile",
"libthiserror",
],
-}
-
-rust_test_host {
- name: "base_host_test_src_lib",
- defaults: ["base_defaults"],
- test_options: {
- unit_test: true,
+ proc_macros: [
+ "libbase_poll_token_derive",
+ "libremain",
+ ],
+ target: {
+ android: {
+ rustlibs: ["libandroid_log_sys"],
+ },
+ linux_bionic_arm64: {
+ // For ARM architecture, we use aarch64-linux-android for BOTH
+ // device and host targets. As a result, host targets are also
+ // built with target_os = "android". Therefore, sys_util/src/android
+ // is used and thus this android module is required.
+ // This seems incorrect, but is inevitable because rustc doesn't
+ // yet support a Linux-based target using Bionic as libc. We can't
+ // use aarch64-unknown-linux-gnu because it's using glibc which
+ // we don't support for cross-host builds.
+ rustlibs: [
+ "libandroid_log_sys",
+ ],
+ },
},
-}
-
-rust_test {
- name: "base_device_test_src_lib",
- defaults: ["base_defaults"],
+ shared_libs: ["libcap"], // specified in src/unix/capabilities.rs
}
rust_library {
@@ -50,56 +69,46 @@ rust_library {
stem: "libbase",
host_supported: true,
crate_name: "base",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
- "libcros_async",
+ "libaudio_streams",
+ "libcfg_if",
+ "libchrono",
"libdata_model",
"liblibc",
+ "librand",
+ "libregex",
"libserde",
"libserde_json",
"libsmallvec",
"libsync_rust",
- "libsys_util",
+ "libtempfile",
"libthiserror",
],
+ proc_macros: [
+ "libbase_poll_token_derive",
+ "libremain",
+ ],
+ target: {
+ android: {
+ rustlibs: ["libandroid_log_sys"],
+ },
+ linux_bionic_arm64: {
+ // For ARM architecture, we use aarch64-linux-android for BOTH
+ // device and host targets. As a result, host targets are also
+ // built with target_os = "android". Therefore, sys_util/src/android
+ // is used and thus this android module is required.
+ // This seems incorrect, but is inevitable because rustc doesn't
+ // yet support a Linux-based target using Bionic as libc. We can't
+ // use aarch64-unknown-linux-gnu because it's using glibc which
+ // we don't support for cross-host builds.
+ rustlibs: [
+ "libandroid_log_sys",
+ ],
+ },
+ },
+ shared_libs: ["libcap"], // specified in src/unix/capabilities.rs
}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/base/Cargo.toml b/base/Cargo.toml
index aecbb3eaa..0dc7854e5 100644
--- a/base/Cargo.toml
+++ b/base/Cargo.toml
@@ -2,18 +2,32 @@
name = "base"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[features]
-chromeos = ["sys_util/chromeos"]
+chromeos = []
[dependencies]
-cros_async = { path = "../cros_async" }
-data_model = { path = "../data_model" }
+audio_streams = { path = "../common/audio_streams" }
+data_model = { path = "../common/data_model" }
libc = "*"
+remain = "0.2"
serde = { version = "1", features = [ "derive" ] }
serde_json = "*"
smallvec = "1.6.1"
-sync = { path = "../sync" }
-sys_util = { path = "../sys_util" }
+sync = { path = "../common/sync" }
thiserror = "1.0.20"
+tempfile = "3"
+base_poll_token_derive = { path = "base_poll_token_derive" }
+cfg-if = "*"
+rand = "*"
+chrono = "*"
+regex = "*"
+
+[target.'cfg(windows)'.dependencies]
+lazy_static = "*"
+winapi = "*"
+win_util = { path = "../win_util"}
+
+[build-dependencies]
+cc = "*"
diff --git a/sys_util/poll_token_derive/Android.bp b/base/base_poll_token_derive/Android.bp
index 3023e24a5..83cff00b6 100644
--- a/sys_util/poll_token_derive/Android.bp
+++ b/base/base_poll_token_derive/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -10,12 +10,19 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_proc_macro {
- name: "libpoll_token_derive",
- defaults: ["crosvm_proc_macro_defaults"],
- crate_name: "poll_token_derive",
+rust_test_host {
+ name: "base_poll_token_derive_test_poll_token_derive",
+ defaults: ["crosvm_defaults"],
+ crate_name: "base_poll_token_derive",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["poll_token_derive.rs"],
- edition: "2018",
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2021",
rustlibs: [
"libproc_macro2",
"libquote",
@@ -23,23 +30,17 @@ rust_proc_macro {
],
}
-rust_test_host {
- name: "poll_token_derive_host_test_poll_token_derive",
+rust_proc_macro {
+ name: "libbase_poll_token_derive",
defaults: ["crosvm_defaults"],
- crate_name: "poll_token_derive",
+ crate_name: "base_poll_token_derive",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["poll_token_derive.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libproc_macro2",
"libquote",
"libsyn",
],
}
-
-// dependent_library ["feature_list"]
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// syn-1.0.70 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// unicode-xid-0.2.1 "default"
diff --git a/base/base_poll_token_derive/Cargo.toml b/base/base_poll_token_derive/Cargo.toml
new file mode 100644
index 000000000..63a270397
--- /dev/null
+++ b/base/base_poll_token_derive/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "base_poll_token_derive"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2021"
+include = ["*.rs", "Cargo.toml"]
+
+[lib]
+proc-macro = true
+path = "poll_token_derive.rs"
+
+[dependencies]
+proc-macro2 = "^1"
+quote = "^1"
+syn = "^1"
+
diff --git a/base/base_poll_token_derive/cargo2android.json b/base/base_poll_token_derive/cargo2android.json
new file mode 100644
index 000000000..fba95e53f
--- /dev/null
+++ b/base/base_poll_token_derive/cargo2android.json
@@ -0,0 +1,7 @@
+{
+ "run": true,
+ "device": false,
+ "tests": true,
+ "global_defaults": "crosvm_defaults",
+ "add_workspace": true
+}
diff --git a/sys_util/poll_token_derive/poll_token_derive.rs b/base/base_poll_token_derive/poll_token_derive.rs
index 7b7baac1c..5b6eb3b17 100644
--- a/sys_util/poll_token_derive/poll_token_derive.rs
+++ b/base/base_poll_token_derive/poll_token_derive.rs
@@ -55,7 +55,7 @@ fn generate_as_raw_token(enum_name: &Ident, variants: &[Variant]) -> TokenStream
// The capture string is for everything between the variant identifier and the `=>` in
// the match arm: the variant's data capture.
let capture = variant.fields.iter().next().map(|field| {
- let member = field_member(&field);
+ let member = field_member(field);
quote!({ #member: data })
});
@@ -96,7 +96,7 @@ fn generate_from_raw_token(enum_name: &Ident, variants: &[Variant]) -> TokenStre
// The data string is for extracting the enum variant's data bits out of the raw token
// data, which includes both variant index and data bits.
let data = variant.fields.iter().next().map(|field| {
- let member = field_member(&field);
+ let member = field_member(field);
let ty = &field.ty;
quote!({ #member: (data >> #variant_bits) as #ty })
});
diff --git a/sys_util/poll_token_derive/tests.rs b/base/base_poll_token_derive/tests.rs
index 935f91c9b..935f91c9b 100644
--- a/sys_util/poll_token_derive/tests.rs
+++ b/base/base_poll_token_derive/tests.rs
diff --git a/base/build.rs b/base/build.rs
new file mode 100644
index 000000000..4517f2b9b
--- /dev/null
+++ b/base/build.rs
@@ -0,0 +1,9 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+fn main() {
+ cc::Build::new()
+ .file("src/windows/stdio_fileno.c")
+ .compile("stdio_fileno");
+}
diff --git a/base/cargo2android.json b/base/cargo2android.json
new file mode 100644
index 000000000..d6d53ef99
--- /dev/null
+++ b/base/cargo2android.json
@@ -0,0 +1,9 @@
+{
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "no_presubmit": true,
+ "patch": "patches/Android.bp.patch",
+ "run": true,
+ "tests": true
+}
diff --git a/base/patches/Android.bp.patch b/base/patches/Android.bp.patch
new file mode 100644
index 000000000..68b5d5bc4
--- /dev/null
+++ b/base/patches/Android.bp.patch
@@ -0,0 +1,81 @@
+diff --git a/base/Android.bp b/base/Android.bp
+index a3fe04d9..55ab423c 100644
+--- a/base/Android.bp
++++ b/base/Android.bp
+@@ -13,8 +13,6 @@ package {
+ rust_test {
+ name: "base_test_src_lib",
+ defaults: ["crosvm_defaults"],
+- // has rustc warnings
+- host_supported: true,
+ crate_name: "base",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
+@@ -22,7 +20,7 @@ rust_test {
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ test_options: {
+- unit_test: false,
++ unit_test: true,
+ },
+ edition: "2021",
+ rustlibs: [
+@@ -44,14 +42,31 @@ rust_test {
+ "libbase_poll_token_derive",
+ "libremain",
+ ],
+- static_libs: ["libstdio_fileno"],
++ target: {
++ android: {
++ rustlibs: ["libandroid_log_sys"],
++ },
++ linux_bionic_arm64: {
++ // For ARM architecture, we use aarch64-linux-android for BOTH
++ // device and host targets. As a result, host targets are also
++ // built with target_os = "android". Therefore, sys_util/src/android
++ // is used and thus this android module is required.
++ // This seems incorrect, but is inevitable because rustc doesn't
++ // yet support a Linux-based target using Bionic as libc. We can't
++ // use aarch64-unknown-linux-gnu because it's using glibc which
++ // we don't support for cross-host builds.
++ rustlibs: [
++ "libandroid_log_sys",
++ ],
++ },
++ },
++ shared_libs: ["libcap"], // specified in src/unix/capabilities.rs
+ }
+
+ rust_library {
+ name: "libbase_rust",
+ defaults: ["crosvm_defaults"],
+ stem: "libbase",
+- // has rustc warnings
+ host_supported: true,
+ crate_name: "base",
+ cargo_env_compat: true,
+@@ -77,5 +92,23 @@ rust_library {
+ "libbase_poll_token_derive",
+ "libremain",
+ ],
+- static_libs: ["libstdio_fileno"],
++ target: {
++ android: {
++ rustlibs: ["libandroid_log_sys"],
++ },
++ linux_bionic_arm64: {
++ // For ARM architecture, we use aarch64-linux-android for BOTH
++ // device and host targets. As a result, host targets are also
++ // built with target_os = "android". Therefore, sys_util/src/android
++ // is used and thus this android module is required.
++ // This seems incorrect, but is inevitable because rustc doesn't
++ // yet support a Linux-based target using Bionic as libc. We can't
++ // use aarch64-unknown-linux-gnu because it's using glibc which
++ // we don't support for cross-host builds.
++ rustlibs: [
++ "libandroid_log_sys",
++ ],
++ },
++ },
++ shared_libs: ["libcap"], // specified in src/unix/capabilities.rs
+ }
diff --git a/sys_util/src/alloc.rs b/base/src/alloc.rs
index 8ac9100a9..9cb6a55fa 100644
--- a/sys_util/src/alloc.rs
+++ b/base/src/alloc.rs
@@ -2,7 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::alloc::{alloc, alloc_zeroed, dealloc, Layout};
+use std::{
+ alloc::{alloc, alloc_zeroed, dealloc, Layout},
+ cmp::min,
+};
/// A contiguous memory allocation with a specified size and alignment, with a
/// Drop impl to perform the deallocation.
@@ -15,7 +18,7 @@ use std::alloc::{alloc, alloc_zeroed, dealloc, Layout};
/// ```
/// use std::alloc::Layout;
/// use std::mem;
-/// use sys_util::LayoutAllocation;
+/// use crate::LayoutAllocation;
///
/// #[repr(C)]
/// struct Header {
@@ -88,6 +91,11 @@ impl LayoutAllocation {
self.ptr as *mut T
}
+ /// Returns a reference to the `Layout` used to create this allocation.
+ pub fn layout(&self) -> &Layout {
+ &self.layout
+ }
+
/// Returns a shared reference to the allocated data.
///
/// # Safety
@@ -109,6 +117,42 @@ impl LayoutAllocation {
pub unsafe fn as_mut<T>(&mut self) -> &mut T {
&mut *self.as_ptr()
}
+
+ /// Returns a shared slice reference to the allocated data.
+ ///
+ /// # Arguments
+ ///
+ /// `num_elements` - Number of `T` elements to include in the slice.
+ /// The length of the slice will be capped to the allocation's size.
+ /// Caller must ensure that any sliced elements are initialized.
+ ///
+ /// # Safety
+ ///
+ /// Caller is responsible for ensuring that the data behind this pointer has
+ /// been initialized as much as necessary and that there are no already
+ /// existing mutable references to any part of the data.
+ pub unsafe fn as_slice<T>(&self, num_elements: usize) -> &[T] {
+ let len = min(num_elements, self.layout.size() / std::mem::size_of::<T>());
+ std::slice::from_raw_parts(self.as_ptr(), len)
+ }
+
+ /// Returns an exclusive slice reference to the allocated data.
+ ///
+ /// # Arguments
+ ///
+ /// `num_elements` - Number of `T` elements to include in the slice.
+ /// The length of the slice will be capped to the allocation's size.
+ /// Caller must ensure that any sliced elements are initialized.
+ ///
+ /// # Safety
+ ///
+ /// Caller is responsible for ensuring that the data behind this pointer has
+ /// been initialized as much as necessary and that there are no already
+ /// existing references to any part of the data.
+ pub unsafe fn as_mut_slice<T>(&mut self, num_elements: usize) -> &mut [T] {
+ let len = min(num_elements, self.layout.size() / std::mem::size_of::<T>());
+ std::slice::from_raw_parts_mut(self.as_ptr(), len)
+ }
}
impl Drop for LayoutAllocation {
@@ -121,3 +165,51 @@ impl Drop for LayoutAllocation {
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::mem::{align_of, size_of};
+
+ #[test]
+ fn test_as_slice_u32() {
+ let layout = Layout::from_size_align(size_of::<u32>() * 15, align_of::<u32>()).unwrap();
+ let allocation = LayoutAllocation::zeroed(layout);
+ let slice: &[u32] = unsafe { allocation.as_slice(15) };
+ assert_eq!(slice.len(), 15);
+ assert_eq!(slice[0], 0);
+ assert_eq!(slice[14], 0);
+ }
+
+ #[test]
+ fn test_as_slice_u32_smaller_len() {
+ let layout = Layout::from_size_align(size_of::<u32>() * 15, align_of::<u32>()).unwrap();
+ let allocation = LayoutAllocation::zeroed(layout);
+
+ // Slice less than the allocation size, which will return a slice of only the requested length.
+ let slice: &[u32] = unsafe { allocation.as_slice(5) };
+ assert_eq!(slice.len(), 5);
+ }
+
+ #[test]
+ fn test_as_slice_u32_larger_len() {
+ let layout = Layout::from_size_align(size_of::<u32>() * 15, align_of::<u32>()).unwrap();
+ let allocation = LayoutAllocation::zeroed(layout);
+
+ // Slice more than the allocation size, which will clamp the returned slice len to the limit.
+ let slice: &[u32] = unsafe { allocation.as_slice(100) };
+ assert_eq!(slice.len(), 15);
+ }
+
+ #[test]
+ fn test_as_slice_u32_remainder() {
+ // Allocate a buffer that is not a multiple of u32 in size.
+ let layout = Layout::from_size_align(size_of::<u32>() * 15 + 2, align_of::<u32>()).unwrap();
+ let allocation = LayoutAllocation::zeroed(layout);
+
+ // Slice as many u32s as possible, which should return a slice that only includes the full
+ // u32s, not the trailing 2 bytes.
+ let slice: &[u32] = unsafe { allocation.as_slice(100) };
+ assert_eq!(slice.len(), 15);
+ }
+}
diff --git a/base/src/async_types.rs b/base/src/async_types.rs
deleted file mode 100644
index 4fbe53e93..000000000
--- a/base/src/async_types.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2020 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use crate::AsRawDescriptor;
-use cros_async::IntoAsync;
-use std::os::unix::io::{AsRawFd, RawFd};
-
-/// Like `cros_async::IntoAsync`, except for use with crosvm's AsRawDescriptor
-/// trait object family.
-pub trait DescriptorIntoAsync: AsRawDescriptor {}
-
-/// To use an IO struct with cros_async, the type must be marked with
-/// DescriptorIntoAsync (to signify it is suitable for use with async
-/// operations), and then wrapped with this type.
-pub struct DescriptorAdapter<T: DescriptorIntoAsync>(pub T);
-impl<T> AsRawFd for DescriptorAdapter<T>
-where
- T: DescriptorIntoAsync,
-{
- fn as_raw_fd(&self) -> RawFd {
- self.0.as_raw_descriptor()
- }
-}
-impl<T> IntoAsync for DescriptorAdapter<T> where T: DescriptorIntoAsync {}
diff --git a/base/src/descriptor.rs b/base/src/descriptor.rs
new file mode 100644
index 000000000..0b840eae9
--- /dev/null
+++ b/base/src/descriptor.rs
@@ -0,0 +1,130 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::{PollToken, RawDescriptor};
+use serde::{Deserialize, Serialize};
+use std::{
+ fs::File,
+ mem::{self, ManuallyDrop},
+};
+
+/// Wraps a RawDescriptor and safely closes it when self falls out of scope.
+#[derive(Serialize, Deserialize, Debug, Eq)]
+#[serde(transparent)]
+pub struct SafeDescriptor {
+ #[serde(with = "super::with_raw_descriptor")]
+ pub(crate) descriptor: RawDescriptor,
+}
+
+/// Trait for forfeiting ownership of the current raw descriptor, and returning the raw descriptor
+pub trait IntoRawDescriptor {
+ fn into_raw_descriptor(self) -> RawDescriptor;
+}
+
+/// Trait for returning the underlying raw descriptor, without giving up ownership of the
+/// descriptor.
+pub trait AsRawDescriptor {
+ fn as_raw_descriptor(&self) -> RawDescriptor;
+}
+
+/// A trait similar to `AsRawDescriptor` but supports an arbitrary number of descriptors.
+pub trait AsRawDescriptors {
+ fn as_raw_descriptors(&self) -> Vec<RawDescriptor>;
+}
+
+pub trait FromRawDescriptor {
+ /// # Safety
+ /// Safe only if the caller ensures nothing has access to the descriptor after passing it to
+ /// `from_raw_descriptor`
+ unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Self;
+}
+
+impl AsRawDescriptor for SafeDescriptor {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.descriptor
+ }
+}
+
+impl<T> AsRawDescriptors for T
+where
+ T: AsRawDescriptor,
+{
+ fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
+ vec![self.as_raw_descriptor()]
+ }
+}
+
+impl IntoRawDescriptor for SafeDescriptor {
+ fn into_raw_descriptor(self) -> RawDescriptor {
+ let descriptor = self.descriptor;
+ mem::forget(self);
+ descriptor
+ }
+}
+
+impl FromRawDescriptor for SafeDescriptor {
+ unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Self {
+ SafeDescriptor { descriptor }
+ }
+}
+
+impl TryFrom<&dyn AsRawDescriptor> for SafeDescriptor {
+ type Error = std::io::Error;
+
+ /// Clones the underlying descriptor (handle), internally creating a new descriptor.
+ ///
+ /// WARNING: Windows does NOT support cloning/duplicating all types of handles. DO NOT use this
+ /// function on IO completion ports, sockets, or pseudo-handles (except those from
+ /// GetCurrentProcess or GetCurrentThread). See
+ /// https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle
+ /// for further details.
+ ///
+ /// TODO(b/191800567): this API has sharp edges on Windows. We should evaluate making some
+ /// adjustments to smooth those edges.
+ fn try_from(rd: &dyn AsRawDescriptor) -> std::result::Result<Self, Self::Error> {
+ // Safe because the underlying raw descriptor is guaranteed valid by rd's existence.
+ //
+ // Note that we are cloning the underlying raw descriptor since we have no guarantee of
+ // its existence after this function returns.
+ let rd_as_safe_desc = ManuallyDrop::new(unsafe {
+ SafeDescriptor::from_raw_descriptor(rd.as_raw_descriptor())
+ });
+
+ // We have to clone rd because we have no guarantee ownership was transferred (rd is
+ // borrowed).
+ rd_as_safe_desc
+ .try_clone()
+ .map_err(|e| Self::Error::from_raw_os_error(e.errno()))
+ }
+}
+
+impl From<File> for SafeDescriptor {
+ fn from(f: File) -> SafeDescriptor {
+ // Safe because we own the File at this point.
+ unsafe { SafeDescriptor::from_raw_descriptor(f.into_raw_descriptor()) }
+ }
+}
+
+/// For use cases where a simple wrapper around a RawDescriptor is needed.
+/// This is a simply a wrapper and does not manage the lifetime of the descriptor.
+/// Most usages should prefer SafeDescriptor or using a RawDescriptor directly
+#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
+#[repr(transparent)]
+pub struct Descriptor(pub RawDescriptor);
+impl AsRawDescriptor for Descriptor {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.0
+ }
+}
+
+/// Implement token for implementations that wish to use this struct as such
+impl PollToken for Descriptor {
+ fn as_raw_token(&self) -> u64 {
+ self.0 as u64
+ }
+
+ fn from_raw_token(data: u64) -> Self {
+ Descriptor(data as RawDescriptor)
+ }
+}
diff --git a/base/src/descriptor_reflection.rs b/base/src/descriptor_reflection.rs
new file mode 100644
index 000000000..4aa7e1162
--- /dev/null
+++ b/base/src/descriptor_reflection.rs
@@ -0,0 +1,550 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Provides infrastructure for de/serializing descriptors embedded in Rust data structures.
+//!
+//! # Example
+//!
+//! ```
+//! use serde_json::to_string;
+//! use crate::platform::{
+//! FileSerdeWrapper, FromRawDescriptor, SafeDescriptor, SerializeDescriptors,
+//! deserialize_with_descriptors,
+//! };
+//! use tempfile::tempfile;
+//!
+//! let tmp_f = tempfile().unwrap();
+//!
+//! // Uses a simple wrapper to serialize a File because we can't implement Serialize for File.
+//! let data = FileSerdeWrapper(tmp_f);
+//!
+//! // Wraps Serialize types to collect side channel descriptors as Serialize is called.
+//! let data_wrapper = SerializeDescriptors::new(&data);
+//!
+//! // Use the wrapper with any serializer to serialize data is normal, grabbing descriptors
+//! // as the data structures are serialized by the serializer.
+//! let out_json = serde_json::to_string(&data_wrapper).expect("failed to serialize");
+//!
+//! // If data_wrapper contains any side channel descriptor refs
+//! // (it contains tmp_f in this case), we can retrieve the actual descriptors
+//! // from the side channel using into_descriptors().
+//! let out_descriptors = data_wrapper.into_descriptors();
+//!
+//! // When sending out_json over some transport, also send out_descriptors.
+//!
+//! // For this example, we aren't really transporting data across the process, but we do need to
+//! // convert the descriptor type.
+//! let mut safe_descriptors = out_descriptors
+//! .iter()
+//! .map(|&v| Some(unsafe { SafeDescriptor::from_raw_descriptor(v) }))
+//! .collect();
+//! std::mem::forget(data); // Prevent double drop of tmp_f.
+//!
+//! // The deserialize_with_descriptors function is used give the descriptor deserializers access
+//! // to side channel descriptors.
+//! let res: FileSerdeWrapper =
+//! deserialize_with_descriptors(|| serde_json::from_str(&out_json), &mut safe_descriptors)
+//! .expect("failed to deserialize");
+//! ```
+
+use std::{
+ cell::{Cell, RefCell},
+ convert::TryInto,
+ fmt,
+ fs::File,
+ ops::{Deref, DerefMut},
+ panic::{catch_unwind, resume_unwind, AssertUnwindSafe},
+};
+
+use serde::{
+ de::{
+ Error, Visitor, {self},
+ },
+ ser, Deserialize, Deserializer, Serialize, Serializer,
+};
+
+use super::RawDescriptor;
+use crate::descriptor::SafeDescriptor;
+
+thread_local! {
+ static DESCRIPTOR_DST: RefCell<Option<Vec<RawDescriptor>>> = Default::default();
+}
+
+/// Initializes the thread local storage for descriptor serialization. Fails if it was already
+/// initialized without an intervening `take_descriptor_dst` on this thread.
+fn init_descriptor_dst() -> Result<(), &'static str> {
+ DESCRIPTOR_DST.with(|d| {
+ let mut descriptors = d.borrow_mut();
+ if descriptors.is_some() {
+ return Err(
+ "attempt to initialize descriptor destination that was already initialized",
+ );
+ }
+ *descriptors = Some(Default::default());
+ Ok(())
+ })
+}
+
+/// Takes the thread local storage for descriptor serialization. Fails if there wasn't a prior call
+/// to `init_descriptor_dst` on this thread.
+fn take_descriptor_dst() -> Result<Vec<RawDescriptor>, &'static str> {
+ match DESCRIPTOR_DST.with(|d| d.replace(None)) {
+ Some(d) => Ok(d),
+ None => Err("attempt to take descriptor destination before it was initialized"),
+ }
+}
+
+/// Pushes a descriptor on the thread local destination of descriptors, returning the index in which
+/// the descriptor was pushed.
+//
+/// Returns Err if the thread local destination was not already initialized.
+fn push_descriptor(rd: RawDescriptor) -> Result<usize, &'static str> {
+ DESCRIPTOR_DST.with(|d| {
+ d.borrow_mut()
+ .as_mut()
+ .ok_or("attempt to serialize descriptor without descriptor destination")
+ .map(|descriptors| {
+ let index = descriptors.len();
+ descriptors.push(rd);
+ index
+ })
+ })
+}
+
+/// Serializes a descriptor for later retrieval in a parent `SerializeDescriptors` struct.
+///
+/// If there is no parent `SerializeDescriptors` being serialized, this will return an error.
+///
+/// For convenience, it is recommended to use the `with_raw_descriptor` module in a `#[serde(with =
+/// "...")]` attribute which will make use of this function.
+pub fn serialize_descriptor<S: Serializer>(
+ rd: &RawDescriptor,
+ se: S,
+) -> std::result::Result<S::Ok, S::Error> {
+ let index = push_descriptor(*rd).map_err(ser::Error::custom)?;
+ se.serialize_u32(
+ index
+ .try_into()
+ .map_err(|_| ser::Error::custom("attempt to serialize too many descriptors at once"))?,
+ )
+}
+
+/// Wrapper for a `Serialize` value which will capture any descriptors exported by the value when
+/// given to an ordinary `Serializer`.
+///
+/// This is the corresponding type to use for serialization before using
+/// `deserialize_with_descriptors`.
+///
+/// # Examples
+///
+/// ```
+/// use serde_json::to_string;
+/// use crate::platform::{FileSerdeWrapper, SerializeDescriptors};
+/// use tempfile::tempfile;
+///
+/// let tmp_f = tempfile().unwrap();
+/// let data = FileSerdeWrapper(tmp_f);
+/// let data_wrapper = SerializeDescriptors::new(&data);
+///
+/// // Serializes `v` as normal...
+/// let out_json = serde_json::to_string(&data_wrapper).expect("failed to serialize");
+/// // If `serialize_descriptor` was called, we can capture the descriptors from here.
+/// let out_descriptors = data_wrapper.into_descriptors();
+/// ```
+pub struct SerializeDescriptors<'a, T: Serialize>(&'a T, Cell<Vec<RawDescriptor>>);
+
+impl<'a, T: Serialize> SerializeDescriptors<'a, T> {
+ pub fn new(inner: &'a T) -> Self {
+ Self(inner, Default::default())
+ }
+
+ pub fn into_descriptors(self) -> Vec<RawDescriptor> {
+ self.1.into_inner()
+ }
+}
+
+impl<'a, T: Serialize> Serialize for SerializeDescriptors<'a, T> {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ init_descriptor_dst().map_err(ser::Error::custom)?;
+
+ // catch_unwind is used to ensure that init_descriptor_dst is always balanced with a call to
+ // take_descriptor_dst afterwards.
+ let res = catch_unwind(AssertUnwindSafe(|| self.0.serialize(serializer)));
+ self.1.set(take_descriptor_dst().unwrap());
+ match res {
+ Ok(r) => r,
+ Err(e) => resume_unwind(e),
+ }
+ }
+}
+
+thread_local! {
+ static DESCRIPTOR_SRC: RefCell<Option<Vec<Option<SafeDescriptor>>>> = Default::default();
+}
+
+/// Sets the thread local storage of descriptors for deserialization. Fails if this was already
+/// called without a call to `take_descriptor_src` on this thread.
+///
+/// This is given as a collection of `Option` so that unused descriptors can be returned.
+fn set_descriptor_src(descriptors: Vec<Option<SafeDescriptor>>) -> Result<(), &'static str> {
+ DESCRIPTOR_SRC.with(|d| {
+ let mut src = d.borrow_mut();
+ if src.is_some() {
+ return Err("attempt to set descriptor source that was already set");
+ }
+ *src = Some(descriptors);
+ Ok(())
+ })
+}
+
+/// Takes the thread local storage of descriptors for deserialization. Fails if the storage was
+/// already taken or never set with `set_descriptor_src`.
+///
+/// If deserialization was done, the descriptors will mostly come back as `None` unless some of them
+/// were unused.
+fn take_descriptor_src() -> Result<Vec<Option<SafeDescriptor>>, &'static str> {
+ DESCRIPTOR_SRC.with(|d| {
+ d.replace(None)
+ .ok_or("attempt to take descriptor source which was never set")
+ })
+}
+
+/// Takes a descriptor at the given index from the thread local source of descriptors.
+//
+/// Returns None if the thread local source was not already initialized.
+fn take_descriptor(index: usize) -> Result<SafeDescriptor, &'static str> {
+ DESCRIPTOR_SRC.with(|d| {
+ d.borrow_mut()
+ .as_mut()
+ .ok_or("attempt to deserialize descriptor without descriptor source")?
+ .get_mut(index)
+ .ok_or("attempt to deserialize out of bounds descriptor")?
+ .take()
+ .ok_or("attempt to deserialize descriptor that was already taken")
+ })
+}
+
+/// Deserializes a descriptor provided via `deserialize_with_descriptors`.
+///
+/// If `deserialize_with_descriptors` is not in the call chain, this will return an error.
+///
+/// For convenience, it is recommended to use the `with_raw_descriptor` module in a `#[serde(with =
+/// "...")]` attribute which will make use of this function.
+pub fn deserialize_descriptor<'de, D>(de: D) -> std::result::Result<SafeDescriptor, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ struct DescriptorVisitor;
+
+ impl<'de> Visitor<'de> for DescriptorVisitor {
+ type Value = u32;
+
+ fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ formatter.write_str("an integer which fits into a u32")
+ }
+
+ fn visit_u8<E: de::Error>(self, value: u8) -> Result<Self::Value, E> {
+ Ok(value as _)
+ }
+
+ fn visit_u16<E: de::Error>(self, value: u16) -> Result<Self::Value, E> {
+ Ok(value as _)
+ }
+
+ fn visit_u32<E: de::Error>(self, value: u32) -> Result<Self::Value, E> {
+ Ok(value)
+ }
+
+ fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
+ value.try_into().map_err(E::custom)
+ }
+
+ fn visit_u128<E: de::Error>(self, value: u128) -> Result<Self::Value, E> {
+ value.try_into().map_err(E::custom)
+ }
+
+ fn visit_i8<E: de::Error>(self, value: i8) -> Result<Self::Value, E> {
+ value.try_into().map_err(E::custom)
+ }
+
+ fn visit_i16<E: de::Error>(self, value: i16) -> Result<Self::Value, E> {
+ value.try_into().map_err(E::custom)
+ }
+
+ fn visit_i32<E: de::Error>(self, value: i32) -> Result<Self::Value, E> {
+ value.try_into().map_err(E::custom)
+ }
+
+ fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
+ value.try_into().map_err(E::custom)
+ }
+
+ fn visit_i128<E: de::Error>(self, value: i128) -> Result<Self::Value, E> {
+ value.try_into().map_err(E::custom)
+ }
+ }
+
+ let index = de.deserialize_u32(DescriptorVisitor)? as usize;
+ take_descriptor(index).map_err(D::Error::custom)
+}
+
+/// Allows the use of any serde deserializer within a closure while providing access to the a set of
+/// descriptors for use in `deserialize_descriptor`.
+///
+/// This is the corresponding call to use deserialize after using `SerializeDescriptors`.
+///
+/// If `deserialize_with_descriptors` is called anywhere within the given closure, it return an
+/// error.
+pub fn deserialize_with_descriptors<F, T, E>(
+ f: F,
+ descriptors: &mut Vec<Option<SafeDescriptor>>,
+) -> Result<T, E>
+where
+ F: FnOnce() -> Result<T, E>,
+ E: de::Error,
+{
+ let swap_descriptors = std::mem::take(descriptors);
+ set_descriptor_src(swap_descriptors).map_err(E::custom)?;
+
+ // catch_unwind is used to ensure that set_descriptor_src is always balanced with a call to
+ // take_descriptor_src afterwards.
+ let res = catch_unwind(AssertUnwindSafe(f));
+
+ // unwrap is used because set_descriptor_src is always called before this, so it should never
+ // panic.
+ *descriptors = take_descriptor_src().unwrap();
+
+ match res {
+ Ok(r) => r,
+ Err(e) => resume_unwind(e),
+ }
+}
+
+/// Module that exports `serialize`/`deserialize` functions for use with `#[serde(with = "...")]`
+/// attribute. It only works with fields with `RawDescriptor` type.
+///
+/// # Examples
+///
+/// ```
+/// use serde::{Deserialize, Serialize};
+/// use crate::platform::RawDescriptor;
+///
+/// #[derive(Serialize, Deserialize)]
+/// struct RawContainer {
+/// #[serde(with = "crate::platform::with_raw_descriptor")]
+/// rd: RawDescriptor,
+/// }
+/// ```
+pub mod with_raw_descriptor {
+ use super::super::RawDescriptor;
+ use crate::descriptor::IntoRawDescriptor;
+ use serde::Deserializer;
+
+ pub use super::serialize_descriptor as serialize;
+
+ pub fn deserialize<'de, D>(de: D) -> std::result::Result<RawDescriptor, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ super::deserialize_descriptor(de).map(IntoRawDescriptor::into_raw_descriptor)
+ }
+}
+
+/// Module that exports `serialize`/`deserialize` functions for use with `#[serde(with = "...")]`
+/// attribute.
+///
+/// # Examples
+///
+/// ```
+/// use std::fs::File;
+/// use serde::{Deserialize, Serialize};
+/// use crate::platform::RawDescriptor;
+///
+/// #[derive(Serialize, Deserialize)]
+/// struct FileContainer {
+/// #[serde(with = "crate::platform::with_as_descriptor")]
+/// file: File,
+/// }
+/// ```
+pub mod with_as_descriptor {
+ use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor};
+ use serde::{Deserializer, Serializer};
+
+ pub fn serialize<S: Serializer>(
+ rd: &dyn AsRawDescriptor,
+ se: S,
+ ) -> std::result::Result<S::Ok, S::Error> {
+ super::serialize_descriptor(&rd.as_raw_descriptor(), se)
+ }
+
+ pub fn deserialize<'de, D, T>(de: D) -> std::result::Result<T, D::Error>
+ where
+ D: Deserializer<'de>,
+ T: FromRawDescriptor,
+ {
+ super::deserialize_descriptor(de)
+ .map(IntoRawDescriptor::into_raw_descriptor)
+ .map(|rd| unsafe { T::from_raw_descriptor(rd) })
+ }
+}
+
+/// A simple wrapper around `File` that implements `Serialize`/`Deserialize`, which is useful when
+/// the `#[serde(with = "with_as_descriptor")]` trait is infeasible, such as for a field with type
+/// `Option<File>`.
+#[derive(Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct FileSerdeWrapper(#[serde(with = "with_as_descriptor")] pub File);
+
+impl fmt::Debug for FileSerdeWrapper {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl From<File> for FileSerdeWrapper {
+ fn from(file: File) -> Self {
+ FileSerdeWrapper(file)
+ }
+}
+
+impl From<FileSerdeWrapper> for File {
+ fn from(f: FileSerdeWrapper) -> File {
+ f.0
+ }
+}
+
+impl Deref for FileSerdeWrapper {
+ type Target = File;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl DerefMut for FileSerdeWrapper {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::super::{
+ deserialize_with_descriptors, with_as_descriptor, with_raw_descriptor, AsRawDescriptor,
+ FileSerdeWrapper, FromRawDescriptor, RawDescriptor, SafeDescriptor, SerializeDescriptors,
+ };
+
+ use std::{collections::HashMap, fs::File, mem::ManuallyDrop};
+
+ use serde::{de::DeserializeOwned, Deserialize, Serialize};
+ use tempfile::tempfile;
+
+ fn deserialize<T: DeserializeOwned>(json: &str, descriptors: &[RawDescriptor]) -> T {
+ let mut safe_descriptors = descriptors
+ .iter()
+ .map(|&v| Some(unsafe { SafeDescriptor::from_raw_descriptor(v) }))
+ .collect();
+
+ let res =
+ deserialize_with_descriptors(|| serde_json::from_str(json), &mut safe_descriptors)
+ .unwrap();
+
+ assert!(safe_descriptors.iter().all(|v| v.is_none()));
+
+ res
+ }
+
+ #[test]
+ fn raw() {
+ #[derive(Serialize, Deserialize, PartialEq, Debug)]
+ struct RawContainer {
+ #[serde(with = "with_raw_descriptor")]
+ rd: RawDescriptor,
+ }
+ // Specifically chosen to not overlap a real descriptor to avoid having to allocate any
+ // descriptors for this test.
+ let fake_rd = 5_123_457_i32;
+ let v = RawContainer {
+ rd: fake_rd as RawDescriptor,
+ };
+ let v_serialize = SerializeDescriptors::new(&v);
+ let json = serde_json::to_string(&v_serialize).unwrap();
+ let descriptors = v_serialize.into_descriptors();
+ let res = deserialize(&json, &descriptors);
+ assert_eq!(v, res);
+ }
+
+ #[test]
+ fn file() {
+ #[derive(Serialize, Deserialize)]
+ struct FileContainer {
+ #[serde(with = "with_as_descriptor")]
+ file: File,
+ }
+
+ let v = FileContainer {
+ file: tempfile().unwrap(),
+ };
+ let v_serialize = SerializeDescriptors::new(&v);
+ let json = serde_json::to_string(&v_serialize).unwrap();
+ let descriptors = v_serialize.into_descriptors();
+ let v = ManuallyDrop::new(v);
+ let res: FileContainer = deserialize(&json, &descriptors);
+ assert_eq!(v.file.as_raw_descriptor(), res.file.as_raw_descriptor());
+ }
+
+ #[test]
+ fn option() {
+ #[derive(Serialize, Deserialize)]
+ struct TestOption {
+ a: Option<FileSerdeWrapper>,
+ b: Option<FileSerdeWrapper>,
+ }
+
+ let v = TestOption {
+ a: None,
+ b: Some(tempfile().unwrap().into()),
+ };
+ let v_serialize = SerializeDescriptors::new(&v);
+ let json = serde_json::to_string(&v_serialize).unwrap();
+ let descriptors = v_serialize.into_descriptors();
+ let v = ManuallyDrop::new(v);
+ let res: TestOption = deserialize(&json, &descriptors);
+ assert!(res.a.is_none());
+ assert!(res.b.is_some());
+ assert_eq!(
+ v.b.as_ref().unwrap().as_raw_descriptor(),
+ res.b.unwrap().as_raw_descriptor()
+ );
+ }
+
+ #[test]
+ fn map() {
+ let mut v: HashMap<String, FileSerdeWrapper> = HashMap::new();
+ v.insert("a".into(), tempfile().unwrap().into());
+ v.insert("b".into(), tempfile().unwrap().into());
+ v.insert("c".into(), tempfile().unwrap().into());
+ let v_serialize = SerializeDescriptors::new(&v);
+ let json = serde_json::to_string(&v_serialize).unwrap();
+ let descriptors = v_serialize.into_descriptors();
+ // Prevent the files in `v` from dropping while allowing the HashMap itself to drop. It is
+ // done this way to prevent a double close of the files (which should reside in `res`)
+ // without triggering the leak sanitizer on `v`'s HashMap heap memory.
+ let v: HashMap<_, _> = v
+ .into_iter()
+ .map(|(k, v)| (k, ManuallyDrop::new(v)))
+ .collect();
+ let res: HashMap<String, FileSerdeWrapper> = deserialize(&json, &descriptors);
+
+ assert_eq!(v.len(), res.len());
+ for (k, v) in v.iter() {
+ assert_eq!(
+ res.get(k).unwrap().as_raw_descriptor(),
+ v.as_raw_descriptor()
+ );
+ }
+ }
+}
diff --git a/sys_util/src/errno.rs b/base/src/errno.rs
index 63af14a04..231f787ca 100644
--- a/sys_util/src/errno.rs
+++ b/base/src/errno.rs
@@ -2,29 +2,39 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fmt::{self, Display};
-use std::io;
-use std::result;
+use std::{
+ convert::{From, TryInto},
+ fmt::{
+ Display, {self},
+ },
+ io, result,
+};
use serde::{Deserialize, Serialize};
+use thiserror::Error;
-/// An error number, retrieved from errno (man 3 errno), set by a libc
+/// A system error
+/// In Unix systems, retrieved from errno (man 3 errno), set by a libc
/// function that returned an error.
-#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
+/// On Windows, retrieved from GetLastError, set by a Windows function
+/// that returned an error
+#[derive(Error, Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
#[serde(transparent)]
pub struct Error(i32);
pub type Result<T> = result::Result<T, Error>;
impl Error {
- /// Constructs a new error with the given errno.
- pub fn new(e: i32) -> Error {
- Error(e)
+ /// Constructs a new error with the given error number.
+ pub fn new<T: TryInto<i32>>(e: T) -> Error {
+ // A value outside the bounds of an i32 will never be a valid
+ // errno/GetLastError
+ Error(e.try_into().unwrap_or_default())
}
- /// Constructs an error from the current errno.
+ /// Constructs an Error from the most recent system error.
///
- /// The result of this only has any meaning just after a libc call that returned a value
- /// indicating errno was set.
+ /// The result of this only has any meaning just after a libc/Windows call that returned
+ /// a value indicating errno was set.
pub fn last() -> Error {
Error(io::Error::last_os_error().raw_os_error().unwrap())
}
@@ -37,7 +47,7 @@ impl Error {
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
- Error::new(e.raw_os_error().unwrap_or_default())
+ Error(e.raw_os_error().unwrap_or_default())
}
}
@@ -47,7 +57,11 @@ impl From<Error> for io::Error {
}
}
-impl std::error::Error for Error {}
+impl From<Error> for Box<dyn std::error::Error + Send> {
+ fn from(e: Error) -> Self {
+ Box::new(e)
+ }
+}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
diff --git a/base/src/event.rs b/base/src/event.rs
index 4f34914a8..f3f7ac73e 100644
--- a/base/src/event.rs
+++ b/base/src/event.rs
@@ -2,19 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::mem;
-use std::ops::Deref;
-use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
-use std::ptr;
-use std::time::Duration;
+use std::{
+ mem,
+ ops::Deref,
+ os::unix::io::{AsRawFd, FromRawFd, IntoRawFd},
+ ptr,
+ time::Duration,
+};
use serde::{Deserialize, Serialize};
-use crate::{AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor, RawDescriptor, Result};
-use sys_util::EventFd;
-pub use sys_util::EventReadResult;
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor};
+pub use crate::platform::EventReadResult;
+use crate::{generate_scoped_event, platform::EventFd, RawDescriptor, Result};
-/// See [EventFd](sys_util::EventFd) for struct- and method-level
+/// See [EventFd](crate::platform::EventFd) for struct- and method-level
/// documentation.
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
@@ -32,7 +34,7 @@ impl Event {
self.0.read()
}
- pub fn read_timeout(&mut self, timeout: Duration) -> Result<EventReadResult> {
+ pub fn read_timeout(&self, timeout: Duration) -> Result<EventReadResult> {
self.0.read_timeout(timeout)
}
@@ -59,40 +61,4 @@ impl IntoRawDescriptor for Event {
}
}
-/// See [ScopedEvent](sys_util::ScopedEvent) for struct- and method-level
-/// documentation.
-pub struct ScopedEvent(Event);
-
-impl ScopedEvent {
- pub fn new() -> Result<ScopedEvent> {
- Ok(Event::new()?.into())
- }
-}
-
-impl From<Event> for ScopedEvent {
- fn from(e: Event) -> Self {
- Self(e)
- }
-}
-
-impl From<ScopedEvent> for Event {
- fn from(scoped_event: ScopedEvent) -> Self {
- let evt = unsafe { ptr::read(&scoped_event.0) };
- mem::forget(scoped_event);
- evt
- }
-}
-
-impl Deref for ScopedEvent {
- type Target = Event;
-
- fn deref(&self) -> &Event {
- &self.0
- }
-}
-
-impl Drop for ScopedEvent {
- fn drop(&mut self) {
- self.write(1).expect("failed to trigger scoped event");
- }
-}
+generate_scoped_event!(Event);
diff --git a/sys_util/src/external_mapping.rs b/base/src/external_mapping.rs
index a9b2c66a9..22c98c753 100644
--- a/sys_util/src/external_mapping.rs
+++ b/base/src/external_mapping.rs
@@ -2,34 +2,26 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use crate::MappedRegion;
-use std::fmt::{self, Display};
+use remain::sorted;
+use thiserror::Error;
-#[derive(Debug, Eq, PartialEq)]
+#[sorted]
+#[derive(Error, Debug, Eq, PartialEq)]
pub enum Error {
- // A null address is typically bad. mmap allows it, but not external libraries
- NullAddress,
// For external mappings that have weird sizes
+ #[error("invalid size returned")]
InvalidSize,
// External library failed to map
+ #[error("library failed to map with {0}")]
LibraryError(i32),
+ // A null address is typically bad. mmap allows it, but not external libraries
+ #[error("null address returned")]
+ NullAddress,
// If external mapping is unsupported.
+ #[error("external mapping unsupported")]
Unsupported,
}
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- NullAddress => write!(f, "null address returned"),
- InvalidSize => write!(f, "invalid size returned"),
- LibraryError(ret) => write!(f, "library failed to map with {}", ret),
- Unsupported => write!(f, "external mapping unsupported"),
- }
- }
-}
-
pub type Result<T> = std::result::Result<T, Error>;
// Maps a external library resource given an id, returning address and size upon success
@@ -76,16 +68,14 @@ impl ExternalMapping {
unmap,
})
}
-}
-unsafe impl MappedRegion for ExternalMapping {
/// used for passing this region to ioctls for setting guest memory.
- fn as_ptr(&self) -> *mut u8 {
+ pub fn as_ptr(&self) -> *mut u8 {
self.ptr as *mut u8
}
/// Returns the size of the memory region in bytes.
- fn size(&self) -> usize {
+ pub fn size(&self) -> usize {
self.size
}
}
diff --git a/base/src/ioctl.rs b/base/src/ioctl.rs
index 307d5d938..995a6eaf7 100644
--- a/base/src/ioctl.rs
+++ b/base/src/ioctl.rs
@@ -2,20 +2,27 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use crate::{AsRawDescriptor, IoctlNr};
+use crate::descriptor::AsRawDescriptor;
+use crate::IoctlNr;
use std::os::raw::{c_int, c_ulong, c_void};
/// Run an ioctl with no arguments.
+/// # Safety
+/// The caller is responsible for determining the safety of the particular ioctl.
pub unsafe fn ioctl<F: AsRawDescriptor>(descriptor: &F, nr: IoctlNr) -> c_int {
libc::ioctl(descriptor.as_raw_descriptor(), nr, 0)
}
/// Run an ioctl with a single value argument.
+/// # Safety
+/// The caller is responsible for determining the safety of the particular ioctl.
pub unsafe fn ioctl_with_val(descriptor: &dyn AsRawDescriptor, nr: IoctlNr, arg: c_ulong) -> c_int {
libc::ioctl(descriptor.as_raw_descriptor(), nr, arg)
}
/// Run an ioctl with an immutable reference.
+/// # Safety
+/// The caller is responsible for determining the safety of the particular ioctl.
pub unsafe fn ioctl_with_ref<T>(descriptor: &dyn AsRawDescriptor, nr: IoctlNr, arg: &T) -> c_int {
libc::ioctl(
descriptor.as_raw_descriptor(),
@@ -25,6 +32,8 @@ pub unsafe fn ioctl_with_ref<T>(descriptor: &dyn AsRawDescriptor, nr: IoctlNr, a
}
/// Run an ioctl with a mutable reference.
+/// # Safety
+/// The caller is responsible for determining the safety of the particular ioctl.
pub unsafe fn ioctl_with_mut_ref<T>(
descriptor: &dyn AsRawDescriptor,
nr: IoctlNr,
@@ -38,6 +47,8 @@ pub unsafe fn ioctl_with_mut_ref<T>(
}
/// Run an ioctl with a raw pointer.
+/// # Safety
+/// The caller is responsible for determining the safety of the particular ioctl.
pub unsafe fn ioctl_with_ptr<T>(
descriptor: &dyn AsRawDescriptor,
nr: IoctlNr,
@@ -47,6 +58,8 @@ pub unsafe fn ioctl_with_ptr<T>(
}
/// Run an ioctl with a mutable raw pointer.
+/// # Safety
+/// The caller is responsible for determining the safety of the particular ioctl.
pub unsafe fn ioctl_with_mut_ptr<T>(
descriptor: &dyn AsRawDescriptor,
nr: IoctlNr,
diff --git a/base/src/lib.rs b/base/src/lib.rs
index b562d48d3..7de12306e 100644
--- a/base/src/lib.rs
+++ b/base/src/lib.rs
@@ -2,58 +2,62 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-pub use sys_util::*;
-
-mod async_types;
-mod event;
-mod ioctl;
-mod mmap;
-mod shm;
-mod timer;
+mod alloc;
+pub mod descriptor;
+pub mod descriptor_reflection;
+mod errno;
+pub mod external_mapping;
+pub mod scoped_event_macro;
mod tube;
-mod wait_context;
-pub use async_types::*;
-pub use event::{Event, EventReadResult, ScopedEvent};
-pub use ioctl::{
- ioctl, ioctl_with_mut_ptr, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref, ioctl_with_val,
-};
-pub use mmap::Unix as MemoryMappingUnix;
-pub use mmap::{MemoryMapping, MemoryMappingBuilder, MemoryMappingBuilderUnix};
-pub use shm::{SharedMemory, Unix as SharedMemoryUnix};
-pub use sys_util::ioctl::*;
-pub use sys_util::sched::*;
-pub use sys_util::{
- volatile_at_impl, volatile_impl, FileAllocate, FileGetLen, FileReadWriteAtVolatile,
- FileReadWriteVolatile, FileSetLen, FileSync,
-};
-pub use sys_util::{SeekHole, WriteZeroesAt};
-pub use timer::{FakeTimer, Timer};
-pub use tube::{AsyncTube, Error as TubeError, Result as TubeResult, Tube};
-pub use wait_context::{EventToken, EventType, TriggeredEvent, WaitContext};
-
-/// Wraps an AsRawDescriptor in the simple Descriptor struct, which
-/// has AsRawFd methods for interfacing with sys_util
-pub fn wrap_descriptor(descriptor: &dyn AsRawDescriptor) -> Descriptor {
- Descriptor(descriptor.as_raw_descriptor())
-}
+#[cfg(unix)]
+pub mod unix;
-/// Verifies that |raw_descriptor| is actually owned by this process and duplicates it
-/// to ensure that we have a unique handle to it.
-pub fn validate_raw_descriptor(raw_descriptor: RawDescriptor) -> Result<RawDescriptor> {
- validate_raw_fd(raw_descriptor)
-}
+#[cfg(windows)]
+pub mod windows;
-/// A trait similar to `AsRawDescriptor` but supports an arbitrary number of descriptors.
-pub trait AsRawDescriptors {
- fn as_raw_descriptors(&self) -> Vec<RawDescriptor>;
-}
+pub use alloc::LayoutAllocation;
+pub use errno::{errno_result, Error, Result};
+pub use external_mapping::{Error as ExternalMappingError, Result as ExternalMappingResult, *};
+pub use scoped_event_macro::*;
+pub use tube::{Error as TubeError, RecvTube, Result as TubeResult, SendTube, Tube};
-impl<T> AsRawDescriptors for T
-where
- T: AsRawDescriptor,
-{
- fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
- vec![self.as_raw_descriptor()]
- }
+cfg_if::cfg_if! {
+ if #[cfg(unix)] {
+ mod event;
+ mod ioctl;
+ mod mmap;
+ mod notifiers;
+ mod shm;
+ mod timer;
+ mod wait_context;
+
+ pub use unix as platform;
+
+ pub use unix::net::*;
+ pub use unix::ioctl::*;
+
+ pub use event::{Event, EventReadResult, ScopedEvent};
+ pub use crate::ioctl::{
+ ioctl, ioctl_with_mut_ptr, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref, ioctl_with_val,
+ };
+ pub use mmap::{
+ MemoryMapping, MemoryMappingBuilder, MemoryMappingBuilderUnix, Unix as MemoryMappingUnix,
+ };
+ pub use notifiers::*;
+ pub use shm::{SharedMemory, Unix as SharedMemoryUnix};
+ pub use timer::{FakeTimer, Timer};
+ pub use wait_context::{EventToken, EventType, TriggeredEvent, WaitContext};
+ } else if #[cfg(windows)] {
+ pub use windows as platform;
+ pub use tube::{set_duplicate_handle_tube, set_alias_pid, DuplicateHandleTube};
+ } else {
+ compile_error!("Unsupported platform");
+ }
}
+
+pub use crate::descriptor::{
+ AsRawDescriptor, AsRawDescriptors, Descriptor, FromRawDescriptor, IntoRawDescriptor,
+ SafeDescriptor,
+};
+pub use platform::*;
diff --git a/base/src/mmap.rs b/base/src/mmap.rs
index d2bd4cb33..8f9c0ce69 100644
--- a/base/src/mmap.rs
+++ b/base/src/mmap.rs
@@ -2,15 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use crate::{wrap_descriptor, AsRawDescriptor, MappedRegion, MmapError, Protection, SharedMemory};
-use data_model::volatile_memory::*;
-use data_model::DataInit;
+use crate::descriptor::AsRawDescriptor;
+use crate::{
+ platform::MemoryMapping as SysUtilMmap, MappedRegion, MemoryMappingArena, MmapError,
+ Protection, SharedMemory,
+};
+use data_model::{volatile_memory::*, DataInit};
use std::fs::File;
-use sys_util::MemoryMapping as SysUtilMmap;
pub type Result<T> = std::result::Result<T, MmapError>;
-/// See [MemoryMapping](sys_util::MemoryMapping) for struct- and method-level
+/// See [MemoryMapping](crate::platform::MemoryMapping) for struct- and method-level
/// documentation.
#[derive(Debug)]
pub struct MemoryMapping {
@@ -48,8 +50,7 @@ impl MemoryMapping {
src: &dyn AsRawDescriptor,
count: usize,
) -> Result<()> {
- self.mapping
- .read_to_memory(mem_offset, &wrap_descriptor(src), count)
+ self.mapping.read_to_memory(mem_offset, src, count)
}
pub fn write_from_memory(
@@ -58,8 +59,7 @@ impl MemoryMapping {
dst: &dyn AsRawDescriptor,
count: usize,
) -> Result<()> {
- self.mapping
- .write_from_memory(mem_offset, &wrap_descriptor(dst), count)
+ self.mapping.write_from_memory(mem_offset, dst, count)
}
}
@@ -74,6 +74,7 @@ impl Unix for MemoryMapping {
}
pub trait MemoryMappingBuilderUnix<'a> {
+ #[allow(clippy::wrong_self_convention)]
fn from_descriptor(self, descriptor: &'a dyn AsRawDescriptor) -> MemoryMappingBuilder;
}
@@ -89,6 +90,7 @@ impl<'a> MemoryMappingBuilderUnix<'a> for MemoryMappingBuilder<'a> {
/// Build the memory mapping given the specified descriptor to mapped memory
///
/// Default: Create a new memory mapping.
+ #[allow(clippy::wrong_self_convention)]
fn from_descriptor(mut self, descriptor: &'a dyn AsRawDescriptor) -> MemoryMappingBuilder {
self.descriptor = Some(descriptor);
self
@@ -114,7 +116,7 @@ impl<'a> MemoryMappingBuilder<'a> {
///
/// Note: this is a forward looking interface to accomodate platforms that
/// require special handling for file backed mappings.
- #[allow(unused_mut)]
+ #[allow(clippy::wrong_self_convention, unused_mut)]
pub fn from_file(mut self, file: &'a File) -> MemoryMappingBuilder {
self.descriptor = Some(file as &dyn AsRawDescriptor);
self
@@ -167,7 +169,7 @@ impl<'a> MemoryMappingBuilder<'a> {
}
Some(descriptor) => {
MemoryMappingBuilder::wrap(SysUtilMmap::from_fd_offset_protection_populate(
- &wrap_descriptor(descriptor),
+ descriptor,
self.size,
self.offset.unwrap_or(0),
self.protection.unwrap_or_else(Protection::read_write),
@@ -200,7 +202,7 @@ impl<'a> MemoryMappingBuilder<'a> {
Some(descriptor) => {
MemoryMappingBuilder::wrap(SysUtilMmap::from_fd_offset_protection_fixed(
addr,
- &wrap_descriptor(descriptor),
+ descriptor,
self.size,
self.offset.unwrap_or(0),
self.protection.unwrap_or_else(Protection::read_write),
@@ -230,3 +232,9 @@ unsafe impl MappedRegion for MemoryMapping {
self.mapping.size()
}
}
+
+impl From<MemoryMapping> for MemoryMappingArena {
+ fn from(mmap: MemoryMapping) -> Self {
+ MemoryMappingArena::from(mmap.mapping)
+ }
+}
diff --git a/base/src/notifiers.rs b/base/src/notifiers.rs
new file mode 100644
index 000000000..9988b6941
--- /dev/null
+++ b/base/src/notifiers.rs
@@ -0,0 +1,33 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#[cfg(unix)]
+use std::os::unix::net::UnixStream;
+
+use crate::descriptor::AsRawDescriptor;
+
+pub trait ReadNotifier {
+ /// Gets a descriptor that can be used in EventContext to wait for events to be available (e.g.
+ /// to avoid receive_events blocking).
+ fn get_read_notifier(&self) -> &dyn AsRawDescriptor;
+}
+
+pub trait CloseNotifier {
+ /// Gets a descriptor that can be used in EventContext to wait for the closed event.
+ fn get_close_notifier(&self) -> &dyn AsRawDescriptor;
+}
+
+#[cfg(unix)]
+impl ReadNotifier for UnixStream {
+ fn get_read_notifier(&self) -> &dyn AsRawDescriptor {
+ self
+ }
+}
+
+#[cfg(unix)]
+impl CloseNotifier for UnixStream {
+ fn get_close_notifier(&self) -> &dyn AsRawDescriptor {
+ self
+ }
+}
diff --git a/base/src/scoped_event_macro.rs b/base/src/scoped_event_macro.rs
new file mode 100644
index 000000000..f0f950ed1
--- /dev/null
+++ b/base/src/scoped_event_macro.rs
@@ -0,0 +1,52 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#[macro_export]
+macro_rules! generate_scoped_event {
+ ($event:ident) => {
+ /// An `Event` wrapper which triggers when it goes out of scope.
+ ///
+ /// If the underlying `Event` fails to trigger during drop, a panic is triggered instead.
+ pub struct ScopedEvent($event);
+
+ impl ScopedEvent {
+ /// Creates a new `ScopedEvent` which triggers when it goes out of scope.
+ pub fn new() -> Result<ScopedEvent> {
+ Ok($event::new()?.into())
+ }
+ }
+
+ impl From<$event> for ScopedEvent {
+ fn from(e: $event) -> Self {
+ Self(e)
+ }
+ }
+
+ impl From<ScopedEvent> for $event {
+ fn from(scoped_event: ScopedEvent) -> Self {
+ // Rust doesn't allow moving out of types with a Drop implementation, so we have to
+ // use something that copies instead of moves. This is safe because we prevent the
+ // drop of `scoped_event` using `mem::forget`, so the underlying `Event` will not
+ // experience a double-drop.
+ let evt = unsafe { ptr::read(&scoped_event.0) };
+ mem::forget(scoped_event);
+ evt
+ }
+ }
+
+ impl Deref for ScopedEvent {
+ type Target = $event;
+
+ fn deref(&self) -> &$event {
+ &self.0
+ }
+ }
+
+ impl Drop for ScopedEvent {
+ fn drop(&mut self) {
+ self.write(1).expect("failed to trigger scoped event");
+ }
+ }
+ };
+}
diff --git a/base/src/shm.rs b/base/src/shm.rs
index a445f3d62..33f0a5f08 100644
--- a/base/src/shm.rs
+++ b/base/src/shm.rs
@@ -2,18 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use crate::{
- AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor, MemfdSeals, RawDescriptor, Result,
- SafeDescriptor,
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor};
+use crate::{Error, MemfdSeals, RawDescriptor, Result};
+#[cfg(unix)]
+use std::os::unix::io::RawFd;
+use std::{
+ ffi::CStr,
+ fs::File,
+ os::unix::io::{AsRawFd, IntoRawFd},
};
-use std::ffi::CStr;
-use std::fs::File;
-use std::os::unix::io::{AsRawFd, IntoRawFd};
+use crate::platform::SharedMemory as SysUtilSharedMemory;
use serde::{Deserialize, Serialize};
-use sys_util::SharedMemory as SysUtilSharedMemory;
-/// See [SharedMemory](sys_util::SharedMemory) for struct- and method-level
+/// See [SharedMemory](crate::platform::SharedMemory) for struct- and method-level
/// documentation.
#[derive(Serialize, Deserialize)]
#[serde(transparent)]
@@ -38,13 +40,6 @@ impl SharedMemory {
pub fn size(&self) -> u64 {
self.0.size()
}
-
- /// Unwraps the sys_util::SharedMemory stored within this type.
- /// This should be used only when necessary for interacting with
- /// external libraries.
- pub fn inner(&self) -> &SysUtilSharedMemory {
- &self.0
- }
}
pub trait Unix {
@@ -87,9 +82,26 @@ impl IntoRawDescriptor for SharedMemory {
}
}
-impl Into<SafeDescriptor> for SharedMemory {
- fn into(self) -> SafeDescriptor {
+impl From<SharedMemory> for SafeDescriptor {
+ fn from(sm: SharedMemory) -> SafeDescriptor {
// Safe because we own the SharedMemory at this point.
- unsafe { SafeDescriptor::from_raw_descriptor(self.into_raw_descriptor()) }
+ unsafe { SafeDescriptor::from_raw_descriptor(sm.into_raw_descriptor()) }
+ }
+}
+
+impl audio_streams::shm_streams::SharedMemory for SharedMemory {
+ type Error = Error;
+
+ fn anon(size: u64) -> Result<Self> {
+ SharedMemory::anon(size)
+ }
+
+ fn size(&self) -> u64 {
+ self.size()
+ }
+
+ #[cfg(unix)]
+ fn as_raw_fd(&self) -> RawFd {
+ self.as_raw_descriptor()
}
}
diff --git a/base/src/timer.rs b/base/src/timer.rs
index d0e6d2639..0912120f7 100644
--- a/base/src/timer.rs
+++ b/base/src/timer.rs
@@ -2,17 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use crate::{
- AsRawDescriptor, FakeClock, FromRawDescriptor, IntoRawDescriptor, RawDescriptor, Result,
-};
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor};
+use crate::{FakeClock, RawDescriptor, Result};
-use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd};
-use std::sync::Arc;
-use std::time::Duration;
+use crate::platform::{FakeTimerFd, TimerFd};
+use std::{
+ os::unix::io::{AsRawFd, FromRawFd, IntoRawFd},
+ sync::Arc,
+ time::Duration,
+};
use sync::Mutex;
-use sys_util::{FakeTimerFd, TimerFd};
-/// See [TimerFd](sys_util::TimerFd) for struct- and method-level
+/// See [TimerFd](crate::platform::TimerFd) for struct- and method-level
/// documentation.
pub struct Timer(pub TimerFd);
impl Timer {
@@ -21,7 +22,7 @@ impl Timer {
}
}
-/// See [FakeTimerFd](sys_util::FakeTimerFd) for struct- and method-level
+/// See [FakeTimerFd](crate::platform::FakeTimerFd) for struct- and method-level
/// documentation.
pub struct FakeTimer(FakeTimerFd);
impl FakeTimer {
@@ -41,10 +42,6 @@ macro_rules! build_timer {
self.0.wait().map(|_| ())
}
- pub fn is_armed(&self) -> Result<bool> {
- self.0.is_armed()
- }
-
pub fn clear(&mut self) -> Result<()> {
self.0.clear()
}
diff --git a/base/src/tube.rs b/base/src/tube.rs
index 21917c931..ffcd3bdd9 100644
--- a/base/src/tube.rs
+++ b/base/src/tube.rs
@@ -2,179 +2,148 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::io::{self, IoSlice};
-use std::marker::PhantomData;
-use std::ops::Deref;
-use std::os::unix::prelude::{AsRawFd, RawFd};
-use std::time::Duration;
-
-use crate::{net::UnixSeqpacket, FromRawDescriptor, SafeDescriptor, ScmSocket, UnsyncMarker};
+use remain::sorted;
+use std::io;
-use cros_async::{Executor, IntoAsync, IoSourceExt};
-use serde::{de::DeserializeOwned, Serialize};
-use sys_util::{
- deserialize_with_descriptors, AsRawDescriptor, RawDescriptor, SerializeDescriptors,
-};
use thiserror::Error as ThisError;
-#[derive(ThisError, Debug)]
-pub enum Error {
- #[error("failed to serialize/deserialize json from packet: {0}")]
- Json(serde_json::Error),
- #[error("failed to send packet: {0}")]
- Send(sys_util::Error),
- #[error("failed to receive packet: {0}")]
- Recv(io::Error),
- #[error("tube was disconnected")]
- Disconnected,
- #[error("failed to crate tube pair: {0}")]
- Pair(io::Error),
- #[error("failed to set send timeout: {0}")]
- SetSendTimeout(io::Error),
- #[error("failed to set recv timeout: {0}")]
- SetRecvTimeout(io::Error),
- #[error("failed to create async tube: {0}")]
- CreateAsync(cros_async::AsyncError),
-}
-
-pub type Result<T> = std::result::Result<T, Error>;
-
-/// Bidirectional tube that support both send and recv.
-pub struct Tube {
- socket: UnixSeqpacket,
- _unsync_marker: UnsyncMarker,
-}
+#[cfg_attr(windows, path = "windows/tube.rs")]
+#[cfg_attr(not(windows), path = "unix/tube.rs")]
+mod tube;
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
+use std::time::Duration;
+pub use tube::*;
impl Tube {
- /// Create a pair of connected tubes. Request is send in one direction while response is in the
- /// other direction.
- pub fn pair() -> Result<(Tube, Tube)> {
- let (socket1, socket2) = UnixSeqpacket::pair().map_err(Error::Pair)?;
- let tube1 = Tube::new(socket1);
- let tube2 = Tube::new(socket2);
- Ok((tube1, tube2))
- }
-
- // Create a new `Tube`.
- pub fn new(socket: UnixSeqpacket) -> Tube {
- Tube {
- socket,
- _unsync_marker: PhantomData,
- }
- }
-
- pub fn into_async_tube(self, ex: &Executor) -> Result<AsyncTube> {
- let inner = ex.async_from(self).map_err(Error::CreateAsync)?;
- Ok(AsyncTube { inner })
- }
-
- pub fn send<T: Serialize>(&self, msg: &T) -> Result<()> {
- let msg_serialize = SerializeDescriptors::new(&msg);
- let msg_json = serde_json::to_vec(&msg_serialize).map_err(Error::Json)?;
- let msg_descriptors = msg_serialize.into_descriptors();
-
- self.socket
- .send_with_fds(&[IoSlice::new(&msg_json)], &msg_descriptors)
- .map_err(Error::Send)?;
- Ok(())
+ /// Creates a Send/Recv pair of Tubes.
+ pub fn directional_pair() -> Result<(SendTube, RecvTube)> {
+ let (t1, t2) = Self::pair()?;
+ Ok((SendTube(t1), RecvTube(t2)))
}
+}
- pub fn recv<T: DeserializeOwned>(&self) -> Result<T> {
- let (msg_json, msg_descriptors) =
- self.socket.recv_as_vec_with_fds().map_err(Error::Recv)?;
-
- if msg_json.is_empty() {
- return Err(Error::Disconnected);
- }
-
- let mut msg_descriptors_safe = msg_descriptors
- .into_iter()
- .map(|v| {
- Some(unsafe {
- // Safe because the socket returns new fds that are owned locally by this scope.
- SafeDescriptor::from_raw_descriptor(v)
- })
- })
- .collect();
+#[derive(Serialize, Deserialize)]
+#[serde(transparent)]
+/// A Tube end which can only send messages. Cloneable.
+pub struct SendTube(Tube);
- deserialize_with_descriptors(
- || serde_json::from_slice(&msg_json),
- &mut msg_descriptors_safe,
- )
- .map_err(Error::Json)
+#[allow(dead_code)]
+impl SendTube {
+ /// TODO(b/145998747, b/184398671): this method should be removed.
+ pub fn set_send_timeout(&self, _timeout: Option<Duration>) -> Result<()> {
+ unimplemented!("To be removed/refactored upstream.");
}
- /// Returns true if there is a packet ready to `recv` without blocking.
- ///
- /// If there is an error trying to determine if there is a packet ready, this returns false.
- pub fn is_packet_ready(&self) -> bool {
- self.socket.get_readable_bytes().unwrap_or(0) > 0
+ pub fn send<T: Serialize>(&self, msg: &T) -> Result<()> {
+ self.0.send(msg)
}
- pub fn set_send_timeout(&self, timeout: Option<Duration>) -> Result<()> {
- self.socket
- .set_write_timeout(timeout)
- .map_err(Error::SetSendTimeout)
+ pub fn try_clone(&self) -> Result<Self> {
+ Ok(SendTube(
+ #[allow(deprecated)]
+ self.0.try_clone()?,
+ ))
}
- pub fn set_recv_timeout(&self, timeout: Option<Duration>) -> Result<()> {
- self.socket
- .set_read_timeout(timeout)
- .map_err(Error::SetRecvTimeout)
+ /// Never call this function, it is for use by cros_async to provide
+ /// directional wrapper types only. Using it in any other context may
+ /// violate concurrency assumptions. (Type splitting across crates has put
+ /// us in a situation where we can't use Rust privacy to enforce this.)
+ #[deprecated]
+ pub fn into_tube(self) -> Tube {
+ self.0
}
}
-impl AsRawDescriptor for Tube {
- fn as_raw_descriptor(&self) -> RawDescriptor {
- self.socket.as_raw_descriptor()
- }
-}
+#[derive(Serialize, Deserialize)]
+#[serde(transparent)]
+/// A Tube end which can only recv messages.
+pub struct RecvTube(Tube);
-impl AsRawFd for Tube {
- fn as_raw_fd(&self) -> RawFd {
- self.socket.as_raw_fd()
+#[allow(dead_code)]
+impl RecvTube {
+ pub fn recv<T: DeserializeOwned>(&self) -> Result<T> {
+ self.0.recv()
}
-}
-
-impl IntoAsync for Tube {}
-pub struct AsyncTube {
- inner: Box<dyn IoSourceExt<Tube>>,
-}
-
-impl AsyncTube {
- pub async fn next<T: DeserializeOwned>(&self) -> Result<T> {
- self.inner.wait_readable().await.unwrap();
- self.inner.as_source().recv()
+ /// TODO(b/145998747, b/184398671): this method should be removed.
+ pub fn set_recv_timeout(&self, _timeout: Option<Duration>) -> Result<()> {
+ unimplemented!("To be removed/refactored upstream.");
}
-}
-impl Deref for AsyncTube {
- type Target = Tube;
-
- fn deref(&self) -> &Self::Target {
- self.inner.as_source()
+ /// Never call this function, it is for use by cros_async to provide
+ /// directional wrapper types only. Using it in any other context may
+ /// violate concurrency assumptions. (Type splitting across crates has put
+ /// us in a situation where we can't use Rust privacy to enforce this.)
+ #[deprecated]
+ pub fn into_tube(self) -> Tube {
+ self.0
}
}
-impl Into<Tube> for AsyncTube {
- fn into(self) -> Tube {
- self.inner.into_source()
- }
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum Error {
+ #[cfg(windows)]
+ #[error("attempt to duplicate descriptor via broker failed")]
+ BrokerDupDescriptor,
+ #[error("failed to clone transport: {0}")]
+ Clone(io::Error),
+ #[error("tube was disconnected")]
+ Disconnected,
+ #[error("failed to duplicate descriptor: {0}")]
+ DupDescriptor(io::Error),
+ #[cfg(windows)]
+ #[error("failed to flush named pipe: {0}")]
+ Flush(io::Error),
+ #[error("failed to serialize/deserialize json from packet: {0}")]
+ Json(serde_json::Error),
+ #[error("cancelled a queued async operation")]
+ OperationCancelled,
+ #[error("failed to crate tube pair: {0}")]
+ Pair(io::Error),
+ #[error("failed to receive packet: {0}")]
+ Recv(io::Error),
+ #[error("Received a message with a zero sized body. This should not happen.")]
+ RecvUnexpectedEmptyBody,
+ #[error("failed to send packet: {0}")]
+ Send(crate::platform::Error),
+ #[error("failed to send packet: {0}")]
+ SendIo(io::Error),
+ #[error("failed to write packet to intermediate buffer: {0}")]
+ SendIoBuf(io::Error),
+ #[error("failed to set recv timeout: {0}")]
+ SetRecvTimeout(io::Error),
+ #[error("failed to set send timeout: {0}")]
+ SetSendTimeout(io::Error),
}
+pub type Result<T> = std::result::Result<T, Error>;
+
#[cfg(test)]
mod tests {
use super::*;
use crate::Event;
- use std::collections::HashMap;
- use std::time::Duration;
+ use std::{collections::HashMap, time::Duration};
use serde::{Deserialize, Serialize};
+ use std::{
+ sync::{Arc, Barrier},
+ thread,
+ };
+
+ #[derive(Serialize, Deserialize)]
+ struct DataStruct {
+ x: u32,
+ }
+
+ // Magics to identify which producer sent a message (& detect corruption).
+ const PRODUCER_ID_1: u32 = 801279273;
+ const PRODUCER_ID_2: u32 = 345234861;
#[track_caller]
- fn test_event_pair(send: Event, mut recv: Event) {
+ fn test_event_pair(send: Event, recv: Event) {
send.write(1).unwrap();
recv.read_timeout(Duration::from_secs(1)).unwrap();
}
@@ -212,6 +181,64 @@ mod tests {
test_event_pair(test_msg.b, recv_msg.b);
}
+ /// Send messages to a Tube with the given identifier (see `consume_messages`; we use this to
+ /// track different message producers).
+ #[track_caller]
+ fn produce_messages(tube: SendTube, data: u32, barrier: Arc<Barrier>) -> SendTube {
+ let data = DataStruct { x: data };
+ barrier.wait();
+ for _ in 0..100 {
+ tube.send(&data).unwrap();
+ }
+ tube
+ }
+
+ /// Consumes the given number of messages from a Tube, returning the number messages read with
+ /// each producer ID.
+ #[track_caller]
+ fn consume_messages(
+ tube: RecvTube,
+ count: usize,
+ barrier: Arc<Barrier>,
+ ) -> (RecvTube, usize, usize) {
+ barrier.wait();
+
+ let mut id1_count = 0usize;
+ let mut id2_count = 0usize;
+
+ for _ in 0..count {
+ let msg = tube.recv::<DataStruct>().unwrap();
+ match msg.x {
+ PRODUCER_ID_1 => id1_count += 1,
+ PRODUCER_ID_2 => id2_count += 1,
+ _ => panic!(
+ "want message with ID {} or {}; got message w/ ID {}.",
+ PRODUCER_ID_1, PRODUCER_ID_2, msg.x
+ ),
+ }
+ }
+ (tube, id1_count, id2_count)
+ }
+
+ #[test]
+ fn send_recv_mpsc() {
+ let (p1, consumer) = Tube::directional_pair().unwrap();
+ let p2 = p1.try_clone().unwrap();
+ let start_block_p1 = Arc::new(Barrier::new(3));
+ let start_block_p2 = start_block_p1.clone();
+ let start_block_consumer = start_block_p1.clone();
+
+ let p1_thread = thread::spawn(move || produce_messages(p1, PRODUCER_ID_1, start_block_p1));
+ let p2_thread = thread::spawn(move || produce_messages(p2, PRODUCER_ID_2, start_block_p2));
+
+ let (_tube, id1_count, id2_count) = consume_messages(consumer, 200, start_block_consumer);
+ assert_eq!(id1_count, 100);
+ assert_eq!(id2_count, 100);
+
+ p1_thread.join().unwrap();
+ p2_thread.join().unwrap();
+ }
+
#[test]
fn send_recv_hash_map() {
let (s1, s2) = Tube::pair().unwrap();
diff --git a/base/src/unix/acpi_event.rs b/base/src/unix/acpi_event.rs
new file mode 100644
index 000000000..39f772c80
--- /dev/null
+++ b/base/src/unix/acpi_event.rs
@@ -0,0 +1,109 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::str;
+
+use super::netlink::*;
+use thiserror::Error;
+
+use data_model::DataInit;
+
+const ACPI_EVENT_SIZE: usize = std::mem::size_of::<AcpiGenlEvent>();
+const GENL_HDRLEN: usize = std::mem::size_of::<GenlMsgHdr>();
+const NLA_HDRLEN: usize = std::mem::size_of::<NlAttr>();
+
+#[derive(Error, Debug)]
+pub enum AcpiEventError {
+ #[error("GenmsghdrCmd or NlAttrType inappropriate for acpi event")]
+ TypeAttrMissmatch,
+ #[error("Something goes wrong: msg_len {0} is not correct")]
+ InvalidMsgLen(usize),
+}
+type Result<T> = std::result::Result<T, AcpiEventError>;
+
+/// attributes of AcpiGenlFamily
+#[allow(dead_code)]
+enum NlAttrType {
+ AcpiGenlAttrUnspec,
+ AcpiGenlAttrEvent, // acpi_event (needed by user space)
+ AcpiGenlAttrMax,
+}
+
+/// commands supported by the AcpiGenlFamily
+#[allow(dead_code)]
+enum GenmsghdrCmd {
+ AcpiGenlCmdUnspec,
+ AcpiGenlCmdEvent, // kernel->user notifications for acpi_events
+ AcpiGenlCmdMax,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+struct AcpiGenlEvent {
+ device_class: [::std::os::raw::c_char; 20usize],
+ bus_id: [::std::os::raw::c_char; 15usize],
+ _type: u32,
+ data: u32,
+}
+unsafe impl DataInit for AcpiGenlEvent {}
+
+pub struct AcpiNotifyEvent {
+ pub device_class: String,
+ pub bus_id: String,
+ pub _type: u32,
+ pub data: u32,
+}
+
+impl AcpiNotifyEvent {
+ /// Create acpi event by decapsulating it from NetlinkMessage.
+ pub fn new(netlink_message: NetlinkMessage) -> Result<Self> {
+ let msg_len = netlink_message.data.len();
+ if msg_len != GENL_HDRLEN + NLA_HDRLEN + ACPI_EVENT_SIZE {
+ return Err(AcpiEventError::InvalidMsgLen(msg_len));
+ }
+
+ let genl_hdr = GenlMsgHdr::from_slice(&netlink_message.data[..GENL_HDRLEN])
+ .expect("unable to get GenlMsgHdr from slice");
+
+ let nlattr_end = GENL_HDRLEN + NLA_HDRLEN;
+ let nl_attr = NlAttr::from_slice(&netlink_message.data[GENL_HDRLEN..nlattr_end])
+ .expect("unable to get NlAttr from slice");
+
+ // Sanity check that the headers have correct for acpi event `cmd` and `_type`
+ if genl_hdr.cmd != GenmsghdrCmd::AcpiGenlCmdEvent as u8
+ || nl_attr._type != NlAttrType::AcpiGenlAttrEvent as u16
+ {
+ return Err(AcpiEventError::TypeAttrMissmatch);
+ }
+
+ let acpi_event = AcpiGenlEvent::from_slice(&netlink_message.data[nlattr_end..msg_len])
+ .expect("unable to get AcpiGenlEvent from slice");
+
+ // The raw::c_char is either i8 or u8 which is known portability issue:
+ // https://github.com/rust-lang/rust/issues/79089,
+ // before using device_class further cast it to u8.
+ let device_class: &[u8; 20usize] =
+ unsafe { ::std::mem::transmute(&acpi_event.device_class) };
+ let bus_id: &[u8; 15usize] = unsafe { ::std::mem::transmute(&acpi_event.bus_id) };
+
+ Ok(AcpiNotifyEvent {
+ device_class: strip_padding(device_class).to_owned(),
+ bus_id: strip_padding(bus_id).to_owned(),
+ _type: acpi_event._type,
+ data: acpi_event.data,
+ })
+ }
+}
+
+// Like `CStr::from_bytes_with_nul` but strips any bytes starting from first '\0'-byte and
+// returns &str. Panics if `b` doesn't contain any '\0' bytes.
+fn strip_padding(b: &[u8]) -> &str {
+ // It would be nice if we could use memchr here but that's locked behind an unstable gate.
+ let pos = b
+ .iter()
+ .position(|&c| c == 0)
+ .expect("`b` doesn't contain any nul bytes");
+
+ str::from_utf8(&b[..pos]).unwrap()
+}
diff --git a/sys_util/src/android/mod.rs b/base/src/unix/android/mod.rs
index bf3fe8885..bf3fe8885 100644
--- a/sys_util/src/android/mod.rs
+++ b/base/src/unix/android/mod.rs
diff --git a/sys_util/src/android/syslog.rs b/base/src/unix/android/syslog.rs
index f3875062d..fae3f279e 100644
--- a/sys_util/src/android/syslog.rs
+++ b/base/src/unix/android/syslog.rs
@@ -6,15 +6,17 @@
extern crate android_log_sys;
-use crate::syslog::{Error, Facility, Priority, Syslog};
+use super::super::syslog::{Error, Facility, Priority, Syslog};
use android_log_sys::{
__android_log_is_loggable, __android_log_message, __android_log_write_log_message, log_id_t,
LogPriority,
};
-use std::ffi::{CString, NulError};
-use std::fmt;
-use std::mem::size_of;
-use std::os::unix::io::RawFd;
+use std::{
+ ffi::{CString, NulError},
+ fmt,
+ mem::size_of,
+ os::unix::io::RawFd,
+};
pub struct PlatformSyslog {
enabled: bool,
diff --git a/sys_util/src/capabilities.rs b/base/src/unix/capabilities.rs
index 90b878471..5d3acd45a 100644
--- a/sys_util/src/capabilities.rs
+++ b/base/src/unix/capabilities.rs
@@ -4,7 +4,7 @@
use libc::{c_int, c_void};
-use crate::{errno_result, Result};
+use super::{errno_result, Result};
#[allow(non_camel_case_types)]
type cap_t = *mut c_void;
diff --git a/sys_util/src/clock.rs b/base/src/unix/clock.rs
index d547fc821..5bf0d3004 100644
--- a/sys_util/src/clock.rs
+++ b/base/src/unix/clock.rs
@@ -5,10 +5,12 @@
// Utility file to provide a fake clock object representing current time, and a timerfd driven by
// that time.
-use std::os::unix::io::AsRawFd;
-use std::time::{Duration, Instant};
+use std::{
+ os::unix::io::AsRawFd,
+ time::{Duration, Instant},
+};
-use crate::EventFd;
+use super::EventFd;
#[derive(Debug, Copy, Clone)]
pub struct Clock(Instant);
diff --git a/base/src/unix/descriptor.rs b/base/src/unix/descriptor.rs
new file mode 100644
index 000000000..6fa6fb06d
--- /dev/null
+++ b/base/src/unix/descriptor.rs
@@ -0,0 +1,237 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ convert::TryFrom,
+ fs::File,
+ io::{Stderr, Stdin, Stdout},
+ net::UdpSocket,
+ ops::Drop,
+ os::unix::{
+ io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
+ net::{UnixDatagram, UnixListener, UnixStream},
+ },
+};
+
+use super::{
+ errno_result,
+ net::{UnixSeqpacket, UnlinkUnixSeqpacketListener},
+ Result,
+};
+use crate::descriptor::{
+ AsRawDescriptor, Descriptor, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor,
+};
+
+pub type RawDescriptor = RawFd;
+
+pub const INVALID_DESCRIPTOR: RawDescriptor = -1;
+
+/// Clones `descriptor`, returning a new `RawDescriptor` that refers to the same open file
+/// description as `descriptor`. The cloned descriptor will have the `FD_CLOEXEC` flag set but will
+/// not share any other file descriptor flags with `descriptor`.
+pub fn clone_descriptor(descriptor: &dyn AsRawDescriptor) -> Result<RawDescriptor> {
+ clone_fd(&descriptor.as_raw_descriptor())
+}
+
+/// Clones `fd`, returning a new file descriptor that refers to the same open file description as
+/// `fd`. The cloned fd will have the `FD_CLOEXEC` flag set but will not share any other file
+/// descriptor flags with `fd`.
+fn clone_fd(fd: &dyn AsRawFd) -> Result<RawFd> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0) };
+ if ret < 0 {
+ errno_result()
+ } else {
+ Ok(ret)
+ }
+}
+
+/// Clears CLOEXEC flag on descriptor
+pub fn clear_descriptor_cloexec<A: AsRawDescriptor>(fd_owner: &A) -> Result<()> {
+ clear_fd_cloexec(&fd_owner.as_raw_descriptor())
+}
+
+/// Clears CLOEXEC flag on fd
+fn clear_fd_cloexec<A: AsRawFd>(fd_owner: &A) -> Result<()> {
+ let fd = fd_owner.as_raw_fd();
+ // Safe because fd is read only.
+ let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
+ if flags == -1 {
+ return errno_result();
+ }
+
+ let masked_flags = flags & !libc::FD_CLOEXEC;
+ // Safe because this has no side effect(s) on the current process.
+ if masked_flags != flags && unsafe { libc::fcntl(fd, libc::F_SETFD, masked_flags) } == -1 {
+ errno_result()
+ } else {
+ Ok(())
+ }
+}
+
+const KCMP_FILE: u32 = 0;
+
+impl PartialEq for SafeDescriptor {
+ fn eq(&self, other: &Self) -> bool {
+ // If RawFd numbers match then we can return early without calling kcmp
+ if self.descriptor == other.descriptor {
+ return true;
+ }
+
+ // safe because we only use the return value and libc says it's always successful
+ let pid = unsafe { libc::getpid() };
+ // safe because we are passing everything by value and checking the return value
+ let ret = unsafe {
+ libc::syscall(
+ libc::SYS_kcmp,
+ pid,
+ pid,
+ KCMP_FILE,
+ self.descriptor,
+ other.descriptor,
+ )
+ };
+
+ ret == 0
+ }
+}
+
+impl Drop for SafeDescriptor {
+ fn drop(&mut self) {
+ let _ = unsafe { libc::close(self.descriptor) };
+ }
+}
+
+impl AsRawFd for SafeDescriptor {
+ fn as_raw_fd(&self) -> RawFd {
+ self.as_raw_descriptor()
+ }
+}
+
+impl TryFrom<&dyn AsRawFd> for SafeDescriptor {
+ type Error = std::io::Error;
+
+ fn try_from(fd: &dyn AsRawFd) -> std::result::Result<Self, Self::Error> {
+ Ok(SafeDescriptor {
+ descriptor: clone_fd(fd)?,
+ })
+ }
+}
+
+impl SafeDescriptor {
+ /// Clones this descriptor, internally creating a new descriptor. The new SafeDescriptor will
+ /// share the same underlying count within the kernel.
+ pub fn try_clone(&self) -> Result<SafeDescriptor> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let descriptor = unsafe { libc::fcntl(self.descriptor, libc::F_DUPFD_CLOEXEC, 0) };
+ if descriptor < 0 {
+ errno_result()
+ } else {
+ Ok(SafeDescriptor { descriptor })
+ }
+ }
+}
+
+impl From<SafeDescriptor> for File {
+ fn from(s: SafeDescriptor) -> File {
+ // Safe because we own the SafeDescriptor at this point.
+ unsafe { File::from_raw_fd(s.into_raw_descriptor()) }
+ }
+}
+
+impl From<SafeDescriptor> for UnixStream {
+ fn from(s: SafeDescriptor) -> Self {
+ // Safe because we own the SafeDescriptor at this point.
+ unsafe { Self::from_raw_fd(s.into_raw_descriptor()) }
+ }
+}
+
+impl From<UnixSeqpacket> for SafeDescriptor {
+ fn from(s: UnixSeqpacket) -> Self {
+ // Safe because we own the UnixSeqpacket at this point.
+ unsafe { SafeDescriptor::from_raw_descriptor(s.into_raw_descriptor()) }
+ }
+}
+
+// AsRawFd for interoperability with interfaces that require it. Within crosvm,
+// always use AsRawDescriptor when possible.
+impl AsRawFd for Descriptor {
+ fn as_raw_fd(&self) -> RawFd {
+ self.0
+ }
+}
+
+macro_rules! AsRawDescriptor {
+ ($name:ident) => {
+ impl AsRawDescriptor for $name {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.as_raw_fd()
+ }
+ }
+ };
+}
+
+macro_rules! FromRawDescriptor {
+ ($name:ident) => {
+ impl FromRawDescriptor for $name {
+ unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Self {
+ $name::from_raw_fd(descriptor)
+ }
+ }
+ };
+}
+
+macro_rules! IntoRawDescriptor {
+ ($name:ident) => {
+ impl IntoRawDescriptor for $name {
+ fn into_raw_descriptor(self) -> RawDescriptor {
+ self.into_raw_fd()
+ }
+ }
+ };
+}
+
+// Implementations for File. This enables the File-type to use
+// RawDescriptor, but does not mean File should be used as a generic
+// descriptor container. That should go to either SafeDescriptor or another more
+// relevant container type.
+AsRawDescriptor!(File);
+AsRawDescriptor!(UnlinkUnixSeqpacketListener);
+AsRawDescriptor!(UdpSocket);
+AsRawDescriptor!(UnixDatagram);
+AsRawDescriptor!(UnixListener);
+AsRawDescriptor!(UnixStream);
+FromRawDescriptor!(File);
+FromRawDescriptor!(UnixStream);
+FromRawDescriptor!(UnixDatagram);
+IntoRawDescriptor!(File);
+IntoRawDescriptor!(UnixDatagram);
+AsRawDescriptor!(Stdin);
+AsRawDescriptor!(Stdout);
+AsRawDescriptor!(Stderr);
+
+#[test]
+#[allow(clippy::eq_op)]
+fn clone_equality() {
+ let ret = unsafe { libc::eventfd(0, 0) };
+ if ret < 0 {
+ panic!("failed to create eventfd");
+ }
+ let descriptor = unsafe { SafeDescriptor::from_raw_descriptor(ret) };
+
+ assert_eq!(descriptor, descriptor);
+
+ assert_eq!(
+ descriptor,
+ descriptor.try_clone().expect("failed to clone eventfd")
+ );
+
+ let ret = unsafe { libc::eventfd(0, 0) };
+ if ret < 0 {
+ panic!("failed to create eventfd");
+ }
+ let another = unsafe { SafeDescriptor::from_raw_descriptor(ret) };
+
+ assert_ne!(descriptor, another);
+}
diff --git a/sys_util/src/eventfd.rs b/base/src/unix/eventfd.rs
index 29fa34469..84080acd6 100644
--- a/sys_util/src/eventfd.rs
+++ b/base/src/unix/eventfd.rs
@@ -2,19 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::mem;
-use std::ops::Deref;
-use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
-use std::ptr;
-use std::time::Duration;
+use std::{
+ mem,
+ ops::Deref,
+ os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
+ ptr,
+ time::Duration,
+};
use libc::{c_void, eventfd, read, write, POLLIN};
use serde::{Deserialize, Serialize};
-use crate::{
- duration_to_timespec, errno_result, AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor,
- RawDescriptor, Result, SafeDescriptor,
-};
+use super::{duration_to_timespec, errno_result, RawDescriptor, Result};
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor};
+use crate::generate_scoped_event;
/// A safe wrapper around a Linux eventfd (man 2 eventfd).
///
@@ -90,7 +91,7 @@ impl EventFd {
/// a timeout does not occur then the count is returned as a EventReadResult::Count(count),
/// and the count is reset to 0. If a timeout does occur then this function will return
/// EventReadResult::Timeout.
- pub fn read_timeout(&mut self, timeout: Duration) -> Result<EventReadResult> {
+ pub fn read_timeout(&self, timeout: Duration) -> Result<EventReadResult> {
let mut pfd = libc::pollfd {
fd: self.as_raw_descriptor(),
events: POLLIN,
@@ -166,49 +167,13 @@ impl IntoRawFd for EventFd {
}
}
-/// An `EventFd` wrapper which triggers when it goes out of scope.
-///
-/// If the underlying `EventFd` fails to trigger during drop, a panic is triggered instead.
-pub struct ScopedEvent(EventFd);
-
-impl ScopedEvent {
- /// Creates a new `ScopedEvent` which triggers when it goes out of scope.
- pub fn new() -> Result<ScopedEvent> {
- Ok(EventFd::new()?.into())
- }
-}
-
-impl From<EventFd> for ScopedEvent {
- fn from(e: EventFd) -> Self {
- Self(e)
- }
-}
-
-impl From<ScopedEvent> for EventFd {
- fn from(scoped_event: ScopedEvent) -> Self {
- // Rust doesn't allow moving out of types with a Drop implementation, so we have to use
- // something that copies instead of moves. This is safe because we prevent the drop of
- // `scoped_event` using `mem::forget`, so the underlying `EventFd` will not experience a
- // double-drop.
- let evt = unsafe { ptr::read(&scoped_event.0) };
- mem::forget(scoped_event);
- evt
- }
-}
-
-impl Deref for ScopedEvent {
- type Target = EventFd;
-
- fn deref(&self) -> &EventFd {
- &self.0
+impl From<EventFd> for SafeDescriptor {
+ fn from(evt: EventFd) -> Self {
+ evt.event_handle
}
}
-impl Drop for ScopedEvent {
- fn drop(&mut self) {
- self.write(1).expect("failed to trigger scoped event");
- }
-}
+generate_scoped_event!(EventFd);
#[cfg(test)]
mod tests {
@@ -251,7 +216,7 @@ mod tests {
#[test]
fn timeout() {
- let mut evt = EventFd::new().expect("failed to create eventfd");
+ let evt = EventFd::new().expect("failed to create eventfd");
assert_eq!(
evt.read_timeout(Duration::from_millis(1))
.expect("failed to read from eventfd with timeout"),
diff --git a/sys_util/src/file_flags.rs b/base/src/unix/file_flags.rs
index 7a2ad1bae..210f891df 100644
--- a/sys_util/src/file_flags.rs
+++ b/base/src/unix/file_flags.rs
@@ -6,7 +6,7 @@ use std::os::unix::io::AsRawFd;
use libc::{fcntl, EINVAL, F_GETFL, O_ACCMODE, O_RDONLY, O_RDWR, O_WRONLY};
-use crate::{errno_result, Error, Result};
+use super::{errno_result, Error, Result};
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum FileFlags {
@@ -35,8 +35,10 @@ impl FileFlags {
#[cfg(test)]
mod tests {
- use super::*;
- use crate::{pipe, EventFd};
+ use super::{
+ super::{pipe, EventFd},
+ *,
+ };
#[test]
fn pipe_pair() {
diff --git a/base/src/unix/file_traits.rs b/base/src/unix/file_traits.rs
new file mode 100644
index 000000000..a109f2327
--- /dev/null
+++ b/base/src/unix/file_traits.rs
@@ -0,0 +1,505 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ fs::File,
+ io::{Error, ErrorKind, Result},
+ os::unix::{
+ io::{AsRawFd, RawFd},
+ net::UnixStream,
+ },
+};
+
+use data_model::VolatileSlice;
+
+use super::{fallocate, FallocateMode};
+
+/// A trait for flushing the contents of a file to disk.
+/// This is equivalent to File's `sync_all` method, but
+/// wrapped in a trait so that it can be implemented for
+/// other types.
+pub trait FileSync {
+ // Flush buffers related to this file to disk.
+ fn fsync(&mut self) -> Result<()>;
+}
+
+impl FileSync for File {
+ fn fsync(&mut self) -> Result<()> {
+ self.sync_all()
+ }
+}
+
+/// A trait for setting the size of a file.
+/// This is equivalent to File's `set_len` method, but
+/// wrapped in a trait so that it can be implemented for
+/// other types.
+pub trait FileSetLen {
+ // Set the size of this file.
+ // This is the moral equivalent of `ftruncate()`.
+ fn set_len(&self, _len: u64) -> Result<()>;
+}
+
+impl FileSetLen for File {
+ fn set_len(&self, len: u64) -> Result<()> {
+ File::set_len(self, len)
+ }
+}
+
+/// A trait for getting the size of a file.
+/// This is equivalent to File's metadata().len() method,
+/// but wrapped in a trait so that it can be implemented for
+/// other types.
+pub trait FileGetLen {
+ /// Get the current length of the file in bytes.
+ fn get_len(&self) -> Result<u64>;
+}
+
+impl FileGetLen for File {
+ fn get_len(&self) -> Result<u64> {
+ Ok(self.metadata()?.len())
+ }
+}
+
+/// A trait for allocating disk space in a sparse file.
+/// This is equivalent to fallocate() with no special flags.
+pub trait FileAllocate {
+ /// Allocate storage for the region of the file starting at `offset` and extending `len` bytes.
+ fn allocate(&mut self, offset: u64, len: u64) -> Result<()>;
+}
+
+impl FileAllocate for File {
+ fn allocate(&mut self, offset: u64, len: u64) -> Result<()> {
+ fallocate(self, FallocateMode::Allocate, true, offset, len)
+ .map_err(|e| Error::from_raw_os_error(e.errno()))
+ }
+}
+
+/// A trait similar to `Read` and `Write`, but uses volatile memory as buffers.
+pub trait FileReadWriteVolatile {
+ /// Read bytes from this file into the given slice, returning the number of bytes read on
+ /// success.
+ fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize>;
+
+ /// Like `read_volatile`, except it reads to a slice of buffers. Data is copied to fill each
+ /// buffer in order, with the final buffer written to possibly being only partially filled. This
+ /// method must behave as a single call to `read_volatile` with the buffers concatenated would.
+ /// The default implementation calls `read_volatile` with either the first nonempty buffer
+ /// provided, or returns `Ok(0)` if none exists.
+ fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
+ bufs.iter()
+ .find(|b| b.size() > 0)
+ .map(|&b| self.read_volatile(b))
+ .unwrap_or(Ok(0))
+ }
+
+ /// Reads bytes from this into the given slice until all bytes in the slice are written, or an
+ /// error is returned.
+ fn read_exact_volatile(&mut self, mut slice: VolatileSlice) -> Result<()> {
+ while slice.size() > 0 {
+ let bytes_read = self.read_volatile(slice)?;
+ if bytes_read == 0 {
+ return Err(Error::from(ErrorKind::UnexpectedEof));
+ }
+ // Will panic if read_volatile read more bytes than we gave it, which would be worthy of
+ // a panic.
+ slice = slice.offset(bytes_read).unwrap();
+ }
+ Ok(())
+ }
+
+ /// Write bytes from the slice to the given file, returning the number of bytes written on
+ /// success.
+ fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize>;
+
+ /// Like `write_volatile`, except that it writes from a slice of buffers. Data is copied from
+ /// each buffer in order, with the final buffer read from possibly being only partially
+ /// consumed. This method must behave as a call to `write_volatile` with the buffers
+ /// concatenated would. The default implementation calls `write_volatile` with either the first
+ /// nonempty buffer provided, or returns `Ok(0)` if none exists.
+ fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
+ bufs.iter()
+ .find(|b| b.size() > 0)
+ .map(|&b| self.write_volatile(b))
+ .unwrap_or(Ok(0))
+ }
+
+ /// Write bytes from the slice to the given file until all the bytes from the slice have been
+ /// written, or an error is returned.
+ fn write_all_volatile(&mut self, mut slice: VolatileSlice) -> Result<()> {
+ while slice.size() > 0 {
+ let bytes_written = self.write_volatile(slice)?;
+ if bytes_written == 0 {
+ return Err(Error::from(ErrorKind::WriteZero));
+ }
+ // Will panic if read_volatile read more bytes than we gave it, which would be worthy of
+ // a panic.
+ slice = slice.offset(bytes_written).unwrap();
+ }
+ Ok(())
+ }
+}
+
+impl<'a, T: FileReadWriteVolatile + ?Sized> FileReadWriteVolatile for &'a mut T {
+ fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
+ (**self).read_volatile(slice)
+ }
+
+ fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
+ (**self).read_vectored_volatile(bufs)
+ }
+
+ fn read_exact_volatile(&mut self, slice: VolatileSlice) -> Result<()> {
+ (**self).read_exact_volatile(slice)
+ }
+
+ fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
+ (**self).write_volatile(slice)
+ }
+
+ fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
+ (**self).write_vectored_volatile(bufs)
+ }
+
+ fn write_all_volatile(&mut self, slice: VolatileSlice) -> Result<()> {
+ (**self).write_all_volatile(slice)
+ }
+}
+
+/// A trait similar to the unix `ReadExt` and `WriteExt` traits, but for volatile memory.
+pub trait FileReadWriteAtVolatile {
+ /// Reads bytes from this file at `offset` into the given slice, returning the number of bytes
+ /// read on success.
+ fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize>;
+
+ /// Like `read_at_volatile`, except it reads to a slice of buffers. Data is copied to fill each
+ /// buffer in order, with the final buffer written to possibly being only partially filled. This
+ /// method must behave as a single call to `read_at_volatile` with the buffers concatenated
+ /// would. The default implementation calls `read_at_volatile` with either the first nonempty
+ /// buffer provided, or returns `Ok(0)` if none exists.
+ fn read_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
+ if let Some(&slice) = bufs.first() {
+ self.read_at_volatile(slice, offset)
+ } else {
+ Ok(0)
+ }
+ }
+
+ /// Reads bytes from this file at `offset` into the given slice until all bytes in the slice are
+ /// read, or an error is returned.
+ fn read_exact_at_volatile(&mut self, mut slice: VolatileSlice, mut offset: u64) -> Result<()> {
+ while slice.size() > 0 {
+ match self.read_at_volatile(slice, offset) {
+ Ok(0) => return Err(Error::from(ErrorKind::UnexpectedEof)),
+ Ok(n) => {
+ slice = slice.offset(n).unwrap();
+ offset = offset.checked_add(n as u64).unwrap();
+ }
+ Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(())
+ }
+
+ /// Writes bytes from this file at `offset` into the given slice, returning the number of bytes
+ /// written on success.
+ fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize>;
+
+ /// Like `write_at_at_volatile`, except that it writes from a slice of buffers. Data is copied
+ /// from each buffer in order, with the final buffer read from possibly being only partially
+ /// consumed. This method must behave as a call to `write_at_volatile` with the buffers
+ /// concatenated would. The default implementation calls `write_at_volatile` with either the
+ /// first nonempty buffer provided, or returns `Ok(0)` if none exists.
+ fn write_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
+ if let Some(&slice) = bufs.first() {
+ self.write_at_volatile(slice, offset)
+ } else {
+ Ok(0)
+ }
+ }
+
+ /// Writes bytes from this file at `offset` into the given slice until all bytes in the slice
+ /// are written, or an error is returned.
+ fn write_all_at_volatile(&mut self, mut slice: VolatileSlice, mut offset: u64) -> Result<()> {
+ while slice.size() > 0 {
+ match self.write_at_volatile(slice, offset) {
+ Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
+ Ok(n) => {
+ slice = slice.offset(n).unwrap();
+ offset = offset.checked_add(n as u64).unwrap();
+ }
+ Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(())
+ }
+}
+
+impl<'a, T: FileReadWriteAtVolatile + ?Sized> FileReadWriteAtVolatile for &'a mut T {
+ fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
+ (**self).read_at_volatile(slice, offset)
+ }
+
+ fn read_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
+ (**self).read_vectored_at_volatile(bufs, offset)
+ }
+
+ fn read_exact_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<()> {
+ (**self).read_exact_at_volatile(slice, offset)
+ }
+
+ fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
+ (**self).write_at_volatile(slice, offset)
+ }
+
+ fn write_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
+ (**self).write_vectored_at_volatile(bufs, offset)
+ }
+
+ fn write_all_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<()> {
+ (**self).write_all_at_volatile(slice, offset)
+ }
+}
+
+// This module allows the below macros to refer to $crate::platform::file_traits::lib::X and ensures other
+// crates don't need to add additional crates to their Cargo.toml.
+pub mod lib {
+ pub use libc::{
+ c_int, c_void, iovec, off64_t, pread64, preadv64, pwrite64, pwritev64, read, readv, size_t,
+ write, writev,
+ };
+
+ pub use data_model::{IoBufMut, VolatileSlice};
+}
+
+#[macro_export]
+macro_rules! volatile_impl {
+ ($ty:ty) => {
+ impl FileReadWriteVolatile for $ty {
+ fn read_volatile(
+ &mut self,
+ slice: $crate::platform::file_traits::lib::VolatileSlice,
+ ) -> std::io::Result<usize> {
+ // Safe because only bytes inside the slice are accessed and the kernel is expected
+ // to handle arbitrary memory for I/O.
+ let ret = unsafe {
+ $crate::platform::file_traits::lib::read(
+ self.as_raw_fd(),
+ slice.as_mut_ptr() as *mut std::ffi::c_void,
+ slice.size() as usize,
+ )
+ };
+ if ret >= 0 {
+ Ok(ret as usize)
+ } else {
+ Err(std::io::Error::last_os_error())
+ }
+ }
+
+ fn read_vectored_volatile(
+ &mut self,
+ bufs: &[$crate::platform::file_traits::lib::VolatileSlice],
+ ) -> std::io::Result<usize> {
+ let iobufs = $crate::platform::file_traits::lib::VolatileSlice::as_iobufs(bufs);
+ let iovecs = $crate::platform::file_traits::lib::IoBufMut::as_iobufs(iobufs);
+
+ if iovecs.is_empty() {
+ return Ok(0);
+ }
+
+ // Safe because only bytes inside the buffers are accessed and the kernel is
+ // expected to handle arbitrary memory for I/O.
+ let ret = unsafe {
+ $crate::platform::file_traits::lib::readv(
+ self.as_raw_fd(),
+ iovecs.as_ptr(),
+ iovecs.len() as std::os::raw::c_int,
+ )
+ };
+ if ret >= 0 {
+ Ok(ret as usize)
+ } else {
+ Err(std::io::Error::last_os_error())
+ }
+ }
+
+ fn write_volatile(
+ &mut self,
+ slice: $crate::platform::file_traits::lib::VolatileSlice,
+ ) -> std::io::Result<usize> {
+ // Safe because only bytes inside the slice are accessed and the kernel is expected
+ // to handle arbitrary memory for I/O.
+ let ret = unsafe {
+ $crate::platform::file_traits::lib::write(
+ self.as_raw_fd(),
+ slice.as_ptr() as *const std::ffi::c_void,
+ slice.size() as usize,
+ )
+ };
+ if ret >= 0 {
+ Ok(ret as usize)
+ } else {
+ Err(std::io::Error::last_os_error())
+ }
+ }
+
+ fn write_vectored_volatile(
+ &mut self,
+ bufs: &[$crate::platform::file_traits::lib::VolatileSlice],
+ ) -> std::io::Result<usize> {
+ let iobufs = $crate::platform::file_traits::lib::VolatileSlice::as_iobufs(bufs);
+ let iovecs = $crate::platform::file_traits::lib::IoBufMut::as_iobufs(iobufs);
+
+ if iovecs.is_empty() {
+ return Ok(0);
+ }
+
+ // Safe because only bytes inside the buffers are accessed and the kernel is
+ // expected to handle arbitrary memory for I/O.
+ let ret = unsafe {
+ $crate::platform::file_traits::lib::writev(
+ self.as_raw_fd(),
+ iovecs.as_ptr(),
+ iovecs.len() as std::os::raw::c_int,
+ )
+ };
+ if ret >= 0 {
+ Ok(ret as usize)
+ } else {
+ Err(std::io::Error::last_os_error())
+ }
+ }
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! volatile_at_impl {
+ ($ty:ty) => {
+ impl FileReadWriteAtVolatile for $ty {
+ fn read_at_volatile(
+ &mut self,
+ slice: $crate::platform::file_traits::lib::VolatileSlice,
+ offset: u64,
+ ) -> std::io::Result<usize> {
+ // Safe because only bytes inside the slice are accessed and the kernel is expected
+ // to handle arbitrary memory for I/O.
+ let ret = unsafe {
+ $crate::platform::file_traits::lib::pread64(
+ self.as_raw_fd(),
+ slice.as_mut_ptr() as *mut std::ffi::c_void,
+ slice.size() as usize,
+ offset as $crate::platform::file_traits::lib::off64_t,
+ )
+ };
+
+ if ret >= 0 {
+ Ok(ret as usize)
+ } else {
+ Err(std::io::Error::last_os_error())
+ }
+ }
+
+ fn read_vectored_at_volatile(
+ &mut self,
+ bufs: &[$crate::platform::file_traits::lib::VolatileSlice],
+ offset: u64,
+ ) -> std::io::Result<usize> {
+ let iobufs = $crate::platform::file_traits::lib::VolatileSlice::as_iobufs(bufs);
+ let iovecs = $crate::platform::file_traits::lib::IoBufMut::as_iobufs(iobufs);
+
+ if iovecs.is_empty() {
+ return Ok(0);
+ }
+
+ // Safe because only bytes inside the buffers are accessed and the kernel is
+ // expected to handle arbitrary memory for I/O.
+ let ret = unsafe {
+ $crate::platform::file_traits::lib::preadv64(
+ self.as_raw_fd(),
+ iovecs.as_ptr(),
+ iovecs.len() as std::os::raw::c_int,
+ offset as $crate::platform::file_traits::lib::off64_t,
+ )
+ };
+ if ret >= 0 {
+ Ok(ret as usize)
+ } else {
+ Err(std::io::Error::last_os_error())
+ }
+ }
+
+ fn write_at_volatile(
+ &mut self,
+ slice: $crate::platform::file_traits::lib::VolatileSlice,
+ offset: u64,
+ ) -> std::io::Result<usize> {
+ // Safe because only bytes inside the slice are accessed and the kernel is expected
+ // to handle arbitrary memory for I/O.
+ let ret = unsafe {
+ $crate::platform::file_traits::lib::pwrite64(
+ self.as_raw_fd(),
+ slice.as_ptr() as *const std::ffi::c_void,
+ slice.size() as usize,
+ offset as $crate::platform::file_traits::lib::off64_t,
+ )
+ };
+
+ if ret >= 0 {
+ Ok(ret as usize)
+ } else {
+ Err(std::io::Error::last_os_error())
+ }
+ }
+
+ fn write_vectored_at_volatile(
+ &mut self,
+ bufs: &[$crate::platform::file_traits::lib::VolatileSlice],
+ offset: u64,
+ ) -> std::io::Result<usize> {
+ let iobufs = $crate::platform::file_traits::lib::VolatileSlice::as_iobufs(bufs);
+ let iovecs = $crate::platform::file_traits::lib::IoBufMut::as_iobufs(iobufs);
+
+ if iovecs.is_empty() {
+ return Ok(0);
+ }
+
+ // Safe because only bytes inside the buffers are accessed and the kernel is
+ // expected to handle arbitrary memory for I/O.
+ let ret = unsafe {
+ $crate::platform::file_traits::lib::pwritev64(
+ self.as_raw_fd(),
+ iovecs.as_ptr(),
+ iovecs.len() as std::os::raw::c_int,
+ offset as $crate::platform::file_traits::lib::off64_t,
+ )
+ };
+ if ret >= 0 {
+ Ok(ret as usize)
+ } else {
+ Err(std::io::Error::last_os_error())
+ }
+ }
+ }
+ };
+}
+
+volatile_impl!(File);
+volatile_at_impl!(File);
+volatile_impl!(UnixStream);
+
+/// A trait similar to `AsRawFd` but supports an arbitrary number of file descriptors.
+pub trait AsRawFds {
+ fn as_raw_fds(&self) -> Vec<RawFd>;
+}
+
+impl<T> AsRawFds for T
+where
+ T: AsRawFd,
+{
+ fn as_raw_fds(&self) -> Vec<RawFd> {
+ vec![self.as_raw_fd()]
+ }
+}
diff --git a/base/src/unix/get_filesystem_type.rs b/base/src/unix/get_filesystem_type.rs
new file mode 100644
index 000000000..9025a642e
--- /dev/null
+++ b/base/src/unix/get_filesystem_type.rs
@@ -0,0 +1,29 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::Result;
+use crate::syscall;
+use libc::fstatfs;
+use std::{fs::File, mem::MaybeUninit, os::unix::io::AsRawFd};
+
+/// Obtain file system type of the file system that the file is served from.
+pub fn get_filesystem_type(file: &File) -> Result<i64> {
+ let mut statfs_buf = MaybeUninit::<libc::statfs>::uninit();
+ // Safe because we just got the memory space with exact required amount and
+ // passing that on.
+ syscall!(unsafe { fstatfs(file.as_raw_fd(), statfs_buf.as_mut_ptr()) })?;
+ // Safe because the kernel guarantees the struct is initialized.
+ let statfs_buf = unsafe { statfs_buf.assume_init() };
+ Ok(statfs_buf.f_type as i64)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn simple_test() {
+ let file = File::open("/dev/null").unwrap();
+ let _fstype = get_filesystem_type(&file).unwrap();
+ }
+}
diff --git a/base/src/unix/handle_eintr.rs b/base/src/unix/handle_eintr.rs
new file mode 100644
index 000000000..cd95b1feb
--- /dev/null
+++ b/base/src/unix/handle_eintr.rs
@@ -0,0 +1,259 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Macro and helper trait for handling interrupted routines.
+
+use std::io;
+
+use libc::EINTR;
+
+/// Trait for determining if a result indicates the operation was interrupted.
+pub trait InterruptibleResult {
+ /// Returns `true` if this result indicates the operation was interrupted and should be retried,
+ /// and `false` in all other cases.
+ fn is_interrupted(&self) -> bool;
+}
+
+impl<T> InterruptibleResult for super::Result<T> {
+ fn is_interrupted(&self) -> bool {
+ matches!(self, Err(e) if e.errno() == EINTR)
+ }
+}
+
+impl<T> InterruptibleResult for io::Result<T> {
+ fn is_interrupted(&self) -> bool {
+ matches!(self, Err(e) if e.kind() == io::ErrorKind::Interrupted)
+ }
+}
+
+/// Macro that retries the given expression every time its result indicates it was interrupted (i.e.
+/// returned `EINTR`). This is useful for operations that are prone to being interrupted by
+/// signals, such as blocking syscalls.
+///
+/// The given expression `$x` can return
+///
+/// * `crate::platform::Result` in which case the expression is retried if the `Error::errno()` is `EINTR`.
+/// * `std::io::Result` in which case the expression is retried if the `ErrorKind` is `ErrorKind::Interrupted`.
+///
+/// Note that if expression returns i32 (i.e. either -1 or error code), then handle_eintr_errno()
+/// or handle_eintr_rc() should be used instead.
+///
+/// In all cases where the result does not indicate that the expression was interrupted, the result
+/// is returned verbatim to the caller of this macro.
+///
+/// See the section titled _Interruption of system calls and library functions by signal handlers_
+/// on the man page for `signal(7)` to see more information about interruptible syscalls.
+///
+/// To summarize, routines that use one of these syscalls _might_ need to handle `EINTR`:
+///
+/// * `accept(2)`
+/// * `clock_nanosleep(2)`
+/// * `connect(2)`
+/// * `epoll_pwait(2)`
+/// * `epoll_wait(2)`
+/// * `fcntl(2)`
+/// * `fifo(7)`
+/// * `flock(2)`
+/// * `futex(2)`
+/// * `getrandom(2)`
+/// * `inotify(7)`
+/// * `io_getevents(2)`
+/// * `ioctl(2)`
+/// * `mq_receive(3)`
+/// * `mq_send(3)`
+/// * `mq_timedreceive(3)`
+/// * `mq_timedsend(3)`
+/// * `msgrcv(2)`
+/// * `msgsnd(2)`
+/// * `nanosleep(2)`
+/// * `open(2)`
+/// * `pause(2)`
+/// * `poll(2)`
+/// * `ppoll(2)`
+/// * `pselect(2)`
+/// * `pthread_cond_wait(3)`
+/// * `pthread_mutex_lock(3)`
+/// * `read(2)`
+/// * `readv(2)`
+/// * `recv(2)`
+/// * `recvfrom(2)`
+/// * `recvmmsg(2)`
+/// * `recvmsg(2)`
+/// * `select(2)`
+/// * `sem_timedwait(3)`
+/// * `sem_wait(3)`
+/// * `semop(2)`
+/// * `semtimedop(2)`
+/// * `send(2)`
+/// * `sendmsg(2)`
+/// * `sendto(2)`
+/// * `setsockopt(2)`
+/// * `sigsuspend(2)`
+/// * `sigtimedwait(2)`
+/// * `sigwaitinfo(2)`
+/// * `sleep(3)`
+/// * `usleep(3)`
+/// * `wait(2)`
+/// * `wait3(2)`
+/// * `wait4(2)`
+/// * `waitid(2)`
+/// * `waitpid(2)`
+/// * `write(2)`
+/// * `writev(2)`
+///
+/// # Examples
+///
+/// ```
+/// # use crate::platform::handle_eintr;
+/// # use std::io::stdin;
+/// # fn main() {
+/// let mut line = String::new();
+/// let res = handle_eintr!(stdin().read_line(&mut line));
+/// # }
+/// ```
+#[macro_export]
+macro_rules! handle_eintr {
+ ($x:expr) => {{
+ use $crate::platform::handle_eintr::InterruptibleResult;
+ let res;
+ loop {
+ match $x {
+ ref v if v.is_interrupted() => continue,
+ v => {
+ res = v;
+ break;
+ }
+ }
+ }
+ res
+ }};
+}
+
+/// Macro that retries the given expression every time its result indicates it was interrupted.
+/// It is intended to use with system functions that return `EINTR` and other error codes
+/// directly as their result.
+/// Most of reentrant functions use this way of signalling errors.
+#[macro_export]
+macro_rules! handle_eintr_rc {
+ ($x:expr) => {{
+ use libc::EINTR;
+ let mut res;
+ loop {
+ res = $x;
+ if res != EINTR {
+ break;
+ }
+ }
+ res
+ }};
+}
+
+/// Macro that retries the given expression every time its result indicates it was interrupted.
+/// It is intended to use with system functions that signal error by returning `-1` and setting
+/// `errno` to appropriate error code (`EINTR`, `EINVAL`, etc.)
+/// Most of standard non-reentrant libc functions use this way of signalling errors.
+#[macro_export]
+macro_rules! handle_eintr_errno {
+ ($x:expr) => {{
+ use libc::EINTR;
+ use $crate::platform::Error;
+ let mut res;
+ loop {
+ res = $x;
+ if res != -1 || Error::last() != Error::new(EINTR) {
+ break;
+ }
+ }
+ res
+ }};
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{super::Error as SysError, *};
+
+ // Sets errno to the given error code.
+ fn set_errno(e: i32) {
+ #[cfg(target_os = "android")]
+ unsafe fn errno_location() -> *mut libc::c_int {
+ libc::__errno()
+ }
+ #[cfg(target_os = "linux")]
+ unsafe fn errno_location() -> *mut libc::c_int {
+ libc::__errno_location()
+ }
+
+ unsafe {
+ *errno_location() = e;
+ }
+ }
+
+ #[test]
+ fn i32_eintr_rc() {
+ let mut count = 3;
+ let mut dummy = || {
+ count -= 1;
+ if count > 0 {
+ EINTR
+ } else {
+ 0
+ }
+ };
+ let res = handle_eintr_rc!(dummy());
+ assert_eq!(res, 0);
+ assert_eq!(count, 0);
+ }
+
+ #[test]
+ fn i32_eintr_errno() {
+ let mut count = 3;
+ let mut dummy = || {
+ count -= 1;
+ if count > 0 {
+ set_errno(EINTR);
+ -1
+ } else {
+ 56
+ }
+ };
+ let res = handle_eintr_errno!(dummy());
+ assert_eq!(res, 56);
+ assert_eq!(count, 0);
+ }
+
+ #[test]
+ fn sys_eintr() {
+ let mut count = 7;
+ let mut dummy = || {
+ count -= 1;
+ if count > 1 {
+ Err(SysError::new(EINTR))
+ } else {
+ Ok(101)
+ }
+ };
+ let res = handle_eintr!(dummy());
+ assert_eq!(res, Ok(101));
+ assert_eq!(count, 1);
+ }
+
+ #[test]
+ fn io_eintr() {
+ let mut count = 108;
+ let mut dummy = || {
+ count -= 1;
+ if count > 99 {
+ Err(io::Error::new(
+ io::ErrorKind::Interrupted,
+ "interrupted again :(",
+ ))
+ } else {
+ Ok(32)
+ }
+ };
+ let res = handle_eintr!(dummy());
+ assert_eq!(res.unwrap(), 32);
+ assert_eq!(count, 99);
+ }
+}
diff --git a/base/src/unix/ioctl.rs b/base/src/unix/ioctl.rs
new file mode 100644
index 000000000..95e185afa
--- /dev/null
+++ b/base/src/unix/ioctl.rs
@@ -0,0 +1,204 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Macros and wrapper functions for dealing with ioctls.
+
+// Allow missing safety comments because this file provides just thin helper functions for
+// `libc::ioctl`. Their safety follows `libc::ioctl`'s safety.
+#![allow(clippy::missing_safety_doc)]
+
+use std::os::{raw::*, unix::io::AsRawFd};
+
+/// Raw macro to declare the expression that calculates an ioctl number
+#[macro_export]
+macro_rules! ioctl_expr {
+ ($dir:expr, $ty:expr, $nr:expr, $size:expr) => {
+ (($dir << $crate::platform::ioctl::_IOC_DIRSHIFT)
+ | ($ty << $crate::platform::ioctl::_IOC_TYPESHIFT)
+ | ($nr << $crate::platform::ioctl::_IOC_NRSHIFT)
+ | ($size << $crate::platform::ioctl::_IOC_SIZESHIFT)) as $crate::platform::IoctlNr
+ };
+}
+
+/// Raw macro to declare a function that returns an ioctl number.
+#[macro_export]
+macro_rules! ioctl_ioc_nr {
+ ($name:ident, $dir:expr, $ty:expr, $nr:expr, $size:expr) => {
+ #[allow(non_snake_case)]
+ pub const fn $name() -> $crate::platform::IoctlNr {
+ $crate::ioctl_expr!($dir, $ty, $nr, $size)
+ }
+ };
+ ($name:ident, $dir:expr, $ty:expr, $nr:expr, $size:expr, $($v:ident),+) => {
+ #[allow(non_snake_case)]
+ pub const fn $name($($v: ::std::os::raw::c_uint),+) -> $crate::platform::IoctlNr {
+ $crate::ioctl_expr!($dir, $ty, $nr, $size)
+ }
+ };
+}
+
+/// Declare an ioctl that transfers no data.
+#[macro_export]
+macro_rules! ioctl_io_nr {
+ ($name:ident, $ty:expr, $nr:expr) => {
+ $crate::ioctl_ioc_nr!($name, $crate::platform::ioctl::_IOC_NONE, $ty, $nr, 0);
+ };
+ ($name:ident, $ty:expr, $nr:expr, $($v:ident),+) => {
+ $crate::ioctl_ioc_nr!($name, $crate::platform::ioctl::_IOC_NONE, $ty, $nr, 0, $($v),+);
+ };
+}
+
+/// Declare an ioctl that reads data.
+#[macro_export]
+macro_rules! ioctl_ior_nr {
+ ($name:ident, $ty:expr, $nr:expr, $size:ty) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $crate::platform::ioctl::_IOC_READ,
+ $ty,
+ $nr,
+ ::std::mem::size_of::<$size>() as u32
+ );
+ };
+ ($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $crate::platform::ioctl::_IOC_READ,
+ $ty,
+ $nr,
+ ::std::mem::size_of::<$size>() as u32,
+ $($v),+
+ );
+ };
+}
+
+/// Declare an ioctl that writes data.
+#[macro_export]
+macro_rules! ioctl_iow_nr {
+ ($name:ident, $ty:expr, $nr:expr, $size:ty) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $crate::platform::ioctl::_IOC_WRITE,
+ $ty,
+ $nr,
+ ::std::mem::size_of::<$size>() as u32
+ );
+ };
+ ($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $crate::platform::ioctl::_IOC_WRITE,
+ $ty,
+ $nr,
+ ::std::mem::size_of::<$size>() as u32,
+ $($v),+
+ );
+ };
+}
+
+/// Declare an ioctl that reads and writes data.
+#[macro_export]
+macro_rules! ioctl_iowr_nr {
+ ($name:ident, $ty:expr, $nr:expr, $size:ty) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $crate::platform::ioctl::_IOC_READ | $crate::platform::ioctl::_IOC_WRITE,
+ $ty,
+ $nr,
+ ::std::mem::size_of::<$size>() as u32
+ );
+ };
+ ($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $crate::platform::ioctl::_IOC_READ | $crate::platform::ioctl::_IOC_WRITE,
+ $ty,
+ $nr,
+ ::std::mem::size_of::<$size>() as u32,
+ $($v),+
+ );
+ };
+}
+
+pub const _IOC_NRBITS: c_uint = 8;
+pub const _IOC_TYPEBITS: c_uint = 8;
+pub const _IOC_SIZEBITS: c_uint = 14;
+pub const _IOC_DIRBITS: c_uint = 2;
+pub const _IOC_NRMASK: c_uint = 255;
+pub const _IOC_TYPEMASK: c_uint = 255;
+pub const _IOC_SIZEMASK: c_uint = 16383;
+pub const _IOC_DIRMASK: c_uint = 3;
+pub const _IOC_NRSHIFT: c_uint = 0;
+pub const _IOC_TYPESHIFT: c_uint = 8;
+pub const _IOC_SIZESHIFT: c_uint = 16;
+pub const _IOC_DIRSHIFT: c_uint = 30;
+pub const _IOC_NONE: c_uint = 0;
+pub const _IOC_WRITE: c_uint = 1;
+pub const _IOC_READ: c_uint = 2;
+pub const IOC_IN: c_uint = 1_073_741_824;
+pub const IOC_OUT: c_uint = 2_147_483_648;
+pub const IOC_INOUT: c_uint = 3_221_225_472;
+pub const IOCSIZE_MASK: c_uint = 1_073_676_288;
+pub const IOCSIZE_SHIFT: c_uint = 16;
+
+#[cfg(target_os = "android")]
+pub type IoctlNr = c_int;
+#[cfg(not(target_os = "android"))]
+pub type IoctlNr = c_ulong;
+
+/// Run an ioctl with no arguments.
+pub unsafe fn ioctl<F: AsRawFd>(fd: &F, nr: IoctlNr) -> c_int {
+ libc::ioctl(fd.as_raw_fd(), nr, 0)
+}
+
+/// Run an ioctl with a single value argument.
+pub unsafe fn ioctl_with_val<F: AsRawFd>(fd: &F, nr: IoctlNr, arg: c_ulong) -> c_int {
+ libc::ioctl(fd.as_raw_fd(), nr, arg)
+}
+
+/// Run an ioctl with an immutable reference.
+pub unsafe fn ioctl_with_ref<F: AsRawFd, T>(fd: &F, nr: IoctlNr, arg: &T) -> c_int {
+ libc::ioctl(fd.as_raw_fd(), nr, arg as *const T as *const c_void)
+}
+
+/// Run an ioctl with a mutable reference.
+pub unsafe fn ioctl_with_mut_ref<F: AsRawFd, T>(fd: &F, nr: IoctlNr, arg: &mut T) -> c_int {
+ libc::ioctl(fd.as_raw_fd(), nr, arg as *mut T as *mut c_void)
+}
+
+/// Run an ioctl with a raw pointer.
+pub unsafe fn ioctl_with_ptr<F: AsRawFd, T>(fd: &F, nr: IoctlNr, arg: *const T) -> c_int {
+ libc::ioctl(fd.as_raw_fd(), nr, arg as *const c_void)
+}
+
+/// Run an ioctl with a mutable raw pointer.
+pub unsafe fn ioctl_with_mut_ptr<F: AsRawFd, T>(fd: &F, nr: IoctlNr, arg: *mut T) -> c_int {
+ libc::ioctl(fd.as_raw_fd(), nr, arg as *mut c_void)
+}
+
+#[cfg(test)]
+mod tests {
+ const TUNTAP: ::std::os::raw::c_uint = 0x54;
+ const VHOST: ::std::os::raw::c_uint = 0xaf;
+ const EVDEV: ::std::os::raw::c_uint = 0x45;
+
+ ioctl_io_nr!(VHOST_SET_OWNER, VHOST, 0x01);
+ ioctl_ior_nr!(TUNGETFEATURES, TUNTAP, 0xcf, ::std::os::raw::c_uint);
+ ioctl_iow_nr!(TUNSETQUEUE, TUNTAP, 0xd9, ::std::os::raw::c_int);
+ ioctl_iowr_nr!(VHOST_GET_VRING_BASE, VHOST, 0x12, ::std::os::raw::c_int);
+
+ ioctl_ior_nr!(EVIOCGBIT, EVDEV, 0x20 + evt, [u8; 128], evt);
+ ioctl_io_nr!(FAKE_IOCTL_2_ARG, EVDEV, 0x01 + x + y, x, y);
+
+ #[test]
+ fn ioctl_macros() {
+ assert_eq!(0x0000af01, VHOST_SET_OWNER());
+ assert_eq!(0x800454cf, TUNGETFEATURES());
+ assert_eq!(0x400454d9, TUNSETQUEUE());
+ assert_eq!(0xc004af12, VHOST_GET_VRING_BASE());
+
+ assert_eq!(0x80804522, EVIOCGBIT(2));
+ assert_eq!(0x00004509, FAKE_IOCTL_2_ARG(3, 5));
+ }
+}
diff --git a/sys_util/src/linux/mod.rs b/base/src/unix/linux/mod.rs
index 8503daa89..8503daa89 100644
--- a/sys_util/src/linux/mod.rs
+++ b/base/src/unix/linux/mod.rs
diff --git a/sys_util/src/linux/syslog.rs b/base/src/unix/linux/syslog.rs
index 1ec876304..05972a3a3 100644
--- a/sys_util/src/linux/syslog.rs
+++ b/base/src/unix/linux/syslog.rs
@@ -4,21 +4,27 @@
//! Implementation of the Syslog trait for Linux.
-use std::fmt;
-use std::fs::File;
-use std::io::{Cursor, ErrorKind, Write};
-use std::mem;
-use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
-use std::os::unix::net::UnixDatagram;
-use std::ptr::null;
+use std::{
+ fmt,
+ fs::File,
+ io::{Cursor, ErrorKind, Write},
+ mem,
+ os::unix::{
+ io::{AsRawFd, FromRawFd, RawFd},
+ net::UnixDatagram,
+ },
+ ptr::null,
+};
use libc::{
closelog, fcntl, localtime_r, openlog, time, time_t, tm, F_GETFD, LOG_NDELAY, LOG_PERROR,
LOG_PID, LOG_USER,
};
-use crate::getpid;
-use crate::syslog::{Error, Facility, Priority, Syslog};
+use super::super::{
+ getpid,
+ syslog::{Error, Facility, Priority, Syslog},
+};
const SYSLOG_PATH: &str = "/dev/log";
@@ -96,7 +102,7 @@ impl Syslog for PlatformSyslog {
};
if let Ok(len) = &res {
- send_buf(&socket, &buf[..*len])
+ send_buf(socket, &buf[..*len])
}
}
}
diff --git a/base/src/unix/mmap.rs b/base/src/unix/mmap.rs
new file mode 100644
index 000000000..5fc30631b
--- /dev/null
+++ b/base/src/unix/mmap.rs
@@ -0,0 +1,1159 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! The mmap module provides a safe interface to mmap memory and ensures unmap is called when the
+//! mmap object leaves scope.
+
+use std::{
+ cmp::min,
+ io,
+ mem::size_of,
+ ptr::{copy_nonoverlapping, null_mut, read_unaligned, write_unaligned},
+};
+
+use crate::external_mapping::ExternalMapping;
+use crate::AsRawDescriptor;
+use libc::{
+ c_int, c_void, read, write, {self},
+};
+use remain::sorted;
+
+use data_model::{volatile_memory::*, DataInit};
+
+use super::{pagesize, Error as ErrnoError};
+
+#[sorted]
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("`add_fd_mapping` is unsupported")]
+ AddFdMappingIsUnsupported,
+ #[error("requested memory out of range")]
+ InvalidAddress,
+ #[error("invalid argument provided when creating mapping")]
+ InvalidArgument,
+ #[error("requested offset is out of range of off_t")]
+ InvalidOffset,
+ #[error("requested memory range spans past the end of the region: offset={0} count={1} region_size={2}")]
+ InvalidRange(usize, usize, usize),
+ #[error("requested memory is not page aligned")]
+ NotPageAligned,
+ #[error("failed to read from file to memory: {0}")]
+ ReadToMemory(#[source] io::Error),
+ #[error("`remove_mapping` is unsupported")]
+ RemoveMappingIsUnsupported,
+ #[error("mmap related system call failed: {0}")]
+ SystemCallFailed(#[source] ErrnoError),
+ #[error("failed to write from memory to file: {0}")]
+ WriteFromMemory(#[source] io::Error),
+}
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// Memory access type for anonymous shared memory mapping.
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub struct Protection(c_int);
+impl Protection {
+ /// Returns Protection allowing no access.
+ #[inline(always)]
+ pub fn none() -> Protection {
+ Protection(libc::PROT_NONE)
+ }
+
+ /// Returns Protection allowing read/write access.
+ #[inline(always)]
+ pub fn read_write() -> Protection {
+ Protection(libc::PROT_READ | libc::PROT_WRITE)
+ }
+
+ /// Returns Protection allowing read access.
+ #[inline(always)]
+ pub fn read() -> Protection {
+ Protection(libc::PROT_READ)
+ }
+
+ /// Set read events.
+ #[inline(always)]
+ pub fn set_read(self) -> Protection {
+ Protection(self.0 | libc::PROT_READ)
+ }
+
+ /// Set write events.
+ #[inline(always)]
+ pub fn set_write(self) -> Protection {
+ Protection(self.0 | libc::PROT_WRITE)
+ }
+}
+
+impl From<c_int> for Protection {
+ fn from(f: c_int) -> Self {
+ Protection(f)
+ }
+}
+
+impl From<Protection> for c_int {
+ fn from(p: Protection) -> c_int {
+ p.0
+ }
+}
+
+/// Validates that `offset`..`offset+range_size` lies within the bounds of a memory mapping of
+/// `mmap_size` bytes. Also checks for any overflow.
+fn validate_includes_range(mmap_size: usize, offset: usize, range_size: usize) -> Result<()> {
+ // Ensure offset + size doesn't overflow
+ let end_offset = offset
+ .checked_add(range_size)
+ .ok_or(Error::InvalidAddress)?;
+ // Ensure offset + size are within the mapping bounds
+ if end_offset <= mmap_size {
+ Ok(())
+ } else {
+ Err(Error::InvalidAddress)
+ }
+}
+
+/// A range of memory that can be msynced, for abstracting over different types of memory mappings.
+///
+/// Safe when implementers guarantee `ptr`..`ptr+size` is an mmaped region owned by this object that
+/// can't be unmapped during the `MappedRegion`'s lifetime.
+pub unsafe trait MappedRegion: Send + Sync {
+ /// Returns a pointer to the beginning of the memory region. Should only be
+ /// used for passing this region to ioctls for setting guest memory.
+ fn as_ptr(&self) -> *mut u8;
+
+ /// Returns the size of the memory region in bytes.
+ fn size(&self) -> usize;
+
+ /// Maps `size` bytes starting at `fd_offset` bytes from within the given `fd`
+ /// at `offset` bytes from the start of the region with `prot` protections.
+ /// `offset` must be page aligned.
+ ///
+ /// # Arguments
+ /// * `offset` - Page aligned offset into the arena in bytes.
+ /// * `size` - Size of memory region in bytes.
+ /// * `fd` - File descriptor to mmap from.
+ /// * `fd_offset` - Offset in bytes from the beginning of `fd` to start the mmap.
+ /// * `prot` - Protection (e.g. readable/writable) of the memory region.
+ fn add_fd_mapping(
+ &mut self,
+ _offset: usize,
+ _size: usize,
+ _fd: &dyn AsRawDescriptor,
+ _fd_offset: u64,
+ _prot: Protection,
+ ) -> Result<()> {
+ Err(Error::AddFdMappingIsUnsupported)
+ }
+
+ /// Remove `size`-byte mapping starting at `offset`.
+ fn remove_mapping(&mut self, _offset: usize, _size: usize) -> Result<()> {
+ Err(Error::RemoveMappingIsUnsupported)
+ }
+}
+
+impl dyn MappedRegion {
+ /// Calls msync with MS_SYNC on a mapping of `size` bytes starting at `offset` from the start of
+ /// the region. `offset`..`offset+size` must be contained within the `MappedRegion`.
+ pub fn msync(&self, offset: usize, size: usize) -> Result<()> {
+ validate_includes_range(self.size(), offset, size)?;
+
+ // Safe because the MemoryMapping/MemoryMappingArena interface ensures our pointer and size
+ // are correct, and we've validated that `offset`..`offset+size` is in the range owned by
+ // this `MappedRegion`.
+ let ret = unsafe {
+ libc::msync(
+ (self.as_ptr() as usize + offset) as *mut libc::c_void,
+ size,
+ libc::MS_SYNC,
+ )
+ };
+ if ret != -1 {
+ Ok(())
+ } else {
+ Err(Error::SystemCallFailed(ErrnoError::last()))
+ }
+ }
+}
+
+unsafe impl MappedRegion for ExternalMapping {
+ fn as_ptr(&self) -> *mut u8 {
+ self.as_ptr()
+ }
+
+ /// Returns the size of the memory region in bytes.
+ fn size(&self) -> usize {
+ self.size()
+ }
+}
+
+/// Wraps an anonymous shared memory mapping in the current process. Provides
+/// RAII semantics including munmap when no longer needed.
+#[derive(Debug)]
+pub struct MemoryMapping {
+ addr: *mut u8,
+ size: usize,
+}
+
+// Send and Sync aren't automatically inherited for the raw address pointer.
+// Accessing that pointer is only done through the stateless interface which
+// allows the object to be shared by multiple threads without a decrease in
+// safety.
+unsafe impl Send for MemoryMapping {}
+unsafe impl Sync for MemoryMapping {}
+
+impl MemoryMapping {
+ /// Creates an anonymous shared, read/write mapping of `size` bytes.
+ ///
+ /// # Arguments
+ /// * `size` - Size of memory region in bytes.
+ pub fn new(size: usize) -> Result<MemoryMapping> {
+ MemoryMapping::new_protection(size, Protection::read_write())
+ }
+
+ /// Creates an anonymous shared mapping of `size` bytes with `prot` protection.
+ ///
+ /// # Arguments
+ /// * `size` - Size of memory region in bytes.
+ /// * `prot` - Protection (e.g. readable/writable) of the memory region.
+ pub fn new_protection(size: usize, prot: Protection) -> Result<MemoryMapping> {
+ // This is safe because we are creating an anonymous mapping in a place not already used by
+ // any other area in this process.
+ unsafe {
+ MemoryMapping::try_mmap(
+ None,
+ size,
+ prot.into(),
+ libc::MAP_ANONYMOUS | libc::MAP_SHARED | libc::MAP_NORESERVE,
+ None,
+ )
+ }
+ }
+
+ /// Maps the first `size` bytes of the given `fd` as read/write.
+ ///
+ /// # Arguments
+ /// * `fd` - File descriptor to mmap from.
+ /// * `size` - Size of memory region in bytes.
+ pub fn from_fd(fd: &dyn AsRawDescriptor, size: usize) -> Result<MemoryMapping> {
+ MemoryMapping::from_fd_offset(fd, size, 0)
+ }
+
+ pub fn from_fd_offset(
+ fd: &dyn AsRawDescriptor,
+ size: usize,
+ offset: u64,
+ ) -> Result<MemoryMapping> {
+ MemoryMapping::from_fd_offset_protection(fd, size, offset, Protection::read_write())
+ }
+
+ /// Maps the `size` bytes starting at `offset` bytes of the given `fd` as read/write.
+ ///
+ /// # Arguments
+ /// * `fd` - File descriptor to mmap from.
+ /// * `size` - Size of memory region in bytes.
+ /// * `offset` - Offset in bytes from the beginning of `fd` to start the mmap.
+ /// * `flags` - flags passed directly to mmap.
+ /// * `prot` - Protection (e.g. readable/writable) of the memory region.
+ fn from_fd_offset_flags(
+ fd: &dyn AsRawDescriptor,
+ size: usize,
+ offset: u64,
+ flags: c_int,
+ prot: Protection,
+ ) -> Result<MemoryMapping> {
+ unsafe {
+ // This is safe because we are creating an anonymous mapping in a place not already used
+ // by any other area in this process.
+ MemoryMapping::try_mmap(None, size, prot.into(), flags, Some((fd, offset)))
+ }
+ }
+
+ /// Maps the `size` bytes starting at `offset` bytes of the given `fd` as read/write.
+ ///
+ /// # Arguments
+ /// * `fd` - File descriptor to mmap from.
+ /// * `size` - Size of memory region in bytes.
+ /// * `offset` - Offset in bytes from the beginning of `fd` to start the mmap.
+ /// * `prot` - Protection (e.g. readable/writable) of the memory region.
+ pub fn from_fd_offset_protection(
+ fd: &dyn AsRawDescriptor,
+ size: usize,
+ offset: u64,
+ prot: Protection,
+ ) -> Result<MemoryMapping> {
+ MemoryMapping::from_fd_offset_flags(fd, size, offset, libc::MAP_SHARED, prot)
+ }
+
+ /// Maps `size` bytes starting at `offset` from the given `fd` as read/write, and requests
+ /// that the pages are pre-populated.
+ /// # Arguments
+ /// * `fd` - File descriptor to mmap from.
+ /// * `size` - Size of memory region in bytes.
+ /// * `offset` - Offset in bytes from the beginning of `fd` to start the mmap.
+ pub fn from_fd_offset_protection_populate(
+ fd: &dyn AsRawDescriptor,
+ size: usize,
+ offset: u64,
+ prot: Protection,
+ populate: bool,
+ ) -> Result<MemoryMapping> {
+ let mut flags = libc::MAP_SHARED;
+ if populate {
+ flags |= libc::MAP_POPULATE;
+ }
+ MemoryMapping::from_fd_offset_flags(fd, size, offset, flags, prot)
+ }
+
+ /// Creates an anonymous shared mapping of `size` bytes with `prot` protection.
+ ///
+ /// # Arguments
+ ///
+ /// * `addr` - Memory address to mmap at.
+ /// * `size` - Size of memory region in bytes.
+ /// * `prot` - Protection (e.g. readable/writable) of the memory region.
+ ///
+ /// # Safety
+ ///
+ /// This function should not be called before the caller unmaps any mmap'd regions already
+ /// present at `(addr..addr+size)`.
+ pub unsafe fn new_protection_fixed(
+ addr: *mut u8,
+ size: usize,
+ prot: Protection,
+ ) -> Result<MemoryMapping> {
+ MemoryMapping::try_mmap(
+ Some(addr),
+ size,
+ prot.into(),
+ libc::MAP_ANONYMOUS | libc::MAP_SHARED | libc::MAP_NORESERVE,
+ None,
+ )
+ }
+
+ /// Maps the `size` bytes starting at `offset` bytes of the given `fd` with
+ /// `prot` protections.
+ ///
+ /// # Arguments
+ ///
+ /// * `addr` - Memory address to mmap at.
+ /// * `fd` - File descriptor to mmap from.
+ /// * `size` - Size of memory region in bytes.
+ /// * `offset` - Offset in bytes from the beginning of `fd` to start the mmap.
+ /// * `prot` - Protection (e.g. readable/writable) of the memory region.
+ ///
+ /// # Safety
+ ///
+ /// This function should not be called before the caller unmaps any mmap'd regions already
+ /// present at `(addr..addr+size)`.
+ pub unsafe fn from_fd_offset_protection_fixed(
+ addr: *mut u8,
+ fd: &dyn AsRawDescriptor,
+ size: usize,
+ offset: u64,
+ prot: Protection,
+ ) -> Result<MemoryMapping> {
+ MemoryMapping::try_mmap(
+ Some(addr),
+ size,
+ prot.into(),
+ libc::MAP_SHARED | libc::MAP_NORESERVE,
+ Some((fd, offset)),
+ )
+ }
+
+ /// Helper wrapper around libc::mmap that does some basic validation, and calls
+ /// madvise with MADV_DONTDUMP on the created mmap
+ unsafe fn try_mmap(
+ addr: Option<*mut u8>,
+ size: usize,
+ prot: c_int,
+ flags: c_int,
+ fd: Option<(&dyn AsRawDescriptor, u64)>,
+ ) -> Result<MemoryMapping> {
+ let mut flags = flags;
+ // If addr is provided, set the FIXED flag, and validate addr alignment
+ let addr = match addr {
+ Some(addr) => {
+ if (addr as usize) % pagesize() != 0 {
+ return Err(Error::NotPageAligned);
+ }
+ flags |= libc::MAP_FIXED;
+ addr as *mut libc::c_void
+ }
+ None => null_mut(),
+ };
+ // If fd is provided, validate fd offset is within bounds
+ let (fd, offset) = match fd {
+ Some((fd, offset)) => {
+ if offset > libc::off_t::max_value() as u64 {
+ return Err(Error::InvalidOffset);
+ }
+ (fd.as_raw_descriptor(), offset as libc::off_t)
+ }
+ None => (-1, 0),
+ };
+ let addr = libc::mmap(addr, size, prot, flags, fd, offset);
+ if addr == libc::MAP_FAILED {
+ return Err(Error::SystemCallFailed(ErrnoError::last()));
+ }
+ // This is safe because we call madvise with a valid address and size.
+ let _ = libc::madvise(addr, size, libc::MADV_DONTDUMP);
+
+ Ok(MemoryMapping {
+ addr: addr as *mut u8,
+ size,
+ })
+ }
+
+ /// Madvise the kernel to use Huge Pages for this mapping.
+ pub fn use_hugepages(&self) -> Result<()> {
+ const SZ_2M: usize = 2 * 1024 * 1024;
+
+ // THP uses 2M pages, so use THP only on mappings that are at least
+ // 2M in size.
+ if self.size() < SZ_2M {
+ return Ok(());
+ }
+
+ // This is safe because we call madvise with a valid address and size, and we check the
+ // return value.
+ let ret = unsafe {
+ libc::madvise(
+ self.as_ptr() as *mut libc::c_void,
+ self.size(),
+ libc::MADV_HUGEPAGE,
+ )
+ };
+ if ret == -1 {
+ Err(Error::SystemCallFailed(ErrnoError::last()))
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Calls msync with MS_SYNC on the mapping.
+ pub fn msync(&self) -> Result<()> {
+ // This is safe since we use the exact address and length of a known
+ // good memory mapping.
+ let ret = unsafe {
+ libc::msync(
+ self.as_ptr() as *mut libc::c_void,
+ self.size(),
+ libc::MS_SYNC,
+ )
+ };
+ if ret == -1 {
+ return Err(Error::SystemCallFailed(ErrnoError::last()));
+ }
+ Ok(())
+ }
+
+ /// Writes a slice to the memory region at the specified offset.
+ /// Returns the number of bytes written. The number of bytes written can
+ /// be less than the length of the slice if there isn't enough room in the
+ /// memory region.
+ ///
+ /// # Examples
+ /// * Write a slice at offset 256.
+ ///
+ /// ```
+ /// # use crate::platform::MemoryMapping;
+ /// # let mut mem_map = MemoryMapping::new(1024).unwrap();
+ /// let res = mem_map.write_slice(&[1,2,3,4,5], 256);
+ /// assert!(res.is_ok());
+ /// assert_eq!(res.unwrap(), 5);
+ /// ```
+ pub fn write_slice(&self, buf: &[u8], offset: usize) -> Result<usize> {
+ match self.size.checked_sub(offset) {
+ Some(size_past_offset) => {
+ let bytes_copied = min(size_past_offset, buf.len());
+ // The bytes_copied equation above ensures we don't copy bytes out of range of
+ // either buf or this slice. We also know that the buffers do not overlap because
+ // slices can never occupy the same memory as a volatile slice.
+ unsafe {
+ copy_nonoverlapping(buf.as_ptr(), self.as_ptr().add(offset), bytes_copied);
+ }
+ Ok(bytes_copied)
+ }
+ None => Err(Error::InvalidAddress),
+ }
+ }
+
+ /// Reads to a slice from the memory region at the specified offset.
+ /// Returns the number of bytes read. The number of bytes read can
+ /// be less than the length of the slice if there isn't enough room in the
+ /// memory region.
+ ///
+ /// # Examples
+ /// * Read a slice of size 16 at offset 256.
+ ///
+ /// ```
+ /// # use crate::platform::MemoryMapping;
+ /// # let mut mem_map = MemoryMapping::new(1024).unwrap();
+ /// let buf = &mut [0u8; 16];
+ /// let res = mem_map.read_slice(buf, 256);
+ /// assert!(res.is_ok());
+ /// assert_eq!(res.unwrap(), 16);
+ /// ```
+ pub fn read_slice(&self, buf: &mut [u8], offset: usize) -> Result<usize> {
+ match self.size.checked_sub(offset) {
+ Some(size_past_offset) => {
+ let bytes_copied = min(size_past_offset, buf.len());
+ // The bytes_copied equation above ensures we don't copy bytes out of range of
+ // either buf or this slice. We also know that the buffers do not overlap because
+ // slices can never occupy the same memory as a volatile slice.
+ unsafe {
+ copy_nonoverlapping(
+ self.as_ptr().add(offset) as *const u8,
+ buf.as_mut_ptr(),
+ bytes_copied,
+ );
+ }
+ Ok(bytes_copied)
+ }
+ None => Err(Error::InvalidAddress),
+ }
+ }
+
+ /// Writes an object to the memory region at the specified offset.
+ /// Returns Ok(()) if the object fits, or Err if it extends past the end.
+ ///
+ /// # Examples
+ /// * Write a u64 at offset 16.
+ ///
+ /// ```
+ /// # use crate::platform::MemoryMapping;
+ /// # let mut mem_map = MemoryMapping::new(1024).unwrap();
+ /// let res = mem_map.write_obj(55u64, 16);
+ /// assert!(res.is_ok());
+ /// ```
+ pub fn write_obj<T: DataInit>(&self, val: T, offset: usize) -> Result<()> {
+ self.range_end(offset, size_of::<T>())?;
+ // This is safe because we checked the bounds above.
+ unsafe {
+ write_unaligned(self.as_ptr().add(offset) as *mut T, val);
+ }
+ Ok(())
+ }
+
+ /// Reads on object from the memory region at the given offset.
+ /// Reading from a volatile area isn't strictly safe as it could change
+ /// mid-read. However, as long as the type T is plain old data and can
+ /// handle random initialization, everything will be OK.
+ ///
+ /// # Examples
+ /// * Read a u64 written to offset 32.
+ ///
+ /// ```
+ /// # use crate::platform::MemoryMapping;
+ /// # let mut mem_map = MemoryMapping::new(1024).unwrap();
+ /// let res = mem_map.write_obj(55u64, 32);
+ /// assert!(res.is_ok());
+ /// let num: u64 = mem_map.read_obj(32).unwrap();
+ /// assert_eq!(55, num);
+ /// ```
+ pub fn read_obj<T: DataInit>(&self, offset: usize) -> Result<T> {
+ self.range_end(offset, size_of::<T>())?;
+ // This is safe because by definition Copy types can have their bits set arbitrarily and
+ // still be valid.
+ unsafe {
+ Ok(read_unaligned(
+ self.as_ptr().add(offset) as *const u8 as *const T
+ ))
+ }
+ }
+
+ /// Reads data from a file descriptor and writes it to guest memory.
+ ///
+ /// # Arguments
+ /// * `mem_offset` - Begin writing memory at this offset.
+ /// * `src` - Read from `src` to memory.
+ /// * `count` - Read `count` bytes from `src` to memory.
+ ///
+ /// # Examples
+ ///
+ /// * Read bytes from /dev/urandom
+ ///
+ /// ```
+ /// # use crate::platform::MemoryMapping;
+ /// # use std::fs::File;
+ /// # use std::path::Path;
+ /// # fn test_read_random() -> Result<u32, ()> {
+ /// # let mut mem_map = MemoryMapping::new(1024).unwrap();
+ /// let mut file = File::open(Path::new("/dev/urandom")).map_err(|_| ())?;
+ /// mem_map.read_to_memory(32, &mut file, 128).map_err(|_| ())?;
+ /// let rand_val: u32 = mem_map.read_obj(40).map_err(|_| ())?;
+ /// # Ok(rand_val)
+ /// # }
+ /// ```
+ pub fn read_to_memory(
+ &self,
+ mut mem_offset: usize,
+ src: &dyn AsRawDescriptor,
+ mut count: usize,
+ ) -> Result<()> {
+ self.range_end(mem_offset, count)
+ .map_err(|_| Error::InvalidRange(mem_offset, count, self.size()))?;
+ while count > 0 {
+ // The check above ensures that no memory outside this slice will get accessed by this
+ // read call.
+ match unsafe {
+ read(
+ src.as_raw_descriptor(),
+ self.as_ptr().add(mem_offset) as *mut c_void,
+ count,
+ )
+ } {
+ 0 => {
+ return Err(Error::ReadToMemory(io::Error::from(
+ io::ErrorKind::UnexpectedEof,
+ )))
+ }
+ r if r < 0 => return Err(Error::ReadToMemory(io::Error::last_os_error())),
+ ret => {
+ let bytes_read = ret as usize;
+ match count.checked_sub(bytes_read) {
+ Some(count_remaining) => count = count_remaining,
+ None => break,
+ }
+ mem_offset += ret as usize;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Writes data from memory to a file descriptor.
+ ///
+ /// # Arguments
+ /// * `mem_offset` - Begin reading memory from this offset.
+ /// * `dst` - Write from memory to `dst`.
+ /// * `count` - Read `count` bytes from memory to `src`.
+ ///
+ /// # Examples
+ ///
+ /// * Write 128 bytes to /dev/null
+ ///
+ /// ```
+ /// # use crate::platform::MemoryMapping;
+ /// # use std::fs::File;
+ /// # use std::path::Path;
+ /// # fn test_write_null() -> Result<(), ()> {
+ /// # let mut mem_map = MemoryMapping::new(1024).unwrap();
+ /// let mut file = File::open(Path::new("/dev/null")).map_err(|_| ())?;
+ /// mem_map.write_from_memory(32, &mut file, 128).map_err(|_| ())?;
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn write_from_memory(
+ &self,
+ mut mem_offset: usize,
+ dst: &dyn AsRawDescriptor,
+ mut count: usize,
+ ) -> Result<()> {
+ self.range_end(mem_offset, count)
+ .map_err(|_| Error::InvalidRange(mem_offset, count, self.size()))?;
+ while count > 0 {
+ // The check above ensures that no memory outside this slice will get accessed by this
+ // write call.
+ match unsafe {
+ write(
+ dst.as_raw_descriptor(),
+ self.as_ptr().add(mem_offset) as *const c_void,
+ count,
+ )
+ } {
+ 0 => {
+ return Err(Error::WriteFromMemory(io::Error::from(
+ io::ErrorKind::WriteZero,
+ )))
+ }
+ ret if ret < 0 => return Err(Error::WriteFromMemory(io::Error::last_os_error())),
+ ret => {
+ let bytes_written = ret as usize;
+ match count.checked_sub(bytes_written) {
+ Some(count_remaining) => count = count_remaining,
+ None => break,
+ }
+ mem_offset += ret as usize;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Uses madvise to tell the kernel to remove the specified range. Subsequent reads
+ /// to the pages in the range will return zero bytes.
+ pub fn remove_range(&self, mem_offset: usize, count: usize) -> Result<()> {
+ self.range_end(mem_offset, count)
+ .map_err(|_| Error::InvalidRange(mem_offset, count, self.size()))?;
+ let ret = unsafe {
+ // madvising away the region is the same as the guest changing it.
+ // Next time it is read, it may return zero pages.
+ libc::madvise(
+ (self.addr as usize + mem_offset) as *mut _,
+ count,
+ libc::MADV_REMOVE,
+ )
+ };
+ if ret < 0 {
+ Err(Error::InvalidRange(mem_offset, count, self.size()))
+ } else {
+ Ok(())
+ }
+ }
+
+ // Check that offset+count is valid and return the sum.
+ fn range_end(&self, offset: usize, count: usize) -> Result<usize> {
+ let mem_end = offset.checked_add(count).ok_or(Error::InvalidAddress)?;
+ if mem_end > self.size() {
+ return Err(Error::InvalidAddress);
+ }
+ Ok(mem_end)
+ }
+}
+
+// Safe because the pointer and size point to a memory range owned by this MemoryMapping that won't
+// be unmapped until it's Dropped.
+unsafe impl MappedRegion for MemoryMapping {
+ fn as_ptr(&self) -> *mut u8 {
+ self.addr
+ }
+
+ fn size(&self) -> usize {
+ self.size
+ }
+}
+
+impl VolatileMemory for MemoryMapping {
+ fn get_slice(&self, offset: usize, count: usize) -> VolatileMemoryResult<VolatileSlice> {
+ let mem_end = calc_offset(offset, count)?;
+ if mem_end > self.size {
+ return Err(VolatileMemoryError::OutOfBounds { addr: mem_end });
+ }
+
+ let new_addr =
+ (self.as_ptr() as usize)
+ .checked_add(offset)
+ .ok_or(VolatileMemoryError::Overflow {
+ base: self.as_ptr() as usize,
+ offset,
+ })?;
+
+ // Safe because we checked that offset + count was within our range and we only ever hand
+ // out volatile accessors.
+ Ok(unsafe { VolatileSlice::from_raw_parts(new_addr as *mut u8, count) })
+ }
+}
+
+impl Drop for MemoryMapping {
+ fn drop(&mut self) {
+ // This is safe because we mmap the area at addr ourselves, and nobody
+ // else is holding a reference to it.
+ unsafe {
+ libc::munmap(self.addr as *mut libc::c_void, self.size);
+ }
+ }
+}
+
+/// Tracks Fixed Memory Maps within an anonymous memory-mapped fixed-sized arena
+/// in the current process.
+pub struct MemoryMappingArena {
+ addr: *mut u8,
+ size: usize,
+}
+
+// Send and Sync aren't automatically inherited for the raw address pointer.
+// Accessing that pointer is only done through the stateless interface which
+// allows the object to be shared by multiple threads without a decrease in
+// safety.
+unsafe impl Send for MemoryMappingArena {}
+unsafe impl Sync for MemoryMappingArena {}
+
+impl MemoryMappingArena {
+ /// Creates an mmap arena of `size` bytes.
+ ///
+ /// # Arguments
+ /// * `size` - Size of memory region in bytes.
+ pub fn new(size: usize) -> Result<MemoryMappingArena> {
+ // Reserve the arena's memory using an anonymous read-only mmap.
+ MemoryMapping::new_protection(size, Protection::none().set_read()).map(From::from)
+ }
+
+ /// Anonymously maps `size` bytes at `offset` bytes from the start of the arena
+ /// with `prot` protections. `offset` must be page aligned.
+ ///
+ /// # Arguments
+ /// * `offset` - Page aligned offset into the arena in bytes.
+ /// * `size` - Size of memory region in bytes.
+ /// * `prot` - Protection (e.g. readable/writable) of the memory region.
+ pub fn add_anon_protection(
+ &mut self,
+ offset: usize,
+ size: usize,
+ prot: Protection,
+ ) -> Result<()> {
+ self.try_add(offset, size, prot, None)
+ }
+
+ /// Anonymously maps `size` bytes at `offset` bytes from the start of the arena.
+ /// `offset` must be page aligned.
+ ///
+ /// # Arguments
+ /// * `offset` - Page aligned offset into the arena in bytes.
+ /// * `size` - Size of memory region in bytes.
+ pub fn add_anon(&mut self, offset: usize, size: usize) -> Result<()> {
+ self.add_anon_protection(offset, size, Protection::read_write())
+ }
+
+ /// Maps `size` bytes from the start of the given `fd` at `offset` bytes from
+ /// the start of the arena. `offset` must be page aligned.
+ ///
+ /// # Arguments
+ /// * `offset` - Page aligned offset into the arena in bytes.
+ /// * `size` - Size of memory region in bytes.
+ /// * `fd` - File descriptor to mmap from.
+ pub fn add_fd(&mut self, offset: usize, size: usize, fd: &dyn AsRawDescriptor) -> Result<()> {
+ self.add_fd_offset(offset, size, fd, 0)
+ }
+
+ /// Maps `size` bytes starting at `fs_offset` bytes from within the given `fd`
+ /// at `offset` bytes from the start of the arena. `offset` must be page aligned.
+ ///
+ /// # Arguments
+ /// * `offset` - Page aligned offset into the arena in bytes.
+ /// * `size` - Size of memory region in bytes.
+ /// * `fd` - File descriptor to mmap from.
+ /// * `fd_offset` - Offset in bytes from the beginning of `fd` to start the mmap.
+ pub fn add_fd_offset(
+ &mut self,
+ offset: usize,
+ size: usize,
+ fd: &dyn AsRawDescriptor,
+ fd_offset: u64,
+ ) -> Result<()> {
+ self.add_fd_offset_protection(offset, size, fd, fd_offset, Protection::read_write())
+ }
+
+ /// Maps `size` bytes starting at `fs_offset` bytes from within the given `fd`
+ /// at `offset` bytes from the start of the arena with `prot` protections.
+ /// `offset` must be page aligned.
+ ///
+ /// # Arguments
+ /// * `offset` - Page aligned offset into the arena in bytes.
+ /// * `size` - Size of memory region in bytes.
+ /// * `fd` - File descriptor to mmap from.
+ /// * `fd_offset` - Offset in bytes from the beginning of `fd` to start the mmap.
+ /// * `prot` - Protection (e.g. readable/writable) of the memory region.
+ pub fn add_fd_offset_protection(
+ &mut self,
+ offset: usize,
+ size: usize,
+ fd: &dyn AsRawDescriptor,
+ fd_offset: u64,
+ prot: Protection,
+ ) -> Result<()> {
+ self.try_add(offset, size, prot, Some((fd, fd_offset)))
+ }
+
+ /// Helper method that calls appropriate MemoryMapping constructor and adds
+ /// the resulting map into the arena.
+ fn try_add(
+ &mut self,
+ offset: usize,
+ size: usize,
+ prot: Protection,
+ fd: Option<(&dyn AsRawDescriptor, u64)>,
+ ) -> Result<()> {
+ // Ensure offset is page-aligned
+ if offset % pagesize() != 0 {
+ return Err(Error::NotPageAligned);
+ }
+ validate_includes_range(self.size(), offset, size)?;
+
+ // This is safe since the range has been validated.
+ let mmap = unsafe {
+ match fd {
+ Some((fd, fd_offset)) => MemoryMapping::from_fd_offset_protection_fixed(
+ self.addr.add(offset),
+ fd,
+ size,
+ fd_offset,
+ prot,
+ )?,
+ None => MemoryMapping::new_protection_fixed(self.addr.add(offset), size, prot)?,
+ }
+ };
+
+ // This mapping will get automatically removed when we drop the whole arena.
+ std::mem::forget(mmap);
+ Ok(())
+ }
+
+ /// Removes `size` bytes at `offset` bytes from the start of the arena. `offset` must be page
+ /// aligned.
+ ///
+ /// # Arguments
+ /// * `offset` - Page aligned offset into the arena in bytes.
+ /// * `size` - Size of memory region in bytes.
+ pub fn remove(&mut self, offset: usize, size: usize) -> Result<()> {
+ self.try_add(offset, size, Protection::read(), None)
+ }
+}
+
+// Safe because the pointer and size point to a memory range owned by this MemoryMappingArena that
+// won't be unmapped until it's Dropped.
+unsafe impl MappedRegion for MemoryMappingArena {
+ fn as_ptr(&self) -> *mut u8 {
+ self.addr
+ }
+
+ fn size(&self) -> usize {
+ self.size
+ }
+
+ fn add_fd_mapping(
+ &mut self,
+ offset: usize,
+ size: usize,
+ fd: &dyn AsRawDescriptor,
+ fd_offset: u64,
+ prot: Protection,
+ ) -> Result<()> {
+ self.add_fd_offset_protection(offset, size, fd, fd_offset, prot)
+ }
+
+ fn remove_mapping(&mut self, offset: usize, size: usize) -> Result<()> {
+ self.remove(offset, size)
+ }
+}
+
+impl From<MemoryMapping> for MemoryMappingArena {
+ fn from(mmap: MemoryMapping) -> Self {
+ let addr = mmap.as_ptr();
+ let size = mmap.size();
+
+ // Forget the original mapping because the `MemoryMappingArena` will take care of calling
+ // `munmap` when it is dropped.
+ std::mem::forget(mmap);
+ MemoryMappingArena { addr, size }
+ }
+}
+
+impl Drop for MemoryMappingArena {
+ fn drop(&mut self) {
+ // This is safe because we own this memory range, and nobody else is holding a reference to
+ // it.
+ unsafe {
+ libc::munmap(self.addr as *mut libc::c_void, self.size);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::descriptor::Descriptor;
+ use data_model::{VolatileMemory, VolatileMemoryError};
+ use tempfile::tempfile;
+
+ #[test]
+ fn basic_map() {
+ let m = MemoryMapping::new(1024).unwrap();
+ assert_eq!(1024, m.size());
+ }
+
+ #[test]
+ fn map_invalid_size() {
+ let res = MemoryMapping::new(0).unwrap_err();
+ if let Error::SystemCallFailed(e) = res {
+ assert_eq!(e.errno(), libc::EINVAL);
+ } else {
+ panic!("unexpected error: {}", res);
+ }
+ }
+
+ #[test]
+ fn map_invalid_fd() {
+ let fd = Descriptor(-1);
+ let res = MemoryMapping::from_fd(&fd, 1024).unwrap_err();
+ if let Error::SystemCallFailed(e) = res {
+ assert_eq!(e.errno(), libc::EBADF);
+ } else {
+ panic!("unexpected error: {}", res);
+ }
+ }
+
+ #[test]
+ fn test_write_past_end() {
+ let m = MemoryMapping::new(5).unwrap();
+ let res = m.write_slice(&[1, 2, 3, 4, 5, 6], 0);
+ assert!(res.is_ok());
+ assert_eq!(res.unwrap(), 5);
+ }
+
+ #[test]
+ fn slice_size() {
+ let m = MemoryMapping::new(5).unwrap();
+ let s = m.get_slice(2, 3).unwrap();
+ assert_eq!(s.size(), 3);
+ }
+
+ #[test]
+ fn slice_addr() {
+ let m = MemoryMapping::new(5).unwrap();
+ let s = m.get_slice(2, 3).unwrap();
+ assert_eq!(s.as_ptr(), unsafe { m.as_ptr().offset(2) });
+ }
+
+ #[test]
+ fn slice_store() {
+ let m = MemoryMapping::new(5).unwrap();
+ let r = m.get_ref(2).unwrap();
+ r.store(9u16);
+ assert_eq!(m.read_obj::<u16>(2).unwrap(), 9);
+ }
+
+ #[test]
+ fn slice_overflow_error() {
+ let m = MemoryMapping::new(5).unwrap();
+ let res = m.get_slice(std::usize::MAX, 3).unwrap_err();
+ assert_eq!(
+ res,
+ VolatileMemoryError::Overflow {
+ base: std::usize::MAX,
+ offset: 3,
+ }
+ );
+ }
+ #[test]
+ fn slice_oob_error() {
+ let m = MemoryMapping::new(5).unwrap();
+ let res = m.get_slice(3, 3).unwrap_err();
+ assert_eq!(res, VolatileMemoryError::OutOfBounds { addr: 6 });
+ }
+
+ #[test]
+ fn from_fd_offset_invalid() {
+ let fd = tempfile().unwrap();
+ let res = MemoryMapping::from_fd_offset(&fd, 4096, (libc::off_t::max_value() as u64) + 1)
+ .unwrap_err();
+ match res {
+ Error::InvalidOffset => {}
+ e => panic!("unexpected error: {}", e),
+ }
+ }
+
+ #[test]
+ fn arena_new() {
+ let m = MemoryMappingArena::new(0x40000).unwrap();
+ assert_eq!(m.size(), 0x40000);
+ }
+
+ #[test]
+ fn arena_add() {
+ let mut m = MemoryMappingArena::new(0x40000).unwrap();
+ assert!(m.add_anon(0, pagesize() * 4).is_ok());
+ }
+
+ #[test]
+ fn arena_remove() {
+ let mut m = MemoryMappingArena::new(0x40000).unwrap();
+ assert!(m.add_anon(0, pagesize() * 4).is_ok());
+ assert!(m.remove(0, pagesize()).is_ok());
+ assert!(m.remove(0, pagesize() * 2).is_ok());
+ }
+
+ #[test]
+ fn arena_add_alignment_error() {
+ let mut m = MemoryMappingArena::new(pagesize() * 2).unwrap();
+ assert!(m.add_anon(0, 0x100).is_ok());
+ let res = m.add_anon(pagesize() + 1, 0x100).unwrap_err();
+ match res {
+ Error::NotPageAligned => {}
+ e => panic!("unexpected error: {}", e),
+ }
+ }
+
+ #[test]
+ fn arena_add_oob_error() {
+ let mut m = MemoryMappingArena::new(pagesize()).unwrap();
+ let res = m.add_anon(0, pagesize() + 1).unwrap_err();
+ match res {
+ Error::InvalidAddress => {}
+ e => panic!("unexpected error: {}", e),
+ }
+ }
+
+ #[test]
+ fn arena_add_overlapping() {
+ let ps = pagesize();
+ let mut m =
+ MemoryMappingArena::new(12 * ps).expect("failed to create `MemoryMappingArena`");
+ m.add_anon(ps * 4, ps * 4)
+ .expect("failed to add sub-mapping");
+
+ // Overlap in the front.
+ m.add_anon(ps * 2, ps * 3)
+ .expect("failed to add front overlapping sub-mapping");
+
+ // Overlap in the back.
+ m.add_anon(ps * 7, ps * 3)
+ .expect("failed to add back overlapping sub-mapping");
+
+ // Overlap the back of the first mapping, all of the middle mapping, and the front of the
+ // last mapping.
+ m.add_anon(ps * 3, ps * 6)
+ .expect("failed to add mapping that overlaps several mappings");
+ }
+
+ #[test]
+ fn arena_remove_overlapping() {
+ let ps = pagesize();
+ let mut m =
+ MemoryMappingArena::new(12 * ps).expect("failed to create `MemoryMappingArena`");
+ m.add_anon(ps * 4, ps * 4)
+ .expect("failed to add sub-mapping");
+ m.add_anon(ps * 2, ps * 2)
+ .expect("failed to add front overlapping sub-mapping");
+ m.add_anon(ps * 8, ps * 2)
+ .expect("failed to add back overlapping sub-mapping");
+
+ // Remove the back of the first mapping and the front of the second.
+ m.remove(ps * 3, ps * 2)
+ .expect("failed to remove front overlapping mapping");
+
+ // Remove the back of the second mapping and the front of the third.
+ m.remove(ps * 7, ps * 2)
+ .expect("failed to remove back overlapping mapping");
+
+ // Remove a mapping that completely overlaps the middle mapping.
+ m.remove(ps * 5, ps * 2)
+ .expect("failed to remove fully overlapping mapping");
+ }
+
+ #[test]
+ fn arena_remove_unaligned() {
+ let ps = pagesize();
+ let mut m =
+ MemoryMappingArena::new(12 * ps).expect("failed to create `MemoryMappingArena`");
+
+ m.add_anon(0, ps).expect("failed to add mapping");
+ m.remove(0, ps - 1)
+ .expect("failed to remove unaligned mapping");
+ }
+
+ #[test]
+ fn arena_msync() {
+ let size = 0x40000;
+ let m = MemoryMappingArena::new(size).unwrap();
+ let ps = pagesize();
+ <dyn MappedRegion>::msync(&m, 0, ps).unwrap();
+ <dyn MappedRegion>::msync(&m, 0, size).unwrap();
+ <dyn MappedRegion>::msync(&m, ps, size - ps).unwrap();
+ let res = <dyn MappedRegion>::msync(&m, ps, size).unwrap_err();
+ match res {
+ Error::InvalidAddress => {}
+ e => panic!("unexpected error: {}", e),
+ }
+ }
+}
diff --git a/base/src/unix/mod.rs b/base/src/unix/mod.rs
new file mode 100644
index 000000000..16576e0f0
--- /dev/null
+++ b/base/src/unix/mod.rs
@@ -0,0 +1,685 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Small system utility modules for usage by other modules.
+
+// Fail sys_util compilation on windows.
+// This will make any unintentional windows code submitted to the crate unusable.
+#[cfg(windows)]
+compile_error!("sys_util is not windows friendly crate. See/use win_sys_util.");
+
+#[cfg(target_os = "android")]
+mod android;
+#[cfg(target_os = "android")]
+use android as target_os;
+#[cfg(target_os = "linux")]
+mod linux;
+#[cfg(target_os = "linux")]
+use linux as target_os;
+#[macro_use]
+pub mod handle_eintr;
+#[macro_use]
+pub mod ioctl;
+#[macro_use]
+pub mod syslog;
+mod acpi_event;
+mod capabilities;
+mod clock;
+mod descriptor;
+mod eventfd;
+mod file_flags;
+pub mod file_traits;
+mod get_filesystem_type;
+mod mmap;
+pub mod net;
+mod netlink;
+mod poll;
+mod priority;
+pub mod rand;
+mod raw_fd;
+pub mod read_dir;
+mod sched;
+pub mod scoped_signal_handler;
+mod shm;
+pub mod signal;
+mod signalfd;
+mod sock_ctrl_msg;
+mod terminal;
+mod timerfd;
+pub mod vsock;
+mod write_zeroes;
+
+pub use crate::descriptor_reflection::{
+ deserialize_with_descriptors, with_as_descriptor, with_raw_descriptor, FileSerdeWrapper,
+ SerializeDescriptors,
+};
+pub use crate::{
+ errno::{Error, Result, *},
+ generate_scoped_event,
+};
+pub use acpi_event::*;
+pub use base_poll_token_derive::*;
+pub use capabilities::drop_capabilities;
+pub use clock::{Clock, FakeClock};
+pub use descriptor::*;
+pub use eventfd::*;
+pub use file_flags::*;
+pub use get_filesystem_type::*;
+pub use ioctl::*;
+pub use mmap::*;
+pub use netlink::*;
+pub use poll::*;
+pub use priority::*;
+pub use raw_fd::*;
+pub use sched::*;
+pub use scoped_signal_handler::*;
+pub use shm::*;
+pub use signal::*;
+pub use signalfd::*;
+pub use sock_ctrl_msg::*;
+pub use terminal::*;
+pub use timerfd::*;
+
+use crate::descriptor::{FromRawDescriptor, SafeDescriptor};
+pub use file_traits::{
+ AsRawFds, FileAllocate, FileGetLen, FileReadWriteAtVolatile, FileReadWriteVolatile, FileSetLen,
+ FileSync,
+};
+pub use mmap::Error as MmapError;
+pub use signalfd::Error as SignalFdError;
+pub use write_zeroes::{PunchHole, WriteZeroes, WriteZeroesAt};
+
+use std::{
+ cell::Cell,
+ convert::TryFrom,
+ ffi::CStr,
+ fs::{remove_file, File, OpenOptions},
+ mem,
+ ops::Deref,
+ os::unix::{
+ io::{AsRawFd, FromRawFd, RawFd},
+ net::{UnixDatagram, UnixListener},
+ },
+ path::Path,
+ ptr,
+ time::Duration,
+};
+
+use libc::{
+ c_int, c_long, fcntl, pipe2, syscall, sysconf, waitpid, SYS_getpid, SYS_gettid, EINVAL,
+ F_GETFL, F_SETFL, O_CLOEXEC, SIGKILL, WNOHANG, _SC_IOV_MAX, _SC_PAGESIZE,
+};
+
+/// Re-export libc types that are part of the API.
+pub type Pid = libc::pid_t;
+pub type Uid = libc::uid_t;
+pub type Gid = libc::gid_t;
+pub type Mode = libc::mode_t;
+
+/// Used to mark types as !Sync.
+pub type UnsyncMarker = std::marker::PhantomData<Cell<usize>>;
+
+#[macro_export]
+macro_rules! syscall {
+ ($e:expr) => {{
+ let res = $e;
+ if res < 0 {
+ $crate::platform::errno_result()
+ } else {
+ Ok(res)
+ }
+ }};
+}
+
+/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
+#[inline(always)]
+pub fn pagesize() -> usize {
+ // Trivially safe
+ unsafe { sysconf(_SC_PAGESIZE) as usize }
+}
+
+/// Safe wrapper for `sysconf(_SC_IOV_MAX)`.
+pub fn iov_max() -> usize {
+ // Trivially safe
+ unsafe { sysconf(_SC_IOV_MAX) as usize }
+}
+
+/// Uses the system's page size in bytes to round the given value up to the nearest page boundary.
+#[inline(always)]
+pub fn round_up_to_page_size(v: usize) -> usize {
+ let page_mask = pagesize() - 1;
+ (v + page_mask) & !page_mask
+}
+
+/// This bypasses `libc`'s caching `getpid(2)` wrapper which can be invalid if a raw clone was used
+/// elsewhere.
+#[inline(always)]
+pub fn getpid() -> Pid {
+ // Safe because this syscall can never fail and we give it a valid syscall number.
+ unsafe { syscall(SYS_getpid as c_long) as Pid }
+}
+
+/// Safe wrapper for the gettid Linux systemcall.
+pub fn gettid() -> Pid {
+ // Calling the gettid() sycall is always safe.
+ unsafe { syscall(SYS_gettid as c_long) as Pid }
+}
+
+/// Safe wrapper for `getsid(2)`.
+pub fn getsid(pid: Option<Pid>) -> Result<Pid> {
+ // Calling the getsid() sycall is always safe.
+ syscall!(unsafe { libc::getsid(pid.unwrap_or(0)) } as Pid)
+}
+
+/// Wrapper for `setsid(2)`.
+pub fn setsid() -> Result<Pid> {
+ // Safe because the return code is checked.
+ syscall!(unsafe { libc::setsid() as Pid })
+}
+
+/// Safe wrapper for `geteuid(2)`.
+#[inline(always)]
+pub fn geteuid() -> Uid {
+ // trivially safe
+ unsafe { libc::geteuid() }
+}
+
+/// Safe wrapper for `getegid(2)`.
+#[inline(always)]
+pub fn getegid() -> Gid {
+ // trivially safe
+ unsafe { libc::getegid() }
+}
+
+/// Safe wrapper for chown(2).
+#[inline(always)]
+pub fn chown(path: &CStr, uid: Uid, gid: Gid) -> Result<()> {
+ // Safe since we pass in a valid string pointer and check the return value.
+ syscall!(unsafe { libc::chown(path.as_ptr(), uid, gid) }).map(|_| ())
+}
+
+/// Safe wrapper for fchmod(2).
+#[inline(always)]
+pub fn fchmod<A: AsRawFd>(fd: &A, mode: Mode) -> Result<()> {
+ // Safe since the function does not operate on pointers and check the return value.
+ syscall!(unsafe { libc::fchmod(fd.as_raw_fd(), mode) }).map(|_| ())
+}
+
+/// Safe wrapper for fchown(2).
+#[inline(always)]
+pub fn fchown<A: AsRawFd>(fd: &A, uid: Uid, gid: Gid) -> Result<()> {
+ // Safe since the function does not operate on pointers and check the return value.
+ syscall!(unsafe { libc::fchown(fd.as_raw_fd(), uid, gid) }).map(|_| ())
+}
+
+/// The operation to perform with `flock`.
+pub enum FlockOperation {
+ LockShared,
+ LockExclusive,
+ Unlock,
+}
+
+/// Safe wrapper for flock(2) with the operation `op` and optionally `nonblocking`. The lock will be
+/// dropped automatically when `file` is dropped.
+#[inline(always)]
+pub fn flock(file: &dyn AsRawFd, op: FlockOperation, nonblocking: bool) -> Result<()> {
+ let mut operation = match op {
+ FlockOperation::LockShared => libc::LOCK_SH,
+ FlockOperation::LockExclusive => libc::LOCK_EX,
+ FlockOperation::Unlock => libc::LOCK_UN,
+ };
+
+ if nonblocking {
+ operation |= libc::LOCK_NB;
+ }
+
+ // Safe since we pass in a valid fd and flock operation, and check the return value.
+ syscall!(unsafe { libc::flock(file.as_raw_fd(), operation) }).map(|_| ())
+}
+
+/// The operation to perform with `fallocate`.
+pub enum FallocateMode {
+ PunchHole,
+ ZeroRange,
+ Allocate,
+}
+
+/// Safe wrapper for `fallocate()`.
+pub fn fallocate(
+ file: &dyn AsRawFd,
+ mode: FallocateMode,
+ keep_size: bool,
+ offset: u64,
+ len: u64,
+) -> Result<()> {
+ let offset = if offset > libc::off64_t::max_value() as u64 {
+ return Err(Error::new(libc::EINVAL));
+ } else {
+ offset as libc::off64_t
+ };
+
+ let len = if len > libc::off64_t::max_value() as u64 {
+ return Err(Error::new(libc::EINVAL));
+ } else {
+ len as libc::off64_t
+ };
+
+ let mut mode = match mode {
+ FallocateMode::PunchHole => libc::FALLOC_FL_PUNCH_HOLE,
+ FallocateMode::ZeroRange => libc::FALLOC_FL_ZERO_RANGE,
+ FallocateMode::Allocate => 0,
+ };
+
+ if keep_size {
+ mode |= libc::FALLOC_FL_KEEP_SIZE;
+ }
+
+ // Safe since we pass in a valid fd and fallocate mode, validate offset and len,
+ // and check the return value.
+ syscall!(unsafe { libc::fallocate64(file.as_raw_fd(), mode, offset, len) }).map(|_| ())
+}
+
+/// A trait used to abstract types that provide a process id that can be operated on.
+pub trait AsRawPid {
+ fn as_raw_pid(&self) -> Pid;
+}
+
+impl AsRawPid for Pid {
+ fn as_raw_pid(&self) -> Pid {
+ *self
+ }
+}
+
+impl AsRawPid for std::process::Child {
+ fn as_raw_pid(&self) -> Pid {
+ self.id() as Pid
+ }
+}
+
+/// A logical set of the values *status can take from libc::wait and libc::waitpid.
+pub enum WaitStatus {
+ Continued,
+ Exited(u8),
+ Running,
+ Signaled(Signal),
+ Stopped(Signal),
+}
+
+impl From<c_int> for WaitStatus {
+ fn from(status: c_int) -> WaitStatus {
+ use WaitStatus::*;
+ if libc::WIFEXITED(status) {
+ Exited(libc::WEXITSTATUS(status) as u8)
+ } else if libc::WIFSIGNALED(status) {
+ Signaled(Signal::try_from(libc::WTERMSIG(status)).unwrap())
+ } else if libc::WIFSTOPPED(status) {
+ Stopped(Signal::try_from(libc::WSTOPSIG(status)).unwrap())
+ } else if libc::WIFCONTINUED(status) {
+ Continued
+ } else {
+ Running
+ }
+ }
+}
+
+/// A safe wrapper around waitpid.
+///
+/// On success if a process was reaped, it will be returned as the first value.
+/// The second returned value is the WaitStatus from the libc::waitpid() call.
+///
+/// Note: this can block if libc::WNOHANG is not set and EINTR is not handled internally.
+pub fn wait_for_pid<A: AsRawPid>(pid: A, options: c_int) -> Result<(Option<Pid>, WaitStatus)> {
+ let pid = pid.as_raw_pid();
+ let mut status: c_int = 1;
+ // Safe because status is owned and the error is checked.
+ let ret = unsafe { libc::waitpid(pid, &mut status, options) };
+ if ret < 0 {
+ return errno_result();
+ }
+ Ok((
+ if ret == 0 { None } else { Some(ret) },
+ WaitStatus::from(status),
+ ))
+}
+
+/// Reaps a child process that has terminated.
+///
+/// Returns `Ok(pid)` where `pid` is the process that was reaped or `Ok(0)` if none of the children
+/// have terminated. An `Error` is with `errno == ECHILD` if there are no children left to reap.
+///
+/// # Examples
+///
+/// Reaps all child processes until there are no terminated children to reap.
+///
+/// ```
+/// fn reap_children() {
+/// loop {
+/// match crate::platform::reap_child() {
+/// Ok(0) => println!("no children ready to reap"),
+/// Ok(pid) => {
+/// println!("reaped {}", pid);
+/// continue
+/// },
+/// Err(e) if e.errno() == libc::ECHILD => println!("no children left"),
+/// Err(e) => println!("error reaping children: {}", e),
+/// }
+/// break
+/// }
+/// }
+/// ```
+pub fn reap_child() -> Result<Pid> {
+ // Safe because we pass in no memory, prevent blocking with WNOHANG, and check for error.
+ let ret = unsafe { waitpid(-1, ptr::null_mut(), WNOHANG) };
+ if ret == -1 {
+ errno_result()
+ } else {
+ Ok(ret)
+ }
+}
+
+/// Kill all processes in the current process group.
+///
+/// On success, this kills all processes in the current process group, including the current
+/// process, meaning this will not return. This is equivalent to a call to `kill(0, SIGKILL)`.
+pub fn kill_process_group() -> Result<()> {
+ unsafe { kill(0, SIGKILL) }?;
+ // Kill succeeded, so this process never reaches here.
+ unreachable!();
+}
+
+/// Spawns a pipe pair where the first pipe is the read end and the second pipe is the write end.
+///
+/// If `close_on_exec` is true, the `O_CLOEXEC` flag will be set during pipe creation.
+pub fn pipe(close_on_exec: bool) -> Result<(File, File)> {
+ let flags = if close_on_exec { O_CLOEXEC } else { 0 };
+ let mut pipe_fds = [-1; 2];
+ // Safe because pipe2 will only write 2 element array of i32 to the given pointer, and we check
+ // for error.
+ let ret = unsafe { pipe2(&mut pipe_fds[0], flags) };
+ if ret == -1 {
+ errno_result()
+ } else {
+ // Safe because both fds must be valid for pipe2 to have returned sucessfully and we have
+ // exclusive ownership of them.
+ Ok(unsafe {
+ (
+ File::from_raw_fd(pipe_fds[0]),
+ File::from_raw_fd(pipe_fds[1]),
+ )
+ })
+ }
+}
+
+/// Sets the pipe signified with fd to `size`.
+///
+/// Returns the new size of the pipe or an error if the OS fails to set the pipe size.
+pub fn set_pipe_size(fd: RawFd, size: usize) -> Result<usize> {
+ // Safe because fcntl with the `F_SETPIPE_SZ` arg doesn't touch memory.
+ syscall!(unsafe { fcntl(fd, libc::F_SETPIPE_SZ, size as c_int) }).map(|ret| ret as usize)
+}
+
+/// Test-only function used to create a pipe that is full. The pipe is created, has its size set to
+/// the minimum and then has that much data written to it. Use `new_pipe_full` to test handling of
+/// blocking `write` calls in unit tests.
+pub fn new_pipe_full() -> Result<(File, File)> {
+ use std::io::Write;
+
+ let (rx, mut tx) = pipe(true)?;
+ // The smallest allowed size of a pipe is the system page size on linux.
+ let page_size = set_pipe_size(tx.as_raw_fd(), round_up_to_page_size(1))?;
+
+ // Fill the pipe with page_size zeros so the next write call will block.
+ let buf = vec![0u8; page_size];
+ tx.write_all(&buf)?;
+
+ Ok((rx, tx))
+}
+
+/// Used to attempt to clean up a named pipe after it is no longer used.
+pub struct UnlinkUnixDatagram(pub UnixDatagram);
+impl AsRef<UnixDatagram> for UnlinkUnixDatagram {
+ fn as_ref(&self) -> &UnixDatagram {
+ &self.0
+ }
+}
+impl Drop for UnlinkUnixDatagram {
+ fn drop(&mut self) {
+ if let Ok(addr) = self.0.local_addr() {
+ if let Some(path) = addr.as_pathname() {
+ if let Err(e) = remove_file(path) {
+ warn!("failed to remove control socket file: {}", e);
+ }
+ }
+ }
+ }
+}
+
+/// Used to attempt to clean up a named pipe after it is no longer used.
+pub struct UnlinkUnixListener(pub UnixListener);
+
+impl AsRef<UnixListener> for UnlinkUnixListener {
+ fn as_ref(&self) -> &UnixListener {
+ &self.0
+ }
+}
+
+impl Deref for UnlinkUnixListener {
+ type Target = UnixListener;
+
+ fn deref(&self) -> &UnixListener {
+ &self.0
+ }
+}
+
+impl Drop for UnlinkUnixListener {
+ fn drop(&mut self) {
+ if let Ok(addr) = self.0.local_addr() {
+ if let Some(path) = addr.as_pathname() {
+ if let Err(e) = remove_file(path) {
+ warn!("failed to remove control socket file: {}", e);
+ }
+ }
+ }
+ }
+}
+
+/// Verifies that |raw_descriptor| is actually owned by this process and duplicates it
+/// to ensure that we have a unique handle to it.
+pub fn validate_raw_descriptor(raw_descriptor: RawDescriptor) -> Result<RawDescriptor> {
+ validate_raw_fd(raw_descriptor)
+}
+
+/// Verifies that |raw_fd| is actually owned by this process and duplicates it to ensure that
+/// we have a unique handle to it.
+pub fn validate_raw_fd(raw_fd: RawFd) -> Result<RawFd> {
+ // Checking that close-on-exec isn't set helps filter out FDs that were opened by
+ // crosvm as all crosvm FDs are close on exec.
+ // Safe because this doesn't modify any memory and we check the return value.
+ let flags = unsafe { libc::fcntl(raw_fd, libc::F_GETFD) };
+ if flags < 0 || (flags & libc::FD_CLOEXEC) != 0 {
+ return Err(Error::new(libc::EBADF));
+ }
+
+ // Duplicate the fd to ensure that we don't accidentally close an fd previously
+ // opened by another subsystem. Safe because this doesn't modify any memory and
+ // we check the return value.
+ let dup_fd = unsafe { libc::fcntl(raw_fd, libc::F_DUPFD_CLOEXEC, 0) };
+ if dup_fd < 0 {
+ return Err(Error::last());
+ }
+ Ok(dup_fd as RawFd)
+}
+
+/// Utility function that returns true if the given FD is readable without blocking.
+///
+/// On an error, such as an invalid or incompatible FD, this will return false, which can not be
+/// distinguished from a non-ready to read FD.
+pub fn poll_in(fd: &dyn AsRawFd) -> bool {
+ let mut fds = libc::pollfd {
+ fd: fd.as_raw_fd(),
+ events: libc::POLLIN,
+ revents: 0,
+ };
+ // Safe because we give a valid pointer to a list (of 1) FD and check the return value.
+ let ret = unsafe { libc::poll(&mut fds, 1, 0) };
+ // An error probably indicates an invalid FD, or an FD that can't be polled. Returning false in
+ // that case is probably correct as such an FD is unlikely to be readable, although there are
+ // probably corner cases in which that is wrong.
+ if ret == -1 {
+ return false;
+ }
+ fds.revents & libc::POLLIN != 0
+}
+
+/// Returns the file flags set for the given `RawFD`
+///
+/// Returns an error if the OS indicates the flags can't be retrieved.
+fn get_fd_flags(fd: RawFd) -> Result<c_int> {
+ // Safe because no third parameter is expected and we check the return result.
+ syscall!(unsafe { fcntl(fd, F_GETFL) })
+}
+
+/// Sets the file flags set for the given `RawFD`.
+///
+/// Returns an error if the OS indicates the flags can't be retrieved.
+fn set_fd_flags(fd: RawFd, flags: c_int) -> Result<()> {
+ // Safe because we supply the third parameter and we check the return result.
+ // fcntlt is trusted not to modify the memory of the calling process.
+ syscall!(unsafe { fcntl(fd, F_SETFL, flags) }).map(|_| ())
+}
+
+/// Performs a logical OR of the given flags with the FD's flags, setting the given bits for the
+/// FD.
+///
+/// Returns an error if the OS indicates the flags can't be retrieved or set.
+pub fn add_fd_flags(fd: RawFd, set_flags: c_int) -> Result<()> {
+ let start_flags = get_fd_flags(fd)?;
+ set_fd_flags(fd, start_flags | set_flags)
+}
+
+/// Clears the given flags in the FD's flags.
+///
+/// Returns an error if the OS indicates the flags can't be retrieved or set.
+pub fn clear_fd_flags(fd: RawFd, clear_flags: c_int) -> Result<()> {
+ let start_flags = get_fd_flags(fd)?;
+ set_fd_flags(fd, start_flags & !clear_flags)
+}
+
+/// Return a timespec filed with the specified Duration `duration`.
+pub fn duration_to_timespec(duration: Duration) -> libc::timespec {
+ // Safe because we are zero-initializing a struct with only primitive member fields.
+ let mut ts: libc::timespec = unsafe { mem::zeroed() };
+
+ ts.tv_sec = duration.as_secs() as libc::time_t;
+ // nsec always fits in i32 because subsec_nanos is defined to be less than one billion.
+ let nsec = duration.subsec_nanos() as i32;
+ ts.tv_nsec = libc::c_long::from(nsec);
+ ts
+}
+
+/// Return the maximum Duration that can be used with libc::timespec.
+pub fn max_timeout() -> Duration {
+ Duration::new(libc::time_t::max_value() as u64, 999999999)
+}
+
+/// If the given path is of the form /proc/self/fd/N for some N, returns `Ok(Some(N))`. Otherwise
+/// returns `Ok(None)`.
+pub fn safe_descriptor_from_path<P: AsRef<Path>>(path: P) -> Result<Option<SafeDescriptor>> {
+ let path = path.as_ref();
+ if path.parent() == Some(Path::new("/proc/self/fd")) {
+ let raw_descriptor = path
+ .file_name()
+ .and_then(|fd_osstr| fd_osstr.to_str())
+ .and_then(|fd_str| fd_str.parse::<RawFd>().ok())
+ .ok_or_else(|| Error::new(EINVAL))?;
+ let validated_fd = validate_raw_fd(raw_descriptor)?;
+ Ok(Some(
+ // Safe because nothing else has access to validated_fd after this call.
+ unsafe { SafeDescriptor::from_raw_descriptor(validated_fd) },
+ ))
+ } else {
+ Ok(None)
+ }
+}
+
+/// Open the file with the given path, or if it is of the form `/proc/self/fd/N` then just use the
+/// file descriptor.
+///
+/// Note that this will not work properly if the same `/proc/self/fd/N` path is used twice in
+/// different places, as the metadata (including the offset) will be shared between both file
+/// descriptors.
+pub fn open_file<P: AsRef<Path>>(path: P, options: &OpenOptions) -> Result<File> {
+ let path = path.as_ref();
+ // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
+ Ok(if let Some(fd) = safe_descriptor_from_path(path)? {
+ fd.into()
+ } else {
+ options.open(path)?
+ })
+}
+
+/// Get the max number of open files allowed by the environment.
+pub fn get_max_open_files() -> Result<u64> {
+ let mut buf = mem::MaybeUninit::<libc::rlimit64>::zeroed();
+
+ // Safe because this will only modify `buf` and we check the return value.
+ let res = unsafe { libc::prlimit64(0, libc::RLIMIT_NOFILE, ptr::null(), buf.as_mut_ptr()) };
+ if res == 0 {
+ // Safe because the kernel guarantees that the struct is fully initialized.
+ let limit = unsafe { buf.assume_init() };
+ Ok(limit.rlim_max)
+ } else {
+ errno_result()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use libc::EBADF;
+ use std::io::Write;
+
+ use super::*;
+
+ #[test]
+ fn pipe_size_and_fill() {
+ let (_rx, mut tx) = new_pipe_full().expect("Failed to pipe");
+
+ // To check that setting the size worked, set the descriptor to non blocking and check that
+ // write returns an error.
+ add_fd_flags(tx.as_raw_fd(), libc::O_NONBLOCK).expect("Failed to set tx non blocking");
+ tx.write(&[0u8; 8])
+ .expect_err("Write after fill didn't fail");
+ }
+
+ #[test]
+ fn safe_descriptor_from_path_valid() {
+ assert!(safe_descriptor_from_path(Path::new("/proc/self/fd/2"))
+ .unwrap()
+ .is_some());
+ }
+
+ #[test]
+ fn safe_descriptor_from_path_invalid_integer() {
+ assert_eq!(
+ safe_descriptor_from_path(Path::new("/proc/self/fd/blah")),
+ Err(Error::new(EINVAL))
+ );
+ }
+
+ #[test]
+ fn safe_descriptor_from_path_invalid_fd() {
+ assert_eq!(
+ safe_descriptor_from_path(Path::new("/proc/self/fd/42")),
+ Err(Error::new(EBADF))
+ );
+ }
+
+ #[test]
+ fn safe_descriptor_from_path_none() {
+ assert_eq!(
+ safe_descriptor_from_path(Path::new("/something/else")).unwrap(),
+ None
+ );
+ }
+}
diff --git a/base/src/unix/net.rs b/base/src/unix/net.rs
new file mode 100644
index 000000000..11cb4b63a
--- /dev/null
+++ b/base/src/unix/net.rs
@@ -0,0 +1,1091 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ cmp::Ordering,
+ convert::TryFrom,
+ ffi::OsString,
+ fs::remove_file,
+ io,
+ mem::{
+ size_of, {self},
+ },
+ net::{SocketAddr, SocketAddrV4, SocketAddrV6, TcpListener, TcpStream, ToSocketAddrs},
+ ops::Deref,
+ os::unix::{
+ ffi::{OsStrExt, OsStringExt},
+ io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
+ },
+ path::{Path, PathBuf},
+ ptr::null_mut,
+ time::{Duration, Instant},
+};
+
+use libc::{
+ c_int, in6_addr, in_addr, recvfrom, sa_family_t, sockaddr, sockaddr_in, sockaddr_in6,
+ socklen_t, AF_INET, AF_INET6, MSG_PEEK, MSG_TRUNC, SOCK_CLOEXEC, SOCK_STREAM,
+};
+use serde::{Deserialize, Serialize};
+
+use super::{
+ sock_ctrl_msg::{ScmSocket, SCM_SOCKET_MAX_FD_COUNT},
+ Error, RawDescriptor,
+};
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor};
+
+/// Assist in handling both IP version 4 and IP version 6.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum InetVersion {
+ V4,
+ V6,
+}
+
+impl InetVersion {
+ pub fn from_sockaddr(s: &SocketAddr) -> Self {
+ match s {
+ SocketAddr::V4(_) => InetVersion::V4,
+ SocketAddr::V6(_) => InetVersion::V6,
+ }
+ }
+}
+
+impl From<InetVersion> for sa_family_t {
+ fn from(v: InetVersion) -> sa_family_t {
+ match v {
+ InetVersion::V4 => AF_INET as sa_family_t,
+ InetVersion::V6 => AF_INET6 as sa_family_t,
+ }
+ }
+}
+
+fn sockaddrv4_to_lib_c(s: &SocketAddrV4) -> sockaddr_in {
+ sockaddr_in {
+ sin_family: AF_INET as sa_family_t,
+ sin_port: s.port().to_be(),
+ sin_addr: in_addr {
+ s_addr: u32::from_ne_bytes(s.ip().octets()),
+ },
+ sin_zero: [0; 8],
+ }
+}
+
+fn sockaddrv6_to_lib_c(s: &SocketAddrV6) -> sockaddr_in6 {
+ sockaddr_in6 {
+ sin6_family: AF_INET6 as sa_family_t,
+ sin6_port: s.port().to_be(),
+ sin6_flowinfo: 0,
+ sin6_addr: in6_addr {
+ s6_addr: s.ip().octets(),
+ },
+ sin6_scope_id: 0,
+ }
+}
+
+/// A TCP socket.
+///
+/// Do not use this class unless you need to change socket options or query the
+/// state of the socket prior to calling listen or connect. Instead use either TcpStream or
+/// TcpListener.
+#[derive(Debug)]
+pub struct TcpSocket {
+ inet_version: InetVersion,
+ fd: RawFd,
+}
+
+impl TcpSocket {
+ pub fn new(inet_version: InetVersion) -> io::Result<Self> {
+ let fd = unsafe {
+ libc::socket(
+ Into::<sa_family_t>::into(inet_version) as c_int,
+ SOCK_STREAM | SOCK_CLOEXEC,
+ 0,
+ )
+ };
+ if fd < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(TcpSocket { inet_version, fd })
+ }
+ }
+
+ pub fn bind<A: ToSocketAddrs>(&mut self, addr: A) -> io::Result<()> {
+ let sockaddr = addr
+ .to_socket_addrs()
+ .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?
+ .next()
+ .unwrap();
+
+ let ret = match sockaddr {
+ SocketAddr::V4(a) => {
+ let sin = sockaddrv4_to_lib_c(&a);
+ // Safe because this doesn't modify any memory and we check the return value.
+ unsafe {
+ libc::bind(
+ self.fd,
+ &sin as *const sockaddr_in as *const sockaddr,
+ size_of::<sockaddr_in>() as socklen_t,
+ )
+ }
+ }
+ SocketAddr::V6(a) => {
+ let sin6 = sockaddrv6_to_lib_c(&a);
+ // Safe because this doesn't modify any memory and we check the return value.
+ unsafe {
+ libc::bind(
+ self.fd,
+ &sin6 as *const sockaddr_in6 as *const sockaddr,
+ size_of::<sockaddr_in6>() as socklen_t,
+ )
+ }
+ }
+ };
+ if ret < 0 {
+ let bind_err = io::Error::last_os_error();
+ Err(bind_err)
+ } else {
+ Ok(())
+ }
+ }
+
+ pub fn connect<A: ToSocketAddrs>(self, addr: A) -> io::Result<TcpStream> {
+ let sockaddr = addr
+ .to_socket_addrs()
+ .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?
+ .next()
+ .unwrap();
+
+ let ret = match sockaddr {
+ SocketAddr::V4(a) => {
+ let sin = sockaddrv4_to_lib_c(&a);
+ // Safe because this doesn't modify any memory and we check the return value.
+ unsafe {
+ libc::connect(
+ self.fd,
+ &sin as *const sockaddr_in as *const sockaddr,
+ size_of::<sockaddr_in>() as socklen_t,
+ )
+ }
+ }
+ SocketAddr::V6(a) => {
+ let sin6 = sockaddrv6_to_lib_c(&a);
+ // Safe because this doesn't modify any memory and we check the return value.
+ unsafe {
+ libc::connect(
+ self.fd,
+ &sin6 as *const sockaddr_in6 as *const sockaddr,
+ size_of::<sockaddr_in>() as socklen_t,
+ )
+ }
+ }
+ };
+
+ if ret < 0 {
+ let connect_err = io::Error::last_os_error();
+ Err(connect_err)
+ } else {
+ // Safe because the ownership of the raw fd is released from self and taken over by the
+ // new TcpStream.
+ Ok(unsafe { TcpStream::from_raw_fd(self.into_raw_fd()) })
+ }
+ }
+
+ pub fn listen(self) -> io::Result<TcpListener> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe { libc::listen(self.fd, 1) };
+ if ret < 0 {
+ let listen_err = io::Error::last_os_error();
+ Err(listen_err)
+ } else {
+ // Safe because the ownership of the raw fd is released from self and taken over by the
+ // new TcpListener.
+ Ok(unsafe { TcpListener::from_raw_fd(self.into_raw_fd()) })
+ }
+ }
+
+ /// Returns the port that this socket is bound to. This can only succeed after bind is called.
+ pub fn local_port(&self) -> io::Result<u16> {
+ match self.inet_version {
+ InetVersion::V4 => {
+ let mut sin = sockaddr_in {
+ sin_family: 0,
+ sin_port: 0,
+ sin_addr: in_addr { s_addr: 0 },
+ sin_zero: [0; 8],
+ };
+
+ // Safe because we give a valid pointer for addrlen and check the length.
+ let mut addrlen = size_of::<sockaddr_in>() as socklen_t;
+ let ret = unsafe {
+ // Get the socket address that was actually bound.
+ libc::getsockname(
+ self.fd,
+ &mut sin as *mut sockaddr_in as *mut sockaddr,
+ &mut addrlen as *mut socklen_t,
+ )
+ };
+ if ret < 0 {
+ let getsockname_err = io::Error::last_os_error();
+ Err(getsockname_err)
+ } else {
+ // If this doesn't match, it's not safe to get the port out of the sockaddr.
+ assert_eq!(addrlen as usize, size_of::<sockaddr_in>());
+
+ Ok(u16::from_be(sin.sin_port))
+ }
+ }
+ InetVersion::V6 => {
+ let mut sin6 = sockaddr_in6 {
+ sin6_family: 0,
+ sin6_port: 0,
+ sin6_flowinfo: 0,
+ sin6_addr: in6_addr { s6_addr: [0; 16] },
+ sin6_scope_id: 0,
+ };
+
+ // Safe because we give a valid pointer for addrlen and check the length.
+ let mut addrlen = size_of::<sockaddr_in6>() as socklen_t;
+ let ret = unsafe {
+ // Get the socket address that was actually bound.
+ libc::getsockname(
+ self.fd,
+ &mut sin6 as *mut sockaddr_in6 as *mut sockaddr,
+ &mut addrlen as *mut socklen_t,
+ )
+ };
+ if ret < 0 {
+ let getsockname_err = io::Error::last_os_error();
+ Err(getsockname_err)
+ } else {
+ // If this doesn't match, it's not safe to get the port out of the sockaddr.
+ assert_eq!(addrlen as usize, size_of::<sockaddr_in>());
+
+ Ok(u16::from_be(sin6.sin6_port))
+ }
+ }
+ }
+ }
+}
+
+impl IntoRawFd for TcpSocket {
+ fn into_raw_fd(self) -> RawFd {
+ let fd = self.fd;
+ mem::forget(self);
+ fd
+ }
+}
+
+impl AsRawFd for TcpSocket {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd
+ }
+}
+
+impl Drop for TcpSocket {
+ fn drop(&mut self) {
+ // Safe because this doesn't modify any memory and we are the only
+ // owner of the file descriptor.
+ unsafe { libc::close(self.fd) };
+ }
+}
+
+// Offset of sun_path in structure sockaddr_un.
+fn sun_path_offset() -> usize {
+ // Prefer 0 to null() so that we do not need to subtract from the `sub_path` pointer.
+ #[allow(clippy::zero_ptr)]
+ let addr = 0 as *const libc::sockaddr_un;
+ // Safe because we only use the dereference to create a pointer to the desired field in
+ // calculating the offset.
+ unsafe { &(*addr).sun_path as *const _ as usize }
+}
+
+// Return `sockaddr_un` for a given `path`
+fn sockaddr_un<P: AsRef<Path>>(path: P) -> io::Result<(libc::sockaddr_un, libc::socklen_t)> {
+ let mut addr = libc::sockaddr_un {
+ sun_family: libc::AF_UNIX as libc::sa_family_t,
+ sun_path: [0; 108],
+ };
+
+ // Check if the input path is valid. Since
+ // * The pathname in sun_path should be null-terminated.
+ // * The length of the pathname, including the terminating null byte,
+ // should not exceed the size of sun_path.
+ //
+ // and our input is a `Path`, we only need to check
+ // * If the string size of `Path` should less than sizeof(sun_path)
+ // and make sure `sun_path` ends with '\0' by initialized the sun_path with zeros.
+ //
+ // Empty path name is valid since abstract socket address has sun_paht[0] = '\0'
+ let bytes = path.as_ref().as_os_str().as_bytes();
+ if bytes.len() >= addr.sun_path.len() {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Input path size should be less than the length of sun_path.",
+ ));
+ };
+
+ // Copy data from `path` to `addr.sun_path`
+ for (dst, src) in addr.sun_path.iter_mut().zip(bytes) {
+ *dst = *src as libc::c_char;
+ }
+
+ // The addrlen argument that describes the enclosing sockaddr_un structure
+ // should have a value of at least:
+ //
+ // offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path) + 1
+ //
+ // or, more simply, addrlen can be specified as sizeof(struct sockaddr_un).
+ let len = sun_path_offset() + bytes.len() + 1;
+ Ok((addr, len as libc::socklen_t))
+}
+
+/// A Unix `SOCK_SEQPACKET` socket point to given `path`
+#[derive(Debug, Serialize, Deserialize)]
+pub struct UnixSeqpacket {
+ #[serde(with = "super::with_raw_descriptor")]
+ fd: RawFd,
+}
+
+impl UnixSeqpacket {
+ /// Open a `SOCK_SEQPACKET` connection to socket named by `path`.
+ ///
+ /// # Arguments
+ /// * `path` - Path to `SOCK_SEQPACKET` socket
+ ///
+ /// # Returns
+ /// A `UnixSeqpacket` structure point to the socket
+ ///
+ /// # Errors
+ /// Return `io::Error` when error occurs.
+ pub fn connect<P: AsRef<Path>>(path: P) -> io::Result<Self> {
+ // Safe socket initialization since we handle the returned error.
+ let fd = unsafe {
+ match libc::socket(libc::AF_UNIX, libc::SOCK_SEQPACKET, 0) {
+ -1 => return Err(io::Error::last_os_error()),
+ fd => fd,
+ }
+ };
+
+ let (addr, len) = sockaddr_un(path.as_ref())?;
+ // Safe connect since we handle the error and use the right length generated from
+ // `sockaddr_un`.
+ unsafe {
+ let ret = libc::connect(fd, &addr as *const _ as *const _, len);
+ if ret < 0 {
+ return Err(io::Error::last_os_error());
+ }
+ }
+ Ok(UnixSeqpacket { fd })
+ }
+
+ /// Creates a pair of connected `SOCK_SEQPACKET` sockets.
+ ///
+ /// Both returned file descriptors have the `CLOEXEC` flag set.s
+ pub fn pair() -> io::Result<(UnixSeqpacket, UnixSeqpacket)> {
+ let mut fds = [0, 0];
+ unsafe {
+ // Safe because we give enough space to store all the fds and we check the return value.
+ let ret = libc::socketpair(
+ libc::AF_UNIX,
+ libc::SOCK_SEQPACKET | libc::SOCK_CLOEXEC,
+ 0,
+ &mut fds[0],
+ );
+ if ret == 0 {
+ Ok((
+ UnixSeqpacket::from_raw_fd(fds[0]),
+ UnixSeqpacket::from_raw_fd(fds[1]),
+ ))
+ } else {
+ Err(io::Error::last_os_error())
+ }
+ }
+ }
+
+ /// Clone the underlying FD.
+ pub fn try_clone(&self) -> io::Result<Self> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let fd = unsafe { libc::fcntl(self.fd, libc::F_DUPFD_CLOEXEC, 0) };
+ if fd < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(Self { fd })
+ }
+ }
+
+ /// Gets the number of bytes that can be read from this socket without blocking.
+ pub fn get_readable_bytes(&self) -> io::Result<usize> {
+ let mut byte_count = 0i32;
+ let ret = unsafe { libc::ioctl(self.fd, libc::FIONREAD, &mut byte_count) };
+ if ret < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(byte_count as usize)
+ }
+ }
+
+ /// Gets the number of bytes in the next packet. This blocks as if `recv` were called,
+ /// respecting the blocking and timeout settings of the underlying socket.
+ pub fn next_packet_size(&self) -> io::Result<usize> {
+ #[cfg(not(debug_assertions))]
+ let buf = null_mut();
+ // Work around for qemu's syscall translation which will reject null pointers in recvfrom.
+ // This only matters for running the unit tests for a non-native architecture. See the
+ // upstream thread for the qemu fix:
+ // https://lists.nongnu.org/archive/html/qemu-devel/2021-03/msg09027.html
+ #[cfg(debug_assertions)]
+ let buf = &mut 0 as *mut _ as *mut _;
+
+ // This form of recvfrom doesn't modify any data because all null pointers are used. We only
+ // use the return value and check for errors on an FD owned by this structure.
+ let ret = unsafe {
+ recvfrom(
+ self.fd,
+ buf,
+ 0,
+ MSG_TRUNC | MSG_PEEK,
+ null_mut(),
+ null_mut(),
+ )
+ };
+ if ret < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(ret as usize)
+ }
+ }
+
+ /// Write data from a given buffer to the socket fd
+ ///
+ /// # Arguments
+ /// * `buf` - A reference to the data buffer.
+ ///
+ /// # Returns
+ /// * `usize` - The size of bytes written to the buffer.
+ ///
+ /// # Errors
+ /// Returns error when `libc::write` failed.
+ pub fn send(&self, buf: &[u8]) -> io::Result<usize> {
+ // Safe since we make sure the input `count` == `buf.len()` and handle the returned error.
+ unsafe {
+ let ret = libc::write(self.fd, buf.as_ptr() as *const _, buf.len());
+ if ret < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(ret as usize)
+ }
+ }
+ }
+
+ /// Read data from the socket fd to a given buffer
+ ///
+ /// # Arguments
+ /// * `buf` - A mut reference to the data buffer.
+ ///
+ /// # Returns
+ /// * `usize` - The size of bytes read to the buffer.
+ ///
+ /// # Errors
+ /// Returns error when `libc::read` failed.
+ pub fn recv(&self, buf: &mut [u8]) -> io::Result<usize> {
+ // Safe since we make sure the input `count` == `buf.len()` and handle the returned error.
+ unsafe {
+ let ret = libc::read(self.fd, buf.as_mut_ptr() as *mut _, buf.len());
+ if ret < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(ret as usize)
+ }
+ }
+ }
+
+ /// Read data from the socket fd to a given `Vec`, resizing it to the received packet's size.
+ ///
+ /// # Arguments
+ /// * `buf` - A mut reference to a `Vec` to resize and read into.
+ ///
+ /// # Errors
+ /// Returns error when `libc::read` or `get_readable_bytes` failed.
+ pub fn recv_to_vec(&self, buf: &mut Vec<u8>) -> io::Result<()> {
+ let packet_size = self.next_packet_size()?;
+ buf.resize(packet_size, 0);
+ let read_bytes = self.recv(buf)?;
+ buf.resize(read_bytes, 0);
+ Ok(())
+ }
+
+ /// Read data from the socket fd to a new `Vec`.
+ ///
+ /// # Returns
+ /// * `vec` - A new `Vec` with the entire received packet.
+ ///
+ /// # Errors
+ /// Returns error when `libc::read` or `get_readable_bytes` failed.
+ pub fn recv_as_vec(&self) -> io::Result<Vec<u8>> {
+ let mut buf = Vec::new();
+ self.recv_to_vec(&mut buf)?;
+ Ok(buf)
+ }
+
+ /// Read data and fds from the socket fd to a new pair of `Vec`.
+ ///
+ /// # Returns
+ /// * `Vec<u8>` - A new `Vec` with the entire received packet's bytes.
+ /// * `Vec<RawFd>` - A new `Vec` with the entire received packet's fds.
+ ///
+ /// # Errors
+ /// Returns error when `recv_with_fds` or `get_readable_bytes` failed.
+ pub fn recv_as_vec_with_fds(&self) -> io::Result<(Vec<u8>, Vec<RawFd>)> {
+ let packet_size = self.next_packet_size()?;
+ let mut buf = vec![0; packet_size];
+ let mut fd_buf = vec![-1; SCM_SOCKET_MAX_FD_COUNT];
+ let (read_bytes, read_fds) =
+ self.recv_with_fds(io::IoSliceMut::new(&mut buf), &mut fd_buf)?;
+ buf.resize(read_bytes, 0);
+ fd_buf.resize(read_fds, -1);
+ Ok((buf, fd_buf))
+ }
+
+ fn set_timeout(&self, timeout: Option<Duration>, kind: libc::c_int) -> io::Result<()> {
+ let timeval = match timeout {
+ Some(t) => {
+ if t.as_secs() == 0 && t.subsec_micros() == 0 {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "zero timeout duration is invalid",
+ ));
+ }
+ // subsec_micros fits in i32 because it is defined to be less than one million.
+ let nsec = t.subsec_micros() as i32;
+ libc::timeval {
+ tv_sec: t.as_secs() as libc::time_t,
+ tv_usec: libc::suseconds_t::from(nsec),
+ }
+ }
+ None => libc::timeval {
+ tv_sec: 0,
+ tv_usec: 0,
+ },
+ };
+ // Safe because we own the fd, and the length of the pointer's data is the same as the
+ // passed in length parameter. The level argument is valid, the kind is assumed to be valid,
+ // and the return value is checked.
+ let ret = unsafe {
+ libc::setsockopt(
+ self.fd,
+ libc::SOL_SOCKET,
+ kind,
+ &timeval as *const libc::timeval as *const libc::c_void,
+ mem::size_of::<libc::timeval>() as libc::socklen_t,
+ )
+ };
+ if ret < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Sets or removes the timeout for read/recv operations on this socket.
+ pub fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
+ self.set_timeout(timeout, libc::SO_RCVTIMEO)
+ }
+
+ /// Sets or removes the timeout for write/send operations on this socket.
+ pub fn set_write_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
+ self.set_timeout(timeout, libc::SO_SNDTIMEO)
+ }
+}
+
+impl Drop for UnixSeqpacket {
+ fn drop(&mut self) {
+ // Safe if the UnixSeqpacket is created from Self::connect.
+ unsafe {
+ libc::close(self.fd);
+ }
+ }
+}
+
+impl FromRawFd for UnixSeqpacket {
+ // Unsafe in drop function
+ unsafe fn from_raw_fd(fd: RawFd) -> Self {
+ Self { fd }
+ }
+}
+
+impl FromRawDescriptor for UnixSeqpacket {
+ unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Self {
+ Self { fd: descriptor }
+ }
+}
+
+impl IntoRawDescriptor for UnixSeqpacket {
+ fn into_raw_descriptor(self) -> RawDescriptor {
+ let fd = self.fd;
+ mem::forget(self);
+ fd
+ }
+}
+
+impl AsRawFd for UnixSeqpacket {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd
+ }
+}
+
+impl AsRawFd for &UnixSeqpacket {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd
+ }
+}
+
+impl AsRawDescriptor for UnixSeqpacket {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.fd
+ }
+}
+
+impl io::Read for UnixSeqpacket {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.recv(buf)
+ }
+}
+
+impl io::Write for UnixSeqpacket {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.send(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+impl IntoRawFd for UnixSeqpacket {
+ fn into_raw_fd(self) -> RawFd {
+ let fd = self.fd;
+ mem::forget(self);
+ fd
+ }
+}
+
+/// Like a `UnixListener` but for accepting `UnixSeqpacket` type sockets.
+pub struct UnixSeqpacketListener {
+ fd: RawFd,
+}
+
+impl UnixSeqpacketListener {
+ /// Creates a new `UnixSeqpacketListener` bound to the given path.
+ pub fn bind<P: AsRef<Path>>(path: P) -> io::Result<Self> {
+ // Safe socket initialization since we handle the returned error.
+ let fd = unsafe {
+ match libc::socket(libc::AF_UNIX, libc::SOCK_SEQPACKET, 0) {
+ -1 => return Err(io::Error::last_os_error()),
+ fd => fd,
+ }
+ };
+
+ let (addr, len) = sockaddr_un(path.as_ref())?;
+ // Safe connect since we handle the error and use the right length generated from
+ // `sockaddr_un`.
+ unsafe {
+ let ret = handle_eintr_errno!(libc::bind(fd, &addr as *const _ as *const _, len));
+ if ret < 0 {
+ return Err(io::Error::last_os_error());
+ }
+ let ret = handle_eintr_errno!(libc::listen(fd, 128));
+ if ret < 0 {
+ return Err(io::Error::last_os_error());
+ }
+ }
+ Ok(UnixSeqpacketListener { fd })
+ }
+
+ /// Blocks for and accepts a new incoming connection and returns the socket associated with that
+ /// connection.
+ ///
+ /// The returned socket has the close-on-exec flag set.
+ pub fn accept(&self) -> io::Result<UnixSeqpacket> {
+ // Safe because we own this fd and the kernel will not write to null pointers.
+ let ret = unsafe { libc::accept4(self.fd, null_mut(), null_mut(), libc::SOCK_CLOEXEC) };
+ if ret < 0 {
+ return Err(io::Error::last_os_error());
+ }
+ // Safe because we checked the return value of accept. Therefore, the return value must be a
+ // valid socket.
+ Ok(unsafe { UnixSeqpacket::from_raw_fd(ret) })
+ }
+
+ pub fn accept_with_timeout(&self, timeout: Duration) -> io::Result<UnixSeqpacket> {
+ let start = Instant::now();
+
+ loop {
+ let mut fds = libc::pollfd {
+ fd: self.fd,
+ events: libc::POLLIN,
+ revents: 0,
+ };
+ let elapsed = Instant::now().saturating_duration_since(start);
+ let remaining = timeout.checked_sub(elapsed).unwrap_or(Duration::ZERO);
+ let cur_timeout_ms = i32::try_from(remaining.as_millis()).unwrap_or(i32::MAX);
+ // Safe because we give a valid pointer to a list (of 1) FD and we check
+ // the return value.
+ match unsafe { libc::poll(&mut fds, 1, cur_timeout_ms) }.cmp(&0) {
+ Ordering::Greater => return self.accept(),
+ Ordering::Equal => return Err(io::Error::from_raw_os_error(libc::ETIMEDOUT)),
+ Ordering::Less => {
+ if Error::last() != Error::new(libc::EINTR) {
+ return Err(io::Error::last_os_error());
+ }
+ }
+ }
+ }
+ }
+
+ /// Gets the path that this listener is bound to.
+ pub fn path(&self) -> io::Result<PathBuf> {
+ let mut addr = libc::sockaddr_un {
+ sun_family: libc::AF_UNIX as libc::sa_family_t,
+ sun_path: [0; 108],
+ };
+ let sun_path_offset = (&addr.sun_path as *const _ as usize
+ - &addr.sun_family as *const _ as usize)
+ as libc::socklen_t;
+ let mut len = mem::size_of::<libc::sockaddr_un>() as libc::socklen_t;
+ // Safe because the length given matches the length of the data of the given pointer, and we
+ // check the return value.
+ let ret = unsafe {
+ handle_eintr_errno!(libc::getsockname(
+ self.fd,
+ &mut addr as *mut libc::sockaddr_un as *mut libc::sockaddr,
+ &mut len
+ ))
+ };
+ if ret < 0 {
+ return Err(io::Error::last_os_error());
+ }
+ if addr.sun_family != libc::AF_UNIX as libc::sa_family_t
+ || addr.sun_path[0] == 0
+ || len < 1 + sun_path_offset
+ {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "getsockname on socket returned invalid value",
+ ));
+ }
+
+ let path_os_str = OsString::from_vec(
+ addr.sun_path[..(len - sun_path_offset - 1) as usize]
+ .iter()
+ .map(|&c| c as _)
+ .collect(),
+ );
+ Ok(path_os_str.into())
+ }
+}
+
+impl Drop for UnixSeqpacketListener {
+ fn drop(&mut self) {
+ // Safe if the UnixSeqpacketListener is created from Self::listen.
+ unsafe {
+ libc::close(self.fd);
+ }
+ }
+}
+
+impl FromRawFd for UnixSeqpacketListener {
+ // Unsafe in drop function
+ unsafe fn from_raw_fd(fd: RawFd) -> Self {
+ Self { fd }
+ }
+}
+
+impl AsRawFd for UnixSeqpacketListener {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd
+ }
+}
+
+/// Used to attempt to clean up a `UnixSeqpacketListener` after it is dropped.
+pub struct UnlinkUnixSeqpacketListener(pub UnixSeqpacketListener);
+impl AsRef<UnixSeqpacketListener> for UnlinkUnixSeqpacketListener {
+ fn as_ref(&self) -> &UnixSeqpacketListener {
+ &self.0
+ }
+}
+
+impl AsRawFd for UnlinkUnixSeqpacketListener {
+ fn as_raw_fd(&self) -> RawFd {
+ self.0.as_raw_fd()
+ }
+}
+
+impl Deref for UnlinkUnixSeqpacketListener {
+ type Target = UnixSeqpacketListener;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl Drop for UnlinkUnixSeqpacketListener {
+ fn drop(&mut self) {
+ if let Ok(path) = self.0.path() {
+ if let Err(e) = remove_file(path) {
+ warn!("failed to remove control socket file: {:?}", e);
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::{env, io::ErrorKind, path::PathBuf};
+
+ fn tmpdir() -> PathBuf {
+ env::temp_dir()
+ }
+
+ #[test]
+ fn sockaddr_un_zero_length_input() {
+ let _res = sockaddr_un(Path::new("")).expect("sockaddr_un failed");
+ }
+
+ #[test]
+ fn sockaddr_un_long_input_err() {
+ let res = sockaddr_un(Path::new(&"a".repeat(108)));
+ assert!(res.is_err());
+ }
+
+ #[test]
+ fn sockaddr_un_long_input_pass() {
+ let _res = sockaddr_un(Path::new(&"a".repeat(107))).expect("sockaddr_un failed");
+ }
+
+ #[test]
+ fn sockaddr_un_len_check() {
+ let (_addr, len) = sockaddr_un(Path::new(&"a".repeat(50))).expect("sockaddr_un failed");
+ assert_eq!(len, (sun_path_offset() + 50 + 1) as u32);
+ }
+
+ #[test]
+ #[allow(clippy::unnecessary_cast)]
+ fn sockaddr_un_pass() {
+ let path_size = 50;
+ let (addr, len) =
+ sockaddr_un(Path::new(&"a".repeat(path_size))).expect("sockaddr_un failed");
+ assert_eq!(len, (sun_path_offset() + path_size + 1) as u32);
+ assert_eq!(addr.sun_family, libc::AF_UNIX as libc::sa_family_t);
+
+ // Check `sun_path` in returned `sockaddr_un`
+ let mut ref_sun_path = [0 as libc::c_char; 108];
+ for path in ref_sun_path.iter_mut().take(path_size) {
+ *path = 'a' as libc::c_char;
+ }
+
+ for (addr_char, ref_char) in addr.sun_path.iter().zip(ref_sun_path.iter()) {
+ assert_eq!(addr_char, ref_char);
+ }
+ }
+
+ #[test]
+ fn unix_seqpacket_path_not_exists() {
+ let res = UnixSeqpacket::connect("/path/not/exists");
+ assert!(res.is_err());
+ }
+
+ #[test]
+ fn unix_seqpacket_listener_path() {
+ let mut socket_path = tmpdir();
+ socket_path.push("unix_seqpacket_listener_path");
+ let listener = UnlinkUnixSeqpacketListener(
+ UnixSeqpacketListener::bind(&socket_path)
+ .expect("failed to create UnixSeqpacketListener"),
+ );
+ let listener_path = listener.path().expect("failed to get socket listener path");
+ assert_eq!(socket_path, listener_path);
+ }
+
+ #[test]
+ fn unix_seqpacket_path_exists_pass() {
+ let mut socket_path = tmpdir();
+ socket_path.push("path_to_socket");
+ let _listener = UnlinkUnixSeqpacketListener(
+ UnixSeqpacketListener::bind(&socket_path)
+ .expect("failed to create UnixSeqpacketListener"),
+ );
+ let _res =
+ UnixSeqpacket::connect(socket_path.as_path()).expect("UnixSeqpacket::connect failed");
+ }
+
+ #[test]
+ fn unix_seqpacket_path_listener_accept_with_timeout() {
+ let mut socket_path = tmpdir();
+ socket_path.push("path_listerner_accept_with_timeout");
+ let listener = UnlinkUnixSeqpacketListener(
+ UnixSeqpacketListener::bind(&socket_path)
+ .expect("failed to create UnixSeqpacketListener"),
+ );
+
+ for d in [Duration::from_millis(10), Duration::ZERO] {
+ let _ = listener.accept_with_timeout(d).expect_err(&format!(
+ "UnixSeqpacket::accept_with_timeout {:?} connected",
+ d
+ ));
+
+ let s1 = UnixSeqpacket::connect(socket_path.as_path())
+ .unwrap_or_else(|_| panic!("UnixSeqpacket::connect {:?} failed", d));
+
+ let s2 = listener
+ .accept_with_timeout(d)
+ .unwrap_or_else(|_| panic!("UnixSeqpacket::accept {:?} failed", d));
+
+ let data1 = &[0, 1, 2, 3, 4];
+ let data2 = &[10, 11, 12, 13, 14];
+ s2.send(data2).expect("failed to send data2");
+ s1.send(data1).expect("failed to send data1");
+ let recv_data = &mut [0; 5];
+ s2.recv(recv_data).expect("failed to recv data");
+ assert_eq!(data1, recv_data);
+ s1.recv(recv_data).expect("failed to recv data");
+ assert_eq!(data2, recv_data);
+ }
+ }
+
+ #[test]
+ fn unix_seqpacket_path_listener_accept() {
+ let mut socket_path = tmpdir();
+ socket_path.push("path_listerner_accept");
+ let listener = UnlinkUnixSeqpacketListener(
+ UnixSeqpacketListener::bind(&socket_path)
+ .expect("failed to create UnixSeqpacketListener"),
+ );
+ let s1 =
+ UnixSeqpacket::connect(socket_path.as_path()).expect("UnixSeqpacket::connect failed");
+
+ let s2 = listener.accept().expect("UnixSeqpacket::accept failed");
+
+ let data1 = &[0, 1, 2, 3, 4];
+ let data2 = &[10, 11, 12, 13, 14];
+ s2.send(data2).expect("failed to send data2");
+ s1.send(data1).expect("failed to send data1");
+ let recv_data = &mut [0; 5];
+ s2.recv(recv_data).expect("failed to recv data");
+ assert_eq!(data1, recv_data);
+ s1.recv(recv_data).expect("failed to recv data");
+ assert_eq!(data2, recv_data);
+ }
+
+ #[test]
+ fn unix_seqpacket_zero_timeout() {
+ let (s1, _s2) = UnixSeqpacket::pair().expect("failed to create socket pair");
+ // Timeouts less than a microsecond are too small and round to zero.
+ s1.set_read_timeout(Some(Duration::from_nanos(10)))
+ .expect_err("successfully set zero timeout");
+ }
+
+ #[test]
+ fn unix_seqpacket_read_timeout() {
+ let (s1, _s2) = UnixSeqpacket::pair().expect("failed to create socket pair");
+ s1.set_read_timeout(Some(Duration::from_millis(1)))
+ .expect("failed to set read timeout for socket");
+ let _ = s1.recv(&mut [0]);
+ }
+
+ #[test]
+ fn unix_seqpacket_write_timeout() {
+ let (s1, _s2) = UnixSeqpacket::pair().expect("failed to create socket pair");
+ s1.set_write_timeout(Some(Duration::from_millis(1)))
+ .expect("failed to set write timeout for socket");
+ }
+
+ #[test]
+ fn unix_seqpacket_send_recv() {
+ let (s1, s2) = UnixSeqpacket::pair().expect("failed to create socket pair");
+ let data1 = &[0, 1, 2, 3, 4];
+ let data2 = &[10, 11, 12, 13, 14];
+ s2.send(data2).expect("failed to send data2");
+ s1.send(data1).expect("failed to send data1");
+ let recv_data = &mut [0; 5];
+ s2.recv(recv_data).expect("failed to recv data");
+ assert_eq!(data1, recv_data);
+ s1.recv(recv_data).expect("failed to recv data");
+ assert_eq!(data2, recv_data);
+ }
+
+ #[test]
+ fn unix_seqpacket_send_fragments() {
+ let (s1, s2) = UnixSeqpacket::pair().expect("failed to create socket pair");
+ let data1 = &[0, 1, 2, 3, 4];
+ let data2 = &[10, 11, 12, 13, 14, 15, 16];
+ s1.send(data1).expect("failed to send data1");
+ s1.send(data2).expect("failed to send data2");
+
+ let recv_data = &mut [0; 32];
+ let size = s2.recv(recv_data).expect("failed to recv data");
+ assert_eq!(size, data1.len());
+ assert_eq!(data1, &recv_data[0..size]);
+
+ let size = s2.recv(recv_data).expect("failed to recv data");
+ assert_eq!(size, data2.len());
+ assert_eq!(data2, &recv_data[0..size]);
+ }
+
+ #[test]
+ fn unix_seqpacket_get_readable_bytes() {
+ let (s1, s2) = UnixSeqpacket::pair().expect("failed to create socket pair");
+ assert_eq!(s1.get_readable_bytes().unwrap(), 0);
+ assert_eq!(s2.get_readable_bytes().unwrap(), 0);
+ let data1 = &[0, 1, 2, 3, 4];
+ s1.send(data1).expect("failed to send data");
+
+ assert_eq!(s1.get_readable_bytes().unwrap(), 0);
+ assert_eq!(s2.get_readable_bytes().unwrap(), data1.len());
+
+ let recv_data = &mut [0; 5];
+ s2.recv(recv_data).expect("failed to recv data");
+ assert_eq!(s1.get_readable_bytes().unwrap(), 0);
+ assert_eq!(s2.get_readable_bytes().unwrap(), 0);
+ }
+
+ #[test]
+ fn unix_seqpacket_next_packet_size() {
+ let (s1, s2) = UnixSeqpacket::pair().expect("failed to create socket pair");
+ let data1 = &[0, 1, 2, 3, 4];
+ s1.send(data1).expect("failed to send data");
+
+ assert_eq!(s2.next_packet_size().unwrap(), 5);
+ s1.set_read_timeout(Some(Duration::from_micros(1)))
+ .expect("failed to set read timeout");
+ assert_eq!(
+ s1.next_packet_size().unwrap_err().kind(),
+ ErrorKind::WouldBlock
+ );
+ drop(s2);
+ assert_eq!(
+ s1.next_packet_size().unwrap_err().kind(),
+ ErrorKind::ConnectionReset
+ );
+ }
+
+ #[test]
+ fn unix_seqpacket_recv_to_vec() {
+ let (s1, s2) = UnixSeqpacket::pair().expect("failed to create socket pair");
+ let data1 = &[0, 1, 2, 3, 4];
+ s1.send(data1).expect("failed to send data");
+
+ let recv_data = &mut vec![];
+ s2.recv_to_vec(recv_data).expect("failed to recv data");
+ assert_eq!(recv_data, &mut vec![0, 1, 2, 3, 4]);
+ }
+
+ #[test]
+ fn unix_seqpacket_recv_as_vec() {
+ let (s1, s2) = UnixSeqpacket::pair().expect("failed to create socket pair");
+ let data1 = &[0, 1, 2, 3, 4];
+ s1.send(data1).expect("failed to send data");
+
+ let recv_data = s2.recv_as_vec().expect("failed to recv data");
+ assert_eq!(recv_data, vec![0, 1, 2, 3, 4]);
+ }
+}
diff --git a/base/src/unix/netlink.rs b/base/src/unix/netlink.rs
new file mode 100644
index 000000000..297f255f9
--- /dev/null
+++ b/base/src/unix/netlink.rs
@@ -0,0 +1,499 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{alloc::Layout, mem::MaybeUninit, os::unix::io::AsRawFd, str};
+
+use data_model::DataInit;
+use libc::EINVAL;
+
+use crate::alloc::LayoutAllocation;
+
+use super::{errno_result, getpid, Error, RawDescriptor, Result};
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, SafeDescriptor};
+
+macro_rules! debug_pr {
+ // By default debugs are suppressed, to enabled them replace macro body with:
+ // $($args:tt)+) => (println!($($args)*))
+ ($($args:tt)+) => {};
+}
+
+const NLMSGHDR_SIZE: usize = std::mem::size_of::<NlMsgHdr>();
+const GENL_HDRLEN: usize = std::mem::size_of::<GenlMsgHdr>();
+const NLA_HDRLEN: usize = std::mem::size_of::<NlAttr>();
+const NLATTR_ALIGN_TO: usize = 4;
+
+// Custom nlmsghdr struct that can be declared DataInit.
+#[repr(C)]
+#[derive(Copy, Clone)]
+struct NlMsgHdr {
+ pub nlmsg_len: u32,
+ pub nlmsg_type: u16,
+ pub nlmsg_flags: u16,
+ pub nlmsg_seq: u32,
+ pub nlmsg_pid: u32,
+}
+unsafe impl DataInit for NlMsgHdr {}
+
+/// Netlink attribute struct, can be used by netlink consumer
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct NlAttr {
+ pub len: u16,
+ pub _type: u16,
+}
+unsafe impl DataInit for NlAttr {}
+
+/// Generic netlink header struct, can be used by netlink consumer
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct GenlMsgHdr {
+ pub cmd: u8,
+ pub version: u8,
+ pub reserved: u16,
+}
+unsafe impl DataInit for GenlMsgHdr {}
+
+/// A single netlink message, including its header and data.
+pub struct NetlinkMessage<'a> {
+ pub _type: u16,
+ pub flags: u16,
+ pub seq: u32,
+ pub pid: u32,
+ pub data: &'a [u8],
+}
+
+pub struct NlAttrWithData<'a> {
+ pub len: u16,
+ pub _type: u16,
+ pub data: &'a [u8],
+}
+
+fn nlattr_align(offset: usize) -> usize {
+ return (offset + NLATTR_ALIGN_TO - 1) & !(NLATTR_ALIGN_TO - 1);
+}
+
+/// Iterator over `struct NlAttr` as received from a netlink socket.
+pub struct NetlinkGenericDataIter<'a> {
+ // `data` must be properly aligned for NlAttr.
+ data: &'a [u8],
+}
+
+impl<'a> Iterator for NetlinkGenericDataIter<'a> {
+ type Item = NlAttrWithData<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.data.len() < NLA_HDRLEN {
+ return None;
+ }
+ let nl_hdr = NlAttr::from_slice(&self.data[..NLA_HDRLEN])?;
+
+ // Make sure NlAtrr fits
+ let nl_data_len = nl_hdr.len as usize;
+ if nl_data_len < NLA_HDRLEN || nl_data_len > self.data.len() {
+ return None;
+ }
+
+ // Get data related to processed NlAttr
+ let data_start = NLA_HDRLEN;
+ let data = &self.data[data_start..nl_data_len];
+
+ // Get next NlAttr
+ let next_hdr = nlattr_align(nl_data_len);
+ if next_hdr >= self.data.len() {
+ self.data = &[];
+ } else {
+ self.data = &self.data[next_hdr..];
+ }
+
+ Some(NlAttrWithData {
+ _type: nl_hdr._type,
+ len: nl_hdr.len,
+ data,
+ })
+ }
+}
+
+/// Iterator over `struct nlmsghdr` as received from a netlink socket.
+pub struct NetlinkMessageIter<'a> {
+ // `data` must be properly aligned for nlmsghdr.
+ data: &'a [u8],
+}
+
+impl<'a> Iterator for NetlinkMessageIter<'a> {
+ type Item = NetlinkMessage<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.data.len() < NLMSGHDR_SIZE {
+ return None;
+ }
+ let hdr = NlMsgHdr::from_slice(&self.data[..NLMSGHDR_SIZE])?;
+
+ // NLMSG_OK
+ let msg_len = hdr.nlmsg_len as usize;
+ if msg_len < NLMSGHDR_SIZE || msg_len > self.data.len() {
+ return None;
+ }
+
+ // NLMSG_DATA
+ let data_start = NLMSGHDR_SIZE;
+ let data = &self.data[data_start..msg_len];
+
+ // NLMSG_NEXT
+ let align_to = std::mem::align_of::<NlMsgHdr>();
+ let next_hdr = (msg_len + align_to - 1) & !(align_to - 1);
+ if next_hdr >= self.data.len() {
+ self.data = &[];
+ } else {
+ self.data = &self.data[next_hdr..];
+ }
+
+ Some(NetlinkMessage {
+ _type: hdr.nlmsg_type,
+ flags: hdr.nlmsg_flags,
+ seq: hdr.nlmsg_seq,
+ pid: hdr.nlmsg_pid,
+ data,
+ })
+ }
+}
+
+/// Safe wrapper for `NETLINK_GENERIC` netlink sockets.
+pub struct NetlinkGenericSocket {
+ sock: SafeDescriptor,
+}
+
+impl AsRawDescriptor for NetlinkGenericSocket {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.sock.as_raw_descriptor()
+ }
+}
+
+impl NetlinkGenericSocket {
+ /// Create and bind a new `NETLINK_GENERIC` socket.
+ pub fn new(nl_groups: u32) -> Result<Self> {
+ // Safe because we check the return value and convert the raw fd into a SafeDescriptor.
+ let sock = unsafe {
+ let fd = libc::socket(
+ libc::AF_NETLINK,
+ libc::SOCK_RAW | libc::SOCK_CLOEXEC,
+ libc::NETLINK_GENERIC,
+ );
+ if fd < 0 {
+ return errno_result();
+ }
+
+ SafeDescriptor::from_raw_descriptor(fd)
+ };
+
+ // This MaybeUninit dance is needed because sockaddr_nl has a private padding field and
+ // doesn't implement Default. Safe because all 0s is valid data for sockaddr_nl.
+ let mut sa = unsafe { MaybeUninit::<libc::sockaddr_nl>::zeroed().assume_init() };
+ sa.nl_family = libc::AF_NETLINK as libc::sa_family_t;
+ sa.nl_groups = nl_groups;
+
+ // Safe because we pass a descriptor that we own and valid pointer/size for sockaddr.
+ unsafe {
+ let res = libc::bind(
+ sock.as_raw_fd(),
+ &sa as *const libc::sockaddr_nl as *const libc::sockaddr,
+ std::mem::size_of_val(&sa) as libc::socklen_t,
+ );
+ if res < 0 {
+ return errno_result();
+ }
+ }
+
+ Ok(NetlinkGenericSocket { sock })
+ }
+
+ /// Receive messages from the netlink socket.
+ pub fn recv(&self) -> Result<NetlinkGenericRead> {
+ let buf_size = 8192; // TODO(dverkamp): make this configurable?
+
+ // Create a buffer with sufficient alignment for nlmsghdr.
+ let layout = Layout::from_size_align(buf_size, std::mem::align_of::<NlMsgHdr>())
+ .map_err(|_| Error::new(EINVAL))?;
+ let allocation = LayoutAllocation::uninitialized(layout);
+
+ // Safe because we pass a valid, owned socket fd and a valid pointer/size for the buffer.
+ let bytes_read = unsafe {
+ let res = libc::recv(
+ self.sock.as_raw_fd(),
+ allocation.as_ptr() as *mut libc::c_void,
+ buf_size,
+ 0,
+ );
+ if res < 0 {
+ return errno_result();
+ }
+ res as usize
+ };
+
+ Ok(NetlinkGenericRead {
+ allocation,
+ len: bytes_read,
+ })
+ }
+
+ pub fn family_name_query(&self, family_name: String) -> Result<NetlinkGenericRead> {
+ let buf_size = 1024;
+ debug_pr!(
+ "preparing query for family name {}, len {}",
+ family_name,
+ family_name.len()
+ );
+
+ // Create a buffer with sufficient alignment for nlmsghdr.
+ let layout = Layout::from_size_align(buf_size, std::mem::align_of::<NlMsgHdr>())
+ .map_err(|_| Error::new(EINVAL))
+ .unwrap();
+ let mut allocation = LayoutAllocation::zeroed(layout);
+
+ // Safe because the data in allocation was initialized up to `buf_size` and is
+ // sufficiently aligned.
+ let data = unsafe { allocation.as_mut_slice(buf_size) };
+
+ // Prepare the netlink message header
+ let mut hdr =
+ &mut NlMsgHdr::from_mut_slice(&mut data[..NLMSGHDR_SIZE]).expect("failed to unwrap");
+ hdr.nlmsg_len = NLMSGHDR_SIZE as u32 + GENL_HDRLEN as u32;
+ hdr.nlmsg_len += NLA_HDRLEN as u32 + family_name.len() as u32 + 1;
+ hdr.nlmsg_flags = libc::NLM_F_REQUEST as u16;
+ hdr.nlmsg_type = libc::GENL_ID_CTRL as u16;
+ hdr.nlmsg_pid = getpid() as u32;
+
+ // Prepare generic netlink message header
+ let genl_hdr_end = NLMSGHDR_SIZE + GENL_HDRLEN;
+ let genl_hdr = GenlMsgHdr::from_mut_slice(&mut data[NLMSGHDR_SIZE..genl_hdr_end])
+ .expect("unable to get GenlMsgHdr from slice");
+ genl_hdr.cmd = libc::CTRL_CMD_GETFAMILY as u8;
+ genl_hdr.version = 0x1;
+
+ // Netlink attributes
+ let nlattr_start = genl_hdr_end;
+ let nlattr_end = nlattr_start + NLA_HDRLEN;
+ let nl_attr = NlAttr::from_mut_slice(&mut data[nlattr_start..nlattr_end])
+ .expect("unable to get NlAttr from slice");
+ nl_attr._type = libc::CTRL_ATTR_FAMILY_NAME as u16;
+ nl_attr.len = family_name.len() as u16 + 1 + NLA_HDRLEN as u16;
+
+ // Fill the message payload with the family name
+ let payload_start = nlattr_end;
+ let payload_end = payload_start + family_name.len();
+ data[payload_start..payload_end].copy_from_slice(family_name.as_bytes());
+
+ // Safe because we pass a valid, owned socket fd and a valid pointer/size for the buffer.
+ let _bytes_read = unsafe {
+ let res = libc::send(
+ self.sock.as_raw_fd(),
+ allocation.as_ptr() as *mut libc::c_void,
+ payload_end + 1,
+ 0,
+ );
+ if res < 0 {
+ error!("failed to send get_family_cmd");
+ return errno_result();
+ }
+ };
+
+ // Return the answer
+ match self.recv() {
+ Ok(msg) => return Ok(msg),
+ Err(e) => {
+ error!("recv get_family returned with error {}", e);
+ return Err(e);
+ }
+ };
+ }
+}
+
+fn parse_ctrl_group_name_and_id(
+ nested_nl_attr_data: NetlinkGenericDataIter,
+ group_name: &str,
+) -> Option<u32> {
+ let mut mcast_group_id: Option<u32> = None;
+
+ for nested_nl_attr in nested_nl_attr_data {
+ debug_pr!(
+ "\t\tmcast_grp: nlattr type {}, len {}",
+ nested_nl_attr._type,
+ nested_nl_attr.len
+ );
+
+ if nested_nl_attr._type == libc::CTRL_ATTR_MCAST_GRP_ID as u16 {
+ mcast_group_id = Some(u32::from_ne_bytes(nested_nl_attr.data.try_into().unwrap()));
+ debug_pr!("\t\t mcast group_id {}", mcast_group_id?);
+ }
+
+ if nested_nl_attr._type == libc::CTRL_ATTR_MCAST_GRP_NAME as u16 {
+ debug_pr!(
+ "\t\t mcast group name {}",
+ strip_padding(&nested_nl_attr.data)
+ );
+
+ // If the group name match and the group_id was set in previous iteration, return,
+ // valid for group_name, group_id
+ if group_name.eq(strip_padding(nested_nl_attr.data)) && mcast_group_id.is_some() {
+ debug_pr!(
+ "\t\t Got what we were looking for group_id = {} for {}",
+ mcast_group_id?,
+ group_name
+ );
+
+ return mcast_group_id;
+ }
+ }
+ }
+
+ None
+}
+
+/// Parse CTRL_ATTR_MCAST_GROUPS data in order to get multicast group id
+///
+/// On success, returns group_id for a given `group_name`
+///
+/// # Arguments
+///
+/// * `nl_attr_area` - Nested attributes area (CTRL_ATTR_MCAST_GROUPS data), where nl_attr's
+/// corresponding to specific groups are embed
+/// * `group_name` - String with group_name for which we are looking group_id
+///
+/// the CTRL_ATTR_MCAST_GROUPS data has nested attributes. Each of nested attribute is per
+/// multicast group attributes, which have another nested attributes: CTRL_ATTR_MCAST_GRP_NAME and
+/// CTRL_ATTR_MCAST_GRP_ID. Need to parse all of them to get mcast group id for a given group_name..
+///
+/// Illustrated layout:
+/// CTRL_ATTR_MCAST_GROUPS:
+/// GR1 (nl_attr._type = 1):
+/// CTRL_ATTR_MCAST_GRP_ID,
+/// CTRL_ATTR_MCAST_GRP_NAME,
+/// GR2 (nl_attr._type = 2):
+/// CTRL_ATTR_MCAST_GRP_ID,
+/// CTRL_ATTR_MCAST_GRP_NAME,
+/// ..
+///
+/// Unfortunately kernel implementation uses `nla_nest_start_noflag` for that
+/// purpose, which means that it never marked their nest attributes with NLA_F_NESTED flag.
+/// Therefore all this nesting stages need to be deduced based on specific nl_attr type.
+fn parse_ctrl_mcast_group_id(
+ nl_attr_area: NetlinkGenericDataIter,
+ group_name: &str,
+) -> Option<u32> {
+ // There may be multiple nested multicast groups, go through all of them.
+ // Each of nested group, has other nested nlattr:
+ // CTRL_ATTR_MCAST_GRP_ID
+ // CTRL_ATTR_MCAST_GRP_NAME
+ //
+ // which are further proceed by parse_ctrl_group_name_and_id
+ for nested_gr_nl_attr in nl_attr_area {
+ debug_pr!(
+ "\tmcast_groups: nlattr type(gr_nr) {}, len {}",
+ nested_gr_nl_attr._type,
+ nested_gr_nl_attr.len
+ );
+
+ let netlink_nested_attr = NetlinkGenericDataIter {
+ data: nested_gr_nl_attr.data,
+ };
+
+ if let Some(mcast_group_id) = parse_ctrl_group_name_and_id(netlink_nested_attr, group_name)
+ {
+ return Some(mcast_group_id);
+ }
+ }
+
+ None
+}
+
+// Like `CStr::from_bytes_with_nul` but strips any bytes starting from first '\0'-byte and
+// returns &str. Panics if `b` doesn't contain any '\0' bytes.
+fn strip_padding(b: &[u8]) -> &str {
+ // It would be nice if we could use memchr here but that's locked behind an unstable gate.
+ let pos = b
+ .iter()
+ .position(|&c| c == 0)
+ .expect("`b` doesn't contain any nul bytes");
+
+ str::from_utf8(&b[..pos]).unwrap()
+}
+
+pub struct NetlinkGenericRead {
+ allocation: LayoutAllocation,
+ len: usize,
+}
+
+impl NetlinkGenericRead {
+ pub fn iter(&self) -> NetlinkMessageIter {
+ // Safe because the data in allocation was initialized up to `self.len` by `recv()` and is
+ // sufficiently aligned.
+ let data = unsafe { &self.allocation.as_slice(self.len) };
+ NetlinkMessageIter { data }
+ }
+
+ /// Parse NetlinkGeneric response in order to get multicast group id
+ ///
+ /// On success, returns group_id for a given `group_name`
+ ///
+ /// # Arguments
+ ///
+ /// * `group_name` - String with group_name for which we are looking group_id
+ ///
+ /// Response from family_name_query (CTRL_CMD_GETFAMILY) is a netlink message with multiple
+ /// attributes encapsulated (some of them are nested). An example response layout is
+ /// illustrated below:
+ ///
+ /// {
+ /// CTRL_ATTR_FAMILY_NAME
+ /// CTRL_ATTR_FAMILY_ID
+ /// CTRL_ATTR_VERSION
+ /// ...
+ /// CTRL_ATTR_MCAST_GROUPS {
+ /// GR1 (nl_attr._type = 1) {
+ /// CTRL_ATTR_MCAST_GRP_ID *we need parse this attr to obtain group id used for
+ /// the group mask
+ /// CTRL_ATTR_MCAST_GRP_NAME *group_name that we need to match with
+ /// }
+ /// GR2 (nl_attr._type = 2) {
+ /// CTRL_ATTR_MCAST_GRP_ID
+ /// CTRL_ATTR_MCAST_GRP_NAME
+ /// }
+ /// ...
+ /// }
+ /// }
+ ///
+ pub fn get_multicast_group_id(&self, group_name: String) -> Option<u32> {
+ for netlink_msg in self.iter() {
+ debug_pr!(
+ "received type: {}, flags {}, pid {}, data {:?}",
+ netlink_msg._type,
+ netlink_msg.flags,
+ netlink_msg.pid,
+ netlink_msg.data
+ );
+
+ if netlink_msg._type != libc::GENL_ID_CTRL as u16 {
+ error!("Received not a generic netlink controller msg");
+ return None;
+ }
+
+ let netlink_data = NetlinkGenericDataIter {
+ data: &netlink_msg.data[GENL_HDRLEN..],
+ };
+ for nl_attr in netlink_data {
+ debug_pr!("nl_attr type {}, len {}", nl_attr._type, nl_attr.len);
+
+ if nl_attr._type == libc::CTRL_ATTR_MCAST_GROUPS as u16 {
+ let netlink_nested_attr = NetlinkGenericDataIter { data: nl_attr.data };
+
+ if let Some(mcast_group_id) =
+ parse_ctrl_mcast_group_id(netlink_nested_attr, &group_name)
+ {
+ return Some(mcast_group_id);
+ }
+ }
+ }
+ }
+ None
+ }
+}
diff --git a/base/src/unix/poll.rs b/base/src/unix/poll.rs
new file mode 100644
index 000000000..5e7e94738
--- /dev/null
+++ b/base/src/unix/poll.rs
@@ -0,0 +1,809 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ cell::{Cell, Ref, RefCell},
+ cmp::min,
+ fs::File,
+ i32, i64,
+ marker::PhantomData,
+ ptr::null_mut,
+ slice, thread,
+ time::Duration,
+};
+
+use libc::{
+ c_int, epoll_create1, epoll_ctl, epoll_event, epoll_wait, EPOLLHUP, EPOLLIN, EPOLLOUT,
+ EPOLLRDHUP, EPOLL_CLOEXEC, EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD,
+};
+
+use super::{errno_result, Result};
+use crate::{AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor, RawDescriptor};
+
+const POLL_CONTEXT_MAX_EVENTS: usize = 16;
+
+/// EpollEvents wraps raw epoll_events, it should only be used with EpollContext.
+pub struct EpollEvents(RefCell<[epoll_event; POLL_CONTEXT_MAX_EVENTS]>);
+
+impl EpollEvents {
+ pub fn new() -> EpollEvents {
+ EpollEvents(RefCell::new(
+ [epoll_event { events: 0, u64: 0 }; POLL_CONTEXT_MAX_EVENTS],
+ ))
+ }
+}
+
+impl Default for EpollEvents {
+ fn default() -> EpollEvents {
+ Self::new()
+ }
+}
+
+/// Trait for a token that can be associated with an `fd` in a `PollContext`.
+///
+/// Simple enums that have no or primitive variant data data can use the `#[derive(PollToken)]`
+/// custom derive to implement this trait. See
+/// [base_poll_token_derive::poll_token](../poll_token_derive/fn.poll_token.html) for details.
+pub trait PollToken {
+ /// Converts this token into a u64 that can be turned back into a token via `from_raw_token`.
+ fn as_raw_token(&self) -> u64;
+
+ /// Converts a raw token as returned from `as_raw_token` back into a token.
+ ///
+ /// It is invalid to give a raw token that was not returned via `as_raw_token` from the same
+ /// `Self`. The implementation can expect that this will never happen as a result of its usage
+ /// in `PollContext`.
+ fn from_raw_token(data: u64) -> Self;
+}
+
+impl PollToken for usize {
+ fn as_raw_token(&self) -> u64 {
+ *self as u64
+ }
+
+ fn from_raw_token(data: u64) -> Self {
+ data as Self
+ }
+}
+
+impl PollToken for u64 {
+ fn as_raw_token(&self) -> u64 {
+ *self as u64
+ }
+
+ fn from_raw_token(data: u64) -> Self {
+ data as Self
+ }
+}
+
+impl PollToken for u32 {
+ fn as_raw_token(&self) -> u64 {
+ u64::from(*self)
+ }
+
+ fn from_raw_token(data: u64) -> Self {
+ data as Self
+ }
+}
+
+impl PollToken for u16 {
+ fn as_raw_token(&self) -> u64 {
+ u64::from(*self)
+ }
+
+ fn from_raw_token(data: u64) -> Self {
+ data as Self
+ }
+}
+
+impl PollToken for u8 {
+ fn as_raw_token(&self) -> u64 {
+ u64::from(*self)
+ }
+
+ fn from_raw_token(data: u64) -> Self {
+ data as Self
+ }
+}
+
+impl PollToken for () {
+ fn as_raw_token(&self) -> u64 {
+ 0
+ }
+
+ fn from_raw_token(_data: u64) -> Self {}
+}
+
+/// An event returned by `PollContext::wait`.
+pub struct PollEvent<'a, T> {
+ event: &'a epoll_event,
+ token: PhantomData<T>, // Needed to satisfy usage of T
+}
+
+impl<'a, T: PollToken> PollEvent<'a, T> {
+ /// Gets the token associated in `PollContext::add` with this event.
+ pub fn token(&self) -> T {
+ T::from_raw_token(self.event.u64)
+ }
+
+ /// True if the `fd` associated with this token in `PollContext::add` is readable.
+ pub fn readable(&self) -> bool {
+ self.event.events & (EPOLLIN as u32) != 0
+ }
+
+ /// True if the `fd` associated with this token in `PollContext::add` is writable.
+ pub fn writable(&self) -> bool {
+ self.event.events & (EPOLLOUT as u32) != 0
+ }
+
+ /// True if the `fd` associated with this token in `PollContext::add` has been hungup on.
+ pub fn hungup(&self) -> bool {
+ self.event.events & ((EPOLLHUP | EPOLLRDHUP) as u32) != 0
+ }
+}
+
+/// An iterator over some (sub)set of events returned by `PollContext::wait`.
+pub struct PollEventIter<'a, I, T>
+where
+ I: Iterator<Item = &'a epoll_event>,
+{
+ mask: u32,
+ iter: I,
+ tokens: PhantomData<[T]>, // Needed to satisfy usage of T
+}
+
+impl<'a, I, T> Iterator for PollEventIter<'a, I, T>
+where
+ I: Iterator<Item = &'a epoll_event>,
+ T: PollToken,
+{
+ type Item = PollEvent<'a, T>;
+ fn next(&mut self) -> Option<Self::Item> {
+ let mask = self.mask;
+ self.iter
+ .find(|event| (event.events & mask) != 0)
+ .map(|event| PollEvent {
+ event,
+ token: PhantomData,
+ })
+ }
+}
+
+/// The list of event returned by `PollContext::wait`.
+pub struct PollEvents<'a, T> {
+ count: usize,
+ events: Ref<'a, [epoll_event; POLL_CONTEXT_MAX_EVENTS]>,
+ tokens: PhantomData<[T]>, // Needed to satisfy usage of T
+}
+
+impl<'a, T: PollToken> PollEvents<'a, T> {
+ /// Copies the events to an owned structure so the reference to this (and by extension
+ /// `PollContext`) can be dropped.
+ pub fn to_owned(&self) -> PollEventsOwned<T> {
+ PollEventsOwned {
+ count: self.count,
+ events: RefCell::new(*self.events),
+ tokens: PhantomData,
+ }
+ }
+
+ /// Iterates over each event.
+ pub fn iter(&self) -> PollEventIter<slice::Iter<epoll_event>, T> {
+ PollEventIter {
+ mask: 0xffff_ffff,
+ iter: self.events[..self.count].iter(),
+ tokens: PhantomData,
+ }
+ }
+
+ /// Iterates over each readable event.
+ pub fn iter_readable(&self) -> PollEventIter<slice::Iter<epoll_event>, T> {
+ PollEventIter {
+ mask: EPOLLIN as u32,
+ iter: self.events[..self.count].iter(),
+ tokens: PhantomData,
+ }
+ }
+
+ /// Iterates over each writable event.
+ pub fn iter_writable(&self) -> PollEventIter<slice::Iter<epoll_event>, T> {
+ PollEventIter {
+ mask: EPOLLOUT as u32,
+ iter: self.events[..self.count].iter(),
+ tokens: PhantomData,
+ }
+ }
+
+ /// Iterates over each hungup event.
+ pub fn iter_hungup(&self) -> PollEventIter<slice::Iter<epoll_event>, T> {
+ PollEventIter {
+ mask: (EPOLLHUP | EPOLLRDHUP) as u32,
+ iter: self.events[..self.count].iter(),
+ tokens: PhantomData,
+ }
+ }
+}
+
+impl<'a, T: PollToken> IntoIterator for &'a PollEvents<'_, T> {
+ type Item = PollEvent<'a, T>;
+ type IntoIter = PollEventIter<'a, slice::Iter<'a, epoll_event>, T>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.iter()
+ }
+}
+
+/// A deep copy of the event records from `PollEvents`.
+pub struct PollEventsOwned<T> {
+ count: usize,
+ events: RefCell<[epoll_event; POLL_CONTEXT_MAX_EVENTS]>,
+ tokens: PhantomData<T>, // Needed to satisfy usage of T
+}
+
+impl<T: PollToken> PollEventsOwned<T> {
+ /// Takes a reference to the events so that they can be iterated via methods in `PollEvents`.
+ pub fn as_ref(&self) -> PollEvents<T> {
+ PollEvents {
+ count: self.count,
+ events: self.events.borrow(),
+ tokens: PhantomData,
+ }
+ }
+}
+
+/// Watching events taken by PollContext.
+pub struct WatchingEvents(u32);
+
+impl WatchingEvents {
+ /// Returns empty Events.
+ #[inline(always)]
+ pub fn empty() -> WatchingEvents {
+ WatchingEvents(0)
+ }
+
+ /// Build Events from raw epoll events (defined in epoll_ctl(2)).
+ #[inline(always)]
+ pub fn new(raw: u32) -> WatchingEvents {
+ WatchingEvents(raw)
+ }
+
+ /// Set read events.
+ #[inline(always)]
+ pub fn set_read(self) -> WatchingEvents {
+ WatchingEvents(self.0 | EPOLLIN as u32)
+ }
+
+ /// Set write events.
+ #[inline(always)]
+ pub fn set_write(self) -> WatchingEvents {
+ WatchingEvents(self.0 | EPOLLOUT as u32)
+ }
+
+ /// Get the underlying epoll events.
+ pub fn get_raw(&self) -> u32 {
+ self.0
+ }
+}
+
+/// EpollContext wraps linux epoll. It provides similar interface to PollContext.
+/// It is thread safe while PollContext is not. It requires user to pass in a reference of
+/// EpollEvents while PollContext does not. Always use PollContext if you don't need to access the
+/// same epoll from different threads.
+pub struct EpollContext<T> {
+ epoll_ctx: File,
+ // Needed to satisfy usage of T
+ tokens: PhantomData<[T]>,
+}
+
+impl<T: PollToken> EpollContext<T> {
+ /// Creates a new `EpollContext`.
+ pub fn new() -> Result<EpollContext<T>> {
+ // Safe because we check the return value.
+ let epoll_fd = unsafe { epoll_create1(EPOLL_CLOEXEC) };
+ if epoll_fd < 0 {
+ return errno_result();
+ }
+ Ok(EpollContext {
+ epoll_ctx: unsafe { File::from_raw_descriptor(epoll_fd) },
+ tokens: PhantomData,
+ })
+ }
+
+ /// Creates a new `EpollContext` and adds the slice of `fd` and `token` tuples to the new
+ /// context.
+ ///
+ /// This is equivalent to calling `new` followed by `add_many`. If there is an error, this will
+ /// return the error instead of the new context.
+ pub fn build_with(fd_tokens: &[(&dyn AsRawDescriptor, T)]) -> Result<EpollContext<T>> {
+ let ctx = EpollContext::new()?;
+ ctx.add_many(fd_tokens)?;
+ Ok(ctx)
+ }
+
+ /// Adds the given slice of `fd` and `token` tuples to this context.
+ ///
+ /// This is equivalent to calling `add` with each `fd` and `token`. If there are any errors,
+ /// this method will stop adding `fd`s and return the first error, leaving this context in a
+ /// undefined state.
+ pub fn add_many(&self, fd_tokens: &[(&dyn AsRawDescriptor, T)]) -> Result<()> {
+ for (fd, token) in fd_tokens {
+ self.add(*fd, T::from_raw_token(token.as_raw_token()))?;
+ }
+ Ok(())
+ }
+
+ /// Adds the given `fd` to this context and associates the given `token` with the `fd`'s
+ /// readable events.
+ ///
+ /// A `fd` can only be added once and does not need to be kept open. If the `fd` is dropped and
+ /// there were no duplicated file descriptors (i.e. adding the same descriptor with a different
+ /// FD number) added to this context, events will not be reported by `wait` anymore.
+ pub fn add(&self, fd: &dyn AsRawDescriptor, token: T) -> Result<()> {
+ self.add_fd_with_events(fd, WatchingEvents::empty().set_read(), token)
+ }
+
+ /// Adds the given `fd` to this context, watching for the specified events and associates the
+ /// given 'token' with those events.
+ ///
+ /// A `fd` can only be added once and does not need to be kept open. If the `fd` is dropped and
+ /// there were no duplicated file descriptors (i.e. adding the same descriptor with a different
+ /// FD number) added to this context, events will not be reported by `wait` anymore.
+ pub fn add_fd_with_events(
+ &self,
+ fd: &dyn AsRawDescriptor,
+ events: WatchingEvents,
+ token: T,
+ ) -> Result<()> {
+ let mut evt = epoll_event {
+ events: events.get_raw(),
+ u64: token.as_raw_token(),
+ };
+ // Safe because we give a valid epoll FD and FD to watch, as well as a valid epoll_event
+ // structure. Then we check the return value.
+ let ret = unsafe {
+ epoll_ctl(
+ self.epoll_ctx.as_raw_descriptor(),
+ EPOLL_CTL_ADD,
+ fd.as_raw_descriptor(),
+ &mut evt,
+ )
+ };
+ if ret < 0 {
+ return errno_result();
+ };
+ Ok(())
+ }
+
+ /// If `fd` was previously added to this context, the watched events will be replaced with
+ /// `events` and the token associated with it will be replaced with the given `token`.
+ pub fn modify(&self, fd: &dyn AsRawDescriptor, events: WatchingEvents, token: T) -> Result<()> {
+ let mut evt = epoll_event {
+ events: events.0,
+ u64: token.as_raw_token(),
+ };
+ // Safe because we give a valid epoll FD and FD to modify, as well as a valid epoll_event
+ // structure. Then we check the return value.
+ let ret = unsafe {
+ epoll_ctl(
+ self.epoll_ctx.as_raw_descriptor(),
+ EPOLL_CTL_MOD,
+ fd.as_raw_descriptor(),
+ &mut evt,
+ )
+ };
+ if ret < 0 {
+ return errno_result();
+ };
+ Ok(())
+ }
+
+ /// Deletes the given `fd` from this context.
+ ///
+ /// If an `fd`'s token shows up in the list of hangup events, it should be removed using this
+ /// method or by closing/dropping (if and only if the fd was never dup()'d/fork()'d) the `fd`.
+ /// Failure to do so will cause the `wait` method to always return immediately, causing ~100%
+ /// CPU load.
+ pub fn delete(&self, fd: &dyn AsRawDescriptor) -> Result<()> {
+ // Safe because we give a valid epoll FD and FD to stop watching. Then we check the return
+ // value.
+ let ret = unsafe {
+ epoll_ctl(
+ self.epoll_ctx.as_raw_descriptor(),
+ EPOLL_CTL_DEL,
+ fd.as_raw_descriptor(),
+ null_mut(),
+ )
+ };
+ if ret < 0 {
+ return errno_result();
+ };
+ Ok(())
+ }
+
+ /// Waits for any events to occur in FDs that were previously added to this context.
+ ///
+ /// The events are level-triggered, meaning that if any events are unhandled (i.e. not reading
+ /// for readable events and not closing for hungup events), subsequent calls to `wait` will
+ /// return immediately. The consequence of not handling an event perpetually while calling
+ /// `wait` is that the callers loop will degenerated to busy loop polling, pinning a CPU to
+ /// ~100% usage.
+ pub fn wait<'a>(&self, events: &'a EpollEvents) -> Result<PollEvents<'a, T>> {
+ self.wait_timeout(events, Duration::new(i64::MAX as u64, 0))
+ }
+
+ /// Like `wait` except will only block for a maximum of the given `timeout`.
+ ///
+ /// This may return earlier than `timeout` with zero events if the duration indicated exceeds
+ /// system limits.
+ pub fn wait_timeout<'a>(
+ &self,
+ events: &'a EpollEvents,
+ timeout: Duration,
+ ) -> Result<PollEvents<'a, T>> {
+ let timeout_millis = if timeout.as_secs() as i64 == i64::max_value() {
+ // We make the convenient assumption that 2^63 seconds is an effectively unbounded time
+ // frame. This is meant to mesh with `wait` calling us with no timeout.
+ -1
+ } else {
+ // In cases where we the number of milliseconds would overflow an i32, we substitute the
+ // maximum timeout which is ~24.8 days.
+ let millis = timeout
+ .as_secs()
+ .checked_mul(1_000)
+ .and_then(|ms| ms.checked_add(u64::from(timeout.subsec_nanos()) / 1_000_000))
+ .unwrap_or(i32::max_value() as u64);
+ min(i32::max_value() as u64, millis) as i32
+ };
+ let ret = {
+ let mut epoll_events = events.0.borrow_mut();
+ let max_events = epoll_events.len() as c_int;
+ // Safe because we give an epoll context and a properly sized epoll_events array
+ // pointer, which we trust the kernel to fill in properly.
+ unsafe {
+ handle_eintr_errno!(epoll_wait(
+ self.epoll_ctx.as_raw_descriptor(),
+ &mut epoll_events[0],
+ max_events,
+ timeout_millis
+ ))
+ }
+ };
+ if ret < 0 {
+ return errno_result();
+ }
+ let epoll_events = events.0.borrow();
+ let events = PollEvents {
+ count: ret as usize,
+ events: epoll_events,
+ tokens: PhantomData,
+ };
+ Ok(events)
+ }
+}
+
+impl<T: PollToken> AsRawDescriptor for EpollContext<T> {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.epoll_ctx.as_raw_descriptor()
+ }
+}
+
+impl<T: PollToken> IntoRawDescriptor for EpollContext<T> {
+ fn into_raw_descriptor(self) -> RawDescriptor {
+ self.epoll_ctx.into_raw_descriptor()
+ }
+}
+
+/// Used to poll multiple objects that have file descriptors.
+///
+/// # Example
+///
+/// ```
+/// # use crate::platform::{Result, EventFd, PollContext, PollEvents};
+/// # fn test() -> Result<()> {
+/// let evt1 = EventFd::new()?;
+/// let evt2 = EventFd::new()?;
+/// evt2.write(1)?;
+///
+/// let ctx: PollContext<u32> = PollContext::new()?;
+/// ctx.add(&evt1, 1)?;
+/// ctx.add(&evt2, 2)?;
+///
+/// let pollevents: PollEvents<u32> = ctx.wait()?;
+/// let tokens: Vec<u32> = pollevents.iter_readable().map(|e| e.token()).collect();
+/// assert_eq!(&tokens[..], &[2]);
+/// # Ok(())
+/// # }
+/// ```
+pub struct PollContext<T> {
+ epoll_ctx: EpollContext<T>,
+
+ // We use a RefCell here so that the `wait` method only requires an immutable self reference
+ // while returning the events (encapsulated by PollEvents). Without the RefCell, `wait` would
+ // hold a mutable reference that lives as long as its returned reference (i.e. the PollEvents),
+ // even though that reference is immutable. This is terribly inconvenient for the caller because
+ // the borrow checking would prevent them from using `delete` and `add` while the events are in
+ // scope.
+ events: EpollEvents,
+
+ // Hangup busy loop detection variables. See `check_for_hungup_busy_loop`.
+ hangups: Cell<usize>,
+ max_hangups: Cell<usize>,
+}
+
+impl<T: PollToken> PollContext<T> {
+ /// Creates a new `PollContext`.
+ pub fn new() -> Result<PollContext<T>> {
+ Ok(PollContext {
+ epoll_ctx: EpollContext::new()?,
+ events: EpollEvents::new(),
+ hangups: Cell::new(0),
+ max_hangups: Cell::new(0),
+ })
+ }
+
+ /// Creates a new `PollContext` and adds the slice of `fd` and `token` tuples to the new
+ /// context.
+ ///
+ /// This is equivalent to calling `new` followed by `add_many`. If there is an error, this will
+ /// return the error instead of the new context.
+ pub fn build_with(fd_tokens: &[(&dyn AsRawDescriptor, T)]) -> Result<PollContext<T>> {
+ let ctx = PollContext::new()?;
+ ctx.add_many(fd_tokens)?;
+ Ok(ctx)
+ }
+
+ /// Adds the given slice of `fd` and `token` tuples to this context.
+ ///
+ /// This is equivalent to calling `add` with each `fd` and `token`. If there are any errors,
+ /// this method will stop adding `fd`s and return the first error, leaving this context in a
+ /// undefined state.
+ pub fn add_many(&self, fd_tokens: &[(&dyn AsRawDescriptor, T)]) -> Result<()> {
+ for (fd, token) in fd_tokens {
+ self.add(*fd, T::from_raw_token(token.as_raw_token()))?;
+ }
+ Ok(())
+ }
+
+ /// Adds the given `fd` to this context and associates the given `token` with the `fd`'s
+ /// readable events.
+ ///
+ /// A `fd` can only be added once and does not need to be kept open. If the `fd` is dropped and
+ /// there were no duplicated file descriptors (i.e. adding the same descriptor with a different
+ /// FD number) added to this context, events will not be reported by `wait` anymore.
+ pub fn add(&self, fd: &dyn AsRawDescriptor, token: T) -> Result<()> {
+ self.add_fd_with_events(fd, WatchingEvents::empty().set_read(), token)
+ }
+
+ /// Adds the given `fd` to this context, watching for the specified events and associates the
+ /// given 'token' with those events.
+ ///
+ /// A `fd` can only be added once and does not need to be kept open. If the `fd` is dropped and
+ /// there were no duplicated file descriptors (i.e. adding the same descriptor with a different
+ /// FD number) added to this context, events will not be reported by `wait` anymore.
+ pub fn add_fd_with_events(
+ &self,
+ fd: &dyn AsRawDescriptor,
+ events: WatchingEvents,
+ token: T,
+ ) -> Result<()> {
+ self.epoll_ctx.add_fd_with_events(fd, events, token)?;
+ self.hangups.set(0);
+ self.max_hangups.set(self.max_hangups.get() + 1);
+ Ok(())
+ }
+
+ /// If `fd` was previously added to this context, the watched events will be replaced with
+ /// `events` and the token associated with it will be replaced with the given `token`.
+ pub fn modify(&self, fd: &dyn AsRawDescriptor, events: WatchingEvents, token: T) -> Result<()> {
+ self.epoll_ctx.modify(fd, events, token)
+ }
+
+ /// Deletes the given `fd` from this context.
+ ///
+ /// If an `fd`'s token shows up in the list of hangup events, it should be removed using this
+ /// method or by closing/dropping (if and only if the fd was never dup()'d/fork()'d) the `fd`.
+ /// Failure to do so will cause the `wait` method to always return immediately, causing ~100%
+ /// CPU load.
+ pub fn delete(&self, fd: &dyn AsRawDescriptor) -> Result<()> {
+ self.epoll_ctx.delete(fd)?;
+ self.hangups.set(0);
+ self.max_hangups.set(self.max_hangups.get() - 1);
+ Ok(())
+ }
+
+ // This method determines if the the user of wait is misusing the `PollContext` by leaving FDs
+ // in this `PollContext` that have been shutdown or hungup on. Such an FD will cause `wait` to
+ // return instantly with a hungup event. If that FD is perpetually left in this context, a busy
+ // loop burning ~100% of one CPU will silently occur with no human visible malfunction.
+ //
+ // How do we know if the client of this context is ignoring hangups? A naive implementation
+ // would trigger if consecutive wait calls yield hangup events, but there are legitimate cases
+ // for this, such as two distinct sockets becoming hungup across two consecutive wait calls. A
+ // smarter implementation would only trigger if `delete` wasn't called between waits that
+ // yielded hangups. Sadly `delete` isn't the only way to remove an FD from this context. The
+ // other way is for the client to close the hungup FD, which automatically removes it from this
+ // context. Assuming that the client always uses close, this implementation would too eagerly
+ // trigger.
+ //
+ // The implementation used here keeps an upper bound of FDs in this context using a counter
+ // hooked into add/delete (which is imprecise because close can also remove FDs without us
+ // knowing). The number of consecutive (no add or delete in between) hangups yielded by wait
+ // calls is counted and compared to the upper bound. If the upper bound is exceeded by the
+ // consecutive hangups, the implementation triggers the check and logs.
+ //
+ // This implementation has false negatives because the upper bound can be completely too high,
+ // in the worst case caused by only using close instead of delete. However, this method has the
+ // advantage of always triggering eventually genuine busy loop cases, requires no dynamic
+ // allocations, is fast and constant time to compute, and has no false positives.
+ fn check_for_hungup_busy_loop(&self, new_hangups: usize) {
+ let old_hangups = self.hangups.get();
+ let max_hangups = self.max_hangups.get();
+ if old_hangups <= max_hangups && old_hangups + new_hangups > max_hangups {
+ warn!(
+ "busy poll wait loop with hungup FDs detected on thread {}",
+ thread::current().name().unwrap_or("")
+ );
+ // This panic is helpful for tests of this functionality.
+ #[cfg(test)]
+ panic!("hungup busy loop detected");
+ }
+ self.hangups.set(old_hangups + new_hangups);
+ }
+
+ /// Waits for any events to occur in FDs that were previously added to this context.
+ ///
+ /// The events are level-triggered, meaning that if any events are unhandled (i.e. not reading
+ /// for readable events and not closing for hungup events), subsequent calls to `wait` will
+ /// return immediately. The consequence of not handling an event perpetually while calling
+ /// `wait` is that the callers loop will degenerated to busy loop polling, pinning a CPU to
+ /// ~100% usage.
+ ///
+ /// # Panics
+ /// Panics if the returned `PollEvents` structure is not dropped before subsequent `wait` calls.
+ pub fn wait(&self) -> Result<PollEvents<T>> {
+ self.wait_timeout(Duration::new(i64::MAX as u64, 0))
+ }
+
+ /// Like `wait` except will only block for a maximum of the given `timeout`.
+ ///
+ /// This may return earlier than `timeout` with zero events if the duration indicated exceeds
+ /// system limits.
+ pub fn wait_timeout(&self, timeout: Duration) -> Result<PollEvents<T>> {
+ let events = self.epoll_ctx.wait_timeout(&self.events, timeout)?;
+ let hangups = events.iter_hungup().count();
+ self.check_for_hungup_busy_loop(hangups);
+ Ok(events)
+ }
+}
+
+impl<T: PollToken> AsRawDescriptor for PollContext<T> {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.epoll_ctx.as_raw_descriptor()
+ }
+}
+
+impl<T: PollToken> IntoRawDescriptor for PollContext<T> {
+ fn into_raw_descriptor(self) -> RawDescriptor {
+ self.epoll_ctx.into_raw_descriptor()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{super::EventFd, *};
+ use base_poll_token_derive::PollToken;
+ use std::{os::unix::net::UnixStream, time::Instant};
+
+ #[test]
+ fn poll_context() {
+ let evt1 = EventFd::new().unwrap();
+ let evt2 = EventFd::new().unwrap();
+ evt1.write(1).unwrap();
+ evt2.write(1).unwrap();
+ let ctx: PollContext<u32> = PollContext::build_with(&[(&evt1, 1), (&evt2, 2)]).unwrap();
+
+ let mut evt_count = 0;
+ while evt_count < 2 {
+ for event in ctx.wait().unwrap().iter_readable() {
+ evt_count += 1;
+ match event.token() {
+ 1 => {
+ evt1.read().unwrap();
+ ctx.delete(&evt1).unwrap();
+ }
+ 2 => {
+ evt2.read().unwrap();
+ ctx.delete(&evt2).unwrap();
+ }
+ _ => panic!("unexpected token"),
+ };
+ }
+ }
+ assert_eq!(evt_count, 2);
+ }
+
+ #[test]
+ fn poll_context_overflow() {
+ const EVT_COUNT: usize = POLL_CONTEXT_MAX_EVENTS * 2 + 1;
+ let ctx: PollContext<usize> = PollContext::new().unwrap();
+ let mut evts = Vec::with_capacity(EVT_COUNT);
+ for i in 0..EVT_COUNT {
+ let evt = EventFd::new().unwrap();
+ evt.write(1).unwrap();
+ ctx.add(&evt, i).unwrap();
+ evts.push(evt);
+ }
+ let mut evt_count = 0;
+ while evt_count < EVT_COUNT {
+ for event in ctx.wait().unwrap().iter_readable() {
+ evts[event.token()].read().unwrap();
+ evt_count += 1;
+ }
+ }
+ }
+
+ #[test]
+ #[should_panic]
+ fn poll_context_hungup() {
+ let (s1, s2) = UnixStream::pair().unwrap();
+ let ctx: PollContext<u32> = PollContext::new().unwrap();
+ ctx.add(&s1, 1).unwrap();
+
+ // Causes s1 to receive hangup events, which we purposefully ignore to trip the detection
+ // logic in `PollContext`.
+ drop(s2);
+
+ // Should easily panic within this many iterations.
+ for _ in 0..1000 {
+ ctx.wait().unwrap();
+ }
+ }
+
+ #[test]
+ fn poll_context_timeout() {
+ let ctx: PollContext<u32> = PollContext::new().unwrap();
+ let dur = Duration::from_millis(10);
+ let start_inst = Instant::now();
+ ctx.wait_timeout(dur).unwrap();
+ assert!(start_inst.elapsed() >= dur);
+ }
+
+ #[test]
+ #[allow(dead_code)]
+ fn poll_token_derive() {
+ #[derive(PollToken)]
+ enum EmptyToken {}
+
+ #[derive(PartialEq, Debug, PollToken)]
+ enum Token {
+ Alpha,
+ Beta,
+ // comments
+ Gamma(u32),
+ Delta { index: usize },
+ Omega,
+ }
+
+ assert_eq!(
+ Token::from_raw_token(Token::Alpha.as_raw_token()),
+ Token::Alpha
+ );
+ assert_eq!(
+ Token::from_raw_token(Token::Beta.as_raw_token()),
+ Token::Beta
+ );
+ assert_eq!(
+ Token::from_raw_token(Token::Gamma(55).as_raw_token()),
+ Token::Gamma(55)
+ );
+ assert_eq!(
+ Token::from_raw_token(Token::Delta { index: 100 }.as_raw_token()),
+ Token::Delta { index: 100 }
+ );
+ assert_eq!(
+ Token::from_raw_token(Token::Omega.as_raw_token()),
+ Token::Omega
+ );
+ }
+}
diff --git a/sys_util/src/priority.rs b/base/src/unix/priority.rs
index 77dfe2946..e02ab753c 100644
--- a/sys_util/src/priority.rs
+++ b/base/src/unix/priority.rs
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use crate::{errno_result, Result};
+use super::{errno_result, Result};
/// Enables real time thread priorities in the current thread up to `limit`.
pub fn set_rt_prio_limit(limit: u64) -> Result<()> {
diff --git a/base/src/unix/rand.rs b/base/src/unix/rand.rs
new file mode 100644
index 000000000..04b1b6f38
--- /dev/null
+++ b/base/src/unix/rand.rs
@@ -0,0 +1,112 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Rust implementation of functionality parallel to libchrome's base/rand_util.h.
+
+use std::{thread::sleep, time::Duration};
+
+use libc::{c_uint, c_void};
+
+use super::{errno_result, Result};
+use crate::handle_eintr_errno;
+
+/// How long to wait before calling getrandom again if it does not return
+/// enough bytes.
+const POLL_INTERVAL: Duration = Duration::from_millis(50);
+
+/// Represents whether or not the random bytes are pulled from the source of
+/// /dev/random or /dev/urandom.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Source {
+ // This is the default and uses the same source as /dev/urandom.
+ Pseudorandom,
+ // This uses the same source as /dev/random and may be.
+ Random,
+}
+
+impl Default for Source {
+ fn default() -> Self {
+ Source::Pseudorandom
+ }
+}
+
+impl Source {
+ fn to_getrandom_flags(&self) -> c_uint {
+ match self {
+ Source::Random => libc::GRND_RANDOM,
+ Source::Pseudorandom => 0,
+ }
+ }
+}
+
+/// Fills `output` completely with random bytes from the specified `source`.
+pub fn rand_bytes(mut output: &mut [u8], source: Source) -> Result<()> {
+ if output.is_empty() {
+ return Ok(());
+ }
+
+ loop {
+ // Safe because output is mutable and the writes are limited by output.len().
+ let bytes = handle_eintr_errno!(unsafe {
+ libc::getrandom(
+ output.as_mut_ptr() as *mut c_void,
+ output.len(),
+ source.to_getrandom_flags(),
+ )
+ });
+
+ if bytes < 0 {
+ return errno_result();
+ }
+ if bytes as usize == output.len() {
+ return Ok(());
+ }
+
+ // Wait for more entropy and try again for the remaining bytes.
+ sleep(POLL_INTERVAL);
+ output = &mut output[bytes as usize..];
+ }
+}
+
+/// Allocates a vector of length `len` filled with random bytes from the
+/// specified `source`.
+pub fn rand_vec(len: usize, source: Source) -> Result<Vec<u8>> {
+ let mut rand = Vec::with_capacity(len);
+ if len == 0 {
+ return Ok(rand);
+ }
+
+ // Safe because rand will either be initialized by getrandom or dropped.
+ unsafe { rand.set_len(len) };
+ rand_bytes(rand.as_mut_slice(), source)?;
+ Ok(rand)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const TEST_SIZE: usize = 64;
+
+ // ANDROID: b/227794338
+// #[test]
+// fn randbytes_success() {
+// let mut rand = vec![0u8; TEST_SIZE];
+// rand_bytes(&mut rand, Source::Pseudorandom).unwrap();
+// assert_ne!(&rand, &[0u8; TEST_SIZE]);
+// }
+
+// #[test]
+// fn randvec_success() {
+// let rand = rand_vec(TEST_SIZE, Source::Pseudorandom).unwrap();
+// assert_eq!(rand.len(), TEST_SIZE);
+// assert_ne!(&rand, &[0u8; TEST_SIZE]);
+// }
+
+// #[test]
+// fn sourcerandom_success() {
+// let rand = rand_vec(TEST_SIZE, Source::Random).unwrap();
+// assert_ne!(&rand, &[0u8; TEST_SIZE]);
+// }
+}
diff --git a/sys_util/src/raw_fd.rs b/base/src/unix/raw_fd.rs
index 05a762f28..05a762f28 100644
--- a/sys_util/src/raw_fd.rs
+++ b/base/src/unix/raw_fd.rs
diff --git a/base/src/unix/read_dir.rs b/base/src/unix/read_dir.rs
new file mode 100644
index 000000000..663204a33
--- /dev/null
+++ b/base/src/unix/read_dir.rs
@@ -0,0 +1,148 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{ffi::CStr, io::Result, mem::size_of, os::unix::io::AsRawFd};
+
+use data_model::DataInit;
+
+use crate::syscall;
+
+#[repr(C, packed)]
+#[derive(Clone, Copy)]
+struct LinuxDirent64 {
+ d_ino: libc::ino64_t,
+ d_off: libc::off64_t,
+ d_reclen: libc::c_ushort,
+ d_ty: libc::c_uchar,
+}
+unsafe impl DataInit for LinuxDirent64 {}
+
+pub struct DirEntry<'r> {
+ pub ino: libc::ino64_t,
+ pub offset: u64,
+ pub type_: u8,
+ pub name: &'r CStr,
+}
+
+pub struct ReadDir<'d, D> {
+ buf: [u8; 256],
+ dir: &'d mut D,
+ current: usize,
+ end: usize,
+}
+
+impl<'d, D: AsRawFd> ReadDir<'d, D> {
+ /// Return the next directory entry. This is implemented as a separate method rather than via
+ /// the `Iterator` trait because rust doesn't currently support generic associated types.
+ #[allow(clippy::should_implement_trait)]
+ pub fn next(&mut self) -> Option<Result<DirEntry>> {
+ if self.current >= self.end {
+ let res: Result<libc::c_long> = syscall!(unsafe {
+ libc::syscall(
+ libc::SYS_getdents64,
+ self.dir.as_raw_fd(),
+ self.buf.as_mut_ptr() as *mut LinuxDirent64,
+ self.buf.len() as libc::c_int,
+ )
+ })
+ .map_err(|e| e.into());
+ match res {
+ Ok(end) => {
+ self.current = 0;
+ self.end = end as usize;
+ }
+ Err(e) => return Some(Err(e)),
+ }
+ }
+
+ let rem = &self.buf[self.current..self.end];
+ if rem.is_empty() {
+ return None;
+ }
+
+ // We only use debug asserts here because these values are coming from the kernel and we
+ // trust them implicitly.
+ debug_assert!(
+ rem.len() >= size_of::<LinuxDirent64>(),
+ "not enough space left in `rem`"
+ );
+
+ let (front, back) = rem.split_at(size_of::<LinuxDirent64>());
+
+ let dirent64 =
+ LinuxDirent64::from_slice(front).expect("unable to get LinuxDirent64 from slice");
+
+ let namelen = dirent64.d_reclen as usize - size_of::<LinuxDirent64>();
+ debug_assert!(namelen <= back.len(), "back is smaller than `namelen`");
+
+ // The kernel will pad the name with additional nul bytes until it is 8-byte aligned so
+ // we need to strip those off here.
+ let name = strip_padding(&back[..namelen]);
+ let entry = DirEntry {
+ ino: dirent64.d_ino,
+ offset: dirent64.d_off as u64,
+ type_: dirent64.d_ty,
+ name,
+ };
+
+ debug_assert!(
+ rem.len() >= dirent64.d_reclen as usize,
+ "rem is smaller than `d_reclen`"
+ );
+ self.current += dirent64.d_reclen as usize;
+ Some(Ok(entry))
+ }
+}
+
+pub fn read_dir<D: AsRawFd>(dir: &mut D, offset: libc::off64_t) -> Result<ReadDir<D>> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ syscall!(unsafe { libc::lseek64(dir.as_raw_fd(), offset, libc::SEEK_SET) })?;
+
+ Ok(ReadDir {
+ buf: [0u8; 256],
+ dir,
+ current: 0,
+ end: 0,
+ })
+}
+
+// Like `CStr::from_bytes_with_nul` but strips any bytes after the first '\0'-byte. Panics if `b`
+// doesn't contain any '\0' bytes.
+fn strip_padding(b: &[u8]) -> &CStr {
+ // It would be nice if we could use memchr here but that's locked behind an unstable gate.
+ let pos = b
+ .iter()
+ .position(|&c| c == 0)
+ .expect("`b` doesn't contain any nul bytes");
+
+ // Safe because we are creating this string with the first nul-byte we found so we can
+ // guarantee that it is nul-terminated and doesn't contain any interior nuls.
+ unsafe { CStr::from_bytes_with_nul_unchecked(&b[..pos + 1]) }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn padded_cstrings() {
+ assert_eq!(strip_padding(b".\0\0\0\0\0\0\0").to_bytes(), b".");
+ assert_eq!(strip_padding(b"..\0\0\0\0\0\0").to_bytes(), b"..");
+ assert_eq!(
+ strip_padding(b"normal cstring\0").to_bytes(),
+ b"normal cstring"
+ );
+ assert_eq!(strip_padding(b"\0\0\0\0").to_bytes(), b"");
+ assert_eq!(
+ strip_padding(b"interior\0nul bytes\0\0\0").to_bytes(),
+ b"interior"
+ );
+ }
+
+ #[test]
+ #[should_panic(expected = "`b` doesn't contain any nul bytes")]
+ fn no_nul_byte() {
+ strip_padding(b"no nul bytes in string");
+ }
+}
diff --git a/base/src/unix/sched.rs b/base/src/unix/sched.rs
new file mode 100644
index 000000000..a5c672ff6
--- /dev/null
+++ b/base/src/unix/sched.rs
@@ -0,0 +1,143 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Wrappers for CPU affinity functions.
+
+use std::{iter::FromIterator, mem};
+
+use libc::{
+ cpu_set_t, prctl, sched_getaffinity, sched_setaffinity, CPU_ISSET, CPU_SET, CPU_SETSIZE,
+ CPU_ZERO, EINVAL,
+};
+
+use super::{errno_result, Error, Result};
+
+// This is needed because otherwise the compiler will complain that the
+// impl doesn't reference any types from inside this crate.
+struct CpuSet(cpu_set_t);
+
+impl CpuSet {
+ pub fn new() -> CpuSet {
+ // cpu_set_t is a C struct and can be safely initialized with zeroed memory.
+ let mut cpuset: cpu_set_t = unsafe { mem::MaybeUninit::zeroed().assume_init() };
+ // Safe because we pass a valid cpuset pointer.
+ unsafe { CPU_ZERO(&mut cpuset) };
+ CpuSet(cpuset)
+ }
+
+ pub fn to_cpus(&self) -> Vec<usize> {
+ let mut cpus = Vec::new();
+ for i in 0..(CPU_SETSIZE as usize) {
+ if unsafe { CPU_ISSET(i, &self.0) } {
+ cpus.push(i);
+ }
+ }
+ cpus
+ }
+}
+
+impl FromIterator<usize> for CpuSet {
+ fn from_iter<I: IntoIterator<Item = usize>>(cpus: I) -> Self {
+ // cpu_set_t is a C struct and can be safely initialized with zeroed memory.
+ let mut cpuset: cpu_set_t = unsafe { mem::zeroed() };
+ // Safe because we pass a valid cpuset pointer.
+ unsafe { CPU_ZERO(&mut cpuset) };
+ for cpu in cpus {
+ // Safe because we pass a valid cpuset pointer and cpu index.
+ unsafe { CPU_SET(cpu, &mut cpuset) };
+ }
+ CpuSet(cpuset)
+ }
+}
+
+/// Set the CPU affinity of the current thread to a given set of CPUs.
+///
+/// # Examples
+///
+/// Set the calling thread's CPU affinity so it will run on only CPUs
+/// 0, 1, 5, and 6.
+///
+/// ```
+/// # use crate::platform::set_cpu_affinity;
+/// set_cpu_affinity(vec![0, 1, 5, 6]).unwrap();
+/// ```
+pub fn set_cpu_affinity<I: IntoIterator<Item = usize>>(cpus: I) -> Result<()> {
+ let CpuSet(cpuset) = cpus
+ .into_iter()
+ .map(|cpu| {
+ if cpu < CPU_SETSIZE as usize {
+ Ok(cpu)
+ } else {
+ Err(Error::new(EINVAL))
+ }
+ })
+ .collect::<Result<CpuSet>>()?;
+
+ // Safe because we pass 0 for the current thread, and cpuset is a valid pointer and only
+ // used for the duration of this call.
+ let res = unsafe { sched_setaffinity(0, mem::size_of_val(&cpuset), &cpuset) };
+
+ if res != 0 {
+ errno_result()
+ } else {
+ Ok(())
+ }
+}
+
+pub fn get_cpu_affinity() -> Result<Vec<usize>> {
+ let mut cpu_set = CpuSet::new();
+
+ // Safe because we pass 0 for the current thread, and cpu_set.0 is a valid pointer and only
+ // used for the duration of this call.
+ crate::syscall!(unsafe { sched_getaffinity(0, mem::size_of_val(&cpu_set.0), &mut cpu_set.0) })?;
+
+ Ok(cpu_set.to_cpus())
+}
+
+/// Enable experimental core scheduling for the current thread.
+///
+/// If successful, the kernel should not schedule this thread with any other thread within the same
+/// SMT core. Because this is experimental, this will return success on kernels which do not support
+/// this function.
+pub fn enable_core_scheduling() -> Result<()> {
+ const PR_SCHED_CORE: i32 = 62;
+ const PR_SCHED_CORE_CREATE: i32 = 1;
+
+ #[allow(clippy::upper_case_acronyms, non_camel_case_types, dead_code)]
+ /// Specifies the scope of the pid parameter of `PR_SCHED_CORE`.
+ enum pid_type {
+ /// `PID` refers to threads.
+ PIDTYPE_PID,
+ /// `TGPID` refers to a process.
+ PIDTYPE_TGID,
+ /// `TGPID` refers to a process group.
+ PIDTYPE_PGID,
+ }
+
+ let ret = match unsafe {
+ prctl(
+ PR_SCHED_CORE,
+ PR_SCHED_CORE_CREATE,
+ 0, // id of target task, 0 indicates current task
+ pid_type::PIDTYPE_PID as i32, // PID scopes to this thread only
+ 0, // ignored by PR_SCHED_CORE_CREATE command
+ )
+ } {
+ #[cfg(feature = "chromeos")]
+ -1 => {
+ // Chrome OS has an pre-upstream version of this functionality which might work.
+ const PR_SET_CORE_SCHED: i32 = 0x200;
+ unsafe { prctl(PR_SET_CORE_SCHED, 1) }
+ }
+ ret => ret,
+ };
+ if ret == -1 {
+ let error = Error::last();
+ // prctl returns EINVAL for unknown functions, which we will ignore for now.
+ if error.errno() != libc::EINVAL {
+ return Err(error);
+ }
+ }
+ Ok(())
+}
diff --git a/sys_util/src/scoped_path.rs b/base/src/unix/scoped_path.rs
index 745137eae..745137eae 100644
--- a/sys_util/src/scoped_path.rs
+++ b/base/src/unix/scoped_path.rs
diff --git a/sys_util/src/scoped_signal_handler.rs b/base/src/unix/scoped_signal_handler.rs
index 76286161c..b4d387df9 100644
--- a/sys_util/src/scoped_signal_handler.rs
+++ b/base/src/unix/scoped_signal_handler.rs
@@ -4,55 +4,47 @@
//! Provides a struct for registering signal handlers that get cleared on drop.
-use std::convert::TryFrom;
-use std::fmt::{self, Display};
-use std::io::{Cursor, Write};
-use std::panic::catch_unwind;
-use std::result;
+use std::{
+ convert::TryFrom,
+ fmt,
+ io::{Cursor, Write},
+ panic::catch_unwind,
+ result,
+};
use libc::{c_int, c_void, STDERR_FILENO};
-
-use crate::errno;
-use crate::signal::{
- clear_signal_handler, has_default_signal_handler, register_signal_handler, wait_for_signal,
- Signal,
+use remain::sorted;
+use thiserror::Error;
+
+use super::{
+ signal::{
+ clear_signal_handler, has_default_signal_handler, register_signal_handler, wait_for_signal,
+ Signal,
+ },
+ Error as ErrnoError,
};
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- /// Sigaction failed.
- Sigaction(Signal, errno::Error),
- /// Failed to check if signal has the default signal handler.
- HasDefaultSignalHandler(Signal, errno::Error),
- /// Failed to register a signal handler.
- RegisterSignalHandler(Signal, errno::Error),
- /// Signal already has a handler.
- HandlerAlreadySet(Signal),
/// Already waiting for interrupt.
+ #[error("already waiting for interrupt.")]
AlreadyWaiting,
+ /// Signal already has a handler.
+ #[error("signal handler already set for {0:?}")]
+ HandlerAlreadySet(Signal),
+ /// Failed to check if signal has the default signal handler.
+ #[error("failed to check the signal handler for {0:?}: {1}")]
+ HasDefaultSignalHandler(Signal, ErrnoError),
+ /// Failed to register a signal handler.
+ #[error("failed to register a signal handler for {0:?}: {1}")]
+ RegisterSignalHandler(Signal, ErrnoError),
+ /// Sigaction failed.
+ #[error("sigaction failed for {0:?}: {1}")]
+ Sigaction(Signal, ErrnoError),
/// Failed to wait for signal.
- WaitForSignal(errno::Error),
-}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- Sigaction(s, e) => write!(f, "sigaction failed for {0:?}: {1}", s, e),
- HasDefaultSignalHandler(s, e) => {
- write!(f, "failed to check the signal handler for {0:?}: {1}", s, e)
- }
- RegisterSignalHandler(s, e) => write!(
- f,
- "failed to register a signal handler for {0:?}: {1}",
- s, e
- ),
- HandlerAlreadySet(s) => write!(f, "signal handler already set for {0:?}", s),
- AlreadyWaiting => write!(f, "already waiting for interrupt."),
- WaitForSignal(e) => write!(f, "wait_for_signal failed: {0}", e),
- }
- }
+ #[error("wait_for_signal failed: {0}")]
+ WaitForSignal(ErrnoError),
}
pub type Result<T> = result::Result<T, Error>;
@@ -189,18 +181,22 @@ pub fn wait_for_interrupt() -> Result<()> {
mod tests {
use super::*;
- use std::fs::File;
- use std::io::{BufRead, BufReader};
- use std::mem::zeroed;
- use std::ptr::{null, null_mut};
- use std::sync::atomic::{AtomicI32, AtomicUsize, Ordering};
- use std::sync::{Arc, Mutex, MutexGuard, Once};
- use std::thread::{sleep, spawn};
- use std::time::{Duration, Instant};
+ use std::{
+ fs::File,
+ io::{BufRead, BufReader},
+ mem::zeroed,
+ ptr::{null, null_mut},
+ sync::{
+ atomic::{AtomicI32, AtomicUsize, Ordering},
+ Arc, Mutex, MutexGuard, Once,
+ },
+ thread::{sleep, spawn},
+ time::{Duration, Instant},
+ };
use libc::sigaction;
- use crate::{gettid, kill, Pid};
+ use super::super::{gettid, kill, Pid};
const TEST_SIGNAL: Signal = Signal::User1;
const TEST_SIGNALS: &[Signal] = &[Signal::User1, Signal::User2];
@@ -231,7 +227,7 @@ mod tests {
let mut sigact: sigaction = unsafe { zeroed() };
if unsafe { sigaction(signal.into(), null(), &mut sigact) } < 0 {
- Err(Error::Sigaction(signal, errno::Error::last()))
+ Err(Error::Sigaction(signal, ErrnoError::last()))
} else {
Ok(sigact)
}
@@ -241,7 +237,7 @@ mod tests {
/// This is only safe if the signal handler set in sigaction is safe.
unsafe fn restore_sigaction(signal: Signal, sigact: sigaction) -> Result<sigaction> {
if sigaction(signal.into(), &sigact, null_mut()) < 0 {
- Err(Error::Sigaction(signal, errno::Error::last()))
+ Err(Error::Sigaction(signal, ErrnoError::last()))
} else {
Ok(sigact)
}
@@ -324,7 +320,7 @@ mod tests {
// Safe because TestHandler is async-signal safe.
assert!(matches!(
- ScopedSignalHandler::new::<TestHandler>(&TEST_SIGNALS),
+ ScopedSignalHandler::new::<TestHandler>(TEST_SIGNALS),
Err(Error::HandlerAlreadySet(Signal::User1))
));
@@ -353,18 +349,18 @@ mod tests {
/// Query /proc/${tid}/status for its State and check if it is either S (sleeping) or in
/// D (disk sleep).
- fn thread_is_sleeping(tid: Pid) -> result::Result<bool, errno::Error> {
+ fn thread_is_sleeping(tid: Pid) -> result::Result<bool, ErrnoError> {
const PREFIX: &str = "State:";
let mut status_reader = BufReader::new(File::open(format!("/proc/{}/status", tid))?);
let mut line = String::new();
loop {
let count = status_reader.read_line(&mut line)?;
if count == 0 {
- return Err(errno::Error::new(libc::EIO));
+ return Err(ErrnoError::new(libc::EIO));
}
- if line.starts_with(PREFIX) {
+ if let Some(stripped) = line.strip_prefix(PREFIX) {
return Ok(matches!(
- line[PREFIX.len()..].trim_start().chars().next(),
+ stripped.trim_start().chars().next(),
Some('S') | Some('D')
));
}
@@ -373,14 +369,14 @@ mod tests {
}
/// Wait for a process to block either in a sleeping or disk sleep state.
- fn wait_for_thread_to_sleep(tid: Pid, timeout: Duration) -> result::Result<(), errno::Error> {
+ fn wait_for_thread_to_sleep(tid: Pid, timeout: Duration) -> result::Result<(), ErrnoError> {
let start = Instant::now();
loop {
if thread_is_sleeping(tid)? {
return Ok(());
}
if start.elapsed() > timeout {
- return Err(errno::Error::new(libc::EAGAIN));
+ return Err(ErrnoError::new(libc::EAGAIN));
}
sleep(Duration::from_millis(50));
}
@@ -401,7 +397,7 @@ mod tests {
let tid = gettid();
WAIT_FOR_INTERRUPT_THREAD_ID.store(tid, Ordering::SeqCst);
- let join_handle = spawn(move || -> result::Result<(), errno::Error> {
+ let join_handle = spawn(move || -> result::Result<(), ErrnoError> {
// Wait unitl the thread is ready to receive the signal.
wait_for_thread_to_sleep(tid, Duration::from_secs(10)).unwrap();
diff --git a/base/src/unix/shm.rs b/base/src/unix/shm.rs
new file mode 100644
index 000000000..5640a20fc
--- /dev/null
+++ b/base/src/unix/shm.rs
@@ -0,0 +1,473 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ ffi::{CStr, CString},
+ fs::{read_link, File},
+ io::{
+ Read, Seek, SeekFrom, Write, {self},
+ },
+ os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
+};
+
+use libc::{
+ c_char, c_int, c_long, c_uint, close, fcntl, ftruncate64, off64_t, syscall, SYS_memfd_create,
+ EINVAL, F_ADD_SEALS, F_GET_SEALS, F_SEAL_FUTURE_WRITE, F_SEAL_GROW, F_SEAL_SEAL, F_SEAL_SHRINK,
+ F_SEAL_WRITE, MFD_ALLOW_SEALING, {self},
+};
+use serde::{Deserialize, Serialize};
+
+use super::{errno_result, Error, Result};
+use crate::{AsRawDescriptor, RawDescriptor};
+
+/// A shared memory file descriptor and its size.
+#[derive(Serialize, Deserialize)]
+pub struct SharedMemory {
+ #[serde(with = "super::with_as_descriptor")]
+ fd: File,
+ size: u64,
+}
+
+// from <sys/memfd.h>
+const MFD_CLOEXEC: c_uint = 0x0001;
+
+unsafe fn memfd_create(name: *const c_char, flags: c_uint) -> c_int {
+ syscall(SYS_memfd_create as c_long, name, flags) as c_int
+}
+
+/// A set of memfd seals.
+///
+/// An enumeration of each bit can be found at `fcntl(2)`.
+#[derive(Copy, Clone, Default)]
+pub struct MemfdSeals(i32);
+
+impl MemfdSeals {
+ /// Returns an empty set of memfd seals.
+ #[inline]
+ pub fn new() -> MemfdSeals {
+ MemfdSeals(0)
+ }
+
+ /// Gets the raw bitmask of seals enumerated in `fcntl(2)`.
+ #[inline]
+ pub fn bitmask(self) -> i32 {
+ self.0
+ }
+
+ /// True if the grow seal bit is present.
+ #[inline]
+ pub fn grow_seal(self) -> bool {
+ self.0 & F_SEAL_GROW != 0
+ }
+
+ /// Sets the grow seal bit.
+ #[inline]
+ pub fn set_grow_seal(&mut self) {
+ self.0 |= F_SEAL_GROW;
+ }
+
+ /// True if the shrink seal bit is present.
+ #[inline]
+ pub fn shrink_seal(self) -> bool {
+ self.0 & F_SEAL_SHRINK != 0
+ }
+
+ /// Sets the shrink seal bit.
+ #[inline]
+ pub fn set_shrink_seal(&mut self) {
+ self.0 |= F_SEAL_SHRINK;
+ }
+
+ /// True if the write seal bit is present.
+ #[inline]
+ pub fn write_seal(self) -> bool {
+ self.0 & F_SEAL_WRITE != 0
+ }
+
+ /// Sets the write seal bit.
+ #[inline]
+ pub fn set_write_seal(&mut self) {
+ self.0 |= F_SEAL_WRITE;
+ }
+
+ /// True if the future write seal bit is present.
+ #[inline]
+ pub fn future_write_seal(self) -> bool {
+ self.0 & F_SEAL_FUTURE_WRITE != 0
+ }
+
+ /// Sets the future write seal bit.
+ #[inline]
+ pub fn set_future_write_seal(&mut self) {
+ self.0 |= F_SEAL_FUTURE_WRITE;
+ }
+
+ /// True of the seal seal bit is present.
+ #[inline]
+ pub fn seal_seal(self) -> bool {
+ self.0 & F_SEAL_SEAL != 0
+ }
+
+ /// Sets the seal seal bit.
+ #[inline]
+ pub fn set_seal_seal(&mut self) {
+ self.0 |= F_SEAL_SEAL;
+ }
+}
+
+impl SharedMemory {
+ /// Convenience function for `SharedMemory::new` that is always named and accepts a wide variety
+ /// of string-like types.
+ ///
+ /// Note that the given name may not have NUL characters anywhere in it, or this will return an
+ /// error.
+ pub fn named<T: Into<Vec<u8>>>(name: T) -> Result<SharedMemory> {
+ Self::new(Some(&CString::new(name).map_err(|_| Error::new(EINVAL))?))
+ }
+
+ /// Convenience function for `SharedMemory::new` that has an arbitrary and unspecified name.
+ pub fn anon() -> Result<SharedMemory> {
+ Self::new(None)
+ }
+
+ /// Creates a new shared memory file descriptor with zero size.
+ ///
+ /// If a name is given, it will appear in `/proc/self/fd/<shm fd>` for the purposes of
+ /// debugging. The name does not need to be unique.
+ ///
+ /// The file descriptor is opened with the close on exec flag and allows memfd sealing.
+ pub fn new(name: Option<&CStr>) -> Result<SharedMemory> {
+ let shm_name = name
+ .map(|n| n.as_ptr())
+ .unwrap_or(b"/crosvm_shm\0".as_ptr() as *const c_char);
+ // The following are safe because we give a valid C string and check the
+ // results of the memfd_create call.
+ let fd = unsafe { memfd_create(shm_name, MFD_CLOEXEC | MFD_ALLOW_SEALING) };
+ if fd < 0 {
+ return errno_result();
+ }
+
+ let file = unsafe { File::from_raw_fd(fd) };
+
+ Ok(SharedMemory { fd: file, size: 0 })
+ }
+
+ /// Constructs a `SharedMemory` instance from a `File` that represents shared memory.
+ ///
+ /// The size of the resulting shared memory will be determined using `File::seek`. If the given
+ /// file's size can not be determined this way, this will return an error.
+ pub fn from_file(mut file: File) -> Result<SharedMemory> {
+ let file_size = file.seek(SeekFrom::End(0))?;
+ Ok(SharedMemory {
+ fd: file,
+ size: file_size as u64,
+ })
+ }
+
+ /// Gets the memfd seals that have already been added to this.
+ ///
+ /// This may fail if this instance was not constructed from a memfd.
+ pub fn get_seals(&self) -> Result<MemfdSeals> {
+ let ret = unsafe { fcntl(self.fd.as_raw_fd(), F_GET_SEALS) };
+ if ret < 0 {
+ return errno_result();
+ }
+ Ok(MemfdSeals(ret))
+ }
+
+ /// Adds the given set of memfd seals.
+ ///
+ /// This may fail if this instance was not constructed from a memfd with sealing allowed or if
+ /// the seal seal (`F_SEAL_SEAL`) bit was already added.
+ pub fn add_seals(&mut self, seals: MemfdSeals) -> Result<()> {
+ let ret = unsafe { fcntl(self.fd.as_raw_fd(), F_ADD_SEALS, seals) };
+ if ret < 0 {
+ return errno_result();
+ }
+ Ok(())
+ }
+
+ /// Gets the size in bytes of the shared memory.
+ ///
+ /// The size returned here does not reflect changes by other interfaces or users of the shared
+ /// memory file descriptor..
+ pub fn size(&self) -> u64 {
+ self.size
+ }
+
+ /// Sets the size in bytes of the shared memory.
+ ///
+ /// Note that if some process has already mapped this shared memory and the new size is smaller,
+ /// that process may get signaled with SIGBUS if they access any page past the new size.
+ pub fn set_size(&mut self, size: u64) -> Result<()> {
+ let ret = unsafe { ftruncate64(self.fd.as_raw_fd(), size as off64_t) };
+ if ret < 0 {
+ return errno_result();
+ }
+ self.size = size;
+ Ok(())
+ }
+
+ /// Reads the name from the underlying file as a `String`.
+ ///
+ /// If the underlying file was not created with `SharedMemory::new` or with `memfd_create`, the
+ /// results are undefined. Because this returns a `String`, the name's bytes are interpreted as
+ /// utf-8.
+ pub fn read_name(&self) -> Result<String> {
+ let fd_path = format!("/proc/self/fd/{}", self.as_raw_fd());
+ let link_name = read_link(fd_path)?;
+ link_name
+ .to_str()
+ .map(|s| {
+ s.trim_start_matches("/memfd:")
+ .trim_end_matches(" (deleted)")
+ .to_owned()
+ })
+ .ok_or_else(|| Error::new(EINVAL))
+ }
+}
+
+impl Read for SharedMemory {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.fd.read(buf)
+ }
+}
+
+impl Read for &SharedMemory {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ (&self.fd).read(buf)
+ }
+}
+
+impl Write for SharedMemory {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.fd.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.fd.flush()
+ }
+}
+
+impl Write for &SharedMemory {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ (&self.fd).write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ (&self.fd).flush()
+ }
+}
+
+impl Seek for SharedMemory {
+ fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
+ self.fd.seek(pos)
+ }
+}
+
+impl Seek for &SharedMemory {
+ fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
+ (&self.fd).seek(pos)
+ }
+}
+
+impl AsRawFd for SharedMemory {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd.as_raw_fd()
+ }
+}
+
+impl AsRawFd for &SharedMemory {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd.as_raw_fd()
+ }
+}
+
+impl IntoRawFd for SharedMemory {
+ fn into_raw_fd(self) -> RawFd {
+ self.fd.into_raw_fd()
+ }
+}
+
+impl AsRawDescriptor for SharedMemory {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.fd.as_raw_descriptor()
+ }
+}
+
+impl From<SharedMemory> for File {
+ fn from(s: SharedMemory) -> File {
+ s.fd
+ }
+}
+
+/// Checks if the kernel we are running on has memfd_create. It was introduced in 3.17.
+/// Only to be used from tests to prevent running on ancient kernels that won't
+/// support the functionality anyways.
+pub fn kernel_has_memfd() -> bool {
+ unsafe {
+ let fd = memfd_create(b"/test_memfd_create\0".as_ptr() as *const c_char, 0);
+ if fd < 0 {
+ if Error::last().errno() == libc::ENOSYS {
+ return false;
+ }
+ return true;
+ }
+ close(fd);
+ }
+ true
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use std::ffi::CString;
+
+ use data_model::VolatileMemory;
+
+ use super::super::MemoryMapping;
+
+ #[test]
+ fn named() {
+ if !kernel_has_memfd() {
+ return;
+ }
+ const TEST_NAME: &str = "Name McCool Person";
+ let shm = SharedMemory::named(TEST_NAME).expect("failed to create shared memory");
+ assert_eq!(shm.read_name(), Ok(TEST_NAME.to_owned()));
+ }
+
+ #[test]
+ fn anon() {
+ if !kernel_has_memfd() {
+ return;
+ }
+ SharedMemory::anon().expect("failed to create shared memory");
+ }
+
+ #[test]
+ fn new() {
+ if !kernel_has_memfd() {
+ return;
+ }
+ let shm = SharedMemory::anon().expect("failed to create shared memory");
+ assert_eq!(shm.size(), 0);
+ }
+
+ #[test]
+ fn new_sized() {
+ if !kernel_has_memfd() {
+ return;
+ }
+ let mut shm = SharedMemory::anon().expect("failed to create shared memory");
+ shm.set_size(1024)
+ .expect("failed to set shared memory size");
+ assert_eq!(shm.size(), 1024);
+ }
+
+ #[test]
+ fn new_huge() {
+ if !kernel_has_memfd() {
+ return;
+ }
+ let mut shm = SharedMemory::anon().expect("failed to create shared memory");
+ shm.set_size(0x7fff_ffff_ffff_ffff)
+ .expect("failed to set shared memory size");
+ assert_eq!(shm.size(), 0x7fff_ffff_ffff_ffff);
+ }
+
+ #[test]
+ fn new_too_huge() {
+ if !kernel_has_memfd() {
+ return;
+ }
+ let mut shm = SharedMemory::anon().expect("failed to create shared memory");
+ shm.set_size(0x8000_0000_0000_0000).unwrap_err();
+ assert_eq!(shm.size(), 0);
+ }
+
+ #[test]
+ fn new_named() {
+ if !kernel_has_memfd() {
+ return;
+ }
+ let name = "very unique name";
+ let cname = CString::new(name).unwrap();
+ let shm = SharedMemory::new(Some(&cname)).expect("failed to create shared memory");
+ assert_eq!(shm.read_name(), Ok(name.to_owned()));
+ }
+
+ #[test]
+ fn new_sealed() {
+ if !kernel_has_memfd() {
+ return;
+ }
+ let mut shm = SharedMemory::anon().expect("failed to create shared memory");
+ let mut seals = shm.get_seals().expect("failed to get seals");
+ assert_eq!(seals.bitmask(), 0);
+ seals.set_seal_seal();
+ shm.add_seals(seals).expect("failed to add seals");
+ seals = shm.get_seals().expect("failed to get seals");
+ assert!(seals.seal_seal());
+ // Adding more seals should be rejected by the kernel.
+ shm.add_seals(seals).unwrap_err();
+ }
+
+ #[test]
+ fn mmap_page() {
+ if !kernel_has_memfd() {
+ return;
+ }
+ let mut shm = SharedMemory::anon().expect("failed to create shared memory");
+ shm.set_size(4096)
+ .expect("failed to set shared memory size");
+
+ let mmap1 =
+ MemoryMapping::from_fd(&shm, shm.size() as usize).expect("failed to map shared memory");
+ let mmap2 =
+ MemoryMapping::from_fd(&shm, shm.size() as usize).expect("failed to map shared memory");
+
+ assert_ne!(
+ mmap1.get_slice(0, 1).unwrap().as_ptr(),
+ mmap2.get_slice(0, 1).unwrap().as_ptr()
+ );
+
+ mmap1
+ .get_slice(0, 4096)
+ .expect("failed to get mmap slice")
+ .write_bytes(0x45);
+
+ for i in 0..4096 {
+ assert_eq!(mmap2.get_ref::<u8>(i).unwrap().load(), 0x45u8);
+ }
+ }
+
+ #[test]
+ fn mmap_page_offset() {
+ if !kernel_has_memfd() {
+ return;
+ }
+ let mut shm = SharedMemory::anon().expect("failed to create shared memory");
+ shm.set_size(8092)
+ .expect("failed to set shared memory size");
+
+ let mmap1 = MemoryMapping::from_fd_offset(&shm, shm.size() as usize, 4096)
+ .expect("failed to map shared memory");
+ let mmap2 =
+ MemoryMapping::from_fd(&shm, shm.size() as usize).expect("failed to map shared memory");
+
+ mmap1
+ .get_slice(0, 4096)
+ .expect("failed to get mmap slice")
+ .write_bytes(0x45);
+
+ for i in 0..4096 {
+ assert_eq!(mmap2.get_ref::<u8>(i).unwrap().load(), 0);
+ }
+ for i in 4096..8092 {
+ assert_eq!(mmap2.get_ref::<u8>(i).unwrap().load(), 0x45u8);
+ }
+ }
+}
diff --git a/sys_util/src/signal.rs b/base/src/unix/signal.rs
index 1191dccb5..164b18918 100644
--- a/sys_util/src/signal.rs
+++ b/base/src/unix/signal.rs
@@ -7,98 +7,78 @@ use libc::{
sigismember, sigpending, sigset_t, sigtimedwait, sigwait, timespec, waitpid, EAGAIN, EINTR,
EINVAL, SA_RESTART, SIG_BLOCK, SIG_DFL, SIG_UNBLOCK, WNOHANG,
};
+use remain::sorted;
+use thiserror::Error;
+
+use std::{
+ cmp::Ordering,
+ convert::TryFrom,
+ io, mem,
+ os::unix::thread::JoinHandleExt,
+ process::Child,
+ ptr::{null, null_mut},
+ result,
+ thread::JoinHandle,
+ time::{Duration, Instant},
+};
-use std::cmp::Ordering;
-use std::convert::TryFrom;
-use std::fmt::{self, Display};
-use std::io;
-use std::mem;
-use std::os::unix::thread::JoinHandleExt;
-use std::process::Child;
-use std::ptr::{null, null_mut};
-use std::result;
-use std::thread::JoinHandle;
-use std::time::{Duration, Instant};
-
-use crate::{duration_to_timespec, errno, errno_result, getsid, Pid, Result};
+use super::{duration_to_timespec, errno_result, getsid, Error as ErrnoError, Pid, Result};
use std::ops::{Deref, DerefMut};
const POLL_RATE: Duration = Duration::from_millis(50);
const DEFAULT_KILL_TIMEOUT: Duration = Duration::from_secs(5);
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- /// Couldn't create a sigset.
- CreateSigset(errno::Error),
- /// The wrapped signal has already been blocked.
- SignalAlreadyBlocked(c_int),
- /// Failed to check if the requested signal is in the blocked set already.
- CompareBlockedSignals(errno::Error),
/// The signal could not be blocked.
- BlockSignal(errno::Error),
- /// The signal mask could not be retrieved.
- RetrieveSignalMask(i32),
- /// The signal could not be unblocked.
- UnblockSignal(errno::Error),
- /// Failed to wait for given signal.
- ClearWaitPending(errno::Error),
- /// Failed to get pending signals.
- ClearGetPending(errno::Error),
+ #[error("signal could not be blocked: {0}")]
+ BlockSignal(ErrnoError),
/// Failed to check if given signal is in the set of pending signals.
- ClearCheckPending(errno::Error),
- /// Failed to send signal to pid.
- Kill(errno::Error),
+ #[error("failed to check whether given signal is in the pending set: {0}")]
+ ClearCheckPending(ErrnoError),
+ /// Failed to get pending signals.
+ #[error("failed to get pending signals: {0}")]
+ ClearGetPending(ErrnoError),
+ /// Failed to wait for given signal.
+ #[error("failed to wait for given signal: {0}")]
+ ClearWaitPending(ErrnoError),
+ /// Failed to check if the requested signal is in the blocked set already.
+ #[error("failed to check whether requested signal is in the blocked set: {0}")]
+ CompareBlockedSignals(ErrnoError),
+ /// Couldn't create a sigset.
+ #[error("couldn't create a sigset: {0}")]
+ CreateSigset(ErrnoError),
/// Failed to get session id.
- GetSid(errno::Error),
- /// Failed to wait for signal.
- WaitForSignal(errno::Error),
- /// Failed to wait for pid.
- WaitPid(errno::Error),
+ #[error("failed to get session id: {0}")]
+ GetSid(ErrnoError),
+ /// Failed to send signal to pid.
+ #[error("failed to send signal: {0}")]
+ Kill(ErrnoError),
+ /// The signal mask could not be retrieved.
+ #[error("failed to retrieve signal mask: {}", io::Error::from_raw_os_error(*.0))]
+ RetrieveSignalMask(i32),
+ /// Converted signum greater than SIGRTMAX.
+ #[error("got RT signal greater than max: {0:?}")]
+ RtSignumGreaterThanMax(Signal),
+ /// The wrapped signal has already been blocked.
+ #[error("signal {0} already blocked")]
+ SignalAlreadyBlocked(c_int),
/// Timeout reached.
+ #[error("timeout reached.")]
TimedOut,
+ /// The signal could not be unblocked.
+ #[error("signal could not be unblocked: {0}")]
+ UnblockSignal(ErrnoError),
/// Failed to convert signum to Signal.
+ #[error("unrecoginized signal number: {0}")]
UnrecognizedSignum(i32),
- /// Converted signum greater than SIGRTMAX.
- RtSignumGreaterThanMax(Signal),
-}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- CreateSigset(e) => write!(f, "couldn't create a sigset: {}", e),
- SignalAlreadyBlocked(num) => write!(f, "signal {} already blocked", num),
- CompareBlockedSignals(e) => write!(
- f,
- "failed to check whether requested signal is in the blocked set: {}",
- e,
- ),
- BlockSignal(e) => write!(f, "signal could not be blocked: {}", e),
- RetrieveSignalMask(errno) => write!(
- f,
- "failed to retrieve signal mask: {}",
- io::Error::from_raw_os_error(*errno),
- ),
- UnblockSignal(e) => write!(f, "signal could not be unblocked: {}", e),
- ClearWaitPending(e) => write!(f, "failed to wait for given signal: {}", e),
- ClearGetPending(e) => write!(f, "failed to get pending signals: {}", e),
- ClearCheckPending(e) => write!(
- f,
- "failed to check whether given signal is in the pending set: {}",
- e,
- ),
- Kill(e) => write!(f, "failed to send signal: {}", e),
- GetSid(e) => write!(f, "failed to get session id: {}", e),
- WaitForSignal(e) => write!(f, "failed to wait for signal: {}", e),
- WaitPid(e) => write!(f, "failed to wait for process: {}", e),
- TimedOut => write!(f, "timeout reached."),
- UnrecognizedSignum(signum) => write!(f, "unrecoginized signal number: {}", signum),
- RtSignumGreaterThanMax(signal) => {
- write!(f, "got RT signal greater than max: {:?}", signal)
- }
- }
- }
+ /// Failed to wait for signal.
+ #[error("failed to wait for signal: {0}")]
+ WaitForSignal(ErrnoError),
+ /// Failed to wait for pid.
+ #[error("failed to wait for process: {0}")]
+ WaitPid(ErrnoError),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -299,10 +279,7 @@ fn valid_rt_signal_num(num: c_int) -> bool {
///
/// This is considered unsafe because the given handler will be called asynchronously, interrupting
/// whatever the thread was doing and therefore must only do async-signal-safe operations.
-pub unsafe fn register_signal_handler(
- num: c_int,
- handler: extern "C" fn(c_int),
-) -> errno::Result<()> {
+pub unsafe fn register_signal_handler(num: c_int, handler: extern "C" fn(c_int)) -> Result<()> {
let mut sigact: sigaction = mem::zeroed();
sigact.sa_flags = SA_RESTART;
sigact.sa_sigaction = handler as *const () as usize;
@@ -316,7 +293,7 @@ pub unsafe fn register_signal_handler(
}
/// Resets the signal handler of signum `num` back to the default.
-pub fn clear_signal_handler(num: c_int) -> errno::Result<()> {
+pub fn clear_signal_handler(num: c_int) -> Result<()> {
// Safe because sigaction is owned and expected to be initialized ot zeros.
let mut sigact: sigaction = unsafe { mem::zeroed() };
sigact.sa_flags = SA_RESTART;
@@ -332,7 +309,7 @@ pub fn clear_signal_handler(num: c_int) -> errno::Result<()> {
}
/// Returns true if the signal handler for signum `num` is the default.
-pub fn has_default_signal_handler(num: c_int) -> errno::Result<bool> {
+pub fn has_default_signal_handler(num: c_int) -> Result<bool> {
// Safe because sigaction is owned and expected to be initialized ot zeros.
let mut sigact: sigaction = unsafe { mem::zeroed() };
@@ -353,12 +330,9 @@ pub fn has_default_signal_handler(num: c_int) -> errno::Result<bool> {
///
/// This is considered unsafe because the given handler will be called asynchronously, interrupting
/// whatever the thread was doing and therefore must only do async-signal-safe operations.
-pub unsafe fn register_rt_signal_handler(
- num: c_int,
- handler: extern "C" fn(c_int),
-) -> errno::Result<()> {
+pub unsafe fn register_rt_signal_handler(num: c_int, handler: extern "C" fn(c_int)) -> Result<()> {
if !valid_rt_signal_num(num) {
- return Err(errno::Error::new(EINVAL));
+ return Err(ErrnoError::new(EINVAL));
}
register_signal_handler(num, handler)
@@ -367,7 +341,7 @@ pub unsafe fn register_rt_signal_handler(
/// Creates `sigset` from an array of signal numbers.
///
/// This is a helper function used when we want to manipulate signals.
-pub fn create_sigset(signals: &[c_int]) -> errno::Result<sigset_t> {
+pub fn create_sigset(signals: &[c_int]) -> Result<sigset_t> {
// sigset will actually be initialized by sigemptyset below.
let mut sigset: sigset_t = unsafe { mem::zeroed() };
@@ -390,7 +364,7 @@ pub fn create_sigset(signals: &[c_int]) -> errno::Result<sigset_t> {
/// Wait for signal before continuing. The signal number of the consumed signal is returned on
/// success. EAGAIN means the timeout was reached.
-pub fn wait_for_signal(signals: &[c_int], timeout: Option<Duration>) -> errno::Result<c_int> {
+pub fn wait_for_signal(signals: &[c_int], timeout: Option<Duration>) -> Result<c_int> {
let sigset = create_sigset(signals)?;
match timeout {
@@ -408,7 +382,7 @@ pub fn wait_for_signal(signals: &[c_int], timeout: Option<Duration>) -> errno::R
let mut ret: c_int = 0;
let err = handle_eintr_rc!(unsafe { sigwait(&sigset, &mut ret as *mut c_int) });
if err != 0 {
- Err(errno::Error::new(err))
+ Err(ErrnoError::new(err))
} else {
Ok(ret)
}
@@ -450,12 +424,12 @@ pub fn block_signal(num: c_int) -> SignalResult<()> {
let mut old_sigset: sigset_t = mem::zeroed();
let ret = pthread_sigmask(SIG_BLOCK, &sigset, &mut old_sigset as *mut sigset_t);
if ret < 0 {
- return Err(Error::BlockSignal(errno::Error::last()));
+ return Err(Error::BlockSignal(ErrnoError::last()));
}
let ret = sigismember(&old_sigset, num);
match ret.cmp(&0) {
Ordering::Less => {
- return Err(Error::CompareBlockedSignals(errno::Error::last()));
+ return Err(Error::CompareBlockedSignals(ErrnoError::last()));
}
Ordering::Greater => {
return Err(Error::SignalAlreadyBlocked(num));
@@ -473,7 +447,7 @@ pub fn unblock_signal(num: c_int) -> SignalResult<()> {
// Safe - return value is checked.
let ret = unsafe { pthread_sigmask(SIG_UNBLOCK, &sigset, null_mut()) };
if ret < 0 {
- return Err(Error::UnblockSignal(errno::Error::last()));
+ return Err(Error::UnblockSignal(ErrnoError::last()));
}
Ok(())
}
@@ -495,11 +469,11 @@ pub fn clear_signal(num: c_int) -> SignalResult<()> {
// is not pending, the call will fail with EAGAIN or EINTR.
let ret = sigtimedwait(&sigset, &mut siginfo, &ts);
if ret < 0 {
- let e = errno::Error::last();
+ let e = ErrnoError::last();
match e.errno() {
EAGAIN | EINTR => {}
_ => {
- return Err(Error::ClearWaitPending(errno::Error::last()));
+ return Err(Error::ClearWaitPending(ErrnoError::last()));
}
}
}
@@ -509,12 +483,12 @@ pub fn clear_signal(num: c_int) -> SignalResult<()> {
// See if more instances of the signal are pending.
let ret = sigpending(&mut chkset);
if ret < 0 {
- return Err(Error::ClearGetPending(errno::Error::last()));
+ return Err(Error::ClearGetPending(ErrnoError::last()));
}
let ret = sigismember(&chkset, num);
if ret < 0 {
- return Err(Error::ClearCheckPending(errno::Error::last()));
+ return Err(Error::ClearCheckPending(ErrnoError::last()));
}
// This is do-while loop condition.
@@ -552,9 +526,9 @@ pub unsafe trait Killable {
/// Sends the signal `num` to this killable thread.
///
/// The value of `num` must be within [`SIGRTMIN`, `SIGRTMAX`] range.
- fn kill(&self, num: c_int) -> errno::Result<()> {
+ fn kill(&self, num: c_int) -> Result<()> {
if !valid_rt_signal_num(num) {
- return Err(errno::Error::new(EINVAL));
+ return Err(ErrnoError::new(EINVAL));
}
// Safe because we ensure we are using a valid pthread handle, a valid signal number, and
@@ -576,19 +550,12 @@ unsafe impl<T> Killable for JoinHandle<T> {
/// Treat some errno's as Ok(()).
macro_rules! ok_if {
- ($result:expr, $($errno:pat)|+) => {{
- let res = $result;
- match res {
- Ok(_) => Ok(()),
- Err(err) => {
- if matches!(err.errno(), $($errno)|+) {
- Ok(())
- } else {
- Err(err)
- }
+ ($result:expr, $errno:pat) => {{
+ match $result {
+ Err(err) if !matches!(err.errno(), $errno) => Err(err),
+ _ => Ok(()),
}
- }
- }}
+ }};
}
/// Terminates and reaps a child process. If the child process is a group leader, its children will
@@ -616,7 +583,7 @@ pub fn kill_tree(child: &mut Child, terminate_timeout: Duration) -> SignalResult
if child_running {
if child
.try_wait()
- .map_err(|e| Error::WaitPid(errno::Error::from(e)))?
+ .map_err(|e| Error::WaitPid(ErrnoError::from(e)))?
.is_some()
{
child_running = false;
@@ -629,7 +596,7 @@ pub fn kill_tree(child: &mut Child, terminate_timeout: Duration) -> SignalResult
let ret = unsafe { waitpid(target, null_mut(), WNOHANG) };
match ret {
-1 => {
- let err = errno::Error::last();
+ let err = ErrnoError::last();
if err.errno() == libc::ECHILD {
// No group members to wait on.
break;
diff --git a/sys_util/src/signalfd.rs b/base/src/unix/signalfd.rs
index bdb94dd30..2d2ef901f 100644
--- a/sys_util/src/signalfd.rs
+++ b/base/src/unix/signalfd.rs
@@ -2,57 +2,42 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fmt::{self, Display};
-use std::fs::File;
-use std::mem;
-use std::os::raw::c_int;
-use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
-use std::result;
-
-use libc::{c_void, read, signalfd, signalfd_siginfo};
-use libc::{EAGAIN, SFD_CLOEXEC, SFD_NONBLOCK};
-
-use crate::{errno, signal, AsRawDescriptor, RawDescriptor};
-
-#[derive(Debug)]
+use std::{
+ fs::File,
+ mem,
+ os::{
+ raw::c_int,
+ unix::io::{AsRawFd, FromRawFd, RawFd},
+ },
+ result,
+};
+
+use libc::{c_void, read, signalfd, signalfd_siginfo, EAGAIN, SFD_CLOEXEC, SFD_NONBLOCK};
+use remain::sorted;
+use thiserror::Error;
+
+use super::{signal, Error as ErrnoError, RawDescriptor};
+use crate::descriptor::AsRawDescriptor;
+
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- /// Failed to construct sigset when creating signalfd.
- CreateSigset(errno::Error),
- /// Failed to create a new signalfd.
- CreateSignalFd(errno::Error),
/// Failed to block the signal when creating signalfd.
+ #[error("failed to block the signal when creating signalfd: {0}")]
CreateBlockSignal(signal::Error),
- /// Unable to read from signalfd.
- SignalFdRead(errno::Error),
+ /// Failed to create a new signalfd.
+ #[error("failed to create a new signalfd: {0}")]
+ CreateSignalFd(ErrnoError),
+ /// Failed to construct sigset when creating signalfd.
+ #[error("failed to construct sigset when creating signalfd: {0}")]
+ CreateSigset(ErrnoError),
/// Signalfd could be read, but didn't return a full siginfo struct.
/// This wraps the number of bytes that were actually read.
+ #[error("signalfd failed to return a full siginfo struct, read only {0} bytes")]
SignalFdPartialRead(usize),
-}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- CreateSigset(e) => write!(
- f,
- "failed to construct sigset when creating signalfd: {}",
- e,
- ),
- CreateSignalFd(e) => write!(f, "failed to create a new signalfd: {}", e),
- CreateBlockSignal(e) => write!(
- f,
- "failed to block the signal when creating signalfd: {}",
- e,
- ),
- SignalFdRead(e) => write!(f, "unable to read from signalfd: {}", e),
- SignalFdPartialRead(read) => write!(
- f,
- "signalfd failed to return a full siginfo struct, read only {} bytes",
- read,
- ),
- }
- }
+ /// Unable to read from signalfd.
+ #[error("unable to read from signalfd: {0}")]
+ SignalFdRead(ErrnoError),
}
pub type Result<T> = result::Result<T, Error>;
@@ -78,7 +63,7 @@ impl SignalFd {
// This is safe as we check the return value and know that fd is valid.
let fd = unsafe { signalfd(-1, &sigset, SFD_CLOEXEC | SFD_NONBLOCK) };
if fd < 0 {
- return Err(Error::CreateSignalFd(errno::Error::last()));
+ return Err(Error::CreateSignalFd(ErrnoError::last()));
}
// Mask out the normal handler for the signal.
@@ -114,7 +99,7 @@ impl SignalFd {
};
if ret < 0 {
- let err = errno::Error::last();
+ let err = ErrnoError::last();
if err.errno() == EAGAIN {
Ok(None)
} else {
@@ -155,10 +140,9 @@ impl Drop for SignalFd {
mod tests {
use super::*;
- use crate::signal::SIGRTMIN;
+ use super::super::signal::SIGRTMIN;
use libc::{pthread_sigmask, raise, sigismember, sigset_t};
- use std::mem;
- use std::ptr::null;
+ use std::{mem, ptr::null};
#[test]
fn new() {
diff --git a/sys_util/src/sock_ctrl_msg.rs b/base/src/unix/sock_ctrl_msg.rs
index 4bdfdc71b..5bc0784e2 100644
--- a/sys_util/src/sock_ctrl_msg.rs
+++ b/base/src/unix/sock_ctrl_msg.rs
@@ -5,22 +5,25 @@
//! Used to send and receive messages with file descriptors on sockets that accept control messages
//! (e.g. Unix domain sockets).
-use std::fs::File;
-use std::io::{IoSlice, IoSliceMut};
-use std::mem::size_of;
-use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
-use std::os::unix::net::{UnixDatagram, UnixStream};
-use std::ptr::{copy_nonoverlapping, null_mut, write_unaligned};
-use std::slice;
+use std::{
+ fs::File,
+ io::{IoSlice, IoSliceMut},
+ mem::size_of,
+ os::unix::{
+ io::{AsRawFd, FromRawFd, RawFd},
+ net::{UnixDatagram, UnixStream},
+ },
+ ptr::{copy_nonoverlapping, null_mut, write_unaligned},
+ slice,
+};
use libc::{
c_long, c_void, cmsghdr, iovec, msghdr, recvmsg, sendmsg, MSG_NOSIGNAL, SCM_RIGHTS, SOL_SOCKET,
};
-use data_model::VolatileSlice;
+use data_model::{IoBufMut, VolatileSlice};
-use crate::net::UnixSeqpacket;
-use crate::{Error, Result};
+use super::{net::UnixSeqpacket, Error, Result};
// Each of the following macros performs the same function as their C counterparts. They are each
// macros because they are used to size statically allocated arrays.
@@ -154,27 +157,15 @@ fn raw_sendmsg<D: AsIobuf>(fd: RawFd, out_data: &[D], out_fds: &[RawFd]) -> Resu
}
}
-fn raw_recvmsg(fd: RawFd, in_data: &mut [u8], in_fds: &mut [RawFd]) -> Result<(usize, usize)> {
- let iovec = iovec {
- iov_base: in_data.as_mut_ptr() as *mut c_void,
- iov_len: in_data.len(),
- };
- raw_recvmsg_iovecs(fd, &mut [iovec], in_fds)
-}
-
-fn raw_recvmsg_iovecs(
- fd: RawFd,
- iovecs: &mut [iovec],
- in_fds: &mut [RawFd],
-) -> Result<(usize, usize)> {
+fn raw_recvmsg(fd: RawFd, iovs: &mut [IoSliceMut], in_fds: &mut [RawFd]) -> Result<(usize, usize)> {
let cmsg_capacity = CMSG_SPACE!(size_of::<RawFd>() * in_fds.len());
let mut cmsg_buffer = CmsgBuffer::with_capacity(cmsg_capacity);
let mut msg = msghdr {
msg_name: null_mut(),
msg_namelen: 0,
- msg_iov: iovecs.as_mut_ptr() as *mut iovec,
- msg_iovlen: iovecs.len(),
+ msg_iov: iovs.as_mut_ptr() as *mut iovec,
+ msg_iovlen: iovs.len(),
msg_control: null_mut(),
msg_controllen: 0,
msg_flags: 0,
@@ -263,7 +254,7 @@ pub trait ScmSocket {
///
/// * `bufs` - A slice of slices of data to send on the `socket`.
/// * `fd` - A file descriptors to be sent.
- fn send_bufs_with_fd(&self, bufs: &[&[u8]], fd: RawFd) -> Result<usize> {
+ fn send_bufs_with_fd(&self, bufs: &[IoSlice], fd: RawFd) -> Result<usize> {
self.send_bufs_with_fds(bufs, &[fd])
}
@@ -275,9 +266,8 @@ pub trait ScmSocket {
///
/// * `bufs` - A slice of slices of data to send on the `socket`.
/// * `fds` - A list of file descriptors to be sent.
- fn send_bufs_with_fds(&self, bufs: &[&[u8]], fd: &[RawFd]) -> Result<usize> {
- let slices: Vec<IoSlice> = bufs.iter().map(|&b| IoSlice::new(b)).collect();
- raw_sendmsg(self.socket_fd(), &slices, fd)
+ fn send_bufs_with_fds(&self, bufs: &[IoSlice], fd: &[RawFd]) -> Result<usize> {
+ raw_sendmsg(self.socket_fd(), bufs, fd)
}
/// Receives data and potentially a file descriptor from the socket.
@@ -287,7 +277,7 @@ pub trait ScmSocket {
/// # Arguments
///
/// * `buf` - A buffer to receive data from the socket.vm
- fn recv_with_fd(&self, buf: &mut [u8]) -> Result<(usize, Option<File>)> {
+ fn recv_with_fd(&self, buf: IoSliceMut) -> Result<(usize, Option<File>)> {
let mut fd = [0];
let (read_count, fd_count) = self.recv_with_fds(buf, &mut fd)?;
let file = if fd_count == 0 {
@@ -313,8 +303,8 @@ pub trait ScmSocket {
/// returned tuple. The caller owns these file descriptors, but they will not be
/// closed on drop like a `File`-like type would be. It is recommended that each valid
/// file descriptor gets wrapped in a drop type that closes it after this returns.
- fn recv_with_fds(&self, buf: &mut [u8], fds: &mut [RawFd]) -> Result<(usize, usize)> {
- raw_recvmsg(self.socket_fd(), buf, fds)
+ fn recv_with_fds(&self, buf: IoSliceMut, fds: &mut [RawFd]) -> Result<(usize, usize)> {
+ raw_recvmsg(self.socket_fd(), &mut [buf], fds)
}
/// Receives data and file descriptors from the socket.
@@ -324,7 +314,10 @@ pub trait ScmSocket {
///
/// # Arguments
///
- /// * `ioves` - A slice of iovecs to store received data.
+ /// * `iovecs` - A slice of buffers to store received data.
+ /// * `offset` - An offset for `bufs`. The first `offset` bytes in `bufs` won't be touched.
+ /// Returns an error if `offset` is larger than or equal to the total size of
+ /// `bufs`.
/// * `fds` - A slice of `RawFd`s to put the received file descriptors into. On success, the
/// number of valid file descriptors is indicated by the second element of the
/// returned tuple. The caller owns these file descriptors, but they will not be
@@ -332,10 +325,10 @@ pub trait ScmSocket {
/// file descriptor gets wrapped in a drop type that closes it after this returns.
fn recv_iovecs_with_fds(
&self,
- iovecs: &mut [iovec],
+ iovecs: &mut [IoSliceMut],
fds: &mut [RawFd],
) -> Result<(usize, usize)> {
- raw_recvmsg_iovecs(self.socket_fd(), iovecs, fds)
+ raw_recvmsg(self.socket_fd(), iovecs, fds)
}
}
@@ -407,11 +400,11 @@ unsafe impl<'a> AsIobuf for IoSliceMut<'a> {
// pointer and size are guaranteed to be accurate.
unsafe impl<'a> AsIobuf for VolatileSlice<'a> {
fn as_iobuf(&self) -> iovec {
- *self.as_iobuf()
+ *self.as_iobuf().as_ref()
}
fn as_iobuf_slice(bufs: &[Self]) -> &[iovec] {
- VolatileSlice::as_iobufs(bufs)
+ IoBufMut::as_iobufs(VolatileSlice::as_iobufs(bufs))
}
}
@@ -419,51 +412,35 @@ unsafe impl<'a> AsIobuf for VolatileSlice<'a> {
mod tests {
use super::*;
- use std::io::Write;
- use std::mem::size_of;
- use std::os::raw::c_long;
- use std::os::unix::net::UnixDatagram;
- use std::slice::from_raw_parts;
+ use std::{
+ io::Write,
+ mem::size_of,
+ os::{raw::c_long, unix::net::UnixDatagram},
+ slice::from_raw_parts,
+ };
use libc::cmsghdr;
- use crate::EventFd;
+ use super::super::EventFd;
+
+ // Doing this as a macro makes it easier to see the line if it fails
+ macro_rules! CMSG_SPACE_TEST {
+ ($len:literal) => {
+ assert_eq!(
+ CMSG_SPACE!(size_of::<[RawFd; $len]>()) as libc::c_uint,
+ unsafe { libc::CMSG_SPACE(size_of::<[RawFd; $len]>() as libc::c_uint) }
+ );
+ };
+ }
#[test]
#[allow(clippy::erasing_op, clippy::identity_op)]
fn buffer_len() {
- assert_eq!(CMSG_SPACE!(0 * size_of::<RawFd>()), size_of::<cmsghdr>());
- assert_eq!(
- CMSG_SPACE!(1 * size_of::<RawFd>()),
- size_of::<cmsghdr>() + size_of::<c_long>()
- );
- if size_of::<RawFd>() == 4 {
- assert_eq!(
- CMSG_SPACE!(2 * size_of::<RawFd>()),
- size_of::<cmsghdr>() + size_of::<c_long>()
- );
- assert_eq!(
- CMSG_SPACE!(3 * size_of::<RawFd>()),
- size_of::<cmsghdr>() + size_of::<c_long>() * 2
- );
- assert_eq!(
- CMSG_SPACE!(4 * size_of::<RawFd>()),
- size_of::<cmsghdr>() + size_of::<c_long>() * 2
- );
- } else if size_of::<RawFd>() == 8 {
- assert_eq!(
- CMSG_SPACE!(2 * size_of::<RawFd>()),
- size_of::<cmsghdr>() + size_of::<c_long>() * 2
- );
- assert_eq!(
- CMSG_SPACE!(3 * size_of::<RawFd>()),
- size_of::<cmsghdr>() + size_of::<c_long>() * 3
- );
- assert_eq!(
- CMSG_SPACE!(4 * size_of::<RawFd>()),
- size_of::<cmsghdr>() + size_of::<c_long>() * 4
- );
- }
+ CMSG_SPACE_TEST!(0);
+ CMSG_SPACE_TEST!(1);
+ CMSG_SPACE_TEST!(2);
+ CMSG_SPACE_TEST!(3);
+ CMSG_SPACE_TEST!(4);
}
#[test]
@@ -481,7 +458,7 @@ mod tests {
let mut buf = [0; 6];
let mut files = [0; 1];
let (read_count, file_count) = s2
- .recv_with_fds(&mut buf[..], &mut files)
+ .recv_with_fds(IoSliceMut::new(&mut buf), &mut files)
.expect("failed to recv data");
assert_eq!(read_count, 6);
@@ -489,12 +466,12 @@ mod tests {
assert_eq!(buf, [1, 1, 2, 21, 34, 55]);
let write_count = s1
- .send_bufs_with_fds(&[&send_buf[..]], &[])
+ .send_bufs_with_fds(&[IoSlice::new(&send_buf[..])], &[])
.expect("failed to send data");
assert_eq!(write_count, 6);
let (read_count, file_count) = s2
- .recv_with_fds(&mut buf[..], &mut files)
+ .recv_with_fds(IoSliceMut::new(&mut buf), &mut files)
.expect("failed to recv data");
assert_eq!(read_count, 6);
@@ -514,7 +491,10 @@ mod tests {
assert_eq!(write_count, 0);
- let (read_count, file_opt) = s2.recv_with_fd(&mut []).expect("failed to recv fd");
+ let mut buf = [];
+ let (read_count, file_opt) = s2
+ .recv_with_fd(IoSliceMut::new(&mut buf))
+ .expect("failed to recv fd");
let mut file = file_opt.unwrap();
@@ -545,7 +525,7 @@ mod tests {
let mut files = [0; 2];
let mut buf = [0u8];
let (read_count, file_count) = s2
- .recv_with_fds(&mut buf, &mut files)
+ .recv_with_fds(IoSliceMut::new(&mut buf), &mut files)
.expect("failed to recv fd");
assert_eq!(read_count, 1);
diff --git a/base/src/unix/syslog.rs b/base/src/unix/syslog.rs
new file mode 100644
index 000000000..0dc4a5d2b
--- /dev/null
+++ b/base/src/unix/syslog.rs
@@ -0,0 +1,585 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Facilities for sending log message to syslog.
+//!
+//! Every function exported by this module is thread-safe. Each function will silently fail until
+//! `syslog::init()` is called and returns `Ok`.
+//!
+//! # Examples
+//!
+//! ```
+//! use crate::platform::{error, syslog, warn};
+//!
+//! if let Err(e) = syslog::init() {
+//! println!("failed to initiailize syslog: {}", e);
+//! return;
+//! }
+//! warn!("this is your {} warning", "final");
+//! error!("something went horribly wrong: {}", "out of RAMs");
+//! ```
+
+use super::{target_os::syslog::PlatformSyslog, RawDescriptor};
+use std::{
+ env,
+ ffi::{OsStr, OsString},
+ fmt::{
+ Display, {self},
+ },
+ fs::File,
+ io,
+ io::{stderr, Cursor, Write},
+ os::unix::io::{AsRawFd, RawFd},
+ path::PathBuf,
+ sync::{MutexGuard, Once},
+};
+
+use remain::sorted;
+use sync::Mutex;
+use thiserror::Error as ThisError;
+
+/// The priority (i.e. severity) of a syslog message.
+///
+/// See syslog man pages for information on their semantics.
+#[derive(Copy, Clone, Debug)]
+pub enum Priority {
+ Emergency = 0,
+ Alert = 1,
+ Critical = 2,
+ Error = 3,
+ Warning = 4,
+ Notice = 5,
+ Info = 6,
+ Debug = 7,
+}
+
+impl Display for Priority {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::Priority::*;
+
+ let string = match self {
+ Emergency => "EMERGENCY",
+ Alert => "ALERT",
+ Critical => "CRITICAL",
+ Error => "ERROR",
+ Warning => "WARNING",
+ Notice => "NOTICE",
+ Info => "INFO",
+ Debug => "DEBUG",
+ };
+
+ write!(f, "{}", string)
+ }
+}
+
+/// The facility of a syslog message.
+///
+/// See syslog man pages for information on their semantics.
+#[derive(Copy, Clone)]
+pub enum Facility {
+ Kernel = 0,
+ User = 1 << 3,
+ Mail = 2 << 3,
+ Daemon = 3 << 3,
+ Auth = 4 << 3,
+ Syslog = 5 << 3,
+ Lpr = 6 << 3,
+ News = 7 << 3,
+ Uucp = 8 << 3,
+ Local0 = 16 << 3,
+ Local1 = 17 << 3,
+ Local2 = 18 << 3,
+ Local3 = 19 << 3,
+ Local4 = 20 << 3,
+ Local5 = 21 << 3,
+ Local6 = 22 << 3,
+ Local7 = 23 << 3,
+}
+
+/// Errors returned by `syslog::init()`.
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum Error {
+ /// Error while attempting to connect socket.
+ #[error("failed to connect socket: {0}")]
+ Connect(io::Error),
+ /// There was an error using `open` to get the lowest file descriptor.
+ #[error("failed to get lowest file descriptor: {0}")]
+ GetLowestFd(io::Error),
+ /// The guess of libc's file descriptor for the syslog connection was invalid.
+ #[error("guess of fd for syslog connection was invalid")]
+ InvalidFd,
+ /// Initialization was never attempted.
+ #[error("initialization was never attempted")]
+ NeverInitialized,
+ /// Initialization has previously failed and can not be retried.
+ #[error("initialization previously failed and cannot be retried")]
+ Poisoned,
+ /// Error while creating socket.
+ #[error("failed to create socket: {0}")]
+ Socket(io::Error),
+}
+
+fn get_proc_name() -> Option<String> {
+ env::args_os()
+ .next()
+ .map(PathBuf::from)
+ .and_then(|s| s.file_name().map(OsStr::to_os_string))
+ .map(OsString::into_string)
+ .and_then(Result::ok)
+}
+
+struct State {
+ stderr: bool,
+ file: Option<File>,
+ proc_name: Option<String>,
+ syslog: PlatformSyslog,
+}
+
+impl State {
+ fn new() -> Result<State, Error> {
+ Ok(State {
+ stderr: true,
+ file: None,
+ proc_name: get_proc_name(),
+ syslog: PlatformSyslog::new()?,
+ })
+ }
+}
+
+static STATE_ONCE: Once = Once::new();
+static mut STATE: *const Mutex<State> = 0 as *const _;
+
+fn new_mutex_ptr<T>(inner: T) -> *const Mutex<T> {
+ Box::into_raw(Box::new(Mutex::new(inner)))
+}
+
+/// Initialize the syslog connection and internal variables.
+///
+/// This should only be called once per process before any other threads have been spawned or any
+/// signal handlers have been registered. Every call made after the first will have no effect
+/// besides return `Ok` or `Err` appropriately.
+pub fn init() -> Result<(), Error> {
+ let mut err = Error::Poisoned;
+ STATE_ONCE.call_once(|| match State::new() {
+ // Safe because STATE mutation is guarded by `Once`.
+ Ok(state) => unsafe { STATE = new_mutex_ptr(state) },
+ Err(e) => err = e,
+ });
+
+ if unsafe { STATE.is_null() } {
+ Err(err)
+ } else {
+ Ok(())
+ }
+}
+
+fn lock() -> Result<MutexGuard<'static, State>, Error> {
+ // Safe because we assume that STATE is always in either a valid or NULL state.
+ let state_ptr = unsafe { STATE };
+ if state_ptr.is_null() {
+ return Err(Error::NeverInitialized);
+ }
+ // Safe because STATE only mutates once and we checked for NULL.
+ let state = unsafe { &*state_ptr };
+ let guard = state.lock();
+ Ok(guard)
+}
+
+// Attempts to lock and retrieve the state. Returns from the function silently on failure.
+macro_rules! lock {
+ () => {
+ match lock() {
+ Ok(s) => s,
+ _ => return,
+ }
+ };
+}
+
+/// Replaces the process name reported in each syslog message.
+///
+/// The default process name is the _file name_ of `argv[0]`. For example, if this program was
+/// invoked as
+///
+/// ```bash
+/// $ path/to/app --delete everything
+/// ```
+///
+/// the default process name would be _app_.
+///
+/// Does nothing if syslog was never initialized.
+pub fn set_proc_name<T: Into<String>>(proc_name: T) {
+ let mut state = lock!();
+ state.proc_name = Some(proc_name.into());
+}
+
+pub(crate) trait Syslog {
+ fn new() -> Result<Self, Error>
+ where
+ Self: Sized;
+
+ /// Enables or disables echoing log messages to the syslog.
+ ///
+ /// The default behavior is **enabled**.
+ ///
+ /// If `enable` goes from `true` to `false`, the syslog connection is closed. The connection is
+ /// reopened if `enable` is set to `true` after it became `false`.
+ ///
+ /// Returns an error if syslog was never initialized or the syslog connection failed to be
+ /// established.
+ ///
+ /// # Arguments
+ /// * `enable` - `true` to enable echoing to syslog, `false` to disable echoing to syslog.
+ fn enable(&mut self, enable: bool) -> Result<(), Error>;
+
+ fn log(
+ &self,
+ proc_name: Option<&str>,
+ pri: Priority,
+ fac: Facility,
+ file_line: Option<(&str, u32)>,
+ args: fmt::Arguments,
+ );
+
+ fn push_fds(&self, fds: &mut Vec<RawFd>);
+}
+
+/// Enables or disables echoing log messages to the syslog.
+///
+/// The default behavior is **enabled**.
+///
+/// If `enable` goes from `true` to `false`, the syslog connection is closed. The connection is
+/// reopened if `enable` is set to `true` after it became `false`.
+///
+/// Returns an error if syslog was never initialized or the syslog connection failed to be
+/// established.
+///
+/// # Arguments
+/// * `enable` - `true` to enable echoing to syslog, `false` to disable echoing to syslog.
+pub fn echo_syslog(enable: bool) -> Result<(), Error> {
+ let state_ptr = unsafe { STATE };
+ if state_ptr.is_null() {
+ return Err(Error::NeverInitialized);
+ }
+ let mut state = lock().map_err(|_| Error::Poisoned)?;
+
+ state.syslog.enable(enable)
+}
+
+/// Replaces the optional `File` to echo log messages to.
+///
+/// The default behavior is to not echo to a file. Passing `None` to this function restores that
+/// behavior.
+///
+/// Does nothing if syslog was never initialized.
+///
+/// # Arguments
+/// * `file` - `Some(file)` to echo to `file`, `None` to disable echoing to the file previously passed to `echo_file`.
+pub fn echo_file(file: Option<File>) {
+ let mut state = lock!();
+ state.file = file;
+}
+
+/// Enables or disables echoing log messages to the `std::io::stderr()`.
+///
+/// The default behavior is **enabled**.
+///
+/// Does nothing if syslog was never initialized.
+///
+/// # Arguments
+/// * `enable` - `true` to enable echoing to stderr, `false` to disable echoing to stderr.
+pub fn echo_stderr(enable: bool) {
+ let mut state = lock!();
+ state.stderr = enable;
+}
+
+/// Retrieves the file descriptors owned by the global syslogger.
+///
+/// Does nothing if syslog was never initialized. If their are any file descriptors, they will be
+/// pushed into `fds`.
+///
+/// Note that the `stderr` file descriptor is never added, as it is not owned by syslog.
+pub fn push_fds(fds: &mut Vec<RawFd>) {
+ let state = lock!();
+ state.syslog.push_fds(fds);
+ fds.extend(state.file.iter().map(|f| f.as_raw_fd()));
+}
+
+/// Does the same as push_fds, but using the RawDescriptorType
+pub fn push_descriptors(descriptors: &mut Vec<RawDescriptor>) {
+ push_fds(descriptors)
+}
+
+/// Records a log message with the given details.
+///
+/// Note that this will fail silently if syslog was not initialized.
+///
+/// # Arguments
+/// * `pri` - The `Priority` (i.e. severity) of the log message.
+/// * `fac` - The `Facility` of the log message. Usually `Facility::User` should be used.
+/// * `file_line` - Optional tuple of the name of the file that generated the
+/// log and the line number within that file.
+/// * `args` - The log's message to record, in the form of `format_args!()` return value
+///
+/// # Examples
+///
+/// ```
+/// # use crate::platform::syslog;
+/// # if let Err(e) = syslog::init() {
+/// # println!("failed to initiailize syslog: {}", e);
+/// # return;
+/// # }
+/// syslog::log(syslog::Priority::Error,
+/// syslog::Facility::User,
+/// Some((file!(), line!())),
+/// format_args!("hello syslog"));
+/// ```
+pub fn log(pri: Priority, fac: Facility, file_line: Option<(&str, u32)>, args: fmt::Arguments) {
+ let mut state = lock!();
+ let mut buf = [0u8; 1024];
+
+ state.syslog.log(
+ state.proc_name.as_ref().map(|s| s.as_ref()),
+ pri,
+ fac,
+ file_line,
+ args,
+ );
+
+ let res = {
+ let mut buf_cursor = Cursor::new(&mut buf[..]);
+ if let Some((file_name, line)) = &file_line {
+ write!(&mut buf_cursor, "[{}:{}:{}] ", pri, file_name, line)
+ } else {
+ Ok(())
+ }
+ .and_then(|()| writeln!(&mut buf_cursor, "{}", args))
+ .map(|()| buf_cursor.position() as usize)
+ };
+ if let Ok(len) = &res {
+ if let Some(file) = &mut state.file {
+ let _ = file.write_all(&buf[..*len]);
+ }
+ if state.stderr {
+ let _ = stderr().write_all(&buf[..*len]);
+ }
+ }
+}
+
+/// A macro for logging at an arbitrary priority level.
+///
+/// Note that this will fail silently if syslog was not initialized.
+#[macro_export]
+macro_rules! log {
+ ($pri:expr, $($args:tt)+) => ({
+ $crate::platform::syslog::log($pri, $crate::platform::syslog::Facility::User, Some((file!(), line!())), format_args!($($args)+))
+ })
+}
+
+/// A macro for logging an error.
+///
+/// Note that this will fail silently if syslog was not initialized.
+#[macro_export]
+macro_rules! error {
+ ($($args:tt)+) => ($crate::log!($crate::platform::syslog::Priority::Error, $($args)*))
+}
+
+/// A macro for logging a warning.
+///
+/// Note that this will fail silently if syslog was not initialized.
+#[macro_export]
+macro_rules! warn {
+ ($($args:tt)+) => ($crate::log!($crate::platform::syslog::Priority::Warning, $($args)*))
+}
+
+/// A macro for logging info.
+///
+/// Note that this will fail silently if syslog was not initialized.
+#[macro_export]
+macro_rules! info {
+ ($($args:tt)+) => ($crate::log!($crate::platform::syslog::Priority::Info, $($args)*))
+}
+
+/// A macro for logging debug information.
+///
+/// Note that this will fail silently if syslog was not initialized.
+#[macro_export]
+macro_rules! debug {
+ ($($args:tt)+) => ($crate::log!($crate::platform::syslog::Priority::Debug, $($args)*))
+}
+
+// Struct that implements io::Write to be used for writing directly to the syslog
+pub struct Syslogger {
+ buf: String,
+ priority: Priority,
+ facility: Facility,
+}
+
+impl Syslogger {
+ pub fn new(p: Priority, f: Facility) -> Syslogger {
+ Syslogger {
+ buf: String::new(),
+ priority: p,
+ facility: f,
+ }
+ }
+}
+
+impl io::Write for Syslogger {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ let parsed_str = String::from_utf8_lossy(buf);
+ self.buf.push_str(&parsed_str);
+
+ if let Some(last_newline_idx) = self.buf.rfind('\n') {
+ for line in self.buf[..last_newline_idx].lines() {
+ log(self.priority, self.facility, None, format_args!("{}", line));
+ }
+
+ self.buf.drain(..=last_newline_idx);
+ }
+
+ Ok(buf.len())
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ // ANDROID: b/227789997
+// use libc::{shm_open, shm_unlink, O_CREAT, O_EXCL, O_RDWR};
+
+ use std::{
+ ffi::CStr,
+ io::{Read, Seek, SeekFrom},
+ os::unix::io::FromRawFd,
+ };
+
+ #[test]
+ fn init_syslog() {
+ init().unwrap();
+ }
+
+ #[test]
+ fn fds() {
+ init().unwrap();
+ let mut fds = Vec::new();
+ push_fds(&mut fds);
+ assert!(!fds.is_empty());
+ for fd in fds {
+ assert!(fd >= 0);
+ }
+ }
+
+ #[test]
+ fn syslog_log() {
+ init().unwrap();
+ log(
+ Priority::Error,
+ Facility::User,
+ Some((file!(), line!())),
+ format_args!("hello syslog"),
+ );
+ }
+
+ #[test]
+ fn proc_name() {
+ init().unwrap();
+ log(
+ Priority::Error,
+ Facility::User,
+ Some((file!(), line!())),
+ format_args!("before proc name"),
+ );
+ set_proc_name("sys_util-test");
+ log(
+ Priority::Error,
+ Facility::User,
+ Some((file!(), line!())),
+ format_args!("after proc name"),
+ );
+ }
+
+ // ANDROID: b/227789997
+// #[test]
+// fn syslog_file() {
+// init().unwrap();
+// let shm_name = CStr::from_bytes_with_nul(b"/crosvm_shm\0").unwrap();
+// let mut file = unsafe {
+// shm_unlink(shm_name.as_ptr());
+// let fd = shm_open(shm_name.as_ptr(), O_RDWR | O_CREAT | O_EXCL, 0o666);
+// assert!(fd >= 0, "error creating shared memory;");
+// shm_unlink(shm_name.as_ptr());
+// File::from_raw_fd(fd)
+// };
+//
+// let syslog_file = file.try_clone().expect("error cloning shared memory file");
+// echo_file(Some(syslog_file));
+//
+// const TEST_STR: &str = "hello shared memory file";
+// log(
+// Priority::Error,
+// Facility::User,
+// Some((file!(), line!())),
+// format_args!("{}", TEST_STR),
+// );
+//
+// file.seek(SeekFrom::Start(0))
+// .expect("error seeking shared memory file");
+// let mut buf = String::new();
+// file.read_to_string(&mut buf)
+// .expect("error reading shared memory file");
+// assert!(buf.contains(TEST_STR));
+// }
+
+ #[test]
+ fn macros() {
+ init().unwrap();
+ error!("this is an error {}", 3);
+ warn!("this is a warning {}", "uh oh");
+ info!("this is info {}", true);
+ debug!("this is debug info {:?}", Some("helpful stuff"));
+ }
+
+ #[test]
+ fn syslogger_char() {
+ init().unwrap();
+ let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
+
+ let string = "Writing chars to syslog";
+ for c in string.chars() {
+ syslogger.write_all(&[c as u8]).expect("error writing char");
+ }
+
+ syslogger
+ .write_all(&[b'\n'])
+ .expect("error writing newline char");
+ }
+
+ #[test]
+ fn syslogger_line() {
+ init().unwrap();
+ let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
+
+ let s = "Writing string to syslog\n";
+ syslogger
+ .write_all(s.as_bytes())
+ .expect("error writing string");
+ }
+
+ #[test]
+ fn syslogger_partial() {
+ init().unwrap();
+ let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
+
+ let s = "Writing partial string";
+ // Should not log because there is no newline character
+ syslogger
+ .write_all(s.as_bytes())
+ .expect("error writing string");
+ }
+}
diff --git a/sys_util/src/terminal.rs b/base/src/unix/terminal.rs
index 89456f970..caafee3de 100644
--- a/sys_util/src/terminal.rs
+++ b/base/src/unix/terminal.rs
@@ -2,16 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::io::Stdin;
-use std::mem::zeroed;
-use std::os::unix::io::RawFd;
+use std::{io::Stdin, mem::zeroed, os::unix::io::RawFd};
use libc::{
isatty, read, tcgetattr, tcsetattr, termios, ECHO, ICANON, ISIG, O_NONBLOCK, STDIN_FILENO,
TCSANOW,
};
-use crate::{add_fd_flags, clear_fd_flags, errno_result, Result};
+use super::{add_fd_flags, clear_fd_flags, errno_result, Result};
fn modify_mode<F: FnOnce(&mut termios)>(fd: RawFd, f: F) -> Result<()> {
// Safe because we check the return value of isatty.
diff --git a/sys_util/src/timerfd.rs b/base/src/unix/timerfd.rs
index 955c63e6c..b6cb93541 100644
--- a/sys_util/src/timerfd.rs
+++ b/base/src/unix/timerfd.rs
@@ -2,20 +2,19 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fs::File;
-use std::mem;
-use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
-use std::ptr;
-use std::sync::Arc;
-use std::time::Duration;
+use std::{
+ fs::File,
+ mem,
+ os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
+ ptr,
+ sync::Arc,
+ time::Duration,
+};
use sync::Mutex;
-use libc::{
- self, clock_getres, timerfd_create, timerfd_gettime, timerfd_settime, CLOCK_MONOTONIC,
- TFD_CLOEXEC,
-};
+use libc::{self, clock_getres, timerfd_create, timerfd_settime, CLOCK_MONOTONIC, TFD_CLOEXEC};
-use crate::{errno_result, EventFd, FakeClock, Result};
+use super::{errno_result, EventFd, FakeClock, Result};
/// A safe wrapper around a Linux timerfd (man 2 timerfd_create).
pub struct TimerFd(File);
@@ -90,20 +89,6 @@ impl TimerFd {
Ok(count)
}
- /// Returns `true` if the timer is currently armed.
- pub fn is_armed(&self) -> Result<bool> {
- // Safe because we are zero-initializing a struct with only primitive member fields.
- let mut spec: libc::itimerspec = unsafe { mem::zeroed() };
-
- // Safe because timerfd_gettime is trusted to only modify `spec`.
- let ret = unsafe { timerfd_gettime(self.as_raw_fd(), &mut spec) };
- if ret < 0 {
- return errno_result();
- }
-
- Ok(spec.it_value.tv_sec != 0 || spec.it_value.tv_nsec != 0)
- }
-
/// Disarms the timer.
pub fn clear(&self) -> Result<()> {
// Safe because we are zero-initializing a struct with only primitive member fields.
@@ -213,11 +198,6 @@ impl FakeTimerFd {
}
}
- /// Returns `true` if the timer is currently armed.
- pub fn is_armed(&self) -> Result<bool> {
- Ok(self.deadline_ns.is_some())
- }
-
/// Disarms the timer.
pub fn clear(&mut self) -> Result<()> {
self.deadline_ns = None;
@@ -246,20 +226,19 @@ impl IntoRawFd for FakeTimerFd {
#[cfg(test)]
mod tests {
use super::*;
- use std::thread::sleep;
- use std::time::{Duration, Instant};
+ use std::{
+ thread::sleep,
+ time::{Duration, Instant},
+ };
#[test]
fn one_shot() {
let tfd = TimerFd::new().expect("failed to create timerfd");
- assert_eq!(tfd.is_armed().unwrap(), false);
let dur = Duration::from_millis(200);
let now = Instant::now();
tfd.reset(dur, None).expect("failed to arm timer");
- assert_eq!(tfd.is_armed().unwrap(), true);
-
let count = tfd.wait().expect("unable to wait for timer");
assert_eq!(count, 1);
@@ -284,12 +263,10 @@ mod tests {
fn fake_one_shot() {
let clock = Arc::new(Mutex::new(FakeClock::new()));
let mut tfd = FakeTimerFd::new(clock.clone());
- assert_eq!(tfd.is_armed().unwrap(), false);
let dur = Duration::from_nanos(200);
tfd.reset(dur, None).expect("failed to arm timer");
- assert_eq!(tfd.is_armed().unwrap(), true);
clock.lock().add_ns(200);
let count = tfd.wait().expect("unable to wait for timer");
diff --git a/base/src/unix/tube.rs b/base/src/unix/tube.rs
new file mode 100644
index 000000000..9583dde10
--- /dev/null
+++ b/base/src/unix/tube.rs
@@ -0,0 +1,146 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ io::IoSlice,
+ marker::PhantomData,
+ os::unix::prelude::{AsRawFd, RawFd},
+ time::Duration,
+};
+
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, SafeDescriptor};
+use crate::{
+ platform::{deserialize_with_descriptors, SerializeDescriptors},
+ tube::{Error, RecvTube, Result, SendTube},
+ RawDescriptor, ReadNotifier, ScmSocket, UnixSeqpacket, UnsyncMarker,
+};
+
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
+
+/// Bidirectional tube that support both send and recv.
+#[derive(Serialize, Deserialize)]
+pub struct Tube {
+ socket: UnixSeqpacket,
+
+ // Windows is !Sync. We share that characteristic to prevent writing cross-platform incompatible
+ // code.
+ _unsync_marker: UnsyncMarker,
+}
+
+impl Tube {
+ /// Create a pair of connected tubes. Request is sent in one direction while response is in the
+ /// other direction.
+ pub fn pair() -> Result<(Tube, Tube)> {
+ let (socket1, socket2) = UnixSeqpacket::pair().map_err(Error::Pair)?;
+ let tube1 = Tube::new(socket1);
+ let tube2 = Tube::new(socket2);
+ Ok((tube1, tube2))
+ }
+
+ /// Create a new `Tube`.
+ pub fn new(socket: UnixSeqpacket) -> Tube {
+ Tube {
+ socket,
+ _unsync_marker: PhantomData,
+ }
+ }
+
+ /// DO NOT USE this method directly as it will become private soon (b/221484449). Use a
+ /// directional Tube pair instead.
+ #[deprecated]
+ pub fn try_clone(&self) -> Result<Self> {
+ self.socket.try_clone().map(Tube::new).map_err(Error::Clone)
+ }
+
+ pub fn send<T: Serialize>(&self, msg: &T) -> Result<()> {
+ let msg_serialize = SerializeDescriptors::new(&msg);
+ let msg_json = serde_json::to_vec(&msg_serialize).map_err(Error::Json)?;
+ let msg_descriptors = msg_serialize.into_descriptors();
+
+ self.socket
+ .send_with_fds(&[IoSlice::new(&msg_json)], &msg_descriptors)
+ .map_err(Error::Send)?;
+ Ok(())
+ }
+
+ pub fn recv<T: DeserializeOwned>(&self) -> Result<T> {
+ let (msg_json, msg_descriptors) =
+ self.socket.recv_as_vec_with_fds().map_err(Error::Recv)?;
+
+ if msg_json.is_empty() {
+ return Err(Error::Disconnected);
+ }
+
+ let mut msg_descriptors_safe = msg_descriptors
+ .into_iter()
+ .map(|v| {
+ Some(unsafe {
+ // Safe because the socket returns new fds that are owned locally by this scope.
+ SafeDescriptor::from_raw_descriptor(v)
+ })
+ })
+ .collect();
+
+ deserialize_with_descriptors(
+ || serde_json::from_slice(&msg_json),
+ &mut msg_descriptors_safe,
+ )
+ .map_err(Error::Json)
+ }
+
+ pub fn set_send_timeout(&self, timeout: Option<Duration>) -> Result<()> {
+ self.socket
+ .set_write_timeout(timeout)
+ .map_err(Error::SetSendTimeout)
+ }
+
+ pub fn set_recv_timeout(&self, timeout: Option<Duration>) -> Result<()> {
+ self.socket
+ .set_read_timeout(timeout)
+ .map_err(Error::SetRecvTimeout)
+ }
+}
+
+impl AsRawDescriptor for Tube {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.socket.as_raw_descriptor()
+ }
+}
+
+impl AsRawFd for Tube {
+ fn as_raw_fd(&self) -> RawFd {
+ self.socket.as_raw_fd()
+ }
+}
+
+impl ReadNotifier for Tube {
+ fn get_read_notifier(&self) -> &dyn AsRawDescriptor {
+ &self.socket
+ }
+}
+
+impl FromRawDescriptor for Tube {
+ /// # Safety:
+ /// Requirements:
+ /// (1) The caller owns rd.
+ /// (2) When the call completes, ownership of rd has transferred to the returned value.
+ unsafe fn from_raw_descriptor(rd: RawDescriptor) -> Self {
+ Self {
+ socket: UnixSeqpacket::from_raw_descriptor(rd),
+ _unsync_marker: PhantomData,
+ }
+ }
+}
+
+impl AsRawFd for SendTube {
+ fn as_raw_fd(&self) -> RawFd {
+ self.0.as_raw_descriptor()
+ }
+}
+
+impl AsRawFd for RecvTube {
+ fn as_raw_fd(&self) -> RawFd {
+ self.0.as_raw_descriptor()
+ }
+}
diff --git a/sys_util/src/vsock.rs b/base/src/unix/vsock.rs
index a51dfdbfc..692652258 100644
--- a/sys_util/src/vsock.rs
+++ b/base/src/unix/vsock.rs
@@ -4,18 +4,25 @@
/// Support for virtual sockets.
use std::fmt;
-use std::io;
-use std::mem::{self, size_of};
-use std::num::ParseIntError;
-use std::os::raw::{c_uchar, c_uint, c_ushort};
-use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
-use std::result;
-use std::str::FromStr;
+use std::{
+ io,
+ mem::{
+ size_of, {self},
+ },
+ num::ParseIntError,
+ os::{
+ raw::{c_uchar, c_uint, c_ushort},
+ unix::io::{AsRawFd, IntoRawFd, RawFd},
+ },
+ result,
+ str::FromStr,
+};
use libc::{
- self, c_void, sa_family_t, size_t, sockaddr, socklen_t, F_GETFL, F_SETFL, O_NONBLOCK,
- VMADDR_CID_ANY, VMADDR_CID_HOST, VMADDR_CID_HYPERVISOR,
+ c_void, sa_family_t, size_t, sockaddr, socklen_t, F_GETFL, F_SETFL, O_NONBLOCK, VMADDR_CID_ANY,
+ VMADDR_CID_HOST, VMADDR_CID_HYPERVISOR, {self},
};
+use thiserror::Error;
// The domain for vsock sockets.
const AF_VSOCK: sa_family_t = 40;
@@ -43,17 +50,12 @@ struct sockaddr_vm {
svm_zero: [c_uchar; PADDING],
}
-#[derive(Debug)]
+#[derive(Error, Debug)]
+#[error("failed to parse vsock address")]
pub struct AddrParseError;
-impl fmt::Display for AddrParseError {
- fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
- write!(fmt, "failed to parse vsock address")
- }
-}
-
/// The vsock equivalent of an IP address.
-#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub enum VsockCid {
/// Vsock equivalent of INADDR_ANY. Indicates the context id of the current endpoint.
Any,
@@ -113,7 +115,7 @@ impl From<VsockCid> for c_uint {
}
/// An address associated with a virtual socket.
-#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct SocketAddr {
pub cid: VsockCid,
pub port: c_uint,
diff --git a/sys_util/src/write_zeroes.rs b/base/src/unix/write_zeroes.rs
index d05a201e4..15c92a23f 100644
--- a/sys_util/src/write_zeroes.rs
+++ b/base/src/unix/write_zeroes.rs
@@ -2,13 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::cmp::min;
-use std::fs::File;
-use std::io::{self, Error, ErrorKind, Seek, SeekFrom};
-use std::os::unix::fs::FileExt;
-
-use crate::fallocate;
-use crate::FallocateMode;
+use std::{
+ cmp::min,
+ fs::File,
+ io::{
+ Error, ErrorKind, Seek, SeekFrom, {self},
+ },
+ os::unix::fs::FileExt,
+};
+
+use super::{fallocate, FallocateMode};
/// A trait for deallocating space in a file.
pub trait PunchHole {
diff --git a/base/src/wait_context.rs b/base/src/wait_context.rs
index f3afb6ae4..ed03e4ce0 100644
--- a/base/src/wait_context.rs
+++ b/base/src/wait_context.rs
@@ -2,12 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::os::unix::io::AsRawFd;
use std::time::Duration;
-use crate::{wrap_descriptor, AsRawDescriptor, RawDescriptor, Result};
+use crate::descriptor::AsRawDescriptor;
+use crate::{
+ platform::{PollContext, PollToken, WatchingEvents},
+ RawDescriptor, Result,
+};
use smallvec::SmallVec;
-use sys_util::{PollContext, PollToken, WatchingEvents};
// Typedef PollToken as EventToken for better adherance to base naming.
// As actual typdefing is experimental, define a new trait with the mirrored
@@ -88,11 +90,8 @@ impl<T: EventToken> WaitContext<T> {
event_type: EventType,
token: T,
) -> Result<()> {
- self.0.add_fd_with_events(
- &wrap_descriptor(descriptor),
- convert_to_watching_events(event_type),
- token,
- )
+ self.0
+ .add_fd_with_events(descriptor, convert_to_watching_events(event_type), token)
}
/// Adds multiple triggers to the WaitContext.
@@ -111,17 +110,14 @@ impl<T: EventToken> WaitContext<T> {
event_type: EventType,
token: T,
) -> Result<()> {
- self.0.modify(
- &wrap_descriptor(descriptor),
- convert_to_watching_events(event_type),
- token,
- )
+ self.0
+ .modify(descriptor, convert_to_watching_events(event_type), token)
}
/// Removes the given handle from triggers registered in the WaitContext if
/// present.
pub fn delete(&self, descriptor: &dyn AsRawDescriptor) -> Result<()> {
- self.0.delete(&wrap_descriptor(descriptor))
+ self.0.delete(descriptor)
}
/// Waits for one or more of the registered triggers to become signaled.
@@ -146,7 +142,7 @@ impl<T: EventToken> WaitContext<T> {
impl<T: PollToken> AsRawDescriptor for WaitContext<T> {
fn as_raw_descriptor(&self) -> RawDescriptor {
- self.0.as_raw_fd()
+ self.0.as_raw_descriptor()
}
}
diff --git a/base/src/windows/clock.rs b/base/src/windows/clock.rs
new file mode 100644
index 000000000..2d8d0ab7e
--- /dev/null
+++ b/base/src/windows/clock.rs
@@ -0,0 +1,119 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Utility file to provide a fake clock object representing current time, and a timer driven by
+// that time.
+
+use std::time::{Duration, Instant};
+
+use super::Event;
+use crate::descriptor::AsRawDescriptor;
+
+#[derive(Debug, Copy, Clone)]
+pub struct Clock(Instant);
+impl Clock {
+ pub fn new() -> Self {
+ Clock(Instant::now())
+ }
+
+ pub fn now(&self) -> Self {
+ Clock(Instant::now())
+ }
+
+ pub fn duration_since(&self, earlier: &Self) -> Duration {
+ self.0.duration_since(earlier.0)
+ }
+
+ pub fn elapsed(&self) -> Duration {
+ self.0.elapsed()
+ }
+
+ pub fn checked_sub(&self, duration: Duration) -> Option<Self> {
+ Some(Clock(self.0.checked_sub(duration)?))
+ }
+}
+
+impl Default for Clock {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+const NS_PER_SEC: u64 = 1_000_000_000;
+/// A fake clock that can be used in tests to give exact control over the time.
+/// For a code example, see the tests in sys_util/src/timer.rs.
+#[derive(Debug)]
+pub struct FakeClock {
+ ns_since_epoch: u64,
+ deadlines: Vec<(u64, Event)>,
+}
+
+impl FakeClock {
+ pub fn new() -> Self {
+ FakeClock {
+ ns_since_epoch: 1_547_163_599 * NS_PER_SEC,
+ deadlines: Vec::new(),
+ }
+ }
+
+ /// Get the current time, according to this clock.
+ pub fn now(&self) -> Self {
+ FakeClock {
+ ns_since_epoch: self.ns_since_epoch,
+ deadlines: Vec::new(),
+ }
+ }
+
+ /// Get the current time in ns, according to this clock.
+ pub fn nanos(&self) -> u64 {
+ self.ns_since_epoch
+ }
+
+ /// Get the duration since |earlier|, assuming that earlier < self.
+ pub fn duration_since(&self, earlier: &Self) -> Duration {
+ let ns_diff = self.ns_since_epoch - earlier.ns_since_epoch;
+ Duration::new(ns_diff / NS_PER_SEC, (ns_diff % NS_PER_SEC) as u32)
+ }
+
+ /// Get the time that has elapsed since this clock was made. Always returns 0 on a FakeClock.
+ pub fn elapsed(&self) -> Duration {
+ self.now().duration_since(self)
+ }
+
+ pub fn checked_sub(&self, duration: Duration) -> Option<Self> {
+ Some(FakeClock {
+ ns_since_epoch: self
+ .ns_since_epoch
+ .checked_sub(duration.as_nanos() as u64)?,
+ deadlines: Vec::new(),
+ })
+ }
+
+ /// Register the event descriptor for a notification when self's time is |deadline_ns|.
+ /// Drop any existing events registered to the same raw descriptor.
+ pub fn add_event(&mut self, deadline_ns: u64, descriptor: Event) {
+ self.deadlines.retain(|(_, old_descriptor)| {
+ descriptor.as_raw_descriptor() != old_descriptor.as_raw_descriptor()
+ });
+ self.deadlines.push((deadline_ns, descriptor));
+ }
+
+ pub fn add_ns(&mut self, ns: u64) {
+ self.ns_since_epoch += ns;
+ let time = self.ns_since_epoch;
+ self.deadlines.retain(|(ns, descriptor)| {
+ let expired = *ns <= time;
+ if expired {
+ descriptor.write(1).unwrap();
+ }
+ !expired
+ });
+ }
+}
+
+impl Default for FakeClock {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/base/src/windows/descriptor.rs b/base/src/windows/descriptor.rs
new file mode 100644
index 000000000..2fa29af0f
--- /dev/null
+++ b/base/src/windows/descriptor.rs
@@ -0,0 +1,181 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::Result;
+use std::{
+ convert::TryFrom,
+ fs::File,
+ io::{Stderr, Stdin, Stdout},
+ ops::Drop,
+};
+
+use crate::descriptor::{
+ AsRawDescriptor, Descriptor, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor,
+};
+use std::{
+ ffi::CString,
+ marker::{Send, Sync},
+ mem::MaybeUninit,
+ os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle},
+ sync::Once,
+};
+use win_util::{duplicate_handle, win32_wide_string};
+use winapi::{
+ shared::minwindef::{BOOL, HMODULE, TRUE},
+ um::{
+ handleapi::{CloseHandle, INVALID_HANDLE_VALUE},
+ libloaderapi,
+ },
+};
+
+pub type RawDescriptor = RawHandle;
+
+pub const INVALID_DESCRIPTOR: RawDescriptor = INVALID_HANDLE_VALUE;
+
+impl PartialEq for SafeDescriptor {
+ fn eq(&self, other: &Self) -> bool {
+ return compare_object_handles(self.descriptor, other.as_raw_descriptor());
+ }
+}
+
+impl Drop for SafeDescriptor {
+ fn drop(&mut self) {
+ unsafe { CloseHandle(self.descriptor) };
+ }
+}
+
+impl AsRawHandle for SafeDescriptor {
+ fn as_raw_handle(&self) -> RawHandle {
+ self.as_raw_descriptor()
+ }
+}
+
+static KERNELBASE_INIT: Once = Once::new();
+static mut KERNELBASE_LIBRARY: MaybeUninit<HMODULE> = MaybeUninit::uninit();
+
+fn compare_object_handles(first: RawHandle, second: RawHandle) -> bool {
+ KERNELBASE_INIT.call_once(|| {
+ unsafe {
+ *KERNELBASE_LIBRARY.as_mut_ptr() =
+ libloaderapi::LoadLibraryW(win32_wide_string("Kernelbase").as_ptr());
+ };
+ });
+ let handle = unsafe { KERNELBASE_LIBRARY.assume_init() };
+ if handle.is_null() {
+ return first == second;
+ }
+
+ let addr = CString::new("CompareObjectHandles").unwrap();
+ let addr_ptr = addr.as_ptr();
+ let symbol = unsafe { libloaderapi::GetProcAddress(handle, addr_ptr) };
+ if symbol.is_null() {
+ return first == second;
+ }
+
+ let func = unsafe {
+ std::mem::transmute::<
+ *mut winapi::shared::minwindef::__some_function,
+ extern "system" fn(RawHandle, RawHandle) -> BOOL,
+ >(symbol)
+ };
+ let ret = func(first, second);
+ ret == TRUE
+}
+
+impl TryFrom<&dyn AsRawHandle> for SafeDescriptor {
+ type Error = std::io::Error;
+
+ fn try_from(handle: &dyn AsRawHandle) -> std::result::Result<Self, Self::Error> {
+ Ok(SafeDescriptor {
+ descriptor: duplicate_handle(handle.as_raw_handle())?,
+ })
+ }
+}
+
+impl SafeDescriptor {
+ /// Clones this descriptor, internally creating a new descriptor. The new SafeDescriptor will
+ /// share the same underlying count within the kernel.
+ pub fn try_clone(&self) -> Result<SafeDescriptor> {
+ // Safe because `duplicate_handle` will return a valid handle, or at the very least error
+ // out.
+ Ok(unsafe {
+ SafeDescriptor::from_raw_descriptor(win_util::duplicate_handle(self.descriptor)?)
+ })
+ }
+}
+
+// On Windows, RawHandles are represented by raw pointers but are not used as such in
+// rust code, and are therefore safe to send between threads.
+unsafe impl Send for SafeDescriptor {}
+unsafe impl Sync for SafeDescriptor {}
+
+// On Windows, RawHandles are represented by raw pointers but are opaque to the
+// userspace and cannot be derefenced by rust code, and are therefore safe to
+// send between threads.
+unsafe impl Send for Descriptor {}
+unsafe impl Sync for Descriptor {}
+
+macro_rules! AsRawDescriptor {
+ ($name:ident) => {
+ impl AsRawDescriptor for $name {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ return self.as_raw_handle();
+ }
+ }
+ };
+}
+
+macro_rules! FromRawDescriptor {
+ ($name:ident) => {
+ impl FromRawDescriptor for $name {
+ unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Self {
+ return $name::from_raw_handle(descriptor);
+ }
+ }
+ };
+}
+
+macro_rules! IntoRawDescriptor {
+ ($name:ident) => {
+ impl IntoRawDescriptor for $name {
+ fn into_raw_descriptor(self) -> RawDescriptor {
+ return self.into_raw_handle();
+ }
+ }
+ };
+}
+
+// Implementations for File. This enables the File-type to use the cross-platform
+// RawDescriptor, but does not mean File should be used as a generic
+// descriptor container. That should go to either SafeDescriptor or another more
+// relevant container type.
+// TODO(b/148971445): Ensure there are no usages of File that aren't actually files.
+AsRawDescriptor!(File);
+FromRawDescriptor!(File);
+IntoRawDescriptor!(File);
+AsRawDescriptor!(Stdin);
+AsRawDescriptor!(Stdout);
+AsRawDescriptor!(Stderr);
+
+#[test]
+#[allow(clippy::eq_op)]
+fn clone_equality() {
+ use super::Event;
+ use crate::descriptor::IntoRawDescriptor;
+
+ let evt = Event::new().unwrap();
+ let descriptor = unsafe { SafeDescriptor::from_raw_descriptor(evt.into_raw_descriptor()) };
+
+ assert_eq!(descriptor, descriptor);
+
+ assert_eq!(
+ descriptor,
+ descriptor.try_clone().expect("failed to clone event")
+ );
+
+ let evt2 = Event::new().unwrap();
+ let another = unsafe { SafeDescriptor::from_raw_descriptor(evt2.into_raw_descriptor()) };
+
+ assert_ne!(descriptor, another);
+}
diff --git a/base/src/windows/eventfd.rs b/base/src/windows/eventfd.rs
new file mode 100644
index 000000000..b286668d3
--- /dev/null
+++ b/base/src/windows/eventfd.rs
@@ -0,0 +1,210 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cmp::PartialEq;
+use std::mem;
+use std::ptr;
+use std::time::Duration;
+
+use libc::{c_void, eventfd, read, write, POLLIN};
+use serde::{Deserialize, Serialize};
+
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor};
+use crate::{duration_to_timespec, errno_result, RawDescriptor, Result};
+use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
+
+/// A safe wrapper around a Linux eventfd (man 2 eventfd).
+///
+/// An eventfd is useful because it is sendable across processes and can be used for signaling in
+/// and out of the KVM API. They can also be polled like any other file descriptor.
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct Event {
+ event_handle: SafeDescriptor,
+}
+
+/// Wrapper around the return value of doing a read on an EventFd which distinguishes between
+/// getting a valid count of the number of times the eventfd has been written to and timing out
+/// waiting for the count to be non-zero.
+#[derive(Debug, PartialEq, Eq)]
+pub enum EventReadResult {
+ Count(u64),
+ Timeout,
+}
+
+impl Event {
+ /// Creates a new blocking Event with an initial value of 0.
+ pub fn new() -> Result<Event> {
+ // This is safe because eventfd merely allocated an eventfd for our process and we handle
+ // the error case.
+ let ret = unsafe { eventfd(0, 0) };
+ if ret < 0 {
+ return errno_result();
+ }
+ // This is safe because we checked ret for success and know the kernel gave us an descriptor that we
+ // own.
+ Ok(Event {
+ event_handle: unsafe { SafeDescriptor::from_raw_descriptor(ret) },
+ })
+ }
+
+ /// Adds `v` to the eventfd's count, blocking until this won't overflow the count.
+ pub fn write(&self, v: u64) -> Result<()> {
+ // This is safe because we made this descriptor and the pointer we pass can not overflow because we
+ // give the syscall's size parameter properly.
+ let ret = unsafe {
+ write(
+ self.as_raw_descriptor(),
+ &v as *const u64 as *const c_void,
+ mem::size_of::<u64>(),
+ )
+ };
+ if ret <= 0 {
+ return errno_result();
+ }
+ Ok(())
+ }
+
+ /// Blocks until the the eventfd's count is non-zero, then resets the count to zero.
+ pub fn read(&self) -> Result<u64> {
+ let mut buf: u64 = 0;
+ let ret = unsafe {
+ // This is safe because we made this descriptor and the pointer we pass can not overflow because
+ // we give the syscall's size parameter properly.
+ read(
+ self.as_raw_descriptor(),
+ &mut buf as *mut u64 as *mut c_void,
+ mem::size_of::<u64>(),
+ )
+ };
+ if ret <= 0 {
+ return errno_result();
+ }
+ Ok(buf)
+ }
+
+ /// Blocks for a maximum of `timeout` duration until the the eventfd's count is non-zero. If
+ /// a timeout does not occur then the count is returned as a EventReadResult::Count(count),
+ /// and the count is reset to 0. If a timeout does occur then this function will return
+ /// EventReadResult::Timeout.
+ pub fn read_timeout(&self, timeout: Duration) -> Result<EventReadResult> {
+ let mut pfd = libc::pollfd {
+ fd: self.as_raw_descriptor(),
+ events: POLLIN,
+ revents: 0,
+ };
+ let timeoutspec: libc::timespec = duration_to_timespec(timeout);
+ // Safe because this only modifies |pfd| and we check the return value
+ let ret = unsafe {
+ libc::ppoll(
+ &mut pfd as *mut libc::pollfd,
+ 1,
+ &timeoutspec,
+ ptr::null_mut(),
+ )
+ };
+ if ret < 0 {
+ return errno_result();
+ }
+
+ // no return events (revents) means we got a timeout
+ if pfd.revents == 0 {
+ return Ok(EventReadResult::Timeout);
+ }
+
+ let mut buf = 0u64;
+ // This is safe because we made this fd and the pointer we pass can not overflow because
+ // we give the syscall's size parameter properly.
+ let ret = unsafe {
+ libc::read(
+ self.as_raw_descriptor(),
+ &mut buf as *mut _ as *mut c_void,
+ mem::size_of::<u64>(),
+ )
+ };
+ if ret < 0 {
+ return errno_result();
+ }
+ Ok(EventReadResult::Count(buf))
+ }
+
+ /// Clones this Event, internally creating a new file descriptor. The new Event will share
+ /// the same underlying count within the kernel.
+ pub fn try_clone(&self) -> Result<Event> {
+ self.event_handle
+ .try_clone()
+ .map(|event_handle| Event { event_handle })
+ }
+}
+
+impl AsRawDescriptor for Event {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.event_handle.as_raw_descriptor()
+ }
+}
+
+impl FromRawDescriptor for Event {
+ unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Self {
+ Event {
+ event_handle: SafeDescriptor::from_raw_descriptor(descriptor),
+ }
+ }
+}
+
+impl IntoRawDescriptor for Event {
+ fn into_raw_descriptor(self) -> RawDescriptor {
+ self.event_handle.into_raw_descriptor()
+ }
+}
+
+/// TODO(nkgold): this is a hack until sys_util is reverted to expose POSIX
+/// primitives (e.g. EventFd).
+impl AsRawFd for Event {
+ fn as_raw_fd(&self) -> RawFd {
+ self.event_handle.as_raw_descriptor()
+ }
+}
+
+/// TODO(nkgold): this is a hack until sys_util is reverted to expose POSIX
+/// primitives (e.g. EventFd).
+impl IntoRawFd for Event {
+ fn into_raw_fd(self) -> RawFd {
+ self.event_handle.into_raw_descriptor()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn new() {
+ Event::new().unwrap();
+ }
+
+ #[test]
+ fn read_write() {
+ let evt = Event::new().unwrap();
+ evt.write(55).unwrap();
+ assert_eq!(evt.read(), Ok(55));
+ }
+
+ #[test]
+ fn clone() {
+ let evt = Event::new().unwrap();
+ let evt_clone = evt.try_clone().unwrap();
+ evt.write(923).unwrap();
+ assert_eq!(evt_clone.read(), Ok(923));
+ }
+
+ #[test]
+ fn timeout() {
+ let evt = Event::new().expect("failed to create event");
+ assert_eq!(
+ evt.read_timeout(Duration::from_millis(1))
+ .expect("failed to read from event with timeout"),
+ EventReadResult::Timeout
+ );
+ }
+}
diff --git a/base/src/windows/events.rs b/base/src/windows/events.rs
new file mode 100644
index 000000000..dda0ba8a8
--- /dev/null
+++ b/base/src/windows/events.rs
@@ -0,0 +1,191 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{clone::Clone, default::Default, marker::Copy};
+
+use super::{PollToken, RawDescriptor};
+use crate::descriptor::AsRawDescriptor;
+
+#[path = "win/wait.rs"]
+mod wait;
+pub use wait::*;
+
+/// Represents descriptor-token pairs which represent an event which can be triggered in the
+/// EventContext
+#[derive(PartialEq)]
+pub struct EventTrigger<T: PollToken> {
+ token: T,
+ event: RawDescriptor,
+}
+
+impl<T: PollToken> EventTrigger<T> {
+ pub fn from(descriptor: &dyn AsRawDescriptor, token: T) -> Self {
+ EventTrigger {
+ token,
+ event: descriptor.as_raw_descriptor(),
+ }
+ }
+}
+
+impl<T: PollToken> Clone for EventTrigger<T> {
+ fn clone(&self) -> Self {
+ EventTrigger {
+ token: T::from_raw_token(self.token.as_raw_token()),
+ event: self.event,
+ }
+ }
+}
+
+/// Represents an event that has been signaled and waited for via a wait function.
+#[derive(Copy, Clone, Debug)]
+pub struct TriggeredEvent<T: PollToken> {
+ pub token: T,
+ pub is_readable: bool,
+ pub is_writable: bool,
+ pub is_hungup: bool,
+}
+
+impl<T: PollToken> Default for TriggeredEvent<T> {
+ fn default() -> Self {
+ TriggeredEvent {
+ token: T::from_raw_token(0),
+ is_readable: false,
+ is_writable: false,
+ is_hungup: false,
+ }
+ }
+}
+
+/// Represents types of events to watch for.
+pub enum EventType {
+ // Used to to temporarily stop waiting for events without
+ // removing the associated descriptor from the WaitContext.
+ // In most cases if a descriptor no longer needs to be
+ // waited on, prefer removing it entirely with
+ // WaitContext#delete
+ None,
+ Read,
+ Write,
+ ReadWrite,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{super::Event, *};
+ use std::time::Duration;
+
+ #[test]
+ fn event_context() {
+ let evt1 = Event::new().unwrap();
+ let evt2 = Event::new().unwrap();
+ evt1.write(1).unwrap();
+ evt2.write(1).unwrap();
+ let ctx: EventContext<u32> =
+ EventContext::build_with(&[EventTrigger::from(&evt1, 1), EventTrigger::from(&evt2, 2)])
+ .unwrap();
+
+ let mut evt_count = 0;
+ while evt_count < 2 {
+ for event in ctx.wait().unwrap().iter() {
+ evt_count += 1;
+ match event.token {
+ 1 => {
+ evt1.read().unwrap();
+ ctx.remove(&evt1).unwrap();
+ }
+ 2 => {
+ evt2.read().unwrap();
+ ctx.remove(&evt2).unwrap();
+ }
+ _ => panic!("unexpected token"),
+ };
+ }
+ }
+ assert_eq!(evt_count, 2);
+ }
+
+ // TODO(145170451) rizhang: This test will be needed to be implemented when the round robn
+ // implementation is complete
+ // #[test]
+ // fn poll_context_overflow() {
+ // const EVT_COUNT: usize = MAXIMUM_WAIT_OBJECTS * 2 + 1;
+ // let ctx: EventContext<usize> = EventContext::new().unwrap();
+ // let mut evts = Vec::with_capacity(EVT_COUNT);
+ // for i in 0..EVT_COUNT {
+ // let evt = Event::new().unwrap();
+ // evt.write(1).unwrap();
+ // ctx.add(&evt, i).unwrap();
+ // evts.push(evt);
+ // }
+ // let mut evt_count = 0;
+ // while evt_count < EVT_COUNT {
+ // for event in ctx.wait().unwrap().iter_readable() {
+ // evts[event.token()].read().unwrap();
+ // evt_count += 1;
+ // }
+ // }
+ // }
+
+ #[test]
+ fn poll_context_timeout() {
+ let ctx: EventContext<u32> = EventContext::new().unwrap();
+ let evt = Event::new().unwrap();
+ ctx.add(EventTrigger::from(&evt, 1))
+ .expect("Failed to add event.");
+ let dur = Duration::from_millis(100);
+ let events = ctx.wait_timeout(dur).unwrap();
+ assert_eq!(events.len(), 0);
+ }
+
+ #[test]
+ fn wait_returns_mulitple_signal_events() {
+ let evt1 = Event::new().unwrap();
+ let evt2 = Event::new().unwrap();
+ let evt3 = Event::new().unwrap();
+ evt1.write(1).expect("Failed to write to event.");
+ evt2.write(1).expect("Failed to write to event.");
+ evt3.write(1).expect("Failed to write to event.");
+ let ctx: EventContext<u32> = EventContext::build_with(&[
+ EventTrigger::from(&evt1, 1),
+ EventTrigger::from(&evt2, 2),
+ EventTrigger::from(&evt3, 3),
+ ])
+ .unwrap();
+ let events = ctx.wait().unwrap();
+
+ let tokens: Vec<u32> = events.iter().map(|e| e.token).collect();
+ assert_eq!(tokens.len(), 3);
+ assert_eq!(tokens, [1, 2, 3]);
+ }
+
+ #[test]
+ fn wait_returns_mulitple_signal_and_unsignaled_events() {
+ let evt1 = Event::new().unwrap();
+ let evt2 = Event::new().unwrap();
+ let evt3 = Event::new().unwrap();
+ let evt4 = Event::new().unwrap();
+ let evt5 = Event::new().unwrap();
+ let evt6 = Event::new().unwrap();
+ let evt7 = Event::new().unwrap();
+ evt1.write(1).unwrap();
+ evt2.write(1).unwrap();
+ evt4.write(1).unwrap();
+ evt7.write(1).unwrap();
+ let ctx: EventContext<u32> = EventContext::build_with(&[
+ EventTrigger::from(&evt1, 1),
+ EventTrigger::from(&evt2, 2),
+ EventTrigger::from(&evt3, 3),
+ EventTrigger::from(&evt4, 4),
+ EventTrigger::from(&evt5, 5),
+ EventTrigger::from(&evt6, 6),
+ EventTrigger::from(&evt7, 7),
+ ])
+ .unwrap();
+ let events = ctx.wait().unwrap();
+
+ let tokens: Vec<u32> = events.iter().map(|e| e.token).collect();
+ assert_eq!(tokens.len(), 4);
+ assert_eq!(tokens, [1, 2, 4, 7]);
+ }
+}
diff --git a/base/src/windows/file_traits.rs b/base/src/windows/file_traits.rs
new file mode 100644
index 000000000..e9b9302d4
--- /dev/null
+++ b/base/src/windows/file_traits.rs
@@ -0,0 +1,430 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+pub use super::win::file_traits::*;
+use super::RawDescriptor;
+use crate::descriptor::AsRawDescriptor;
+
+use std::{
+ fs::File,
+ io::{Error, ErrorKind, Result},
+};
+
+use data_model::VolatileSlice;
+
+/// A trait for flushing the contents of a file to disk.
+/// This is equivalent to File's `sync_all` method, but
+/// wrapped in a trait so that it can be implemented for
+/// other types.
+pub trait FileSync {
+ // Flush buffers related to this file to disk.
+ fn fsync(&mut self) -> Result<()>;
+}
+
+impl FileSync for File {
+ fn fsync(&mut self) -> Result<()> {
+ self.sync_all()
+ }
+}
+
+/// A trait for setting the size of a file.
+/// This is equivalent to File's `set_len` method, but
+/// wrapped in a trait so that it can be implemented for
+/// other types.
+pub trait FileSetLen {
+ // Set the size of this file.
+ // This is the moral equivalent of `ftruncate()`.
+ fn set_len(&self, _len: u64) -> Result<()>;
+}
+
+impl FileSetLen for File {
+ fn set_len(&self, len: u64) -> Result<()> {
+ File::set_len(self, len)
+ }
+}
+
+/// A trait for allocating disk space in a sparse file.
+/// This is equivalent to fallocate() with no special flags.
+pub trait FileAllocate {
+ /// Allocate storage for the region of the file starting at `offset` and extending `len` bytes.
+ fn allocate(&mut self, offset: u64, len: u64) -> Result<()>;
+}
+
+/// A trait for getting the size of a file.
+/// This is equivalent to File's metadata().len() method,
+/// but wrapped in a trait so that it can be implemented for
+/// other types.
+pub trait FileGetLen {
+ /// Get the current length of the file in bytes.
+ fn get_len(&self) -> Result<u64>;
+}
+
+impl FileGetLen for File {
+ fn get_len(&self) -> Result<u64> {
+ Ok(self.metadata()?.len())
+ }
+}
+
+/// A trait similar to `Read` and `Write`, but uses volatile memory as buffers.
+pub trait FileReadWriteVolatile {
+ /// Read bytes from this file into the given slice, returning the number of bytes read on
+ /// success.
+ fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize>;
+
+ /// Like `read_volatile`, except it reads to a slice of buffers. Data is copied to fill each
+ /// buffer in order, with the final buffer written to possibly being only partially filled. This
+ /// method must behave as a single call to `read_volatile` with the buffers concatenated would.
+ /// The default implementation calls `read_volatile` with either the first nonempty buffer
+ /// provided, or returns `Ok(0)` if none exists.
+ fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
+ bufs.iter()
+ .find(|b| b.size() > 0)
+ .map(|&b| self.read_volatile(b))
+ .unwrap_or(Ok(0))
+ }
+
+ /// Reads bytes from this into the given slice until all bytes in the slice are written, or an
+ /// error is returned.
+ fn read_exact_volatile(&mut self, mut slice: VolatileSlice) -> Result<()> {
+ while slice.size() > 0 {
+ let bytes_read = self.read_volatile(slice)?;
+ if bytes_read == 0 {
+ return Err(Error::from(ErrorKind::UnexpectedEof));
+ }
+ // Will panic if read_volatile read more bytes than we gave it, which would be worthy of
+ // a panic.
+ slice = slice.offset(bytes_read).unwrap();
+ }
+ Ok(())
+ }
+
+ /// Write bytes from the slice to the given file, returning the number of bytes written on
+ /// success.
+ fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize>;
+
+ /// Like `write_volatile`, except that it writes from a slice of buffers. Data is copied from
+ /// each buffer in order, with the final buffer read from possibly being only partially
+ /// consumed. This method must behave as a call to `write_volatile` with the buffers
+ /// concatenated would. The default implementation calls `write_volatile` with either the first
+ /// nonempty buffer provided, or returns `Ok(0)` if none exists.
+ fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
+ bufs.iter()
+ .find(|b| b.size() > 0)
+ .map(|&b| self.write_volatile(b))
+ .unwrap_or(Ok(0))
+ }
+
+ /// Write bytes from the slice to the given file until all the bytes from the slice have been
+ /// written, or an error is returned.
+ fn write_all_volatile(&mut self, mut slice: VolatileSlice) -> Result<()> {
+ while slice.size() > 0 {
+ let bytes_written = self.write_volatile(slice)?;
+ if bytes_written == 0 {
+ return Err(Error::from(ErrorKind::WriteZero));
+ }
+ // Will panic if read_volatile read more bytes than we gave it, which would be worthy of
+ // a panic.
+ slice = slice.offset(bytes_written).unwrap();
+ }
+ Ok(())
+ }
+}
+
+impl<'a, T: FileReadWriteVolatile + ?Sized> FileReadWriteVolatile for &'a mut T {
+ fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
+ (**self).read_volatile(slice)
+ }
+
+ fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
+ (**self).read_vectored_volatile(bufs)
+ }
+
+ fn read_exact_volatile(&mut self, slice: VolatileSlice) -> Result<()> {
+ (**self).read_exact_volatile(slice)
+ }
+
+ fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
+ (**self).write_volatile(slice)
+ }
+
+ fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
+ (**self).write_vectored_volatile(bufs)
+ }
+
+ fn write_all_volatile(&mut self, slice: VolatileSlice) -> Result<()> {
+ (**self).write_all_volatile(slice)
+ }
+}
+
+/// A trait similar to the unix `ReadExt` and `WriteExt` traits, but for volatile memory.
+pub trait FileReadWriteAtVolatile {
+ /// Reads bytes from this file at `offset` into the given slice, returning the number of bytes
+ /// read on success. On Windows file pointer will update with the read, but on Linux the
+ /// file pointer will not change.
+ fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize>;
+
+ /// Like `read_at_volatile`, except it reads to a slice of buffers. Data is copied to fill each
+ /// buffer in order, with the final buffer written to possibly being only partially filled. This
+ /// method must behave as a single call to `read_at_volatile` with the buffers concatenated
+ /// would. The default implementation calls `read_at_volatile` with either the first nonempty
+ /// buffer provided, or returns `Ok(0)` if none exists.
+ /// On Windows file pointer will update with the read, but on Linux the file pointer will not change.
+ fn read_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
+ if let Some(&slice) = bufs.first() {
+ self.read_at_volatile(slice, offset)
+ } else {
+ Ok(0)
+ }
+ }
+
+ /// Reads bytes from this file at `offset` into the given slice until all bytes in the slice are
+ /// read, or an error is returned. On Windows file pointer will update with the read, but on Linux the
+ /// file pointer will not change.
+ fn read_exact_at_volatile(&mut self, mut slice: VolatileSlice, mut offset: u64) -> Result<()> {
+ while slice.size() > 0 {
+ match self.read_at_volatile(slice, offset) {
+ Ok(0) => return Err(Error::from(ErrorKind::UnexpectedEof)),
+ Ok(n) => {
+ slice = slice.offset(n).unwrap();
+ offset = offset.checked_add(n as u64).unwrap();
+ }
+ Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(())
+ }
+
+ /// Writes bytes to this file at `offset` from the given slice, returning the number of bytes
+ /// written on success. On Windows file pointer will update with the write, but on Linux the
+ /// file pointer will not change.
+ fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize>;
+
+ /// Like `write_at_volatile`, except that it writes from a slice of buffers. Data is copied
+ /// from each buffer in order, with the final buffer read from possibly being only partially
+ /// consumed. This method must behave as a call to `write_at_volatile` with the buffers
+ /// concatenated would. The default implementation calls `write_at_volatile` with either the
+ /// first nonempty buffer provided, or returns `Ok(0)` if none exists.
+ /// On Windows file pointer will update with the write, but on Linux the file pointer will not change.
+ fn write_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
+ if let Some(&slice) = bufs.first() {
+ self.write_at_volatile(slice, offset)
+ } else {
+ Ok(0)
+ }
+ }
+
+ /// Writes bytes to this file at `offset` from the given slice until all bytes in the slice
+ /// are written, or an error is returned. On Windows file pointer will update with the write,
+ /// but on Linux the file pointer will not change.
+ fn write_all_at_volatile(&mut self, mut slice: VolatileSlice, mut offset: u64) -> Result<()> {
+ while slice.size() > 0 {
+ match self.write_at_volatile(slice, offset) {
+ Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
+ Ok(n) => {
+ slice = slice.offset(n).unwrap();
+ offset = offset.checked_add(n as u64).unwrap();
+ }
+ Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
+ Err(e) => return Err(e),
+ }
+ }
+ Ok(())
+ }
+}
+
+impl<'a, T: FileReadWriteAtVolatile + ?Sized> FileReadWriteAtVolatile for &'a mut T {
+ fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
+ (**self).read_at_volatile(slice, offset)
+ }
+
+ fn read_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
+ (**self).read_vectored_at_volatile(bufs, offset)
+ }
+
+ fn read_exact_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<()> {
+ (**self).read_exact_at_volatile(slice, offset)
+ }
+
+ fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
+ (**self).write_at_volatile(slice, offset)
+ }
+
+ fn write_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
+ (**self).write_vectored_at_volatile(bufs, offset)
+ }
+
+ fn write_all_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<()> {
+ (**self).write_all_at_volatile(slice, offset)
+ }
+}
+
+/// A trait similar to `AsRawFd` but supports an arbitrary number of file descriptors.
+pub trait AsRawDescriptors {
+ fn as_raw_descriptors(&self) -> Vec<RawDescriptor>;
+}
+
+impl<T> AsRawDescriptors for T
+where
+ T: AsRawDescriptor,
+{
+ fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
+ vec![self.as_raw_descriptor()]
+ }
+}
+
+crate::volatile_impl!(File);
+crate::volatile_at_impl!(File);
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use std::io::{Read, SeekFrom, Write};
+ use tempfile::tempfile;
+
+ #[test]
+ fn read_file() -> Result<()> {
+ let mut f = tempfile()?;
+ f.write_all(b"AAAAAAAAAAbbbbbbbbbbAAAAA")
+ .expect("Failed to write bytes");
+ f.seek(SeekFrom::Start(0))?;
+
+ let mut omem = [0u8; 30];
+ let om = &mut omem[..];
+ let buf = VolatileSlice::new(om);
+ f.read_volatile(buf).expect("read_volatile failed.");
+
+ f.seek(SeekFrom::Start(0))?;
+
+ let mut mem = [0u8; 30];
+ let (m1, rest) = mem.split_at_mut(10);
+ let (m2, m3) = rest.split_at_mut(10);
+ let buf1 = VolatileSlice::new(m1);
+ let buf2 = VolatileSlice::new(m2);
+ let buf3 = VolatileSlice::new(m3);
+ let bufs = [buf1, buf2, buf3];
+
+ f.read_vectored_volatile(&bufs)
+ .expect("read_vectored_volatile failed.");
+
+ assert_eq!(&mem[..], b"AAAAAAAAAAbbbbbbbbbbAAAAA\0\0\0\0\0");
+ Ok(())
+ }
+
+ #[test]
+ fn write_file() -> Result<()> {
+ let mut f = tempfile()?;
+
+ let mut omem = [0u8; 25];
+ let om = &mut omem[..];
+ let buf = VolatileSlice::new(om);
+ buf.write_bytes(65);
+ f.write_volatile(buf).expect("write_volatile failed.");
+
+ f.seek(SeekFrom::Start(0))?;
+
+ let mut filebuf = [0u8; 25];
+ f.read_exact(&mut filebuf).expect("Failed to read filebuf");
+ assert_eq!(&filebuf, b"AAAAAAAAAAAAAAAAAAAAAAAAA");
+ Ok(())
+ }
+
+ #[test]
+ fn write_vectored_file() -> Result<()> {
+ let mut f = tempfile()?;
+
+ let mut mem = [0u8; 30];
+ let (m1, rest) = mem.split_at_mut(10);
+ let (m2, m3) = rest.split_at_mut(10);
+ let buf1 = VolatileSlice::new(m1);
+ let buf2 = VolatileSlice::new(m2);
+ let buf3 = VolatileSlice::new(m3);
+ buf1.write_bytes(65);
+ buf2.write_bytes(98);
+ buf3.write_bytes(65);
+ let bufs = [buf1, buf2, buf3];
+ f.write_vectored_volatile(&bufs)
+ .expect("write_vectored_volatile failed.");
+
+ f.seek(SeekFrom::Start(0))?;
+
+ let mut filebuf = [0u8; 30];
+ f.read_exact(&mut filebuf).expect("Failed to read filebuf.");
+ assert_eq!(&filebuf, b"AAAAAAAAAAbbbbbbbbbbAAAAAAAAAA");
+ Ok(())
+ }
+
+ #[test]
+ fn read_at_file() -> Result<()> {
+ let mut f = tempfile()?;
+ f.write_all(b"AAAAAAAAAAbbbbbbbbbbAAAAA")
+ .expect("Failed to write bytes.");
+
+ let mut omem = [0u8; 20];
+ let om = &mut omem[..];
+ let buf = VolatileSlice::new(om);
+ f.read_at_volatile(buf, 10)
+ .expect("read_at_volatile failed.");
+
+ assert_eq!(om, b"bbbbbbbbbbAAAAA\0\0\0\0\0");
+
+ let mut mem = [0u8; 20];
+ let (m1, m2) = mem.split_at_mut(10);
+ let buf1 = VolatileSlice::new(m1);
+ let buf2 = VolatileSlice::new(m2);
+ let bufs = [buf1, buf2];
+
+ f.read_vectored_at_volatile(&bufs, 10)
+ .expect("read_vectored_at_volatile failed.");
+
+ assert_eq!(&mem[..], b"bbbbbbbbbbAAAAA\0\0\0\0\0");
+ Ok(())
+ }
+
+ #[test]
+ fn write_at_file() -> Result<()> {
+ let mut f = tempfile()?;
+ f.write_all(b"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ")
+ .expect("Failed to write bytes");
+
+ let mut omem = [0u8; 15];
+ let om = &mut omem[..];
+ let buf = VolatileSlice::new(om);
+ buf.write_bytes(65);
+ f.write_at_volatile(buf, 10)
+ .expect("write_at_volatile failed.");
+
+ f.seek(SeekFrom::Start(0))?;
+
+ let mut filebuf = [0u8; 30];
+ f.read_exact(&mut filebuf).expect("Failed to read filebuf.");
+ assert_eq!(&filebuf, b"ZZZZZZZZZZAAAAAAAAAAAAAAAZZZZZ");
+ Ok(())
+ }
+
+ #[test]
+ fn write_vectored_at_file() -> Result<()> {
+ let mut f = tempfile()?;
+ f.write_all(b"ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ")
+ .expect("Failed to write bytes");
+
+ let mut mem = [0u8; 30];
+ let (m1, m2) = mem.split_at_mut(10);
+ let buf1 = VolatileSlice::new(m1);
+ let buf2 = VolatileSlice::new(m2);
+ buf1.write_bytes(65);
+ buf2.write_bytes(98);
+ let bufs = [buf1, buf2];
+ f.write_vectored_at_volatile(&bufs, 10)
+ .expect("write_vectored_at_volatile failed.");
+
+ f.seek(SeekFrom::Start(0))?;
+
+ let mut filebuf = [0u8; 30];
+ f.read_exact(&mut filebuf).expect("Failed to read filebuf.");
+ assert_eq!(&filebuf, b"ZZZZZZZZZZAAAAAAAAAAbbbbbbbbbb");
+ Ok(())
+ }
+}
diff --git a/base/src/windows/gmtime.rs b/base/src/windows/gmtime.rs
new file mode 100644
index 000000000..ea32fac6d
--- /dev/null
+++ b/base/src/windows/gmtime.rs
@@ -0,0 +1,38 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use libc::{gmtime_s, time_t, tm};
+
+/// # Safety
+/// safe because we are passing in the allocated tm struct
+/// for the operating system to fill in.
+pub unsafe fn gmtime_secure(now: *const time_t, result: *mut tm) {
+ gmtime_s(result, now);
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::mem;
+
+ #[test]
+ fn gmtime() {
+ // Safe because struct is not used before being initialized
+ // by gmtime_secure.
+ let mut tm: tm = unsafe { mem::zeroed() };
+ let time_12_11_2019_noonish_pst = 1576094579;
+ unsafe {
+ gmtime_secure(&time_12_11_2019_noonish_pst, &mut tm);
+ }
+ assert_eq!(tm.tm_sec, 59);
+ assert_eq!(tm.tm_min, 2);
+ assert_eq!(tm.tm_hour, 20);
+ assert_eq!(tm.tm_mday, 11);
+ assert_eq!(tm.tm_mon, 11);
+ assert_eq!(tm.tm_year, 119);
+ assert_eq!(tm.tm_wday, 3);
+ assert_eq!(tm.tm_yday, 344);
+ assert_eq!(tm.tm_isdst, 0);
+ }
+}
diff --git a/base/src/windows/ioctl.rs b/base/src/windows/ioctl.rs
new file mode 100644
index 000000000..869f80495
--- /dev/null
+++ b/base/src/windows/ioctl.rs
@@ -0,0 +1,251 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Macros and wrapper functions for dealing with ioctls.
+
+use std::os::raw::*;
+
+use crate::descriptor::AsRawDescriptor;
+
+/// Raw macro to declare the expression that calculates an ioctl number
+#[macro_export]
+macro_rules! ioctl_expr {
+ ($dir:expr, $ty:expr, $nr:expr, $size:expr) => {
+ (($dir << $crate::platform::ioctl::_IOC_DIRSHIFT)
+ | ($ty << $crate::platform::ioctl::_IOC_TYPESHIFT)
+ | ($nr << $crate::platform::ioctl::_IOC_NRSHIFT)
+ | ($size << $crate::platform::ioctl::_IOC_SIZESHIFT)) as $crate::platform::IoctlNr
+ };
+}
+
+/// Raw macro to declare a function that returns an ioctl number.
+#[macro_export]
+macro_rules! ioctl_ioc_nr {
+ ($name:ident, $dir:expr, $ty:expr, $nr:expr, $size:expr) => {
+ #[allow(non_snake_case)]
+ pub const fn $name() -> $crate::platform::IoctlNr {
+ $crate::ioctl_expr!($dir, $ty, $nr, $size)
+ }
+ };
+ ($name:ident, $dir:expr, $ty:expr, $nr:expr, $size:expr, $($v:ident),+) => {
+ #[allow(non_snake_case)]
+ pub const fn $name($($v: ::std::os::raw::c_uint),+) -> $crate::platform::IoctlNr {
+ $crate::ioctl_expr!($dir, $ty, $nr, $size)
+ }
+ };
+}
+
+/// Declare an ioctl that transfers no data.
+#[macro_export]
+macro_rules! ioctl_io_nr {
+ ($name:ident, $ty:expr, $nr:expr) => {
+ $crate::ioctl_ioc_nr!($name, $crate::platform::ioctl::_IOC_NONE, $ty, $nr, 0);
+ };
+ ($name:ident, $ty:expr, $nr:expr, $($v:ident),+) => {
+ $crate::ioctl_ioc_nr!($name, $crate::platform::ioctl::_IOC_NONE, $ty, $nr, 0, $($v),+);
+ };
+}
+
+/// Declare an ioctl that reads data.
+#[macro_export]
+macro_rules! ioctl_ior_nr {
+ ($name:ident, $ty:expr, $nr:expr, $size:ty) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $crate::platform::ioctl::_IOC_READ,
+ $ty,
+ $nr,
+ ::std::mem::size_of::<$size>() as u32
+ );
+ };
+ ($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $crate::platform::ioctl::_IOC_READ,
+ $ty,
+ $nr,
+ ::std::mem::size_of::<$size>() as u32,
+ $($v),+
+ );
+ };
+}
+
+/// Declare an ioctl that writes data.
+#[macro_export]
+macro_rules! ioctl_iow_nr {
+ ($name:ident, $ty:expr, $nr:expr, $size:ty) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $crate::platform::ioctl::_IOC_WRITE,
+ $ty,
+ $nr,
+ ::std::mem::size_of::<$size>() as u32
+ );
+ };
+ ($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $crate::platform::ioctl::_IOC_WRITE,
+ $ty,
+ $nr,
+ ::std::mem::size_of::<$size>() as u32,
+ $($v),+
+ );
+ };
+}
+
+/// Declare an ioctl that reads and writes data.
+#[macro_export]
+macro_rules! ioctl_iowr_nr {
+ ($name:ident, $ty:expr, $nr:expr, $size:ty) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $crate::platform::ioctl::_IOC_READ | $crate::platform::ioctl::_IOC_WRITE,
+ $ty,
+ $nr,
+ ::std::mem::size_of::<$size>() as u32
+ );
+ };
+ ($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $crate::platform::ioctl::_IOC_READ | $crate::platform::ioctl::_IOC_WRITE,
+ $ty,
+ $nr,
+ ::std::mem::size_of::<$size>() as u32,
+ $($v),+
+ );
+ };
+}
+
+pub const _IOC_NRBITS: c_uint = 8;
+pub const _IOC_TYPEBITS: c_uint = 8;
+pub const _IOC_SIZEBITS: c_uint = 14;
+pub const _IOC_DIRBITS: c_uint = 2;
+pub const _IOC_NRMASK: c_uint = 255;
+pub const _IOC_TYPEMASK: c_uint = 255;
+pub const _IOC_SIZEMASK: c_uint = 16383;
+pub const _IOC_DIRMASK: c_uint = 3;
+pub const _IOC_NRSHIFT: c_uint = 0;
+pub const _IOC_TYPESHIFT: c_uint = 8;
+pub const _IOC_SIZESHIFT: c_uint = 16;
+pub const _IOC_DIRSHIFT: c_uint = 30;
+pub const _IOC_NONE: c_uint = 0;
+pub const _IOC_WRITE: c_uint = 1;
+pub const _IOC_READ: c_uint = 2;
+pub const IOC_IN: c_uint = 1_073_741_824;
+pub const IOC_OUT: c_uint = 2_147_483_648;
+pub const IOC_INOUT: c_uint = 3_221_225_472;
+pub const IOCSIZE_MASK: c_uint = 1_073_676_288;
+pub const IOCSIZE_SHIFT: c_uint = 16;
+
+/// Run an ioctl with no arguments.
+/// # Safety
+/// 1. descriptor must be an open FD.
+pub unsafe fn ioctl<F: AsRawDescriptor>(descriptor: &F, nr: IoctlNr) -> c_int {
+ libc::ioctl(descriptor.as_raw_descriptor(), nr, 0)
+}
+
+/// Run an ioctl with a single value argument.
+/// # Safety
+/// 1. descriptor must be an open FD.
+/// 2. the ioctl expects a value as its argument, and not a pointer.
+pub unsafe fn ioctl_with_val<F: AsRawDescriptor>(
+ descriptor: &F,
+ nr: IoctlNr,
+ arg: c_ulong,
+) -> c_int {
+ libc::ioctl(descriptor.as_raw_descriptor(), nr, arg)
+}
+
+/// Run an ioctl with an immutable reference.
+/// # Safety
+/// 1. descriptor must be an open FD.
+/// 2. the memory provided by arg is of the expected size for the ioctl.
+pub unsafe fn ioctl_with_ref<F: AsRawDescriptor, T>(descriptor: &F, nr: IoctlNr, arg: &T) -> c_int {
+ libc::ioctl(
+ descriptor.as_raw_descriptor(),
+ nr,
+ arg as *const T as *const c_void,
+ )
+}
+
+/// Run an ioctl with a mutable reference.
+/// # Safety
+/// 1. descriptor must be an open FD.
+/// 2. the memory provided by arg is of the expected size for the ioctl.
+pub unsafe fn ioctl_with_mut_ref<F: AsRawDescriptor, T>(
+ descriptor: &F,
+ nr: IoctlNr,
+ arg: &mut T,
+) -> c_int {
+ libc::ioctl(
+ descriptor.as_raw_descriptor(),
+ nr,
+ arg as *mut T as *mut c_void,
+ )
+}
+
+/// Run an ioctl with a raw pointer.
+/// # Safety
+/// 1. descriptor must be an open FD.
+/// 2. arg points to valid memory set up as expected by the ioctl.
+pub unsafe fn ioctl_with_ptr<F: AsRawDescriptor, T>(
+ descriptor: &F,
+ nr: IoctlNr,
+ arg: *const T,
+) -> c_int {
+ libc::ioctl(descriptor.as_raw_descriptor(), nr, arg as *const c_void)
+}
+
+/// Run an ioctl with a raw pointer, specifying the size of the buffer.
+/// # Safety
+/// 1. descriptor must be an open FD.
+/// 2. arg points to valid memory set up as expected by the ioctl.
+pub unsafe fn ioctl_with_ptr_sized<F: AsRawDescriptor, T>(
+ handle: &F,
+ nr: IoctlNr,
+ arg: *const T,
+ _size: usize,
+) -> c_int {
+ ioctl_with_ptr(handle, nr, arg)
+}
+
+/// Run an ioctl with a mutable raw pointer.
+/// # Safety
+/// 1. descriptor must be an open FD.
+/// 2. arg points to valid memory set up as expected by the ioctl.
+pub unsafe fn ioctl_with_mut_ptr<F: AsRawDescriptor, T>(
+ descriptor: &F,
+ nr: IoctlNr,
+ arg: *mut T,
+) -> c_int {
+ libc::ioctl(descriptor.as_raw_descriptor(), nr, arg as *mut c_void)
+}
+
+#[cfg(test)]
+mod tests {
+ const TUNTAP: ::std::os::raw::c_uint = 0x54;
+ const VHOST: ::std::os::raw::c_uint = 0xaf;
+ const EVDEV: ::std::os::raw::c_uint = 0x45;
+
+ ioctl_io_nr!(VHOST_SET_OWNER, VHOST, 0x01);
+ ioctl_ior_nr!(TUNGETFEATURES, TUNTAP, 0xcf, ::std::os::raw::c_uint);
+ ioctl_iow_nr!(TUNSETQUEUE, TUNTAP, 0xd9, ::std::os::raw::c_int);
+ ioctl_iowr_nr!(VHOST_GET_VRING_BASE, VHOST, 0x12, ::std::os::raw::c_int);
+
+ ioctl_ior_nr!(EVIOCGBIT, EVDEV, 0x20 + evt, [u8; 128], evt);
+ ioctl_io_nr!(FAKE_IOCTL_2_ARG, EVDEV, 0x01 + x + y, x, y);
+
+ #[test]
+ fn ioctl_macros() {
+ assert_eq!(0x0000af01, VHOST_SET_OWNER());
+ assert_eq!(0x800454cf, TUNGETFEATURES());
+ assert_eq!(0x400454d9, TUNSETQUEUE());
+ assert_eq!(0xc004af12, VHOST_GET_VRING_BASE());
+
+ assert_eq!(0x80804522, EVIOCGBIT(2));
+ assert_eq!(0x00004509, FAKE_IOCTL_2_ARG(3, 5));
+ }
+}
diff --git a/base/src/windows/mmap.rs b/base/src/windows/mmap.rs
new file mode 100644
index 000000000..fbce506e2
--- /dev/null
+++ b/base/src/windows/mmap.rs
@@ -0,0 +1,491 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use remain::sorted;
+use std::{
+ cmp::min,
+ io,
+ mem::size_of,
+ ptr::{copy_nonoverlapping, read_unaligned, write_unaligned},
+};
+
+use data_model::{volatile_memory::*, DataInit};
+
+use libc::{c_int, c_uint, c_void};
+
+use super::RawDescriptor;
+use crate::descriptor::{AsRawDescriptor, Descriptor};
+use crate::external_mapping::ExternalMapping;
+
+#[path = "win/mmap.rs"]
+mod mmap_platform;
+pub use mmap_platform::MemoryMappingArena;
+
+#[sorted]
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+ #[error("`add_fd_mapping` is unsupported")]
+ AddFdMappingIsUnsupported,
+ #[error("requested memory out of range")]
+ InvalidAddress,
+ #[error("invalid argument provided when creating mapping")]
+ InvalidArgument,
+ #[error("requested offset is out of range of off_t")]
+ InvalidOffset,
+ #[error("requested memory range spans past the end of the region: offset={0} count={1} region_size={2}")]
+ InvalidRange(usize, usize, usize),
+ #[error("requested memory is not page aligned")]
+ NotPageAligned,
+ #[error("failed to read from file to memory: {0}")]
+ ReadToMemory(#[source] io::Error),
+ #[error("`remove_mapping` is unsupported")]
+ RemoveMappingIsUnsupported,
+ #[error("system call failed while creating the mapping: {0}")]
+ StdSyscallFailed(io::Error),
+ #[error("mmap related system call failed: {0}")]
+ SystemCallFailed(#[source] super::Error),
+ #[error("failed to write from memory to file: {0}")]
+ WriteFromMemory(#[source] io::Error),
+}
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// Memory access type for anonymous shared memory mapping.
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub struct Protection(c_uint);
+
+impl Protection {
+ /// Returns Protection allowing no access. Note that on Windows this is not a
+ /// viable state and will return an error if used for mapping. It exists only
+ /// to serve as a base protection to set READ or WRITE on.
+ #[inline(always)]
+ pub fn none() -> Protection {
+ Protection(mmap_platform::PROT_NONE)
+ }
+
+ /// Returns Protection allowing read/write access.
+ #[inline(always)]
+ pub fn read_write() -> Protection {
+ Protection(mmap_platform::PROT_READ | mmap_platform::PROT_WRITE)
+ }
+
+ /// Returns Protection allowing read access.
+ #[inline(always)]
+ pub fn read() -> Protection {
+ Protection(mmap_platform::PROT_READ)
+ }
+
+ /// Set read events.
+ #[inline(always)]
+ pub fn set_read(self) -> Protection {
+ Protection(self.0 | mmap_platform::PROT_READ)
+ }
+
+ /// Set write events.
+ #[inline(always)]
+ pub fn set_write(self) -> Protection {
+ Protection(self.0 | mmap_platform::PROT_WRITE)
+ }
+}
+
+impl From<c_uint> for Protection {
+ fn from(f: c_uint) -> Self {
+ Protection(f)
+ }
+}
+
+impl From<Protection> for c_uint {
+ fn from(p: Protection) -> c_uint {
+ p.0 as c_uint
+ }
+}
+
+impl From<c_int> for Protection {
+ fn from(f: c_int) -> Self {
+ Protection(f as c_uint)
+ }
+}
+
+impl From<Protection> for c_int {
+ fn from(p: Protection) -> c_int {
+ p.0 as c_int
+ }
+}
+
+/// Validates that `offset`..`offset+range_size` lies within the bounds of a memory mapping of
+/// `mmap_size` bytes. Also checks for any overflow.
+fn validate_includes_range(mmap_size: usize, offset: usize, range_size: usize) -> Result<()> {
+ // Ensure offset + size doesn't overflow
+ let end_offset = offset
+ .checked_add(range_size)
+ .ok_or(Error::InvalidAddress)?;
+ // Ensure offset + size are within the mapping bounds
+ if end_offset <= mmap_size {
+ Ok(())
+ } else {
+ Err(Error::InvalidAddress)
+ }
+}
+
+/// A range of memory that can be msynced, for abstracting over different types of memory mappings.
+///
+/// Safe when implementers guarantee `ptr`..`ptr+size` is an mmaped region owned by this object that
+/// can't be unmapped during the `MappedRegion`'s lifetime.
+pub unsafe trait MappedRegion: Send + Sync {
+ /// Returns a pointer to the beginning of the memory region. Should only be
+ /// used for passing this region to ioctls for setting guest memory.
+ fn as_ptr(&self) -> *mut u8;
+
+ /// Returns the size of the memory region in bytes.
+ fn size(&self) -> usize;
+
+ /// Maps `size` bytes starting at `fd_offset` bytes from within the given `fd`
+ /// at `offset` bytes from the start of the region with `prot` protections.
+ /// `offset` must be page aligned.
+ ///
+ /// # Arguments
+ /// * `offset` - Page aligned offset into the arena in bytes.
+ /// * `size` - Size of memory region in bytes.
+ /// * `fd` - File descriptor to mmap from.
+ /// * `fd_offset` - Offset in bytes from the beginning of `fd` to start the mmap.
+ /// * `prot` - Protection (e.g. readable/writable) of the memory region.
+ fn add_fd_mapping(
+ &mut self,
+ _offset: usize,
+ _size: usize,
+ _fd: &dyn AsRawDescriptor,
+ _fd_offset: u64,
+ _prot: Protection,
+ ) -> Result<()> {
+ Err(Error::AddFdMappingIsUnsupported)
+ }
+
+ /// Remove `size`-byte mapping starting at `offset`.
+ fn remove_mapping(&mut self, _offset: usize, _size: usize) -> Result<()> {
+ Err(Error::RemoveMappingIsUnsupported)
+ }
+}
+
+impl dyn MappedRegion {
+ /// Calls msync with MS_SYNC on a mapping of `size` bytes starting at `offset` from the start of
+ /// the region. `offset`..`offset+size` must be contained within the `MappedRegion`.
+ pub fn msync(&self, offset: usize, size: usize) -> Result<()> {
+ validate_includes_range(self.size(), offset, size)?;
+
+ // Safe because the MemoryMapping/MemoryMappingArena interface ensures our pointer and size
+ // are correct, and we've validated that `offset`..`offset+size` is in the range owned by
+ // this `MappedRegion`.
+ let ret = unsafe {
+ use winapi::um::memoryapi::FlushViewOfFile;
+ if FlushViewOfFile((self.as_ptr() as usize + offset) as *mut libc::c_void, size) == 0 {
+ -1
+ } else {
+ 0
+ }
+ };
+ if ret != -1 {
+ Ok(())
+ } else {
+ Err(Error::SystemCallFailed(super::Error::last()))
+ }
+ }
+}
+
+/// Wraps an anonymous shared memory mapping in the current process. Provides
+/// RAII semantics including munmap when no longer needed.
+#[derive(Debug)]
+pub struct MemoryMapping {
+ addr: *mut c_void,
+ size: usize,
+}
+
+// Send and Sync aren't automatically inherited for the raw address pointer.
+// Accessing that pointer is only done through the stateless interface which
+// allows the object to be shared by multiple threads without a decrease in
+// safety.
+unsafe impl Send for MemoryMapping {}
+unsafe impl Sync for MemoryMapping {}
+
+impl MemoryMapping {
+ /// Creates an anonymous shared, read/write mapping of `size` bytes.
+ ///
+ /// # Arguments
+ /// * `size` - Size of memory region in bytes.
+ pub fn new(size: usize) -> Result<MemoryMapping> {
+ MemoryMapping::new_protection(size, Protection::read_write())
+ }
+
+ /// Maps the first `size` bytes of the given `descriptor` as read/write.
+ ///
+ /// # Arguments
+ /// * `file_handle` - File handle to map from.
+ /// * `size` - Size of memory region in bytes.
+ pub fn from_descriptor(
+ file_handle: &dyn AsRawDescriptor,
+ size: usize,
+ ) -> Result<MemoryMapping> {
+ MemoryMapping::from_descriptor_offset(file_handle, size, 0)
+ }
+
+ pub fn from_raw_descriptor(file_handle: RawDescriptor, size: usize) -> Result<MemoryMapping> {
+ MemoryMapping::from_descriptor_offset(&Descriptor(file_handle), size, 0)
+ }
+
+ pub fn from_descriptor_offset(
+ file_handle: &dyn AsRawDescriptor,
+ size: usize,
+ offset: u64,
+ ) -> Result<MemoryMapping> {
+ MemoryMapping::from_descriptor_offset_protection(
+ file_handle,
+ size,
+ offset,
+ Protection::read_write(),
+ )
+ }
+
+ /// Writes a slice to the memory region at the specified offset.
+ /// Returns the number of bytes written. The number of bytes written can
+ /// be less than the length of the slice if there isn't enough room in the
+ /// memory region.
+ ///
+ /// # Examples
+ /// * Write a slice at offset 256.
+ ///
+ /// ```
+ /// use crate::platform::MemoryMapping;
+ /// use crate::platform::SharedMemory;
+ /// let mut mem_map = MemoryMapping::from_descriptor(
+ /// &SharedMemory::anon(1024).unwrap(), 1024).unwrap();
+ /// let res = mem_map.write_slice(&[1,2,3,4,5], 256);
+ /// assert!(res.is_ok());
+ /// assert_eq!(res.unwrap(), 5);
+ /// ```
+ pub fn write_slice(&self, buf: &[u8], offset: usize) -> Result<usize> {
+ match self.size.checked_sub(offset) {
+ Some(size_past_offset) => {
+ let bytes_copied = min(size_past_offset, buf.len());
+ // The bytes_copied equation above ensures we don't copy bytes out of range of
+ // either buf or this slice. We also know that the buffers do not overlap because
+ // slices can never occupy the same memory as a volatile slice.
+ unsafe {
+ copy_nonoverlapping(buf.as_ptr(), self.as_ptr().add(offset), bytes_copied);
+ }
+ Ok(bytes_copied)
+ }
+ None => Err(Error::InvalidAddress),
+ }
+ }
+
+ /// Reads to a slice from the memory region at the specified offset.
+ /// Returns the number of bytes read. The number of bytes read can
+ /// be less than the length of the slice if there isn't enough room in the
+ /// memory region.
+ ///
+ /// # Examples
+ /// * Read a slice of size 16 at offset 256.
+ ///
+ /// ```
+ /// use crate::platform::MemoryMapping;
+ /// use crate::platform::SharedMemory;
+ /// let mut mem_map = MemoryMapping::from_descriptor(
+ /// &SharedMemory::anon(1024).unwrap(), 1024).unwrap();
+ /// let buf = &mut [0u8; 16];
+ /// let res = mem_map.read_slice(buf, 256);
+ /// assert!(res.is_ok());
+ /// assert_eq!(res.unwrap(), 16);
+ /// ```
+ pub fn read_slice(&self, buf: &mut [u8], offset: usize) -> Result<usize> {
+ match self.size.checked_sub(offset) {
+ Some(size_past_offset) => {
+ let bytes_copied = min(size_past_offset, buf.len());
+ // The bytes_copied equation above ensures we don't copy bytes out of range of
+ // either buf or this slice. We also know that the buffers do not overlap because
+ // slices can never occupy the same memory as a volatile slice.
+ unsafe {
+ copy_nonoverlapping(
+ self.as_ptr().add(offset) as *const u8,
+ buf.as_mut_ptr(),
+ bytes_copied,
+ );
+ }
+ Ok(bytes_copied)
+ }
+ None => Err(Error::InvalidAddress),
+ }
+ }
+
+ /// Writes an object to the memory region at the specified offset.
+ /// Returns Ok(()) if the object fits, or Err if it extends past the end.
+ ///
+ /// # Examples
+ /// * Write a u64 at offset 16.
+ ///
+ /// ```
+ /// use crate::platform::MemoryMapping;
+ /// use crate::platform::SharedMemory;
+ /// let mut mem_map = MemoryMapping::from_descriptor(
+ /// &SharedMemory::anon(1024).unwrap(), 1024).unwrap();
+ /// let res = mem_map.write_obj(55u64, 16);
+ /// assert!(res.is_ok());
+ /// ```
+ pub fn write_obj<T: DataInit>(&self, val: T, offset: usize) -> Result<()> {
+ self.range_end(offset, size_of::<T>())?;
+ // This is safe because we checked the bounds above.
+ unsafe {
+ write_unaligned(self.as_ptr().add(offset) as *mut T, val);
+ }
+ Ok(())
+ }
+
+ /// Reads on object from the memory region at the given offset.
+ /// Reading from a volatile area isn't strictly safe as it could change
+ /// mid-read. However, as long as the type T is plain old data and can
+ /// handle random initialization, everything will be OK.
+ ///
+ /// # Examples
+ /// * Read a u64 written to offset 32.
+ ///
+ /// ```
+ /// use crate::platform::MemoryMapping;
+ /// use crate::platform::SharedMemory;
+ /// let mut mem_map = MemoryMapping::from_descriptor(
+ /// &SharedMemory::anon(1024).unwrap(), 1024).unwrap();
+ /// let res = mem_map.write_obj(55u64, 32);
+ /// assert!(res.is_ok());
+ /// let num: u64 = mem_map.read_obj(32).unwrap();
+ /// assert_eq!(55, num);
+ /// ```
+ pub fn read_obj<T: DataInit>(&self, offset: usize) -> Result<T> {
+ self.range_end(offset, size_of::<T>())?;
+ // This is safe because by definition Copy types can have their bits set arbitrarily and
+ // still be valid.
+ unsafe {
+ Ok(read_unaligned(
+ self.as_ptr().add(offset) as *const u8 as *const T
+ ))
+ }
+ }
+
+ // Check that offset+count is valid and return the sum.
+ fn range_end(&self, offset: usize, count: usize) -> Result<usize> {
+ let mem_end = offset.checked_add(count).ok_or(Error::InvalidAddress)?;
+ if mem_end > self.size() {
+ return Err(Error::InvalidAddress);
+ }
+ Ok(mem_end)
+ }
+}
+
+// Safe because the pointer and size point to a memory range owned by this MemoryMapping that won't
+// be unmapped until it's Dropped.
+unsafe impl MappedRegion for MemoryMapping {
+ fn as_ptr(&self) -> *mut u8 {
+ self.addr as *mut u8
+ }
+
+ fn size(&self) -> usize {
+ self.size
+ }
+}
+
+unsafe impl MappedRegion for ExternalMapping {
+ /// used for passing this region to ioctls for setting guest memory.
+ fn as_ptr(&self) -> *mut u8 {
+ self.as_ptr()
+ }
+
+ /// Returns the size of the memory region in bytes.
+ fn size(&self) -> usize {
+ self.size()
+ }
+}
+
+impl VolatileMemory for MemoryMapping {
+ fn get_slice(&self, offset: usize, count: usize) -> VolatileMemoryResult<VolatileSlice> {
+ let mem_end = calc_offset(offset, count)?;
+ if mem_end > self.size {
+ return Err(VolatileMemoryError::OutOfBounds { addr: mem_end });
+ }
+
+ let new_addr =
+ (self.as_ptr() as usize)
+ .checked_add(offset)
+ .ok_or(VolatileMemoryError::Overflow {
+ base: self.as_ptr() as usize,
+ offset,
+ })?;
+
+ // Safe because we checked that offset + count was within our range and we only ever hand
+ // out volatile accessors.
+ Ok(unsafe { VolatileSlice::from_raw_parts(new_addr as *mut u8, count) })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{super::shm::SharedMemory, *};
+ use data_model::{VolatileMemory, VolatileMemoryError};
+
+ #[test]
+ fn basic_map() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, 1024).unwrap();
+ assert_eq!(1024, m.size());
+ }
+
+ #[test]
+ fn test_write_past_end() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, 5).unwrap();
+ let res = m.write_slice(&[1, 2, 3, 4, 5, 6], 0);
+ assert!(res.is_ok());
+ assert_eq!(res.unwrap(), 5);
+ }
+
+ #[test]
+ fn slice_size() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, 5).unwrap();
+ let s = m.get_slice(2, 3).unwrap();
+ assert_eq!(s.size(), 3);
+ }
+
+ #[test]
+ fn slice_addr() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, 5).unwrap();
+ let s = m.get_slice(2, 3).unwrap();
+ assert_eq!(s.as_ptr(), unsafe { m.as_ptr().offset(2) });
+ }
+
+ #[test]
+ fn slice_store() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, 5).unwrap();
+ let r = m.get_ref(2).unwrap();
+ r.store(9u16);
+ assert_eq!(m.read_obj::<u16>(2).unwrap(), 9);
+ }
+
+ #[test]
+ fn slice_overflow_error() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, 5).unwrap();
+ let res = m.get_slice(std::usize::MAX, 3).unwrap_err();
+ assert_eq!(
+ res,
+ VolatileMemoryError::Overflow {
+ base: std::usize::MAX,
+ offset: 3,
+ }
+ );
+ }
+ #[test]
+ fn slice_oob_error() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, 5).unwrap();
+ let res = m.get_slice(3, 3).unwrap_err();
+ assert_eq!(res, VolatileMemoryError::OutOfBounds { addr: 6 });
+ }
+}
diff --git a/base/src/windows/mod.rs b/base/src/windows/mod.rs
new file mode 100644
index 000000000..09148e89a
--- /dev/null
+++ b/base/src/windows/mod.rs
@@ -0,0 +1,110 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Small system utility modules for usage by other modules.
+
+#![cfg(windows)]
+
+#[macro_use]
+pub mod win;
+
+#[path = "win/ioctl.rs"]
+#[macro_use]
+pub mod ioctl;
+#[macro_use]
+pub mod syslog;
+mod clock;
+#[path = "win/console.rs"]
+mod console;
+mod descriptor;
+#[path = "win/event.rs"]
+mod event;
+mod events;
+pub mod file_traits;
+#[path = "win/get_filesystem_type.rs"]
+mod get_filesystem_type;
+mod gmtime;
+mod mmap;
+#[path = "win/named_pipes.rs"]
+pub mod named_pipes;
+mod notifiers;
+mod poll;
+#[path = "win/priority.rs"]
+mod priority;
+// Add conditional compile?
+#[path = "win/sched.rs"]
+mod sched;
+mod shm;
+mod stream_channel;
+mod timer;
+
+pub mod thread;
+
+mod write_zeroes;
+
+pub use crate::descriptor_reflection::{
+ deserialize_with_descriptors, with_as_descriptor, with_raw_descriptor, FileSerdeWrapper,
+ SerializeDescriptors,
+};
+pub use crate::errno::{Error, Result, *};
+pub use base_poll_token_derive::*;
+pub use clock::{Clock, FakeClock};
+pub use console::*;
+pub use descriptor::*;
+pub use event::*;
+pub use events::*;
+pub use get_filesystem_type::*;
+pub use gmtime::*;
+pub use ioctl::*;
+pub use mmap::*;
+pub use notifiers::*;
+pub use poll::*;
+pub use priority::*;
+pub use sched::*;
+pub use shm::*;
+pub use stream_channel::*;
+pub use timer::*;
+pub use win::*;
+
+pub use file_traits::{
+ AsRawDescriptors, FileAllocate, FileGetLen, FileReadWriteAtVolatile, FileReadWriteVolatile,
+ FileSetLen, FileSync,
+};
+pub use mmap::Error as MmapError;
+pub use write_zeroes::{PunchHole, WriteZeroes, WriteZeroesAt};
+
+use std::cell::Cell;
+
+// Define libc::* types
+#[allow(non_camel_case_types)]
+pub type pid_t = i32;
+#[allow(non_camel_case_types)]
+pub type uid_t = u32;
+#[allow(non_camel_case_types)]
+pub type gid_t = u32;
+#[allow(non_camel_case_types)]
+pub type mode_t = u32;
+
+/// Re-export libc types that are part of the API.
+pub type Pid = pid_t;
+pub type Uid = uid_t;
+pub type Gid = gid_t;
+pub type Mode = mode_t;
+
+/// Used to mark types as !Sync.
+pub type UnsyncMarker = std::marker::PhantomData<Cell<usize>>;
+
+/// Uses the system's page size in bytes to round the given value up to the nearest page boundary.
+#[inline(always)]
+pub fn round_up_to_page_size(v: usize) -> usize {
+ let page_mask = pagesize() - 1;
+ (v + page_mask) & !page_mask
+}
+
+#[macro_export]
+macro_rules! CHRONO_TIMESTAMP_FIXED_FMT {
+ () => {
+ "%F %T%.9f"
+ };
+}
diff --git a/base/src/windows/notifiers.rs b/base/src/windows/notifiers.rs
new file mode 100644
index 000000000..71dbdfa52
--- /dev/null
+++ b/base/src/windows/notifiers.rs
@@ -0,0 +1,16 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::descriptor::AsRawDescriptor;
+
+pub trait ReadNotifier {
+ /// Gets a descriptor that can be used in EventContext to wait for events to be available (e.g.
+ /// to avoid receive_events blocking).
+ fn get_read_notifier(&self) -> &dyn AsRawDescriptor;
+}
+
+pub trait CloseNotifier {
+ /// Gets a descriptor that can be used in EventContext to wait for the closed event.
+ fn get_close_notifier(&self) -> &dyn AsRawDescriptor;
+}
diff --git a/base/src/windows/poll.rs b/base/src/windows/poll.rs
new file mode 100644
index 000000000..3648f8953
--- /dev/null
+++ b/base/src/windows/poll.rs
@@ -0,0 +1,123 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// Trait that can be used to associate events with arbitrary enums when using
+/// EventContext.
+///
+/// Simple enums that have no or primitive variant data data can use the `#[derive(PollToken)]`
+/// custom derive to implement this trait. See
+/// [poll_token_derive::poll_token](../poll_token_derive/fn.poll_token.html) for details.
+pub trait PollToken {
+ /// Converts this token into a u64 that can be turned back into a token via `from_raw_token`.
+ fn as_raw_token(&self) -> u64;
+
+ /// Converts a raw token as returned from `as_raw_token` back into a token.
+ ///
+ /// It is invalid to give a raw token that was not returned via `as_raw_token` from the same
+ /// `Self`. The implementation can expect that this will never happen as a result of its usage
+ /// in `EventContext`.
+ fn from_raw_token(data: u64) -> Self;
+}
+
+impl PollToken for usize {
+ fn as_raw_token(&self) -> u64 {
+ *self as u64
+ }
+
+ fn from_raw_token(data: u64) -> Self {
+ data as Self
+ }
+}
+
+impl PollToken for u64 {
+ fn as_raw_token(&self) -> u64 {
+ *self as u64
+ }
+
+ fn from_raw_token(data: u64) -> Self {
+ data as Self
+ }
+}
+
+impl PollToken for u32 {
+ fn as_raw_token(&self) -> u64 {
+ u64::from(*self)
+ }
+
+ fn from_raw_token(data: u64) -> Self {
+ data as Self
+ }
+}
+
+impl PollToken for u16 {
+ fn as_raw_token(&self) -> u64 {
+ u64::from(*self)
+ }
+
+ fn from_raw_token(data: u64) -> Self {
+ data as Self
+ }
+}
+
+impl PollToken for u8 {
+ fn as_raw_token(&self) -> u64 {
+ u64::from(*self)
+ }
+
+ fn from_raw_token(data: u64) -> Self {
+ data as Self
+ }
+}
+
+impl PollToken for () {
+ fn as_raw_token(&self) -> u64 {
+ 0
+ }
+
+ fn from_raw_token(_data: u64) -> Self {}
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use base_poll_token_derive::PollToken;
+
+ #[test]
+ #[allow(dead_code)]
+ fn poll_token_derive() {
+ #[derive(PollToken)]
+ enum EmptyToken {}
+
+ #[derive(PartialEq, Debug, PollToken)]
+ enum Token {
+ Alpha,
+ Beta,
+ // comments
+ Gamma(u32),
+ Delta { index: usize },
+ Omega,
+ }
+
+ assert_eq!(
+ Token::from_raw_token(Token::Alpha.as_raw_token()),
+ Token::Alpha
+ );
+ assert_eq!(
+ Token::from_raw_token(Token::Beta.as_raw_token()),
+ Token::Beta
+ );
+ assert_eq!(
+ Token::from_raw_token(Token::Gamma(55).as_raw_token()),
+ Token::Gamma(55)
+ );
+ assert_eq!(
+ Token::from_raw_token(Token::Delta { index: 100 }.as_raw_token()),
+ Token::Delta { index: 100 }
+ );
+ assert_eq!(
+ Token::from_raw_token(Token::Omega.as_raw_token()),
+ Token::Omega
+ );
+ }
+}
diff --git a/base/src/windows/priority.rs b/base/src/windows/priority.rs
new file mode 100644
index 000000000..1dc5ce76f
--- /dev/null
+++ b/base/src/windows/priority.rs
@@ -0,0 +1,45 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::{errno_result, Result};
+
+const AUDIO_THREAD_RTPRIO: u16 = 10; // Matches other cros audio clients.
+
+pub fn set_audio_thread_priorities() -> Result<()> {
+ set_rt_prio_limit(u64::from(AUDIO_THREAD_RTPRIO))?;
+ set_rt_round_robin(i32::from(AUDIO_THREAD_RTPRIO))?;
+ Ok(())
+}
+/// Enables real time thread priorities in the current thread up to `limit`.
+pub fn set_rt_prio_limit(limit: u64) -> Result<()> {
+ let rt_limit_arg = libc::rlimit {
+ rlim_cur: limit as libc::rlim_t,
+ rlim_max: limit as libc::rlim_t,
+ };
+ // Safe because the kernel doesn't modify memory that is accessible to the process here.
+ let res = unsafe { libc::setrlimit(libc::RLIMIT_RTPRIO, &rt_limit_arg) };
+
+ if res != 0 {
+ errno_result()
+ } else {
+ Ok(())
+ }
+}
+
+/// Sets the current thread to be scheduled using the round robin real time class with `priority`.
+pub fn set_rt_round_robin(priority: i32) -> Result<()> {
+ let sched_param = libc::sched_param {
+ sched_priority: priority,
+ };
+
+ // Safe because the kernel doesn't modify memory that is accessible to the process here.
+ let res =
+ unsafe { libc::pthread_setschedparam(libc::pthread_self(), libc::SCHED_RR, &sched_param) };
+
+ if res != 0 {
+ errno_result()
+ } else {
+ Ok(())
+ }
+}
diff --git a/base/src/windows/rand.rs b/base/src/windows/rand.rs
new file mode 100644
index 000000000..9b45a9f61
--- /dev/null
+++ b/base/src/windows/rand.rs
@@ -0,0 +1,121 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Rust implementation of functionality parallel to libchrome's base/rand_util.h.
+
+use std::thread::sleep;
+use std::time::Duration;
+
+use libc::{c_uint, c_void};
+
+use crate::{errno_result, handle_eintr_errno, Result};
+
+/// How long to wait before calling getrandom again if it does not return
+/// enough bytes.
+const POLL_INTERVAL: Duration = Duration::from_millis(50);
+
+/// Represents whether or not the random bytes are pulled from the source of
+/// /dev/random or /dev/urandom.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub enum Source {
+ // This is the default and uses the same source as /dev/urandom.
+ Pseudorandom,
+ // This uses the same source as /dev/random and may be.
+ Random,
+}
+
+impl Default for Source {
+ fn default() -> Self {
+ Source::Pseudorandom
+ }
+}
+
+impl Source {
+ fn to_getrandom_flags(&self) -> c_uint {
+ match self {
+ Source::Random => libc::GRND_RANDOM,
+ Source::Pseudorandom => 0,
+ }
+ }
+}
+
+/// Fills `output` completely with random bytes from the specified `source`.
+pub fn rand_bytes(mut output: &mut [u8], source: Source) -> Result<()> {
+ if output.is_empty() {
+ return Ok(());
+ }
+
+ loop {
+ // Safe because output is mutable and the writes are limited by output.len().
+ let bytes = handle_eintr_errno!(unsafe {
+ getrandom(
+ output.as_mut_ptr() as *mut c_void,
+ output.len(),
+ source.to_getrandom_flags(),
+ )
+ });
+
+ if bytes < 0 {
+ return errno_result();
+ }
+ if bytes as usize == output.len() {
+ return Ok(());
+ }
+
+ // Wait for more entropy and try again for the remaining bytes.
+ sleep(POLL_INTERVAL);
+ output = &mut output[bytes as usize..];
+ }
+}
+
+/// TODO (b/186157353): remove this function and call libc::getrandom instead once we update our
+/// linux presubmits to use a newer linux version.
+unsafe fn getrandom(
+ buf: *mut libc::c_void,
+ buflen: libc::size_t,
+ flags: libc::c_uint,
+) -> libc::ssize_t {
+ libc::syscall(libc::SYS_getrandom, buf, buflen, flags) as libc::ssize_t
+}
+
+/// Allocates a vector of length `len` filled with random bytes from the
+/// specified `source`.
+pub fn rand_vec(len: usize, source: Source) -> Result<Vec<u8>> {
+ let mut rand = Vec::with_capacity(len);
+ if len == 0 {
+ return Ok(rand);
+ }
+
+ // Safe because rand will either be initialized by getrandom or dropped.
+ unsafe { rand.set_len(len) };
+ rand_bytes(rand.as_mut_slice(), source)?;
+ Ok(rand)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ const TEST_SIZE: usize = 64;
+
+ #[test]
+ fn randbytes_success() {
+ let mut rand = vec![0u8; TEST_SIZE];
+ rand_bytes(&mut rand, Source::Pseudorandom).unwrap();
+ assert_ne!(&rand, &[0u8; TEST_SIZE]);
+ }
+
+ #[test]
+ fn randvec_success() {
+ let rand = rand_vec(TEST_SIZE, Source::Pseudorandom).unwrap();
+ assert_eq!(rand.len(), TEST_SIZE);
+ assert_ne!(&rand, &[0u8; TEST_SIZE]);
+ }
+
+ #[test]
+ fn sourcerandom_success() {
+ let rand = rand_vec(TEST_SIZE, Source::Random).unwrap();
+ assert_ne!(&rand, &[0u8; TEST_SIZE]);
+ }
+}
diff --git a/base/src/windows/shm.rs b/base/src/windows/shm.rs
new file mode 100644
index 000000000..d94483266
--- /dev/null
+++ b/base/src/windows/shm.rs
@@ -0,0 +1,152 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::ffi::CString;
+
+use super::{MemoryMapping, RawDescriptor, Result};
+use crate::descriptor::{AsRawDescriptor, IntoRawDescriptor, SafeDescriptor};
+use libc::EINVAL;
+use std::io::{
+ Error, ErrorKind, Read, Seek, SeekFrom, Write, {self},
+};
+
+#[path = "win/shm.rs"]
+mod shm_platform;
+pub use shm_platform::*;
+
+/// A shared memory file descriptor and its size.
+pub struct SharedMemory {
+ pub descriptor: SafeDescriptor,
+ pub size: u64,
+
+ // Elements used internally to perform File-like operations on this Shared Memory
+ pub mapping: MemoryMapping,
+ pub cursor: usize,
+}
+
+impl SharedMemory {
+ /// Convenience function for `SharedMemory::new` that is always named and accepts a wide variety
+ /// of string-like types.
+ ///
+ /// Note that the given name may not have NUL characters anywhere in it, or this will return an
+ /// error.
+ pub fn named<T: Into<Vec<u8>>>(name: T, size: u64) -> Result<SharedMemory> {
+ SharedMemory::new(
+ Some(&CString::new(name).map_err(|_| super::Error::new(EINVAL))?),
+ size,
+ )
+ }
+
+ /// Convenience function for `SharedMemory::new` that has an arbitrary and unspecified name.
+ pub fn anon(size: u64) -> Result<SharedMemory> {
+ SharedMemory::new(None, size)
+ }
+
+ /// Gets the size in bytes of the shared memory.
+ ///
+ /// The size returned here does not reflect changes by other interfaces or users of the shared
+ /// memory file descriptor.
+ pub fn size(&self) -> u64 {
+ self.size
+ }
+}
+
+/// USE THIS CAUTIOUSLY. The returned handle is not a file handle and cannot be
+/// used as if it were one. It is a handle to a the associated file mapping object
+/// and should only be used for memory-mapping the file view.
+impl AsRawDescriptor for SharedMemory {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.descriptor.as_raw_descriptor()
+ }
+}
+
+impl IntoRawDescriptor for SharedMemory {
+ fn into_raw_descriptor(self) -> RawDescriptor {
+ self.descriptor.into_raw_descriptor()
+ }
+}
+
+impl Read for SharedMemory {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ let result = match self.mapping.read_slice(buf, self.cursor) {
+ Ok(result) => result,
+ Err(e) => {
+ return Err(Error::new(
+ ErrorKind::Other,
+ format!("Unable to read from shared memory: {}", e),
+ ));
+ }
+ };
+ let size_read = result;
+ self.cursor += size_read;
+ Ok(size_read)
+ }
+}
+
+impl Write for SharedMemory {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ let result = match self.mapping.write_slice(buf, self.cursor) {
+ Ok(result) => result,
+ Err(e) => {
+ return Err(Error::new(
+ ErrorKind::Other,
+ format!("Unable to write to shared memory: {}", e),
+ ));
+ }
+ };
+ let size_written = result;
+ self.cursor += size_written;
+ Ok(size_written)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ // No buffering is used, no flushing required
+ Ok(())
+ }
+}
+
+impl Seek for SharedMemory {
+ fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
+ let new_cursor: i64 = match pos {
+ SeekFrom::Start(offset) => offset as i64,
+ SeekFrom::End(offset) => self.size as i64 + offset,
+ SeekFrom::Current(offset) => self.cursor as i64 + offset,
+ };
+
+ if new_cursor < 0 {
+ return Err(Error::new(
+ ErrorKind::InvalidInput,
+ "Cannot seek to a negative value",
+ ));
+ }
+
+ self.cursor = new_cursor as usize;
+ Ok(self.cursor as u64)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::ffi::CString;
+
+ #[test]
+ fn named() {
+ const TEST_NAME: &str = "Name McCool Person";
+ SharedMemory::named(TEST_NAME, 1028).expect("failed to create shared memory");
+ }
+
+ #[test]
+ fn new_sized() {
+ let shm = SharedMemory::anon(1028).expect("Failed to create named shared memory");
+ assert_eq!(shm.size(), 1028);
+ }
+
+ #[test]
+ fn new_named() {
+ let name = "very unique name";
+ let cname = CString::new(name).unwrap();
+ SharedMemory::new(Some(&cname), 16).expect("failed to create shared memory");
+ }
+}
diff --git a/base/src/windows/stdio_fileno.c b/base/src/windows/stdio_fileno.c
new file mode 100644
index 000000000..1100559c6
--- /dev/null
+++ b/base/src/windows/stdio_fileno.c
@@ -0,0 +1,13 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdio.h>
+
+int stdout_fileno() {
+ return fileno(stdout);
+}
+
+int stderr_fileno() {
+ return fileno(stderr);
+}
diff --git a/base/src/windows/stream_channel.rs b/base/src/windows/stream_channel.rs
new file mode 100644
index 000000000..5b142b50f
--- /dev/null
+++ b/base/src/windows/stream_channel.rs
@@ -0,0 +1,131 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::RawDescriptor;
+use crate::descriptor::AsRawDescriptor;
+use std::io;
+#[path = "win/stream_channel.rs"]
+mod stream_channel;
+pub use stream_channel::*;
+
+#[derive(Copy, Clone)]
+pub enum FramingMode {
+ Message,
+ Byte,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum BlockingMode {
+ Blocking,
+ Nonblocking,
+}
+
+impl io::Read for StreamChannel {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.inner_read(buf)
+ }
+}
+
+impl io::Read for &StreamChannel {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.inner_read(buf)
+ }
+}
+
+impl AsRawDescriptor for StreamChannel {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ (&self).as_raw_descriptor()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{
+ super::{EventContext, EventTrigger, PollToken, ReadNotifier},
+ *,
+ };
+ use std::io::{Read, Write};
+
+ #[derive(PollToken, Debug, Eq, PartialEq, Copy, Clone)]
+ enum Token {
+ ReceivedData,
+ }
+
+ #[test]
+ fn test_non_blocking_pair() {
+ let (mut sender, mut receiver) =
+ StreamChannel::pair(BlockingMode::Nonblocking, FramingMode::Byte).unwrap();
+
+ sender.write_all(&[75, 77, 54, 82, 76, 65]).unwrap();
+
+ // Wait for the data to arrive.
+ let event_ctx: EventContext<Token> = EventContext::build_with(&[EventTrigger::from(
+ receiver.get_read_notifier(),
+ Token::ReceivedData,
+ )])
+ .unwrap();
+ let events = event_ctx.wait().unwrap();
+ let tokens: Vec<Token> = events
+ .iter()
+ .filter(|e| e.is_readable)
+ .map(|e| e.token)
+ .collect();
+ assert_eq!(tokens, vec! {Token::ReceivedData});
+
+ // Smaller than what we sent so we get multiple chunks
+ let mut recv_buffer: [u8; 4] = [0; 4];
+
+ let mut size = receiver.read(&mut recv_buffer).unwrap();
+ assert_eq!(size, 4);
+ assert_eq!(recv_buffer, [75, 77, 54, 82]);
+
+ size = receiver.read(&mut recv_buffer).unwrap();
+ assert_eq!(size, 2);
+ assert_eq!(recv_buffer[0..2], [76, 65]);
+
+ // Now that we've polled for & received all data, polling again should show no events.
+ assert_eq!(
+ event_ctx
+ .wait_timeout(std::time::Duration::new(0, 0))
+ .unwrap()
+ .len(),
+ 0
+ );
+ }
+
+ #[test]
+ fn test_non_blocking_pair_error_no_data() {
+ let (mut sender, mut receiver) =
+ StreamChannel::pair(BlockingMode::Nonblocking, FramingMode::Byte).unwrap();
+ receiver
+ .set_nonblocking(true)
+ .expect("Failed to set receiver to nonblocking mode.");
+
+ sender.write_all(&[75, 77]).unwrap();
+
+ // Wait for the data to arrive.
+ let event_ctx: EventContext<Token> = EventContext::build_with(&[EventTrigger::from(
+ receiver.get_read_notifier(),
+ Token::ReceivedData,
+ )])
+ .unwrap();
+ let events = event_ctx.wait().unwrap();
+ let tokens: Vec<Token> = events
+ .iter()
+ .filter(|e| e.is_readable)
+ .map(|e| e.token)
+ .collect();
+ assert_eq!(tokens, vec! {Token::ReceivedData});
+
+ // We only read 2 bytes, even though we requested 4 bytes.
+ let mut recv_buffer: [u8; 4] = [0; 4];
+ let size = receiver.read(&mut recv_buffer).unwrap();
+ assert_eq!(size, 2);
+ assert_eq!(recv_buffer, [75, 77, 00, 00]);
+
+ // Further reads should encounter an error since there is no available data and this is a
+ // non blocking pipe.
+ assert!(receiver.read(&mut recv_buffer).is_err());
+ }
+}
diff --git a/base/src/windows/syslog.rs b/base/src/windows/syslog.rs
new file mode 100644
index 000000000..eb54575f5
--- /dev/null
+++ b/base/src/windows/syslog.rs
@@ -0,0 +1,865 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Facilities for sending log message to syslog.
+//!
+//! Every function exported by this module is thread-safe. Each function will silently fail until
+//! `syslog::init()` is called and returns `Ok`.
+//!
+//! # Examples
+//!
+//! ```
+//! use crate::platform::{error, syslog, warn};
+//!
+//! if let Err(e) = syslog::init() {
+//! println!("failed to initiailize syslog: {}", e);
+//! return;
+//! }
+//! warn!("this is your {} warning", "final");
+//! error!("something went horribly wrong: {}", "out of RAMs");
+//! ```
+
+pub use super::win::syslog::PlatformSyslog;
+use super::RawDescriptor;
+use crate::descriptor::AsRawDescriptor;
+use crate::{syslog_lock, CHRONO_TIMESTAMP_FIXED_FMT};
+use serde::{Deserialize, Serialize};
+use std::{
+ convert::{From, Into, TryFrom},
+ env,
+ ffi::{OsStr, OsString},
+ fmt::{
+ Display, {self},
+ },
+ fs::File,
+ io,
+ io::{stderr, Cursor, Write},
+ path::{Path, PathBuf},
+ sync::{MutexGuard, Once},
+};
+
+use remain::sorted;
+use sync::Mutex;
+use thiserror::Error as ThisError;
+
+/// The priority (i.e. severity) of a syslog message.
+///
+/// See syslog man pages for information on their semantics.
+#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub enum Priority {
+ Emergency = 0,
+ Alert = 1,
+ Critical = 2,
+ Error = 3,
+ Warning = 4,
+ Notice = 5,
+ Info = 6,
+ Debug = 7,
+}
+
+impl Display for Priority {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::Priority::*;
+
+ let string = match self {
+ Emergency => "EMERGENCY",
+ Alert => "ALERT",
+ Critical => "CRITICAL",
+ Error => "ERROR",
+ Warning => "WARNING",
+ Notice => "NOTICE",
+ Info => "INFO",
+ Debug => "DEBUG",
+ };
+
+ write!(f, "{}", string)
+ }
+}
+
+impl TryFrom<&str> for Priority {
+ type Error = &'static str;
+
+ fn try_from(value: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
+ match value {
+ "0" | "EMERGENCY" => Ok(Priority::Emergency),
+ "1" | "ALERT" => Ok(Priority::Alert),
+ "2" | "CRITICAL" => Ok(Priority::Critical),
+ "3" | "ERROR" => Ok(Priority::Error),
+ "4" | "WARNING" => Ok(Priority::Warning),
+ "5" | "NOTICE" => Ok(Priority::Notice),
+ "6" | "INFO" => Ok(Priority::Info),
+ "7" | "DEBUG" => Ok(Priority::Debug),
+ _ => Err("Priority can only be parsed from 0-7 and given variant names"),
+ }
+ }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub enum PriorityFilter {
+ Silent,
+ Priority(Priority),
+ ShowAll,
+}
+
+impl From<Priority> for PriorityFilter {
+ fn from(pri: Priority) -> Self {
+ PriorityFilter::Priority(pri)
+ }
+}
+
+impl TryFrom<&str> for PriorityFilter {
+ type Error = &'static str;
+
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
+ match value.to_uppercase().as_str() {
+ "S" | "SILENT" => Ok(PriorityFilter::Silent),
+ "*" => Ok(PriorityFilter::ShowAll),
+ value => match Priority::try_from(value) {
+ Ok(pri) => Ok(PriorityFilter::Priority(pri)),
+ Err(_) => Err("PriorityFilter can only be parsed from valid Priority
+ value, S, *, or SILENT"),
+ },
+ }
+ }
+}
+
+/// The facility of a syslog message.
+///
+/// See syslog man pages for information on their semantics.
+#[derive(Copy, Clone)]
+pub enum Facility {
+ Kernel = 0,
+ User = 1 << 3,
+ Mail = 2 << 3,
+ Daemon = 3 << 3,
+ Auth = 4 << 3,
+ Syslog = 5 << 3,
+ Lpr = 6 << 3,
+ News = 7 << 3,
+ Uucp = 8 << 3,
+ Local0 = 16 << 3,
+ Local1 = 17 << 3,
+ Local2 = 18 << 3,
+ Local3 = 19 << 3,
+ Local4 = 20 << 3,
+ Local5 = 21 << 3,
+ Local6 = 22 << 3,
+ Local7 = 23 << 3,
+}
+
+/// Errors returned by `syslog::init()`.
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum Error {
+ /// Error while attempting to connect socket.
+ #[error("failed to connect socket: {0}")]
+ Connect(io::Error),
+ /// There was an error using `open` to get the lowest file descriptor.
+ #[error("failed to get lowest file descriptor: {0}")]
+ GetLowestFd(io::Error),
+ /// The guess of libc's file descriptor for the syslog connection was invalid.
+ #[error("guess of fd for syslog connection was invalid")]
+ InvalidFd,
+ /// Initialization was never attempted.
+ #[error("initialization was never attempted")]
+ NeverInitialized,
+ /// Initialization has previously failed and can not be retried.
+ #[error("initialization previously failed and cannot be retried")]
+ Poisoned,
+ /// Error while creating socket.
+ #[error("failed to create socket: {0}")]
+ Socket(io::Error),
+}
+
+/// Defines a log level override for a set of source files with the given path_prefix
+#[derive(Debug)]
+struct PathFilter {
+ path_prefix: PathBuf,
+ level: PriorityFilter,
+}
+
+fn get_proc_name() -> Option<String> {
+ env::args_os()
+ .next()
+ .map(PathBuf::from)
+ .and_then(|s| s.file_name().map(OsStr::to_os_string))
+ .map(OsString::into_string)
+ .and_then(Result::ok)
+}
+
+struct State {
+ stderr: bool,
+ file: Option<File>,
+ proc_name: Option<String>,
+ syslog: PlatformSyslog,
+ log_level: PriorityFilter, // This is the default global log level
+ path_log_levels: Vec<PathFilter>, // These are sorted with longest path prefixes first
+}
+
+impl State {
+ fn new() -> Result<State, Error> {
+ Ok(State {
+ stderr: true,
+ file: None,
+ proc_name: get_proc_name(),
+ syslog: PlatformSyslog::new()?,
+ log_level: PriorityFilter::Priority(Priority::Info),
+ path_log_levels: Vec::new(),
+ })
+ }
+}
+
+/// Set the log level filter.
+///
+/// Does nothing if syslog was never initialized. Set log level filter with the given priority filter level.
+pub fn set_log_level<T: Into<PriorityFilter>>(log_level: T) {
+ let mut state = syslog_lock!();
+ state.log_level = log_level.into();
+}
+
+/// Adds a new per-path log level filter.
+pub fn add_path_log_level<T: Into<PriorityFilter>>(path_prefix: &str, log_level: T) {
+ // Insert filter so that path_log_levels is always sorted with longer prefixes first
+ let mut state = syslog_lock!();
+ let index = state
+ .path_log_levels
+ .binary_search_by_key(&path_prefix.len(), |p| {
+ std::usize::MAX - p.path_prefix.as_os_str().len()
+ })
+ .unwrap_or_else(|e| e);
+ state.path_log_levels.insert(
+ index,
+ PathFilter {
+ path_prefix: PathBuf::from(path_prefix),
+ level: log_level.into(),
+ },
+ );
+}
+
+static STATE_ONCE: Once = Once::new();
+static mut STATE: *const Mutex<State> = 0 as *const _;
+
+fn new_mutex_ptr<T>(inner: T) -> *const Mutex<T> {
+ Box::into_raw(Box::new(Mutex::new(inner)))
+}
+
+/// Initialize the syslog connection and internal variables.
+///
+/// This should only be called once per process before any other threads have been spawned or any
+/// signal handlers have been registered. Every call made after the first will have no effect
+/// besides return `Ok` or `Err` appropriately.
+pub fn init() -> Result<(), Error> {
+ let mut err = Error::Poisoned;
+ STATE_ONCE.call_once(|| match State::new() {
+ // Safe because STATE mutation is guarded by `Once`.
+ Ok(state) => unsafe { STATE = new_mutex_ptr(state) },
+ Err(e) => err = e,
+ });
+
+ if unsafe { STATE.is_null() } {
+ Err(err)
+ } else {
+ Ok(())
+ }
+}
+
+fn lock() -> Result<MutexGuard<'static, State>, Error> {
+ // Safe because we assume that STATE is always in either a valid or NULL state.
+ let state_ptr = unsafe { STATE };
+ if state_ptr.is_null() {
+ return Err(Error::NeverInitialized);
+ }
+ // Safe because STATE only mutates once and we checked for NULL.
+ let state = unsafe { &*state_ptr };
+ let guard = state.lock();
+ Ok(guard)
+}
+
+// Attempts to lock and retrieve the state. Returns from the function silently on failure.
+#[macro_export]
+macro_rules! syslog_lock {
+ () => {
+ match lock() {
+ Ok(s) => s,
+ _ => return,
+ }
+ };
+}
+
+pub fn log_enabled(pri: Priority, file_path: &str) -> Option<bool> {
+ let log_level = match lock() {
+ Ok(state) => {
+ let parsed_path = Path::new(file_path);
+ // Since path_log_levels is sorted with longest prefixes first, this will yield the
+ // longest matching prefix if one exists.
+ state
+ .path_log_levels
+ .iter()
+ .find_map(|filter| {
+ if parsed_path.starts_with(filter.path_prefix.as_path()) {
+ Some(filter.level)
+ } else {
+ None
+ }
+ })
+ .unwrap_or(state.log_level)
+ }
+ _ => return None,
+ };
+ match log_level {
+ PriorityFilter::ShowAll => Some(true),
+ PriorityFilter::Silent => Some(false),
+ PriorityFilter::Priority(log_level) => Some((pri as u8) <= (log_level as u8)),
+ }
+}
+
+/// A macro for logging at an arbitrary priority level.
+///
+/// Note that this will fail silently if syslog was not initialized.
+#[macro_export]
+macro_rules! log {
+ ($pri:expr, $($args:tt)+) => ({
+ if let Some(true) = $crate::platform::syslog::log_enabled($pri, file!()) {
+ $crate::platform::syslog::log($pri, $crate::platform::syslog::Facility::User, Some((file!(), line!())), format_args!($($args)+))
+ }
+ })
+}
+
+/// Replaces the process name reported in each syslog message.
+///
+/// The default process name is the _file name_ of `argv[0]`. For example, if this program was
+/// invoked as
+///
+/// ```bash
+/// $ path/to/app --delete everything
+/// ```
+///
+/// the default process name would be _app_.
+///
+/// Does nothing if syslog was never initialized.
+pub fn set_proc_name<T: Into<String>>(proc_name: T) {
+ let mut state = syslog_lock!();
+ state.proc_name = Some(proc_name.into());
+}
+
+pub(crate) trait Syslog {
+ fn new() -> Result<Self, Error>
+ where
+ Self: Sized;
+
+ /// Enables or disables echoing log messages to the syslog.
+ ///
+ /// The default behavior is **enabled**.
+ ///
+ /// If `enable` goes from `true` to `false`, the syslog connection is closed. The connection is
+ /// reopened if `enable` is set to `true` after it became `false`.
+ ///
+ /// Returns an error if syslog was never initialized or the syslog connection failed to be
+ /// established.
+ ///
+ /// # Arguments
+ /// * `enable` - `true` to enable echoing to syslog, `false` to disable echoing to syslog.
+ fn enable(&mut self, enable: bool) -> Result<(), Error>;
+
+ fn log(
+ &self,
+ proc_name: Option<&str>,
+ pri: Priority,
+ fac: Facility,
+ file_line: Option<(&str, u32)>,
+ args: fmt::Arguments,
+ );
+
+ fn push_descriptors(&self, fds: &mut Vec<RawDescriptor>);
+}
+
+/// Enables or disables echoing log messages to the syslog.
+///
+/// The default behavior is **enabled**.
+///
+/// If `enable` goes from `true` to `false`, the syslog connection is closed. The connection is
+/// reopened if `enable` is set to `true` after it became `false`.
+///
+/// Returns an error if syslog was never initialized or the syslog connection failed to be
+/// established.
+///
+/// # Arguments
+/// * `enable` - `true` to enable echoing to syslog, `false` to disable echoing to syslog.
+pub fn echo_syslog(enable: bool) -> Result<(), Error> {
+ let state_ptr = unsafe { STATE };
+ if state_ptr.is_null() {
+ return Err(Error::NeverInitialized);
+ }
+ let mut state = lock().map_err(|_| Error::Poisoned)?;
+
+ state.syslog.enable(enable)
+}
+
+/// Replaces the optional `File` to echo log messages to.
+///
+/// The default behavior is to not echo to a file. Passing `None` to this function restores that
+/// behavior.
+///
+/// Does nothing if syslog was never initialized.
+///
+/// # Arguments
+/// * `file` - `Some(file)` to echo to `file`, `None` to disable echoing to the file previously passed to `echo_file`.
+pub fn echo_file(file: Option<File>) {
+ let mut state = syslog_lock!();
+ state.file = file;
+}
+
+/// Enables or disables echoing log messages to the `std::io::stderr()`.
+///
+/// The default behavior is **enabled**.
+///
+/// Does nothing if syslog was never initialized.
+///
+/// # Arguments
+/// * `enable` - `true` to enable echoing to stderr, `false` to disable echoing to stderr.
+pub fn echo_stderr(enable: bool) {
+ let mut state = syslog_lock!();
+ state.stderr = enable;
+}
+
+/// Retrieves the file descriptors owned by the global syslogger.
+///
+/// Does nothing if syslog was never initialized. If their are any file descriptors, they will be
+/// pushed into `fds`.
+///
+/// Note that the `stderr` file descriptor is never added, as it is not owned by syslog.
+pub fn push_descriptors(fds: &mut Vec<RawDescriptor>) {
+ let state = syslog_lock!();
+ state.syslog.push_descriptors(fds);
+ fds.extend(state.file.iter().map(|f| f.as_raw_descriptor()));
+}
+
+/// Compatability function for callers using the old naming
+pub fn push_fds(fds: &mut Vec<RawDescriptor>) {
+ push_descriptors(fds)
+}
+
+/// Records a log message with the given details.
+///
+/// Note that this will fail silently if syslog was not initialized.
+///
+/// # Arguments
+/// * `pri` - The `Priority` (i.e. severity) of the log message.
+/// * `fac` - The `Facility` of the log message. Usually `Facility::User` should be used.
+/// * `file_line` - Optional tuple of the name of the file that generated the
+/// log and the line number within that file.
+/// * `args` - The log's message to record, in the form of `format_args!()` return value
+///
+/// # Examples
+///
+/// ```
+/// # use crate::platform::syslog;
+/// # if let Err(e) = syslog::init() {
+/// # println!("failed to initiailize syslog: {}", e);
+/// # return;
+/// # }
+/// syslog::log(syslog::Priority::Error,
+/// syslog::Facility::User,
+/// Some((file!(), line!())),
+/// format_args!("hello syslog"));
+/// ```
+pub fn log(pri: Priority, fac: Facility, file_line: Option<(&str, u32)>, args: fmt::Arguments) {
+ let mut state = syslog_lock!();
+ let mut buf = [0u8; 2048];
+
+ state.syslog.log(
+ state.proc_name.as_ref().map(|s| s.as_ref()),
+ pri,
+ fac,
+ file_line,
+ args,
+ );
+
+ let res = {
+ let mut buf_cursor = Cursor::new(&mut buf[..]);
+ let now = chrono::Local::now()
+ .format(CHRONO_TIMESTAMP_FIXED_FMT!())
+ .to_string();
+ if let Some((file_name, line)) = &file_line {
+ write!(&mut buf_cursor, "[{}:{}:{}:{}] ", now, pri, file_name, line)
+ } else {
+ write!(&mut buf_cursor, "[{}]", now)
+ }
+ .and_then(|()| writeln!(&mut buf_cursor, "{}", args))
+ .map(|()| buf_cursor.position() as usize)
+ };
+ if let Ok(len) = &res {
+ write_to_file(&buf, *len, &mut state);
+ } else if let Err(e) = &res {
+ // Don't use warn macro to avoid potential recursion issues after macro expansion.
+ let err_buf = [0u8; 1024];
+ let mut buf_cursor = Cursor::new(&mut buf[..]);
+ if writeln!(
+ &mut buf_cursor,
+ "[{}]:WARNING: Failed to log with err: {:?}",
+ chrono::Local::now().format(CHRONO_TIMESTAMP_FIXED_FMT!()),
+ e
+ )
+ .is_ok()
+ {
+ write_to_file(&err_buf, buf_cursor.position() as usize, &mut state);
+ }
+ }
+}
+
+fn write_to_file(buf: &[u8], len: usize, state: &mut State) {
+ if let Some(file) = state.file.as_mut() {
+ let _ = file.write_all(&buf[..len]);
+ }
+ if state.stderr {
+ let _ = stderr().write_all(&buf[..len]);
+ }
+}
+
+/// A macro for logging an error.
+///
+/// Note that this will fail silently if syslog was not initialized.
+#[macro_export]
+macro_rules! error {
+ ($($args:tt)+) => ($crate::log!($crate::platform::syslog::Priority::Error, $($args)*))
+}
+
+/// A macro for logging a warning.
+///
+/// Note that this will fail silently if syslog was not initialized.
+#[macro_export]
+macro_rules! warn {
+ ($($args:tt)+) => ($crate::log!($crate::platform::syslog::Priority::Warning, $($args)*))
+}
+
+/// A macro for logging info.
+///
+/// Note that this will fail silently if syslog was not initialized.
+#[macro_export]
+macro_rules! info {
+ ($($args:tt)+) => ($crate::log!($crate::platform::syslog::Priority::Info, $($args)*))
+}
+
+/// A macro for logging debug information.
+///
+/// Note that this will fail silently if syslog was not initialized.
+#[macro_export]
+macro_rules! debug {
+ ($($args:tt)+) => ($crate::log!($crate::platform::syslog::Priority::Debug, $($args)*))
+}
+
+// Struct that implements io::Write to be used for writing directly to the syslog
+pub struct Syslogger {
+ buf: String,
+ priority: Priority,
+ facility: Facility,
+}
+
+impl Syslogger {
+ pub fn new(p: Priority, f: Facility) -> Syslogger {
+ Syslogger {
+ buf: String::new(),
+ priority: p,
+ facility: f,
+ }
+ }
+}
+
+impl io::Write for Syslogger {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ let parsed_str = String::from_utf8_lossy(buf);
+ self.buf.push_str(&parsed_str);
+
+ if let Some(last_newline_idx) = self.buf.rfind('\n') {
+ for line in self.buf[..last_newline_idx].lines() {
+ log(self.priority, self.facility, None, format_args!("{}", line));
+ }
+
+ self.buf.drain(..=last_newline_idx);
+ }
+
+ Ok(buf.len())
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+// TODO(b/223733375): Enable ignored flaky tests.
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use super::super::{BlockingMode, FramingMode, StreamChannel};
+ use regex::Regex;
+ use std::{convert::TryInto, io::Read, os::windows::io::FromRawHandle};
+
+ #[test]
+ #[ignore]
+ fn init_syslog() {
+ init().unwrap();
+ }
+
+ #[test]
+ #[ignore]
+ fn syslog_log() {
+ init().unwrap();
+ log(
+ Priority::Error,
+ Facility::User,
+ Some((file!(), line!())),
+ format_args!("hello syslog"),
+ );
+ }
+
+ #[test]
+ #[ignore]
+ fn proc_name() {
+ init().unwrap();
+ log(
+ Priority::Error,
+ Facility::User,
+ Some((file!(), line!())),
+ format_args!("before proc name"),
+ );
+ set_proc_name("win_sys_util-test");
+ log(
+ Priority::Error,
+ Facility::User,
+ Some((file!(), line!())),
+ format_args!("after proc name"),
+ );
+ }
+
+ #[test]
+ #[ignore]
+ fn macros() {
+ init().unwrap();
+ error!("this is an error {}", 3);
+ warn!("this is a warning {}", "uh oh");
+ info!("this is info {}", true);
+ debug!("this is debug info {:?}", Some("helpful stuff"));
+ }
+
+ #[test]
+ #[ignore]
+ fn syslogger_char() {
+ init().unwrap();
+ let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
+
+ let string = "Writing chars to syslog";
+ for c in string.chars() {
+ syslogger.write_all(&[c as u8]).expect("error writing char");
+ }
+
+ syslogger
+ .write_all(&[b'\n'])
+ .expect("error writing newline char");
+ }
+
+ #[test]
+ #[ignore]
+ fn syslogger_line() {
+ init().unwrap();
+ let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
+
+ let s = "Writing string to syslog\n";
+ syslogger
+ .write_all(s.as_bytes())
+ .expect("error writing string");
+ }
+
+ #[test]
+ #[ignore]
+ fn syslogger_partial() {
+ init().unwrap();
+ let mut syslogger = Syslogger::new(Priority::Info, Facility::Daemon);
+
+ let s = "Writing partial string";
+ // Should not log because there is no newline character
+ syslogger
+ .write_all(s.as_bytes())
+ .expect("error writing string");
+ }
+
+ fn clear_path_log_levels() {
+ syslog_lock!().path_log_levels.clear();
+ }
+
+ #[test]
+ #[ignore]
+ fn syslogger_verify_line_written() {
+ init().unwrap();
+ clear_path_log_levels();
+ let (mut reader, writer) =
+ StreamChannel::pair(BlockingMode::Blocking, FramingMode::Byte).unwrap();
+
+ // Safe because writer is guaranteed to exist, and we forget the StreamChannel that used
+ // to own the StreamChannel.
+ let writer_file = unsafe { File::from_raw_handle(writer.as_raw_descriptor()) };
+ std::mem::forget(writer);
+
+ echo_file(Some(writer_file));
+ error!("Test message.");
+
+ // Ensure the message we wrote actually was written to the supplied "file" (which is
+ // really a named pipe here).
+ let mut buf: [u8; 1024] = [0; 1024];
+ let bytes_read = reader.read(&mut buf).unwrap();
+ assert!(bytes_read > 0);
+ let log_msg = String::from_utf8(buf.to_vec()).unwrap();
+ let re = Regex::new(r"^\[.+:ERROR:.+:[0-9]+\] Test message.").unwrap();
+ assert!(re.is_match(&log_msg));
+ }
+
+ #[test]
+ #[ignore]
+ fn log_priority_try_from_number() {
+ assert_eq!("0".try_into(), Ok(Priority::Emergency));
+ assert!(Priority::try_from("100").is_err());
+ }
+
+ #[test]
+ #[ignore]
+ fn log_priority_try_from_words() {
+ assert_eq!("EMERGENCY".try_into(), Ok(Priority::Emergency));
+ assert!(Priority::try_from("_EMERGENCY").is_err());
+ }
+
+ #[test]
+ #[ignore]
+ fn log_priority_filter_try_from_star() {
+ assert_eq!("*".try_into(), Ok(PriorityFilter::ShowAll));
+ }
+
+ #[test]
+ #[ignore]
+ fn log_priority_filter_try_from_silence() {
+ assert_eq!("S".try_into(), Ok(PriorityFilter::Silent));
+ assert_eq!("s".try_into(), Ok(PriorityFilter::Silent));
+ assert_eq!("SILENT".try_into(), Ok(PriorityFilter::Silent));
+ }
+
+ #[test]
+ #[ignore]
+ fn log_priority_filter_try_from_priority_str() {
+ assert_eq!(
+ "DEBUG".try_into(),
+ Ok(PriorityFilter::Priority(Priority::Debug))
+ );
+ assert_eq!(
+ "debug".try_into(),
+ Ok(PriorityFilter::Priority(Priority::Debug))
+ );
+ assert!(PriorityFilter::try_from("_DEBUG").is_err());
+ }
+
+ #[test]
+ #[ignore]
+ fn log_should_always_be_enabled_for_level_show_all() {
+ init().unwrap();
+ clear_path_log_levels();
+ set_log_level(PriorityFilter::ShowAll);
+ assert!(log_enabled(Priority::Debug, "").unwrap());
+ }
+
+ #[test]
+ #[ignore]
+ fn log_should_always_be_disabled_for_level_silent() {
+ init().unwrap();
+ clear_path_log_levels();
+ set_log_level(PriorityFilter::Silent);
+ let enabled = log_enabled(Priority::Emergency, "");
+ set_log_level(PriorityFilter::ShowAll);
+ assert!(!enabled.unwrap());
+ }
+
+ #[test]
+ #[ignore]
+ fn log_should_be_enabled_if_filter_level_has_a_lower_or_equal_priority() {
+ init().unwrap();
+ clear_path_log_levels();
+ set_log_level(Priority::Info);
+ let info_enabled = log_enabled(Priority::Info, "");
+ let warn_enabled = log_enabled(Priority::Warning, "");
+ set_log_level(PriorityFilter::ShowAll);
+ assert!(info_enabled.unwrap());
+ assert!(warn_enabled.unwrap());
+ }
+
+ #[test]
+ #[ignore]
+ fn log_should_be_disabled_if_filter_level_has_a_higher_priority() {
+ init().unwrap();
+ clear_path_log_levels();
+ set_log_level(Priority::Info);
+ let enabled = log_enabled(Priority::Debug, "");
+ set_log_level(PriorityFilter::ShowAll);
+ assert!(!enabled.unwrap());
+ }
+
+ #[test]
+ #[ignore]
+ fn path_overides_should_apply_to_logs() {
+ init().unwrap();
+ clear_path_log_levels();
+ set_log_level(Priority::Info);
+ add_path_log_level("fake/debug/src", Priority::Debug);
+
+ assert!(!log_enabled(Priority::Debug, "test.rs").unwrap());
+ assert!(log_enabled(Priority::Debug, "fake/debug/src/test.rs").unwrap());
+ }
+
+ #[test]
+ #[ignore]
+ fn longest_path_prefix_match_should_apply_if_multiple_filters_match() {
+ init().unwrap();
+ clear_path_log_levels();
+ set_log_level(Priority::Info);
+ add_path_log_level("fake/debug/src", Priority::Debug);
+ add_path_log_level("fake/debug/src/silence", PriorityFilter::Silent);
+
+ assert!(!log_enabled(Priority::Info, "fake/debug/src/silence/test.rs").unwrap());
+ }
+
+ #[test]
+ #[ignore]
+ fn syslogger_log_level_filter() {
+ init().unwrap();
+ clear_path_log_levels();
+ set_log_level(Priority::Error);
+
+ let (mut reader, writer) =
+ StreamChannel::pair(BlockingMode::Blocking, FramingMode::Byte).unwrap();
+
+ // Safe because writer is guaranteed to exist, and we forget the StreamChannel that used
+ // to own the StreamChannel.
+ let writer_file = unsafe { File::from_raw_handle(writer.as_raw_descriptor()) };
+ std::mem::forget(writer);
+
+ echo_file(Some(writer_file));
+ error!("Test message.");
+ debug!("Test message.");
+
+ add_path_log_level(file!(), Priority::Debug);
+ debug!("Test with file filter.");
+
+ set_log_level(PriorityFilter::ShowAll);
+
+ // Ensure the message we wrote actually was written to the supplied "file" (which is
+ // really a named pipe here).
+ let mut buf: [u8; 1024] = [0; 1024];
+ let bytes_read = reader.read(&mut buf).unwrap();
+ assert!(bytes_read > 0);
+ let log_msg = String::from_utf8(buf.to_vec()).unwrap();
+ let re_error = Regex::new(r"(?m)^\[.*ERROR:.+:[0-9]+\] Test message.").unwrap();
+ let re_debug = Regex::new(r"(?m)^\[.*DEBUG:.+:[0-9]+\] Test message.").unwrap();
+ let filter_debug =
+ Regex::new(r"(?m)^\[.*DEBUG:.+:[0-9]+\] Test with file filter.").unwrap();
+ assert!(re_error.is_match(&log_msg));
+ assert!(!re_debug.is_match(&log_msg));
+ assert!(filter_debug.is_match(&log_msg));
+ }
+}
diff --git a/base/src/windows/thread.rs b/base/src/windows/thread.rs
new file mode 100644
index 000000000..71e9c5a72
--- /dev/null
+++ b/base/src/windows/thread.rs
@@ -0,0 +1,52 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ any::Any,
+ panic,
+ panic::UnwindSafe,
+ sync::mpsc::{channel, Receiver},
+ thread,
+ thread::JoinHandle,
+ time::Duration,
+};
+
+/// Spawns a thread that can be joined with a timeout.
+pub fn spawn_with_timeout<F, T>(f: F) -> JoinHandleWithTimeout<T>
+where
+ F: FnOnce() -> T,
+ F: Send + UnwindSafe + 'static,
+ T: Send + 'static,
+{
+ // Use a channel to signal completion to the join handle
+ let (tx, rx) = channel();
+ let handle = thread::spawn(move || {
+ let val = panic::catch_unwind(f);
+ tx.send(()).unwrap();
+ val
+ });
+ JoinHandleWithTimeout { handle, rx }
+}
+
+pub struct JoinHandleWithTimeout<T> {
+ handle: JoinHandle<thread::Result<T>>,
+ rx: Receiver<()>,
+}
+
+#[derive(Debug)]
+pub enum JoinError {
+ Panic(Box<dyn Any>),
+ Timeout,
+}
+
+impl<T> JoinHandleWithTimeout<T> {
+ /// Tries to join the thread. Returns an error if the join takes more than `timeout_ms`.
+ pub fn try_join(self, timeout: Duration) -> Result<T, JoinError> {
+ if self.rx.recv_timeout(timeout).is_ok() {
+ self.handle.join().unwrap().map_err(|e| JoinError::Panic(e))
+ } else {
+ Err(JoinError::Timeout)
+ }
+ }
+}
diff --git a/base/src/windows/timer.rs b/base/src/windows/timer.rs
new file mode 100644
index 000000000..a20c931b4
--- /dev/null
+++ b/base/src/windows/timer.rs
@@ -0,0 +1,355 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ sync::Arc,
+ time::{Duration, Instant},
+};
+use sync::Mutex;
+
+use super::{Event, EventReadResult, FakeClock, RawDescriptor, Result};
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor};
+
+#[path = "win/timer.rs"]
+mod timer_platform;
+
+pub struct Timer {
+ handle: SafeDescriptor,
+ interval: Option<Duration>,
+}
+
+impl Timer {
+ /// Creates a new `Timer` instance that shares the same underlying `SafeDescriptor` as the
+ /// existing `Timer` instance.
+ pub fn try_clone(&self) -> std::result::Result<Timer, std::io::Error> {
+ self.handle
+ .try_clone()
+ .map(|handle| Timer {
+ handle,
+ interval: self.interval,
+ })
+ .map_err(|err| std::io::Error::from_raw_os_error(err.errno()))
+ }
+}
+
+// This enum represents those two different retrun values from a "wait" call. Either the
+// timer will "expire", meaning it has reached it's duration, or the caller will time out
+// waiting for the timer to expire. If no timeout option is provieded to the wait call
+// then it can only return WaitResult::Expired or an error.
+#[derive(PartialEq, Debug)]
+pub enum WaitResult {
+ Expired,
+ Timeout,
+}
+
+impl AsRawDescriptor for Timer {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.handle.as_raw_descriptor()
+ }
+}
+
+impl FromRawDescriptor for Timer {
+ unsafe fn from_raw_descriptor(handle: RawDescriptor) -> Self {
+ Timer {
+ handle: SafeDescriptor::from_raw_descriptor(handle),
+ interval: None,
+ }
+ }
+}
+
+impl IntoRawDescriptor for Timer {
+ fn into_raw_descriptor(self) -> RawDescriptor {
+ self.handle.into_raw_descriptor()
+ }
+}
+
+/// FakeTimer: For use in tests.
+pub struct FakeTimer {
+ clock: Arc<Mutex<FakeClock>>,
+ deadline_ns: Option<u64>,
+ interval: Option<Duration>,
+ event: Event,
+}
+
+impl FakeTimer {
+ /// Creates a new fake Timer. The timer is initally disarmed and must be armed by calling
+ /// `reset`.
+ pub fn new(clock: Arc<Mutex<FakeClock>>) -> Self {
+ FakeTimer {
+ clock,
+ deadline_ns: None,
+ interval: None,
+ event: Event::new().unwrap(),
+ }
+ }
+
+ /// Sets the timer to expire after `dur`. If `interval` is not `None` it represents
+ /// the period for repeated expirations after the initial expiration. Otherwise
+ /// the timer will expire just once. Cancels any existing duration and repeating interval.
+ pub fn reset(&mut self, dur: Duration, interval: Option<Duration>) -> Result<()> {
+ let mut guard = self.clock.lock();
+ let deadline = guard.nanos() + dur.as_nanos() as u64;
+ self.deadline_ns = Some(deadline);
+ self.interval = interval;
+ guard.add_event(deadline, self.event.try_clone()?);
+ Ok(())
+ }
+
+ /// Waits until the timer expires, returning WaitResult::Expired when it expires.
+ ///
+ /// If timeout is not None, block for a maximum of the given `timeout` duration.
+ /// If a timeout occurs, return WaitResult::Timeout.
+ pub fn wait(&mut self, timeout: Option<Duration>) -> Result<WaitResult> {
+ let wait_start = Instant::now();
+ loop {
+ if let Some(timeout) = timeout {
+ let elapsed = Instant::now() - wait_start;
+ if let Some(remaining) = elapsed.checked_sub(timeout) {
+ if let EventReadResult::Timeout = self.event.read_timeout(remaining)? {
+ return Ok(WaitResult::Timeout);
+ }
+ } else {
+ return Ok(WaitResult::Timeout);
+ }
+ } else {
+ self.event.read()?;
+ }
+
+ if let Some(deadline_ns) = &mut self.deadline_ns {
+ let mut guard = self.clock.lock();
+ let now = guard.nanos();
+ if now >= *deadline_ns {
+ let mut expirys = 0;
+ if let Some(interval) = self.interval {
+ let interval_ns = interval.as_nanos() as u64;
+ if interval_ns > 0 {
+ expirys += (now - *deadline_ns) / interval_ns;
+ *deadline_ns += (expirys + 1) * interval_ns;
+ guard.add_event(*deadline_ns, self.event.try_clone()?);
+ }
+ }
+ return Ok(WaitResult::Expired);
+ }
+ }
+ }
+ }
+
+ /// After a timer is triggered from an EventContext, mark the timer as having been waited for.
+ /// If a timer is not marked waited, it will immediately trigger the event context again. This
+ /// does not need to be called after calling Timer::wait.
+ ///
+ /// Returns true if the timer has been adjusted since the EventContext was triggered by this
+ /// timer.
+ pub fn mark_waited(&mut self) -> Result<bool> {
+ // Just do a self.wait with a timeout of 0. If it times out then the timer has been
+ // adjusted.
+ if let WaitResult::Timeout = self.wait(Some(Duration::from_secs(0)))? {
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+
+ /// Disarms the timer.
+ pub fn clear(&mut self) -> Result<()> {
+ self.deadline_ns = None;
+ self.interval = None;
+ Ok(())
+ }
+
+ /// Returns the resolution of timers on the host.
+ pub fn resolution() -> Result<Duration> {
+ Ok(Duration::from_nanos(1))
+ }
+}
+
+impl AsRawDescriptor for FakeTimer {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.event.as_raw_descriptor()
+ }
+}
+impl IntoRawDescriptor for FakeTimer {
+ fn into_raw_descriptor(self) -> RawDescriptor {
+ self.event.into_raw_descriptor()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::time::{Duration, Instant};
+
+ // clock error is 2*clock_resolution + 100 microseconds to handle
+ // time change from calling now() to arming timer
+ fn get_clock_error() -> Duration {
+ Timer::resolution()
+ .expect("expected to be able to read timer resolution")
+ .checked_mul(2)
+ .expect("timer resolution x 2 should not overflow")
+ .checked_add(Duration::from_micros(100))
+ .expect("timer resolution x 2 + 100 microsecond should not overflow")
+ }
+
+ #[test]
+ #[ignore]
+ fn one_shot() {
+ // This test relies on the host having a reliable clock and not being
+ // overloaded, so it's marked as "ignore". You can run by running
+ // cargo test -p win_sys_util timer -- --ignored
+
+ let mut tfd = Timer::new().expect("failed to create Timer");
+
+ let dur = Duration::from_millis(1000);
+ let clock_error = get_clock_error();
+
+ let now = Instant::now();
+ tfd.reset(dur, None).expect("failed to arm timer");
+
+ tfd.wait(None).expect("unable to wait for timer");
+ let elapsed = now.elapsed();
+ // elapsed is within +-clock_error from expected duration
+ assert!(elapsed - clock_error <= dur);
+ assert!(elapsed + clock_error >= dur);
+ }
+
+ /// Similar to one_shot, except this one waits for a clone of the timer.
+ #[test]
+ #[ignore]
+ fn one_shot_cloned() {
+ let mut tfd = Timer::new().expect("failed to create Timer");
+ let dur = Duration::from_millis(1000);
+ let mut cloned_tfd = tfd.try_clone().expect("failed to clone timer");
+
+ // clock error is 2*clock_resolution + 100 microseconds to handle
+ // time change from calling now() to arming timer
+ let clock_error = get_clock_error();
+
+ let now = Instant::now();
+ tfd.reset(dur, None).expect("failed to arm timer");
+ cloned_tfd.wait(None).expect("unable to wait for timer");
+ let elapsed = now.elapsed();
+
+ // elapsed is within +-clock_error from expected duration
+ assert!(elapsed - clock_error <= dur);
+ assert!(elapsed + clock_error >= dur);
+ }
+
+ #[test]
+ #[ignore]
+ fn repeating() {
+ // This test relies on the host having a reliable clock and not being
+ // overloaded, so it's marked as "ignore". You can run by running
+ // cargo test -p win_sys_util timer -- --ignored
+
+ let mut tfd = Timer::new().expect("failed to create Timer");
+
+ let dur = Duration::from_millis(200);
+ // clock error is 2*clock_resolution + 100 microseconds to handle
+ // time change from calling now() to arming timer
+ let clock_error = Timer::resolution()
+ .expect("expected to be able to read timer resolution")
+ .checked_mul(2)
+ .expect("timer resolution x 2 should not overflow")
+ .checked_add(Duration::from_micros(100))
+ .expect("timer resolution x 2 + 100 microsecond should not overflow");
+ let interval = Duration::from_millis(100);
+ let now = Instant::now();
+ tfd.reset(dur, Some(interval)).expect("failed to arm timer");
+
+ tfd.wait(None).expect("unable to wait for timer");
+ // should take "dur" duration for the first wait
+ assert!(now.elapsed() + clock_error >= dur);
+ tfd.wait(None).expect("unable to wait for timer");
+ // subsequent waits should take "interval" duration
+ assert!(now.elapsed() + clock_error >= dur + interval);
+ tfd.wait(None).expect("unable to wait for timer");
+ assert!(now.elapsed() + clock_error >= dur + interval * 2);
+ }
+
+ #[test]
+ fn fake_one_shot() {
+ let clock = Arc::new(Mutex::new(FakeClock::new()));
+ let mut tfd = FakeTimer::new(clock.clone());
+
+ let dur = Duration::from_nanos(200);
+ tfd.reset(dur, None).expect("failed to arm timer");
+
+ clock.lock().add_ns(200);
+
+ let result = tfd.wait(None).expect("unable to wait for timer");
+
+ assert_eq!(result, WaitResult::Expired);
+ }
+
+ #[test]
+ fn fake_one_shot_timeout() {
+ let clock = Arc::new(Mutex::new(FakeClock::new()));
+ let mut tfd = FakeTimer::new(clock.clone());
+
+ let dur = Duration::from_nanos(200);
+ tfd.reset(dur, None).expect("failed to arm timer");
+
+ clock.lock().add_ns(100);
+ let result = tfd
+ .wait(Some(Duration::from_millis(0)))
+ .expect("unable to wait for timer");
+ assert_eq!(result, WaitResult::Timeout);
+ let result = tfd
+ .wait(Some(Duration::from_millis(1)))
+ .expect("unable to wait for timer");
+ assert_eq!(result, WaitResult::Timeout);
+
+ clock.lock().add_ns(100);
+ let result = tfd
+ .wait(Some(Duration::from_millis(0)))
+ .expect("unable to wait for timer");
+ assert_eq!(result, WaitResult::Expired);
+ }
+
+ #[test]
+ fn fake_repeating() {
+ let clock = Arc::new(Mutex::new(FakeClock::new()));
+ let mut tfd = FakeTimer::new(clock.clone());
+
+ let dur = Duration::from_nanos(200);
+ let interval = Duration::from_nanos(100);
+ tfd.reset(dur, Some(interval)).expect("failed to arm timer");
+
+ clock.lock().add_ns(300);
+
+ let mut result = tfd.wait(None).expect("unable to wait for timer");
+ // An expiration from the initial expiry and from 1 repeat.
+ assert_eq!(result, WaitResult::Expired);
+
+ clock.lock().add_ns(300);
+ result = tfd.wait(None).expect("unable to wait for timer");
+ assert_eq!(result, WaitResult::Expired);
+ }
+
+ #[test]
+ #[ignore]
+ fn zero_interval() {
+ // This test relies on the host having a reliable clock and not being
+ // overloaded, so it's marked as "ignore". You can run by running
+ // cargo test -p win_sys_util timer -- --ignored
+
+ let mut tfd = Timer::new().expect("failed to create timer");
+
+ let dur = Duration::from_nanos(200);
+ // interval is 0, so should not repeat
+ let interval = Duration::from_nanos(0);
+
+ tfd.reset(dur, Some(interval)).expect("failed to arm timer");
+
+ // should wait successfully the first time
+ tfd.wait(None).expect("unable to wait for timer");
+
+ // now this wait should timeout
+ let wr = tfd
+ .wait(Some(Duration::from_secs(1)))
+ .expect("unable to wait on timer");
+
+ assert_eq!(wr, WaitResult::Timeout);
+ }
+}
diff --git a/base/src/windows/tube.rs b/base/src/windows/tube.rs
new file mode 100644
index 000000000..3f59ffa00
--- /dev/null
+++ b/base/src/windows/tube.rs
@@ -0,0 +1,448 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ io::{
+ Cursor, Read, Write, {self},
+ },
+ time::Duration,
+};
+
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, SafeDescriptor};
+use crate::{
+ platform::{deserialize_with_descriptors, RawDescriptor, SerializeDescriptors},
+ tube::{Error, RecvTube, Result, SendTube},
+ BlockingMode, CloseNotifier, FramingMode, PollToken, ReadNotifier, StreamChannel,
+};
+use data_model::DataInit;
+use lazy_static::lazy_static;
+use serde::{de::DeserializeOwned, Deserialize, Serialize, Serializer};
+use std::{
+ mem,
+ os::windows::io::{AsRawHandle, RawHandle},
+};
+use winapi::shared::winerror::ERROR_MORE_DATA;
+
+/// Bidirectional tube that support both send and recv.
+///
+/// NOTE: serializing this type across processes is slightly involved. Suppose there is a Tube pair
+/// (A, B). We wish to send B to another process, and communicate with it using A from the current
+/// process:
+/// 1. B's target_pid must be set to the current PID *before* serialization. There is a
+/// serialization hook that sets it to the current PID automatically if target_pid is unset.
+/// 2. A's target_pid must be set to the PID of the process where B was sent.
+///
+/// If instead you are sending both A and B to separate processes, then:
+/// 1. A's target_pid must be set to B's pid, manually.
+/// 2. B's target_pid must be set to A's pid, manually.
+///
+/// Automating all of this and getting a completely clean interface is tricky. We would need
+/// intercept the serialization of Tubes in any part of Serde messages, and use Weak refs to sync
+/// state about PIDs between the ends. There are alternatives like reusing the underlying
+/// StreamChannel to share PIDs, or having a separate pipe just for this purpose; however, we've yet
+/// to find a compelling solution that isn't a mess to implement. Suggestions are welcome.
+#[derive(Serialize, Deserialize, Debug)]
+pub struct Tube {
+ socket: StreamChannel,
+
+ // Default target_pid to current PID on serialization (see `Tube` comment header for details).
+ #[serde(serialize_with = "set_tube_pid_on_serialize")]
+ target_pid: Option<u32>,
+}
+
+/// For a Tube which has not had its target_pid set, when it is serialized, we should automatically
+/// default it to the current process, because the other end will be in the current process.
+fn set_tube_pid_on_serialize<S>(
+ existing_pid_value: &Option<u32>,
+ serializer: S,
+) -> std::result::Result<S::Ok, S::Error>
+where
+ S: Serializer,
+{
+ match existing_pid_value {
+ Some(pid) => serializer.serialize_u32(*pid),
+ None => serializer.serialize_u32(ALIAS_PID.lock().unwrap_or(std::process::id())),
+ }
+}
+
+#[derive(Copy, Clone, Debug)]
+#[repr(C)]
+struct MsgHeader {
+ msg_json_size: usize,
+ descriptor_json_size: usize,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for MsgHeader {}
+
+lazy_static! {
+ static ref DH_TUBE: sync::Mutex<Option<DuplicateHandleTube>> = sync::Mutex::new(None);
+ static ref ALIAS_PID: sync::Mutex<Option<u32>> = sync::Mutex::new(None);
+}
+
+/// Set a tube to delegate duplicate handle calls.
+pub fn set_duplicate_handle_tube(dh_tube: DuplicateHandleTube) {
+ DH_TUBE.lock().replace(dh_tube);
+}
+
+/// Set alias pid for use with a DuplicateHandleTube.
+pub fn set_alias_pid(alias_pid: u32) {
+ ALIAS_PID.lock().replace(alias_pid);
+}
+
+impl Tube {
+ /// Create a pair of connected tubes. Request is sent in one direction while response is
+ /// received in the other direction.
+ /// The result is in the form (server, client).
+ pub fn pair() -> Result<(Tube, Tube)> {
+ let (socket1, socket2) = StreamChannel::pair(BlockingMode::Blocking, FramingMode::Message)
+ .map_err(|e| Error::Pair(io::Error::from_raw_os_error(e.errno())))?;
+
+ Ok((Tube::new(socket1), Tube::new(socket2)))
+ }
+
+ /// Create a pair of connected tubes with the specified buffer size.
+ /// Request is sent in one direction while response is received in the other direction.
+ /// The result is in the form (server, client).
+ pub fn pair_with_buffer_size(buffer_size: usize) -> Result<(Tube, Tube)> {
+ let (socket1, socket2) = StreamChannel::pair_with_buffer_size(
+ BlockingMode::Blocking,
+ FramingMode::Message,
+ buffer_size,
+ )
+ .map_err(|e| Error::Pair(io::Error::from_raw_os_error(e.errno())))?;
+ let tube1 = Tube::new(socket1);
+ let tube2 = Tube::new(socket2);
+ Ok((tube1, tube2))
+ }
+
+ // Create a new `Tube`.
+ pub fn new(socket: StreamChannel) -> Tube {
+ Tube {
+ socket,
+ target_pid: None,
+ }
+ }
+
+ pub(super) fn try_clone(&self) -> Result<Self> {
+ Ok(Tube {
+ socket: self.socket.try_clone().map_err(Error::Clone)?,
+ target_pid: self.target_pid,
+ })
+ }
+
+ pub fn send<T: Serialize>(&self, msg: &T) -> Result<()> {
+ serialize_and_send(|buf| self.socket.write_immutable(buf), msg, self.target_pid)
+ }
+
+ pub fn recv<T: DeserializeOwned>(&self) -> Result<T> {
+ deserialize_and_recv(|buf| (&self.socket).read(buf))
+ }
+
+ /// NOTE: On Windows this will only succeed if called on a server pipe. See #pair
+ /// documentation to ensure you have a server pipe before calling.
+ #[cfg(windows)]
+ pub fn flush_blocking(&mut self) -> Result<()> {
+ self.socket.flush_blocking().map_err(Error::Flush)
+ }
+
+ /// For Tubes that span processes, this method must be used to set the PID of the other end
+ /// of the Tube, otherwise sending handles to the other end won't work.
+ pub fn set_target_pid(&mut self, target_pid: u32) {
+ self.target_pid = Some(target_pid);
+ }
+
+ /// Returns the PID of the process at the other end of the Tube, if any is set.
+ pub fn target_pid(&self) -> Option<u32> {
+ self.target_pid
+ }
+
+ /// TODO(b/145998747, b/184398671): this method should be removed.
+ pub fn set_send_timeout(&self, _timeout: Option<Duration>) -> Result<()> {
+ unimplemented!("To be removed/refactored upstream.");
+ }
+
+ /// TODO(b/145998747, b/184398671): this method should be removed.
+ pub fn set_recv_timeout(&self, _timeout: Option<Duration>) -> Result<()> {
+ unimplemented!("To be removed/refactored upstream.");
+ }
+}
+
+pub fn serialize_and_send<T: Serialize, F: Fn(&[u8]) -> io::Result<usize>>(
+ write_fn: F,
+ msg: &T,
+ target_pid: Option<u32>,
+) -> Result<()> {
+ let msg_serialize = SerializeDescriptors::new(&msg);
+ let msg_json = serde_json::to_vec(&msg_serialize).map_err(Error::Json)?;
+ let msg_descriptors = msg_serialize.into_descriptors();
+
+ let mut duped_descriptors = Vec::with_capacity(msg_descriptors.len());
+ for desc in msg_descriptors {
+ // Safe because these handles are guaranteed to be valid. Details:
+ // 1. They come from sys_util::descriptor_reflection::with_as_descriptor.
+ // 2. with_as_descriptor is intended to be applied to owned descriptor types (e.g. File,
+ // SafeDescriptor).
+ // 3. The owning object is borrowed by msg until sending is complete.
+ duped_descriptors.push(duplicate_handle(desc, target_pid)? as usize)
+ }
+
+ let descriptor_json = if duped_descriptors.is_empty() {
+ None
+ } else {
+ Some(serde_json::to_vec(&duped_descriptors).map_err(Error::Json)?)
+ };
+
+ let header = MsgHeader {
+ msg_json_size: msg_json.len(),
+ descriptor_json_size: descriptor_json.as_ref().map_or(0, |json| json.len()),
+ };
+
+ let mut data_packet = Cursor::new(Vec::with_capacity(
+ header.as_slice().len() + header.msg_json_size + header.descriptor_json_size,
+ ));
+ data_packet
+ .write(header.as_slice())
+ .map_err(Error::SendIoBuf)?;
+ data_packet
+ .write(msg_json.as_slice())
+ .map_err(Error::SendIoBuf)?;
+ if let Some(descriptor_json) = descriptor_json {
+ data_packet
+ .write(descriptor_json.as_slice())
+ .map_err(Error::SendIoBuf)?;
+ }
+
+ // Multiple writers (producers) are safe because each write is atomic.
+ let data_bytes = data_packet.into_inner();
+
+ write_fn(&data_bytes).map_err(Error::SendIo)?;
+ Ok(())
+}
+
+fn duplicate_handle(desc: RawHandle, target_pid: Option<u32>) -> Result<RawHandle> {
+ match target_pid {
+ Some(pid) => match &*DH_TUBE.lock() {
+ Some(tube) => tube.request_duplicate_handle(pid, desc),
+ None => {
+ win_util::duplicate_handle_with_target_pid(desc, pid).map_err(Error::DupDescriptor)
+ }
+ },
+ None => win_util::duplicate_handle(desc).map_err(Error::DupDescriptor),
+ }
+}
+
+/// Reads a part of a Tube packet asserting that it was correctly read. This means:
+/// * Treats partial "message" (transport framing) reads are Ok, as long as we filled our buffer.
+/// We use this to ignore errors when reading the message header, which has the lengths we need
+/// to allocate our buffers for the remainder of the message.
+/// * We filled the supplied buffer.
+fn perform_read<F: Fn(&mut [u8]) -> io::Result<usize>>(
+ read_fn: &F,
+ buf: &mut [u8],
+) -> io::Result<usize> {
+ let res = match read_fn(buf) {
+ Ok(s) => Ok(s),
+ Err(e)
+ if e.raw_os_error()
+ .map_or(false, |errno| errno == ERROR_MORE_DATA as i32) =>
+ {
+ Ok(buf.len())
+ }
+ Err(e) => Err(e),
+ };
+
+ let bytes_read = res?;
+ if bytes_read != buf.len() {
+ Err(io::Error::new(
+ io::ErrorKind::UnexpectedEof,
+ "failed to fill whole buffer",
+ ))
+ } else {
+ Ok(bytes_read)
+ }
+}
+
+/// Deserializes a Tube packet by calling the supplied read function. This function MUST
+/// assert that the buffer was filled.
+pub fn deserialize_and_recv<T: DeserializeOwned, F: Fn(&mut [u8]) -> io::Result<usize>>(
+ read_fn: F,
+) -> Result<T> {
+ let mut header_bytes = vec![0u8; mem::size_of::<MsgHeader>()];
+ perform_read(&read_fn, header_bytes.as_mut_slice()).map_err(Error::Recv)?;
+
+ // Safe because the header is always written by the send function, and only that function
+ // writes to this channel.
+ let header =
+ MsgHeader::from_slice(header_bytes.as_slice()).expect("Tube header failed to deserialize.");
+
+ let mut msg_json = vec![0u8; header.msg_json_size];
+ perform_read(&read_fn, msg_json.as_mut_slice()).map_err(Error::Recv)?;
+
+ if msg_json.is_empty() {
+ // This means we got a message header, but there is no json body (due to a zero size in
+ // the header). This should never happen because it means the receiver is getting no
+ // data whatsoever from the sender.
+ return Err(Error::RecvUnexpectedEmptyBody);
+ }
+
+ let msg_descriptors: Vec<RawDescriptor> = if header.descriptor_json_size > 0 {
+ let mut msg_descriptors_json = vec![0u8; header.descriptor_json_size];
+ perform_read(&read_fn, msg_descriptors_json.as_mut_slice()).map_err(Error::Recv)?;
+ let descriptor_usizes: Vec<usize> =
+ serde_json::from_slice(msg_descriptors_json.as_slice()).map_err(Error::Json)?;
+
+ // Safe because the usizes are RawDescriptors that were converted to usize in the send
+ // method.
+ descriptor_usizes
+ .iter()
+ .map(|item| *item as RawDescriptor)
+ .collect()
+ } else {
+ Vec::new()
+ };
+
+ let mut msg_descriptors_safe = msg_descriptors
+ .into_iter()
+ .map(|v| {
+ Some(unsafe {
+ // Safe because the socket returns new fds that are owned locally by this scope.
+ SafeDescriptor::from_raw_descriptor(v)
+ })
+ })
+ .collect();
+
+ deserialize_with_descriptors(
+ || serde_json::from_slice(&msg_json),
+ &mut msg_descriptors_safe,
+ )
+ .map_err(Error::Json)
+}
+
+#[derive(PollToken, Eq, PartialEq, Copy, Clone)]
+enum Token {
+ SocketReady,
+}
+
+impl AsRawDescriptor for Tube {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.socket.as_raw_descriptor()
+ }
+}
+
+impl AsRawHandle for Tube {
+ fn as_raw_handle(&self) -> RawHandle {
+ self.as_raw_descriptor()
+ }
+}
+
+impl ReadNotifier for Tube {
+ fn get_read_notifier(&self) -> &dyn AsRawDescriptor {
+ self.socket.get_read_notifier()
+ }
+}
+
+impl CloseNotifier for Tube {
+ fn get_close_notifier(&self) -> &dyn AsRawDescriptor {
+ self.socket.get_close_notifier()
+ }
+}
+
+impl AsRawHandle for SendTube {
+ fn as_raw_handle(&self) -> RawHandle {
+ self.0.as_raw_descriptor()
+ }
+}
+
+impl AsRawHandle for RecvTube {
+ fn as_raw_handle(&self) -> RawHandle {
+ self.0.as_raw_descriptor()
+ }
+}
+
+/// A request to duplicate a handle to a target process.
+#[derive(Serialize, Deserialize, Debug)]
+pub struct DuplicateHandleRequest {
+ pub target_alias_pid: u32,
+ pub handle: usize,
+}
+
+/// Contains a duplicated handle or None if an error occurred.
+#[derive(Serialize, Deserialize, Debug)]
+pub struct DuplicateHandleResponse {
+ pub handle: Option<usize>,
+}
+
+/// Wrapper for tube which is used to delegate DuplicateHandle function calls to
+/// the broker process.
+#[derive(Serialize, Deserialize, Debug)]
+pub struct DuplicateHandleTube(Tube);
+
+impl DuplicateHandleTube {
+ pub fn new(tube: Tube) -> Self {
+ Self(tube)
+ }
+
+ pub fn request_duplicate_handle(
+ &self,
+ target_alias_pid: u32,
+ handle: RawHandle,
+ ) -> Result<RawHandle> {
+ let req = DuplicateHandleRequest {
+ target_alias_pid,
+ handle: handle as usize,
+ };
+ self.0.send(&req)?;
+ let res: DuplicateHandleResponse = self.0.recv()?;
+ res.handle
+ .map(|h| h as RawHandle)
+ .ok_or(Error::BrokerDupDescriptor)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{EventContext, EventTrigger, PollToken, ReadNotifier};
+ use std::time;
+
+ const EVENT_WAIT_TIME: time::Duration = time::Duration::from_secs(10);
+
+ #[derive(PollToken, Debug, Eq, PartialEq, Copy, Clone)]
+ enum Token {
+ ReceivedData,
+ }
+
+ #[test]
+ fn test_serialize_tube() {
+ let (tube_1, tube_2) = Tube::pair().unwrap();
+ let event_ctx: EventContext<Token> = EventContext::build_with(&[EventTrigger::from(
+ tube_2.get_read_notifier(),
+ Token::ReceivedData,
+ )])
+ .unwrap();
+
+ // Serialize the Tube
+ let msg_serialize = SerializeDescriptors::new(&tube_1);
+ let serialized = serde_json::to_vec(&msg_serialize).unwrap();
+ let msg_descriptors = msg_serialize.into_descriptors();
+
+ // Deserialize the Tube
+ let mut msg_descriptors_safe = msg_descriptors
+ .into_iter()
+ .map(|v| Some(unsafe { SafeDescriptor::from_raw_descriptor(v) }))
+ .collect();
+ let tube_deserialized: Tube = deserialize_with_descriptors(
+ || serde_json::from_slice(&serialized),
+ &mut msg_descriptors_safe,
+ )
+ .unwrap();
+
+ // Send a message through deserialized Tube
+ tube_deserialized.send(&"hi".to_string()).unwrap();
+
+ assert_eq!(event_ctx.wait_timeout(EVENT_WAIT_TIME).unwrap().len(), 1);
+ assert_eq!(tube_2.recv::<String>().unwrap(), "hi");
+ }
+}
diff --git a/base/src/windows/win/console.rs b/base/src/windows/win/console.rs
new file mode 100644
index 000000000..7957c5d0f
--- /dev/null
+++ b/base/src/windows/win/console.rs
@@ -0,0 +1,35 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::descriptor::AsRawDescriptor;
+use std::io::{stdin, Error, Read, Result};
+use winapi::{
+ shared::{minwindef::LPVOID, ntdef::NULL},
+ um::{fileapi::ReadFile, minwinbase::LPOVERLAPPED},
+};
+
+pub struct Console;
+
+impl Read for Console {
+ fn read(&mut self, out: &mut [u8]) -> Result<usize> {
+ let mut num_of_bytes_read: u32 = 0;
+ // Safe because `out` is guarenteed to be a valid mutable array
+ // and `num_of_bytes_read` is a valid u32.
+ let res = unsafe {
+ ReadFile(
+ stdin().as_raw_descriptor(),
+ out.as_mut_ptr() as LPVOID,
+ out.len() as u32,
+ &mut num_of_bytes_read,
+ NULL as LPOVERLAPPED,
+ )
+ };
+ let error = Error::last_os_error();
+ if res == 0 {
+ Err(error)
+ } else {
+ Ok(num_of_bytes_read as usize)
+ }
+ }
+}
diff --git a/base/src/windows/win/event.rs b/base/src/windows/win/event.rs
new file mode 100644
index 000000000..c7500007a
--- /dev/null
+++ b/base/src/windows/win/event.rs
@@ -0,0 +1,309 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::generate_scoped_event;
+use serde::{Deserialize, Serialize};
+use std::{
+ ffi::CString,
+ mem,
+ mem::MaybeUninit,
+ ops::Deref,
+ os::windows::io::{AsRawHandle, RawHandle},
+ ptr,
+ ptr::null,
+ time::Duration,
+};
+use win_util::{SecurityAttributes, SelfRelativeSecurityDescriptor};
+use winapi::{
+ shared::{
+ minwindef::{DWORD, FALSE, TRUE},
+ winerror::WAIT_TIMEOUT,
+ },
+ um::{
+ handleapi::DuplicateHandle,
+ processthreadsapi::GetCurrentProcess,
+ synchapi::{CreateEventA, OpenEventA, ResetEvent, SetEvent, WaitForSingleObject},
+ winbase::WAIT_FAILED,
+ winnt::{DUPLICATE_SAME_ACCESS, EVENT_MODIFY_STATE, HANDLE},
+ },
+};
+
+use super::{errno_result, Error, RawDescriptor, Result};
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor};
+
+/// A safe wrapper around Windows synchapi methods used to mimic Linux eventfd (man 2 eventfd).
+/// Since the eventfd isn't using "EFD_SEMAPHORE", we don't need to keep count so we can just use
+/// events.
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct Event {
+ event_handle: SafeDescriptor,
+}
+
+/// Wrapper around the return value of doing a read on an EventFd which distinguishes between
+/// getting a valid count of the number of times the eventfd has been written to and timing out
+/// waiting for the count to be non-zero.
+#[derive(Debug, PartialEq, Eq)]
+pub enum EventReadResult {
+ Count(u64),
+ Timeout,
+}
+
+impl Event {
+ pub fn new_with_manual_reset(manual_reset: bool) -> Result<Event> {
+ let handle = unsafe {
+ CreateEventA(
+ SecurityAttributes::new_with_security_descriptor(
+ SelfRelativeSecurityDescriptor::get_singleton(),
+ /* inherit= */ false,
+ )
+ .as_mut(),
+ if manual_reset { TRUE } else { FALSE },
+ FALSE, // initial state = unsignalled
+ null(),
+ )
+ };
+ if handle.is_null() {
+ return errno_result();
+ }
+ Ok(Event {
+ event_handle: unsafe { SafeDescriptor::from_raw_descriptor(handle) },
+ })
+ }
+
+ pub fn create_event_with_name(name: &str) -> Result<Event> {
+ let event_str = CString::new(String::from(name)).unwrap();
+ let handle = unsafe {
+ CreateEventA(
+ SecurityAttributes::new_with_security_descriptor(
+ SelfRelativeSecurityDescriptor::get_singleton(),
+ /* inherit= */ false,
+ )
+ .as_mut(),
+ FALSE, // manual_reset = false
+ FALSE, // initial state = unsignalled
+ event_str.as_ptr(),
+ )
+ };
+ if handle.is_null() {
+ return errno_result();
+ }
+ Ok(Event {
+ event_handle: unsafe { SafeDescriptor::from_raw_descriptor(handle) },
+ })
+ }
+
+ pub fn new() -> Result<Event> {
+ // Require manual reset
+ Event::new_with_manual_reset(true)
+ }
+
+ pub fn open(name: &str) -> Result<Event> {
+ let event_str = CString::new(String::from(name)).unwrap();
+ let handle = unsafe { OpenEventA(EVENT_MODIFY_STATE, FALSE, event_str.as_ptr()) };
+ if handle.is_null() {
+ return errno_result();
+ }
+ Ok(Event {
+ event_handle: unsafe { SafeDescriptor::from_raw_descriptor(handle) },
+ })
+ }
+
+ pub fn new_auto_reset() -> Result<Event> {
+ Event::new_with_manual_reset(false)
+ }
+
+ pub fn write(&self, _v: u64) -> Result<()> {
+ let event_result = unsafe { SetEvent(self.event_handle.as_raw_descriptor()) };
+ if event_result == 0 {
+ return errno_result();
+ }
+ Ok(())
+ }
+
+ pub fn read(&self) -> Result<u64> {
+ let read_result = self.read_timeout(Duration::new(std::i64::MAX as u64, 0));
+ match read_result {
+ Ok(EventReadResult::Count(c)) => Ok(c),
+ Ok(EventReadResult::Timeout) => Err(Error::new(WAIT_TIMEOUT)),
+ Err(e) => Err(e),
+ }
+ }
+
+ pub fn reset(&self) -> Result<()> {
+ let res = unsafe { ResetEvent(self.event_handle.as_raw_descriptor()) };
+ if res == 0 {
+ errno_result()
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Blocks for a maximum of `timeout` duration until the the event is signaled. If
+ /// a timeout does not occur then the count is returned as a EventReadResult::Count(1),
+ /// and the event resets. If a timeout does occur then this function will return
+ /// EventReadResult::Timeout.
+ pub fn read_timeout(&self, timeout: Duration) -> Result<EventReadResult> {
+ let wait_result = unsafe {
+ WaitForSingleObject(
+ self.event_handle.as_raw_descriptor(),
+ timeout.as_millis() as DWORD,
+ )
+ };
+
+ // We are using an infinite timeout so we can ignore WAIT_ABANDONED
+ match wait_result {
+ WAIT_FAILED => errno_result(),
+ WAIT_TIMEOUT => Ok(EventReadResult::Timeout),
+ _ => {
+ // Safe because self manages the handle and we know it was valid as it
+ // was just successfully waited upon. It is safe to reset a non manual reset event as well.
+ match unsafe { ResetEvent(self.event_handle.as_raw_descriptor()) } {
+ 0 => errno_result(),
+ _ => Ok(EventReadResult::Count(1)),
+ }
+ }
+ }
+ }
+
+ pub fn try_clone(&self) -> Result<Event> {
+ let mut event_clone: HANDLE = MaybeUninit::uninit().as_mut_ptr();
+ let duplicate_result = unsafe {
+ DuplicateHandle(
+ GetCurrentProcess(),
+ self.event_handle.as_raw_descriptor(),
+ GetCurrentProcess(),
+ &mut event_clone,
+ 0,
+ 0,
+ DUPLICATE_SAME_ACCESS,
+ )
+ };
+ if duplicate_result == 0 {
+ return errno_result();
+ }
+ Ok(unsafe { Event::from_raw_descriptor(event_clone) })
+ }
+}
+
+impl AsRawDescriptor for Event {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.event_handle.as_raw_descriptor()
+ }
+}
+
+impl FromRawDescriptor for Event {
+ unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Self {
+ Event {
+ event_handle: SafeDescriptor::from_raw_descriptor(descriptor),
+ }
+ }
+}
+
+impl AsRawHandle for Event {
+ fn as_raw_handle(&self) -> RawHandle {
+ self.as_raw_descriptor()
+ }
+}
+
+impl IntoRawDescriptor for Event {
+ fn into_raw_descriptor(self) -> RawDescriptor {
+ self.event_handle.into_raw_descriptor()
+ }
+}
+
+// Event is safe for send & Sync despite containing a raw handle to its
+// file mapping object. As long as the instance to Event stays alive, this
+// pointer will be a valid handle.
+unsafe impl Send for Event {}
+unsafe impl Sync for Event {}
+
+generate_scoped_event!(Event);
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use winapi::{
+ shared::winerror::WAIT_TIMEOUT,
+ um::winbase::{INFINITE, WAIT_OBJECT_0},
+ };
+
+ #[test]
+ fn new() {
+ Event::new().unwrap();
+ }
+
+ #[test]
+ fn read_write() {
+ let evt = Event::new().unwrap();
+ evt.write(55).unwrap();
+ assert_eq!(evt.read(), Ok(1));
+ }
+
+ #[test]
+ fn read_write_auto_reset() {
+ let evt = Event::new_auto_reset().unwrap();
+ evt.write(55).unwrap();
+
+ // Wait for the notification.
+ let result = unsafe { WaitForSingleObject(evt.as_raw_descriptor(), INFINITE) };
+ assert_eq!(result, WAIT_OBJECT_0);
+
+ // The notification should have reset since we already received it.
+ let result = unsafe { WaitForSingleObject(evt.as_raw_descriptor(), 0) };
+ assert_eq!(result, WAIT_TIMEOUT);
+ }
+
+ #[test]
+ fn read_write_notifies_until_read() {
+ let evt = Event::new().unwrap();
+ evt.write(55).unwrap();
+
+ // Wait for the notification.
+ let result = unsafe { WaitForSingleObject(evt.as_raw_descriptor(), INFINITE) };
+ assert_eq!(result, WAIT_OBJECT_0);
+
+ // The notification should still be active because read wasn't called.
+ let result = unsafe { WaitForSingleObject(evt.as_raw_descriptor(), 0) };
+ assert_eq!(result, WAIT_OBJECT_0);
+
+ // Read and ensure the notification has cleared.
+ evt.read().expect("Failed to read event.");
+ let result = unsafe { WaitForSingleObject(evt.as_raw_descriptor(), 0) };
+ assert_eq!(result, WAIT_TIMEOUT);
+ }
+
+ #[test]
+ fn clone() {
+ let evt = Event::new().unwrap();
+ let evt_clone = evt.try_clone().unwrap();
+ evt.write(923).unwrap();
+ assert_eq!(evt_clone.read(), Ok(1));
+ }
+
+ #[test]
+ fn timeout() {
+ let evt = Event::new().expect("failed to create event");
+ assert_eq!(
+ evt.read_timeout(Duration::from_millis(1))
+ .expect("failed to read from event with timeout"),
+ EventReadResult::Timeout
+ );
+ }
+
+ #[test]
+ fn scoped_event() {
+ let scoped_evt = ScopedEvent::new().unwrap();
+ let evt_clone: Event = scoped_evt.try_clone().unwrap();
+ drop(scoped_evt);
+ assert_eq!(evt_clone.read(), Ok(1));
+ }
+
+ #[test]
+ fn eventfd_from_scoped_event() {
+ let scoped_evt = ScopedEvent::new().unwrap();
+ let evt: Event = scoped_evt.into();
+ evt.write(1).unwrap();
+ }
+}
diff --git a/base/src/windows/win/file_traits.rs b/base/src/windows/win/file_traits.rs
new file mode 100644
index 000000000..3bc635067
--- /dev/null
+++ b/base/src/windows/win/file_traits.rs
@@ -0,0 +1,224 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::super::{FileAllocate, WriteZeroesAt};
+use std::{fs::File, io};
+
+#[macro_export]
+macro_rules! volatile_impl {
+ ($ty:ty) => {
+ // Unused imports are "unneeded" because the only usage of this macro already has those
+ // names in scope.
+ #[allow(unused_imports)]
+ use crate::AsRawDescriptor as _;
+ #[allow(unused_imports)]
+ use std::convert::TryInto as _;
+ #[allow(unused_imports)]
+ use std::io::Seek as _;
+ impl FileReadWriteVolatile for $ty {
+ fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
+ // Safe because only bytes inside the slice are accessed and the kernel is expected
+ // to handle arbitrary memory for I/O.
+ let mut bytes = 0;
+ let ret = unsafe {
+ winapi::um::fileapi::ReadFile(
+ self.as_raw_descriptor(),
+ slice.as_ptr() as *mut libc::c_void,
+ slice.size().try_into().unwrap(),
+ &mut bytes,
+ std::ptr::null_mut(),
+ )
+ };
+
+ if ret > 0 {
+ Ok(bytes as usize)
+ } else {
+ Err(Error::last_os_error())
+ }
+ }
+
+ fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
+ if bufs.is_empty() {
+ return Ok(0);
+ }
+
+ // Windows has ReadFileScatter, but that requires the buffers to all be the same
+ // size and aligned to a page size boundary.
+ // readv makes some guarantees that we can't guarantee in windows, like atomicity.
+ let mut ret = 0usize;
+ for buf in bufs.iter() {
+ match self.read_volatile(*buf) {
+ Ok(bytes) => ret += bytes,
+ Err(_) => return Err(Error::last_os_error()),
+ }
+ }
+
+ Ok(ret)
+ }
+
+ fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
+ // Safe because only bytes inside the slice are accessed and the kernel is expected
+ // to handle arbitrary memory for I/O.
+ let mut bytes = 0;
+ let ret = unsafe {
+ winapi::um::fileapi::WriteFile(
+ self.as_raw_descriptor(),
+ slice.as_ptr() as *mut libc::c_void,
+ slice.size().try_into().unwrap(),
+ &mut bytes,
+ std::ptr::null_mut(),
+ )
+ };
+
+ if ret > 0 {
+ Ok(bytes as usize)
+ } else {
+ Err(Error::last_os_error())
+ }
+ }
+
+ fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
+ if bufs.is_empty() {
+ return Ok(0);
+ }
+
+ // Windows has WriteFileGather, but that requires the buffers to all be the same
+ // size and aligned to a page size boundary, and only writes one page of data
+ // from each buffer.
+ // writev makes some guarantees that we can't guarantee in windows, like atomicity.
+ let mut ret = 0usize;
+ for buf in bufs.iter() {
+ match self.write_volatile(*buf) {
+ Ok(bytes) => ret += bytes,
+ Err(_) => return Err(Error::last_os_error()),
+ }
+ }
+
+ Ok(ret)
+ }
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! volatile_at_impl {
+ ($ty:ty) => {
+ impl FileReadWriteAtVolatile for $ty {
+ fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
+ // The unix implementation uses pread, which doesn't modify the file
+ // pointer. Windows doesn't have an option for that, unfortunately.
+
+ // Safe because only bytes inside the slice are accessed and the kernel is expected
+ // to handle arbitrary memory for I/O.
+ let mut bytes = 0;
+
+ let ret = unsafe {
+ let mut overlapped: winapi::um::minwinbase::OVERLAPPED = std::mem::zeroed();
+ overlapped.u.s_mut().Offset = offset as u32;
+ overlapped.u.s_mut().OffsetHigh = (offset >> 32) as u32;
+
+ winapi::um::fileapi::ReadFile(
+ self.as_raw_descriptor(),
+ slice.as_ptr() as *mut libc::c_void,
+ slice.size().try_into().unwrap(),
+ &mut bytes,
+ &mut overlapped,
+ )
+ };
+
+ if ret > 0 {
+ Ok(bytes as usize)
+ } else {
+ Err(Error::last_os_error())
+ }
+ }
+
+ fn read_vectored_at_volatile(
+ &mut self,
+ bufs: &[VolatileSlice],
+ offset: u64,
+ ) -> Result<usize> {
+ if bufs.is_empty() {
+ return Ok(0);
+ }
+
+ // Windows has ReadFileScatter, but that requires the buffers to all be the same
+ // size and aligned to a page size boundary.
+ // preadv makes some guarantees that we can't guarantee in windows, like atomicity.
+ let mut ret: usize = 0;
+ for buf in bufs.iter() {
+ match self.read_at_volatile(*buf, offset + ret as u64) {
+ Ok(bytes) => ret += bytes,
+ Err(_) => return Err(Error::last_os_error()),
+ }
+ }
+
+ Ok(ret)
+ }
+
+ fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
+ // The unix implementation uses pwrite, which doesn't modify the file
+ // pointer. Windows doesn't have an option for that, unfortunately.
+
+ // Safe because only bytes inside the slice are accessed and the kernel is expected
+ // to handle arbitrary memory for I/O.
+ let mut bytes = 0;
+
+ let ret = unsafe {
+ let mut overlapped: winapi::um::minwinbase::OVERLAPPED = std::mem::zeroed();
+ overlapped.u.s_mut().Offset = offset as u32;
+ overlapped.u.s_mut().OffsetHigh = (offset >> 32) as u32;
+
+ winapi::um::fileapi::WriteFile(
+ self.as_raw_descriptor(),
+ slice.as_ptr() as *mut libc::c_void,
+ slice.size().try_into().unwrap(),
+ &mut bytes,
+ &mut overlapped,
+ )
+ };
+
+ if ret > 0 {
+ Ok(bytes as usize)
+ } else {
+ Err(Error::last_os_error())
+ }
+ }
+
+ fn write_vectored_at_volatile(
+ &mut self,
+ bufs: &[VolatileSlice],
+ offset: u64,
+ ) -> Result<usize> {
+ if bufs.is_empty() {
+ return Ok(0);
+ }
+
+ // Windows has WriteFileGather, but that requires the buffers to all be the same
+ // size and aligned to a page size boundary, and only writes one page of data
+ // from each buffer.
+ // pwritev makes some guarantees that we can't guarantee in windows, like atomicity.
+ let mut ret: usize = 0;
+ for buf in bufs.iter() {
+ match self.write_at_volatile(*buf, offset + ret as u64) {
+ Ok(bytes) => ret += bytes,
+ Err(_) => return Err(Error::last_os_error()),
+ }
+ }
+
+ Ok(ret as usize)
+ }
+ }
+ };
+}
+
+impl FileAllocate for File {
+ fn allocate(&mut self, offset: u64, len: u64) -> io::Result<()> {
+ // The Windows equivalent of fallocate's default mode (allocate a zeroed block of space in a
+ // file) is to just call write_zeros_at. There are not, at the time of writing, any syscalls
+ // that will extend the file and zero the range while maintaining the disk allocation in a
+ // more efficient manner.
+ self.write_zeroes_all_at(offset, len as usize)
+ }
+}
diff --git a/base/src/windows/win/file_util.rs b/base/src/windows/win/file_util.rs
new file mode 100644
index 000000000..eed42bb24
--- /dev/null
+++ b/base/src/windows/win/file_util.rs
@@ -0,0 +1,27 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::descriptor::AsRawDescriptor;
+use std::{ffi::c_void, io};
+pub use winapi::um::winioctl::FSCTL_SET_SPARSE;
+
+/// Marks the given file as sparse. Required if we want hole punching to be performant.
+/// (If a file is not marked as sparse, a hole punch will just write zeros.)
+/// # Safety
+/// handle *must* be File. We accept all AsRawDescriptors for convenience.
+pub fn set_sparse_file<T: AsRawDescriptor>(handle: &T) -> io::Result<()> {
+ // Safe because we check the return value and handle is guaranteed to be a
+ // valid file handle by the caller.
+ let result = unsafe {
+ super::super::ioctl::ioctl_with_ptr(
+ handle,
+ FSCTL_SET_SPARSE,
+ std::ptr::null_mut() as *mut c_void,
+ )
+ };
+ if result != 0 {
+ return Err(io::Error::from_raw_os_error(result));
+ }
+ Ok(())
+}
diff --git a/base/src/windows/win/get_filesystem_type.rs b/base/src/windows/win/get_filesystem_type.rs
new file mode 100644
index 000000000..0aec7a4fd
--- /dev/null
+++ b/base/src/windows/win/get_filesystem_type.rs
@@ -0,0 +1,11 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::Result;
+use std::fs::File;
+
+pub fn get_filesystem_type(_file: &File) -> Result<i64> {
+ // TODO (b/203574110): create a windows equivalent to get the filesystem type like fstatfs
+ Ok(0)
+}
diff --git a/base/src/windows/win/ioctl.rs b/base/src/windows/win/ioctl.rs
new file mode 100644
index 000000000..5c04f565c
--- /dev/null
+++ b/base/src/windows/win/ioctl.rs
@@ -0,0 +1,476 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Macros and wrapper functions for dealing with ioctls.
+
+use std::{mem::size_of, os::raw::*, ptr::null_mut};
+
+use crate::descriptor::AsRawDescriptor;
+pub use winapi::um::winioctl::{CTL_CODE, FILE_ANY_ACCESS, METHOD_BUFFERED};
+use winapi::um::{errhandlingapi::GetLastError, ioapiset::DeviceIoControl};
+
+/// Raw macro to declare the expression that calculates an ioctl number
+#[macro_export]
+macro_rules! device_io_control_expr {
+ // TODO (colindr) b/144440409: right now GVM is our only DeviceIOControl
+ // target on windows, and it only uses METHOD_BUFFERED for the transfer
+ // type and FILE_ANY_ACCESS for the required access, so we're going to
+ // just use that for now. However, we may need to support more
+ // options later.
+ ($dtype:expr, $code:expr) => {
+ $crate::platform::CTL_CODE(
+ $dtype,
+ $code,
+ $crate::platform::METHOD_BUFFERED,
+ $crate::platform::FILE_ANY_ACCESS,
+ ) as ::std::os::raw::c_ulong
+ };
+}
+
+/// Raw macro to declare a function that returns an DeviceIOControl code.
+#[macro_export]
+macro_rules! ioctl_ioc_nr {
+ ($name:ident, $dtype:expr, $code:expr) => {
+ #[allow(non_snake_case)]
+ pub fn $name() -> ::std::os::raw::c_ulong {
+ $crate::device_io_control_expr!($dtype, $code)
+ }
+ };
+ ($name:ident, $dtype:expr, $code:expr, $($v:ident),+) => {
+ #[allow(non_snake_case)]
+ pub fn $name($($v: ::std::os::raw::c_uint),+) -> ::std::os::raw::c_ulong {
+ $crate::device_io_control_expr!($dtype, $code)
+ }
+ };
+}
+
+/// Declare an ioctl that transfers no data.
+#[macro_export]
+macro_rules! ioctl_io_nr {
+ ($name:ident, $ty:expr, $nr:expr) => {
+ $crate::ioctl_ioc_nr!($name, $ty, $nr);
+ };
+ ($name:ident, $ty:expr, $nr:expr, $($v:ident),+) => {
+ $crate::ioctl_ioc_nr!($name, $ty, $nr, $($v),+);
+ };
+}
+
+/// Declare an ioctl that reads data.
+#[macro_export]
+macro_rules! ioctl_ior_nr {
+ ($name:ident, $ty:expr, $nr:expr, $size:ty) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $ty,
+ $nr
+ );
+ };
+ ($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $ty,
+ $nr,
+ $($v),+
+ );
+ };
+}
+
+/// Declare an ioctl that writes data.
+#[macro_export]
+macro_rules! ioctl_iow_nr {
+ ($name:ident, $ty:expr, $nr:expr, $size:ty) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $ty,
+ $nr
+ );
+ };
+ ($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $ty,
+ $nr,
+ $($v),+
+ );
+ };
+}
+
+/// Declare an ioctl that reads and writes data.
+#[macro_export]
+macro_rules! ioctl_iowr_nr {
+ ($name:ident, $ty:expr, $nr:expr, $size:ty) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $ty,
+ $nr
+ );
+ };
+ ($name:ident, $ty:expr, $nr:expr, $size:ty, $($v:ident),+) => {
+ $crate::ioctl_ioc_nr!(
+ $name,
+ $ty,
+ $nr,
+ $($v),+
+ );
+ };
+}
+
+pub type IoctlNr = c_ulong;
+
+/// Run an ioctl with no arguments.
+// (colindr) b/144457461 : This will probably not be used on windows.
+// It's only used on linux for the ioctls that override the exit code to
+// be the return value of the ioctl. As far as I can tell, no DeviceIoControl
+// will do this, they will always instead return values in the output
+// buffer. So, as a result, we have no tests for this function, and
+// we may want to remove it if we never use it on windows, but we can't
+// remove it right now until we re-implement all the code that calls
+// this funciton for windows.
+/// # Safety
+/// This method should be safe as `DeviceIoControl` will handle error cases
+/// and it does size checking.
+pub unsafe fn ioctl<F: AsRawDescriptor>(handle: &F, nr: IoctlNr) -> c_int {
+ let mut byte_ret: c_ulong = 0;
+ let ret = DeviceIoControl(
+ handle.as_raw_descriptor(),
+ nr,
+ null_mut(),
+ 0,
+ null_mut(),
+ 0,
+ &mut byte_ret,
+ null_mut(),
+ );
+
+ if ret == 1 {
+ return 0;
+ }
+
+ GetLastError() as i32
+}
+
+/// Run an ioctl with a single value argument
+/// # Safety
+/// This method should be safe as `DeviceIoControl` will handle error cases
+/// and it does size checking.
+pub unsafe fn ioctl_with_val(handle: &dyn AsRawDescriptor, nr: IoctlNr, mut arg: c_ulong) -> c_int {
+ let mut byte_ret: c_ulong = 0;
+
+ let ret = DeviceIoControl(
+ handle.as_raw_descriptor(),
+ nr,
+ &mut arg as *mut c_ulong as *mut c_void,
+ size_of::<c_ulong>() as u32,
+ null_mut(),
+ 0,
+ &mut byte_ret,
+ null_mut(),
+ );
+
+ if ret == 1 {
+ return 0;
+ }
+
+ GetLastError() as i32
+}
+
+/// Run an ioctl with an immutable reference.
+/// # Safety
+/// Look at `ioctl_with_ptr` comments.
+pub unsafe fn ioctl_with_ref<T>(handle: &dyn AsRawDescriptor, nr: IoctlNr, arg: &T) -> c_int {
+ ioctl_with_ptr(handle, nr, arg)
+}
+
+/// Run an ioctl with a mutable reference.
+/// # Safety
+/// Look at `ioctl_with_ptr` comments.
+pub unsafe fn ioctl_with_mut_ref<T>(
+ handle: &dyn AsRawDescriptor,
+ nr: IoctlNr,
+ arg: &mut T,
+) -> c_int {
+ ioctl_with_mut_ptr(handle, nr, arg)
+}
+
+/// Run an ioctl with a raw pointer, specifying the size of the buffer.
+/// # Safety
+/// This method should be safe as `DeviceIoControl` will handle error cases
+/// and it does size checking. Also The caller should make sure `T` is valid.
+pub unsafe fn ioctl_with_ptr_sized<T>(
+ handle: &dyn AsRawDescriptor,
+ nr: IoctlNr,
+ arg: *const T,
+ size: usize,
+) -> c_int {
+ let mut byte_ret: c_ulong = 0;
+
+ // We are trusting the DeviceIoControl function to not write anything
+ // to the input buffer. Just because it's a *const does not prevent
+ // the unsafe call from writing to it.
+ let ret = DeviceIoControl(
+ handle.as_raw_descriptor(),
+ nr,
+ arg as *mut c_void,
+ size as u32,
+ // We pass a null_mut as the output buffer. If you expect
+ // an output, you should be calling the mut variant of this
+ // function.
+ null_mut(),
+ 0,
+ &mut byte_ret,
+ null_mut(),
+ );
+
+ if ret == 1 {
+ return 0;
+ }
+
+ GetLastError() as i32
+}
+
+/// Run an ioctl with a raw pointer.
+/// # Safety
+/// This method should be safe as `DeviceIoControl` will handle error cases
+/// and it does size checking. Also The caller should make sure `T` is valid.
+pub unsafe fn ioctl_with_ptr<T>(handle: &dyn AsRawDescriptor, nr: IoctlNr, arg: *const T) -> c_int {
+ ioctl_with_ptr_sized(handle, nr, arg, size_of::<T>())
+}
+
+/// Run an ioctl with a mutable raw pointer.
+/// # Safety
+/// This method should be safe as `DeviceIoControl` will handle error cases
+/// and it does size checking. Also The caller should make sure `T` is valid.
+pub unsafe fn ioctl_with_mut_ptr<T>(
+ handle: &dyn AsRawDescriptor,
+ nr: IoctlNr,
+ arg: *mut T,
+) -> c_int {
+ let mut byte_ret: c_ulong = 0;
+
+ let ret = DeviceIoControl(
+ handle.as_raw_descriptor(),
+ nr,
+ arg as *mut c_void,
+ size_of::<T>() as u32,
+ arg as *mut c_void,
+ size_of::<T>() as u32,
+ &mut byte_ret,
+ null_mut(),
+ );
+
+ if ret == 1 {
+ return 0;
+ }
+
+ GetLastError() as i32
+}
+
+/// Run a DeviceIoControl, specifying all options, only available on windows
+/// # Safety
+/// This method should be safe as `DeviceIoControl` will handle error cases
+/// for invalid paramters and takes input buffer and output buffer size
+/// arguments. Also The caller should make sure `T` is valid.
+pub unsafe fn device_io_control<F: AsRawDescriptor, T, T2>(
+ handle: &F,
+ nr: IoctlNr,
+ input: &T,
+ inputsize: u32,
+ output: &mut T2,
+ outputsize: u32,
+) -> c_int {
+ let mut byte_ret: c_ulong = 0;
+
+ let ret = DeviceIoControl(
+ handle.as_raw_descriptor(),
+ nr,
+ input as *const T as *mut c_void,
+ inputsize,
+ output as *mut T2 as *mut c_void,
+ outputsize,
+ &mut byte_ret,
+ null_mut(),
+ );
+
+ if ret == 1 {
+ return 0;
+ }
+
+ GetLastError() as i32
+}
+
+#[cfg(test)]
+mod tests {
+
+ use winapi::um::winioctl::{FSCTL_GET_COMPRESSION, FSCTL_SET_COMPRESSION};
+
+ use winapi::um::{
+ fileapi::{CreateFileW, OPEN_EXISTING},
+ winbase::SECURITY_SQOS_PRESENT,
+ winnt::{
+ COMPRESSION_FORMAT_LZNT1, COMPRESSION_FORMAT_NONE, FILE_SHARE_READ, FILE_SHARE_WRITE,
+ GENERIC_READ, GENERIC_WRITE,
+ },
+ };
+
+ use std::{fs::OpenOptions, os::raw::*, ptr::null_mut};
+
+ use std::{ffi::OsStr, fs::File, io::prelude::*, os::windows::ffi::OsStrExt};
+
+ use std::os::windows::prelude::*;
+
+ use tempfile::tempdir;
+
+ // helper func, returns str as Vec<u16>
+ fn to_u16s<S: AsRef<OsStr>>(s: S) -> std::io::Result<Vec<u16>> {
+ Ok(s.as_ref().encode_wide().chain(Some(0)).collect())
+ }
+
+ #[test]
+ fn ioct_get_and_set_compression() {
+ let dir = tempdir().unwrap();
+ let file_path = dir.path().join("test.dat");
+ let file_path = file_path.as_path();
+
+ // compressed = empty short for compressed status to be read into
+ let mut compressed: c_ushort = 0x0000;
+
+ // open our random file and write "foo" in it
+ let mut f = OpenOptions::new()
+ .write(true)
+ .create(true)
+ .open(file_path)
+ .unwrap();
+ f.write_all(b"foo").expect("Failed to write bytes.");
+ f.sync_all().expect("Failed to sync all.");
+
+ // read the compression status
+ let ecode = unsafe {
+ super::super::ioctl::ioctl_with_mut_ref(&f, FSCTL_GET_COMPRESSION, &mut compressed)
+ };
+
+ // shouldn't error
+ assert_eq!(ecode, 0);
+ // should not be compressed by default (not sure if this will be the case on
+ // all machines...)
+ assert_eq!(compressed, COMPRESSION_FORMAT_NONE);
+
+ // Now do a FSCTL_SET_COMPRESSED to set it to COMPRESSION_FORMAT_LZNT1.
+ compressed = COMPRESSION_FORMAT_LZNT1;
+
+ // NOTE: Theoretically I should be able to open this file like so:
+ // let mut f = OpenOptions::new()
+ // .access_mode(GENERIC_WRITE|GENERIC_WRITE)
+ // .share_mode(FILE_SHARE_READ|FILE_SHARE_WRITE)
+ // .open("test.dat").unwrap();
+ //
+ // However, that does not work, and I'm not sure why. Here's where
+ // the underlying std code is doing a CreateFileW:
+ // https://github.com/rust-lang/rust/blob/master/src/libstd/sys/windows/fs.rs#L260
+ // For now I'm just going to leave this test as-is.
+ //
+ let f = unsafe {
+ File::from_raw_handle(CreateFileW(
+ to_u16s(file_path).unwrap().as_ptr(),
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ null_mut(),
+ OPEN_EXISTING,
+ // I read there's some security concerns if you don't use this
+ SECURITY_SQOS_PRESENT,
+ null_mut(),
+ ))
+ };
+
+ let ecode =
+ unsafe { super::super::ioctl::ioctl_with_ref(&f, FSCTL_SET_COMPRESSION, &compressed) };
+
+ assert_eq!(ecode, 0);
+ // set compressed short back to 0 for reading purposes,
+ // otherwise we can't be sure we're the FSCTL_GET_COMPRESSION
+ // is writing anything to the compressed pointer.
+ compressed = 0;
+
+ let ecode = unsafe {
+ super::super::ioctl::ioctl_with_mut_ref(&f, FSCTL_GET_COMPRESSION, &mut compressed)
+ };
+
+ // now should be compressed
+ assert_eq!(ecode, 0);
+ assert_eq!(compressed, COMPRESSION_FORMAT_LZNT1);
+
+ drop(f);
+ // clean up
+ dir.close().expect("Failed to close the temp directory.");
+ }
+
+ #[test]
+ fn ioctl_with_val() {
+ let dir = tempdir().unwrap();
+ let file_path = dir.path().join("test.dat");
+ let file_path = file_path.as_path();
+
+ // compressed = empty short for compressed status to be read into
+ let mut compressed: c_ushort;
+
+ // open our random file and write "foo" in it
+ let mut f = OpenOptions::new()
+ .write(true)
+ .create(true)
+ .open(file_path)
+ .unwrap();
+ f.write_all(b"foo").expect("Failed to write bytes.");
+ f.sync_all().expect("Failed to sync all.");
+
+ // Now do a FSCTL_SET_COMPRESSED to set it to COMPRESSION_FORMAT_LZNT1.
+ compressed = COMPRESSION_FORMAT_LZNT1;
+
+ // NOTE: Theoretically I should be able to open this file like so:
+ // let mut f = OpenOptions::new()
+ // .access_mode(GENERIC_WRITE|GENERIC_WRITE)
+ // .share_mode(FILE_SHARE_READ|FILE_SHARE_WRITE)
+ // .open("test.dat").unwrap();
+ //
+ // However, that does not work, and I'm not sure why. Here's where
+ // the underlying std code is doing a CreateFileW:
+ // https://github.com/rust-lang/rust/blob/master/src/libstd/sys/windows/fs.rs#L260
+ // For now I'm just going to leave this test as-is.
+ //
+ let f = unsafe {
+ File::from_raw_handle(CreateFileW(
+ to_u16s(file_path).unwrap().as_ptr(),
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ null_mut(),
+ OPEN_EXISTING,
+ // I read there's some security concerns if you don't use this
+ SECURITY_SQOS_PRESENT,
+ null_mut(),
+ ))
+ };
+
+ // now we call ioctl_with_val, which isn't particularly any more helpful than
+ // ioctl_with_ref except for the cases where the input is only a word long
+ let ecode = unsafe {
+ super::super::ioctl::ioctl_with_val(&f, FSCTL_SET_COMPRESSION, compressed.into())
+ };
+
+ assert_eq!(ecode, 0);
+ // set compressed short back to 0 for reading purposes,
+ // otherwise we can't be sure we're the FSCTL_GET_COMPRESSION
+ // is writing anything to the compressed pointer.
+ compressed = 0;
+
+ let ecode = unsafe {
+ super::super::ioctl::ioctl_with_mut_ref(&f, FSCTL_GET_COMPRESSION, &mut compressed)
+ };
+
+ // now should be compressed
+ assert_eq!(ecode, 0);
+ assert_eq!(compressed, COMPRESSION_FORMAT_LZNT1);
+
+ drop(f);
+ // clean up
+ dir.close().expect("Failed to close the temp directory.");
+ }
+}
diff --git a/base/src/windows/win/mmap.rs b/base/src/windows/win/mmap.rs
new file mode 100644
index 000000000..dbd43103d
--- /dev/null
+++ b/base/src/windows/win/mmap.rs
@@ -0,0 +1,412 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! The mmap module provides a safe interface to map memory and ensures UnmapViewOfFile is called when the
+//! mmap object leaves scope.
+
+use std::{
+ io,
+ slice::{from_raw_parts, from_raw_parts_mut},
+};
+
+use libc::{
+ c_uint, c_void, {self},
+};
+
+use win_util::{allocation_granularity, get_high_order, get_low_order};
+use winapi::um::memoryapi::{
+ FlushViewOfFile, MapViewOfFile, MapViewOfFileEx, UnmapViewOfFile, FILE_MAP_READ, FILE_MAP_WRITE,
+};
+
+use super::{super::RawDescriptor, Error, MappedRegion, MemoryMapping, Protection, Result};
+use crate::descriptor::AsRawDescriptor;
+use crate::warn;
+
+pub(super) const PROT_NONE: c_uint = 0;
+pub(super) const PROT_READ: c_uint = FILE_MAP_READ;
+pub(super) const PROT_WRITE: c_uint = FILE_MAP_WRITE;
+
+impl MemoryMapping {
+ /// Creates an anonymous shared mapping of `size` bytes with `prot` protection.
+ ///
+ /// # Arguments
+ /// * `size` - Size of memory region in bytes.
+ /// * `prot` - Protection (e.g. readable/writable) of the memory region.
+ pub fn new_protection(size: usize, prot: Protection) -> Result<MemoryMapping> {
+ // This is safe because we are creating an anonymous mapping in a place not already used by
+ // any other area in this process.
+ unsafe { MemoryMapping::try_mmap(None, size, prot.into(), None) }
+ }
+
+ /// Create a Memory mapping from a raw address returned by
+ /// the kernel.
+ /// # Arguments
+ /// * `addr` - Raw pointer to memory region.
+ /// * `size` - Size of memory region in bytes.
+ pub fn from_raw_ptr(addr: *mut c_void, size: usize) -> Result<MemoryMapping> {
+ Ok(MemoryMapping { addr, size })
+ }
+
+ pub fn from_raw_handle_offset(
+ file_handle: RawDescriptor,
+ size: usize,
+ ) -> Result<MemoryMapping> {
+ // This is safe because we are creating an anonymous mapping in a place not already used by
+ // any other area in this process.
+ unsafe {
+ MemoryMapping::try_mmap(
+ None,
+ size,
+ Protection::read_write().into(),
+ Some((file_handle, 0)),
+ )
+ }
+ }
+
+ /// Maps the `size` bytes starting at `offset` bytes of the given `descriptor` as read/write.
+ ///
+ /// # Arguments
+ /// * `file_handle` - File handle to map from.
+ /// * `size` - Size of memory region in bytes.
+ /// * `offset` - Offset in bytes from the beginning of `descriptor` to start the mmap.
+ /// * `prot` - Protection (e.g. readable/writable) of the memory region.
+ pub fn from_descriptor_offset_protection(
+ file_handle: &dyn AsRawDescriptor,
+ size: usize,
+ offset: u64,
+ prot: Protection,
+ ) -> Result<MemoryMapping> {
+ // This is safe because we are creating an anonymous mapping in a place not already used by
+ // any other area in this process.
+ unsafe {
+ MemoryMapping::try_mmap(
+ None,
+ size,
+ prot.into(),
+ Some((file_handle.as_raw_descriptor(), offset)),
+ )
+ }
+ }
+
+ /// Creates an anonymous shared mapping of `size` bytes with `prot` protection.
+ ///
+ /// # Safety
+ /// Unsafe: unmaps any mmap'd regions already present at (addr..addr+size).
+ ///
+ /// # Arguments
+ /// * `addr` - Memory address to mmap at.
+ /// * `size` - Size of memory region in bytes.
+ /// * `prot` - Protection (e.g. readable/writable) of the memory region.
+ pub unsafe fn new_protection_fixed(
+ addr: *mut u8,
+ size: usize,
+ prot: Protection,
+ ) -> Result<MemoryMapping> {
+ MemoryMapping::try_mmap(Some(addr), size, prot.into(), None)
+ }
+
+ /// Maps the `size` bytes starting at `offset` bytes of the given `descriptor` with
+ /// `prot` protections.
+ ///
+ /// # Safety
+ /// Unsafe: unmaps any mmap'd regions already present at (addr..addr+size).
+ ///
+ /// # Arguments
+ /// * `addr` - Memory address to mmap at.
+ /// * `descriptor` - File descriptor to mmap from.
+ /// * `size` - Size of memory region in bytes.
+ /// * `offset` - Offset in bytes from the beginning of `descriptor` to start the mmap.
+ /// * `prot` - Protection (e.g. readable/writable) of the memory region.
+ pub unsafe fn from_descriptor_offset_protection_fixed(
+ addr: *mut u8,
+ descriptor: &dyn AsRawDescriptor,
+ size: usize,
+ offset: u64,
+ prot: Protection,
+ ) -> Result<MemoryMapping> {
+ MemoryMapping::try_mmap(
+ Some(addr),
+ size,
+ prot.into(),
+ Some((descriptor.as_raw_descriptor(), offset)),
+ )
+ }
+
+ /// Calls MapViewOfFile/MapViewOfFileEx with the parameters given
+ unsafe fn try_mmap(
+ addr: Option<*mut u8>,
+ size: usize,
+ access_flags: c_uint,
+ file_handle: Option<(RawDescriptor, u64)>,
+ ) -> Result<MemoryMapping> {
+ if file_handle.is_none() {
+ // TODO(mikehoyle): For now, do not allow the no-handle scenario. As per comments
+ // in guest_memory, the no-handle case is for legacy support and shouldn't have
+ // significant usage.
+ return Err(Error::InvalidAddress);
+ }
+ let file_handle = file_handle.unwrap();
+
+ // on windows, pages needed to be of fixed granular size, and the
+ // maximum valid value is an i64.
+ if file_handle.1 % allocation_granularity() != 0 || file_handle.1 > i64::max_value() as u64
+ {
+ return Err(Error::InvalidOffset);
+ }
+
+ let created_address = match addr {
+ Some(addr) => MapViewOfFileEx(
+ file_handle.0,
+ access_flags,
+ get_high_order(file_handle.1),
+ get_low_order(file_handle.1),
+ size,
+ addr as *mut c_void,
+ ),
+ None => MapViewOfFile(
+ file_handle.0,
+ access_flags,
+ get_high_order(file_handle.1),
+ get_low_order(file_handle.1),
+ size,
+ ),
+ };
+
+ if created_address.is_null() {
+ return Err(Error::SystemCallFailed(super::super::Error::last()));
+ }
+
+ Ok(MemoryMapping {
+ addr: created_address,
+ size,
+ })
+ }
+
+ /// Calls FlushViewOfFile on the mapped memory range, ensuring all changes that would
+ /// be written to disk are written immediately
+ pub fn msync(&self) -> Result<()> {
+ // Safe because self can only be created as a successful memory mapping
+ unsafe {
+ if FlushViewOfFile(self.addr, self.size) == 0 {
+ return Err(Error::SystemCallFailed(super::super::Error::last()));
+ }
+ };
+ Ok(())
+ }
+
+ /// Reads data from a file descriptor and writes it to guest memory.
+ ///
+ /// # Arguments
+ /// * `mem_offset` - Begin writing memory at this offset.
+ /// * `src` - Read from `src` to memory.
+ /// * `count` - Read `count` bytes from `src` to memory.
+ ///
+ /// # Examples
+ ///
+ /// * Read bytes from /dev/urandom
+ ///
+ /// ```
+ /// use crate::platform::MemoryMapping;
+ /// use crate::platform::SharedMemory;
+ /// use std::fs::File;
+ /// use std::path::Path;
+ /// fn test_read_random() -> Result<u32, ()> {
+ /// let mut mem_map = MemoryMapping::from_descriptor(
+ /// &SharedMemory::anon(1024).unwrap(), 1024).unwrap();
+ /// let mut file = File::open(Path::new("/dev/urandom")).map_err(|_| ())?;
+ /// mem_map.read_to_memory(32, &mut file, 128).map_err(|_| ())?;
+ /// let rand_val: u32 = mem_map.read_obj(40).map_err(|_| ())?;
+ /// Ok(rand_val)
+ /// }
+ /// ```
+ pub fn read_to_memory<F: io::Read>(
+ &self,
+ mem_offset: usize,
+ src: &mut F,
+ count: usize,
+ ) -> Result<()> {
+ self.range_end(mem_offset, count)
+ .map_err(|_| Error::InvalidRange(mem_offset, count, self.size()))?;
+ // Safe because the check above ensures that no memory outside this slice will get accessed
+ // by this read call.
+ let buf: &mut [u8] = unsafe { from_raw_parts_mut(self.as_ptr().add(mem_offset), count) };
+ match src.read_exact(buf) {
+ Err(e) => Err(Error::ReadToMemory(e)),
+ Ok(()) => Ok(()),
+ }
+ }
+
+ /// Writes data from memory to a file descriptor.
+ ///
+ /// # Arguments
+ /// * `mem_offset` - Begin reading memory from this offset.
+ /// * `dst` - Write from memory to `dst`.
+ /// * `count` - Read `count` bytes from memory to `src`.
+ ///
+ /// # Examples
+ ///
+ /// * Write 128 bytes to /dev/null
+ ///
+ /// ```
+ /// use crate::platform::MemoryMapping;
+ /// use crate::platform::SharedMemory;
+ /// use std::fs::File;
+ /// use std::path::Path;
+ /// fn test_write_null() -> Result<(), ()> {
+ /// let mut mem_map = MemoryMapping::from_descriptor(
+ /// &SharedMemory::anon(1024).unwrap(), 1024).unwrap();
+ /// let mut file = File::open(Path::new("/dev/null")).map_err(|_| ())?;
+ /// mem_map.write_from_memory(32, &mut file, 128).map_err(|_| ())?;
+ /// Ok(())
+ /// }
+ /// ```
+ pub fn write_from_memory<F: io::Write>(
+ &self,
+ mem_offset: usize,
+ dst: &mut F,
+ count: usize,
+ ) -> Result<()> {
+ self.range_end(mem_offset, count)
+ .map_err(|_| Error::InvalidRange(mem_offset, count, self.size()))?;
+ // Safe because the check above ensures that no memory outside this slice will get accessed
+ // by this write call.
+ let buf: &[u8] = unsafe { from_raw_parts(self.as_ptr().add(mem_offset), count) };
+ match dst.write_all(buf) {
+ Err(e) => Err(Error::WriteFromMemory(e)),
+ Ok(()) => Ok(()),
+ }
+ }
+}
+
+impl Drop for MemoryMapping {
+ fn drop(&mut self) {
+ // This is safe because we MapViewOfFile the area at addr ourselves, and nobody
+ // else is holding a reference to it.
+ unsafe {
+ if UnmapViewOfFile(self.addr) == 0 {
+ warn!(
+ "Unsuccessful unmap of file: {}",
+ super::super::Error::last()
+ );
+ }
+ }
+ }
+}
+
+/// TODO(b/150415526): This is currently an empty implementation to allow simple usages in
+/// shared structs. Any attempts to use it will result in compiler errors. It will need
+/// to be ported to actually be used on Windows.
+pub struct MemoryMappingArena();
+
+#[cfg(test)]
+mod tests {
+ use super::{
+ super::super::{pagesize, SharedMemory},
+ *,
+ };
+ use crate::descriptor::FromRawDescriptor;
+ use data_model::{VolatileMemory, VolatileMemoryError};
+ use std::ptr;
+ use winapi::shared::winerror;
+
+ #[test]
+ fn basic_map() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, 1024).unwrap();
+ assert_eq!(1024, m.size());
+ }
+
+ #[test]
+ fn map_invalid_fd() {
+ let descriptor = unsafe { std::fs::File::from_raw_descriptor(ptr::null_mut()) };
+ let res = MemoryMapping::from_descriptor(&descriptor, 1024).unwrap_err();
+ if let Error::SystemCallFailed(e) = res {
+ assert_eq!(e.errno(), winerror::ERROR_INVALID_HANDLE as i32);
+ } else {
+ panic!("unexpected error: {}", res);
+ }
+ }
+
+ #[test]
+ fn test_write_past_end() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, 5).unwrap();
+ let res = m.write_slice(&[1, 2, 3, 4, 5, 6], 0);
+ assert!(res.is_ok());
+ assert_eq!(res.unwrap(), 5);
+ }
+
+ #[test]
+ fn slice_size() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, 5).unwrap();
+ let s = m.get_slice(2, 3).unwrap();
+ assert_eq!(s.size(), 3);
+ }
+
+ #[test]
+ fn slice_addr() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, 5).unwrap();
+ let s = m.get_slice(2, 3).unwrap();
+ assert_eq!(s.as_ptr(), unsafe { m.as_ptr().offset(2) });
+ }
+
+ #[test]
+ fn slice_store() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, 5).unwrap();
+ let r = m.get_ref(2).unwrap();
+ r.store(9u16);
+ assert_eq!(m.read_obj::<u16>(2).unwrap(), 9);
+ }
+
+ #[test]
+ fn slice_overflow_error() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, 5).unwrap();
+ let res = m.get_slice(std::usize::MAX, 3).unwrap_err();
+ assert_eq!(
+ res,
+ VolatileMemoryError::Overflow {
+ base: std::usize::MAX,
+ offset: 3,
+ }
+ );
+ }
+ #[test]
+ fn slice_oob_error() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, 5).unwrap();
+ let res = m.get_slice(3, 3).unwrap_err();
+ assert_eq!(res, VolatileMemoryError::OutOfBounds { addr: 6 });
+ }
+
+ #[test]
+ fn from_descriptor_offset_invalid() {
+ let shm = SharedMemory::anon(1028).unwrap();
+ let res = MemoryMapping::from_descriptor_offset(&shm, 4096, (i64::max_value() as u64) + 1)
+ .unwrap_err();
+ match res {
+ Error::InvalidOffset => {}
+ e => panic!("unexpected error: {}", e),
+ }
+ }
+
+ #[test]
+ fn arena_msync() {
+ let size: usize = 0x40000;
+ let shm = SharedMemory::anon(size as u64).unwrap();
+ let m = MemoryMapping::from_descriptor(&shm, size).unwrap();
+ let ps = pagesize();
+ <dyn MappedRegion>::msync(&m, 0, ps).unwrap();
+ <dyn MappedRegion>::msync(&m, 0, size).unwrap();
+ <dyn MappedRegion>::msync(&m, ps, size - ps).unwrap();
+ let res = <dyn MappedRegion>::msync(&m, ps, size).unwrap_err();
+ match res {
+ Error::InvalidAddress => {}
+ e => panic!("unexpected error: {}", e),
+ }
+ }
+}
diff --git a/base/src/windows/win/mod.rs b/base/src/windows/win/mod.rs
new file mode 100644
index 000000000..1fcd891b8
--- /dev/null
+++ b/base/src/windows/win/mod.rs
@@ -0,0 +1,175 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Defines Windows-specific submodules of sys_util
+
+// Modules ported to windows
+pub mod file_traits;
+pub mod syslog;
+
+mod platform_timer_utils;
+pub use platform_timer_utils::*;
+
+mod file_util;
+pub use file_util::*;
+
+use super::{errno_result, pid_t, Error, Result};
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, SafeDescriptor};
+use serde::{Deserialize, Serialize};
+use std::{
+ fs::{File, OpenOptions},
+ path::Path,
+ ptr::null_mut,
+ sync::Once,
+};
+use winapi::{
+ shared::{minwindef::DWORD, winerror::WAIT_TIMEOUT},
+ um::{
+ handleapi::INVALID_HANDLE_VALUE,
+ processthreadsapi::GetCurrentProcessId,
+ synchapi::{CreateMutexA, ReleaseMutex, WaitForSingleObject},
+ winbase::{INFINITE, WAIT_ABANDONED, WAIT_OBJECT_0},
+ winuser::AllowSetForegroundWindow,
+ },
+};
+
+#[inline(always)]
+pub fn pagesize() -> usize {
+ win_util::pagesize()
+}
+
+/// Cross-platform wrapper around getting the current process id.
+#[inline(always)]
+pub fn getpid() -> pid_t {
+ // Safe because we only use the return value. ProcessId can safely be converted from DWORD to i32.
+ unsafe { GetCurrentProcessId() as pid_t }
+}
+
+#[link(name = "stdio_fileno", kind = "static")]
+extern "C" {
+ fn stdout_fileno() -> i32;
+ fn stderr_fileno() -> i32;
+}
+
+static mut STDOUT_FILENO: (Option<i32>, Once) = (None, Once::new());
+static mut STDERR_FILENO: (Option<i32>, Once) = (None, Once::new());
+
+pub fn get_stdout_fileno() -> i32 {
+ // safe because use once to initialize this global structure and never write to it ever since
+ unsafe {
+ STDOUT_FILENO.1.call_once(|| {
+ STDOUT_FILENO.0.replace(stdout_fileno());
+ });
+ STDOUT_FILENO.0.unwrap()
+ }
+}
+
+pub fn get_stderr_fileno() -> i32 {
+ // safe because use once to initialize this global structure and never write to it ever since
+ unsafe {
+ STDERR_FILENO.1.call_once(|| {
+ STDERR_FILENO.0.replace(stderr_fileno());
+ });
+ STDERR_FILENO.0.unwrap()
+ }
+}
+
+/// A Mutex (no data) that works across processes on Windows.
+#[derive(Serialize, Deserialize, Debug)]
+pub struct MultiProcessMutex {
+ lock: SafeDescriptor,
+}
+
+impl MultiProcessMutex {
+ pub fn new() -> Result<Self> {
+ // Trivially safe (no memory passed, error checked).
+ //
+ // Note that we intentionally make this handle uninheritable by default via the mutex attrs.
+ let lock_handle = unsafe {
+ CreateMutexA(
+ /* lpMutexAttributes= */ null_mut(),
+ false as i32,
+ null_mut(),
+ )
+ };
+
+ if lock_handle == INVALID_HANDLE_VALUE {
+ Err(Error::last())
+ } else {
+ Ok(Self {
+ // Safe because the handle is valid & we own it exclusively.
+ lock: unsafe { SafeDescriptor::from_raw_descriptor(lock_handle) },
+ })
+ }
+ }
+
+ /// Locks the mutex, returning a RAII guard similar to std::sync::Mutex.
+ pub fn lock(&self) -> MultiProcessMutexGuard {
+ if let Some(guard) = self.try_lock(INFINITE) {
+ guard
+ } else {
+ // This should *never* happen.
+ panic!("Timed out locking mutex with an infinite timeout. This should never happen.");
+ }
+ }
+
+ /// Tries to lock the mutex, returning a RAII guard similar to std::sync::Mutex if we obtained
+ /// the lock within the timeout.
+ pub fn try_lock(&self, timeout_ms: u32) -> Option<MultiProcessMutexGuard> {
+ // Safe because the mutex handle is guaranteed to exist.
+ match unsafe { WaitForSingleObject(self.lock.as_raw_descriptor(), timeout_ms) } {
+ WAIT_OBJECT_0 => Some(MultiProcessMutexGuard { lock: &self.lock }),
+ WAIT_TIMEOUT => None,
+ WAIT_ABANDONED => panic!(
+ "The thread holding the mutex exited without releasing the mutex.\
+ Protected data may be corrupt."
+ ),
+ _ => {
+ // This should *never* happen.
+ panic!("Failed to lock mutex {:?}", Error::last())
+ }
+ }
+ }
+
+ /// Creates a new reference to the mutex.
+ pub fn try_clone(&self) -> Result<Self> {
+ Ok(Self {
+ lock: self.lock.try_clone()?,
+ })
+ }
+}
+
+/// RAII guard for MultiProcessMutex.
+pub struct MultiProcessMutexGuard<'a> {
+ lock: &'a SafeDescriptor,
+}
+
+impl<'a> Drop for MultiProcessMutexGuard<'a> {
+ fn drop(&mut self) {
+ if unsafe { ReleaseMutex(self.lock.as_raw_descriptor()) } == 0 {
+ panic!("Failed to unlock mutex: {:?}.", Error::last())
+ }
+ }
+}
+
+/// Open the file with the given path.
+///
+/// Note that on POSIX< this wrapper handles opening existing FDs via /proc/self/fd/N. On Windows,
+/// this functionality doesn't exist, but we preserve this seemingly not very useful function to
+/// simplify cross platform code.
+pub fn open_file<P: AsRef<Path>>(path: P, options: &OpenOptions) -> Result<File> {
+ Ok(options.open(path)?)
+}
+
+/// Grants the given process id temporary permission to foreground another window. This succeeds
+/// only when the emulator is in the foreground, and will persist only until the next user
+/// interaction with the window
+pub fn give_foregrounding_permission(process_id: DWORD) -> Result<()> {
+ // Safe because this API does not modify memory, and process_id remains in scope for
+ // the duration of the call.
+ match unsafe { AllowSetForegroundWindow(process_id) } {
+ 0 => errno_result(),
+ _ => Ok(()),
+ }
+}
diff --git a/base/src/windows/win/named_pipes.rs b/base/src/windows/win/named_pipes.rs
new file mode 100644
index 000000000..57bd6796d
--- /dev/null
+++ b/base/src/windows/win/named_pipes.rs
@@ -0,0 +1,1035 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use rand::Rng;
+use std::{
+ ffi::CString,
+ fs::OpenOptions,
+ io,
+ io::Result,
+ mem,
+ os::windows::fs::OpenOptionsExt,
+ process, ptr,
+ sync::atomic::{AtomicUsize, Ordering},
+};
+
+use super::{Event, RawDescriptor};
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor};
+use serde::{Deserialize, Serialize};
+use win_util::{SecurityAttributes, SelfRelativeSecurityDescriptor};
+use winapi::{
+ shared::{
+ minwindef::{DWORD, LPCVOID, LPVOID, TRUE},
+ winerror::{ERROR_IO_PENDING, ERROR_NO_DATA, ERROR_PIPE_CONNECTED},
+ },
+ um::{
+ errhandlingapi::GetLastError,
+ fileapi::{FlushFileBuffers, ReadFile, WriteFile},
+ handleapi::INVALID_HANDLE_VALUE,
+ ioapiset::{CancelIoEx, GetOverlappedResult},
+ minwinbase::OVERLAPPED,
+ namedpipeapi::{
+ ConnectNamedPipe, GetNamedPipeInfo, PeekNamedPipe, SetNamedPipeHandleState,
+ },
+ winbase::{
+ CreateNamedPipeA, FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED,
+ PIPE_ACCESS_DUPLEX, PIPE_NOWAIT, PIPE_READMODE_BYTE, PIPE_READMODE_MESSAGE,
+ PIPE_REJECT_REMOTE_CLIENTS, PIPE_TYPE_BYTE, PIPE_TYPE_MESSAGE, PIPE_WAIT,
+ SECURITY_IDENTIFICATION,
+ },
+ },
+};
+
+/// The default buffer size for all named pipes in the system. If this size is too small, writers
+/// on named pipes that expect not to block *can* block until the reading side empties the buffer.
+///
+/// The general rule is this should be *at least* as big as the largest message, otherwise
+/// unexpected blocking behavior can result; for example, if too small, this can interact badly with
+/// crate::platform::StreamChannel, which expects to be able to make a complete write before releasing
+/// a lock that the opposite side needs to complete a read. This means that if the buffer is too
+/// small:
+/// * The writer can't complete its write and release the lock because the buffer is too small.
+/// * The reader can't start reading because the lock is held by the writer, so it can't
+/// relieve buffer pressure. Note that for message pipes, the reader couldn't do anything
+/// to help anyway, because a message mode pipe should NOT have a partial read (which is
+/// what we would need to relieve pressure).
+/// * Conditions for deadlock are met, and both the reader & writer enter circular waiting.
+pub const DEFAULT_BUFFER_SIZE: usize = 50 * 1024;
+
+static NEXT_PIPE_INDEX: AtomicUsize = AtomicUsize::new(1);
+
+/// Represents one end of a named pipe
+#[derive(Serialize, Deserialize, Debug)]
+pub struct PipeConnection {
+ handle: SafeDescriptor,
+ framing_mode: FramingMode,
+ blocking_mode: BlockingMode,
+}
+
+/// Wraps the OVERLAPPED structure. Also keeps track of whether OVERLAPPED is being used by a
+/// Readfile or WriteFile operation and holds onto the event object so it doesn't get dropped.
+pub struct OverlappedWrapper {
+ // Allocated on the heap so that the OVERLAPPED struct doesn't move when performing I/O
+ // operations.
+ overlapped: Box<OVERLAPPED>,
+ // This field prevents the event handle from being dropped too early and allows callers to
+ // be notified when a read or write overlapped operation has completed.
+ h_event: Option<Event>,
+ in_use: bool,
+}
+
+impl OverlappedWrapper {
+ pub fn get_h_event_ref(&self) -> Option<&Event> {
+ self.h_event.as_ref()
+ }
+}
+
+#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
+pub enum FramingMode {
+ Byte,
+ Message,
+}
+
+impl FramingMode {
+ fn to_readmode(self) -> DWORD {
+ match self {
+ FramingMode::Message => PIPE_READMODE_MESSAGE,
+ FramingMode::Byte => PIPE_READMODE_BYTE,
+ }
+ }
+
+ fn to_pipetype(self) -> DWORD {
+ match self {
+ FramingMode::Message => PIPE_TYPE_MESSAGE,
+ FramingMode::Byte => PIPE_TYPE_BYTE,
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Debug)]
+pub enum BlockingMode {
+ /// Calls to read() block until data is received
+ Wait,
+ /// Calls to read() return immediately even if there is nothing read with error code 232
+ /// (Rust maps this to BrokenPipe but it's actually ERROR_NO_DATA)
+ ///
+ /// NOTE: This mode is discouraged by the Windows API documentation.
+ NoWait,
+}
+
+impl From<&BlockingMode> for DWORD {
+ fn from(blocking_mode: &BlockingMode) -> DWORD {
+ match blocking_mode {
+ BlockingMode::Wait => PIPE_WAIT,
+ BlockingMode::NoWait => PIPE_NOWAIT,
+ }
+ }
+}
+
+/// Sets the handle state for a named pipe in a rust friendly way.
+/// This is safe if the pipe handle is open.
+unsafe fn set_named_pipe_handle_state(
+ pipe_handle: RawDescriptor,
+ client_mode: &mut DWORD,
+) -> Result<()> {
+ // Safe when the pipe handle is open. Safety also requires checking the return value, which we
+ // do below.
+ let success_flag = SetNamedPipeHandleState(
+ /* hNamedPipe= */ pipe_handle,
+ /* lpMode= */ client_mode,
+ /* lpMaxCollectionCount= */ ptr::null_mut(),
+ /* lpCollectDataTimeout= */ ptr::null_mut(),
+ );
+ if success_flag == 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+}
+
+pub fn pair(
+ framing_mode: &FramingMode,
+ blocking_mode: &BlockingMode,
+ timeout: u64,
+) -> Result<(PipeConnection, PipeConnection)> {
+ pair_with_buffer_size(
+ framing_mode,
+ blocking_mode,
+ timeout,
+ DEFAULT_BUFFER_SIZE,
+ false,
+ )
+}
+
+/// Creates a pair of handles connected to either end of a duplex named pipe.
+///
+/// The pipe created will have a semi-random name and a default set of security options that
+/// help prevent common named-pipe based vulnerabilities. Specifically the pipe is set to reject
+/// remote clients, allow only a single server instance, and prevent impersonation by the server
+/// end of the pipe.
+///
+/// # Arguments
+///
+/// * `framing_mode` - Whether the system should provide a simple byte stream (Byte) or an
+/// automatically framed sequence of messages (Message). In message mode it's an
+/// error to read fewer bytes than were sent in a message from the other end of
+/// the pipe.
+/// * `blocking_mode` - Whether the system should wait on read() until data is available (Wait) or
+/// return immediately if there is nothing available (NoWait).
+/// * `timeout` - A timeout to apply for socket operations, in milliseconds.
+/// Setting this to zero will create sockets with the system
+/// default timeout.
+/// * `buffer_size` - The default buffer size for the named pipe. The system should expand the
+/// buffer automatically as needed, except in the case of NOWAIT pipes, where
+/// it will just fail writes that don't fit in the buffer.
+/// # Return value
+///
+/// Returns a pair of pipes, of the form (server, client). Note that for some winapis, such as
+/// FlushFileBuffers, the server & client ends WILL BEHAVE DIFFERENTLY.
+pub fn pair_with_buffer_size(
+ framing_mode: &FramingMode,
+ blocking_mode: &BlockingMode,
+ timeout: u64,
+ buffer_size: usize,
+ overlapped: bool,
+) -> Result<(PipeConnection, PipeConnection)> {
+ // Give the pipe a unique name to avoid accidental collisions
+ let pipe_name = format!(
+ r"\\.\pipe\crosvm_ipc.pid{}.{}.rand{}",
+ process::id(),
+ NEXT_PIPE_INDEX.fetch_add(1, Ordering::SeqCst),
+ rand::thread_rng().gen::<u32>(),
+ );
+
+ let server_end = create_server_pipe(
+ &pipe_name,
+ framing_mode,
+ blocking_mode,
+ timeout,
+ buffer_size,
+ overlapped,
+ )?;
+
+ // Open the named pipe we just created as the client
+ let client_end = create_client_pipe(&pipe_name, framing_mode, blocking_mode, overlapped)?;
+
+ // Accept the client's connection
+ // Not sure if this is strictly needed but I'm doing it just in case.
+ // We expect at this point that the client will already be connected,
+ // so we'll get a return code of 0 and an ERROR_PIPE_CONNECTED.
+ // It's also OK if we get a return code of success.
+ server_end.wait_for_client_connection()?;
+
+ Ok((server_end, client_end))
+}
+
+/// Creates a PipeConnection for the server end of a named pipe with the given path and pipe
+/// settings.
+///
+/// The pipe will be set to reject remote clients and allow only a single connection at a time.
+///
+/// # Arguments
+///
+/// * `pipe_name` - The path of the named pipe to create. Should be in the form
+/// `\\.\pipe\<some-name>`.
+/// * `framing_mode` - Whether the system should provide a simple byte stream (Byte) or an
+/// automatically framed sequence of messages (Message). In message mode it's an
+/// error to read fewer bytes than were sent in a message from the other end of
+/// the pipe.
+/// * `blocking_mode` - Whether the system should wait on read() until data is available (Wait) or
+/// return immediately if there is nothing available (NoWait).
+/// * `timeout` - A timeout to apply for socket operations, in milliseconds.
+/// Setting this to zero will create sockets with the system
+/// default timeout.
+/// * `buffer_size` - The default buffer size for the named pipe. The system should expand the
+/// buffer automatically as needed, except in the case of NOWAIT pipes, where
+/// it will just fail writes that don't fit in the buffer.
+/// * `overlapped` - Sets whether overlapped mode is set on the pipe.
+pub fn create_server_pipe(
+ pipe_name: &str,
+ framing_mode: &FramingMode,
+ blocking_mode: &BlockingMode,
+ timeout: u64,
+ buffer_size: usize,
+ overlapped: bool,
+) -> Result<PipeConnection> {
+ let c_pipe_name = CString::new(pipe_name).unwrap();
+
+ let mut open_mode_flags = PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE;
+ if overlapped {
+ open_mode_flags |= FILE_FLAG_OVERLAPPED
+ }
+
+ // This sets flags so there will be an error if >1 instance (server end)
+ // of this pipe name is opened because we expect exactly one.
+ let server_handle = unsafe {
+ // Safe because security attributes are valid, pipe_name is valid C string,
+ // and we're checking the return code
+ CreateNamedPipeA(
+ c_pipe_name.as_ptr(),
+ /* dwOpenMode= */
+ open_mode_flags,
+ /* dwPipeMode= */
+ framing_mode.to_pipetype()
+ | framing_mode.to_readmode()
+ | DWORD::from(blocking_mode)
+ | PIPE_REJECT_REMOTE_CLIENTS,
+ /* nMaxInstances= */ 1,
+ /* nOutBufferSize= */ buffer_size as DWORD,
+ /* nInBufferSize= */ buffer_size as DWORD,
+ /* nDefaultTimeOut= */ timeout as DWORD, // Default is 50ms
+ /* lpSecurityAttributes= */
+ SecurityAttributes::new_with_security_descriptor(
+ SelfRelativeSecurityDescriptor::get_singleton(),
+ /* inherit= */ true,
+ )
+ .as_mut(),
+ )
+ };
+
+ if server_handle == INVALID_HANDLE_VALUE {
+ Err(io::Error::last_os_error())
+ } else {
+ unsafe {
+ Ok(PipeConnection {
+ handle: SafeDescriptor::from_raw_descriptor(server_handle),
+ framing_mode: *framing_mode,
+ blocking_mode: *blocking_mode,
+ })
+ }
+ }
+}
+
+/// Creates a PipeConnection for the client end of a named pipe with the given path and pipe
+/// settings.
+///
+/// The pipe will be set to prevent impersonation of the client by the server process.
+///
+/// # Arguments
+///
+/// * `pipe_name` - The path of the named pipe to create. Should be in the form
+/// `\\.\pipe\<some-name>`.
+/// * `framing_mode` - Whether the system should provide a simple byte stream (Byte) or an
+/// automatically framed sequence of messages (Message). In message mode it's an
+/// error to read fewer bytes than were sent in a message from the other end of
+/// the pipe.
+/// * `blocking_mode` - Whether the system should wait on read() until data is available (Wait) or
+/// return immediately if there is nothing available (NoWait).
+/// * `overlapped` - Sets whether the pipe is opened in overlapped mode.
+pub fn create_client_pipe(
+ pipe_name: &str,
+ framing_mode: &FramingMode,
+ blocking_mode: &BlockingMode,
+ overlapped: bool,
+) -> Result<PipeConnection> {
+ let client_handle = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create(true)
+ .security_qos_flags(SECURITY_IDENTIFICATION)
+ .custom_flags(if overlapped { FILE_FLAG_OVERLAPPED } else { 0 })
+ .open(pipe_name)?
+ .into_raw_descriptor();
+
+ let mut client_mode = framing_mode.to_readmode() | DWORD::from(blocking_mode);
+
+ // Safe because client_handle's open() call did not return an error.
+ unsafe {
+ set_named_pipe_handle_state(client_handle, &mut client_mode)?;
+ }
+
+ Ok(PipeConnection {
+ // Safe because client_handle is valid
+ handle: unsafe { SafeDescriptor::from_raw_descriptor(client_handle) },
+ framing_mode: *framing_mode,
+ blocking_mode: *blocking_mode,
+ })
+}
+
+// This is used to mark types which can be appropriately sent through the
+// generic helper functions write_to_pipe and read_from_pipe.
+pub trait PipeSendable {
+ // Default values used to fill in new empty indexes when resizing a buffer to
+ // a larger size.
+ fn default() -> Self;
+}
+impl PipeSendable for u8 {
+ fn default() -> Self {
+ 0
+ }
+}
+impl PipeSendable for RawDescriptor {
+ fn default() -> Self {
+ ptr::null_mut()
+ }
+}
+
+impl PipeConnection {
+ pub fn try_clone(&self) -> Result<PipeConnection> {
+ let copy_handle = self.handle.try_clone()?;
+ Ok(PipeConnection {
+ handle: copy_handle,
+ framing_mode: self.framing_mode,
+ blocking_mode: self.blocking_mode,
+ })
+ }
+
+ /// Creates a PipeConnection from an existing RawDescriptor, and the underlying the framing &
+ /// blocking modes.
+ ///
+ /// # Safety
+ /// 1. rd is valid and ownership is transferred to this function when it is called.
+ ///
+ /// To avoid undefined behavior, framing_mode & blocking_modes must match those of the
+ /// underlying pipe.
+ pub unsafe fn from_raw_descriptor(
+ rd: RawDescriptor,
+ framing_mode: FramingMode,
+ blocking_mode: BlockingMode,
+ ) -> PipeConnection {
+ PipeConnection {
+ handle: SafeDescriptor::from_raw_descriptor(rd),
+ framing_mode,
+ blocking_mode,
+ }
+ }
+
+ /// Reads bytes from the pipe into the provided buffer, up to the capacity of the buffer.
+ /// Returns the number of bytes (not values) read.
+ ///
+ /// # Safety
+ ///
+ /// This is safe only when the following conditions hold:
+ /// 1. The data on the other end of the pipe is a valid binary representation of data for
+ /// type T, and
+ /// 2. The number of bytes read is a multiple of the size of T; this must be checked by
+ /// the caller.
+ /// If buf's type is file descriptors, this is only safe when those file descriptors are valid
+ /// for the process where this function was called.
+ pub unsafe fn read<T: PipeSendable>(&self, buf: &mut [T]) -> Result<usize> {
+ PipeConnection::read_internal(&self.handle, self.blocking_mode, buf, None)
+ }
+
+ /// Similar to `PipeConnection::read` except it also allows:
+ /// 1. The same end of the named pipe to read and write at the same time in different
+ /// threads.
+ /// 2. Asynchronous read and write (read and write won't block).
+ ///
+ /// When reading, it will not block, but instead an `OVERLAPPED` struct that contains an event
+ /// (can be created with `PipeConnection::create_overlapped_struct`) will be passed into
+ /// `ReadFile`. That event will be triggered when the read operation is complete.
+ ///
+ /// In order to get how many bytes were read, call `get_overlapped_result`. That function will
+ /// also help with waiting until the read operation is complete.
+ ///
+ /// # Safety
+ ///
+ /// Same as `PipeConnection::read` safety comments. In addition, the pipe MUST be opened in
+ /// overlapped mode otherwise there may be unexpected behavior.
+ pub unsafe fn read_overlapped<T: PipeSendable>(
+ &mut self,
+ buf: &mut [T],
+ overlapped_wrapper: &mut OverlappedWrapper,
+ ) -> Result<()> {
+ if overlapped_wrapper.in_use {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::InvalidInput,
+ "Overlapped struct already in use",
+ ));
+ }
+ overlapped_wrapper.in_use = true;
+
+ PipeConnection::read_internal(
+ &self.handle,
+ self.blocking_mode,
+ buf,
+ Some(&mut overlapped_wrapper.overlapped),
+ )?;
+ Ok(())
+ }
+
+ /// Helper for `read_overlapped` and `read`
+ ///
+ /// # Safety
+ /// Comments `read_overlapped` or `read`, depending on which is used.
+ unsafe fn read_internal<T: PipeSendable>(
+ handle: &SafeDescriptor,
+ blocking_mode: BlockingMode,
+ buf: &mut [T],
+ overlapped: Option<&mut OVERLAPPED>,
+ ) -> Result<usize> {
+ let max_bytes_to_read: DWORD = mem::size_of_val(buf) as DWORD;
+ // Used to verify if ERROR_IO_PENDING should be an error.
+ let is_overlapped = overlapped.is_some();
+
+ // Safe because we cap the size of the read to the size of the buffer
+ // and check the return code
+ let mut bytes_read: DWORD = 0;
+ let success_flag = ReadFile(
+ handle.as_raw_descriptor(),
+ buf.as_ptr() as LPVOID,
+ max_bytes_to_read,
+ match overlapped {
+ Some(_) => std::ptr::null_mut(),
+ None => &mut bytes_read,
+ },
+ match overlapped {
+ Some(v) => v,
+ None => std::ptr::null_mut(),
+ },
+ );
+
+ if success_flag == 0 {
+ let e = io::Error::last_os_error();
+ match e.raw_os_error() {
+ Some(error_code)
+ if blocking_mode == BlockingMode::NoWait
+ && error_code == ERROR_NO_DATA as i32 =>
+ {
+ // A NOWAIT pipe will return ERROR_NO_DATA when no data is available; however,
+ // this code is interpreted as a std::io::ErrorKind::BrokenPipe, which is not
+ // correct. For further details see:
+ // https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
+ // https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipe-type-read-and-wait-modes
+ Err(std::io::Error::new(std::io::ErrorKind::WouldBlock, e))
+ }
+ // ERROR_IO_PENDING, according the to docs, isn't really an error. This just means
+ // that the ReadFile operation hasn't completed. In this case,
+ // `get_overlapped_result` will wait until the operation is completed.
+ Some(error_code) if error_code == ERROR_IO_PENDING as i32 && is_overlapped => {
+ return Ok(0);
+ }
+ _ => Err(e),
+ }
+ } else {
+ Ok(bytes_read as usize)
+ }
+ }
+
+ /// Gets the size in bytes of data in the pipe.
+ ///
+ /// Note that PeekNamedPipes (the underlying win32 API) will return zero if the packets have
+ /// not finished writing on the producer side.
+ pub fn get_available_byte_count(&self) -> io::Result<u32> {
+ let mut total_bytes_avail: DWORD = 0;
+
+ // Safe because the underlying pipe handle is guaranteed to be open, and the output values
+ // live at valid memory locations.
+ let res = unsafe {
+ PeekNamedPipe(
+ self.as_raw_descriptor(),
+ ptr::null_mut(),
+ 0,
+ ptr::null_mut(),
+ &mut total_bytes_avail,
+ ptr::null_mut(),
+ )
+ };
+
+ if res == 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(total_bytes_avail)
+ }
+ }
+
+ /// Writes the bytes from a slice into the pipe. Returns the number of bytes written, which
+ /// callers should check to ensure that it was the number expected.
+ pub fn write<T: PipeSendable>(&self, buf: &[T]) -> Result<usize> {
+ PipeConnection::write_internal(&self.handle, buf, None)
+ }
+
+ /// Similar to `PipeConnection::write` except it also allows:
+ /// 1. The same end of the named pipe to read and write at the same time in different
+ /// threads.
+ /// 2. Asynchronous read and write (read and write won't block).
+ ///
+ /// When writing, it will not block, but instead an `OVERLAPPED` struct that contains an event
+ /// (can be created with `PipeConnection::create_overlapped_struct`) will be passed into
+ /// `WriteFile`. That event will be triggered when the write operation is complete.
+ ///
+ /// In order to get how many bytes were written, call `get_overlapped_result`. That function will
+ /// also help with waiting until the write operation is complete. The pipe must be opened in
+ /// overlapped otherwise there may be unexpected behavior.
+ pub fn write_overlapped<T: PipeSendable>(
+ &mut self,
+ buf: &[T],
+ overlapped_wrapper: &mut OverlappedWrapper,
+ ) -> Result<()> {
+ if overlapped_wrapper.in_use {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::InvalidInput,
+ "Overlapped struct already in use",
+ ));
+ }
+ overlapped_wrapper.in_use = true;
+
+ PipeConnection::write_internal(
+ &self.handle,
+ buf,
+ Some(&mut overlapped_wrapper.overlapped),
+ )?;
+ Ok(())
+ }
+
+ /// Helper for `write_overlapped` and `write`.
+ fn write_internal<T: PipeSendable>(
+ handle: &SafeDescriptor,
+ buf: &[T],
+ overlapped: Option<&mut OVERLAPPED>,
+ ) -> Result<usize> {
+ let bytes_to_write: DWORD = mem::size_of_val(buf) as DWORD;
+ let is_overlapped = overlapped.is_some();
+
+ // Safe because buf points to a valid region of memory whose size we have computed,
+ // pipe has not been closed (as it's managed by this object), and we check the return
+ // value for any errors
+ unsafe {
+ let mut bytes_written: DWORD = 0;
+ let success_flag = WriteFile(
+ handle.as_raw_descriptor(),
+ buf.as_ptr() as LPCVOID,
+ bytes_to_write,
+ match overlapped {
+ Some(_) => std::ptr::null_mut(),
+ None => &mut bytes_written,
+ },
+ match overlapped {
+ Some(v) => v,
+ None => std::ptr::null_mut(),
+ },
+ );
+
+ if success_flag == 0 {
+ let err = io::Error::last_os_error().raw_os_error().unwrap() as u32;
+ if err == ERROR_IO_PENDING && is_overlapped {
+ return Ok(0);
+ }
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(bytes_written as usize)
+ }
+ }
+ }
+
+ /// Sets the blocking mode on the pipe.
+ pub fn set_blocking(&mut self, blocking_mode: &BlockingMode) -> io::Result<()> {
+ let mut client_mode = DWORD::from(blocking_mode) | self.framing_mode.to_readmode();
+ self.blocking_mode = *blocking_mode;
+
+ // Safe because the pipe has not been closed (it is managed by this object).
+ unsafe { set_named_pipe_handle_state(self.handle.as_raw_descriptor(), &mut client_mode) }
+ }
+
+ /// For a server named pipe, waits for a client to connect
+ pub fn wait_for_client_connection(&self) -> Result<()> {
+ // Safe because the handle is valid and we're checking the return
+ // code according to the documentation
+ unsafe {
+ let success_flag = ConnectNamedPipe(
+ self.as_raw_descriptor(),
+ /* lpOverlapped= */ ptr::null_mut(),
+ );
+ if success_flag == 0 && GetLastError() != ERROR_PIPE_CONNECTED {
+ return Err(io::Error::last_os_error());
+ }
+ }
+ Ok(())
+ }
+
+ /// Used for overlapped read and write operations.
+ ///
+ /// This will block until the ReadFile or WriteFile operation that also took in
+ /// `overlapped_wrapper` is complete, assuming `overlapped_wrapper` was created from
+ /// `create_overlapped_struct` or that OVERLAPPED.hEvent is set. This will also get
+ /// the number of bytes that were read or written.
+ pub fn get_overlapped_result(
+ &mut self,
+ overlapped_wrapper: &mut OverlappedWrapper,
+ ) -> io::Result<u32> {
+ if !overlapped_wrapper.in_use {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::InvalidInput,
+ "Overlapped struct is not in use",
+ ));
+ }
+ let mut size_transferred = 0;
+ // Safe as long as `overlapped_struct` isn't copied and also contains a valid event.
+ // Also the named pipe handle must created with `FILE_FLAG_OVERLAPPED`.
+ let res = unsafe {
+ GetOverlappedResult(
+ self.handle.as_raw_descriptor(),
+ &mut *overlapped_wrapper.overlapped,
+ &mut size_transferred,
+ TRUE,
+ )
+ };
+ overlapped_wrapper.in_use = false;
+ if res == 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(size_transferred)
+ }
+ }
+
+ /// Creates a valid `OVERLAPPED` struct used to pass into `ReadFile` and `WriteFile` in order
+ /// to perform asynchronous I/O. When passing in the OVERLAPPED struct, the Event object
+ /// returned must not be dropped.
+ ///
+ /// There is an option to create the event object and set it to the `hEvent` field. If hEvent
+ /// is not set and the named pipe handle was created with `FILE_FLAG_OVERLAPPED`, then the file
+ /// handle will be signaled when the operation is complete. In other words, you can use
+ /// `WaitForSingleObject` on the file handle. Not setting an event is highly discouraged by
+ /// Microsoft though.
+ pub fn create_overlapped_struct(include_event: bool) -> Result<OverlappedWrapper> {
+ let mut overlapped = OVERLAPPED::default();
+ let h_event = if include_event {
+ Some(Event::new()?)
+ } else {
+ None
+ };
+ overlapped.hEvent = h_event.as_ref().unwrap().as_raw_descriptor();
+ Ok(OverlappedWrapper {
+ overlapped: Box::new(overlapped),
+ h_event,
+ in_use: false,
+ })
+ }
+
+ /// Cancels I/O Operations in the current process. Since `lpOverlapped` is null, this will
+ /// cancel all I/O requests for the file handle passed in.
+ pub fn cancel_io(&mut self) -> Result<()> {
+ let res = unsafe {
+ CancelIoEx(
+ self.handle.as_raw_descriptor(),
+ /* lpOverlapped= */ std::ptr::null_mut(),
+ )
+ };
+ if res == 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Get the framing mode of the pipe.
+ pub fn get_framing_mode(&self) -> FramingMode {
+ self.framing_mode
+ }
+
+ /// Returns metadata about the connected NamedPipe.
+ pub fn get_info(&self, is_server_connection: bool) -> Result<NamedPipeInfo> {
+ let mut flags: u32 = 0;
+ // Marked mutable because they are mutated in a system call
+ #[allow(unused_mut)]
+ let mut incoming_buffer_size: u32 = 0;
+ #[allow(unused_mut)]
+ let mut outgoing_buffer_size: u32 = 0;
+ #[allow(unused_mut)]
+ let mut max_instances: u32 = 0;
+ // Client side with BYTE type are default flags
+ if is_server_connection {
+ flags |= 0x00000001 /* PIPE_SERVER_END */
+ }
+ if self.framing_mode == FramingMode::Message {
+ flags |= 0x00000004 /* PIPE_TYPE_MESSAGE */
+ }
+ // Safe because we have allocated all pointers and own
+ // them as mutable.
+ let res = unsafe {
+ GetNamedPipeInfo(
+ self.as_raw_descriptor(),
+ flags as *mut u32,
+ outgoing_buffer_size as *mut u32,
+ incoming_buffer_size as *mut u32,
+ max_instances as *mut u32,
+ )
+ };
+
+ if res == 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(NamedPipeInfo {
+ outgoing_buffer_size,
+ incoming_buffer_size,
+ max_instances,
+ })
+ }
+ }
+
+ /// For a server pipe, flush the pipe contents. This will
+ /// block until the pipe is cleared by the client. Only
+ /// call this if you are sure the client is reading the
+ /// data!
+ pub fn flush_data_blocking(&self) -> Result<()> {
+ // Safe because the only buffers interacted with are
+ // outside of Rust memory
+ let res = unsafe { FlushFileBuffers(self.as_raw_descriptor()) };
+ if res == 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+ }
+}
+
+impl AsRawDescriptor for PipeConnection {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.handle.as_raw_descriptor()
+ }
+}
+
+impl IntoRawDescriptor for PipeConnection {
+ fn into_raw_descriptor(self) -> RawDescriptor {
+ self.handle.into_raw_descriptor()
+ }
+}
+
+unsafe impl Send for PipeConnection {}
+unsafe impl Sync for PipeConnection {}
+
+impl io::Read for PipeConnection {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ // This is safe because PipeConnection::read is always safe for u8
+ unsafe { PipeConnection::read(self, buf) }
+ }
+}
+
+impl io::Write for PipeConnection {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ PipeConnection::write(self, buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+/// A simple data struct representing
+/// metadata about a NamedPipe.
+pub struct NamedPipeInfo {
+ pub outgoing_buffer_size: u32,
+ pub incoming_buffer_size: u32,
+ pub max_instances: u32,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn duplex_pipe_stream() {
+ let (p1, p2) = pair(&FramingMode::Byte, &BlockingMode::Wait, 0).unwrap();
+
+ // Test both forward and reverse direction since the underlying APIs are a bit asymmetrical
+ unsafe {
+ for (dir, sender, receiver) in [("1 -> 2", &p1, &p2), ("2 -> 1", &p2, &p1)].iter() {
+ println!("{}", dir);
+
+ sender.write(&[75, 77, 54, 82, 76, 65]).unwrap();
+
+ // Smaller than what we sent so we get multiple chunks
+ let mut recv_buffer: [u8; 4] = [0; 4];
+
+ let mut size = receiver.read(&mut recv_buffer).unwrap();
+ assert_eq!(size, 4);
+ assert_eq!(recv_buffer, [75, 77, 54, 82]);
+
+ size = receiver.read(&mut recv_buffer).unwrap();
+ assert_eq!(size, 2);
+ assert_eq!(recv_buffer[0..2], [76, 65]);
+ }
+ }
+ }
+
+ #[test]
+ fn available_byte_count_byte_mode() {
+ let (p1, p2) = pair(&FramingMode::Byte, &BlockingMode::Wait, 0).unwrap();
+ p1.write(&[1, 23, 45]).unwrap();
+ assert_eq!(p2.get_available_byte_count().unwrap(), 3);
+
+ // PeekNamedPipe should NOT touch the data in the pipe. So if we call it again, it should
+ // yield the same value.
+ assert_eq!(p2.get_available_byte_count().unwrap(), 3);
+ }
+
+ #[test]
+ fn available_byte_count_message_mode() {
+ let (p1, p2) = pair(&FramingMode::Message, &BlockingMode::Wait, 0).unwrap();
+ p1.write(&[1, 23, 45]).unwrap();
+ assert_eq!(p2.get_available_byte_count().unwrap(), 3);
+
+ // PeekNamedPipe should NOT touch the data in the pipe. So if we call it again, it should
+ // yield the same value.
+ assert_eq!(p2.get_available_byte_count().unwrap(), 3);
+ }
+
+ #[test]
+ fn available_byte_count_message_mode_multiple_messages() {
+ let (p1, p2) = pair(&FramingMode::Message, &BlockingMode::Wait, 0).unwrap();
+ p1.write(&[1, 2, 3]).unwrap();
+ p1.write(&[4, 5]).unwrap();
+ assert_eq!(p2.get_available_byte_count().unwrap(), 5);
+ }
+
+ #[test]
+ fn duplex_pipe_message() {
+ let (p1, p2) = pair(&FramingMode::Message, &BlockingMode::Wait, 0).unwrap();
+
+ // Test both forward and reverse direction since the underlying APIs are a bit asymmetrical
+ unsafe {
+ for (dir, sender, receiver) in [("1 -> 2", &p1, &p2), ("2 -> 1", &p2, &p1)].iter() {
+ println!("{}", dir);
+
+ // Send 2 messages so that we can check that message framing works
+ sender.write(&[1, 23, 45]).unwrap();
+ sender.write(&[67, 89, 10]).unwrap();
+
+ let mut recv_buffer: [u8; 5] = [0; 5]; // Larger than required for messages
+
+ let mut size = receiver.read(&mut recv_buffer).unwrap();
+ assert_eq!(size, 3);
+ assert_eq!(recv_buffer[0..3], [1, 23, 45]);
+
+ size = receiver.read(&mut recv_buffer).unwrap();
+ assert_eq!(size, 3);
+ assert_eq!(recv_buffer[0..3], [67, 89, 10]);
+ }
+ }
+ }
+
+ #[cfg(test)]
+ fn duplex_nowait_helper(p1: &PipeConnection, p2: &PipeConnection) {
+ let mut recv_buffer: [u8; 1] = [0; 1];
+
+ // Test both forward and reverse direction since the underlying APIs are a bit asymmetrical
+ unsafe {
+ for (dir, sender, receiver) in [("1 -> 2", &p1, &p2), ("2 -> 1", &p2, &p1)].iter() {
+ println!("{}", dir);
+ sender.write(&[1]).unwrap();
+ assert_eq!(receiver.read(&mut recv_buffer).unwrap(), 1); // Should succeed!
+ assert_eq!(
+ receiver.read(&mut recv_buffer).unwrap_err().kind(),
+ std::io::ErrorKind::WouldBlock
+ );
+ }
+ }
+ }
+
+ #[test]
+ fn duplex_nowait() {
+ let (p1, p2) = pair(&FramingMode::Byte, &BlockingMode::NoWait, 0).unwrap();
+ duplex_nowait_helper(&p1, &p2);
+ }
+
+ #[test]
+ fn duplex_nowait_set_after_creation() {
+ // Tests non blocking setting after pipe creation
+ let (mut p1, mut p2) = pair(&FramingMode::Byte, &BlockingMode::Wait, 0).unwrap();
+ p1.set_blocking(&BlockingMode::NoWait)
+ .expect("Failed to set blocking mode on pipe p1");
+ p2.set_blocking(&BlockingMode::NoWait)
+ .expect("Failed to set blocking mode on pipe p2");
+ duplex_nowait_helper(&p1, &p2);
+ }
+
+ #[test]
+ fn duplex_overlapped() {
+ let pipe_name = generate_pipe_name();
+
+ let mut p1 = create_server_pipe(
+ &pipe_name,
+ &FramingMode::Message,
+ &BlockingMode::Wait,
+ /* timeout= */ 0,
+ /* buffer_size= */ 1000,
+ /* overlapped= */ true,
+ )
+ .unwrap();
+
+ let mut p2 = create_client_pipe(
+ &pipe_name,
+ &FramingMode::Message,
+ &BlockingMode::Wait,
+ /* overlapped= */ true,
+ )
+ .unwrap();
+
+ // Safe because `read_overlapped` can be called since overlapped struct is created.
+ unsafe {
+ let mut p1_overlapped_wrapper =
+ PipeConnection::create_overlapped_struct(/* include_event= */ true).unwrap();
+ p1.write_overlapped(&[75, 77, 54, 82, 76, 65], &mut p1_overlapped_wrapper)
+ .unwrap();
+ let size = p1
+ .get_overlapped_result(&mut p1_overlapped_wrapper)
+ .unwrap();
+ assert_eq!(size, 6);
+
+ let mut recv_buffer: [u8; 6] = [0; 6];
+
+ let mut p2_overlapped_wrapper =
+ PipeConnection::create_overlapped_struct(/* include_event= */ true).unwrap();
+ p2.read_overlapped(&mut recv_buffer, &mut p2_overlapped_wrapper)
+ .unwrap();
+ let size = p2
+ .get_overlapped_result(&mut p2_overlapped_wrapper)
+ .unwrap();
+ assert_eq!(size, 6);
+ assert_eq!(recv_buffer, [75, 77, 54, 82, 76, 65]);
+ }
+ }
+
+ #[test]
+ fn duplex_overlapped_test_in_use() {
+ let pipe_name = generate_pipe_name();
+
+ let mut p1 = create_server_pipe(
+ &pipe_name,
+ &FramingMode::Message,
+ &BlockingMode::Wait,
+ /* timeout= */ 0,
+ /* buffer_size= */ 1000,
+ /* overlapped= */ true,
+ )
+ .unwrap();
+
+ let mut p2 = create_client_pipe(
+ &pipe_name,
+ &FramingMode::Message,
+ &BlockingMode::Wait,
+ /* overlapped= */ true,
+ )
+ .unwrap();
+ let mut overlapped_wrapper =
+ PipeConnection::create_overlapped_struct(/* include_event= */ true).unwrap();
+
+ let res = p1.get_overlapped_result(&mut overlapped_wrapper);
+ assert!(res.is_err());
+
+ let res = p1.write_overlapped(&[75, 77, 54, 82, 76, 65], &mut overlapped_wrapper);
+ assert!(res.is_ok());
+
+ let res = p2.write_overlapped(&[75, 77, 54, 82, 76, 65], &mut overlapped_wrapper);
+ assert!(res.is_err());
+
+ let mut recv_buffer: [u8; 6] = [0; 6];
+ let res = unsafe { p2.read_overlapped(&mut recv_buffer, &mut overlapped_wrapper) };
+ assert!(res.is_err());
+
+ let res = p1.get_overlapped_result(&mut overlapped_wrapper);
+ assert!(res.is_ok());
+
+ let mut recv_buffer: [u8; 6] = [0; 6];
+ let res = unsafe { p2.read_overlapped(&mut recv_buffer, &mut overlapped_wrapper) };
+ assert!(res.is_ok());
+ }
+
+ fn generate_pipe_name() -> String {
+ format!(
+ r"\\.\pipe\test-ipc-pipe-name.rand{}",
+ rand::thread_rng().gen::<u64>(),
+ )
+ }
+}
diff --git a/base/src/windows/win/platform_timer_utils.rs b/base/src/windows/win/platform_timer_utils.rs
new file mode 100644
index 000000000..1fd558980
--- /dev/null
+++ b/base/src/windows/win/platform_timer_utils.rs
@@ -0,0 +1,206 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ io,
+ mem::MaybeUninit,
+ sync::Once,
+ time::{Duration, Instant},
+};
+
+use winapi::shared::minwindef::{
+ HINSTANCE, HMODULE, PULONG, {self},
+};
+
+use winapi::{
+ shared::{
+ ntdef::{NTSTATUS, ULONG},
+ ntstatus::STATUS_SUCCESS,
+ },
+ um::{libloaderapi, mmsystem::TIMERR_NOERROR},
+};
+
+use std::thread::sleep;
+use win_util::{win32_string, win32_wide_string};
+use winapi::um::{
+ timeapi::{timeBeginPeriod, timeEndPeriod},
+ winnt::BOOLEAN,
+};
+
+use super::super::{Error, Result};
+use crate::warn;
+
+static NT_INIT: Once = Once::new();
+static mut NT_LIBRARY: MaybeUninit<HMODULE> = MaybeUninit::uninit();
+
+#[inline]
+fn init_ntdll() -> Result<HINSTANCE> {
+ NT_INIT.call_once(|| {
+ unsafe {
+ *NT_LIBRARY.as_mut_ptr() =
+ libloaderapi::LoadLibraryW(win32_wide_string("ntdll").as_ptr());
+
+ if NT_LIBRARY.assume_init().is_null() {
+ warn!("Failed to load ntdll: {}", Error::last());
+ }
+ };
+ });
+
+ let handle = unsafe { NT_LIBRARY.assume_init() };
+ if handle.is_null() {
+ Err(Error::from(io::Error::new(
+ io::ErrorKind::NotFound,
+ "ntdll failed to load",
+ )))
+ } else {
+ Ok(handle)
+ }
+}
+
+fn get_symbol(handle: HMODULE, proc_name: &str) -> Result<*mut minwindef::__some_function> {
+ let symbol = unsafe { libloaderapi::GetProcAddress(handle, win32_string(proc_name).as_ptr()) };
+ if symbol.is_null() {
+ Err(Error::last())
+ } else {
+ Ok(symbol)
+ }
+}
+
+/// Returns the resolution of timers on the host (current_res, max_res).
+pub fn nt_query_timer_resolution() -> Result<(Duration, Duration)> {
+ let handle = init_ntdll()?;
+
+ let func = unsafe {
+ std::mem::transmute::<
+ *mut minwindef::__some_function,
+ extern "system" fn(PULONG, PULONG, PULONG) -> NTSTATUS,
+ >(get_symbol(handle, "NtQueryTimerResolution")?)
+ };
+
+ let mut min_res: u32 = 0;
+ let mut max_res: u32 = 0;
+ let mut current_res: u32 = 0;
+ let ret = func(
+ &mut min_res as *mut u32,
+ &mut max_res as *mut u32,
+ &mut current_res as *mut u32,
+ );
+
+ if ret != STATUS_SUCCESS {
+ Err(Error::from(io::Error::new(
+ io::ErrorKind::Other,
+ "NtQueryTimerResolution failed",
+ )))
+ } else {
+ Ok((
+ Duration::from_nanos((current_res as u64) * 100),
+ Duration::from_nanos((max_res as u64) * 100),
+ ))
+ }
+}
+
+pub fn nt_set_timer_resolution(resolution: Duration) -> Result<()> {
+ let handle = init_ntdll()?;
+ let func = unsafe {
+ std::mem::transmute::<
+ *mut minwindef::__some_function,
+ extern "system" fn(ULONG, BOOLEAN, PULONG) -> NTSTATUS,
+ >(get_symbol(handle, "NtSetTimerResolution")?)
+ };
+
+ let requested_res: u32 = (resolution.as_nanos() / 100) as u32;
+ let mut current_res: u32 = 0;
+ let ret = func(
+ requested_res,
+ 1, /* true */
+ &mut current_res as *mut u32,
+ );
+
+ if ret != STATUS_SUCCESS {
+ Err(Error::from(io::Error::new(
+ io::ErrorKind::Other,
+ "NtSetTimerResolution failed",
+ )))
+ } else {
+ Ok(())
+ }
+}
+
+/// Measures the timer resolution by taking the 90th percentile wall time of 1ms sleeps.
+pub fn measure_timer_resolution() -> Duration {
+ let mut durations = Vec::with_capacity(100);
+ for _ in 0..100 {
+ let start = Instant::now();
+ // Windows cannot support sleeps shorter than 1ms.
+ sleep(Duration::from_millis(1));
+ durations.push(Instant::now() - start);
+ }
+
+ durations.sort();
+ durations[89]
+}
+
+/// Note that Durations below 1ms are not supported and will panic.
+pub fn set_time_period(res: Duration, begin: bool) -> Result<()> {
+ if res.as_millis() < 1 {
+ panic!(
+ "time(Begin|End)Period does not support values below 1ms, but {:?} was requested.",
+ res
+ );
+ }
+ if res.as_millis() > u32::MAX as u128 {
+ panic!("time(Begin|End)Period does not support values above u32::MAX.",);
+ }
+
+ // Trivially safe. Note that the casts are safe because we know res is within u32's range.
+ let ret = if begin {
+ unsafe { timeBeginPeriod(res.as_millis() as u32) }
+ } else {
+ unsafe { timeEndPeriod(res.as_millis() as u32) }
+ };
+ if ret != TIMERR_NOERROR {
+ // These functions only have two return codes: NOERROR and NOCANDO.
+ Err(Error::from(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "timeBegin/EndPeriod failed",
+ )))
+ } else {
+ Ok(())
+ }
+}
+
+/// Note that these tests cannot run on Kokoro due to random slowness in that environment.
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ /// We're testing whether NtSetTimerResolution does what it says on the tin.
+ #[test]
+ #[ignore]
+ fn setting_nt_timer_resolution_changes_resolution() {
+ let (old_res, _) = nt_query_timer_resolution().unwrap();
+
+ nt_set_timer_resolution(Duration::from_millis(1)).unwrap();
+ assert_res_within_bound(measure_timer_resolution());
+ nt_set_timer_resolution(old_res).unwrap();
+ }
+
+ #[test]
+ #[ignore]
+ fn setting_timer_resolution_changes_resolution() {
+ let res = Duration::from_millis(1);
+
+ set_time_period(res, true).unwrap();
+ assert_res_within_bound(measure_timer_resolution());
+ set_time_period(res, false).unwrap();
+ }
+
+ fn assert_res_within_bound(actual_res: Duration) {
+ assert!(
+ actual_res <= Duration::from_millis(2),
+ "actual_res was {:?}, expected <= 2ms",
+ actual_res
+ );
+ }
+}
diff --git a/base/src/windows/win/priority.rs b/base/src/windows/win/priority.rs
new file mode 100644
index 000000000..cacdd2b60
--- /dev/null
+++ b/base/src/windows/win/priority.rs
@@ -0,0 +1,91 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::{errno_result, Result};
+use std::os::windows::raw::HANDLE;
+use winapi::{
+ shared::minwindef::FALSE,
+ um::{
+ avrt::{AvRevertMmThreadCharacteristics, AvSetMmThreadCharacteristicsA},
+ errhandlingapi::GetLastError,
+ processthreadsapi::{GetCurrentThread, SetThreadPriority},
+ },
+};
+
+pub fn set_audio_thread_priorities() -> Result<SafeMultimediaHandle> {
+ // Safe because we know Pro Audio is part of windows and we down task_index.
+ let multimedia_handle = unsafe {
+ let mut task_index: u32 = 0;
+ // "Pro Audio" is defined in:
+ // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Multimedia\SystemProfile\Tasks\Pro Audio
+ let pro_audio = std::ffi::CString::new("Pro Audio").unwrap();
+ AvSetMmThreadCharacteristicsA(pro_audio.as_ptr(), &mut task_index)
+ };
+
+ if multimedia_handle.is_null() {
+ warn!(
+ "Failed to set audio thread to Pro Audio. Error: {}",
+ unsafe { GetLastError() }
+ );
+ errno_result()
+ } else {
+ Ok(SafeMultimediaHandle { multimedia_handle })
+ }
+}
+
+pub fn set_thread_priority(thread_priority: i32) -> Result<()> {
+ let res =
+ // Safe because priority level value is valid and a valid thread handle will be passed in
+ unsafe { SetThreadPriority(GetCurrentThread(), thread_priority) };
+ if res == 0 {
+ errno_result()
+ } else {
+ Ok(())
+ }
+}
+
+pub struct SafeMultimediaHandle {
+ multimedia_handle: HANDLE,
+}
+
+impl Drop for SafeMultimediaHandle {
+ fn drop(&mut self) {
+ // Safe because we `multimedia_handle` is defined in the same thread and is created in the
+ // function above. `multimedia_handle` needs be created from `AvSetMmThreadCharacteristicsA`.
+ // This will also drop the `mulitmedia_handle`.
+ if unsafe { AvRevertMmThreadCharacteristics(self.multimedia_handle) } == FALSE {
+ warn!("Failed to revert audio thread. Error: {}", unsafe {
+ GetLastError()
+ });
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use winapi::um::{
+ processthreadsapi::{GetCurrentThread, GetThreadPriority},
+ winbase::{THREAD_PRIORITY_NORMAL, THREAD_PRIORITY_TIME_CRITICAL},
+ };
+
+ // TODO(b/223733375): Enable ignored flaky tests.
+ #[test]
+ #[ignore]
+ fn test_mm_handle_is_dropped() {
+ // Safe because the only the only unsafe functions called are to get the thread
+ // priority.
+ unsafe {
+ let thread_priority = GetThreadPriority(GetCurrentThread());
+ assert_eq!(thread_priority, THREAD_PRIORITY_NORMAL as i32);
+ {
+ let _handle = set_audio_thread_priorities();
+ let thread_priority = GetThreadPriority(GetCurrentThread());
+ assert_eq!(thread_priority, THREAD_PRIORITY_TIME_CRITICAL as i32);
+ }
+ let thread_priority = GetThreadPriority(GetCurrentThread());
+ assert_eq!(thread_priority, THREAD_PRIORITY_NORMAL as i32);
+ }
+ }
+}
diff --git a/base/src/windows/win/punch_hole.rs b/base/src/windows/win/punch_hole.rs
new file mode 100644
index 000000000..654f5f4ea
--- /dev/null
+++ b/base/src/windows/win/punch_hole.rs
@@ -0,0 +1,53 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::descriptor::AsRawDescriptor;
+use std::io::{
+ Error, {self},
+};
+use win_util::LargeInteger;
+pub use winapi::um::winioctl::FSCTL_SET_ZERO_DATA;
+use winapi::um::winnt::LARGE_INTEGER;
+
+// This struct is not implemented in the winapi so we need to implement it ourselves
+#[repr(C)]
+#[allow(non_snake_case)] // to match win32 naming api.
+#[allow(non_camel_case_types)]
+struct FILE_ZERO_DATA_INFORMATION {
+ FileOffset: LARGE_INTEGER,
+ BeyondFinalZero: LARGE_INTEGER,
+}
+
+pub fn execute_punch_hole<T: AsRawDescriptor>(
+ handle: &mut T,
+ offset: u64,
+ length: u64,
+) -> io::Result<()> {
+ let large_offset = if offset > std::i64::MAX as u64 {
+ return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
+ } else {
+ LargeInteger::new(offset as i64)
+ };
+
+ if (offset + length) > std::i64::MAX as u64 {
+ return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
+ }
+
+ let end_offset = LargeInteger::new((offset + length) as i64);
+
+ let zero_data = FILE_ZERO_DATA_INFORMATION {
+ FileOffset: *large_offset,
+ BeyondFinalZero: *end_offset,
+ };
+
+ // Safe because we check the return value and all values should be set
+ let result =
+ unsafe { super::super::ioctl::ioctl_with_ref(handle, FSCTL_SET_ZERO_DATA, &zero_data) };
+
+ if result != 0 {
+ return Err(Error::from_raw_os_error(result));
+ }
+
+ Ok(())
+}
diff --git a/base/src/windows/win/sched.rs b/base/src/windows/win/sched.rs
new file mode 100644
index 000000000..c5e2d7b30
--- /dev/null
+++ b/base/src/windows/win/sched.rs
@@ -0,0 +1,73 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use libc::EINVAL;
+use winapi::um::{processthreadsapi::GetCurrentThread, winbase::SetThreadAffinityMask};
+
+use super::{errno_result, Error, Result};
+
+/// Set the CPU affinity of the current thread to a given set of CPUs.
+/// The cpus must be a subset of those in the process affinity mask.
+/// If there are more than 64 processors, the affinity mask must specify
+/// processors in the thread's current processor group.
+///
+/// Returns the previous bits set for cpu_affinity.
+/// # Examples
+///
+/// Set the calling thread's CPU affinity so it will run on only CPUs
+/// 0, 1, 5, and 6.
+///
+/// ```
+/// # use crate::platform::set_cpu_affinity;
+/// set_cpu_affinity(vec![0, 1, 5, 6]).unwrap();
+/// ```
+pub fn set_cpu_affinity<I: IntoIterator<Item = usize>>(cpus: I) -> Result<usize> {
+ let mut affinity_mask: usize = 0;
+ for cpu in cpus {
+ affinity_mask |= 1 << cpu;
+ }
+ set_cpu_affinity_mask(affinity_mask)
+}
+
+pub fn set_cpu_affinity_mask(affinity_mask: usize) -> Result<usize> {
+ let res: usize = unsafe {
+ let thread_handle = GetCurrentThread();
+ SetThreadAffinityMask(thread_handle, affinity_mask)
+ };
+ if res == 0 {
+ return errno_result::<usize>();
+ }
+ Ok(res)
+}
+
+pub fn get_cpu_affinity() -> Result<Vec<usize>> {
+ // Unimplemented for now, since this is unused on Windows. Thread affinity can be read by
+ // calling GetThreadAffinityMask twice, to get and restore the previous affinities.
+ Err(Error::new(EINVAL))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::super::sched::*;
+ use winapi::um::{processthreadsapi::GetCurrentProcess, winbase::GetProcessAffinityMask};
+ #[test]
+ fn cpu_affinity() {
+ let mut process_affinity_mask: usize = 0;
+ let mut system_affinity_mask: usize = 0;
+ let res = unsafe {
+ GetProcessAffinityMask(
+ GetCurrentProcess(),
+ &mut process_affinity_mask as *mut usize,
+ &mut system_affinity_mask as *mut usize,
+ )
+ };
+ assert_ne!(res, 0);
+ let mut prev_thread_affinity = set_cpu_affinity(vec![0, 1]).unwrap();
+ assert_eq!(process_affinity_mask, prev_thread_affinity);
+ // reset to original process affinity and grab previous affinity.
+ prev_thread_affinity = set_cpu_affinity_mask(process_affinity_mask).unwrap();
+ // cpu 0 + cpu 1, means bit 0 and bit 1 are set (1 + 2 = 3). Verify that is the case.
+ assert_eq!(prev_thread_affinity, 3);
+ }
+}
diff --git a/base/src/windows/win/shm.rs b/base/src/windows/win/shm.rs
new file mode 100644
index 000000000..b4cfb1b71
--- /dev/null
+++ b/base/src/windows/win/shm.rs
@@ -0,0 +1,71 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::ffi::CStr;
+use win_util::create_file_mapping;
+use winapi::um::winnt::PAGE_EXECUTE_READWRITE;
+
+use super::super::{shm::SharedMemory, MemoryMapping, MmapError, Result};
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, SafeDescriptor};
+
+impl SharedMemory {
+ /// Creates a new shared memory file mapping with zero size.
+ pub fn new(name: Option<&CStr>, size: u64) -> Result<Self> {
+ // Safe because we do not provide a handle.
+ let mapping_handle = unsafe {
+ create_file_mapping(
+ None,
+ size,
+ PAGE_EXECUTE_READWRITE,
+ name.map(|s| s.to_str().unwrap()),
+ )
+ }
+ .map_err(super::super::Error::from)?;
+
+ // Safe because we have exclusive ownership of mapping_handle & it is valid.
+ Self::from_safe_descriptor(
+ unsafe { SafeDescriptor::from_raw_descriptor(mapping_handle) },
+ size,
+ )
+ }
+
+ pub fn from_safe_descriptor(mapping_handle: SafeDescriptor, size: u64) -> Result<Self> {
+ let mapping = match MemoryMapping::from_raw_descriptor(
+ mapping_handle.as_raw_descriptor(),
+ size as usize,
+ ) {
+ Ok(mapping) => mapping,
+ Err(e) => {
+ return match e {
+ MmapError::SystemCallFailed(e) => Err(e),
+ // TODO(b/150414994): This error lacks meaning. Consider adding custom errors to
+ // shm
+ _ => Err(super::super::Error::new(0)),
+ };
+ }
+ };
+
+ Ok(SharedMemory {
+ descriptor: mapping_handle,
+ size,
+ mapping,
+ cursor: 0,
+ })
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use winapi::shared::winerror::ERROR_NOT_ENOUGH_MEMORY;
+
+ #[test]
+ fn new_too_huge() {
+ let result = SharedMemory::anon(0x8000_0000_0000_0000);
+ assert_eq!(
+ result.err().unwrap().errno(),
+ ERROR_NOT_ENOUGH_MEMORY as i32
+ );
+ }
+}
diff --git a/base/src/windows/win/stream_channel.rs b/base/src/windows/win/stream_channel.rs
new file mode 100644
index 000000000..49b3f308e
--- /dev/null
+++ b/base/src/windows/win/stream_channel.rs
@@ -0,0 +1,469 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::super::{
+ named_pipes::{
+ PipeConnection, {self},
+ },
+ stream_channel::{BlockingMode, FramingMode},
+ CloseNotifier, Event, MultiProcessMutex, RawDescriptor, ReadNotifier, Result,
+};
+use crate::descriptor::AsRawDescriptor;
+use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
+use std::{cell::RefCell, io, sync::Arc};
+use sync::Mutex;
+
+impl From<FramingMode> for named_pipes::FramingMode {
+ fn from(framing_mode: FramingMode) -> Self {
+ match framing_mode {
+ FramingMode::Message => named_pipes::FramingMode::Message,
+ FramingMode::Byte => named_pipes::FramingMode::Byte,
+ }
+ }
+}
+
+impl From<BlockingMode> for named_pipes::BlockingMode {
+ fn from(blocking_mode: BlockingMode) -> Self {
+ match blocking_mode {
+ BlockingMode::Blocking => named_pipes::BlockingMode::Wait,
+ BlockingMode::Nonblocking => named_pipes::BlockingMode::NoWait,
+ }
+ }
+}
+
+pub const DEFAULT_BUFFER_SIZE: usize = 50 * 1024;
+
+/// An abstraction over named pipes and unix socketpairs.
+///
+/// The ReadNotifier will return an event handle that is set when data is in the channel.
+///
+/// In message mode, single writes larger than
+/// `crate::platform::named_pipes::DEFAULT_BUFFER_SIZE` are not permitted.
+///
+/// # Notes for maintainers
+/// 1. This struct contains extremely subtle thread safety considerations.
+/// 2. Serialization is not derived! New fields need to be added manually.
+#[derive(Deserialize, Debug)]
+pub struct StreamChannel {
+ pipe_conn: named_pipes::PipeConnection,
+ write_notify: Event,
+ read_notify: Event,
+ pipe_closed: Event,
+
+ // Held when reading on this end, to prevent additional writes from corrupting notification
+ // state.
+ remote_write_lock: MultiProcessMutex,
+
+ // Held when a write is made on this end, so that if the remote end is reading, we wait to
+ // write to avoid corrupting notification state.
+ local_write_lock: MultiProcessMutex,
+
+ // Held for the entire duration of a read. This enables the StreamChannel to be sync,
+ // ensuring there is no chance of concurrent reads creating a bad state in StreamChannel.
+ //
+ // In practice, there is no use-case for multiple threads actually contending over
+ // reading from a single pipe through StreamChannel, so this is mostly to provide a
+ // compiler guarantee while passing the StreamChannel to/from background executors.
+ //
+ // Note that this mutex does not work across processes, so the same StreamChannel end should
+ // NOT be concurrently used across process boundaries. (Odds are if you want to do this, it's
+ // not what you want. Wanting this means you want two readers on the *same end* of the pipe,
+ // which is not well defined behavior.)
+ #[serde(skip)]
+ #[serde(default = "create_read_lock")]
+ read_lock: Arc<Mutex<()>>,
+
+ // Serde only has an immutable reference. Because of that, we have to cheat to signal when this
+ // channel end has been serialized. Once serialized, we know that the current end MUST NOT
+ // signal the channel has been closed when it was dropped, because a copy of it was sent to
+ // another process. It is the copy's responsibility to close the pipe.
+ #[serde(skip)]
+ #[serde(default = "create_true_cell")]
+ is_channel_closed_on_drop: RefCell<bool>,
+
+ // For StreamChannels created via pair_with_buffer_size, allows the channel to accept messages
+ // up to that size.
+ send_buffer_size: usize,
+}
+
+fn create_read_lock() -> Arc<Mutex<()>> {
+ Arc::new(Mutex::new(()))
+}
+
+fn create_true_cell() -> RefCell<bool> {
+ RefCell::new(true)
+}
+
+/// Serialize is manually implemented because we need to tell the local copy that a remote copy
+/// exists, and to not send the close event. Our serialization is otherwise identical to what
+/// derive would have generated.
+impl Serialize for StreamChannel {
+ fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let mut s = serializer.serialize_struct("StreamChannel", 7)?;
+ s.serialize_field("pipe_conn", &self.pipe_conn)?;
+ s.serialize_field("write_notify", &self.write_notify)?;
+ s.serialize_field("read_notify", &self.read_notify)?;
+ s.serialize_field("pipe_closed", &self.pipe_closed)?;
+ s.serialize_field("remote_write_lock", &self.remote_write_lock)?;
+ s.serialize_field("local_write_lock", &self.local_write_lock)?;
+ s.serialize_field("send_buffer_size", &self.send_buffer_size)?;
+ let ret = s.end();
+
+ // Because this end has been serialized, the serialized copy is now responsible for setting
+ // the close event.
+ if ret.is_ok() {
+ *self.is_channel_closed_on_drop.borrow_mut() = false;
+ }
+
+ ret
+ }
+}
+
+impl Drop for StreamChannel {
+ fn drop(&mut self) {
+ if *self.is_channel_closed_on_drop.borrow() {
+ if let Err(e) = self.pipe_closed.write(0) {
+ warn!("failed to notify on channel drop: {}", e);
+ }
+ }
+ }
+}
+
+impl StreamChannel {
+ pub fn set_nonblocking(&mut self, nonblocking: bool) -> io::Result<()> {
+ // Safe because the pipe is open.
+ if nonblocking {
+ self.pipe_conn
+ .set_blocking(&named_pipes::BlockingMode::NoWait)
+ } else {
+ self.pipe_conn
+ .set_blocking(&named_pipes::BlockingMode::Wait)
+ }
+ }
+
+ // WARNING: Generally, multiple StreamChannel ends are not wanted. StreamChannel behavior with
+ // > 1 reader per end is not defined.
+ pub fn try_clone(&self) -> io::Result<Self> {
+ Ok(StreamChannel {
+ pipe_conn: self.pipe_conn.try_clone()?,
+ write_notify: self.write_notify.try_clone()?,
+ read_notify: self.read_notify.try_clone()?,
+ pipe_closed: self.pipe_closed.try_clone()?,
+ remote_write_lock: self.remote_write_lock.try_clone()?,
+ local_write_lock: self.local_write_lock.try_clone()?,
+ read_lock: self.read_lock.clone(),
+ is_channel_closed_on_drop: create_true_cell(),
+ send_buffer_size: self.send_buffer_size,
+ })
+ }
+
+ fn get_readable_byte_count(&self) -> io::Result<u32> {
+ self.pipe_conn.get_available_byte_count().map_err(|e| {
+ error!("StreamChannel failed to get readable byte count: {}", e);
+ e
+ })
+ }
+
+ pub(super) fn inner_read(&self, buf: &mut [u8]) -> io::Result<usize> {
+ // We ensure concurrent read safety by holding a lock for the duration of the method.
+ // (If multiple concurrent readers were permitted, the pipe could be emptied after we decide
+ // that the notifier should be set, leading to an invalid notified/readable state which
+ // could stall readers.)
+ let _read_lock = self.read_lock.lock();
+
+ let res = unsafe {
+ // Safe because no partial reads are possible, and the underlying code bounds the
+ // read by buf's size.
+ self.pipe_conn.read(buf)
+ };
+
+ // The entire goal of this complex section is to avoid the need for shared memory between
+ // each channel end to synchronize the notification state. It is very subtle, modify with
+ // care.
+ loop {
+ // No other thread is reading, so we can find out, without the write lock, whether or
+ // not we need to clear the read notifier. If we don't, then we don't even have to try
+ // acquiring the write lock. This avoids deadlocks where the pipe is full and the write
+ // side blocks on a writing with the lock held. If it looks like we do need to clear
+ // the notifier though, then we have to be sure, so we'll proceed to the next section.
+ let byte_count = self.get_readable_byte_count()?;
+ if byte_count > 0 {
+ // It's always safe to set the read notifier here because we know there is data in the
+ // pipe, and no one else could read it out from under us.
+ self.read_notify.write(0).map_err(|e| {
+ io::Error::new(
+ io::ErrorKind::Other,
+ format!("failed to write to read notifier: {:?}", e),
+ )
+ })?;
+
+ // Notifier state has been safely synced.
+ return res;
+ }
+
+ // At this point, there *may* be no data in the pipe, meaning we may want to clear the
+ // notifier. Instead of just trying to acquire the lock outright which could deadlock
+ // with the writing side, we'll try with a timeout. If it fails, we know that the other
+ // side is in the middle of a write, so there either will be data in the pipe soon (a),
+ // or there won't be and we have to clear a spurious notification (b).
+ //
+ // For (a), we can safely return from the read without needing the lock, so we just come
+ // around in the loop to check again, and the loop will exit quickly.
+ //
+ // For (b) we'll return to this point and acquire the lock, as we're just waiting for
+ // the spurious notification to arrive so we can clear it (that code path is very fast),
+ // and the loop will exit.
+ //
+ // If we successfully acquire the lock though, then we can go ahead and clear the
+ // notifier if the pipe is indeed empty, because we are assured that no writes are
+ // happening (we hold the lock). Here, we wait up to 1ms to acquire the lock because
+ // that's a decent balance between avoiding an unnecessary iteration, and minimizing
+ // latency.
+ if let Some(_write_lock) = self.remote_write_lock.try_lock(/* timeout_ms= */ 1) {
+ let byte_count = self.get_readable_byte_count()?;
+ if byte_count > 0 {
+ // Safe because no one else can be reading from the pipe.
+ self.read_notify.write(0).map_err(|e| {
+ io::Error::new(
+ io::ErrorKind::Other,
+ format!("failed to write to read notifier: {:?}", e),
+ )
+ })?;
+ } else {
+ // Safe because no other writes can be happening (_lock is held).
+ self.read_notify.reset().map_err(|e| {
+ io::Error::new(
+ io::ErrorKind::Other,
+ format!("failed to reset read notifier: {:?}", e),
+ )
+ })?;
+ }
+
+ // Notifier state has been safely synced.
+ return res;
+ }
+ }
+ }
+
+ /// Exists as a workaround for Tube which does not expect its transport to be mutable,
+ /// even though io::Write requires it.
+ pub fn write_immutable(&self, buf: &[u8]) -> io::Result<usize> {
+ if self.pipe_conn.get_framing_mode() == named_pipes::FramingMode::Message
+ && buf.len() > self.send_buffer_size
+ {
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ format!(
+ "StreamChannel forbids message mode writes larger than the \
+ default buffer size of {}.",
+ self.send_buffer_size,
+ ),
+ ));
+ }
+
+ let _lock = self.local_write_lock.lock();
+ let res = self.pipe_conn.write(buf);
+
+ // We can always set the write notifier because we know that the reader is in one of the
+ // following states:
+ // 1) a read is running, and it consumes these bytes, so the notification is
+ // unnecessary. That's fine, because the reader will resync the notifier state once
+ // it finishes reading.
+ // 2) a read has completed and is blocked on the lock. The notification state is
+ // already correct, and the read's resync won't change that.
+ if res.is_ok() {
+ self.write_notify.write(0).map_err(|e| {
+ io::Error::new(
+ io::ErrorKind::Other,
+ format!("failed to write to read notifier: {:?}", e),
+ )
+ })?;
+ }
+
+ res
+ }
+
+ /// This only works with empty pipes. U.B. will result if used in any other scenario.
+ pub fn from_pipes(
+ pipe_a: PipeConnection,
+ pipe_b: PipeConnection,
+ send_buffer_size: usize,
+ ) -> Result<(StreamChannel, StreamChannel)> {
+ let (notify_a_write, notify_b_write) = (Event::new()?, Event::new()?);
+ let pipe_closed = Event::new()?;
+
+ let write_lock_a = MultiProcessMutex::new()?;
+ let write_lock_b = MultiProcessMutex::new()?;
+
+ let sock_a = StreamChannel {
+ pipe_conn: pipe_a,
+ write_notify: notify_a_write.try_clone()?,
+ read_notify: notify_b_write.try_clone()?,
+ read_lock: Arc::new(Mutex::new(())),
+ local_write_lock: write_lock_a.try_clone()?,
+ remote_write_lock: write_lock_b.try_clone()?,
+ pipe_closed: pipe_closed.try_clone()?,
+ is_channel_closed_on_drop: create_true_cell(),
+ send_buffer_size,
+ };
+ let sock_b = StreamChannel {
+ pipe_conn: pipe_b,
+ write_notify: notify_b_write,
+ read_notify: notify_a_write,
+ read_lock: Arc::new(Mutex::new(())),
+ local_write_lock: write_lock_b,
+ remote_write_lock: write_lock_a,
+ pipe_closed,
+ is_channel_closed_on_drop: create_true_cell(),
+ send_buffer_size,
+ };
+ Ok((sock_a, sock_b))
+ }
+
+ /// Create a pair with a specific buffer size. Note that this is the only way to send messages
+ /// larger than the default named pipe buffer size.
+ pub fn pair_with_buffer_size(
+ blocking_mode: BlockingMode,
+ framing_mode: FramingMode,
+ buffer_size: usize,
+ ) -> Result<(StreamChannel, StreamChannel)> {
+ let (pipe_a, pipe_b) = named_pipes::pair_with_buffer_size(
+ &named_pipes::FramingMode::from(framing_mode),
+ &named_pipes::BlockingMode::from(blocking_mode),
+ 0,
+ buffer_size,
+ false,
+ )?;
+ Self::from_pipes(pipe_a, pipe_b, buffer_size)
+ }
+ /// Creates a cross platform channel pair.
+ /// On Windows the result is in the form (server, client).
+ pub fn pair(
+ blocking_mode: BlockingMode,
+ framing_mode: FramingMode,
+ ) -> Result<(StreamChannel, StreamChannel)> {
+ let (pipe_a, pipe_b) = named_pipes::pair_with_buffer_size(
+ &named_pipes::FramingMode::from(framing_mode),
+ &named_pipes::BlockingMode::from(blocking_mode),
+ 0,
+ DEFAULT_BUFFER_SIZE,
+ false,
+ )?;
+ Self::from_pipes(pipe_a, pipe_b, DEFAULT_BUFFER_SIZE)
+ }
+
+ /// Blocks until the pipe buffer is empty.
+ /// NOTE: that this will only work for server pipes on Windows.
+ pub fn flush_blocking(&self) -> io::Result<()> {
+ self.pipe_conn.flush_data_blocking().map_err(|e| e)
+ }
+}
+
+impl io::Write for StreamChannel {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.write_immutable(buf)
+ }
+ fn flush(&mut self) -> io::Result<()> {
+ // There is no userspace buffering inside crosvm to flush for named pipes. We write
+ // directly to the named pipe using WriteFile.
+ Ok(())
+ }
+}
+
+impl AsRawDescriptor for &StreamChannel {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.pipe_conn.as_raw_descriptor()
+ }
+}
+
+impl ReadNotifier for StreamChannel {
+ /// Returns a RawDescriptor that can be polled for reads using PollContext.
+ fn get_read_notifier(&self) -> &dyn AsRawDescriptor {
+ &self.read_notify
+ }
+}
+
+impl CloseNotifier for StreamChannel {
+ fn get_close_notifier(&self) -> &dyn AsRawDescriptor {
+ &self.pipe_closed
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{
+ super::super::{EventContext, EventTrigger, PollToken, ReadNotifier},
+ *,
+ };
+ use std::{
+ io::{Read, Write},
+ time::Duration,
+ };
+
+ #[derive(PollToken, Debug, Eq, PartialEq, Copy, Clone)]
+ enum Token {
+ ReceivedData,
+ }
+
+ const EVENT_WAIT_TIME: Duration = Duration::from_secs(10);
+
+ #[test]
+ fn test_read_notifies_multiple_writes() {
+ let (mut sender, mut receiver) =
+ StreamChannel::pair(BlockingMode::Blocking, FramingMode::Byte).unwrap();
+ sender.write_all(&[1, 2]).unwrap();
+
+ // Wait for the write to arrive.
+ let event_ctx: EventContext<Token> = EventContext::build_with(&[EventTrigger::from(
+ receiver.get_read_notifier(),
+ Token::ReceivedData,
+ )])
+ .unwrap();
+ assert_eq!(event_ctx.wait_timeout(EVENT_WAIT_TIME).unwrap().len(), 1);
+
+ // Read just one byte. This leaves another byte in the pipe.
+ let mut recv_buffer = [0u8; 1];
+ let size = receiver.read(&mut recv_buffer).unwrap();
+ assert_eq!(size, 1);
+ assert_eq!(recv_buffer[0], 1);
+
+ // The notifier should still be set, because the pipe has data.
+ assert_eq!(event_ctx.wait_timeout(EVENT_WAIT_TIME).unwrap().len(), 1);
+ let size = receiver.read(&mut recv_buffer).unwrap();
+ assert_eq!(size, 1);
+ assert_eq!(recv_buffer[0], 2);
+ }
+
+ #[test]
+ fn test_blocked_writer_wont_deadlock() {
+ let (mut writer, mut reader) =
+ StreamChannel::pair_with_buffer_size(BlockingMode::Blocking, FramingMode::Byte, 100)
+ .unwrap();
+ const NUM_OPS: usize = 100;
+
+ // We set the buffer size to 100 bytes. It seems that we must exceed that buffer size by
+ // 100x before we run into a blocking write, so that's what we do here. This makes sense
+ // to a degree because the docs suggest that some automatic expansion of a pipe's buffers
+ // is supposed to be handled by the kernel.
+ let writer = std::thread::spawn(move || {
+ let buf = [0u8; 100];
+ for _ in 0..NUM_OPS {
+ assert_eq!(writer.write(&buf).unwrap(), buf.len());
+ }
+ writer
+ });
+
+ // The test passes if the reader can read (this used to deadlock).
+ let mut buf = [0u8; 100];
+ for _ in 0..NUM_OPS {
+ assert_eq!(reader.read(&mut buf).unwrap(), buf.len());
+ }
+
+ // Writer must exit cleanly.
+ writer.join().unwrap();
+ }
+}
diff --git a/base/src/windows/win/syslog.rs b/base/src/windows/win/syslog.rs
new file mode 100644
index 000000000..40507724a
--- /dev/null
+++ b/base/src/windows/win/syslog.rs
@@ -0,0 +1,38 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Implementation of the Syslog trait as a wrapper around Window's events
+
+use super::super::{
+ syslog::{Error, Facility, Priority, Syslog},
+ RawDescriptor,
+};
+
+pub struct PlatformSyslog {
+ enabled: bool,
+}
+
+impl Syslog for PlatformSyslog {
+ fn new() -> Result<Self, Error> {
+ Ok(Self { enabled: true })
+ }
+
+ fn enable(&mut self, enable: bool) -> Result<(), Error> {
+ self.enabled = enable;
+ Ok(())
+ }
+
+ fn push_descriptors(&self, _fds: &mut Vec<RawDescriptor>) {}
+
+ fn log(
+ &self,
+ _proc_name: Option<&str>,
+ _pri: Priority,
+ _fac: Facility,
+ _file_line: Option<(&str, u32)>,
+ _args: std::fmt::Arguments,
+ ) {
+ // do nothing. We don't plan to support writing to windows system logs.
+ }
+}
diff --git a/base/src/windows/win/timer.rs b/base/src/windows/win/timer.rs
new file mode 100644
index 000000000..24d3d2d48
--- /dev/null
+++ b/base/src/windows/win/timer.rs
@@ -0,0 +1,159 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ os::windows::io::{AsRawHandle, RawHandle},
+ ptr,
+ time::Duration,
+};
+
+use win_util::{LargeInteger, SecurityAttributes, SelfRelativeSecurityDescriptor};
+use winapi::{
+ shared::{minwindef::FALSE, winerror::WAIT_TIMEOUT},
+ um::{
+ synchapi::{CancelWaitableTimer, SetWaitableTimer, WaitForSingleObject},
+ winbase::{CreateWaitableTimerA, INFINITE, WAIT_OBJECT_0},
+ },
+};
+
+use super::{
+ super::{errno_result, win::nt_query_timer_resolution, Result},
+ Timer, WaitResult,
+};
+use crate::descriptor::{AsRawDescriptor, FromRawDescriptor, SafeDescriptor};
+
+impl AsRawHandle for Timer {
+ fn as_raw_handle(&self) -> RawHandle {
+ self.handle.as_raw_descriptor()
+ }
+}
+
+impl Timer {
+ /// Creates a new timer. The timer is initally disarmed and must be armed by calling
+ /// `reset`.
+ pub fn new() -> Result<Timer> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let handle = unsafe {
+ CreateWaitableTimerA(
+ // Not inheritable, duplicate before passing to child prcesses
+ SecurityAttributes::new_with_security_descriptor(
+ SelfRelativeSecurityDescriptor::get_singleton(),
+ /* inherit= */ false,
+ )
+ .as_mut(),
+ // This is a synchronization timer, not a manual-reset timer.
+ FALSE,
+ // TODO (colindr) b/145622516 - we may have to give this a name if we later
+ // want to use names to test object equality
+ ptr::null_mut(),
+ )
+ };
+
+ if handle.is_null() {
+ return errno_result();
+ }
+
+ // Safe because we uniquely own the file descriptor.
+ Ok(Timer {
+ handle: unsafe { SafeDescriptor::from_raw_descriptor(handle) },
+ interval: None,
+ })
+ }
+
+ /// Sets the timer to expire after `dur`. If `interval` is not `None` and non-zero
+ /// it represents the period for repeated expirations after the initial expiration.
+ /// Otherwise the timer will expire just once. Cancels any existing duration and
+ /// repeating interval.
+ pub fn reset(&mut self, dur: Duration, mut interval: Option<Duration>) -> Result<()> {
+ // If interval is 0 or None it means that this timer does not repeat. We
+ // set self.interval to None in this case so it can easily be checked
+ // in self.wait.
+ if interval == Some(Duration::from_secs(0)) {
+ interval = None;
+ }
+ self.interval = interval;
+ // Windows timers use negative values for relative times, and positive
+ // values for absolute times, so we'll use negative times.
+
+ // Windows timers also use a 64 number of 100 nanosecond intervals,
+ // which we get like so: (dur.as_secs()*1e7 + dur.subsec_nanos()/100)
+
+ let due_time = LargeInteger::new(
+ -((dur.as_secs() * 10_000_000 + (dur.subsec_nanos() as u64) / 100) as i64),
+ );
+ let period: i32 = match interval {
+ Some(int) => int.as_millis() as i32,
+ None => 0,
+ };
+
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe {
+ SetWaitableTimer(
+ self.as_raw_descriptor(),
+ &*due_time,
+ period,
+ None, // no completion routine
+ ptr::null_mut(), // or routine argument
+ FALSE, // no restoring system from power conservation mode
+ )
+ };
+ if ret == 0 {
+ return errno_result();
+ }
+
+ Ok(())
+ }
+
+ /// Waits until the timer expires, returing WaitResult::Expired when it expires.
+ ///
+ /// If timeout is not None, block for a maximum of the given `timeout` duration.
+ /// If a timeout occurs, return WaitResult::Timeout.
+ pub fn wait(&mut self, timeout: Option<Duration>) -> Result<WaitResult> {
+ let timeout = match timeout {
+ None => INFINITE,
+ Some(dur) => dur.as_millis() as u32,
+ };
+
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe { WaitForSingleObject(self.as_raw_descriptor(), timeout) };
+
+ // Should return WAIT_OBJECT_0, otherwise it's some sort of error or
+ // timeout (which shouldn't happen in this case).
+ match ret {
+ WAIT_OBJECT_0 => Ok(WaitResult::Expired),
+ WAIT_TIMEOUT => Ok(WaitResult::Timeout),
+ _ => errno_result(),
+ }
+ }
+
+ /// After a timer is triggered from an EventContext, mark the timer as having been waited for.
+ /// If a timer is not marked waited, it will immediately trigger the event context again. This
+ /// does not need to be called after calling Timer::wait.
+ ///
+ /// Returns true if the timer has been adjusted since the EventContext was triggered by this
+ /// timer.
+ pub fn mark_waited(&mut self) -> Result<bool> {
+ // We use a synchronization timer on windows, meaning waiting on the timer automatically
+ // un-signals the timer. We assume this is atomic so the return value is always false.
+ Ok(false)
+ }
+
+ /// Disarms the timer.
+ pub fn clear(&mut self) -> Result<()> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe { CancelWaitableTimer(self.as_raw_descriptor()) };
+
+ if ret == 0 {
+ return errno_result();
+ }
+
+ self.interval = None;
+ Ok(())
+ }
+
+ /// Returns the resolution of timers on the host.
+ pub fn resolution() -> Result<Duration> {
+ nt_query_timer_resolution().map(|(current_res, _)| current_res)
+ }
+}
diff --git a/base/src/windows/win/wait.rs b/base/src/windows/win/wait.rs
new file mode 100644
index 000000000..892cda3de
--- /dev/null
+++ b/base/src/windows/win/wait.rs
@@ -0,0 +1,262 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ cmp::min,
+ collections::HashMap,
+ os::windows::io::RawHandle,
+ sync::{Arc, Mutex},
+ time::Duration,
+};
+
+use winapi::{
+ shared::{
+ minwindef::{DWORD, FALSE},
+ winerror::{ERROR_INVALID_PARAMETER, WAIT_TIMEOUT},
+ },
+ um::{synchapi::WaitForMultipleObjects, winbase::WAIT_OBJECT_0},
+};
+
+use super::super::{
+ errno_result, Error, Event, EventTrigger, EventType, PollToken, Result, TriggeredEvent,
+};
+use crate::descriptor::{AsRawDescriptor, Descriptor};
+use crate::error;
+// MAXIMUM_WAIT_OBJECTS = 64
+pub const MAXIMUM_WAIT_OBJECTS: usize = winapi::um::winnt::MAXIMUM_WAIT_OBJECTS as usize;
+
+// TODO(145170451) rizhang: implement round robin if event size is greater than 64
+
+struct RegisteredHandles<T: PollToken> {
+ triggers: HashMap<Descriptor, T>,
+ raw_handles: Vec<Descriptor>,
+}
+
+pub struct EventContext<T: PollToken> {
+ registered_handles: Arc<Mutex<RegisteredHandles<T>>>,
+
+ // An internally-used event to signify that the list of handles has been modified
+ // mid-wait. This is to solve for instances where Thread A has started waiting and
+ // Thread B adds an event trigger, which needs to notify Thread A a change has been
+ // made.
+ handles_modified_event: Event,
+}
+
+impl<T: PollToken> EventContext<T> {
+ pub fn new() -> Result<EventContext<T>> {
+ let new = EventContext {
+ registered_handles: Arc::new(Mutex::new(RegisteredHandles {
+ triggers: HashMap::new(),
+ raw_handles: Vec::new(),
+ })),
+ handles_modified_event: Event::new().unwrap(),
+ };
+ // The handles-modified event will be everpresent on the raw_handles to be waited
+ // upon to ensure the wait stops and we update it any time the handles list is
+ // modified.
+ new.registered_handles
+ .lock()
+ .unwrap()
+ .raw_handles
+ .push(Descriptor(new.handles_modified_event.as_raw_descriptor()));
+ Ok(new)
+ }
+
+ /// Creates a new EventContext with the the associated triggers.
+ pub fn build_with(triggers: &[EventTrigger<T>]) -> Result<EventContext<T>> {
+ let ctx = EventContext::new()?;
+ ctx.add_many(triggers)?;
+ Ok(ctx)
+ }
+
+ /// Adds a trigger to the EventContext.
+ pub fn add(&self, trigger: EventTrigger<T>) -> Result<()> {
+ self.add_for_event(trigger, EventType::Read)
+ }
+
+ /// Adds a trigger to the EventContext.
+ pub fn add_many(&self, triggers: &[EventTrigger<T>]) -> Result<()> {
+ for trigger in triggers {
+ self.add(trigger.clone())?
+ }
+ Ok(())
+ }
+
+ pub fn add_for_event(&self, trigger: EventTrigger<T>, _event_type: EventType) -> Result<()> {
+ let mut registered_handles_locked = self.registered_handles.lock().unwrap();
+ if registered_handles_locked
+ .triggers
+ .contains_key(&Descriptor(trigger.event))
+ {
+ // If this handle is already added, silently succeed with a noop
+ return Ok(());
+ }
+ registered_handles_locked
+ .triggers
+ .insert(Descriptor(trigger.event), trigger.token);
+ registered_handles_locked
+ .raw_handles
+ .push(Descriptor(trigger.event));
+ // Windows doesn't support watching for specific types of events. Just treat this
+ // like a normal add and do nothing with event_type
+ self.handles_modified_event.write(1)
+ }
+
+ pub fn modify(&self, trigger: EventTrigger<T>, _event_type: EventType) -> Result<()> {
+ let mut registered_handles_locked = self.registered_handles.lock().unwrap();
+ if let std::collections::hash_map::Entry::Occupied(mut e) = registered_handles_locked
+ .triggers
+ .entry(Descriptor(trigger.event))
+ {
+ e.insert(trigger.token);
+ }
+ // Windows doesn't support watching for specific types of events. Ignore the event_type
+ // and just modify the token.
+ self.handles_modified_event.write(1)
+ }
+
+ pub fn remove(&self, event_handle: &dyn AsRawDescriptor) -> Result<()> {
+ let mut registered_handles_locked = self.registered_handles.lock().unwrap();
+ let result = registered_handles_locked
+ .triggers
+ .remove(&Descriptor(event_handle.as_raw_descriptor()));
+ if result.is_none() {
+ // this handle was not registered in the first place. Silently succeed with a noop
+ return Ok(());
+ }
+ let index = registered_handles_locked
+ .raw_handles
+ .iter()
+ .position(|item| item == &Descriptor(event_handle.as_raw_descriptor()))
+ .unwrap();
+ registered_handles_locked.raw_handles.remove(index);
+ self.handles_modified_event.write(1)
+ }
+
+ pub fn clear(&self) -> Result<()> {
+ let mut registered_handles_locked = self.registered_handles.lock().unwrap();
+ registered_handles_locked.triggers.clear();
+ registered_handles_locked.raw_handles.clear();
+
+ registered_handles_locked
+ .raw_handles
+ .push(Descriptor(self.handles_modified_event.as_raw_descriptor()));
+ self.handles_modified_event.write(1)
+ }
+
+ /// Waits for one or more of the registered triggers to become signaled.
+ pub fn wait(&self) -> Result<Vec<TriggeredEvent<T>>> {
+ self.wait_timeout(Duration::new(i64::MAX as u64, 0))
+ }
+
+ pub fn wait_timeout(&self, timeout: Duration) -> Result<Vec<TriggeredEvent<T>>> {
+ let raw_handles_list: Vec<RawHandle> = self
+ .registered_handles
+ .lock()
+ .unwrap()
+ .raw_handles
+ .clone()
+ .into_iter()
+ .map(|handle| handle.0)
+ .collect();
+ if raw_handles_list.len() == 1 {
+ // Disallow calls with no handles to wait on. Do not include the handles_modified_event
+ // which always populates the list.
+ return Err(Error::new(ERROR_INVALID_PARAMETER));
+ }
+ let result = unsafe {
+ WaitForMultipleObjects(
+ raw_handles_list.len() as DWORD,
+ raw_handles_list.as_ptr(),
+ FALSE, // return when one event is signaled
+ timeout.as_millis() as DWORD,
+ )
+ };
+ let handles_len = min(MAXIMUM_WAIT_OBJECTS, raw_handles_list.len()) as usize;
+
+ const MAXIMUM_WAIT_OBJECTS_U32: u32 = MAXIMUM_WAIT_OBJECTS as u32;
+ match result as u32 {
+ WAIT_OBJECT_0..=MAXIMUM_WAIT_OBJECTS_U32 => {
+ let mut event_index = (result - WAIT_OBJECT_0) as usize;
+ if event_index >= handles_len {
+ // This is not a valid index and should return an error. This case should not be possible
+ // and will likely not return a meaningful system error code, but is still an invalid case.
+ error!("Wait returned index out of range");
+ return errno_result();
+ }
+ if event_index == 0 {
+ // The handles list has been modified and triggered the wait, try again with the updated
+ // handles list. Note it is possible the list was modified again after the wait which will
+ // trigger the handles_modified_event again, but that will only err towards the safe side
+ // of recursing an extra time.
+ let _ = self.handles_modified_event.read();
+ return self.wait_timeout(timeout);
+ }
+
+ let mut events_to_return: Vec<TriggeredEvent<T>> = vec![];
+ // Multiple events may be triggered at once, but WaitForMultipleObjects will only return one.
+ // Once it returns, loop through the remaining triggers checking each to ensure they haven't
+ // also been triggered.
+ let mut handles_offset: usize = 0;
+ loop {
+ let event_to_return = raw_handles_list[event_index + handles_offset];
+ events_to_return.push(TriggeredEvent {
+ token: T::from_raw_token(
+ self.registered_handles
+ .lock()
+ .unwrap()
+ .triggers
+ .get(&Descriptor(event_to_return))
+ .unwrap()
+ .as_raw_token(),
+ ),
+ // In Windows, events aren't associated with read/writability, so for cross-
+ // compatability, associate with both.
+ is_readable: true,
+ is_writable: true,
+ is_hungup: false,
+ });
+
+ handles_offset += event_index + 1;
+ if handles_offset >= handles_len {
+ break;
+ }
+ event_index = (unsafe {
+ WaitForMultipleObjects(
+ (raw_handles_list.len() - handles_offset) as DWORD,
+ raw_handles_list[handles_offset..].as_ptr(),
+ FALSE, // return when one event is signaled
+ 0, /* instantaneous timeout */
+ )
+ } - WAIT_OBJECT_0) as usize;
+
+ if event_index >= (handles_len - handles_offset) {
+ // This indicates a failure condition, as return values greater than the length
+ // of the provided array are reserved for failures.
+ break;
+ }
+ }
+
+ Ok(events_to_return)
+ }
+ WAIT_TIMEOUT => Ok(vec![]),
+ // Invalid cases. This is most likely an WAIT_FAILED, but anything not matched by the
+ // above is an error case.
+ _ => errno_result(),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ #[should_panic]
+ fn error_on_empty_context_wait() {
+ let ctx: EventContext<u32> = EventContext::new().unwrap();
+ let dur = Duration::from_millis(10);
+ ctx.wait_timeout(dur).unwrap();
+ }
+}
diff --git a/base/src/windows/win/write_zeros.rs b/base/src/windows/win/write_zeros.rs
new file mode 100644
index 000000000..541e4d047
--- /dev/null
+++ b/base/src/windows/win/write_zeros.rs
@@ -0,0 +1,29 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::{PunchHole, WriteZeroesAt};
+use std::{cmp::min, fs::File, io, os::windows::fs::FileExt};
+
+impl WriteZeroesAt for File {
+ // TODO(b/195151495): Fix so that this will extend a file size if needed.
+ fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize> {
+ // Try to punch a hole first.
+ if let Ok(()) = self.punch_hole(offset, length as u64) {
+ return Ok(length);
+ }
+
+ // fall back to write()
+ // punch_hole() failed; fall back to writing a buffer of zeroes
+ // until we have written up to length.
+ let buf_size = min(length, 0x10000);
+ let buf = vec![0u8; buf_size];
+ let mut nwritten: usize = 0;
+ while nwritten < length {
+ let remaining = length - nwritten;
+ let write_size = min(remaining, buf_size);
+ nwritten += self.seek_write(&buf[0..write_size], offset + nwritten as u64)?;
+ }
+ Ok(length)
+ }
+}
diff --git a/base/src/windows/write_zeroes.rs b/base/src/windows/write_zeroes.rs
new file mode 100644
index 000000000..a6fac5ca1
--- /dev/null
+++ b/base/src/windows/write_zeroes.rs
@@ -0,0 +1,216 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ fs::File,
+ io::{
+ Error, ErrorKind, Seek, SeekFrom, {self},
+ },
+};
+
+#[path = "win/punch_hole.rs"]
+mod punch_hole;
+
+#[path = "win/write_zeros.rs"]
+mod write_zeros;
+
+use super::write_zeroes::punch_hole::execute_punch_hole;
+
+/// A trait for deallocating space in a file.
+pub trait PunchHole {
+ /// Replace a range of bytes with a hole.
+ fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()>;
+}
+
+impl PunchHole for File {
+ fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()> {
+ execute_punch_hole(self, offset, length)
+ }
+}
+
+/// A trait for writing zeroes to a stream.
+pub trait WriteZeroes {
+ /// Write up to `length` bytes of zeroes to the stream, returning how many bytes were written.
+ fn write_zeroes(&mut self, length: usize) -> io::Result<usize>;
+
+ /// Write zeroes to the stream until `length` bytes have been written.
+ ///
+ /// This method will continuously call `write_zeroes` until the requested
+ /// `length` is satisfied or an error is encountered.
+ fn write_zeroes_all(&mut self, mut length: usize) -> io::Result<()> {
+ while length > 0 {
+ match self.write_zeroes(length) {
+ Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
+ Ok(bytes_written) => {
+ length = length
+ .checked_sub(bytes_written)
+ .ok_or_else(|| Error::from(ErrorKind::Other))?
+ }
+ Err(e) => {
+ if e.kind() != ErrorKind::Interrupted {
+ return Err(e);
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+/// A trait for writing zeroes to an arbitrary position in a file.
+pub trait WriteZeroesAt {
+ /// Write up to `length` bytes of zeroes starting at `offset`, returning how many bytes were
+ /// written.
+ fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize>;
+
+ /// Write zeroes starting at `offset` until `length` bytes have been written.
+ ///
+ /// This method will continuously call `write_zeroes_at` until the requested
+ /// `length` is satisfied or an error is encountered.
+ fn write_zeroes_all_at(&mut self, mut offset: u64, mut length: usize) -> io::Result<()> {
+ while length > 0 {
+ match self.write_zeroes_at(offset, length) {
+ Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
+ Ok(bytes_written) => {
+ length = length
+ .checked_sub(bytes_written)
+ .ok_or_else(|| Error::from(ErrorKind::Other))?;
+ offset = offset
+ .checked_add(bytes_written as u64)
+ .ok_or_else(|| Error::from(ErrorKind::Other))?;
+ }
+ Err(e) => {
+ if e.kind() != ErrorKind::Interrupted {
+ return Err(e);
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+impl<T: WriteZeroesAt + Seek> WriteZeroes for T {
+ fn write_zeroes(&mut self, length: usize) -> io::Result<usize> {
+ let offset = self.seek(SeekFrom::Current(0))?;
+ let nwritten = self.write_zeroes_at(offset, length)?;
+ // Advance the seek cursor as if we had done a real write().
+ self.seek(SeekFrom::Current(nwritten as i64))?;
+ Ok(length)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::{
+ fs::OpenOptions,
+ io::{Read, Seek, SeekFrom, Write},
+ };
+ use tempfile::TempDir;
+
+ #[test]
+ fn simple_test() {
+ let tempdir = TempDir::new().unwrap();
+ let mut path = tempdir.path().to_owned();
+ path.push("file");
+ let mut f = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create(true)
+ .open(&path)
+ .unwrap();
+ f.set_len(16384).unwrap();
+
+ // Write buffer of non-zero bytes to offset 1234
+ let orig_data = [0x55u8; 5678];
+ f.seek(SeekFrom::Start(1234)).unwrap();
+ f.write_all(&orig_data).unwrap();
+
+ // Read back the data plus some overlap on each side
+ let mut readback = [0u8; 16384];
+ f.seek(SeekFrom::Start(0)).unwrap();
+ f.read_exact(&mut readback).unwrap();
+ // Bytes before the write should still be 0
+ for read in &readback[0..1234] {
+ assert_eq!(*read, 0);
+ }
+ // Bytes that were just written should be 0x55
+ for read in &readback[1234..(1234 + 5678)] {
+ assert_eq!(*read, 0x55);
+ }
+ // Bytes after the written area should still be 0
+ for read in &readback[(1234 + 5678)..] {
+ assert_eq!(*read, 0);
+ }
+
+ // Overwrite some of the data with zeroes
+ f.seek(SeekFrom::Start(2345)).unwrap();
+ f.write_zeroes_all(4321).expect("write_zeroes failed");
+ // Verify seek position after write_zeroes_all()
+ assert_eq!(f.seek(SeekFrom::Current(0)).unwrap(), 2345 + 4321);
+
+ // Read back the data and verify that it is now zero
+ f.seek(SeekFrom::Start(0)).unwrap();
+ f.read_exact(&mut readback).unwrap();
+ // Bytes before the write should still be 0
+ for read in &readback[0..1234] {
+ assert_eq!(*read, 0);
+ }
+ // Original data should still exist before the write_zeroes region
+ for read in &readback[1234..2345] {
+ assert_eq!(*read, 0x55);
+ }
+ // The write_zeroes region should now be zero
+ for read in &readback[2345..(2345 + 4321)] {
+ assert_eq!(*read, 0);
+ }
+ // Original data should still exist after the write_zeroes region
+ for read in &readback[(2345 + 4321)..(1234 + 5678)] {
+ assert_eq!(*read, 0x55);
+ }
+ // The rest of the file should still be 0
+ for read in &readback[(1234 + 5678)..] {
+ assert_eq!(*read, 0);
+ }
+ }
+
+ #[test]
+ fn large_write_zeroes() {
+ let tempdir = TempDir::new().unwrap();
+ let mut path = tempdir.path().to_owned();
+ path.push("file");
+ let mut f = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create(true)
+ .open(&path)
+ .unwrap();
+ f.set_len(16384).unwrap();
+
+ // Write buffer of non-zero bytes
+ let orig_data = [0x55u8; 0x20000];
+ f.seek(SeekFrom::Start(0)).unwrap();
+ f.write_all(&orig_data).unwrap();
+
+ // Overwrite some of the data with zeroes
+ f.seek(SeekFrom::Start(0)).unwrap();
+ f.write_zeroes_all(0x10001).expect("write_zeroes failed");
+ // Verify seek position after write_zeroes_all()
+ assert_eq!(f.seek(SeekFrom::Current(0)).unwrap(), 0x10001);
+
+ // Read back the data and verify that it is now zero
+ let mut readback = [0u8; 0x20000];
+ f.seek(SeekFrom::Start(0)).unwrap();
+ f.read_exact(&mut readback).unwrap();
+ // The write_zeroes region should now be zero
+ for read in &readback[0..0x10001] {
+ assert_eq!(*read, 0);
+ }
+ // Original data should still exist after the write_zeroes region
+ for read in &readback[0x10001..0x20000] {
+ assert_eq!(*read, 0x55);
+ }
+ }
+}
diff --git a/bin/clippy b/bin/clippy
index 0e930793c..9aa418e9a 100755
--- a/bin/clippy
+++ b/bin/clippy
@@ -1,99 +1,5 @@
#!/bin/bash
-
-# Copyright 2019 The Chromium OS Authors. All rights reserved.
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-
-# Run `cargo clippy` on all Rust code in crosvm with a mindful set of lints
-# suppressed.
-
-set -eo pipefail
-
-USE_CACHE=false
-CLIPPY_ARGS=("$@")
-
-# TODO: When we add more options, use a fancier parsing mechanism such as
-# getopts. Also use the Rust convention of -- separating the arguments for this
-# script itself from the ones for clippy.
-if (("$#" > 0)) && [[ "$1" == "--use-cache" ]]; then
- USE_CACHE=true
- CLIPPY_ARGS=("${CLIPPY_ARGS[@]:1}")
-fi
-
-# Change into directory of script, which is crosvm/bin.
-cd "$(dirname "${BASH_SOURCE[0]}")"
-
-# Jump up to root directory of crosvm repo.
-cd ..
-
-SUPPRESS=(
- # TODO(crbug/908640): To be resolved.
- borrowed_box
- char_lit_as_u8
- clone_on_copy
- collapsible_if
- comparison_chain
- extra_unused_lifetimes
- for_kv_map
- inefficient_to_string
- into_iter_on_ref
- let_unit_value
- missing_safety_doc
- needless_range_loop
- needless_return
- option_map_unit_fn
- question_mark
- range_plus_one
- redundant_clone
- redundant_closure
- single_match
- slow_vector_initialization
- unnecessary_filter_map
- unnecessary_mut_passed
- unneeded_field_pattern
- useless_format
- wrong_self_convention
-
- # False positives affecting WlVfd @ `devices/src/virtio/wl.rs`.
- # Bug: https://github.com/rust-lang/rust-clippy/issues/6312
- field_reassign_with_default
-
- # We don't care about these lints. Okay to remain suppressed globally.
- blacklisted_name
- cast_lossless
- cognitive_complexity
- enum_variant_names
- identity_op
- len_without_is_empty
- len_zero
- match_bool
- match_wild_err_arm
- module_inception
- needless_bool
- new_without_default
- or_fun_call
- should_implement_trait
- single_char_pattern
- too_many_arguments
- transmute_ptr_to_ptr
- trivially_copy_pass_by_ref
- type_complexity
- unreadable_literal
- useless_let_if_seq
- useless_transmute
- new-ret-no-self
-)
-
-# Needed or else clippy won't re-run on code that has already compiled.
-if [[ "${USE_CACHE}" == false ]]; then
- cargo clean
-fi
-
-# Need to set pass --sysroot for cargo-clippy manually.
-# cf. https://github.com/rust-lang/rust-clippy/issues/3523
-RUST_SYSROOT=$(rustc --print sysroot)
-RUSTFLAGS="${RUSTFLAGS:-}"
-export RUSTFLAGS="$RUSTFLAGS --sysroot=$RUST_SYSROOT"
-
-cargo clippy --all-features --all-targets -- ${SUPPRESS[@]/#/-Aclippy::} \
- "${CLIPPY_ARGS[@]}" -D warnings
+echo "./bin/clippy is deprecated. Please use: ./tools/clippy"
diff --git a/bin/crate_coverage b/bin/crate_coverage
deleted file mode 100755
index 4319ce7c6..000000000
--- a/bin/crate_coverage
+++ /dev/null
@@ -1,36 +0,0 @@
-#!/bin/bash
-# Copyright 2020 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Calculates coverage for the specified crate only
-# Usage:
-# $ ./bin/crate_coverage arch [additional arguments for cargo test]
-# Requirements:
-# $ rustup toolchain install nightly
-# $ cargo install grcov rust-covfix
-set -ex
-cd "${0%/*}/../"
-
-target_dir=$(
- cargo metadata --no-deps --format-version 1 |
- jq -r ".target_directory"
-)
-
-# Delete old coverage profiles
-find "$target_dir/debug" -name "*.gcda" -delete
-
-# Run test with coverage profiling
-(cd $1 && CARGO_INCREMENTAL=0 \
- RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Coverflow-checks=off \
--Zpanic_abort_tests" \
- cargo +nightly test "${@:2}")
-
-# Calculate code coverage
-grcov "$target_dir/debug" -s . \
- --ignore "/*" --ignore-not-existing \
- -t lcov --llvm --branch \
- -o /tmp/lcov.info
-
-# Apply code coverage fixes
-rust-covfix /tmp/lcov.info >lcov.info
diff --git a/bin/fmt b/bin/fmt
index 60070dddc..7a9ff8ee0 100755
--- a/bin/fmt
+++ b/bin/fmt
@@ -1,29 +1,5 @@
#!/bin/bash
-
-# Copyright 2019 The Chromium OS Authors. All rights reserved.
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-
-# Run `rustfmt` on all Rust code contained in crosvm. This is different from
-# `cargo fmt --all` which formats multiple crates but a single workspace only.
-# Crosvm consists of multiple workspaces.
-#
-# Usage:
-#
-# $ bin/fmt
-#
-# To print a diff and exit 1 if code is not formatted, but without changing any
-# files, use:
-#
-# $ bin/fmt --check
-#
-
-set -euo pipefail
-
-# Change into directory of script, which is crosvm/bin.
-cd "$(dirname "${BASH_SOURCE[0]}")"
-
-# Jump up to root directory of crosvm repo.
-cd ..
-
-find . -name '*.rs' -print0 | grep -vz '^./target/' | xargs -0 rustfmt --edition=2018 "$@" --
+echo "./bin/fmt is deprecated. Please use: ./tools/fmt"
diff --git a/bin/preupload-clippy b/bin/preupload-clippy
deleted file mode 100755
index ea32f93de..000000000
--- a/bin/preupload-clippy
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/bin/bash
-
-# Copyright 2020 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-SCRIPT_PATH="$(dirname "$(realpath "$0")")"
-exec "${SCRIPT_PATH}"/clippy --use-cache "$@"
diff --git a/bin/sync_ebuild_files b/bin/sync_ebuild_files
deleted file mode 100755
index 54b6ec981..000000000
--- a/bin/sync_ebuild_files
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Run inside `cros_sdk` to uprev dependency versions between Cargo.lock and
-# chromiumos-overlay ebuild files.
-
-import re
-import glob
-import os
-import sys
-import subprocess
-from distutils.version import StrictVersion
-from datetime import datetime
-
-# Ideally we would install a toml parser, but this will do for a quick little
-# tool.
-CARGO_LOCK_REGEX = re.compile(
- r"""name = "([^"]+)"
-version = "([^"]+)"
-source ="""
-)
-
-IGNORED_PACKAGES = ["pin-utils", "pkg-config"]
-
-DEV_RUST_PATH = "../../third_party/chromiumos-overlay/dev-rust"
-EBUILD_FILE_GLOB = f"{DEV_RUST_PATH}/*/*.ebuild"
-EBUILD_FILE_REGEX = re.compile(r"([\w\-_]+)-([0-9\.]+)\.ebuild")
-YEAR = datetime.today().year
-
-
-def ebuild_template(package: str):
- return f"""\
-# Copyright {YEAR} The Chromium OS Authors. All rights reserved.
-# Distributed under the terms of the GNU General Public License v2
-
-EAPI="7"
-
-CROS_RUST_REMOVE_DEV_DEPS=1
-
-inherit cros-rust
-
-DESCRIPTION="Build file for the {package} crate."
-HOMEPAGE="https://crates.io/crates/{package}"
-SRC_URI="https://crates.io/api/v1/crates/${{PN}}/${{PV}}/download -> ${{P}}.crate"
-
-LICENSE="|| ( MIT Apache-2.0 )"
-SLOT="${{PV}}/${{PR}}"
-KEYWORDS="*"
-
-# TODO: Add crate dependencies
-DEPEND=""
-"""
-
-
-def ebuild_file_path(package: str, version: str):
- return f"{DEV_RUST_PATH}/{package}/{package}-{version}.ebuild"
-
-
-def parse_cargo_lock():
- """Parses Cargo.lock file and returns (package, version) pairs."""
- with open("Cargo.lock", "r") as lock_file:
- lock_str = lock_file.read()
- for match in CARGO_LOCK_REGEX.finditer(lock_str):
- yield (match.group(1), match.group(2))
-
-
-def all_ebuild_versions():
- """Returns (package, version) pairs of ebuild files in dev-rust."""
- for ebuild_path in glob.glob(EBUILD_FILE_GLOB):
- ebuild_name = os.path.basename(ebuild_path)
- match = EBUILD_FILE_REGEX.match(ebuild_name)
- if match:
- yield (match.group(1), match.group(2))
-
-
-def ebuild_versions_dict():
- """Returns a dict of package versions of all ebuild files in dev-rust."""
- versions: dict[str, list[str]] = {}
- for (package, version) in all_ebuild_versions():
- if package in versions:
- versions[package].append(version)
- versions[package].sort(key=StrictVersion)
- else:
- versions[package] = [version]
- return versions
-
-
-def update_manifest(package: str, version: str):
- """Regenerate ebuild manifest for the provided package/version."""
- cmd = ["ebuild", ebuild_file_path(package, version), "manifest"]
- print(" ", " ".join(cmd))
- subprocess.run(cmd)
-
-
-def uprev_ebuild(package: str, new_version: str, old_version: str):
- """Updates the ebuild file from `old_version` to `new_version`."""
- old_path = ebuild_file_path(package, old_version)
- new_path = ebuild_file_path(package, new_version)
- print(f" {old_path} -> {new_path}")
- os.rename(old_path, new_path)
- update_manifest(package, new_version)
-
-
-def add_ebuild(package: str, version: str):
- """Creates a new ebuild file for the provided package."""
- ebuild_path = ebuild_file_path(package, version)
- print(f" Writing {ebuild_path}")
- open(ebuild_path, "w").write(ebuild_template(package))
- update_manifest(package, version)
-
-
-def update_cargo(package: str, latest_version: str):
- """Runs `cargo update` to update the version in Cargo.lock."""
- cmd = ["cargo", "update", "-p", package, "--precise", latest_version]
- print(" ", " ".join(cmd))
- subprocess.run(cmd)
-
-
-def confirm(question: str):
- print(f"{question} [y/N]")
- return sys.stdin.readline().strip().lower() == "y"
-
-
-def main():
- ebuild_packages = ebuild_versions_dict()
- for (package, cargo_version) in parse_cargo_lock():
- ebuild_versions = ebuild_packages.get(package, [])
- if package in IGNORED_PACKAGES:
- continue
- if cargo_version in ebuild_versions:
- continue
- if not ebuild_versions:
- print(f"{package}: No ebuild file.")
- if confirm("Create ebuild?"):
- add_ebuild(package, cargo_version)
- elif StrictVersion(ebuild_versions[-1]) > StrictVersion(cargo_version):
- print(
- f"{package}: Cargo version {cargo_version} is older than "
- f"latest ebuild version ({', '.join(ebuild_versions)})."
- )
- if confirm("Update Cargo.lock?"):
- update_cargo(package, ebuild_versions[-1])
- else:
- print(
- f"{package}: Ebuild versions ({', '.join(ebuild_versions)}) "
- f"are older than cargo version {cargo_version}."
- )
- if confirm("Uprev ebuild?"):
- uprev_ebuild(package, cargo_version, ebuild_versions[-1])
-
-
-if __name__ == "__main__":
- main()
diff --git a/bit_field/Android.bp b/bit_field/Android.bp
index e421b4aa0..798275c12 100644
--- a/bit_field/Android.bp
+++ b/bit_field/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace --no-subdir.
+// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -11,45 +11,23 @@ package {
}
rust_defaults {
- name: "bit_field_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "bit_field",
- srcs: ["src/lib.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- proc_macros: ["libbit_field_derive"],
-}
-
-rust_test_host {
- name: "bit_field_host_test_src_lib",
- defaults: ["bit_field_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "bit_field_device_test_src_lib",
- defaults: ["bit_field_defaults"],
-}
-
-rust_defaults {
- name: "bit_field_defaults_bit_field",
+ name: "bit_field_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "bit_field",
+ cargo_env_compat: true,
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbit_field",
],
proc_macros: ["libbit_field_derive"],
}
-rust_test_host {
- name: "bit_field_host_test_tests_test_enum",
- defaults: ["bit_field_defaults_bit_field"],
+rust_test {
+ name: "bit_field_test_tests_test_enum",
+ defaults: ["bit_field_test_defaults"],
+ host_supported: true,
srcs: ["tests/test_enum.rs"],
test_options: {
unit_test: true,
@@ -57,38 +35,22 @@ rust_test_host {
}
rust_test {
- name: "bit_field_device_test_tests_test_enum",
- defaults: ["bit_field_defaults_bit_field"],
- srcs: ["tests/test_enum.rs"],
-}
-
-rust_test_host {
- name: "bit_field_host_test_tests_test_tuple_struct",
- defaults: ["bit_field_defaults_bit_field"],
+ name: "bit_field_test_tests_test_tuple_struct",
+ defaults: ["bit_field_test_defaults"],
+ host_supported: true,
srcs: ["tests/test_tuple_struct.rs"],
test_options: {
unit_test: true,
},
}
-rust_test {
- name: "bit_field_device_test_tests_test_tuple_struct",
- defaults: ["bit_field_defaults_bit_field"],
- srcs: ["tests/test_tuple_struct.rs"],
-}
-
rust_library {
name: "libbit_field",
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "bit_field",
+ cargo_env_compat: true,
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
proc_macros: ["libbit_field_derive"],
}
-
-// dependent_library ["feature_list"]
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// syn-1.0.70 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// unicode-xid-0.2.1 "default"
diff --git a/bit_field/Cargo.toml b/bit_field/Cargo.toml
index ac8846ca1..540412f94 100644
--- a/bit_field/Cargo.toml
+++ b/bit_field/Cargo.toml
@@ -2,7 +2,7 @@
name = "bit_field"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
bit_field_derive = { path = "bit_field_derive" }
diff --git a/bit_field/bit_field_derive/Android.bp b/bit_field/bit_field_derive/Android.bp
index c667ae41f..b71e8a713 100644
--- a/bit_field/bit_field_derive/Android.bp
+++ b/bit_field/bit_field_derive/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -10,36 +10,17 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_test_host {
- name: "bit_field_derive_host_test_bit_field_derive",
- defaults: ["crosvm_defaults"],
- crate_name: "bit_field_derive",
- srcs: ["bit_field_derive.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libproc_macro2",
- "libquote",
- "libsyn",
- ],
-}
-
rust_proc_macro {
name: "libbit_field_derive",
- defaults: ["crosvm_proc_macro_defaults"],
+ defaults: ["crosvm_defaults"],
crate_name: "bit_field_derive",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["bit_field_derive.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libproc_macro2",
"libquote",
"libsyn",
],
}
-
-// dependent_library ["feature_list"]
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// syn-1.0.70 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// unicode-xid-0.2.1 "default"
diff --git a/bit_field/bit_field_derive/Cargo.toml b/bit_field/bit_field_derive/Cargo.toml
index 4642b9389..dddf7b4e2 100644
--- a/bit_field/bit_field_derive/Cargo.toml
+++ b/bit_field/bit_field_derive/Cargo.toml
@@ -2,7 +2,7 @@
name = "bit_field_derive"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
proc-macro2 = "^1"
diff --git a/bit_field/bit_field_derive/bit_field_derive.rs b/bit_field/bit_field_derive/bit_field_derive.rs
index df3e2825e..e8a536709 100644
--- a/bit_field/bit_field_derive/bit_field_derive.rs
+++ b/bit_field/bit_field_derive/bit_field_derive.rs
@@ -325,10 +325,10 @@ fn bitfield_struct_impl(ast: &DeriveInput, fields: &FieldsNamed) -> Result<Token
let vis = &ast.vis;
let attrs = &ast.attrs;
let fields = get_struct_fields(fields)?;
- let struct_def = get_struct_def(vis, &name, &fields);
- let bits_impl = get_bits_impl(&name);
+ let struct_def = get_struct_def(vis, name, &fields);
+ let bits_impl = get_bits_impl(name);
let fields_impl = get_fields_impl(&fields);
- let debug_fmt_impl = get_debug_fmt_impl(&name, &fields);
+ let debug_fmt_impl = get_debug_fmt_impl(name, &fields);
let expanded = quote! {
#(#attrs)*
diff --git a/bit_field/bit_field_derive/cargo2android.json b/bit_field/bit_field_derive/cargo2android.json
new file mode 100644
index 000000000..9a6eee39c
--- /dev/null
+++ b/bit_field/bit_field_derive/cargo2android.json
@@ -0,0 +1,7 @@
+{
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "run": true,
+ "tests": false
+} \ No newline at end of file
diff --git a/bit_field/cargo2android.json b/bit_field/cargo2android.json
new file mode 100644
index 000000000..9df4a5593
--- /dev/null
+++ b/bit_field/cargo2android.json
@@ -0,0 +1,9 @@
+{
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "no-pkg-vers": true,
+ "no-subdir": true,
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/cargo2android.json b/cargo2android.json
new file mode 100644
index 000000000..48996d78c
--- /dev/null
+++ b/cargo2android.json
@@ -0,0 +1,15 @@
+{
+ "add-module-block": "cargo2android_module.bp",
+ "add-toplevel-block": "cargo2android_defaults.bp",
+ "dependencies": true,
+ "dependency-blocklist": ["aarch64", "x86_64"],
+ "device": true,
+ "features": "audio,usb",
+ "global_defaults": "crosvm_defaults",
+ "no-pkg-vers": true,
+ "no-subdir": true,
+ "patch": "patches/Android.bp.patch",
+ "run": true,
+ "test-blocklist": ["tests/plugins.rs"],
+ "tests": true
+}
diff --git a/cargo2android_defaults.bp b/cargo2android_defaults.bp
new file mode 100644
index 000000000..344fd56cc
--- /dev/null
+++ b/cargo2android_defaults.bp
@@ -0,0 +1,27 @@
+rust_defaults {
+ name: "crosvm_defaults",
+ edition: "2018",
+ enabled: false,
+ target: {
+ linux_glibc_x86_64: {
+ enabled: true,
+ },
+ linux_musl_x86_64: {
+ enabled: true,
+ },
+ android64: {
+ compile_multilib: "64",
+ enabled: true,
+ },
+ linux_bionic_arm64: {
+ enabled: true,
+ },
+ darwin: {
+ enabled: false,
+ },
+ },
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
+}
diff --git a/cargo2android_module.bp b/cargo2android_module.bp
new file mode 100644
index 000000000..b48986b04
--- /dev/null
+++ b/cargo2android_module.bp
@@ -0,0 +1,39 @@
+arch: {
+ x86_64: {
+ rustlibs: ["libx86_64_rust"],
+ },
+ arm64: {
+ rustlibs: ["libaarch64"],
+ },
+},
+target: {
+ host: {
+ features: [
+ "gfxstream",
+ "gpu",
+ ],
+ },
+ android: {
+ shared_libs: [
+ "libprocessgroup",
+ ],
+ },
+ host_linux: {
+ features: [
+ "gdb",
+ "gdbstub",
+ ],
+ rustlibs: [
+ "libgdbstub",
+ "libgdbstub_arch",
+ "libthiserror",
+ ],
+ shared_libs: [
+ "libprocessgroup",
+ ],
+ },
+},
+ld_flags: [
+ "-Wl,--rpath,\\$$ORIGIN",
+ "-Wl,--rpath,\\$$ORIGIN/../../lib64",
+]
diff --git a/ci/Makefile b/ci/Makefile
deleted file mode 100644
index 21f0abc95..000000000
--- a/ci/Makefile
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Builds docker images for the crosvm builders.
-# Run the `upload` target to upload the images to the container registry
-# (provided you are authorized to upload them).
-#
-# Images are always built with docker (since buildkit is a lot faster than
-# podman/buildah). But we do automatically pull images into podman if podman
-# is installed.
-
-export DOCKER_BUILDKIT=1
-
-TAG_BASE=gcr.io/crosvm-packages
-TAG_VERSION=$(shell cat image_tag)
-
-DOCKER ?= docker
-
-all: crosvm_builder crosvm_aarch64_builder
-
-upload: all
- $(DOCKER) push $(TAG_BASE)/crosvm_base:$(TAG_VERSION)
- $(DOCKER) push $(TAG_BASE)/crosvm_builder:$(TAG_VERSION)
- $(DOCKER) push $(TAG_BASE)/crosvm_aarch64_builder:$(TAG_VERSION)
- $(DOCKER) push $(TAG_BASE)/crosvm_test_vm_amd64:$(TAG_VERSION)
- $(DOCKER) push $(TAG_BASE)/crosvm_test_vm_arm64:$(TAG_VERSION)
-
-crosvm_base:
- cd $@ && $(DOCKER) build -t $(TAG_BASE)/$@:$(TAG_VERSION) .
-
-crosvm_builder: crosvm_base crosvm_test_vm_amd64
- cd $@ && $(DOCKER) build \
- -t $(TAG_BASE)/$@:$(TAG_VERSION) \
- --build-arg TAG=$(TAG_VERSION) \
- .
-ifneq (, $(shell command -v podman))
- podman pull docker-daemon:$(TAG_BASE)/$@:$(TAG_VERSION)
-endif
-
-crosvm_aarch64_builder: crosvm_base crosvm_test_vm_arm64
- cd $@ && $(DOCKER) build \
- -t $(TAG_BASE)/$@:$(TAG_VERSION) \
- --build-arg TAG=$(TAG_VERSION) \
- .
-ifneq (, $(shell command -v podman))
- podman pull docker-daemon:$(TAG_BASE)/$@:$(TAG_VERSION)
-endif
-
-crosvm_test_vm_amd64:
- cd crosvm_test_vm && \
- $(DOCKER) build -t $(TAG_BASE)/$@:$(TAG_VERSION) --build-arg VM_ARCH=amd64 .
-
-crosvm_test_vm_arm64:
- cd crosvm_test_vm && \
- $(DOCKER) build -t $(TAG_BASE)/$@:$(TAG_VERSION) --build-arg VM_ARCH=arm64 .
-
-.PHONY: all crosvm_base crosvm_builder crosvm_aarch64_builder
diff --git a/ci/README.md b/ci/README.md
deleted file mode 100644
index dd2557653..000000000
--- a/ci/README.md
+++ /dev/null
@@ -1,132 +0,0 @@
-# CrosVM Continuous Integration
-
-Crosvm has a complex set of dependencies and requirements on the host machine to
-successfully build and run test cases. To allow for consistent testing in our
-continuous integration system (kokoro) and reproduction of those tests locally,
-we provide docker containers containing the build toolchain and a VM for
-testing.
-
-## How to run crosvm tests
-
-### Setting up the source
-
-Since crosvm is part of chromiumos, and uses a couple of it's projects as
-dependencies, you need a standard chromiumos checkout as described by the
-[ChromiumOS Developer Guide](https://chromium.googlesource.com/chromiumos/docs/+/master/developer_guide.md#Get-the-Source).
-
-To reduce the number of repositories to download, you can use the `-g crosvm`
-argument on `repo init`. This will be significantly faster:
-
-In summary:
-
-```
-$ repo init -u https://chromium.googlesource.com/chromiumos/manifest.git --repo-url https://chromium.googlesource.com/external/repo.git -g crosvm
-$ repo sync -j4
-$ cd src/platform/crosvm
-```
-
-### Installing Podman (or Docker)
-
-See [Podman Installation](https://podman.io/getting-started/installation) for
-instructions on how to install podman.
-
-For Googlers, see [go/dont-install-docker](http://go/dont-install-docker) for
-special instructions on how to set up podman.
-
-If you already have docker installed, that will do as well. However podman is
-recommended as it will not run containers with root privileges.
-
-### Running all tests
-
-To run all tests, just run:
-
-```
-./test_all
-```
-
-This will run all tests using the x86 and aarch64 builder containers. What does
-this do?
-
-1. It will start `./ci/[aarch64_]builder --vm`.
-
- The builder will build ChromeOS dependencies from your local repo checkout.
- If you make modifications to these dependencies (e.g. minijail, tpm2, cras)
- these will be included in tests.
-
- Then it will start a VM for running tests in the background. The VM is
- booting while the next step is running.
-
-2. Then it will call `./run_tests` inside the builder
-
- The script will pick which tests to execute and where. Simple tests can be
- executed directly, other tests require privileged access to devices and will
- be loaded into the VM to execute.
-
- Each test will in the end be executed by a call to
- `cargo test -p crate_name`.
-
-Intermediate build data is stored in a scratch directory at `./target/ci/` to
-allow for faster subsequent calls (Note: If running with docker, these files
-will be owned by root).
-
-### Fast, iterative test runs
-
-For faster iteration time, you can directly invoke some of these steps directly:
-
-To only run x86 tests: `./ci/[aarch64_]builder --vm ./run_tests`.
-
-To run a simple test (e.g. the tempfile crate) that does not need the vm:
-`./ci/[aarch64_]builder cargo test -p tempfile`.
-
-Or run a single test (e.g. kvm_sys) inside the vm:
-`./ci/[aarch64*]builder --vm cargo test -p kvm_sys`.
-
-Since the VM (especially the fully emulated aarch64 VM) can be slow to boot, you
-can start an interactive shell and run commands from there as usual. All cargo
-tests will be executed inside the VM, without the need to restart the VM between
-test calls.
-
-```
-host$ ./ci/aarch64_builder --vm
-crosvm-aarch64$ ./run_tests
-crosvm-aarch64$ cargo test -p kvm_sys
-...
-```
-
-### Reproducing Kokoro locally
-
-Kokoro uses the same builders and the same `run_tests` script to run tests.
-However, to keep the build stable, it syncs the chromiumos checkout to the fixed
-manifest found at `./ci/kokoro/manifest.xml`.
-
-To run tests using the same manifest, as well as the same build process that
-Kokoro uses, you can run: `./ci/kokoro/simulate_all`.
-
-## Implementation Overview
-
-Directories:
-
-- ci/build_environment: Contains tooling for building the dependencies of
- crosvm.
-- ci/crosvm_aarch64_builder: An x86 docker image to cross-compile for aarch64
- and test with user-space emulation.
-- ci/crosvm_base: Docker image shared by crosvm_builder and
- crosvm_aarch64_builder
-- ci/crosvm_builder: A native docker image for building and testing crosvm
-- ci/crosvm_test_vm: Dockerfile to build the VM included in the builder
- containers.
-- ci/kokoro: Configuration files and build scripts used by Kokoro to run crosvm
- tests.
-
-Scripts:
-
-- ci/aarch64_builder: Script to start the crosvm_aarch64_builder container
-- ci/builder: Script to start the crosvm_builder container
-- ci/run_container.sh: Implementation behind the above scripts.
-- test_runner.py: Implementation behind the `./test_all` script.
-
-### Building and uploading a new version of builders
-
-The docker images for all builders can be built with `make` and uploaded with
-`make upload`. Of course you need to have docker push permissions for
-`gcr.io/crosvm-packages/` for the upload to work.
diff --git a/ci/aarch64_builder b/ci/aarch64_builder
deleted file mode 100755
index fb20821df..000000000
--- a/ci/aarch64_builder
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-crosvm_root=$(realpath "$(dirname $0)/..")
-export CROSVM_BUILDER_SCRATCH_DIR="\
-${crosvm_root}/target/ci/crosvm_aarch64_builder"
-
-"$(dirname $0)/run_container.sh" crosvm_aarch64_builder "$@"
diff --git a/ci/build_environment/Makefile b/ci/build_environment/Makefile
deleted file mode 100644
index 716daa40a..000000000
--- a/ci/build_environment/Makefile
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# This makefile is run by the ./ci/crosvm_* containers to build ChromiumOS
-# dependencies required by crosvm.
-#
-# Setting TARGET_ARCH=aarch64 enables cross-compilation for aarch64.
-
-SRC ?= /workspace/src
-BUILD ?= /workspace/scratch/build
-LIB ?= /workspace/scratch/lib
-TARGET_ARCH ?=
-
-MAKEFILE_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
-
-ifeq ($(TARGET_ARCH),aarch64)
-CROSS_COMPILE = aarch64-linux-gnu-
-MESON_ARGS = --cross-file $(BUILD)/meson-cross
-else
-CROSS_COMPILE =
-MESON_ARGS =
-endif
-
-
-all: $(LIB) tpm2 minijail minigbm virglrenderer
- ldconfig $(LIB)
-
-clean:
- rm -rf $(BUILD) $(LIB)
-
-# Targets to build the needed chromiumos projects.
-#
-# These are phony targets so that we can delegate the dirty-check to the
-# underlying build system for each library.
-
-tpm2:
- mkdir -p $(BUILD)/tpm2
- $(MAKE) -C $(SRC)/third_party/tpm2 \
- obj=$(BUILD)/tpm2 \
- AR=$(CROSS_COMPILE)ar \
- CC=$(CROSS_COMPILE)gcc \
- OBJCOPY=$(CROSS_COMPILE)objcopy V=1
-
-minijail:
- mkdir -p $(BUILD)/minijail
- $(MAKE) -C $(SRC)/aosp/external/minijail \
- OUT=$(BUILD)/minijail \
- CROSS_COMPILE=$(CROSS_COMPILE)
-
-minigbm:
- mkdir -p $(BUILD)/minigbm
- $(MAKE) -C $(SRC)/platform/minigbm \
- OUT=$(BUILD)/minigbm \
- CROSS_COMPILE=$(CROSS_COMPILE)
-
-virglrenderer: minigbm $(BUILD)/meson-cross
- meson setup \
- $(BUILD)/virglrenderer \
- $(SRC)/third_party/virglrenderer \
- $(MESON_ARGS)
-
- CPATH=$(SRC)/platform/minigbm \
- meson compile -C $(BUILD)/virglrenderer
-
-
-# File needed by meson for cross-compilation.
-$(BUILD)/meson-cross:
-ifeq ($(TARGET_ARCH),aarch64)
- mkdir -p $(BUILD)
- /usr/share/meson/debcrossgen --arch arm64 -o $@
-else
- mkdir -p $(BUILD)
- touch $@
-endif
-
-# Sets up the $(LIB) directory with links to the generated binaries in $(BUILD).
-$(LIB):
- mkdir -p $(LIB) $(LIB)/pkgconfig
-
- # tpm2
- ln -sf $(BUILD)/tpm2/libtpm2.a $(LIB)/libtpm2.a
- ln -sf $(MAKEFILE_DIR)/pkgconfig/libtpm2.pc $(LIB)/pkgconfig/
-
- # minijail
- ln -sf $(BUILD)/minijail/libminijail.so $(LIB)
- ln -sf $(LIB)/libminijail.so $(LIB)/libminijail.so.1
- ln -sf $(MAKEFILE_DIR)/pkgconfig/libminijail.pc $(LIB)/pkgconfig/
-
- # minigbm
- ln -sf $(BUILD)/minigbm/libminigbm.so.1.0.0 $(LIB)/libgbm.so
- ln -sf $(LIB)/libgbm.so $(LIB)/libgbm.so.1
- ln -sf $(SRC)/platform/minigbm/gbm.pc $(LIB)/pkgconfig/
-
- # virglrenderer
- ln -sf $(BUILD)/virglrenderer/src/libvirglrenderer.so $(LIB)
- ln -sf $(LIB)/libvirglrenderer.so $(LIB)/libvirglrenderer.so.1
- ln -sf $(BUILD)/virglrenderer/virglrenderer.pc $(LIB)/pkgconfig/
-
-.PHONY: all clean tpm2 minijail sysroot minigbm virglrenderer
diff --git a/ci/build_environment/pkgconfig/libminijail.pc b/ci/build_environment/pkgconfig/libminijail.pc
deleted file mode 100644
index 1c139b148..000000000
--- a/ci/build_environment/pkgconfig/libminijail.pc
+++ /dev/null
@@ -1,4 +0,0 @@
-Name: libminijail
-Description: Dummy config for libminijail
-Version: 0.0.0
-Libs: -lminijail
diff --git a/ci/build_environment/pkgconfig/libtpm2.pc b/ci/build_environment/pkgconfig/libtpm2.pc
deleted file mode 100644
index 24095f013..000000000
--- a/ci/build_environment/pkgconfig/libtpm2.pc
+++ /dev/null
@@ -1,5 +0,0 @@
-Name: tpm2
-Description: Dummy config for libtpm2
-Version: 0.0.0
-Requires: libssl
-Libs: -ltpm2
diff --git a/ci/builder b/ci/builder
deleted file mode 100755
index f967d3fbc..000000000
--- a/ci/builder
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-crosvm_root=$(realpath "$(dirname $0)/..")
-export CROSVM_BUILDER_SCRATCH_DIR="\
-${crosvm_root}/target/ci/crosvm_builder"
-
-"$(dirname $0)/run_container.sh" crosvm_builder "$@"
diff --git a/ci/crosvm_aarch64_builder/Dockerfile b/ci/crosvm_aarch64_builder/Dockerfile
deleted file mode 100644
index e4e200085..000000000
--- a/ci/crosvm_aarch64_builder/Dockerfile
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Docker container that cross-compiles crosvm for aarch64.
-
-# Build-argument of the image tag of dependencies to use. Set to the same
-# version as `ci/image_tag`
-ARG TAG
-
-# Stage containing VM data to be used later.
-# (COPY --from does not allow the use of ARGs)
-FROM gcr.io/crosvm-packages/crosvm_test_vm_arm64:${TAG} as vm
-
-# Main stage
-FROM gcr.io/crosvm-packages/crosvm_base:${TAG}
-
-# Add repositories for arm64 packages
-RUN dpkg --add-architecture arm64
-
-# Install cross-compilation and VM tooling
-RUN apt-get update && apt-get install --yes --no-install-recommends \
- dpkg-dev \
- g++-aarch64-linux-gnu \
- gcc-aarch64-linux-gnu \
- ipxe-qemu \
- qemu-efi-aarch64 \
- qemu-system-aarch64 \
- qemu-user-static
-
-RUN apt-get install --yes --no-install-recommends -o APT::Immediate-Configure=false \
- libcap-dev:arm64 \
- libdbus-1-dev:arm64 \
- libdrm-dev:arm64 \
- libepoxy-dev:arm64 \
- libssl-dev:arm64 \
- libwayland-dev:arm64
-
-RUN apt-get install --yes -t testing --no-install-recommends \
- libdrm-dev:arm64 \
- libepoxy-dev:arm64
-
-# Setup rust for cross-compilation
-RUN rustup target add aarch64-unknown-linux-gnu
-ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
- CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \
- CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ \
- CARGO_BUILD_TARGET=aarch64-unknown-linux-gnu \
- PKG_CONFIG=aarch64-linux-gnu-pkg-config \
- PKG_CONFIG_PATH=/workspace/scratch/lib/pkgconfig
-
-# Allow GCC/Rust to find packages and libraries stored on the scratch volume. We
-# have to link to a known search path since LIBRARY_PATH is not used by
-# cross-compile GCC.
-RUN ln -s /workspace/scratch/lib/ /usr/local/lib/aarch64-linux-gnu
-
-# Hack: For some reason the libgcc-10-dev-arm64-cross package does not install
-# this link correctly.
-RUN cd /usr/aarch64-linux-gnu/lib && ln -s libgcc_s.so.1 libgcc_s.so
-
-# Allow qemu-aarch64-static to find aarch64 libraries
-ENV QEMU_LD_PREFIX=/usr/aarch64-linux-gnu
-
-# Include test VM inside this container
-COPY --from=vm \
- /workspace/vm/* \
- /workspace/vm/
-COPY --from=vm \
- /root/.ssh /root/.ssh
-
-# Setup entrypoint and interactive shell
-WORKDIR /workspace/src/platform/crosvm
-COPY bashrc /root/.bashrc
-COPY entrypoint /workspace
-ENTRYPOINT ["/workspace/entrypoint"]
diff --git a/ci/crosvm_aarch64_builder/bashrc b/ci/crosvm_aarch64_builder/bashrc
deleted file mode 100644
index bf0e42d2f..000000000
--- a/ci/crosvm_aarch64_builder/bashrc
+++ /dev/null
@@ -1,5 +0,0 @@
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file
-
-export PS1="crosvm-aarch64:\\w# "
diff --git a/ci/crosvm_aarch64_builder/entrypoint b/ci/crosvm_aarch64_builder/entrypoint
deleted file mode 100755
index 74ff7e510..000000000
--- a/ci/crosvm_aarch64_builder/entrypoint
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/bin/bash
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# Clean scratch directory if requested.
-if [ "$1" = "--clean" ]; then
- shift
- echo "Cleaning scratch directory..."
- rm -rf /workspace/scratch/*
-fi
-
-echo "Building ChromeOS dependencies..."
-if ! make -j $(nproc) -C ci/build_environment TARGET_ARCH=aarch64 \
- >/workspace/logs/build_environment.log 2>&1; then
- echo "Failed to build ChromeOS dependencies"
- cat /workspace/logs/build_environment.log
- # Drop into an interactive shell for debugging.
- if [[ $# -eq 0 ]]; then
- /bin/bash
- fi
- exit 1
-fi
-
-if [ "$1" = "--vm" ]; then
- shift
- echo "Starting testing vm..."
- (cd /workspace/vm && screen -Sdm vm ./start_vm)
- export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER="\
- /workspace/src/platform/crosvm/ci/vm_tools/exec_binary_in_vm"
-
- if [[ $# -eq 0 ]]; then
- test_target="Virtual Machine (See 'screen -r vm' or 'ssh vm')"
- else
- test_target="Virtual Machine"
- fi
- export CROSVM_USE_VM=1
-else
- test_target="User-space emulation"
- export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER="\
- qemu-${target_arch}-static \
- -E LD_LIBRARY_PATH=/workspace/scratch/lib"
-fi
-
-echo ""
-echo "crosvm builder is ready:"
-echo " Cargo version: $(cargo --version)"
-echo " Target architecture: ${CARGO_BUILD_TARGET}"
-echo " Test target: ${test_target}"
-echo ""
-
-# Run user provided command or interactive shell
-if [[ $# -eq 0 ]]; then
- /bin/bash
-else
- echo "$ $@"
- /bin/bash -c "$@"
-fi
diff --git a/ci/crosvm_base/Dockerfile b/ci/crosvm_base/Dockerfile
deleted file mode 100644
index 2117a3219..000000000
--- a/ci/crosvm_base/Dockerfile
+++ /dev/null
@@ -1,78 +0,0 @@
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Base image for crosvm_builder and crosvm_aarch64_builder containing basic
-# devleopment environment for building rust.
-
-# TODO(b/177078591): Use debian buster and backports (or manual dpkg downloads)
-# of outdated libraries. Sid could blow up on us any day.
-FROM debian:buster
-
-# Set timezone so apt-get won't try to prompt
-ARG DEBIAN_FRONTEND=noninteractive
-ENV TZ=US/Pacific
-
-# Add bullseye to sources so we can install some newer versions of packages.
-RUN echo 'deb http://deb.debian.org/debian bullseye main' >/etc/apt/sources.list.d/testing.list
-RUN echo 'APT::Default-Release "stable";' >/etc/apt/apt.conf.d/99_default_stable
-
-RUN apt-get update && apt-get install --yes --no-install-recommends \
- ca-certificates \
- curl \
- g++ \
- gcc \
- git \
- jq \
- make \
- meson/testing \
- nasm \
- ninja-build \
- openssh-client \
- pkg-config \
- protobuf-compiler \
- python3 \
- python3-setuptools \
- rsync \
- screen \
- sudo
-
-# This is a scratch volume for build files. It can be used to allow incremental
-# builds between container runs.
-# Note: This volume is optional if incremental builds are not required.
-VOLUME /workspace/scratch
-
-# This is where the chromiumos source tree will be mounted
-VOLUME /workspace/src
-
-# This is a volume to store additional logs for kokoro builds that are uploaded
-# to sponge.
-VOLUME /workspace/logs
-
-# Install the current crosvm rust toolchain via rustup.
-COPY rust-toolchain ./
-RUN curl https://sh.rustup.rs -sSf | sh -s -- \
- -y \
- --profile minimal \
- -c rustfmt,clippy \
- --default-toolchain $(cat rust-toolchain)
-ENV PATH="/root/.cargo/bin:${PATH}"
-
-# Point cargo to store data on the scratch volume.
-ENV CARGO_TARGET_DIR=/workspace/scratch/cargo_target
-ENV CARGO_HOME=/workspace/scratch/cargo_home
-
-# Warms up the cargo registry cache for future cargo runs. Cargo will still
-# update the cache using a git pull, but it only needs to download files that
-# were changed since this image was built.
-RUN cargo install thisiznotarealpackage -q || true
-
-# We are building out of source with a readonly source filesystem. This flag
-# tells some build.rs files to not write into the src filesystem.
-ENV RUSTFLAGS='--cfg hermetic'
-
-# Environment variables for the run_tests script to enable all tests.
-ENV CROSVM_CROS_BUILD=1
-
-# All commands will be executed in the crosvm src directory.
-WORKDIR /workspace/src/platform/crosvm
diff --git a/ci/crosvm_base/rust-toolchain b/ci/crosvm_base/rust-toolchain
deleted file mode 100644
index 5a5c7211d..000000000
--- a/ci/crosvm_base/rust-toolchain
+++ /dev/null
@@ -1 +0,0 @@
-1.50.0
diff --git a/ci/crosvm_builder/Dockerfile b/ci/crosvm_builder/Dockerfile
deleted file mode 100644
index 238ed0b6f..000000000
--- a/ci/crosvm_builder/Dockerfile
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Docker container to build crosvm for the host architecture.
-
-# Build-argument of the image tag of dependencies to use. Set to the same
-# version as `ci/image_tag`
-ARG TAG
-
-# Stage containing VM data to be used later.
-# (COPY --from does not allow the use of ARGs)
-FROM gcr.io/crosvm-packages/crosvm_test_vm_amd64:${TAG} as vm
-
-# Main stage
-FROM gcr.io/crosvm-packages/crosvm_base:${TAG}
-
-# Install libraries needed to compile crosvm and it's dependencies.
-RUN apt-get install --yes --no-install-recommends \
- libcap-dev \
- libdbus-1-dev \
- libdrm-dev \
- libepoxy-dev \
- libssl-dev \
- libwayland-dev \
- qemu-system-x86
-
-RUN apt-get install --yes -t testing --no-install-recommends \
- libdrm-dev \
- libepoxy-dev
-
-# Allow GCC/Rust to find packages and libraries stored on the scratch volume.
-ENV LIBRARY_PATH=/workspace/scratch/lib
-ENV LD_LIBRARY_PATH=/workspace/scratch/lib
-ENV PKG_CONFIG_PATH=/workspace/scratch/lib/pkgconfig
-
-# Include test VM inside this container
-COPY --from=vm \
- /workspace/vm/* \
- /workspace/vm/
-COPY --from=vm \
- /root/.ssh /root/.ssh
-
-# Setup entrypoint and interactive shell
-WORKDIR /workspace/src/platform/crosvm
-COPY bashrc /root/.bashrc
-COPY entrypoint /workspace
-ENTRYPOINT ["/workspace/entrypoint"]
diff --git a/ci/crosvm_builder/bashrc b/ci/crosvm_builder/bashrc
deleted file mode 100644
index 56a3848f0..000000000
--- a/ci/crosvm_builder/bashrc
+++ /dev/null
@@ -1,5 +0,0 @@
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file
-
-export PS1="crosvm-$(arch):\\w# "
diff --git a/ci/crosvm_builder/entrypoint b/ci/crosvm_builder/entrypoint
deleted file mode 100755
index ec4c0765b..000000000
--- a/ci/crosvm_builder/entrypoint
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/bin/bash
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-# Clean scratch directory if requested.
-if [ "$1" = "--clean" ]; then
- shift
- echo "Cleaning scratch directory..."
- rm -rf /workspace/scratch/*
-fi
-
-echo "Building ChromeOS dependencies..."
-if ! make -j $(nproc) -C ci/build_environment \
- >/workspace/logs/build_environment.log 2>&1; then
- echo "Failed to build ChromeOS dependencies"
- cat /workspace/logs/build_environment.log
- # Drop into an interactive shell for debugging.
- if [[ $# -eq 0 ]]; then
- /bin/bash
- fi
- exit 1
-fi
-
-if [ "$1" = "--vm" ]; then
- shift
- echo "Starting testing vm..."
- (cd /workspace/vm && screen -Sdm vm ./start_vm)
- export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER="\
- /workspace/src/platform/crosvm/ci/vm_tools/exec_binary_in_vm"
-
- if [[ $# -eq 0 ]]; then
- test_target="Virtual Machine (See 'screen -r vm' or 'ssh vm')"
- else
- test_target="Virtual Machine"
- fi
- export CROSVM_USE_VM=1
-else
- test_target="Native execution"
-fi
-
-echo ""
-echo "crosvm builder is ready:"
-echo " Cargo version: $(cargo --version)"
-echo " Test target: ${test_target}"
-echo ""
-
-# Run user provided command or interactive shell
-if [[ $# -eq 0 ]]; then
- /bin/bash
-else
- echo "$ $@"
- /bin/bash -c "$@"
-fi
diff --git a/ci/crosvm_test_vm/Dockerfile b/ci/crosvm_test_vm/Dockerfile
deleted file mode 100644
index aa19f1748..000000000
--- a/ci/crosvm_test_vm/Dockerfile
+++ /dev/null
@@ -1,78 +0,0 @@
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Docker container for VMs that can run crosvm tests. This container is
-# primarily an intermediate step and imported into the crosvm_(aarch64)_builder
-# containers, but for testing purposes it can be run directly.
-
-# Target architecture of the VM. Either arm64 or amd64.
-ARG VM_ARCH
-
-# Build stage which will build the rootfs for our VM
-FROM debian:buster as vm_builder
-ARG VM_ARCH
-
-RUN apt-get update && apt-get install --yes \
- cloud-image-utils \
- curl \
- expect \
- qemu-system-arm \
- qemu-system-x86 \
- qemu-efi-aarch64
-
-WORKDIR /workspace/vm
-
-RUN curl -sSfL -o rootfs.qcow2 \
- "http://cloud.debian.org/images/cloud/bullseye/daily/20210208-542/debian-11-generic-${VM_ARCH}-daily-20210208-542.qcow2"
-
-# Package `cloud_init_data.yaml` to be loaded during `first_boot.expect`
-COPY build/cloud_init_data.yaml ./
-RUN cloud-localds -v cloud_init.img cloud_init_data.yaml
-
-# Boot the VM once, to do initialization work so we do not have to do it at
-# every launch.
-COPY runtime/start_vm.${VM_ARCH} ./start_vm
-COPY build/first_boot.expect ./
-RUN expect -f first_boot.expect
-
-# Compress and sparsify image after doing all the init work.
-RUN qemu-img convert -O qcow2 -c rootfs.qcow2 rootfs.compressed \
- && mv -f rootfs.compressed rootfs.qcow2
-
-
-# Runtime environment for amd64
-FROM debian:buster as runtime_amd64
-RUN apt-get update && apt-get install --yes --no-install-recommends \
- qemu-system-x86
-
-
-# Runtime environment for arm64
-FROM debian:buster as runtime_arm64
-RUN apt-get update && apt-get install --yes --no-install-recommends \
- ipxe-qemu \
- qemu-system-arm \
- qemu-efi-aarch64
-
-
-# Select the correct stage as runtime stage
-FROM runtime_${VM_ARCH} as runtime
-ARG VM_ARCH
-
-RUN apt-get install --yes --no-install-recommends \
- openssh-client
-
-# Copy rootfs into runtime stage
-WORKDIR /workspace/vm
-COPY --from=vm_builder /workspace/vm/rootfs.qcow2 ./
-
-# Setup SSH profile for `vm`.
-RUN mkdir -p ~/.ssh
-COPY runtime/ssh /root/.ssh
-RUN chmod 0600 /root/.ssh/id_rsa
-
-# Copy utility scripts
-COPY runtime/start_vm.${VM_ARCH} ./start_vm
-
-# Automatically start the VM.
-ENTRYPOINT [ "/workspace/vm/start_vm" ]
diff --git a/ci/crosvm_test_vm/build/cloud_init_data.yaml b/ci/crosvm_test_vm/build/cloud_init_data.yaml
deleted file mode 100644
index a9d4dc953..000000000
--- a/ci/crosvm_test_vm/build/cloud_init_data.yaml
+++ /dev/null
@@ -1,54 +0,0 @@
-#cloud-config
-users:
- - name: crosvm
- sudo: ALL=(ALL) NOPASSWD:ALL
- lock_passwd: False
- shell: /bin/bash
- # Hashed password is 'crosvm'
- passwd: $6$rounds=4096$os6Q9Ok4Y9a8hKvG$EwQ1bbS0qd4IJyRP.bnRbyjPbSS8BwxEJh18PfhsyD0w7a4GhTwakrmYZ6KuBoyP.cSjYYSW9wYwko4oCPoJr.
- # Pubkey for `../vm_key`
- ssh_authorized_keys:
- - ssh-rsa
- AAAAB3NzaC1yc2EAAAADAQABAAABgQCYan8oXtUm6WTIClGMsfEf3hmJe+T8p08t9O8StuuDHyAtl1lC+8hOcuXTNvbc52/HNdZ5EO4ZpP3n+N6XftfXFWQanI8OrIHVpsMATMnofHE9RBHWcR/gH0V3xKnXcTvo3S0T3ennfCYxjtL7l7EvHDMdacX8NFOaARH92qJd/YdFp73mqykmc81OCZ4ToQ5s+20T7xvRzedksfSj/wIx3z8BJK9iovkQhNGFd1o557Vq1g3Bxk1PbcIUAvFPl0SwwlFfHBi2M9kZgCVa8UfokBzu77zvxWFud+MXVrfralwKV88k9Cy9FL5QGbtCiZ7RDP5pf69xapKBK+z2L+zuVlSkvaB1CcXuqqVDjD84LXEA+io0peXQcsqbAfbLo0666P7JsweCyQ07qc4AM8gv52SzFuQTIuHLciYxbPgkZTieKgmQLJ1EgfJelOG/+60XC24LbzPIAQxcO83erC/SQ3mTUizu6BueJt7LD1V6vXHcjLfE19FecIJ8U0XDaDU=
- crosvm@localhost
- groups: kvm, disk, tty
-
-hostname: testvm
-
-# Store working data on tmpfs to reduce unnecessary disk IO
-mounts:
- - [swap, null]
- - [tmpfs, /tmp, tmpfs]
-
-# Runtime dependencies of crosvm binaries.
-# Note: Keep in sync with -dev packages of crosvm_(aarch64_)builder
-packages:
- - libcap2
- - libdbus-1-3
- - libdrm2
- - libepoxy0
- - libssl1.1
- - libwayland-client0
- - libx11-6
- - libxext6
- - rsync
-
-runcmd:
- # Prevent those annoying "host not found errors".
- - echo 127.0.0.1 testvm >> /etc/hosts
-
- # Make it easier to identify which VM we are in.
- - echo "export PS1=\"testvm-$(arch):\\\\w# \"" >> /etc/bash.bashrc
-
- # Enable core dumps for debugging crashes
- - echo "* soft core unlimited" > /etc/security/limits.conf
-
- # Trim some fat
- - [apt-get, remove, --yes, vim-runtime, iso-codes, perl, grub-common]
- - [apt-get, autoremove, --yes]
- - [apt-get, clean, --yes]
- - [rm, -rf, /var/lib/apt/lists]
-
- # Fill empty space with zeros, so the image can be sparsified.
- - [dd, if=/dev/zero, of=/mytempfile]
- - [rm, /mytempfile]
diff --git a/ci/crosvm_test_vm/build/first_boot.expect b/ci/crosvm_test_vm/build/first_boot.expect
deleted file mode 100755
index fc7c8c1e6..000000000
--- a/ci/crosvm_test_vm/build/first_boot.expect
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/expect -f
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-set timeout -1
-
-spawn ./start_vm -hdb cloud_init.img
-
-expect "login:"
-send "crosvm\r"
-
-expect "Password:"
-send "crosvm\r"
-
-expect "$ "
-send "cloud-init status --wait\r"
-
-expect "$ "
-send "sudo poweroff\r"
-
-expect eof
diff --git a/ci/crosvm_test_vm/runtime/ssh/config b/ci/crosvm_test_vm/runtime/ssh/config
deleted file mode 100644
index 9d70c1c0a..000000000
--- a/ci/crosvm_test_vm/runtime/ssh/config
+++ /dev/null
@@ -1,5 +0,0 @@
-Host vm
- HostName localhost
- User crosvm
- Port 8022
- StrictHostKeyChecking no
diff --git a/ci/crosvm_test_vm/runtime/start_vm.amd64 b/ci/crosvm_test_vm/runtime/start_vm.amd64
deleted file mode 100755
index f6718ba2c..000000000
--- a/ci/crosvm_test_vm/runtime/start_vm.amd64
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-qemu_args=(
- -smp 8 -m 4G
- -serial stdio -display none
- -device virtio-net-pci,netdev=net0
- -netdev user,id=net0,hostfwd=tcp::8022-:22
- -hda rootfs.qcow2
-)
-
-# The build environment for first_boot.expect does not support KVM.
-if [ -e /dev/kvm ]; then
- qemu_args+=(-enable-kvm -cpu host)
-fi
-mkdir -p /workspace/scratch/logs
-qemu-system-x86_64 ${qemu_args[@]} $@ | tee /workspace/logs/vm.log
diff --git a/ci/crosvm_test_vm/runtime/start_vm.arm64 b/ci/crosvm_test_vm/runtime/start_vm.arm64
deleted file mode 100755
index a0fc04f0b..000000000
--- a/ci/crosvm_test_vm/runtime/start_vm.arm64
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/bash
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-qemu_args=(
- -M virt -machine virtualization=true -machine virt,gic-version=3
- -cpu cortex-a57 -smp 8 -m 4G
- -serial stdio -display none
- -device virtio-net-pci,netdev=net0
- -netdev user,id=net0,hostfwd=tcp::8022-:22
- -bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd
- -hda rootfs.qcow2
-)
-mkdir -p /workspace/scratch/logs
-qemu-system-aarch64 ${qemu_args[@]} $@ | tee /workspace/logs/vm.log
diff --git a/ci/image_tag b/ci/image_tag
deleted file mode 100644
index b7be5a127..000000000
--- a/ci/image_tag
+++ /dev/null
@@ -1 +0,0 @@
-r0004
diff --git a/ci/kokoro/build-aarch64.sh b/ci/kokoro/build-aarch64.sh
index ceb4ddc58..c7af74947 100755
--- a/ci/kokoro/build-aarch64.sh
+++ b/ci/kokoro/build-aarch64.sh
@@ -5,6 +5,4 @@
source "$(dirname $0)/common.sh"
-./ci/run_container.sh crosvm_aarch64_builder --vm "\
- ./run_tests -v --require-all \
- --junit-file=/workspace/logs/cargo_test/sponge_log.xml"
+./tools/dev_container --hermetic ./tools/run_tests --target=vm:aarch64 -v
diff --git a/ci/kokoro/simulate_all b/ci/kokoro/build-armhf.sh
index 591245bd5..9f676ee66 100755
--- a/ci/kokoro/simulate_all
+++ b/ci/kokoro/build-armhf.sh
@@ -2,9 +2,7 @@
# Copyright 2021 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-#
-# Simulates all kokoro jobs.
-cd $(dirname "$0")
-time ./simulate build-x86_64.sh &&
- time ./simulate build-aarch64.sh
+source "$(dirname $0)/common.sh"
+
+./tools/dev_container --hermetic ./tools/run_tests --target=vm:aarch64 --arch armhf -v
diff --git a/ci/kokoro/build-merge-into-chromeos.sh b/ci/kokoro/build-merge-into-chromeos.sh
new file mode 100755
index 000000000..2e84c68ca
--- /dev/null
+++ b/ci/kokoro/build-merge-into-chromeos.sh
@@ -0,0 +1,222 @@
+#!/bin/bash
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+set -e
+
+readonly GERRIT_URL=https://chromium-review.googlesource.com
+readonly ORIGIN=https://chromium.googlesource.com/chromiumos/platform/crosvm
+readonly RETRIES=3
+readonly MIN_COMMIT_COUNT=${MIN_COMMIT_COUNT:-5}
+
+gerrit_api_get() {
+ # GET request to the gerrit API. Strips XSSI protection line from output.
+ # See: https://gerrit-review.googlesource.com/Documentation/dev-rest-api.html
+ local url="${GERRIT_URL}/${1}"
+ curl --silent "$url" | tail -n +2
+}
+
+gerrit_api_post() {
+ # POST to gerrit API using http cookies from git.
+ local endpoint=$1
+ local body=$2
+ local cookie_file=$(git config http.cookiefile)
+ if [[ -z "${cookie_file}" ]]; then
+ echo 1>&2 "Cannot find git http cookie file."
+ return 1
+ fi
+ local url="${GERRIT_URL}/${endpoint}"
+ curl --silent \
+ --cookie "${cookie_file}" \
+ -X POST \
+ -d "${body}" \
+ -H "Content-Type: application/json" \
+ "$url" |
+ tail -n +2
+}
+
+query_change() {
+ # Query gerrit for a specific change.
+ # See: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-change
+ gerrit_api_get "changes/$1/?o=CURRENT_REVISION"
+}
+
+query_changes() {
+ # Query gerrit for a list of changes.
+ # See: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
+ local query=$(printf '%s+' "$@")
+ gerrit_api_get "changes/?q=${query}"
+}
+
+query_related_changes() {
+ # Query related changes from gerrit.
+ # See: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#get-related-changes
+ gerrit_api_get "changes/$1/revisions/current/related"
+}
+
+get_previous_merge_id() {
+ # Query all open merge commits previously made by crosvm-bot. May be null if
+ # none are open.
+ query=(
+ project:chromiumos/platform/crosvm
+ branch:chromeos
+ status:open
+ owner:crosvm-bot@crosvm-packages.iam.gserviceaccount.com
+ -hashtag:dryrun
+ )
+ # Pick the one that was created last.
+ query_changes "${query[@]}" |
+ jq --raw-output 'sort_by(.created)[-1].id'
+}
+
+get_last_change_in_chain() {
+ # Use the related changes API to find the last change in the chain of
+ # commits.
+ local change_id=$1
+
+ # The list of commits is sorted by the git commit order, with the latest
+ # change first and includes the current change.
+ local last_change
+ last_change=$(query_related_changes "$change_id" |
+ jq --raw-output "[.changes[] | select(.status == \"NEW\")][0].change_id")
+
+ # If there are no related changes the list will be empty.
+ if [ "$last_change" == "null" ]; then
+ echo "${change_id}"
+ else
+ # The related API does not give us the unique ID of changes. Build it manually.
+ echo "chromiumos%2Fplatform%2Fcrosvm~chromeos~${last_change}"
+ fi
+}
+
+fetch_change() {
+ # Fetch the provided change and print the commit sha.
+ local change_id=$1
+
+ # Find the git ref we need to fetch.
+ local change_ref
+ change_ref=$(query_change "$change_id" |
+ jq --raw-output -e ".revisions[.current_revision].ref")
+ git fetch -q origin "${change_ref}"
+}
+
+get_dry_run_ids() {
+ # Query all dry run changes. They are identified by the hashtag:dryrun when
+ # uploaded.
+ query=(
+ project:chromiumos/platform/crosvm
+ branch:chromeos
+ status:open
+ hashtag:dryrun
+ owner:crosvm-bot@crosvm-packages.iam.gserviceaccount.com
+ )
+ query_changes "${query[@]}" |
+ jq --raw-output '.[].id'
+}
+
+abandon_dry_runs() {
+ # Abandon all pending dry run commits
+ for change in $(get_dry_run_ids); do
+ echo "Abandoning ${GERRIT_URL}/q/${change}"
+ gerrit_api_post "a/changes/${change}/abandon" "{}" >/dev/null
+ done
+}
+
+gerrit_prerequisites() {
+ # Authenticate to GoB if we don't already have a cookie.
+ # This should only happen when running in Kokoro, not locally.
+ # See: go/gob-gce
+ if [[ -z $(git config http.cookiefile) ]]; then
+ git clone https://gerrit.googlesource.com/gcompute-tools \
+ "${KOKORO_ARTIFACTS_DIR}/gcompute-tools"
+ "${KOKORO_ARTIFACTS_DIR}/gcompute-tools/git-cookie-authdaemon" --no-fork
+
+ # Setup correct user info for the service account.
+ git config user.name "Crosvm Bot"
+ git config user.email crosvm-bot@crosvm-packages.iam.gserviceaccount.com
+ fi
+
+ # We cannot use the original origin that kokoro used, as we no longer have
+ # access the GoB host via rpc://.
+ git remote remove origin
+ git remote add origin ${ORIGIN}
+ git fetch -q origin
+
+ # Set up gerrit Change-Id hook.
+ mkdir -p .git/hooks
+ curl -Lo .git/hooks/commit-msg \
+ https://gerrit-review.googlesource.com/tools/hooks/commit-msg
+ chmod +x .git/hooks/commit-msg
+}
+
+upload() {
+ git push origin \
+ "HEAD:refs/for/chromeos%r=crosvm-uprev@google.com,$1"
+}
+
+upload_with_retries() {
+ # Try uploading to gerrit. Retry due to errors on first upload.
+ # See: b/209031134
+ for i in $(seq 1 $RETRIES); do
+ echo "Push attempt $i"
+ if upload "$1"; then
+ return 0
+ fi
+ done
+ return 1
+}
+
+main() {
+ cd "${KOKORO_ARTIFACTS_DIR}/git/crosvm"
+
+ gerrit_prerequisites
+
+ # Make a copy of the merge script, so we are using the HEAD version to
+ # create the merge.
+ cp ./tools/chromeos/create_merge "${KOKORO_ARTIFACTS_DIR}/create_merge"
+
+ # Clean possible stray files from previous builds.
+ git clean -f -d -x
+ git checkout -f
+
+ # Parent commit to use for this merge.
+ local parent_commit="origin/chromeos"
+
+ # Query gerrit to find the latest merge commit and fetch it to be used as
+ # a parent.
+ local previous_merge="$(get_previous_merge_id)"
+ if [ "$previous_merge" != "null" ]; then
+ # The oncall may have uploaded a custom merge or cherry-pick on top
+ # of the detected merge. Find the last changed in that chain.
+ local last_change_in_chain=$(get_last_change_in_chain "${previous_merge}")
+ echo "Found previous merge: ${GERRIT_URL}/q/${previous_merge}"
+ echo "Last change in that chain: ${GERRIT_URL}/q/${last_change_in_chain}"
+ fetch_change "${last_change_in_chain}"
+ parent_commit="FETCH_HEAD"
+ fi
+
+ echo "Checking out parent: ${parent_commit}"
+ git checkout -b chromeos "${parent_commit}"
+ git branch --set-upstream-to origin/chromeos chromeos
+
+ local merge_count=$(git log --oneline --decorate=no --no-color \
+ "${parent_commit}..origin/main" | wc -l)
+ if [ "${merge_count}" -ge "$MIN_COMMIT_COUNT" ]; then
+ "${KOKORO_ARTIFACTS_DIR}/create_merge" "origin/main"
+ else
+ echo "Not enough commits to merge."
+ fi
+
+ upload_with_retries
+
+ echo "Abandoning previous dry runs"
+ abandon_dry_runs
+
+ echo "Creating dry run merge"
+ git checkout -b dryrun --track origin/chromeos
+
+ "${KOKORO_ARTIFACTS_DIR}/create_merge" --dry-run-only "origin/main"
+ upload_with_retries "hashtag=dryrun,l=Commit-Queue+1,l=Bot-Commit+1"
+}
+
+main
diff --git a/ci/kokoro/build-push-to-github.sh b/ci/kokoro/build-push-to-github.sh
new file mode 100755
index 000000000..814e24aef
--- /dev/null
+++ b/ci/kokoro/build-push-to-github.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+GITHUB_TOKEN_FILE=$KOKORO_KEYSTORE_DIR/76320_github_token
+TOKEN=$(cat $GITHUB_TOKEN_FILE)
+
+cd "${KOKORO_ARTIFACTS_DIR}/git/crosvm"
+git push --force --all "https://crosvm-bot:${TOKEN}@github.com/google/crosvm.git"
diff --git a/ci/kokoro/build-x86_64.sh b/ci/kokoro/build-x86_64.sh
index d35fc2228..6132fa2d0 100755
--- a/ci/kokoro/build-x86_64.sh
+++ b/ci/kokoro/build-x86_64.sh
@@ -4,8 +4,10 @@
# found in the LICENSE file.
source "$(dirname $0)/common.sh"
-./ci/run_container.sh crosvm_builder --vm "\
- ./run_tests -v --require-all \
- --junit-file=/workspace/logs/cargo_test/sponge_log.xml \
- && bin/clippy \
- && bin/fmt --check"
+./tools/dev_container --hermetic bash -c "\
+ ./tools/run_tests --target=host -v \
+ && ./tools/clippy \
+ && ./tools/fmt --check \
+ && cargo build --verbose --no-default-features \
+ && mdbook build ./docs/book \
+ && ./tools/cargo-doc"
diff --git a/ci/kokoro/common.sh b/ci/kokoro/common.sh
index c1e1e8b3b..74c7ee12f 100755
--- a/ci/kokoro/common.sh
+++ b/ci/kokoro/common.sh
@@ -3,8 +3,6 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-crosvm_root="${KOKORO_ARTIFACTS_DIR}"/git/crosvm
-
# Enable SSH access to the kokoro builder.
# Use the fusion2/ UI to trigger a build and set the DEBUG_SSH_KEY environment
# variable to your public key, that will allow you to connect to the builder
@@ -14,49 +12,49 @@ crosvm_root="${KOKORO_ARTIFACTS_DIR}"/git/crosvm
if [[ ! -z "${DEBUG_SSH_KEY}" ]]; then
echo "${DEBUG_SSH_KEY}" >>~/.ssh/authorized_keys
external_ip=$(
- curl -s -H "Metadata-Flavor: Google"
- http://metadata/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip
+ curl -s -H "Metadata-Flavor: Google" \
+ http://metadata/computeMetadata/v1/instance/network-interfaces/0/access-configs/0/external-ip
)
echo "SSH Debug enabled. Connect to: kbuilder@${external_ip}"
fi
setup_source() {
- if [ -z "${KOKORO_ARTIFACTS_DIR}" ]; then
+ if [ ! -d "${KOKORO_ARTIFACTS_DIR}" ]; then
echo "This script must be run in kokoro"
exit 1
fi
- cd "${KOKORO_ARTIFACTS_DIR}"
+ mkdir -p "${KOKORO_ARTIFACTS_DIR}/logs"
- echo ""
- echo "Downloading crosvm dependencies to $(pwd)/cros..."
- mkdir cros
- cd cros
+ cd "${KOKORO_ARTIFACTS_DIR}/git/crosvm"
- # repo gets confused by pyenv, make sure we select 3.6.1 as our default
+ # The Kokoro builder has not the required packages from ./tools/install-deps and an old python
# version.
- if command -v pyenv >/dev/null; then
- echo "Selecting Python 3.6.1"
- pyenv global 3.6.1
+ # Install what is needed to run ./tools/dev_container
+ # Note: This won't be necessary once we switch to a custom Kokoro image (or luci)
+ if command -v pyenv; then
+ pyenv install -v 3.9.5
+ pyenv global 3.9.5
+ pip install argh
fi
- curl -s https://storage.googleapis.com/git-repo-downloads/repo >repo
- chmod +x repo
- ./repo init --depth 1 \
- -u https://chromium.googlesource.com/chromiumos/manifest.git \
- --repo-url https://chromium.googlesource.com/external/repo.git \
- -g crosvm || return 1
- ./repo sync -j8 -c || return 1
- # Bind mount source into cros checkout.
- echo ""
- echo "Mounting crosvm source to $(pwd)/src/platform/crosvm..."
- rm -rf src/platform/crosvm && mkdir -p src/platform/crosvm
- if command -v bindfs >/dev/null; then
- bindfs "${crosvm_root}" src/platform/crosvm || return 1
- else
- sudo mount --bind "${crosvm_root}" src/platform/crosvm || return 1
+ echo "Rebasing changes to ToT"
+ # We cannot use the original origin that kokoro used, as we no longer have
+ # access the GoB host via rpc://.
+ git remote remove origin
+ git remote add origin https://chromium.googlesource.com/chromiumos/platform/crosvm
+ git fetch -q origin
+
+ # For some mysterious reason symlinks show up as modified, which prevents
+ # us from rebasing the changes.
+ git checkout -f
+ git rebase origin/main
+ if [ $? -ne 0 ]; then
+ return 1
fi
+ echo "Fetching Submodules..."
+ git submodule update --init
}
cleanup() {
@@ -66,12 +64,6 @@ cleanup() {
sleep 1h
fi
- if command -v bindfs >/dev/null; then
- fusermount -uz "${KOKORO_ARTIFACTS_DIR}/cros/src/platform/crosvm"
- else
- sudo umount --lazy "${KOKORO_ARTIFACTS_DIR}/cros/src/platform/crosvm"
- fi
-
# List files in the logs directory which are uploaded to sponge.
echo "Build Artifacts:"
ls "${KOKORO_ARTIFACTS_DIR}/logs"
@@ -86,4 +78,4 @@ setup_source || {
# Set logs directory so we can copy them to sponge
export CROSVM_BUILDER_LOGS_DIR="${KOKORO_ARTIFACTS_DIR}/logs"
-cd "${KOKORO_ARTIFACTS_DIR}/cros/src/platform/crosvm"
+cd "${KOKORO_ARTIFACTS_DIR}/git/crosvm"
diff --git a/ci/kokoro/continuous-armhf.cfg b/ci/kokoro/continuous-armhf.cfg
new file mode 100644
index 000000000..a628238e8
--- /dev/null
+++ b/ci/kokoro/continuous-armhf.cfg
@@ -0,0 +1,3 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+build_file: "crosvm/ci/kokoro/build-armhf.sh"
diff --git a/ci/kokoro/merge-into-chromeos.cfg b/ci/kokoro/merge-into-chromeos.cfg
new file mode 100644
index 000000000..bb5b1416f
--- /dev/null
+++ b/ci/kokoro/merge-into-chromeos.cfg
@@ -0,0 +1,3 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+build_file: "crosvm/ci/kokoro/build-merge-into-chromeos.sh"
diff --git a/ci/kokoro/presubmit-cq-armhf.cfg b/ci/kokoro/presubmit-cq-armhf.cfg
new file mode 100644
index 000000000..a628238e8
--- /dev/null
+++ b/ci/kokoro/presubmit-cq-armhf.cfg
@@ -0,0 +1,3 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+build_file: "crosvm/ci/kokoro/build-armhf.sh"
diff --git a/ci/kokoro/presubmit-cr-armhf.cfg b/ci/kokoro/presubmit-cr-armhf.cfg
new file mode 100644
index 000000000..a628238e8
--- /dev/null
+++ b/ci/kokoro/presubmit-cr-armhf.cfg
@@ -0,0 +1,3 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+build_file: "crosvm/ci/kokoro/build-armhf.sh"
diff --git a/ci/kokoro/push-to-github.cfg b/ci/kokoro/push-to-github.cfg
new file mode 100644
index 000000000..2ab07995d
--- /dev/null
+++ b/ci/kokoro/push-to-github.cfg
@@ -0,0 +1,13 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+build_file: "crosvm/ci/kokoro/build-push-to-github.sh"
+
+before_action {
+ fetch_keystore {
+ keystore_resource {
+ keystore_config_id: 76320
+ keyname: "github_token"
+ backend: "blade:keystore-fastconfigpush"
+ }
+ }
+}
diff --git a/ci/kokoro/simulate b/ci/kokoro/simulate
deleted file mode 100755
index bd192abbb..000000000
--- a/ci/kokoro/simulate
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/bash
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Simulates a Kokoro run executing one of the build-*.sh scripts.
-# e.g. ./ci/kokoro/simulate build-aarch64.sh
-#
-# For ease of use, just call: ./ci/kokoro/simulate_all
-
-crosvm_src=$(realpath $(dirname $0)/../../)
-kokoro_root=$(mktemp -d)
-kokoro_src="${kokoro_root}/src/git/crosvm"
-
-cleanup() {
- rm -rf "${kokoro_root}"
-}
-
-main() {
- echo "Copying ${crosvm_src}/ to ${kokoro_src}"
- mkdir -p "${kokoro_src}"
- rsync -arq --exclude "target" --exclude ".git" "${crosvm_src}/" "${kokoro_src}"
-
- # Run user-provided kokoro build script.
- export KOKORO_ARTIFACTS_DIR="${kokoro_root}/src"
- echo "Running $1 in ${kokoro_root}"
- bash $(dirname $0)/$1
- echo "$1 returned $?"
-}
-
-trap cleanup EXIT
-main "$@"
diff --git a/ci/kokoro/simulate.py b/ci/kokoro/simulate.py
new file mode 100755
index 000000000..40eee623d
--- /dev/null
+++ b/ci/kokoro/simulate.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from pathlib import Path
+import argparse
+import os
+import subprocess
+import tempfile
+
+USAGE="""
+Simulates a Kokoro run executing one of the build-* scripts.
+
+Example:
+ $ cd ./ci/kokoro
+ $ ./simulate.py build-aarch64.sh
+"""
+
+CROSVM_ROOT = Path(__file__).parent.parent.parent.resolve()
+
+
+def git_clone_source(source: Path, destination: Path):
+ destination.mkdir(parents=True, exist_ok=True)
+ print(f"Cloning {source} into {destination}:")
+ subprocess.check_call(['git', 'clone', '-q', source, destination])
+
+
+def run_kokoro_build_script(kokoro_root: Path, script_path: Path):
+ print(f"Running {script_path}:")
+ env=os.environ.copy()
+ env['KOKORO_ARTIFACTS_DIR'] = str(kokoro_root / 'src')
+ subprocess.check_call([script_path.resolve()], cwd=kokoro_root, env=env)
+
+
+def simulate_kokoro(kokoro_root: Path, script_path: Path):
+ git_clone_source(CROSVM_ROOT, kokoro_root / 'src/git/crosvm')
+ run_kokoro_build_script(kokoro_root, script_path)
+
+
+def main():
+ parser = argparse.ArgumentParser(usage=USAGE)
+ parser.add_argument("script_name", type=Path)
+ args = parser.parse_args()
+
+ script_path: Path= args.script_name
+ if not script_path.exists():
+ raise ValueError(f"Script '{script_path} not found.")
+
+ with tempfile.TemporaryDirectory() as temp_dir:
+ simulate_kokoro(Path(temp_dir), script_path)
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ci/kokoro/windows/common.cfg b/ci/kokoro/windows/common.cfg
new file mode 100644
index 000000000..8749e89fc
--- /dev/null
+++ b/ci/kokoro/windows/common.cfg
@@ -0,0 +1,9 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+timeout_mins: 40
+
+action {
+ define_artifacts {
+ regex: "logs/**/*"
+ }
+}
diff --git a/ci/kokoro/windows/continuous-windows.cfg b/ci/kokoro/windows/continuous-windows.cfg
new file mode 100644
index 000000000..9605de89a
--- /dev/null
+++ b/ci/kokoro/windows/continuous-windows.cfg
@@ -0,0 +1,3 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+build_file: "crosvm/ci/kokoro/windows/crosvm_build.bat"
diff --git a/ci/kokoro/windows/crosvm_build.bat b/ci/kokoro/windows/crosvm_build.bat
new file mode 100644
index 000000000..942e477d2
--- /dev/null
+++ b/ci/kokoro/windows/crosvm_build.bat
@@ -0,0 +1,54 @@
+:: Copyright 2022 The Chromium OS Authors. All rights reserved.
+:: Use of this source code is governed by a BSD-style license that can be
+:: found in the LICENSE file.
+
+:: Make environment changes (cd, env vars, etc.) local, so they don't affect the calling batch file
+setlocal
+
+:: Code under repo is checked out to %KOKORO_ARTIFACTS_DIR%\git.
+:: The final directory name in this path is determined by the scm name specified
+:: in the job configuration
+cd %KOKORO_ARTIFACTS_DIR%\git\crosvm
+
+:: Pin rustup to a known/tested version.
+set rustup_version=1.24.3
+
+:: Install rust toolchain through rustup.
+echo [%TIME%] installing rustup %rustup_version%
+choco install -y rustup.install --version=%rustup_version%
+
+:: Reload path for installed rustup binary
+call RefreshEnv.cmd
+
+:: Toolchain version and necessary components will be automatically picked
+:: up from rust-toolchain
+cargo install bindgen
+
+:: Install python. The default kokoro intalled version is 3.7 but linux tests
+:: seem to run on 3.9+.
+choco install -y python
+choco install python --version=3.9.0
+
+:: Reload path for installed rust toolchain.
+call RefreshEnv.cmd
+
+:: Log the version of the Rust toolchain
+echo [%TIME%] Using Rust toolchain version:
+cargo --version
+rustc --version
+
+:: Log python version
+echo [%TIME%] Python version:
+py --version
+
+py -m pip install argh --user
+
+echo [%TIME%] Calling crosvm\tools\clippy
+py .\tools\clippy
+if %ERRORLEVEL% neq 0 ( exit /b %ERRORLEVEL% )
+
+echo [%TIME%] Calling crosvm\build_test.py
+py ./tools\impl/test_runner.py --arch win64 -v
+if %ERRORLEVEL% neq 0 ( exit /b %ERRORLEVEL% )
+
+exit /b %ERRORLEVEL%
diff --git a/ci/kokoro/windows/presubmit-windows.cfg b/ci/kokoro/windows/presubmit-windows.cfg
new file mode 100644
index 000000000..9605de89a
--- /dev/null
+++ b/ci/kokoro/windows/presubmit-windows.cfg
@@ -0,0 +1,3 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+build_file: "crosvm/ci/kokoro/windows/crosvm_build.bat"
diff --git a/ci/run_container.sh b/ci/run_container.sh
deleted file mode 100755
index 27ecc2a8a..000000000
--- a/ci/run_container.sh
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/bin/bash
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Runs a crosvm builder. Will use podman if available, falls back to docker.
-# Usage:
-# run_container.sh builder_name entry point args...
-#
-# The scratch or logs directory can be enabled by setting the env variables
-# CROSVM_BUILDER_SCRATCH_DIR or CROSVM_BUILDER_LOGS_DIR.
-
-crosvm_root=$(realpath "$(dirname $0)/..")
-cros_root=$(realpath "${crosvm_root}/../../..")
-
-if [ ! -d "${cros_root}/.repo" ]; then
- echo "The CI builder must be run from a cros checkout. See ci/README.md"
- exit 1
-fi
-
-# Parse parameters
-builder="$1"
-shift
-
-# User podman if available for root-less execution. Fall-back to docker.
-if which podman >/dev/null; then
- run() {
- # The run.oci.keep_original_groups flag allows us to access devices to
- # which the calling user only has access via a group membership (i.e.
- # /dev/kvm). See: https://github.com/containers/podman/issues/4477
- podman run \
- --runtime /usr/bin/crun \
- --annotation run.oci.keep_original_groups=1 \
- --security-opt label=disable \
- "$@"
- }
-else
- run() {
- docker run "$@"
- }
-fi
-
-version=$(cat $(dirname $0)/image_tag)
-echo "Using builder: ${builder}:${version}"
-
-src="${cros_root}/src"
-echo "Using source directory: ${src} (Available at /workspace/src)"
-
-docker_args=(
- --rm
- --device /dev/kvm
- --volume /dev/log:/dev/log
- --volume "${src}":/workspace/src:rw
-)
-
-if [ ! -z "${CROSVM_BUILDER_SCRATCH_DIR}" ]; then
- echo "Using scratch directory: ${CROSVM_BUILDER_SCRATCH_DIR}\
- (Available at /workspace/scratch)"
- mkdir -p "${CROSVM_BUILDER_SCRATCH_DIR}"
- docker_args+=(
- --volume "${CROSVM_BUILDER_SCRATCH_DIR}:/workspace/scratch:rw"
- )
-fi
-
-if [ ! -z "${CROSVM_BUILDER_LOGS_DIR}" ]; then
- echo "Using logs directory: ${CROSVM_BUILDER_LOGS_DIR}\
- (Available at /workspace/logs)"
- mkdir -p "${CROSVM_BUILDER_LOGS_DIR}"
- docker_args+=(--volume "${CROSVM_BUILDER_LOGS_DIR}":/workspace/logs:rw)
-fi
-
-# Enable interactive mode when running in an interactive terminal.
-if [ -t 1 ]; then
- docker_args+=(-it)
-fi
-
-echo ""
-run ${docker_args[@]} \
- "gcr.io/crosvm-packages/${builder}:${version}" \
- "$@"
diff --git a/ci/test_runner.py b/ci/test_runner.py
deleted file mode 100644
index 1207207c1..000000000
--- a/ci/test_runner.py
+++ /dev/null
@@ -1,629 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Test runner for crosvm:
-# - Selects which tests to run based on local environment
-# - Can run some tests single-threaded
-# - Can run some tests using the VM provided by the builders.
-# - Can generate junit xml files for integration with sponge
-#
-# The crates and feature to test are configured in ./run_tests
-
-from typing import Iterable, List, Dict, Set, Optional, Union
-import argparse
-import enum
-import os
-import platform
-import subprocess
-import sys
-import re
-import xml.etree.ElementTree as ET
-import pathlib
-
-# Print debug info. Overriden by -v or -vv
-VERBOSE = False
-VERY_VERBOSE = False
-
-# Runs tests using the exec_file wrapper, which will run the test inside the
-# builders built-in VM.
-VM_TEST_RUNNER = (
- os.path.abspath("./ci/vm_tools/exec_binary_in_vm") + " --no-sync"
-)
-
-# Runs tests using QEMU user-space emulation.
-QEMU_TEST_RUNNER = (
- "qemu-aarch64-static -E LD_LIBRARY_PATH=/workspace/scratch/lib"
-)
-
-# Kill a test after 5 minutes to prevent frozen tests from running too long.
-TEST_TIMEOUT_SECS = 300
-
-
-class Requirements(enum.Enum):
- # Test can only be built for aarch64.
- AARCH64 = "aarch64"
-
- # Test can only be built for x86_64.
- X86_64 = "x86_64"
-
- # Requires ChromeOS build environment.
- CROS_BUILD = "cros_build"
-
- # Test is disabled explicitly.
- DISABLED = "disabled"
-
- # Test needs to be executed with expanded privileges for device access and
- # will be run inside a VM.
- PRIVILEGED = "privileged"
-
- # Test needs to run single-threaded
- SINGLE_THREADED = "single_threaded"
-
- # Separate workspaces that have dev-dependencies cannot be built from the
- # crosvm workspace and need to be built separately.
- # Note: Separate workspaces are built with no features enabled.
- SEPARATE_WORKSPACE = "separate_workspace"
-
- # Build, but do not run.
- DO_NOT_RUN = "do_not_run"
-
-
-BUILD_TIME_REQUIREMENTS = [
- Requirements.AARCH64,
- Requirements.X86_64,
- Requirements.CROS_BUILD,
- Requirements.DISABLED,
-]
-
-
-class CrateInfo(object):
- """Informaton about whether a crate can be built or run on this host."""
-
- def __init__(
- self,
- name: str,
- requirements: Set[Requirements],
- capabilities: Set[Requirements],
- ):
- self.name = name
- self.requirements = requirements
- self.single_threaded = Requirements.SINGLE_THREADED in requirements
- self.needs_privilege = Requirements.PRIVILEGED in requirements
-
- build_reqs = requirements.intersection(BUILD_TIME_REQUIREMENTS)
- self.can_build = all(req in capabilities for req in build_reqs)
-
- self.can_run = (
- self.can_build
- and (
- not self.needs_privilege
- or Requirements.PRIVILEGED in capabilities
- )
- and not Requirements.DO_NOT_RUN in self.requirements
- )
-
- def __repr__(self):
- return f"{self.name} {self.requirements}"
-
-
-def target_arch():
- """Returns architecture cargo is set up to build for."""
- if "CARGO_BUILD_TARGET" in os.environ:
- target = os.environ["CARGO_BUILD_TARGET"]
- return target.split("-")[0]
- else:
- return platform.machine()
-
-
-def get_test_runner_env(use_vm: bool):
- """Sets the target.*.runner cargo setting to use the correct test runner."""
- env = os.environ.copy()
- key = f"CARGO_TARGET_{target_arch().upper()}_UNKNOWN_LINUX_GNU_RUNNER"
- if use_vm:
- env[key] = VM_TEST_RUNNER
- else:
- if target_arch() == "aarch64":
- env[key] = QEMU_TEST_RUNNER
- else:
- if key in env:
- del env[key]
- return env
-
-
-class TestResult(enum.Enum):
- PASS = "Pass"
- FAIL = "Fail"
- SKIP = "Skip"
- UNKNOWN = "Unknown"
-
-
-class CrateResults(object):
- """Container for results of a single cargo test call."""
-
- def __init__(self, crate_name: str, success: bool, cargo_test_log: str):
- self.crate_name = crate_name
- self.success = success
- self.cargo_test_log = cargo_test_log
-
- # Parse "test test_name... ok|ignored|FAILED" messages from cargo log.
- test_regex = re.compile(r"^test ([\w\/_\-\.:() ]+) \.\.\. (\w+)$")
- self.tests: Dict[str, TestResult] = {}
- for line in cargo_test_log.split(os.linesep):
- match = test_regex.match(line)
- if match:
- name = match.group(1)
- result = match.group(2)
- if result == "ok":
- self.tests[name] = TestResult.PASS
- elif result == "ignored":
- self.tests[name] = TestResult.SKIP
- elif result == "FAILED":
- self.tests[name] = TestResult.FAIL
- else:
- self.tests[name] = TestResult.UNKNOWN
-
- def total(self):
- return len(self.tests)
-
- def count(self, result: TestResult):
- return sum(r == result for r in self.tests.values())
-
- def to_junit(self):
- testsuite = ET.Element(
- "testsuite",
- {
- "name": self.crate_name,
- "tests": str(self.total()),
- "failures": str(self.count(TestResult.FAIL)),
- },
- )
- for (test, result) in self.tests.items():
- testcase = ET.SubElement(
- testsuite, "testcase", {"name": f"{self.crate_name} - ${test}"}
- )
- if result == TestResult.SKIP:
- ET.SubElement(
- testcase, "skipped", {"message": "Disabled in rust code."}
- )
- else:
- testcase.set("status", "run")
- if result == TestResult.FAIL:
- failure = ET.SubElement(
- testcase, "failure", {"message": "Test failed."}
- )
- failure.text = self.cargo_test_log
-
- return testsuite
-
-
-class RunResults(object):
- """Container for results of the whole test run."""
-
- def __init__(self, crate_results: Iterable[CrateResults]):
- self.crate_results = list(crate_results)
- self.success: bool = (
- len(self.crate_results) > 0 and self.count(TestResult.FAIL) == 0
- )
-
- def total(self):
- return sum(r.total() for r in self.crate_results)
-
- def count(self, result: TestResult):
- return sum(r.count(result) for r in self.crate_results)
-
- def to_junit(self):
- testsuites = ET.Element("testsuites", {"name": "Cargo Tests"})
- for crate_result in self.crate_results:
- testsuites.append(crate_result.to_junit())
- return testsuites
-
-
-def results_summary(results: Union[RunResults, CrateResults]):
- """Returns a concise 'N passed, M failed' summary of `results`"""
- num_pass = results.count(TestResult.PASS)
- num_skip = results.count(TestResult.SKIP)
- num_fail = results.count(TestResult.FAIL)
- msg: List[str] = []
- if num_pass:
- msg.append(f"{num_pass} passed")
- if num_skip:
- msg.append(f"{num_skip} skipped")
- if num_fail:
- msg.append(f"{num_fail} failed")
- return ", ".join(msg)
-
-
-def cargo_build_process(
- cwd: str = ".", crates: List[CrateInfo] = [], features: Set[str] = set()
-):
- """Builds the main crosvm crate."""
- cmd = [
- "cargo",
- "build",
- "--color=never",
- "--no-default-features",
- "--features",
- ",".join(features),
- ]
-
- for crate in sorted(crate.name for crate in crates):
- cmd += ["-p", crate]
-
- if VERY_VERBOSE:
- print("CMD", " ".join(cmd))
-
- process = subprocess.run(
- cmd,
- cwd=cwd,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- text=True,
- )
- if process.returncode != 0 or VERBOSE:
- print()
- print(process.stdout)
- return process
-
-
-def cargo_test_process(
- cwd: str,
- crates: List[CrateInfo] = [],
- features: Set[str] = set(),
- run: bool = True,
- single_threaded: bool = False,
- use_vm: bool = False,
- timeout: Optional[int] = None,
-):
- """Creates the subprocess to run `cargo test`."""
- cmd = ["cargo", "test", "--color=never"]
- if not run:
- cmd += ["--no-run"]
- if features:
- cmd += ["--no-default-features", "--features", ",".join(features)]
-
- # Skip doc tests as these cannot be run in the VM.
- if use_vm:
- cmd += ["--bins", "--tests"]
-
- for crate in sorted(crate.name for crate in crates):
- cmd += ["-p", crate]
-
- cmd += ["--", "--color=never"]
- if single_threaded:
- cmd += ["--test-threads=1"]
- env = get_test_runner_env(use_vm)
-
- if VERY_VERBOSE:
- print("ENV", env)
- print("CMD", " ".join(cmd))
-
- process = subprocess.run(
- cmd,
- cwd=cwd,
- env=env,
- timeout=timeout,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- text=True,
- )
- if process.returncode != 0 or VERBOSE:
- print()
- print(process.stdout)
- return process
-
-
-def cargo_build_tests(crates: List[CrateInfo], features: Set[str]):
- """Runs cargo test --no-run to build all listed `crates`."""
- separate_workspace_crates = [
- crate
- for crate in crates
- if Requirements.SEPARATE_WORKSPACE in crate.requirements
- ]
- workspace_crates = [
- crate
- for crate in crates
- if Requirements.SEPARATE_WORKSPACE not in crate.requirements
- ]
-
- print(
- "Building workspace: ",
- ", ".join(crate.name for crate in workspace_crates),
- )
- build_process = cargo_build_process(
- cwd=".", crates=workspace_crates, features=features
- )
- if build_process.returncode != 0:
- return False
- test_process = cargo_test_process(
- cwd=".", crates=workspace_crates, features=features, run=False
- )
- if test_process.returncode != 0:
- return False
-
- for crate in separate_workspace_crates:
- print("Building crate:", crate.name)
- build_process = cargo_build_process(cwd=crate.name)
- if build_process.returncode != 0:
- return False
- test_process = cargo_test_process(cwd=crate.name, run=False)
- if test_process.returncode != 0:
- return False
- return True
-
-
-def cargo_test(
- crates: List[CrateInfo],
- features: Set[str],
- single_threaded: bool = False,
- use_vm: bool = False,
-) -> Iterable[CrateResults]:
- """Runs cargo test for all listed `crates`."""
- for crate in crates:
- msg = ["Testing crate", crate.name]
- if use_vm:
- msg.append("in vm")
- if single_threaded:
- msg.append("(single-threaded)")
- if Requirements.SEPARATE_WORKSPACE in crate.requirements:
- msg.append("(separate workspace)")
- sys.stdout.write(f"{' '.join(msg)}... ")
- sys.stdout.flush()
-
- if Requirements.SEPARATE_WORKSPACE in crate.requirements:
- process = cargo_test_process(
- cwd=crate.name,
- run=True,
- single_threaded=single_threaded,
- use_vm=use_vm,
- timeout=TEST_TIMEOUT_SECS,
- )
- else:
- process = cargo_test_process(
- cwd=".",
- crates=[crate],
- features=features,
- run=True,
- single_threaded=single_threaded,
- use_vm=use_vm,
- timeout=TEST_TIMEOUT_SECS,
- )
- results = CrateResults(
- crate.name, process.returncode == 0, process.stdout
- )
- print(results_summary(results))
- yield results
-
-
-def execute_batched_by_parallelism(
- crates: List[CrateInfo], features: Set[str], use_vm: bool
-) -> Iterable[CrateResults]:
- """Batches tests by single-threaded and parallel, then executes them."""
- run_single = [crate for crate in crates if crate.single_threaded]
- yield from cargo_test(
- run_single, features, single_threaded=True, use_vm=use_vm
- )
-
- run_parallel = [crate for crate in crates if not crate.single_threaded]
- yield from cargo_test(run_parallel, features, use_vm=use_vm)
-
-
-def execute_batched_by_privilege(
- crates: List[CrateInfo], features: Set[str], use_vm: bool
-) -> Iterable[CrateResults]:
- """
- Batches tests by whether or not a test needs privileged access to run.
-
- Non-privileged tests are run first. Privileged tests are executed in
- a VM if use_vm is set.
- """
- build_crates = [crate for crate in crates if crate.can_build]
- if not cargo_build_tests(build_crates, features):
- return []
-
- simple_crates = [
- crate for crate in crates if crate.can_run and not crate.needs_privilege
- ]
- yield from execute_batched_by_parallelism(
- simple_crates, features, use_vm=False
- )
-
- privileged_crates = [
- crate for crate in crates if crate.can_run and crate.needs_privilege
- ]
- if privileged_crates:
- if use_vm:
- subprocess.run("./ci/vm_tools/sync_deps", check=True)
- yield from execute_batched_by_parallelism(
- privileged_crates, features, use_vm=True
- )
- else:
- yield from execute_batched_by_parallelism(
- privileged_crates, features, use_vm=False
- )
-
-
-def results_report(
- feature_requirements: Dict[str, List[Requirements]],
- crates: List[CrateInfo],
- features: Set[str],
- run_results: RunResults,
-):
- """Prints a summary report of all test results."""
- print()
-
- if len(run_results.crate_results) == 0:
- print("Could not build tests.")
- return
-
- crates_not_built = [crate.name for crate in crates if not crate.can_build]
- print(f"Crates not built: {', '.join(crates_not_built)}")
-
- crates_not_run = [
- crate.name for crate in crates if crate.can_build and not crate.can_run
- ]
- print(f"Crates not tested: {', '.join(crates_not_run)}")
-
- disabled_features: Set[str] = set(feature_requirements.keys()).difference(
- features
- )
- print(f"Disabled features: {', '.join(disabled_features)}")
-
- print()
- if not run_results.success:
- for crate_results in run_results.crate_results:
- if crate_results.success:
- continue
- print(f"Test failures in {crate_results.crate_name}:")
- for (test, result) in crate_results.tests.items():
- if result == TestResult.FAIL:
- print(f" {test}")
- print()
- print("Some tests failed:", results_summary(run_results))
- else:
- print("All tests passed:", results_summary(run_results))
-
-
-def execute_tests(
- crate_requirements: Dict[str, List[Requirements]],
- feature_requirements: Dict[str, List[Requirements]],
- capabilities: Set[Requirements],
- use_vm: bool,
- junit_file: Optional[str] = None,
-):
- print("Capabilities:", ", ".join(cap.value for cap in capabilities))
-
- # Select all features where capabilities meet the requirements
- features = set(
- feature
- for (feature, requirements) in feature_requirements.items()
- if all(r in capabilities for r in requirements)
- )
-
- # Disable sandboxing for tests until our builders are set up to run with
- # sandboxing.
- features.add("default-no-sandbox")
- print("Features:", ", ".join(features))
-
- crates = [
- CrateInfo(crate, set(requirements), capabilities)
- for (crate, requirements) in crate_requirements.items()
- ]
- run_results = RunResults(
- execute_batched_by_privilege(crates, features, use_vm)
- )
-
- if junit_file:
- pathlib.Path(junit_file).parent.mkdir(parents=True, exist_ok=True)
- ET.ElementTree(run_results.to_junit()).write(junit_file)
-
- results_report(feature_requirements, crates, features, run_results)
- if not run_results.success:
- exit(-1)
-
-
-DESCRIPTION = """\
-Runs tests for crosvm based on the capabilities of the local host.
-
-This script can be run directly on a worksation to run a limited number of tests
-that can be built and run on a standard debian system.
-
-It can also be run via the CI builder: `./ci/builder --vm ./run_tests`. This
-will build all tests and runs tests that require special privileges inside the
-virtual machine provided by the builder.
-"""
-
-
-def main(
- crate_requirements: Dict[str, List[Requirements]],
- feature_requirements: Dict[str, List[Requirements]],
-):
- parser = argparse.ArgumentParser(description=DESCRIPTION)
- parser.add_argument(
- "--verbose",
- "-v",
- action="store_true",
- default=False,
- help="Print all test output.",
- )
- parser.add_argument(
- "--very-verbose",
- "-vv",
- action="store_true",
- default=False,
- help="Print debug information and commands executed.",
- )
- parser.add_argument(
- "--run-privileged",
- action="store_true",
- default=False,
- help="Enable tests that requires privileged access to the system.",
- )
- parser.add_argument(
- "--cros-build",
- action="store_true",
- default=False,
- help=(
- "Enables tests that require a ChromeOS build environment. "
- "Can also be set by CROSVM_CROS_BUILD"
- ),
- )
- parser.add_argument(
- "--use-vm",
- action="store_true",
- default=False,
- help=(
- "Enables privileged tests to run in a VM. "
- "Can also be set by CROSVM_USE_VM"
- ),
- )
- parser.add_argument(
- "--require-all",
- action="store_true",
- default=False,
- help="Requires all tests to run, fail if tests would be disabled.",
- )
- parser.add_argument(
- "--junit-file",
- default=None,
- help="Path to file where to store junit xml results",
- )
- args = parser.parse_args()
-
- global VERBOSE, VERY_VERBOSE
- VERBOSE = args.verbose or args.very_verbose # type: ignore
- VERY_VERBOSE = args.very_verbose # type: ignore
-
- use_vm = os.environ.get("CROSVM_USE_VM") != None or args.use_vm
- cros_build = os.environ.get("CROSVM_CROS_BUILD") != None or args.cros_build
-
- capabilities = set()
- if target_arch() == "aarch64":
- capabilities.add(Requirements.AARCH64)
- elif target_arch() == "x86_64":
- capabilities.add(Requirements.X86_64)
-
- if cros_build:
- capabilities.add(Requirements.CROS_BUILD)
-
- if use_vm:
- if not os.path.exists("/workspace/vm"):
- print("--use-vm can only be used within the ./ci/builder's.")
- exit(1)
- capabilities.add(Requirements.PRIVILEGED)
-
- if args.run_privileged:
- capabilities.add(Requirements.PRIVILEGED)
-
- if args.require_all and not Requirements.PRIVILEGED in capabilities:
- print("--require-all needs to be run with --use-vm or --run-privileged")
- exit(1)
-
- execute_tests(
- crate_requirements,
- feature_requirements,
- capabilities,
- use_vm,
- args.junit_file,
- )
diff --git a/ci/vm_tools/README.md b/ci/vm_tools/README.md
deleted file mode 100644
index 0dcc87f06..000000000
--- a/ci/vm_tools/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-# VM Tools
-
-The scripts in this directory are used to make it easier to work with the test
-VM.
-
-The VM is expected to be accessible via `ssh vm` without a password prompt, this
-is set up by the builders, so it'll work out-of-the-box when running inside the
-builder shell.
diff --git a/ci/vm_tools/exec_binary_in_vm b/ci/vm_tools/exec_binary_in_vm
deleted file mode 100755
index 7b2c230d1..000000000
--- a/ci/vm_tools/exec_binary_in_vm
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Uploads and executes a file in the VM. This script can be set as a runner
-# for cargo to execute tests inside the VM.
-
-${0%/*}/wait_for_vm_with_timeout || exit 1
-
-if [ "$1" = "--no-sync" ]; then
- shift
-else
- echo "Syncing dependencies..."
- ${0%/*}/sync_deps || exit 1
-fi
-
-filepath=$1
-filename=$(basename $filepath)
-
-echo "Executing $filename ${@:2}"
-scp -q $filepath vm:/tmp/$filename
-ssh vm -q -t "cd /tmp && sudo ./$filename ${@:2}"
-
-# Make sure to preserve the exit code of $filename after cleaning up the file.
-ret=$?
-ssh vm -q -t "rm /tmp/$filename"
-exit $ret
diff --git a/ci/vm_tools/sync_deps b/ci/vm_tools/sync_deps
deleted file mode 100755
index a97b019e0..000000000
--- a/ci/vm_tools/sync_deps
+++ /dev/null
@@ -1,37 +0,0 @@
-#!/bin/bash
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Synchronizes dependencies of crosvm into the virtual machine to allow test
-# binaries to execute.
-
-${0%/*}/wait_for_vm_with_timeout || exit 1
-
-crosvm_root="/workspace/src/platform/crosvm"
-rust_toolchain=$(cat ${crosvm_root}/rust-toolchain)
-target_dir=$(
- cargo metadata --no-deps --format-version 1 |
- jq -r ".target_directory"
-)
-
-# List of shared objects used by crosvm that need to be synced.
-shared_objects=(
- /workspace/scratch/lib/*.so*
- /root/.rustup/toolchains/${rust_toolchain}-*/lib/libstd-*.so
- /root/.rustup/toolchains/${rust_toolchain}-*/lib/libtest-*.so
-)
-rsync -azPLq --rsync-path="sudo rsync" ${shared_objects[@]} vm:/usr/lib
-
-# Files needed by binaries at runtime in the working directory.
-if [ -z "${CARGO_BUILD_TARGET}" ]; then
- runtime_files=(
- "${target_dir}/debug/crosvm"
- )
-else
- runtime_files=(
- "${target_dir}/${CARGO_BUILD_TARGET}/debug/crosvm"
- )
-fi
-
-rsync -azPLq --rsync-path="sudo rsync" ${runtime_files} vm:/tmp
diff --git a/ci/vm_tools/wait_for_vm b/ci/vm_tools/wait_for_vm
deleted file mode 100755
index dca090f9a..000000000
--- a/ci/vm_tools/wait_for_vm
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Waits indefinitely for the VM to become available.
-
-test_ssh="ssh -o ConnectTimeout=1 vm -t exit"
-
-if ! $test_ssh 2>/dev/null >/dev/null; then
- echo "Waiting for VM to be available."
- until $test_ssh 2>/dev/null >/dev/null; do
- sleep 1
- printf "."
- done
- echo ""
-fi
diff --git a/common/README.md b/common/README.md
new file mode 100644
index 000000000..6011d653e
--- /dev/null
+++ b/common/README.md
@@ -0,0 +1,11 @@
+# Crosvm General Purpose Libraries
+
+The crates in this folder are general purpose libraries used by other projects in ChromeOS as well.
+
+To make them accessible independendly of crosvm, each of these crates is excluded from the crosvm
+workspace.
+
+## List of libraries
+
+- [cros-fuzz](cros-fuzz/): Support crate for fuzzing rust code in ChromeOS
+- [p9](p9): Server implementation of the 9p file system protocol
diff --git a/assertions/Android.bp b/common/assertions/Android.bp
index 5692b5930..6be93513f 100644
--- a/assertions/Android.bp
+++ b/common/assertions/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -10,34 +10,13 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "assertions_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "assertions",
- srcs: ["src/lib.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
-}
-
-rust_test_host {
- name: "assertions_host_test_src_lib",
- defaults: ["assertions_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "assertions_device_test_src_lib",
- defaults: ["assertions_defaults"],
-}
-
rust_library {
name: "libassertions",
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "assertions",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
}
diff --git a/assertions/Cargo.toml b/common/assertions/Cargo.toml
index 60b1c5e59..6608f092b 100644
--- a/assertions/Cargo.toml
+++ b/common/assertions/Cargo.toml
@@ -2,7 +2,7 @@
name = "assertions"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
include = ["src/**/*", "Cargo.toml"]
[workspace]
diff --git a/assertions/src/lib.rs b/common/assertions/src/lib.rs
index 2e9d18819..2e9d18819 100644
--- a/assertions/src/lib.rs
+++ b/common/assertions/src/lib.rs
diff --git a/assertions/src/mechanism.rs b/common/assertions/src/mechanism.rs
index e14b91a86..e14b91a86 100644
--- a/assertions/src/mechanism.rs
+++ b/common/assertions/src/mechanism.rs
diff --git a/common/audio_streams/Android.bp b/common/audio_streams/Android.bp
new file mode 100644
index 000000000..cd7b537c3
--- /dev/null
+++ b/common/audio_streams/Android.bp
@@ -0,0 +1,57 @@
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
+// Do not modify this file as changes will be overridden on upgrade.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_crosvm_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-BSD
+ default_applicable_licenses: ["external_crosvm_license"],
+}
+
+rust_test {
+ name: "audio_streams_test_src_audio_streams",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "audio_streams",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
+ srcs: ["src/audio_streams.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2021",
+ rustlibs: [
+ "libfutures",
+ "libsync_rust",
+ "libsys_util",
+ "libthiserror",
+ ],
+ proc_macros: [
+ "libasync_trait",
+ "libremain",
+ ],
+}
+
+rust_library {
+ name: "libaudio_streams",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "audio_streams",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
+ srcs: ["src/audio_streams.rs"],
+ edition: "2021",
+ rustlibs: [
+ "libfutures",
+ "libsync_rust",
+ "libthiserror",
+ ],
+ proc_macros: [
+ "libasync_trait",
+ "libremain",
+ ],
+}
diff --git a/common/audio_streams/Cargo.toml b/common/audio_streams/Cargo.toml
new file mode 100644
index 000000000..ceca885ea
--- /dev/null
+++ b/common/audio_streams/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "audio_streams"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2021"
+
+[lib]
+path = "src/audio_streams.rs"
+
+[dependencies]
+async-trait = "0.1.36"
+remain = "0.2"
+sync = { path = "../sync" } # provided by ebuild
+thiserror = "1.0.20"
+futures = "0.3"
+
+[target.'cfg(unix)'.dev-dependencies]
+sys_util = { path = "../sys_util" } # provided by ebuild
diff --git a/common/audio_streams/README.md b/common/audio_streams/README.md
new file mode 100644
index 000000000..1586d80bc
--- /dev/null
+++ b/common/audio_streams/README.md
@@ -0,0 +1,5 @@
+# Audio Server and Stream interfaces
+
+The `audio_streams` crate provides a basic interface for playing audio. This will be used to enable
+playback to various audio subsystems such as Alsa and cras. To start, an empty playback example
+`NoopStreamSource` is provided.
diff --git a/common/audio_streams/src/async_api.rs b/common/audio_streams/src/async_api.rs
new file mode 100644
index 000000000..8e94a009d
--- /dev/null
+++ b/common/audio_streams/src/async_api.rs
@@ -0,0 +1,83 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Traits required by `audio_streams` to perform async operations.
+//!
+//! Consumers must provide an implementation of `AudioStreamsExecutor` to allow the
+//! async `audio_streams` interface to do async io on the Executor provided by the consumer.
+//!
+//! These traits follow the interface of `cros_async`, since it is used by both crosvm and libcras
+//! to implement them.
+//!
+//! The implementation is provided in `cros_async::audio_streams_async`.
+
+#[cfg(unix)]
+use std::os::unix::net::UnixStream;
+
+use async_trait::async_trait;
+use std::io::Result;
+use std::time::Duration;
+
+#[async_trait(?Send)]
+pub trait ReadAsync {
+ /// Read asynchronously into the provided Vec.
+ ///
+ /// The ownership of the Vec is returned after the operation completes, along with the number of
+ /// bytes read.
+ async fn read_to_vec<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ vec: Vec<u8>,
+ ) -> Result<(usize, Vec<u8>)>;
+}
+
+#[async_trait(?Send)]
+pub trait WriteAsync {
+ /// Write asynchronously from the provided Vec.
+ ///
+ /// The ownership of the Vec is returned after the operation completes, along with the number of
+ /// bytes written.
+ async fn write_from_vec<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ vec: Vec<u8>,
+ ) -> Result<(usize, Vec<u8>)>;
+}
+
+pub trait ReadWriteAsync: ReadAsync + WriteAsync {}
+
+pub type AsyncStream = Box<dyn ReadWriteAsync + Send>;
+
+/// Trait of Executor functionality used by `audio_streams`.
+#[async_trait(?Send)]
+pub trait AudioStreamsExecutor {
+ /// Create an object to allow async reads/writes from the specified UnixStream.
+ #[cfg(unix)]
+ fn async_unix_stream(&self, f: UnixStream) -> Result<AsyncStream>;
+
+ /// Returns a future that resolves after the specified time.
+ async fn delay(&self, dur: Duration) -> Result<()>;
+}
+
+#[cfg(test)]
+pub mod test {
+ use super::*;
+
+ /// Stub implementation of the AudioStreamsExecutor to use in unit tests.
+ pub struct TestExecutor {}
+
+ #[async_trait(?Send)]
+ impl AudioStreamsExecutor for TestExecutor {
+ #[cfg(unix)]
+ fn async_unix_stream(&self, _f: UnixStream) -> Result<AsyncStream> {
+ panic!("Not Implemented");
+ }
+
+ async fn delay(&self, dur: Duration) -> Result<()> {
+ // Simulates the delay by blocking to satisfy behavior expected in unit tests.
+ std::thread::sleep(dur);
+ return Ok(());
+ }
+ }
+}
diff --git a/common/audio_streams/src/audio_streams.rs b/common/audio_streams/src/audio_streams.rs
new file mode 100644
index 000000000..c0b75d00c
--- /dev/null
+++ b/common/audio_streams/src/audio_streams.rs
@@ -0,0 +1,919 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Provides an interface for playing and recording audio.
+//!
+//! When implementing an audio playback system, the `StreamSource` trait is implemented.
+//! Implementors of this trait allow creation of `PlaybackBufferStream` objects. The
+//! `PlaybackBufferStream` provides the actual audio buffers to be filled with audio samples. These
+//! buffers can be filled with `write_playback_buffer`.
+//!
+//! Users playing audio fill the provided buffers with audio. When a `PlaybackBuffer` is dropped,
+//! the samples written to it are committed to the `PlaybackBufferStream` it came from.
+//!
+//! ```
+//! use audio_streams::{BoxError, PlaybackBuffer, SampleFormat, StreamSource, NoopStreamSource};
+//! use std::io::Write;
+//!
+//! const buffer_size: usize = 120;
+//! const num_channels: usize = 2;
+//!
+//! # fn main() -> std::result::Result<(), BoxError> {
+//! let mut stream_source = NoopStreamSource::new();
+//! let sample_format = SampleFormat::S16LE;
+//! let frame_size = num_channels * sample_format.sample_bytes();
+//!
+//! let (_, mut stream) = stream_source
+//! .new_playback_stream(num_channels, sample_format, 48000, buffer_size)?;
+//! // Play 10 buffers of DC.
+//! let mut buf = Vec::new();
+//! buf.resize(buffer_size * frame_size, 0xa5u8);
+//! for _ in 0..10 {
+//! let mut copy_cb = |stream_buffer: &mut PlaybackBuffer| {
+//! assert_eq!(stream_buffer.write(&buf)?, buffer_size * frame_size);
+//! Ok(())
+//! };
+//! stream.write_playback_buffer(&mut copy_cb)?;
+//! }
+//! # Ok (())
+//! # }
+//! ```
+pub mod async_api;
+
+use async_trait::async_trait;
+use std::cmp::min;
+use std::error;
+use std::fmt::{self, Display};
+use std::io::{self, Read, Write};
+#[cfg(unix)]
+use std::os::unix::io::RawFd;
+use std::result::Result;
+use std::str::FromStr;
+use std::time::{Duration, Instant};
+
+pub use async_api::{AsyncStream, AudioStreamsExecutor};
+use remain::sorted;
+use thiserror::Error;
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum SampleFormat {
+ U8,
+ S16LE,
+ S24LE,
+ S32LE,
+}
+
+impl SampleFormat {
+ pub fn sample_bytes(self) -> usize {
+ use SampleFormat::*;
+ match self {
+ U8 => 1,
+ S16LE => 2,
+ S24LE => 4, // Not a typo, S24_LE samples are stored in 4 byte chunks.
+ S32LE => 4,
+ }
+ }
+}
+
+impl Display for SampleFormat {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use SampleFormat::*;
+ match self {
+ U8 => write!(f, "Unsigned 8 bit"),
+ S16LE => write!(f, "Signed 16 bit Little Endian"),
+ S24LE => write!(f, "Signed 24 bit Little Endian"),
+ S32LE => write!(f, "Signed 32 bit Little Endian"),
+ }
+ }
+}
+
+/// Valid directions of an audio stream.
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum StreamDirection {
+ Playback,
+ Capture,
+}
+
+/// Valid effects for an audio stream.
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum StreamEffect {
+ NoEffect,
+ EchoCancellation,
+}
+
+pub mod capture;
+pub mod shm_streams;
+
+impl Default for StreamEffect {
+ fn default() -> Self {
+ StreamEffect::NoEffect
+ }
+}
+
+/// Errors that can pass across threads.
+pub type BoxError = Box<dyn error::Error + Send + Sync>;
+
+/// Errors that are possible from a `StreamEffect`.
+#[sorted]
+#[derive(Error, Debug)]
+pub enum StreamEffectError {
+ #[error("Must be in [EchoCancellation, aec]")]
+ InvalidEffect,
+}
+
+impl FromStr for StreamEffect {
+ type Err = StreamEffectError;
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ match s {
+ "EchoCancellation" | "aec" => Ok(StreamEffect::EchoCancellation),
+ _ => Err(StreamEffectError::InvalidEffect),
+ }
+ }
+}
+
+#[sorted]
+#[derive(Error, Debug)]
+pub enum Error {
+ #[error("Unimplemented")]
+ Unimplemented,
+}
+
+/// `StreamSource` creates streams for playback or capture of audio.
+pub trait StreamSource: Send {
+ /// Returns a stream control and buffer generator object. These are separate as the buffer
+ /// generator might want to be passed to the audio stream.
+ #[allow(clippy::type_complexity)]
+ fn new_playback_stream(
+ &mut self,
+ num_channels: usize,
+ format: SampleFormat,
+ frame_rate: u32,
+ buffer_size: usize,
+ ) -> Result<(Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>), BoxError>;
+
+ /// Returns a stream control and async buffer generator object. These are separate as the buffer
+ /// generator might want to be passed to the audio stream.
+ #[allow(clippy::type_complexity)]
+ fn new_async_playback_stream(
+ &mut self,
+ _num_channels: usize,
+ _format: SampleFormat,
+ _frame_rate: u32,
+ _buffer_size: usize,
+ _ex: &dyn AudioStreamsExecutor,
+ ) -> Result<(Box<dyn StreamControl>, Box<dyn AsyncPlaybackBufferStream>), BoxError> {
+ Err(Box::new(Error::Unimplemented))
+ }
+
+ /// Returns a stream control and buffer generator object. These are separate as the buffer
+ /// generator might want to be passed to the audio stream.
+ /// Default implementation returns `NoopStreamControl` and `NoopCaptureStream`.
+ #[allow(clippy::type_complexity)]
+ fn new_capture_stream(
+ &mut self,
+ num_channels: usize,
+ format: SampleFormat,
+ frame_rate: u32,
+ buffer_size: usize,
+ _effects: &[StreamEffect],
+ ) -> Result<
+ (
+ Box<dyn StreamControl>,
+ Box<dyn capture::CaptureBufferStream>,
+ ),
+ BoxError,
+ > {
+ Ok((
+ Box::new(NoopStreamControl::new()),
+ Box::new(capture::NoopCaptureStream::new(
+ num_channels,
+ format,
+ frame_rate,
+ buffer_size,
+ )),
+ ))
+ }
+
+ /// Returns a stream control and async buffer generator object. These are separate as the buffer
+ /// generator might want to be passed to the audio stream.
+ /// Default implementation returns `NoopStreamControl` and `NoopCaptureStream`.
+ #[allow(clippy::type_complexity)]
+ fn new_async_capture_stream(
+ &mut self,
+ num_channels: usize,
+ format: SampleFormat,
+ frame_rate: u32,
+ buffer_size: usize,
+ _effects: &[StreamEffect],
+ _ex: &dyn AudioStreamsExecutor,
+ ) -> Result<
+ (
+ Box<dyn StreamControl>,
+ Box<dyn capture::AsyncCaptureBufferStream>,
+ ),
+ BoxError,
+ > {
+ Ok((
+ Box::new(NoopStreamControl::new()),
+ Box::new(capture::NoopCaptureStream::new(
+ num_channels,
+ format,
+ frame_rate,
+ buffer_size,
+ )),
+ ))
+ }
+
+ /// Returns any open file descriptors needed by the implementor. The FD list helps users of the
+ /// StreamSource enter Linux jails making sure not to close needed FDs.
+ #[cfg(unix)]
+ fn keep_fds(&self) -> Option<Vec<RawFd>> {
+ None
+ }
+}
+
+/// `PlaybackBufferStream` provides `PlaybackBuffer`s to fill with audio samples for playback.
+pub trait PlaybackBufferStream: Send {
+ fn next_playback_buffer<'b, 's: 'b>(&'s mut self) -> Result<PlaybackBuffer<'b>, BoxError>;
+
+ /// Call `f` with a `PlaybackBuffer`, and trigger the buffer done call back after. `f` should
+ /// write playback data to the given `PlaybackBuffer`.
+ fn write_playback_buffer<'b, 's: 'b>(
+ &'s mut self,
+ f: &mut dyn FnMut(&mut PlaybackBuffer<'b>) -> Result<(), BoxError>,
+ ) -> Result<(), BoxError> {
+ let mut buf = self.next_playback_buffer()?;
+ f(&mut buf)?;
+ buf.commit();
+ Ok(())
+ }
+}
+
+impl<S: PlaybackBufferStream + ?Sized> PlaybackBufferStream for &mut S {
+ fn next_playback_buffer<'b, 's: 'b>(&'s mut self) -> Result<PlaybackBuffer<'b>, BoxError> {
+ (**self).next_playback_buffer()
+ }
+}
+
+/// `PlaybackBufferStream` provides `PlaybackBuffer`s asynchronously to fill with audio samples for
+/// playback.
+#[async_trait(?Send)]
+pub trait AsyncPlaybackBufferStream: Send {
+ async fn next_playback_buffer<'a>(
+ &'a mut self,
+ _ex: &dyn AudioStreamsExecutor,
+ ) -> Result<AsyncPlaybackBuffer<'a>, BoxError>;
+}
+
+#[async_trait(?Send)]
+impl<S: AsyncPlaybackBufferStream + ?Sized> AsyncPlaybackBufferStream for &mut S {
+ async fn next_playback_buffer<'a>(
+ &'a mut self,
+ ex: &dyn AudioStreamsExecutor,
+ ) -> Result<AsyncPlaybackBuffer<'a>, BoxError> {
+ (**self).next_playback_buffer(ex).await
+ }
+}
+
+/// Call `f` with a `AsyncPlaybackBuffer`, and trigger the buffer done call back after. `f` should
+/// write playback data to the given `AsyncPlaybackBuffer`.
+///
+/// This cannot be a trait method because trait methods with generic parameters are not object safe.
+pub async fn async_write_playback_buffer<F>(
+ stream: &mut dyn AsyncPlaybackBufferStream,
+ f: F,
+ ex: &dyn AudioStreamsExecutor,
+) -> Result<(), BoxError>
+where
+ F: for<'a> FnOnce(&'a mut AsyncPlaybackBuffer) -> Result<(), BoxError>,
+{
+ let mut buf = stream.next_playback_buffer(ex).await?;
+ f(&mut buf)?;
+ buf.commit().await;
+ Ok(())
+}
+
+/// `StreamControl` provides a way to set the volume and mute states of a stream. `StreamControl`
+/// is separate from the stream so it can be owned by a different thread if needed.
+pub trait StreamControl: Send + Sync {
+ fn set_volume(&mut self, _scaler: f64) {}
+ fn set_mute(&mut self, _mute: bool) {}
+}
+
+/// `BufferCommit` is a cleanup funcion that must be called before dropping the buffer,
+/// allowing arbitrary code to be run after the buffer is filled or read by the user.
+pub trait BufferCommit {
+ /// `write_playback_buffer` or `read_capture_buffer` would trigger this automatically. `nframes`
+ /// indicates the number of audio frames that were read or written to the device.
+ fn commit(&mut self, nframes: usize);
+}
+
+/// `AsyncBufferCommit` is a cleanup funcion that must be called before dropping the buffer,
+/// allowing arbitrary code to be run after the buffer is filled or read by the user.
+#[async_trait(?Send)]
+pub trait AsyncBufferCommit {
+ /// `async_write_playback_buffer` or `async_read_capture_buffer` would trigger this
+ /// automatically. `nframes` indicates the number of audio frames that were read or written to
+ /// the device.
+ async fn commit(&mut self, nframes: usize);
+}
+
+/// Errors that are possible from a `PlaybackBuffer`.
+#[sorted]
+#[derive(Error, Debug)]
+pub enum PlaybackBufferError {
+ #[error("Invalid buffer length")]
+ InvalidLength,
+}
+
+/// `AudioBuffer` is one buffer that holds buffer_size audio frames.
+/// It is the inner data of `PlaybackBuffer` and `CaptureBuffer`.
+struct AudioBuffer<'a> {
+ buffer: &'a mut [u8],
+ offset: usize, // Read or Write offset in frames.
+ frame_size: usize, // Size of a frame in bytes.
+}
+
+impl<'a> AudioBuffer<'a> {
+ /// Returns the number of audio frames that fit in the buffer.
+ pub fn frame_capacity(&self) -> usize {
+ self.buffer.len() / self.frame_size
+ }
+
+ fn calc_len(&self, size: usize) -> usize {
+ min(
+ size / self.frame_size * self.frame_size,
+ self.buffer.len() - self.offset,
+ )
+ }
+
+ /// Writes up to `size` bytes directly to this buffer inside of the given callback function.
+ pub fn write_copy_cb<F: FnOnce(&mut [u8])>(&mut self, size: usize, cb: F) -> io::Result<usize> {
+ // only write complete frames.
+ let len = self.calc_len(size);
+ cb(&mut self.buffer[self.offset..(self.offset + len)]);
+ self.offset += len;
+ Ok(len)
+ }
+
+ /// Reads up to `size` bytes directly from this buffer inside of the given callback function.
+ pub fn read_copy_cb<F: FnOnce(&[u8])>(&mut self, size: usize, cb: F) -> io::Result<usize> {
+ let len = self.calc_len(size);
+ cb(&self.buffer[self.offset..(self.offset + len)]);
+ self.offset += len;
+ Ok(len)
+ }
+
+ /// Copy data from an io::Reader
+ pub fn copy_from(&mut self, reader: &mut dyn Read) -> io::Result<usize> {
+ let bytes = reader.read(&mut self.buffer[self.offset..])?;
+ self.offset += bytes;
+ Ok(bytes)
+ }
+
+ /// Copy data to an io::Write
+ pub fn copy_to(&mut self, writer: &mut dyn Write) -> io::Result<usize> {
+ let bytes = writer.write(&self.buffer[self.offset..])?;
+ self.offset += bytes;
+ Ok(bytes)
+ }
+}
+
+impl<'a> Write for AudioBuffer<'a> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ let written = (&mut self.buffer[self.offset..]).write(&buf[..buf.len()])?;
+ self.offset += written;
+ Ok(written)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+impl<'a> Read for AudioBuffer<'a> {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ let len = buf.len() / self.frame_size * self.frame_size;
+ let written = (&mut buf[..len]).write(&self.buffer[self.offset..])?;
+ self.offset += written;
+ Ok(written)
+ }
+}
+
+/// `PlaybackBuffer` is one buffer that holds buffer_size audio frames. It is used to temporarily
+/// allow access to an audio buffer and notifes the owning stream of write completion when dropped.
+pub struct PlaybackBuffer<'a> {
+ buffer: AudioBuffer<'a>,
+ drop: &'a mut dyn BufferCommit,
+}
+
+impl<'a> PlaybackBuffer<'a> {
+ /// Creates a new `PlaybackBuffer` that holds a reference to the backing memory specified in
+ /// `buffer`.
+ pub fn new<F>(
+ frame_size: usize,
+ buffer: &'a mut [u8],
+ drop: &'a mut F,
+ ) -> Result<Self, PlaybackBufferError>
+ where
+ F: BufferCommit,
+ {
+ if buffer.len() % frame_size != 0 {
+ return Err(PlaybackBufferError::InvalidLength);
+ }
+
+ Ok(PlaybackBuffer {
+ buffer: AudioBuffer {
+ buffer,
+ offset: 0,
+ frame_size,
+ },
+ drop,
+ })
+ }
+
+ /// Returns the number of audio frames that fit in the buffer.
+ pub fn frame_capacity(&self) -> usize {
+ self.buffer.frame_capacity()
+ }
+
+ /// This triggers the commit of `BufferCommit`. This should be called after the data is copied
+ /// to the buffer.
+ pub fn commit(&mut self) {
+ self.drop
+ .commit(self.buffer.offset / self.buffer.frame_size);
+ }
+
+ /// Writes up to `size` bytes directly to this buffer inside of the given callback function.
+ pub fn copy_cb<F: FnOnce(&mut [u8])>(&mut self, size: usize, cb: F) -> io::Result<usize> {
+ self.buffer.write_copy_cb(size, cb)
+ }
+}
+
+impl<'a> Write for PlaybackBuffer<'a> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.buffer.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.buffer.flush()
+ }
+}
+
+/// `AsyncPlaybackBuffer` is the async version of `PlaybackBuffer`.
+pub struct AsyncPlaybackBuffer<'a> {
+ buffer: AudioBuffer<'a>,
+ trigger: &'a mut dyn AsyncBufferCommit,
+}
+
+impl<'a> AsyncPlaybackBuffer<'a> {
+ /// Creates a new `AsyncPlaybackBuffer` that holds a reference to the backing memory specified
+ /// in `buffer`.
+ pub fn new<F>(
+ frame_size: usize,
+ buffer: &'a mut [u8],
+ trigger: &'a mut F,
+ ) -> Result<Self, PlaybackBufferError>
+ where
+ F: AsyncBufferCommit,
+ {
+ if buffer.len() % frame_size != 0 {
+ return Err(PlaybackBufferError::InvalidLength);
+ }
+
+ Ok(AsyncPlaybackBuffer {
+ buffer: AudioBuffer {
+ buffer,
+ offset: 0,
+ frame_size,
+ },
+ trigger,
+ })
+ }
+
+ /// Returns the number of audio frames that fit in the buffer.
+ pub fn frame_capacity(&self) -> usize {
+ self.buffer.frame_capacity()
+ }
+
+ /// This triggers the callback of `AsyncBufferCommit`. This should be called after the data is
+ /// copied to the buffer.
+ pub async fn commit(&mut self) {
+ self.trigger
+ .commit(self.buffer.offset / self.buffer.frame_size)
+ .await;
+ }
+
+ /// Writes up to `size` bytes directly to this buffer inside of the given callback function.
+ pub fn copy_cb<F: FnOnce(&mut [u8])>(&mut self, size: usize, cb: F) -> io::Result<usize> {
+ self.buffer.write_copy_cb(size, cb)
+ }
+
+ /// Copy data from an io::Reader
+ pub fn copy_from(&mut self, reader: &mut dyn Read) -> io::Result<usize> {
+ self.buffer.copy_from(reader)
+ }
+}
+
+impl<'a> Write for AsyncPlaybackBuffer<'a> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.buffer.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.buffer.flush()
+ }
+}
+/// Stream that accepts playback samples but drops them.
+pub struct NoopStream {
+ buffer: Vec<u8>,
+ frame_size: usize,
+ interval: Duration,
+ next_frame: Duration,
+ start_time: Option<Instant>,
+ buffer_drop: NoopBufferCommit,
+}
+
+/// NoopStream data that is needed from the buffer complete callback.
+struct NoopBufferCommit {
+ which_buffer: bool,
+}
+
+impl BufferCommit for NoopBufferCommit {
+ fn commit(&mut self, _nwritten: usize) {
+ // When a buffer completes, switch to the other one.
+ self.which_buffer ^= true;
+ }
+}
+
+#[async_trait(?Send)]
+impl AsyncBufferCommit for NoopBufferCommit {
+ async fn commit(&mut self, _nwritten: usize) {
+ // When a buffer completes, switch to the other one.
+ self.which_buffer ^= true;
+ }
+}
+
+impl NoopStream {
+ pub fn new(
+ num_channels: usize,
+ format: SampleFormat,
+ frame_rate: u32,
+ buffer_size: usize,
+ ) -> Self {
+ let frame_size = format.sample_bytes() * num_channels;
+ let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
+ NoopStream {
+ buffer: vec![0; buffer_size * frame_size],
+ frame_size,
+ interval,
+ next_frame: interval,
+ start_time: None,
+ buffer_drop: NoopBufferCommit {
+ which_buffer: false,
+ },
+ }
+ }
+}
+
+impl PlaybackBufferStream for NoopStream {
+ fn next_playback_buffer<'b, 's: 'b>(&'s mut self) -> Result<PlaybackBuffer<'b>, BoxError> {
+ if let Some(start_time) = self.start_time {
+ let elapsed = start_time.elapsed();
+ if elapsed < self.next_frame {
+ std::thread::sleep(self.next_frame - elapsed);
+ }
+ self.next_frame += self.interval;
+ } else {
+ self.start_time = Some(Instant::now());
+ self.next_frame = self.interval;
+ }
+ Ok(PlaybackBuffer::new(
+ self.frame_size,
+ &mut self.buffer,
+ &mut self.buffer_drop,
+ )?)
+ }
+}
+
+#[async_trait(?Send)]
+impl AsyncPlaybackBufferStream for NoopStream {
+ async fn next_playback_buffer<'a>(
+ &'a mut self,
+ ex: &dyn AudioStreamsExecutor,
+ ) -> Result<AsyncPlaybackBuffer<'a>, BoxError> {
+ if let Some(start_time) = self.start_time {
+ let elapsed = start_time.elapsed();
+ if elapsed < self.next_frame {
+ ex.delay(self.next_frame - elapsed).await?;
+ }
+ self.next_frame += self.interval;
+ } else {
+ self.start_time = Some(Instant::now());
+ self.next_frame = self.interval;
+ }
+ Ok(AsyncPlaybackBuffer::new(
+ self.frame_size,
+ &mut self.buffer,
+ &mut self.buffer_drop,
+ )?)
+ }
+}
+
+/// No-op control for `NoopStream`s.
+#[derive(Default)]
+pub struct NoopStreamControl;
+
+impl NoopStreamControl {
+ pub fn new() -> Self {
+ NoopStreamControl {}
+ }
+}
+
+impl StreamControl for NoopStreamControl {}
+
+/// Source of `NoopStream` and `NoopStreamControl` objects.
+#[derive(Default)]
+pub struct NoopStreamSource;
+
+impl NoopStreamSource {
+ pub fn new() -> Self {
+ NoopStreamSource {}
+ }
+}
+
+impl StreamSource for NoopStreamSource {
+ #[allow(clippy::type_complexity)]
+ fn new_playback_stream(
+ &mut self,
+ num_channels: usize,
+ format: SampleFormat,
+ frame_rate: u32,
+ buffer_size: usize,
+ ) -> Result<(Box<dyn StreamControl>, Box<dyn PlaybackBufferStream>), BoxError> {
+ Ok((
+ Box::new(NoopStreamControl::new()),
+ Box::new(NoopStream::new(
+ num_channels,
+ format,
+ frame_rate,
+ buffer_size,
+ )),
+ ))
+ }
+
+ #[allow(clippy::type_complexity)]
+ fn new_async_playback_stream(
+ &mut self,
+ num_channels: usize,
+ format: SampleFormat,
+ frame_rate: u32,
+ buffer_size: usize,
+ _ex: &dyn AudioStreamsExecutor,
+ ) -> Result<(Box<dyn StreamControl>, Box<dyn AsyncPlaybackBufferStream>), BoxError> {
+ Ok((
+ Box::new(NoopStreamControl::new()),
+ Box::new(NoopStream::new(
+ num_channels,
+ format,
+ frame_rate,
+ buffer_size,
+ )),
+ ))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::async_api::test::TestExecutor;
+ use super::*;
+ use futures::FutureExt;
+ use io::{self, Write};
+
+ #[test]
+ fn invalid_buffer_length() {
+ // Playback buffers can't be created with a size that isn't divisible by the frame size.
+ let mut pb_buf = [0xa5u8; 480 * 2 * 2 + 1];
+ let mut buffer_drop = NoopBufferCommit {
+ which_buffer: false,
+ };
+ assert!(PlaybackBuffer::new(2, &mut pb_buf, &mut buffer_drop).is_err());
+ }
+
+ #[test]
+ fn audio_buffer_copy_from() {
+ const PERIOD_SIZE: usize = 8192;
+ const NUM_CHANNELS: usize = 6;
+ const FRAME_SIZE: usize = NUM_CHANNELS * 2;
+ let mut dst_buf = [0u8; PERIOD_SIZE * FRAME_SIZE];
+ let src_buf = [0xa5u8; PERIOD_SIZE * FRAME_SIZE];
+ let mut aud_buf = AudioBuffer {
+ buffer: &mut dst_buf,
+ offset: 0,
+ frame_size: FRAME_SIZE,
+ };
+ aud_buf
+ .copy_from(&mut &src_buf[..])
+ .expect("all data should be copied.");
+ assert_eq!(dst_buf, src_buf);
+ }
+
+ #[test]
+ fn audio_buffer_copy_from_repeat() {
+ const PERIOD_SIZE: usize = 8192;
+ const NUM_CHANNELS: usize = 6;
+ const FRAME_SIZE: usize = NUM_CHANNELS * 2;
+ let mut dst_buf = [0u8; PERIOD_SIZE * FRAME_SIZE];
+ let mut aud_buf = AudioBuffer {
+ buffer: &mut dst_buf,
+ offset: 0,
+ frame_size: FRAME_SIZE,
+ };
+ let bytes = aud_buf
+ .copy_from(&mut io::repeat(1))
+ .expect("all data should be copied.");
+ assert_eq!(bytes, PERIOD_SIZE * FRAME_SIZE);
+ assert_eq!(dst_buf, [1u8; PERIOD_SIZE * FRAME_SIZE]);
+ }
+
+ #[test]
+ fn audio_buffer_copy_to() {
+ const PERIOD_SIZE: usize = 8192;
+ const NUM_CHANNELS: usize = 6;
+ const FRAME_SIZE: usize = NUM_CHANNELS * 2;
+ let mut dst_buf = [0u8; PERIOD_SIZE * FRAME_SIZE];
+ let mut src_buf = [0xa5u8; PERIOD_SIZE * FRAME_SIZE];
+ let mut aud_buf = AudioBuffer {
+ buffer: &mut src_buf,
+ offset: 0,
+ frame_size: FRAME_SIZE,
+ };
+ aud_buf
+ .copy_to(&mut &mut dst_buf[..])
+ .expect("all data should be copied.");
+ assert_eq!(dst_buf, src_buf);
+ }
+
+ #[test]
+ fn audio_buffer_copy_to_sink() {
+ const PERIOD_SIZE: usize = 8192;
+ const NUM_CHANNELS: usize = 6;
+ const FRAME_SIZE: usize = NUM_CHANNELS * 2;
+ let mut src_buf = [0xa5u8; PERIOD_SIZE * FRAME_SIZE];
+ let mut aud_buf = AudioBuffer {
+ buffer: &mut src_buf,
+ offset: 0,
+ frame_size: FRAME_SIZE,
+ };
+ let bytes = aud_buf
+ .copy_to(&mut io::sink())
+ .expect("all data should be copied.");
+ assert_eq!(bytes, PERIOD_SIZE * FRAME_SIZE);
+ }
+
+ #[test]
+ fn io_copy_audio_buffer() {
+ const PERIOD_SIZE: usize = 8192;
+ const NUM_CHANNELS: usize = 6;
+ const FRAME_SIZE: usize = NUM_CHANNELS * 2;
+ let mut dst_buf = [0u8; PERIOD_SIZE * FRAME_SIZE];
+ let src_buf = [0xa5u8; PERIOD_SIZE * FRAME_SIZE];
+ let mut aud_buf = AudioBuffer {
+ buffer: &mut dst_buf,
+ offset: 0,
+ frame_size: FRAME_SIZE,
+ };
+ io::copy(&mut &src_buf[..], &mut aud_buf).expect("all data should be copied.");
+ assert_eq!(dst_buf, src_buf);
+ }
+
+ #[test]
+ fn commit() {
+ struct TestCommit {
+ frame_count: usize,
+ }
+ impl BufferCommit for TestCommit {
+ fn commit(&mut self, nwritten: usize) {
+ self.frame_count += nwritten;
+ }
+ }
+ let mut test_commit = TestCommit { frame_count: 0 };
+ {
+ const FRAME_SIZE: usize = 4;
+ let mut buf = [0u8; 480 * FRAME_SIZE];
+ let mut pb_buf = PlaybackBuffer::new(FRAME_SIZE, &mut buf, &mut test_commit).unwrap();
+ pb_buf.write_all(&[0xa5u8; 480 * FRAME_SIZE]).unwrap();
+ pb_buf.commit();
+ }
+ assert_eq!(test_commit.frame_count, 480);
+ }
+
+ #[test]
+ fn sixteen_bit_stereo() {
+ let mut server = NoopStreamSource::new();
+ let (_, mut stream) = server
+ .new_playback_stream(2, SampleFormat::S16LE, 48000, 480)
+ .unwrap();
+ let mut copy_cb = |buf: &mut PlaybackBuffer| {
+ assert_eq!(buf.buffer.frame_capacity(), 480);
+ let pb_buf = [0xa5u8; 480 * 2 * 2];
+ assert_eq!(buf.write(&pb_buf).unwrap(), 480 * 2 * 2);
+ Ok(())
+ };
+ stream.write_playback_buffer(&mut copy_cb).unwrap();
+ }
+
+ #[test]
+ fn consumption_rate() {
+ let mut server = NoopStreamSource::new();
+ let (_, mut stream) = server
+ .new_playback_stream(2, SampleFormat::S16LE, 48000, 480)
+ .unwrap();
+ let start = Instant::now();
+ {
+ let mut copy_cb = |buf: &mut PlaybackBuffer| {
+ let pb_buf = [0xa5u8; 480 * 2 * 2];
+ assert_eq!(buf.write(&pb_buf).unwrap(), 480 * 2 * 2);
+ Ok(())
+ };
+ stream.write_playback_buffer(&mut copy_cb).unwrap();
+ }
+ // The second call should block until the first buffer is consumed.
+ let mut assert_cb = |_: &mut PlaybackBuffer| {
+ let elapsed = start.elapsed();
+ assert!(
+ elapsed > Duration::from_millis(10),
+ "next_playback_buffer didn't block long enough {}",
+ elapsed.subsec_millis()
+ );
+ Ok(())
+ };
+ stream.write_playback_buffer(&mut assert_cb).unwrap();
+ }
+
+ #[test]
+ fn async_commit() {
+ struct TestCommit {
+ frame_count: usize,
+ }
+ #[async_trait(?Send)]
+ impl AsyncBufferCommit for TestCommit {
+ async fn commit(&mut self, nwritten: usize) {
+ self.frame_count += nwritten;
+ }
+ }
+ async fn this_test() {
+ let mut test_commit = TestCommit { frame_count: 0 };
+ {
+ const FRAME_SIZE: usize = 4;
+ let mut buf = [0u8; 480 * FRAME_SIZE];
+ let mut pb_buf =
+ AsyncPlaybackBuffer::new(FRAME_SIZE, &mut buf, &mut test_commit).unwrap();
+ pb_buf.write_all(&[0xa5u8; 480 * FRAME_SIZE]).unwrap();
+ pb_buf.commit().await;
+ }
+ assert_eq!(test_commit.frame_count, 480);
+ }
+
+ this_test().now_or_never();
+ }
+
+ #[test]
+ fn consumption_rate_async() {
+ async fn this_test(ex: &TestExecutor) {
+ let mut server = NoopStreamSource::new();
+ let (_, mut stream) = server
+ .new_async_playback_stream(2, SampleFormat::S16LE, 48000, 480, ex)
+ .unwrap();
+ let start = Instant::now();
+ {
+ let copy_func = |buf: &mut AsyncPlaybackBuffer| {
+ let pb_buf = [0xa5u8; 480 * 2 * 2];
+ assert_eq!(buf.write(&pb_buf).unwrap(), 480 * 2 * 2);
+ Ok(())
+ };
+ async_write_playback_buffer(&mut *stream, copy_func, ex)
+ .await
+ .unwrap();
+ }
+ // The second call should block until the first buffer is consumed.
+ let assert_func = |_: &mut AsyncPlaybackBuffer| {
+ let elapsed = start.elapsed();
+ assert!(
+ elapsed > Duration::from_millis(10),
+ "write_playback_buffer didn't block long enough {}",
+ elapsed.subsec_millis()
+ );
+ Ok(())
+ };
+
+ async_write_playback_buffer(&mut *stream, assert_func, ex)
+ .await
+ .unwrap();
+ }
+
+ let ex = TestExecutor {};
+ this_test(&ex).now_or_never();
+ }
+}
diff --git a/common/audio_streams/src/capture.rs b/common/audio_streams/src/capture.rs
new file mode 100644
index 000000000..ab1d4efe2
--- /dev/null
+++ b/common/audio_streams/src/capture.rs
@@ -0,0 +1,463 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//! ```
+//! use audio_streams::{BoxError, capture::CaptureBuffer, SampleFormat, StreamSource,
+//! NoopStreamSource};
+//! use std::io::Read;
+//!
+//! const buffer_size: usize = 120;
+//! const num_channels: usize = 2;
+//!
+//! # fn main() -> std::result::Result<(),BoxError> {
+//! let mut stream_source = NoopStreamSource::new();
+//! let sample_format = SampleFormat::S16LE;
+//! let frame_size = num_channels * sample_format.sample_bytes();
+//!
+//! let (_, mut stream) = stream_source
+//! .new_capture_stream(num_channels, sample_format, 48000, buffer_size, &[])?;
+//! // Capture 10 buffers of zeros.
+//! let mut buf = Vec::new();
+//! buf.resize(buffer_size * frame_size, 0xa5u8);
+//! for _ in 0..10 {
+//! let mut copy_func = |stream_buffer: &mut CaptureBuffer| {
+//! assert_eq!(stream_buffer.read(&mut buf)?, buffer_size * frame_size);
+//! Ok(())
+//! };
+//! stream.read_capture_buffer(&mut copy_func)?;
+//! }
+//! # Ok (())
+//! # }
+//! ```
+
+use async_trait::async_trait;
+use std::{
+ io::{self, Read, Write},
+ time::{Duration, Instant},
+};
+
+use super::async_api::AudioStreamsExecutor;
+use super::{
+ AsyncBufferCommit, AudioBuffer, BoxError, BufferCommit, NoopBufferCommit, SampleFormat,
+};
+use remain::sorted;
+use thiserror::Error;
+
+/// `CaptureBufferStream` provides `CaptureBuffer`s to read with audio samples from capture.
+pub trait CaptureBufferStream: Send {
+ fn next_capture_buffer<'b, 's: 'b>(&'s mut self) -> Result<CaptureBuffer<'b>, BoxError>;
+
+ /// Call `f` with a `CaptureBuffer`, and trigger the buffer done call back after. `f` can read
+ /// the capture data from the given `CaptureBuffer`.
+ fn read_capture_buffer<'b, 's: 'b>(
+ &'s mut self,
+ f: &mut dyn FnMut(&mut CaptureBuffer<'b>) -> Result<(), BoxError>,
+ ) -> Result<(), BoxError> {
+ let mut buf = self.next_capture_buffer()?;
+ f(&mut buf)?;
+ buf.commit();
+ Ok(())
+ }
+}
+
+impl<S: CaptureBufferStream + ?Sized> CaptureBufferStream for &mut S {
+ fn next_capture_buffer<'b, 's: 'b>(&'s mut self) -> Result<CaptureBuffer<'b>, BoxError> {
+ (**self).next_capture_buffer()
+ }
+}
+
+#[async_trait(?Send)]
+pub trait AsyncCaptureBufferStream: Send {
+ async fn next_capture_buffer<'a>(
+ &'a mut self,
+ _ex: &dyn AudioStreamsExecutor,
+ ) -> Result<AsyncCaptureBuffer<'a>, BoxError>;
+}
+
+#[async_trait(?Send)]
+impl<S: AsyncCaptureBufferStream + ?Sized> AsyncCaptureBufferStream for &mut S {
+ async fn next_capture_buffer<'a>(
+ &'a mut self,
+ ex: &dyn AudioStreamsExecutor,
+ ) -> Result<AsyncCaptureBuffer<'a>, BoxError> {
+ (**self).next_capture_buffer(ex).await
+ }
+}
+
+/// `CaptureBuffer` contains a block of audio samples got from capture stream. It provides
+/// temporary view to those samples and will notifies capture stream when dropped.
+/// Note that it'll always send `buffer.len() / frame_size` to drop function when it got destroyed
+/// since `CaptureBufferStream` assumes that users get all the samples from the buffer.
+pub struct CaptureBuffer<'a> {
+ buffer: AudioBuffer<'a>,
+ drop: &'a mut dyn BufferCommit,
+}
+
+/// Async version of 'CaptureBuffer`
+pub struct AsyncCaptureBuffer<'a> {
+ buffer: AudioBuffer<'a>,
+ trigger: &'a mut dyn AsyncBufferCommit,
+}
+
+/// Errors that are possible from a `CaptureBuffer`.
+#[sorted]
+#[derive(Error, Debug)]
+pub enum CaptureBufferError {
+ #[error("Invalid buffer length")]
+ InvalidLength,
+}
+
+impl<'a> CaptureBuffer<'a> {
+ /// Creates a new `CaptureBuffer` that holds a reference to the backing memory specified in
+ /// `buffer`.
+ pub fn new<F>(
+ frame_size: usize,
+ buffer: &'a mut [u8],
+ drop: &'a mut F,
+ ) -> Result<Self, CaptureBufferError>
+ where
+ F: BufferCommit,
+ {
+ if buffer.len() % frame_size != 0 {
+ return Err(CaptureBufferError::InvalidLength);
+ }
+
+ Ok(CaptureBuffer {
+ buffer: AudioBuffer {
+ buffer,
+ frame_size,
+ offset: 0,
+ },
+ drop,
+ })
+ }
+
+ /// Returns the number of audio frames that fit in the buffer.
+ pub fn frame_capacity(&self) -> usize {
+ self.buffer.frame_capacity()
+ }
+
+ /// This triggers the callback of `BufferCommit`. This should be called after the data is read
+ /// from the buffer.
+ ///
+ /// Always sends `frame_capacity`.
+ pub fn commit(&mut self) {
+ self.drop.commit(self.frame_capacity());
+ }
+
+ /// Reads up to `size` bytes directly from this buffer inside of the given callback function.
+ pub fn copy_cb<F: FnOnce(&[u8])>(&mut self, size: usize, cb: F) -> io::Result<usize> {
+ self.buffer.read_copy_cb(size, cb)
+ }
+}
+
+impl<'a> Read for CaptureBuffer<'a> {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.buffer.read(buf)
+ }
+}
+
+impl<'a> AsyncCaptureBuffer<'a> {
+ /// Creates a new `AsyncCaptureBuffer` that holds a reference to the backing memory specified in
+ /// `buffer`.
+ pub fn new<F>(
+ frame_size: usize,
+ buffer: &'a mut [u8],
+ trigger: &'a mut F,
+ ) -> Result<Self, CaptureBufferError>
+ where
+ F: AsyncBufferCommit,
+ {
+ if buffer.len() % frame_size != 0 {
+ return Err(CaptureBufferError::InvalidLength);
+ }
+
+ Ok(AsyncCaptureBuffer {
+ buffer: AudioBuffer {
+ buffer,
+ frame_size,
+ offset: 0,
+ },
+ trigger,
+ })
+ }
+
+ /// Returns the number of audio frames that fit in the buffer.
+ pub fn frame_capacity(&self) -> usize {
+ self.buffer.frame_capacity()
+ }
+
+ /// This triggers the callback of `AsyncBufferCommit`. This should be called after the data is
+ /// read from the buffer.
+ ///
+ /// Always sends `frame_capacity`.
+ pub async fn commit(&mut self) {
+ self.trigger.commit(self.frame_capacity()).await;
+ }
+
+ /// Reads up to `size` bytes directly from this buffer inside of the given callback function.
+ pub fn copy_cb<F: FnOnce(&[u8])>(&mut self, size: usize, cb: F) -> io::Result<usize> {
+ self.buffer.read_copy_cb(size, cb)
+ }
+
+ /// Copy data to an io::Write
+ pub fn copy_to(&mut self, writer: &mut dyn Write) -> io::Result<usize> {
+ self.buffer.copy_to(writer)
+ }
+}
+
+impl<'a> Read for AsyncCaptureBuffer<'a> {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.buffer.read(buf)
+ }
+}
+
+/// Stream that provides null capture samples.
+pub struct NoopCaptureStream {
+ buffer: Vec<u8>,
+ frame_size: usize,
+ interval: Duration,
+ next_frame: Duration,
+ start_time: Option<Instant>,
+ buffer_drop: NoopBufferCommit,
+}
+
+impl NoopCaptureStream {
+ pub fn new(
+ num_channels: usize,
+ format: SampleFormat,
+ frame_rate: u32,
+ buffer_size: usize,
+ ) -> Self {
+ let frame_size = format.sample_bytes() * num_channels;
+ let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
+ NoopCaptureStream {
+ buffer: vec![0; buffer_size * frame_size],
+ frame_size,
+ interval,
+ next_frame: interval,
+ start_time: None,
+ buffer_drop: NoopBufferCommit {
+ which_buffer: false,
+ },
+ }
+ }
+}
+
+impl CaptureBufferStream for NoopCaptureStream {
+ fn next_capture_buffer<'b, 's: 'b>(&'s mut self) -> Result<CaptureBuffer<'b>, BoxError> {
+ if let Some(start_time) = self.start_time {
+ let elapsed = start_time.elapsed();
+ if elapsed < self.next_frame {
+ std::thread::sleep(self.next_frame - elapsed);
+ }
+ self.next_frame += self.interval;
+ } else {
+ self.start_time = Some(Instant::now());
+ self.next_frame = self.interval;
+ }
+ Ok(CaptureBuffer::new(
+ self.frame_size,
+ &mut self.buffer,
+ &mut self.buffer_drop,
+ )?)
+ }
+}
+
+#[async_trait(?Send)]
+impl AsyncCaptureBufferStream for NoopCaptureStream {
+ async fn next_capture_buffer<'a>(
+ &'a mut self,
+ ex: &dyn AudioStreamsExecutor,
+ ) -> Result<AsyncCaptureBuffer<'a>, BoxError> {
+ if let Some(start_time) = self.start_time {
+ let elapsed = start_time.elapsed();
+ if elapsed < self.next_frame {
+ ex.delay(self.next_frame - elapsed).await?;
+ }
+ self.next_frame += self.interval;
+ } else {
+ self.start_time = Some(Instant::now());
+ self.next_frame = self.interval;
+ }
+ Ok(AsyncCaptureBuffer::new(
+ self.frame_size,
+ &mut self.buffer,
+ &mut self.buffer_drop,
+ )?)
+ }
+}
+
+/// Call `f` with a `AsyncCaptureBuffer`, and trigger the buffer done call back after. `f` can read
+/// the capture data from the given `AsyncCaptureBuffer`.
+///
+/// This cannot be a trait method because trait methods with generic parameters are not object safe.
+pub async fn async_read_capture_buffer<F>(
+ stream: &mut dyn AsyncCaptureBufferStream,
+ f: F,
+ ex: &dyn AudioStreamsExecutor,
+) -> Result<(), BoxError>
+where
+ F: FnOnce(&mut AsyncCaptureBuffer) -> Result<(), BoxError>,
+{
+ let mut buf = stream.next_capture_buffer(ex).await?;
+ f(&mut buf)?;
+ buf.commit().await;
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::super::async_api::test::TestExecutor;
+ use super::super::*;
+ use super::*;
+ use futures::FutureExt;
+
+ #[test]
+ fn invalid_buffer_length() {
+ // Capture buffers can't be created with a size that isn't divisible by the frame size.
+ let mut cp_buf = [0xa5u8; 480 * 2 * 2 + 1];
+ let mut buffer_drop = NoopBufferCommit {
+ which_buffer: false,
+ };
+ assert!(CaptureBuffer::new(2, &mut cp_buf, &mut buffer_drop).is_err());
+ }
+
+ #[test]
+ fn commit() {
+ struct TestCommit {
+ frame_count: usize,
+ }
+ impl BufferCommit for TestCommit {
+ fn commit(&mut self, nwritten: usize) {
+ self.frame_count += nwritten;
+ }
+ }
+ let mut test_commit = TestCommit { frame_count: 0 };
+ {
+ const FRAME_SIZE: usize = 4;
+ let mut buf = [0u8; 480 * FRAME_SIZE];
+ let mut cp_buf = CaptureBuffer::new(FRAME_SIZE, &mut buf, &mut test_commit).unwrap();
+ let mut local_buf = [0u8; 240 * FRAME_SIZE];
+ assert_eq!(cp_buf.read(&mut local_buf).unwrap(), 240 * FRAME_SIZE);
+ cp_buf.commit();
+ }
+ // This should be 480 no matter how many samples are read.
+ assert_eq!(test_commit.frame_count, 480);
+ }
+
+ #[test]
+ fn sixteen_bit_stereo() {
+ let mut server = NoopStreamSource::new();
+ let (_, mut stream) = server
+ .new_capture_stream(2, SampleFormat::S16LE, 48000, 480, &[])
+ .unwrap();
+ let mut copy_func = |b: &mut CaptureBuffer| {
+ assert_eq!(b.buffer.frame_capacity(), 480);
+ let mut pb_buf = [0xa5u8; 480 * 2 * 2];
+ assert_eq!(b.read(&mut pb_buf).unwrap(), 480 * 2 * 2);
+ Ok(())
+ };
+ stream.read_capture_buffer(&mut copy_func).unwrap();
+ }
+
+ #[test]
+ fn consumption_rate() {
+ let mut server = NoopStreamSource::new();
+ let (_, mut stream) = server
+ .new_capture_stream(2, SampleFormat::S16LE, 48000, 480, &[])
+ .unwrap();
+ let start = Instant::now();
+ {
+ let mut copy_func = |b: &mut CaptureBuffer| {
+ let mut cp_buf = [0xa5u8; 480 * 2 * 2];
+ assert_eq!(b.read(&mut cp_buf).unwrap(), 480 * 2 * 2);
+ for buf in cp_buf.iter() {
+ assert_eq!(*buf, 0, "Read samples should all be zeros.");
+ }
+ Ok(())
+ };
+ stream.read_capture_buffer(&mut copy_func).unwrap();
+ }
+ // The second call should block until the first buffer is consumed.
+ let mut assert_func = |_: &mut CaptureBuffer| {
+ let elapsed = start.elapsed();
+ assert!(
+ elapsed > Duration::from_millis(10),
+ "next_capture_buffer didn't block long enough {}",
+ elapsed.subsec_millis()
+ );
+ Ok(())
+ };
+ stream.read_capture_buffer(&mut assert_func).unwrap();
+ }
+
+ #[test]
+ fn async_commit() {
+ struct TestCommit {
+ frame_count: usize,
+ }
+ #[async_trait(?Send)]
+ impl AsyncBufferCommit for TestCommit {
+ async fn commit(&mut self, nwritten: usize) {
+ self.frame_count += nwritten;
+ }
+ }
+ async fn this_test() {
+ let mut test_commit = TestCommit { frame_count: 0 };
+ {
+ const FRAME_SIZE: usize = 4;
+ let mut buf = [0u8; 480 * FRAME_SIZE];
+ let mut cp_buf =
+ AsyncCaptureBuffer::new(FRAME_SIZE, &mut buf, &mut test_commit).unwrap();
+ let mut local_buf = [0u8; 240 * FRAME_SIZE];
+ assert_eq!(cp_buf.read(&mut local_buf).unwrap(), 240 * FRAME_SIZE);
+ cp_buf.commit().await;
+ }
+ // This should be 480 no matter how many samples are read.
+ assert_eq!(test_commit.frame_count, 480);
+ }
+
+ this_test().now_or_never();
+ }
+
+ #[test]
+ fn consumption_rate_async() {
+ async fn this_test(ex: &TestExecutor) {
+ let mut server = NoopStreamSource::new();
+ let (_, mut stream) = server
+ .new_async_capture_stream(2, SampleFormat::S16LE, 48000, 480, &[], ex)
+ .unwrap();
+ let start = Instant::now();
+ {
+ let copy_func = |buf: &mut AsyncCaptureBuffer| {
+ let mut cp_buf = [0xa5u8; 480 * 2 * 2];
+ assert_eq!(buf.read(&mut cp_buf).unwrap(), 480 * 2 * 2);
+ for buf in cp_buf.iter() {
+ assert_eq!(*buf, 0, "Read samples should all be zeros.");
+ }
+ Ok(())
+ };
+ async_read_capture_buffer(&mut *stream, copy_func, ex)
+ .await
+ .unwrap();
+ }
+ // The second call should block until the first buffer is consumed.
+ let assert_func = |_: &mut AsyncCaptureBuffer| {
+ let elapsed = start.elapsed();
+ assert!(
+ elapsed > Duration::from_millis(10),
+ "write_playback_buffer didn't block long enough {}",
+ elapsed.subsec_millis()
+ );
+ Ok(())
+ };
+ async_read_capture_buffer(&mut *stream, assert_func, ex)
+ .await
+ .unwrap();
+ }
+
+ let ex = TestExecutor {};
+ this_test(&ex).now_or_never();
+ }
+}
diff --git a/common/audio_streams/src/shm_streams.rs b/common/audio_streams/src/shm_streams.rs
new file mode 100644
index 000000000..b2ad68779
--- /dev/null
+++ b/common/audio_streams/src/shm_streams.rs
@@ -0,0 +1,599 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#[cfg(unix)]
+use std::os::unix::io::RawFd;
+use std::sync::Arc;
+use std::time::{Duration, Instant};
+
+use remain::sorted;
+use sync::{Condvar, Mutex};
+use thiserror::Error;
+
+use crate::{BoxError, SampleFormat, StreamDirection, StreamEffect};
+
+type GenericResult<T> = std::result::Result<T, BoxError>;
+
+/// `BufferSet` is used as a callback mechanism for `ServerRequest` objects.
+/// It is meant to be implemented by the audio stream, allowing arbitrary code
+/// to be run after a buffer offset and length is set.
+pub trait BufferSet {
+ /// Called when the client sets a buffer offset and length.
+ ///
+ /// `offset` is the offset within shared memory of the buffer and `frames`
+ /// indicates the number of audio frames that can be read from or written to
+ /// the buffer.
+ fn callback(&mut self, offset: usize, frames: usize) -> GenericResult<()>;
+
+ /// Called when the client ignores a request from the server.
+ fn ignore(&mut self) -> GenericResult<()>;
+}
+
+#[sorted]
+#[derive(Error, Debug)]
+pub enum Error {
+ #[error("Provided number of frames {0} exceeds requested number of frames {1}")]
+ TooManyFrames(usize, usize),
+}
+
+/// `ServerRequest` represents an active request from the server for the client
+/// to provide a buffer in shared memory to playback from or capture to.
+pub struct ServerRequest<'a> {
+ requested_frames: usize,
+ buffer_set: &'a mut dyn BufferSet,
+}
+
+impl<'a> ServerRequest<'a> {
+ /// Create a new ServerRequest object
+ ///
+ /// Create a ServerRequest object representing a request from the server
+ /// for a buffer `requested_frames` in size.
+ ///
+ /// When the client responds to this request by calling
+ /// [`set_buffer_offset_and_frames`](ServerRequest::set_buffer_offset_and_frames),
+ /// BufferSet::callback will be called on `buffer_set`.
+ ///
+ /// # Arguments
+ /// * `requested_frames` - The requested buffer size in frames.
+ /// * `buffer_set` - The object implementing the callback for when a buffer is provided.
+ pub fn new<D: BufferSet>(requested_frames: usize, buffer_set: &'a mut D) -> Self {
+ Self {
+ requested_frames,
+ buffer_set,
+ }
+ }
+
+ /// Get the number of frames of audio data requested by the server.
+ ///
+ /// The returned value should never be greater than the `buffer_size`
+ /// given in [`new_stream`](ShmStreamSource::new_stream).
+ pub fn requested_frames(&self) -> usize {
+ self.requested_frames
+ }
+
+ /// Sets the buffer offset and length for the requested buffer.
+ ///
+ /// Sets the buffer offset and length of the buffer that fulfills this
+ /// server request to `offset` and `length`, respectively. This means that
+ /// `length` bytes of audio samples may be read from/written to that
+ /// location in `client_shm` for a playback/capture stream, respectively.
+ /// This function may only be called once for a `ServerRequest`, at which
+ /// point the ServerRequest is dropped and no further calls are possible.
+ ///
+ /// # Arguments
+ ///
+ /// * `offset` - The value to use as the new buffer offset for the next buffer.
+ /// * `frames` - The length of the next buffer in frames.
+ ///
+ /// # Errors
+ ///
+ /// * If `frames` is greater than `requested_frames`.
+ pub fn set_buffer_offset_and_frames(self, offset: usize, frames: usize) -> GenericResult<()> {
+ if frames > self.requested_frames {
+ return Err(Box::new(Error::TooManyFrames(
+ frames,
+ self.requested_frames,
+ )));
+ }
+
+ self.buffer_set.callback(offset, frames)
+ }
+
+ /// Ignore this request
+ ///
+ /// If the client does not intend to respond to this ServerRequest with a
+ /// buffer, they should call this function. The stream will be notified that
+ /// the request has been ignored and will handle it properly.
+ pub fn ignore_request(self) -> GenericResult<()> {
+ self.buffer_set.ignore()
+ }
+}
+
+/// `ShmStream` allows a client to interact with an active CRAS stream.
+pub trait ShmStream: Send {
+ /// Get the size of a frame of audio data for this stream.
+ fn frame_size(&self) -> usize;
+
+ /// Get the number of channels of audio data for this stream.
+ fn num_channels(&self) -> usize;
+
+ /// Get the frame rate of audio data for this stream.
+ fn frame_rate(&self) -> u32;
+
+ /// Waits until the next server message indicating action is required.
+ ///
+ /// For playback streams, this will be `AUDIO_MESSAGE_REQUEST_DATA`, meaning
+ /// that we must set the buffer offset to the next location where playback
+ /// data can be found.
+ /// For capture streams, this will be `AUDIO_MESSAGE_DATA_READY`, meaning
+ /// that we must set the buffer offset to the next location where captured
+ /// data can be written to.
+ /// Will return early if `timeout` elapses before a message is received.
+ ///
+ /// # Arguments
+ ///
+ /// * `timeout` - The amount of time to wait until a message is received.
+ ///
+ /// # Return value
+ ///
+ /// Returns `Some(request)` where `request` is an object that implements the
+ /// [`ServerRequest`](ServerRequest) trait and which can be used to get the
+ /// number of bytes requested for playback streams or that have already been
+ /// written to shm for capture streams.
+ ///
+ /// If the timeout occurs before a message is received, returns `None`.
+ ///
+ /// # Errors
+ ///
+ /// * If an invalid message type is received for the stream.
+ fn wait_for_next_action_with_timeout(
+ &mut self,
+ timeout: Duration,
+ ) -> GenericResult<Option<ServerRequest>>;
+}
+
+/// `SharedMemory` specifies features of shared memory areas passed on to `ShmStreamSource`.
+pub trait SharedMemory {
+ type Error: std::error::Error;
+
+ /// Creates a new shared memory file descriptor without specifying a name.
+ fn anon(size: u64) -> Result<Self, Self::Error>
+ where
+ Self: Sized;
+
+ /// Gets the size in bytes of the shared memory.
+ ///
+ /// The size returned here does not reflect changes by other interfaces or users of the shared
+ /// memory file descriptor..
+ fn size(&self) -> u64;
+
+ /// Returns the underlying raw fd.
+ #[cfg(unix)]
+ fn as_raw_fd(&self) -> RawFd;
+}
+
+/// `ShmStreamSource` creates streams for playback or capture of audio.
+pub trait ShmStreamSource<E: std::error::Error>: Send {
+ /// Creates a new [`ShmStream`](ShmStream)
+ ///
+ /// Creates a new `ShmStream` object, which allows:
+ /// * Waiting until the server has communicated that data is ready or
+ /// requested that we make more data available.
+ /// * Setting the location and length of buffers for reading/writing audio data.
+ ///
+ /// # Arguments
+ ///
+ /// * `direction` - The direction of the stream, either `Playback` or `Capture`.
+ /// * `num_channels` - The number of audio channels for the stream.
+ /// * `format` - The audio format to use for audio samples.
+ /// * `frame_rate` - The stream's frame rate in Hz.
+ /// * `buffer_size` - The maximum size of an audio buffer. This will be the
+ /// size used for transfers of audio data between client
+ /// and server.
+ /// * `effects` - Audio effects to use for the stream, such as echo-cancellation.
+ /// * `client_shm` - The shared memory area that will contain samples.
+ /// * `buffer_offsets` - The two initial values to use as buffer offsets
+ /// for streams. This way, the server will not write
+ /// audio data to an arbitrary offset in `client_shm`
+ /// if the client fails to update offsets in time.
+ ///
+ /// # Errors
+ ///
+ /// * If sending the connect stream message to the server fails.
+ #[allow(clippy::too_many_arguments)]
+ fn new_stream(
+ &mut self,
+ direction: StreamDirection,
+ num_channels: usize,
+ format: SampleFormat,
+ frame_rate: u32,
+ buffer_size: usize,
+ effects: &[StreamEffect],
+ client_shm: &dyn SharedMemory<Error = E>,
+ buffer_offsets: [u64; 2],
+ ) -> GenericResult<Box<dyn ShmStream>>;
+
+ /// Get a list of file descriptors used by the implementation.
+ ///
+ /// Returns any open file descriptors needed by the implementation.
+ /// This list helps users of the ShmStreamSource enter Linux jails without
+ /// closing needed file descriptors.
+ #[cfg(unix)]
+ fn keep_fds(&self) -> Vec<RawFd> {
+ Vec::new()
+ }
+}
+
+/// Class that implements ShmStream trait but does nothing with the samples
+pub struct NullShmStream {
+ num_channels: usize,
+ frame_rate: u32,
+ buffer_size: usize,
+ frame_size: usize,
+ interval: Duration,
+ next_frame: Duration,
+ start_time: Instant,
+}
+
+impl NullShmStream {
+ /// Attempt to create a new NullShmStream with the given number of channels,
+ /// format, frame_rate, and buffer_size.
+ pub fn new(
+ buffer_size: usize,
+ num_channels: usize,
+ format: SampleFormat,
+ frame_rate: u32,
+ ) -> Self {
+ let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
+ Self {
+ num_channels,
+ frame_rate,
+ buffer_size,
+ frame_size: format.sample_bytes() * num_channels,
+ interval,
+ next_frame: interval,
+ start_time: Instant::now(),
+ }
+ }
+}
+
+impl BufferSet for NullShmStream {
+ fn callback(&mut self, _offset: usize, _frames: usize) -> GenericResult<()> {
+ Ok(())
+ }
+
+ fn ignore(&mut self) -> GenericResult<()> {
+ Ok(())
+ }
+}
+
+impl ShmStream for NullShmStream {
+ fn frame_size(&self) -> usize {
+ self.frame_size
+ }
+
+ fn num_channels(&self) -> usize {
+ self.num_channels
+ }
+
+ fn frame_rate(&self) -> u32 {
+ self.frame_rate
+ }
+
+ fn wait_for_next_action_with_timeout(
+ &mut self,
+ timeout: Duration,
+ ) -> GenericResult<Option<ServerRequest>> {
+ let elapsed = self.start_time.elapsed();
+ if elapsed < self.next_frame {
+ if timeout < self.next_frame - elapsed {
+ std::thread::sleep(timeout);
+ return Ok(None);
+ } else {
+ std::thread::sleep(self.next_frame - elapsed);
+ }
+ }
+ self.next_frame += self.interval;
+ Ok(Some(ServerRequest::new(self.buffer_size, self)))
+ }
+}
+
+/// Source of `NullShmStream` objects.
+#[derive(Default)]
+pub struct NullShmStreamSource;
+
+impl NullShmStreamSource {
+ pub fn new() -> Self {
+ Self::default()
+ }
+}
+
+impl<E: std::error::Error> ShmStreamSource<E> for NullShmStreamSource {
+ fn new_stream(
+ &mut self,
+ _direction: StreamDirection,
+ num_channels: usize,
+ format: SampleFormat,
+ frame_rate: u32,
+ buffer_size: usize,
+ _effects: &[StreamEffect],
+ _client_shm: &dyn SharedMemory<Error = E>,
+ _buffer_offsets: [u64; 2],
+ ) -> GenericResult<Box<dyn ShmStream>> {
+ let new_stream = NullShmStream::new(buffer_size, num_channels, format, frame_rate);
+ Ok(Box::new(new_stream))
+ }
+}
+
+#[derive(Clone)]
+pub struct MockShmStream {
+ num_channels: usize,
+ frame_rate: u32,
+ request_size: usize,
+ frame_size: usize,
+ request_notifier: Arc<(Mutex<bool>, Condvar)>,
+}
+
+impl MockShmStream {
+ /// Attempt to create a new MockShmStream with the given number of
+ /// channels, frame_rate, format, and buffer_size.
+ pub fn new(
+ num_channels: usize,
+ frame_rate: u32,
+ format: SampleFormat,
+ buffer_size: usize,
+ ) -> Self {
+ Self {
+ num_channels,
+ frame_rate,
+ request_size: buffer_size,
+ frame_size: format.sample_bytes() * num_channels,
+ request_notifier: Arc::new((Mutex::new(false), Condvar::new())),
+ }
+ }
+
+ /// Call to request data from the stream, causing it to return from
+ /// `wait_for_next_action_with_timeout`. Will block until
+ /// `set_buffer_offset_and_frames` is called on the ServerRequest returned
+ /// from `wait_for_next_action_with_timeout`, or until `timeout` elapses.
+ /// Returns true if a response was successfully received.
+ pub fn trigger_callback_with_timeout(&mut self, timeout: Duration) -> bool {
+ let &(ref lock, ref cvar) = &*self.request_notifier;
+ let mut requested = lock.lock();
+ *requested = true;
+ cvar.notify_one();
+ let start_time = Instant::now();
+ while *requested {
+ requested = cvar.wait_timeout(requested, timeout).0;
+ if start_time.elapsed() > timeout {
+ // We failed to get a callback in time, mark this as false.
+ *requested = false;
+ return false;
+ }
+ }
+
+ true
+ }
+
+ fn notify_request(&mut self) {
+ let &(ref lock, ref cvar) = &*self.request_notifier;
+ let mut requested = lock.lock();
+ *requested = false;
+ cvar.notify_one();
+ }
+}
+
+impl BufferSet for MockShmStream {
+ fn callback(&mut self, _offset: usize, _frames: usize) -> GenericResult<()> {
+ self.notify_request();
+ Ok(())
+ }
+
+ fn ignore(&mut self) -> GenericResult<()> {
+ self.notify_request();
+ Ok(())
+ }
+}
+
+impl ShmStream for MockShmStream {
+ fn frame_size(&self) -> usize {
+ self.frame_size
+ }
+
+ fn num_channels(&self) -> usize {
+ self.num_channels
+ }
+
+ fn frame_rate(&self) -> u32 {
+ self.frame_rate
+ }
+
+ fn wait_for_next_action_with_timeout(
+ &mut self,
+ timeout: Duration,
+ ) -> GenericResult<Option<ServerRequest>> {
+ {
+ let start_time = Instant::now();
+ let &(ref lock, ref cvar) = &*self.request_notifier;
+ let mut requested = lock.lock();
+ while !*requested {
+ requested = cvar.wait_timeout(requested, timeout).0;
+ if start_time.elapsed() > timeout {
+ return Ok(None);
+ }
+ }
+ }
+
+ Ok(Some(ServerRequest::new(self.request_size, self)))
+ }
+}
+
+/// Source of `MockShmStream` objects.
+#[derive(Clone, Default)]
+pub struct MockShmStreamSource {
+ last_stream: Arc<(Mutex<Option<MockShmStream>>, Condvar)>,
+}
+
+impl MockShmStreamSource {
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ /// Get the last stream that has been created from this source. If no stream
+ /// has been created, block until one has.
+ pub fn get_last_stream(&self) -> MockShmStream {
+ let &(ref last_stream, ref cvar) = &*self.last_stream;
+ let mut stream = last_stream.lock();
+ loop {
+ match &*stream {
+ None => stream = cvar.wait(stream),
+ Some(ref s) => return s.clone(),
+ };
+ }
+ }
+}
+
+impl<E: std::error::Error> ShmStreamSource<E> for MockShmStreamSource {
+ fn new_stream(
+ &mut self,
+ _direction: StreamDirection,
+ num_channels: usize,
+ format: SampleFormat,
+ frame_rate: u32,
+ buffer_size: usize,
+ _effects: &[StreamEffect],
+ _client_shm: &dyn SharedMemory<Error = E>,
+ _buffer_offsets: [u64; 2],
+ ) -> GenericResult<Box<dyn ShmStream>> {
+ let &(ref last_stream, ref cvar) = &*self.last_stream;
+ let mut stream = last_stream.lock();
+
+ let new_stream = MockShmStream::new(num_channels, frame_rate, format, buffer_size);
+ *stream = Some(new_stream.clone());
+ cvar.notify_one();
+ Ok(Box::new(new_stream))
+ }
+}
+
+// Tests that run only for Unix, where `sys_util::SharedMemory` is used.
+#[cfg(all(test, unix))]
+pub mod tests {
+ use super::*;
+
+ use std::os::unix::io::AsRawFd;
+ use sys_util::SharedMemory as SysSharedMemory;
+
+ impl SharedMemory for SysSharedMemory {
+ type Error = sys_util::Error;
+
+ fn anon(_: u64) -> Result<Self, Self::Error> {
+ SysSharedMemory::anon()
+ }
+
+ fn size(&self) -> u64 {
+ self.size()
+ }
+
+ #[cfg(unix)]
+ fn as_raw_fd(&self) -> RawFd {
+ AsRawFd::as_raw_fd(self)
+ }
+ }
+
+ #[test]
+ fn mock_trigger_callback() {
+ let stream_source = MockShmStreamSource::new();
+ let mut thread_stream_source = stream_source.clone();
+
+ let buffer_size = 480;
+ let num_channels = 2;
+ let format = SampleFormat::S24LE;
+ let shm = SysSharedMemory::anon().expect("Failed to create shm");
+
+ let handle = std::thread::spawn(move || {
+ let mut stream = thread_stream_source
+ .new_stream(
+ StreamDirection::Playback,
+ num_channels,
+ format,
+ 44100,
+ buffer_size,
+ &[],
+ &shm,
+ [400, 8000],
+ )
+ .expect("Failed to create stream");
+
+ let request = stream
+ .wait_for_next_action_with_timeout(Duration::from_secs(5))
+ .expect("Failed to wait for next action");
+ match request {
+ Some(r) => {
+ let requested = r.requested_frames();
+ r.set_buffer_offset_and_frames(872, requested)
+ .expect("Failed to set buffer offset and frames");
+ requested
+ }
+ None => 0,
+ }
+ });
+
+ let mut stream = stream_source.get_last_stream();
+ assert!(stream.trigger_callback_with_timeout(Duration::from_secs(1)));
+
+ let requested_frames = handle.join().expect("Failed to join thread");
+ assert_eq!(requested_frames, buffer_size);
+ }
+
+ #[test]
+ fn null_consumption_rate() {
+ let frame_rate = 44100;
+ let buffer_size = 480;
+ let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
+
+ let shm = SysSharedMemory::anon().expect("Failed to create shm");
+
+ let start = Instant::now();
+
+ let mut stream_source = NullShmStreamSource::new();
+ let mut stream = stream_source
+ .new_stream(
+ StreamDirection::Playback,
+ 2,
+ SampleFormat::S24LE,
+ frame_rate,
+ buffer_size,
+ &[],
+ &shm,
+ [400, 8000],
+ )
+ .expect("Failed to create stream");
+
+ let timeout = Duration::from_secs(5);
+ let request = stream
+ .wait_for_next_action_with_timeout(timeout)
+ .expect("Failed to wait for first request")
+ .expect("First request should not have timed out");
+ request
+ .set_buffer_offset_and_frames(276, 480)
+ .expect("Failed to set buffer offset and length");
+
+ // The second call should block until the first buffer is consumed.
+ let _request = stream
+ .wait_for_next_action_with_timeout(timeout)
+ .expect("Failed to wait for second request");
+ let elapsed = start.elapsed();
+ assert!(
+ elapsed > interval,
+ "wait_for_next_action_with_timeout didn't block long enough: {:?}",
+ elapsed
+ );
+
+ assert!(
+ elapsed < timeout,
+ "wait_for_next_action_with_timeout blocked for too long: {:?}",
+ elapsed
+ );
+ }
+}
diff --git a/common/balloon_control/Android.bp b/common/balloon_control/Android.bp
new file mode 100644
index 000000000..696e124c2
--- /dev/null
+++ b/common/balloon_control/Android.bp
@@ -0,0 +1,25 @@
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
+// Do not modify this file as changes will be overridden on upgrade.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_crosvm_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-BSD
+ default_applicable_licenses: ["external_crosvm_license"],
+}
+
+rust_library {
+ name: "libballoon_control",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "balloon_control",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ rustlibs: [
+ "libserde",
+ ],
+}
diff --git a/common/balloon_control/Cargo.toml b/common/balloon_control/Cargo.toml
new file mode 100644
index 000000000..11b8acfe7
--- /dev/null
+++ b/common/balloon_control/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "balloon_control"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2021"
+
+[dependencies]
+serde = { version = "1", features = [ "derive" ] }
diff --git a/common/balloon_control/src/lib.rs b/common/balloon_control/src/lib.rs
new file mode 100644
index 000000000..0ea428302
--- /dev/null
+++ b/common/balloon_control/src/lib.rs
@@ -0,0 +1,58 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use serde::{Deserialize, Serialize};
+
+// Balloon commands that are send on the balloon command tube.
+#[derive(Serialize, Deserialize, Debug)]
+pub enum BalloonTubeCommand {
+ // Set the size of the VM's balloon.
+ Adjust {
+ num_bytes: u64,
+ // When this flag is set, adjust attempts can fail. After adjustment, the final
+ // size of the balloon is returned via a BalloonTubeResult::Adjust message.
+ //
+ // The flag changes the semantics of inflating the balloon. By default, the driver
+ // will indefinitely retry if it fails to allocate pages when inflating the
+ // balloon. However, when this flag is set, the balloon device responds to page
+ // allocation failures in the guest by stopping inflation at the balloon's current
+ // size.
+ allow_failure: bool,
+ },
+ // Fetch balloon stats. The ID can be used to discard stale states
+ // if any previous stats requests failed or timed out.
+ Stats {
+ id: u64,
+ },
+}
+
+// BalloonStats holds stats returned from the stats_queue.
+#[derive(Default, Serialize, Deserialize, Debug)]
+pub struct BalloonStats {
+ pub swap_in: Option<u64>,
+ pub swap_out: Option<u64>,
+ pub major_faults: Option<u64>,
+ pub minor_faults: Option<u64>,
+ pub free_memory: Option<u64>,
+ pub total_memory: Option<u64>,
+ pub available_memory: Option<u64>,
+ pub disk_caches: Option<u64>,
+ pub hugetlb_allocations: Option<u64>,
+ pub hugetlb_failures: Option<u64>,
+ pub shared_memory: Option<u64>,
+ pub unevictable_memory: Option<u64>,
+}
+
+// BalloonTubeResult are results to BalloonTubeCommand defined above.
+#[derive(Serialize, Deserialize, Debug)]
+pub enum BalloonTubeResult {
+ Stats {
+ stats: BalloonStats,
+ balloon_actual: u64,
+ id: u64,
+ },
+ Adjusted {
+ num_bytes: u64,
+ },
+}
diff --git a/syscall_defines/Cargo.toml b/common/cros-fuzz/Cargo.toml
index d99cd2c3a..c304d95dc 100644
--- a/syscall_defines/Cargo.toml
+++ b/common/cros-fuzz/Cargo.toml
@@ -1,10 +1,11 @@
[package]
-name = "syscall_defines"
+name = "cros_fuzz"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
-include = ["src/**/*", "Cargo.toml"]
+edition = "2021"
+include = ["Cargo.toml", "src/*.rs"]
[dependencies]
+rand_core = "0.4"
[workspace]
diff --git a/cros_async/.build_test_skip b/common/cros-fuzz/OWNERS
index e69de29bb..e69de29bb 100644
--- a/cros_async/.build_test_skip
+++ b/common/cros-fuzz/OWNERS
diff --git a/common/cros-fuzz/README.md b/common/cros-fuzz/README.md
new file mode 100644
index 000000000..d98898055
--- /dev/null
+++ b/common/cros-fuzz/README.md
@@ -0,0 +1,3 @@
+# Support crate for fuzzing rust code
+
+The crate provides support for fuzzing rust code on Chrome OS.
diff --git a/common/cros-fuzz/cargo2android.json b/common/cros-fuzz/cargo2android.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/common/cros-fuzz/cargo2android.json
@@ -0,0 +1 @@
+{} \ No newline at end of file
diff --git a/common/cros-fuzz/src/lib.rs b/common/cros-fuzz/src/lib.rs
new file mode 100644
index 000000000..da5bd6db4
--- /dev/null
+++ b/common/cros-fuzz/src/lib.rs
@@ -0,0 +1,95 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Support crate for writing fuzzers in Chrome OS.
+//!
+//! The major features provided by this crate are:
+//!
+//! * The [`fuzz_target`] macro which wraps the body of the fuzzing code with
+//! all with all the boilerplate needed to build and run it as a fuzzer on
+//! Chrome OS infrastructure.
+//! * The [`FuzzRng`] type that provides a random number generator using fuzzer
+//! input as the source of its randomness. Fuzzers that need to generate
+//! structured data can use this type in conjunction with the [`rand`] crate
+//! to generate the data they need.
+//!
+//! # Getting Started
+//!
+//! To use this crate add it as a dependency to the fuzzer's `Cargo.toml` along
+//! with the crate to be fuzzed:
+//!
+//! ```Cargo.toml
+//! [dependencies]
+//! cros_fuzz = "*"
+//! your_crate = "*"
+//! ```
+//!
+//! Then use the [`fuzz_target`] macro to write the body of the fuzzer. All
+//! fuzzers should use the `#![no_main]` crate attribute as the main function
+//! will be provided by the fuzzer runtime.
+//!
+//! ```rust,ignore
+//! #![no_main]
+//!
+//! use cros_fuzz::fuzz_target;
+//! use your_crate::some_function;
+//!
+//! fuzz_target!(|data: &[u8]| {
+//! some_function(data);
+//! });
+//! ```
+//!
+//! [`FuzzRng`]: rand/struct.FuzzRng.html
+//! [`fuzz_target`]: macro.fuzz_target.html
+//! [`rand`]: https://docs.rs/rand
+
+pub mod rand;
+
+/// The main macro for writing a fuzzer. The fuzzer runtime will repeatedly
+/// call the body of `fuzz_target!` with a slice of pseudo-random bytes, until
+/// your program hits an error condition (segfault, panic, etc).
+///
+/// # Examples
+///
+/// ```
+/// use std::str;
+/// # #[macro_use] extern crate cros_fuzz;
+///
+/// fuzz_target!(|data: &[u8]| {
+/// let _ = str::from_utf8(data);
+/// });
+///
+/// # fn main() {
+/// # let buf = b"hello, world!";
+/// # llvm_fuzzer_test_one_input(buf.as_ptr(), buf.len());
+/// # }
+/// ```
+#[macro_export]
+macro_rules! fuzz_target {
+ (|$bytes:ident| $body:block) => {
+ use std::panic;
+ use std::process;
+ use std::slice;
+
+ #[export_name = "LLVMFuzzerTestOneInput"]
+ fn llvm_fuzzer_test_one_input(data: *const u8, size: usize) -> i32 {
+ // We cannot unwind past ffi boundaries.
+ panic::catch_unwind(|| {
+ // Safe because the libfuzzer runtime will guarantee that `data` is
+ // at least `size` bytes long and that it will be valid for the lifetime
+ // of this function.
+ let $bytes = unsafe { slice::from_raw_parts(data, size) };
+
+ $body
+ })
+ .err()
+ .map(|_| process::abort());
+
+ 0
+ }
+ };
+ (|$bytes:ident: &[u8]| $body:block) => {
+ fuzz_target!(|$bytes| $body);
+ };
+}
diff --git a/common/cros-fuzz/src/rand.rs b/common/cros-fuzz/src/rand.rs
new file mode 100644
index 000000000..f5ee4a044
--- /dev/null
+++ b/common/cros-fuzz/src/rand.rs
@@ -0,0 +1,213 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cmp::min;
+use std::fmt;
+use std::mem::size_of;
+use std::result::Result;
+
+use rand_core::{Error, ErrorKind, RngCore};
+
+/// A random number generator that uses fuzzer input as the source of its
+/// randomness. When run on the same input, it provides the same output, as
+/// long as its methods are called in the same order and with the same
+/// arguments.
+pub struct FuzzRng<'a> {
+ buf: &'a [u8],
+}
+
+impl<'a> FuzzRng<'a> {
+ /// Creates a new `FuzzRng` from `buf`, which should be part or all of an
+ /// input buffer provided by a fuzzing library.
+ pub fn new(buf: &'a [u8]) -> FuzzRng<'a> {
+ FuzzRng { buf }
+ }
+
+ /// Consumes `self` and returns the inner slice.
+ pub fn into_inner(self) -> &'a [u8] {
+ let FuzzRng { buf } = self;
+ buf
+ }
+}
+
+impl<'a> RngCore for FuzzRng<'a> {
+ fn next_u32(&mut self) -> u32 {
+ let mut buf = [0u8; size_of::<u32>()];
+ self.fill_bytes(&mut buf);
+
+ u32::from_ne_bytes(buf)
+ }
+
+ fn next_u64(&mut self) -> u64 {
+ let mut buf = [0u8; size_of::<u64>()];
+ self.fill_bytes(&mut buf);
+
+ u64::from_ne_bytes(buf)
+ }
+
+ fn fill_bytes(&mut self, dest: &mut [u8]) {
+ let amt = min(self.buf.len(), dest.len());
+ let (a, b) = self.buf.split_at(amt);
+ dest[..amt].copy_from_slice(a);
+ self.buf = b;
+
+ if amt < dest.len() {
+ // We didn't have enough data to fill the whole buffer. Fill the rest
+ // with zeroes. The compiler is smart enough to turn this into a memset.
+ for b in &mut dest[amt..] {
+ *b = 0;
+ }
+ }
+ }
+
+ fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
+ if self.buf.len() >= dest.len() {
+ self.fill_bytes(dest);
+ Ok(())
+ } else {
+ Err(Error::new(
+ ErrorKind::Unavailable,
+ "not enough data in fuzzer input",
+ ))
+ }
+ }
+}
+
+impl<'a> fmt::Debug for FuzzRng<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "FuzzRng {{ {} bytes }}", self.buf.len())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn gen_u32() {
+ let val = 0xc2a744u32;
+ let buf = val.to_ne_bytes();
+ let mut rng = FuzzRng::new(&buf);
+
+ assert_eq!(rng.next_u32(), val);
+ assert_eq!(rng.next_u32(), 0);
+ }
+
+ #[test]
+ fn gen_u64() {
+ let val = 0xac75689deeu64;
+ let buf = val.to_ne_bytes();
+ let mut rng = FuzzRng::new(&buf);
+
+ assert_eq!(rng.next_u64(), val);
+ assert_eq!(rng.next_u64(), 0);
+ }
+
+ #[test]
+ fn fill_bytes() {
+ let buf = &[
+ 0xed, 0x90, 0xf3, 0xa4, 0x8f, 0xbf, 0x6e, 0xdb, 0x68, 0xb9, 0x1f, 0x9a, 0x13, 0xfc,
+ 0x9f, 0xc8, 0x9e, 0xfa, 0x4a, 0x02, 0x5e, 0xc8, 0xb1, 0xe5, 0x2d, 0x59, 0x22, 0x89,
+ 0x10, 0x23, 0xc3, 0x31, 0x6c, 0x42, 0x40, 0xce, 0xfe, 0x6e, 0x5c, 0x3d, 0x10, 0xba,
+ 0x0d, 0x11, 0xbc, 0x6a, 0x1f, 0x21, 0xc9, 0x72, 0x37, 0xba, 0xfa, 0x00, 0xb2, 0xa8,
+ 0x51, 0x6d, 0xb2, 0x94, 0xf2, 0x34, 0xf8, 0x3c, 0x21, 0xc9, 0x59, 0x24, 0xd8, 0x77,
+ 0x51, 0x3f, 0x64, 0xde, 0x19, 0xc8, 0xb3, 0x03, 0x26, 0x81, 0x85, 0x4c, 0xef, 0xb0,
+ 0xd5, 0xd8, 0x65, 0xe1, 0x89, 0x8f, 0xb7, 0x14, 0x9b, 0x0d, 0xd9, 0xcb, 0xda, 0x35,
+ 0xb2, 0xff, 0xd5, 0xd1, 0xae, 0x38, 0x55, 0xd5, 0x65, 0xba, 0xdc, 0xa1, 0x82, 0x62,
+ 0xbf, 0xe6, 0x3d, 0x7a, 0x8f, 0x13, 0x65, 0x2f, 0x4b, 0xdc, 0xcb, 0xee, 0xd8, 0x99,
+ 0x2c, 0x21, 0x97, 0xc8, 0x6e, 0x8e, 0x09, 0x0f, 0xf1, 0x4b, 0x85, 0xb5, 0x0f, 0x52,
+ 0x82, 0x7f, 0xe0, 0x23, 0xc5, 0x9a, 0x6a, 0x7c, 0xf1, 0x46, 0x7d, 0xbf, 0x3f, 0x14,
+ 0x0d, 0x41, 0x09, 0xd5, 0x63, 0x70, 0xa1, 0x0e, 0x04, 0x3c, 0x06, 0x0a, 0x0b, 0x5c,
+ 0x95, 0xaf, 0xbd, 0xf5, 0x4b, 0x7f, 0xbe, 0x8d, 0xe2, 0x09, 0xce, 0xa2, 0xf6, 0x1e,
+ 0x58, 0xd8, 0xda, 0xd4, 0x56, 0x56, 0xe1, 0x32, 0x30, 0xef, 0x0f, 0x2e, 0xed, 0xb9,
+ 0x14, 0x57, 0xa8, 0x8a, 0x9c, 0xd8, 0x58, 0x7f, 0xd9, 0x4f, 0x11, 0xb2, 0x7a, 0xcf,
+ 0xc0, 0xef, 0xf3, 0xc7, 0xc1, 0xc5, 0x1e, 0x86, 0x47, 0xc6, 0x42, 0x71, 0x15, 0xc8,
+ 0x25, 0x1d, 0x94, 0x00, 0x8d, 0x04, 0x37, 0xe7, 0xfe, 0xf6, 0x10, 0x28, 0xe5, 0xb2,
+ 0xef, 0x95, 0xa6, 0x53, 0x20, 0xf8, 0x51, 0xdb, 0x54, 0x99, 0x40, 0x4a, 0x7c, 0xd6,
+ 0x90, 0x4a, 0x55, 0xdc, 0x37, 0xb8, 0xbc, 0x0b, 0xc4, 0x54, 0xd1, 0x9b, 0xb3, 0x8c,
+ 0x09, 0x55, 0x77, 0xf5, 0x1b, 0xa7, 0x36, 0x06, 0x29, 0x4c, 0xa3, 0x26, 0x35, 0x1b,
+ 0x29, 0xa3, 0xa3, 0x45, 0x74, 0xee, 0x0b, 0x78, 0xf8, 0x69, 0x70, 0xa4, 0x1d, 0x11,
+ 0x7a, 0x91, 0xca, 0x4c, 0x83, 0xb3, 0xbf, 0xf6, 0x7f, 0x54, 0xca, 0xdb, 0x1f, 0xc4,
+ 0xd2, 0xb2, 0x23, 0xfa, 0xc0, 0x24, 0x77, 0x74, 0x61, 0x9e, 0x0b, 0x77, 0x49, 0x29,
+ 0xf1, 0xd9, 0xbf, 0xf0, 0x5e, 0x99, 0xa6, 0xf1, 0x00, 0xa4, 0x7f, 0xa0, 0xb1, 0x6b,
+ 0xd8, 0xbe, 0xef, 0xa0, 0xa1, 0xa5, 0x33, 0x9c, 0xc3, 0x95, 0xaa, 0x9f,
+ ];
+
+ let mut rng = FuzzRng::new(&buf[..]);
+ let mut dest = Vec::with_capacity(buf.len());
+ for chunk in buf.chunks(11) {
+ dest.resize(chunk.len(), 0);
+ rng.fill_bytes(&mut dest);
+
+ assert_eq!(chunk, &*dest);
+ }
+
+ dest.resize(97, 0x2c);
+ rng.fill_bytes(&mut dest);
+
+ let zero_buf = vec![0; dest.len()];
+ assert_eq!(zero_buf, dest);
+ }
+
+ #[test]
+ fn try_fill_bytes() {
+ let buf = &[
+ 0xdb, 0x35, 0xad, 0x4e, 0x9d, 0xf5, 0x2d, 0xf6, 0x0d, 0xc5, 0xd2, 0xfc, 0x9f, 0x4c,
+ 0xb5, 0x12, 0xe3, 0x78, 0x40, 0x8d, 0x8b, 0xa1, 0x5c, 0xfe, 0x66, 0x49, 0xa9, 0xc0,
+ 0x43, 0xa0, 0x95, 0xae, 0x31, 0x99, 0xd2, 0xaa, 0xbc, 0x85, 0x9e, 0x4b, 0x08, 0xca,
+ 0x59, 0x21, 0x2b, 0x66, 0x37, 0x6a, 0xb9, 0xb2, 0xd8, 0x71, 0x84, 0xdd, 0xf6, 0x47,
+ 0xa5, 0xb9, 0x87, 0x9f, 0x24, 0x97, 0x01, 0x65, 0x15, 0x38, 0x01, 0xd6, 0xb6, 0xf2,
+ 0x80,
+ ];
+ let mut rng = FuzzRng::new(&buf[..]);
+ let mut dest = Vec::with_capacity(buf.len());
+ for chunk in buf.chunks(13) {
+ dest.resize(chunk.len(), 0);
+ rng.try_fill_bytes(&mut dest)
+ .expect("failed to fill bytes while data is remaining");
+
+ assert_eq!(chunk, &*dest);
+ }
+
+ dest.resize(buf.len(), 0);
+ rng.try_fill_bytes(&mut dest)
+ .expect_err("successfully filled bytes when no data is remaining");
+ }
+
+ #[test]
+ fn try_fill_bytes_partial() {
+ let buf = &[
+ 0x8b, 0xe3, 0x20, 0x8d, 0xe0, 0x0b, 0xbe, 0x51, 0xa6, 0xec, 0x8a, 0xb5, 0xd6, 0x17,
+ 0x04, 0x3f, 0x87, 0xae, 0xc8, 0xe8, 0xf8, 0xe7, 0xd4, 0xbd, 0xf3, 0x4e, 0x74, 0xcf,
+ 0xbf, 0x0e, 0x9d, 0xe5, 0x78, 0xc3, 0xe6, 0x44, 0xb8, 0xd1, 0x40, 0xda, 0x63, 0x9f,
+ 0x48, 0xf4, 0x09, 0x9c, 0x5c, 0x5f, 0x36, 0x0b, 0x0d, 0x2b, 0xe3, 0xc7, 0xcc, 0x3e,
+ 0x9a, 0xb9, 0x0a, 0xca, 0x6d, 0x90, 0x77, 0x3b, 0x7a, 0x50, 0x16, 0x13, 0x5d, 0x20,
+ 0x70, 0xc0, 0x88, 0x04, 0x9c, 0xac, 0x2b, 0xd6, 0x61, 0xa0, 0xbe, 0xa4, 0xff, 0xbd,
+ 0xac, 0x9c, 0xa1, 0xb2, 0x95, 0x26, 0xeb, 0x99, 0x46, 0x67, 0xe4, 0xcd, 0x88, 0x7b,
+ 0x20, 0x4d, 0xb2, 0x92, 0x40, 0x9f, 0x1c, 0xbd, 0xba, 0x22, 0xff, 0xca, 0x89, 0x3c,
+ 0x3b,
+ ];
+
+ let mut rng = FuzzRng::new(&buf[..]);
+ let mut dest = Vec::with_capacity(buf.len());
+ dest.resize((buf.len() / 2) + 1, 0);
+
+ // The first time should be successful because there is enough data left
+ // in the buffer.
+ rng.try_fill_bytes(&mut dest).expect("failed to fill bytes");
+ assert_eq!(&buf[..dest.len()], &*dest);
+
+ // The second time should fail because while there is data in the buffer it
+ // is not enough to fill `dest`.
+ rng.try_fill_bytes(&mut dest)
+ .expect_err("filled bytes with insufficient data in buffer");
+
+ // This should succeed because `dest` is exactly big enough to hold all the remaining
+ // data in the buffer.
+ dest.resize(buf.len() - dest.len(), 0);
+ rng.try_fill_bytes(&mut dest)
+ .expect("failed to fill bytes with exact-sized buffer");
+ assert_eq!(&buf[buf.len() - dest.len()..], &*dest);
+ }
+}
diff --git a/fuzz/.build_test_skip b/common/cros_async/.build_test_skip
index e69de29bb..e69de29bb 100644
--- a/fuzz/.build_test_skip
+++ b/common/cros_async/.build_test_skip
diff --git a/common/cros_async/Cargo.toml b/common/cros_async/Cargo.toml
new file mode 100644
index 000000000..f1bb1616c
--- /dev/null
+++ b/common/cros_async/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "cros_async"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2021"
+
+[dependencies]
+async-trait = "0.1.36"
+async-task = "4"
+data_model = { path = "../data_model" } # provided by ebuild
+intrusive-collections = "0.9"
+io_uring = { path = "../io_uring" } # provided by ebuild
+libc = "*"
+once_cell = "1.7.2"
+paste = "1.0"
+pin-utils = "0.1.0-alpha.4"
+remain = "0.2"
+slab = "0.4"
+sync = { path = "../sync" } # provided by ebuild
+sys_util = { path = "../sys_util" } # provided by ebuild
+thiserror = "1.0.20"
+audio_streams = { path = "../audio_streams" } # provided by ebuild
+anyhow = "1.0"
+
+[dependencies.futures]
+version = "*"
+default-features = false
+features = ["alloc"]
+
+[dev-dependencies]
+futures = { version = "*", features = ["executor"] }
+futures-executor = { version = "0.3", features = ["thread-pool"] }
+futures-util = "0.3"
+tempfile = "3"
+
+[workspace]
diff --git a/common/cros_async/DEPRECATED.md b/common/cros_async/DEPRECATED.md
new file mode 100644
index 000000000..77e18642d
--- /dev/null
+++ b/common/cros_async/DEPRECATED.md
@@ -0,0 +1,4 @@
+Use crosvm/cros_async instead.
+
+Code in this directory is not used by crosvm, it is only used in ChromeOS and will move to a
+separate ChromeOS repository soon.
diff --git a/io_uring/TEST_MAPPING b/common/cros_async/TEST_MAPPING
index 1dd1b4435..335e68ada 100644
--- a/io_uring/TEST_MAPPING
+++ b/common/cros_async/TEST_MAPPING
@@ -4,10 +4,10 @@
// "presubmit": [
// {
// "host": true,
-// "name": "io_uring_host_test_src_lib"
+// "name": "cros_async_test_src_lib"
// },
// {
-// "name": "io_uring_device_test_src_lib"
+// "name": "cros_async_test_src_lib"
// }
// ]
}
diff --git a/common/cros_async/cargo2android.json b/common/cros_async/cargo2android.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/common/cros_async/cargo2android.json
@@ -0,0 +1 @@
+{}
diff --git a/common/cros_async/src/audio_streams_async.rs b/common/cros_async/src/audio_streams_async.rs
new file mode 100644
index 000000000..735006658
--- /dev/null
+++ b/common/cros_async/src/audio_streams_async.rs
@@ -0,0 +1,68 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Implements the interface required by `audio_streams` using the cros_async Executor.
+//!
+//! It implements the `AudioStreamsExecutor` trait for `Executor`, so it can be passed into
+//! the audio_streams API.
+#[cfg(unix)]
+use std::os::unix::net::UnixStream;
+
+use std::{io::Result, time::Duration};
+
+use super::{AsyncWrapper, IntoAsync, IoSourceExt, TimerAsync};
+use async_trait::async_trait;
+use audio_streams::async_api::{
+ AsyncStream, AudioStreamsExecutor, ReadAsync, ReadWriteAsync, WriteAsync,
+};
+
+/// A wrapper around IoSourceExt that is compatible with the audio_streams traits.
+pub struct IoSourceWrapper<T: IntoAsync + Send> {
+ source: Box<dyn IoSourceExt<T> + Send>,
+}
+
+#[async_trait(?Send)]
+impl<T: IntoAsync + Send> ReadAsync for IoSourceWrapper<T> {
+ async fn read_to_vec<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ vec: Vec<u8>,
+ ) -> Result<(usize, Vec<u8>)> {
+ self.source
+ .read_to_vec(file_offset, vec)
+ .await
+ .map_err(Into::into)
+ }
+}
+
+#[async_trait(?Send)]
+impl<T: IntoAsync + Send> WriteAsync for IoSourceWrapper<T> {
+ async fn write_from_vec<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ vec: Vec<u8>,
+ ) -> Result<(usize, Vec<u8>)> {
+ self.source
+ .write_from_vec(file_offset, vec)
+ .await
+ .map_err(Into::into)
+ }
+}
+
+#[async_trait(?Send)]
+impl<T: IntoAsync + Send> ReadWriteAsync for IoSourceWrapper<T> {}
+
+#[async_trait(?Send)]
+impl AudioStreamsExecutor for super::Executor {
+ #[cfg(unix)]
+ fn async_unix_stream(&self, stream: UnixStream) -> Result<AsyncStream> {
+ return Ok(Box::new(IoSourceWrapper {
+ source: self.async_from(AsyncWrapper::new(stream))?,
+ }));
+ }
+
+ async fn delay(&self, dur: Duration) -> Result<()> {
+ TimerAsync::sleep(self, dur).await.map_err(Into::into)
+ }
+}
diff --git a/common/cros_async/src/blocking.rs b/common/cros_async/src/blocking.rs
new file mode 100644
index 000000000..f6430a78b
--- /dev/null
+++ b/common/cros_async/src/blocking.rs
@@ -0,0 +1,9 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod block_on;
+mod pool;
+
+pub use block_on::*;
+pub use pool::*;
diff --git a/common/cros_async/src/blocking/block_on.rs b/common/cros_async/src/blocking/block_on.rs
new file mode 100644
index 000000000..2cb0b1e24
--- /dev/null
+++ b/common/cros_async/src/blocking/block_on.rs
@@ -0,0 +1,202 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ future::Future,
+ ptr,
+ sync::{
+ atomic::{AtomicI32, Ordering},
+ Arc,
+ },
+ task::{Context, Poll},
+};
+
+use futures::{
+ pin_mut,
+ task::{waker_ref, ArcWake},
+};
+
+// Randomly generated values to indicate the state of the current thread.
+const WAITING: i32 = 0x25de_74d1;
+const WOKEN: i32 = 0x72d3_2c9f;
+
+const FUTEX_WAIT_PRIVATE: libc::c_int = libc::FUTEX_WAIT | libc::FUTEX_PRIVATE_FLAG;
+const FUTEX_WAKE_PRIVATE: libc::c_int = libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG;
+
+thread_local!(static PER_THREAD_WAKER: Arc<Waker> = Arc::new(Waker(AtomicI32::new(WAITING))));
+
+#[repr(transparent)]
+struct Waker(AtomicI32);
+
+extern "C" {
+ #[cfg_attr(target_os = "android", link_name = "__errno")]
+ #[cfg_attr(target_os = "linux", link_name = "__errno_location")]
+ fn errno_location() -> *mut libc::c_int;
+}
+
+impl ArcWake for Waker {
+ fn wake_by_ref(arc_self: &Arc<Self>) {
+ let state = arc_self.0.swap(WOKEN, Ordering::Release);
+ if state == WAITING {
+ // The thread hasn't already been woken up so wake it up now. Safe because this doesn't
+ // modify any memory and we check the return value.
+ let res = unsafe {
+ libc::syscall(
+ libc::SYS_futex,
+ &arc_self.0,
+ FUTEX_WAKE_PRIVATE,
+ libc::INT_MAX, // val
+ ptr::null() as *const libc::timespec, // timeout
+ ptr::null() as *const libc::c_int, // uaddr2
+ 0_i32, // val3
+ )
+ };
+ if res < 0 {
+ panic!("unexpected error from FUTEX_WAKE_PRIVATE: {}", unsafe {
+ *errno_location()
+ });
+ }
+ }
+ }
+}
+
+/// Run a future to completion on the current thread.
+///
+/// This method will block the current thread until `f` completes. Useful when you need to call an
+/// async fn from a non-async context.
+pub fn block_on<F: Future>(f: F) -> F::Output {
+ pin_mut!(f);
+
+ PER_THREAD_WAKER.with(|thread_waker| {
+ let waker = waker_ref(thread_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ loop {
+ if let Poll::Ready(t) = f.as_mut().poll(&mut cx) {
+ return t;
+ }
+
+ let state = thread_waker.0.swap(WAITING, Ordering::Acquire);
+ if state == WAITING {
+ // If we weren't already woken up then wait until we are. Safe because this doesn't
+ // modify any memory and we check the return value.
+ let res = unsafe {
+ libc::syscall(
+ libc::SYS_futex,
+ &thread_waker.0,
+ FUTEX_WAIT_PRIVATE,
+ state,
+ ptr::null() as *const libc::timespec, // timeout
+ ptr::null() as *const libc::c_int, // uaddr2
+ 0_i32, // val3
+ )
+ };
+
+ if res < 0 {
+ // Safe because libc guarantees that this is a valid pointer.
+ match unsafe { *errno_location() } {
+ libc::EAGAIN | libc::EINTR => {}
+ e => panic!("unexpected error from FUTEX_WAIT_PRIVATE: {}", e),
+ }
+ }
+
+ // Clear the state to prevent unnecessary extra loop iterations and also to allow
+ // nested usage of `block_on`.
+ thread_waker.0.store(WAITING, Ordering::Release);
+ }
+ }
+ })
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use std::{
+ future::Future,
+ pin::Pin,
+ sync::{
+ mpsc::{channel, Sender},
+ Arc,
+ },
+ task::{Context, Poll, Waker},
+ thread,
+ time::Duration,
+ };
+
+ use super::super::super::sync::SpinLock;
+
+ struct TimerState {
+ fired: bool,
+ waker: Option<Waker>,
+ }
+ struct Timer {
+ state: Arc<SpinLock<TimerState>>,
+ }
+
+ impl Future for Timer {
+ type Output = ();
+
+ fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ let mut state = self.state.lock();
+ if state.fired {
+ return Poll::Ready(());
+ }
+
+ state.waker = Some(cx.waker().clone());
+ Poll::Pending
+ }
+ }
+
+ fn start_timer(dur: Duration, notify: Option<Sender<()>>) -> Timer {
+ let state = Arc::new(SpinLock::new(TimerState {
+ fired: false,
+ waker: None,
+ }));
+
+ let thread_state = Arc::clone(&state);
+ thread::spawn(move || {
+ thread::sleep(dur);
+ let mut ts = thread_state.lock();
+ ts.fired = true;
+ if let Some(waker) = ts.waker.take() {
+ waker.wake();
+ }
+ drop(ts);
+
+ if let Some(tx) = notify {
+ tx.send(()).expect("Failed to send completion notification");
+ }
+ });
+
+ Timer { state }
+ }
+
+ #[test]
+ fn it_works() {
+ block_on(start_timer(Duration::from_millis(100), None));
+ }
+
+ #[test]
+ fn nested() {
+ async fn inner() {
+ block_on(start_timer(Duration::from_millis(100), None));
+ }
+
+ block_on(inner());
+ }
+
+ #[test]
+ fn ready_before_poll() {
+ let (tx, rx) = channel();
+
+ let timer = start_timer(Duration::from_millis(50), Some(tx));
+
+ rx.recv()
+ .expect("Failed to receive completion notification");
+
+ // We know the timer has already fired so the poll should complete immediately.
+ block_on(timer);
+ }
+}
diff --git a/common/cros_async/src/blocking/pool.rs b/common/cros_async/src/blocking/pool.rs
new file mode 100644
index 000000000..4bf45020e
--- /dev/null
+++ b/common/cros_async/src/blocking/pool.rs
@@ -0,0 +1,524 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ collections::VecDeque,
+ mem,
+ sync::{
+ mpsc::{channel, Receiver, Sender},
+ Arc,
+ },
+ thread::{
+ JoinHandle, {self},
+ },
+ time::{Duration, Instant},
+};
+
+use async_task::{Runnable, Task};
+use slab::Slab;
+use sync::{Condvar, Mutex};
+use sys_util::{error, warn};
+
+const DEFAULT_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(10);
+
+struct State {
+ tasks: VecDeque<Runnable>,
+ num_threads: usize,
+ num_idle: usize,
+ num_notified: usize,
+ worker_threads: Slab<JoinHandle<()>>,
+ exited_threads: Option<Receiver<usize>>,
+ exit: Sender<usize>,
+ shutting_down: bool,
+}
+
+fn run_blocking_thread(idx: usize, inner: Arc<Inner>, exit: Sender<usize>) {
+ let mut state = inner.state.lock();
+ while !state.shutting_down {
+ if let Some(runnable) = state.tasks.pop_front() {
+ drop(state);
+ runnable.run();
+ state = inner.state.lock();
+ continue;
+ }
+
+ // No more tasks so wait for more work.
+ state.num_idle += 1;
+
+ let (guard, result) = inner
+ .condvar
+ .wait_timeout_while(state, inner.keepalive, |s| {
+ !s.shutting_down && s.num_notified == 0
+ });
+ state = guard;
+
+ // If `state.num_notified > 0` then this was a real wakeup.
+ if state.num_notified > 0 {
+ state.num_notified -= 1;
+ continue;
+ }
+
+ // Only decrement the idle count if we timed out. Otherwise, it was decremented when new
+ // work was added to `state.tasks`.
+ if result.timed_out() {
+ state.num_idle = state
+ .num_idle
+ .checked_sub(1)
+ .expect("`num_idle` underflow on timeout");
+ break;
+ }
+ }
+
+ state.num_threads -= 1;
+
+ // If we're shutting down then the BlockingPool will take care of joining all the threads.
+ // Otherwise, we need to join the last worker thread that exited here.
+ let last_exited_thread = if let Some(exited_threads) = state.exited_threads.as_mut() {
+ exited_threads
+ .try_recv()
+ .map(|idx| state.worker_threads.remove(idx))
+ .ok()
+ } else {
+ None
+ };
+
+ // Drop the lock before trying to join the last exited thread.
+ drop(state);
+
+ if let Some(handle) = last_exited_thread {
+ let _ = handle.join();
+ }
+
+ if let Err(e) = exit.send(idx) {
+ error!("Failed to send thread exit event on channel: {}", e);
+ }
+}
+
+struct Inner {
+ state: Mutex<State>,
+ condvar: Condvar,
+ max_threads: usize,
+ keepalive: Duration,
+}
+
+impl Inner {
+ fn schedule(self: &Arc<Inner>, runnable: Runnable) {
+ let mut state = self.state.lock();
+
+ // If we're shutting down then nothing is going to run this task.
+ if state.shutting_down {
+ return;
+ }
+
+ state.tasks.push_back(runnable);
+
+ if state.num_idle == 0 {
+ // There are no idle threads. Spawn a new one if possible.
+ if state.num_threads < self.max_threads {
+ state.num_threads += 1;
+ let exit = state.exit.clone();
+ let entry = state.worker_threads.vacant_entry();
+ let idx = entry.key();
+ let inner = self.clone();
+ entry.insert(
+ thread::Builder::new()
+ .name(format!("blockingPool{}", idx))
+ .spawn(move || run_blocking_thread(idx, inner, exit))
+ .unwrap(),
+ );
+ }
+ } else {
+ // We have idle threads, wake one up.
+ state.num_idle -= 1;
+ state.num_notified += 1;
+ self.condvar.notify_one();
+ }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error("{0} BlockingPool threads did not exit in time and will be detached")]
+pub struct ShutdownTimedOut(usize);
+
+/// A thread pool for running work that may block.
+///
+/// It is generally discouraged to do any blocking work inside an async function. However, this is
+/// sometimes unavoidable when dealing with interfaces that don't provide async variants. In this
+/// case callers may use the `BlockingPool` to run the blocking work on a different thread and
+/// `await` for its result to finish, which will prevent blocking the main thread of the
+/// application.
+///
+/// Since the blocking work is sent to another thread, users should be careful when using the
+/// `BlockingPool` for latency-sensitive operations. Additionally, the `BlockingPool` is intended to
+/// be used for work that will eventually complete on its own. Users who want to spawn a thread
+/// should just use `thread::spawn` directly.
+///
+/// There is no way to cancel work once it has been picked up by one of the worker threads in the
+/// `BlockingPool`. Dropping or shutting down the pool will block up to a timeout (default 10
+/// seconds) to wait for any active blocking work to finish. Any threads running tasks that have not
+/// completed by that time will be detached.
+///
+/// # Examples
+///
+/// Spawn a task to run in the `BlockingPool` and await on its result.
+///
+/// ```edition2018
+/// use cros_async::BlockingPool;
+///
+/// # async fn do_it() {
+/// let pool = BlockingPool::default();
+///
+/// let res = pool.spawn(move || {
+/// // Do some CPU-intensive or blocking work here.
+///
+/// 42
+/// }).await;
+///
+/// assert_eq!(res, 42);
+/// # }
+/// # cros_async::block_on(do_it());
+/// ```
+pub struct BlockingPool {
+ inner: Arc<Inner>,
+}
+
+impl BlockingPool {
+ /// Create a new `BlockingPool`.
+ ///
+ /// The `BlockingPool` will never spawn more than `max_threads` threads to do work, regardless
+ /// of the number of tasks that are added to it. This value should be set relatively low (for
+ /// example, the number of CPUs on the machine) if the pool is intended to run CPU intensive
+ /// work or it should be set relatively high (128 or more) if the pool is intended to be used
+ /// for various IO operations that cannot be completed asynchronously. The default value is 256.
+ ///
+ /// Worker threads are spawned on demand when new work is added to the pool and will
+ /// automatically exit after being idle for some time so there is no overhead for setting
+ /// `max_threads` to a large value when there is little to no work assigned to the
+ /// `BlockingPool`. `keepalive` determines the idle duration after which the worker thread will
+ /// exit. The default value is 10 seconds.
+ pub fn new(max_threads: usize, keepalive: Duration) -> BlockingPool {
+ let (exit, exited_threads) = channel();
+ BlockingPool {
+ inner: Arc::new(Inner {
+ state: Mutex::new(State {
+ tasks: VecDeque::new(),
+ num_threads: 0,
+ num_idle: 0,
+ num_notified: 0,
+ worker_threads: Slab::new(),
+ exited_threads: Some(exited_threads),
+ exit,
+ shutting_down: false,
+ }),
+ condvar: Condvar::new(),
+ max_threads,
+ keepalive,
+ }),
+ }
+ }
+
+ /// Like new but with pre-allocating capacity for up to `max_threads`.
+ pub fn with_capacity(max_threads: usize, keepalive: Duration) -> BlockingPool {
+ let (exit, exited_threads) = channel();
+ BlockingPool {
+ inner: Arc::new(Inner {
+ state: Mutex::new(State {
+ tasks: VecDeque::new(),
+ num_threads: 0,
+ num_idle: 0,
+ num_notified: 0,
+ worker_threads: Slab::with_capacity(max_threads),
+ exited_threads: Some(exited_threads),
+ exit,
+ shutting_down: false,
+ }),
+ condvar: Condvar::new(),
+ max_threads,
+ keepalive,
+ }),
+ }
+ }
+
+ /// Spawn a task to run in the `BlockingPool`.
+ ///
+ /// Callers may `await` the returned `Task` to be notified when the work is completed.
+ ///
+ /// # Panics
+ ///
+ /// `await`ing a `Task` after dropping the `BlockingPool` or calling `BlockingPool::shutdown`
+ /// will panic if the work was not completed before the pool was shut down.
+ pub fn spawn<F, R>(&self, f: F) -> Task<R>
+ where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+ {
+ let raw = Arc::downgrade(&self.inner);
+ let schedule = move |runnable| {
+ if let Some(i) = raw.upgrade() {
+ i.schedule(runnable);
+ }
+ };
+
+ let (runnable, task) = async_task::spawn(async move { f() }, schedule);
+ runnable.schedule();
+
+ task
+ }
+
+ /// Shut down the `BlockingPool`.
+ ///
+ /// If `deadline` is provided then this will block until either all worker threads exit or the
+ /// deadline is exceeded. If `deadline` is not given then this will block indefinitely until all
+ /// worker threads exit. Any work that was added to the `BlockingPool` but not yet picked up by
+ /// a worker thread will not complete and `await`ing on the `Task` for that work will panic.
+ pub fn shutdown(&self, deadline: Option<Instant>) -> Result<(), ShutdownTimedOut> {
+ let mut state = self.inner.state.lock();
+
+ if state.shutting_down {
+ // We've already shut down this BlockingPool.
+ return Ok(());
+ }
+
+ state.shutting_down = true;
+ let exited_threads = state.exited_threads.take().expect("exited_threads missing");
+ let unfinished_tasks = std::mem::take(&mut state.tasks);
+ let mut worker_threads = mem::replace(&mut state.worker_threads, Slab::new());
+ drop(state);
+
+ self.inner.condvar.notify_all();
+
+ // Cancel any unfinished work after releasing the lock.
+ drop(unfinished_tasks);
+
+ // Now wait for all worker threads to exit.
+ if let Some(deadline) = deadline {
+ let mut now = Instant::now();
+ while now < deadline && !worker_threads.is_empty() {
+ if let Ok(idx) = exited_threads.recv_timeout(deadline - now) {
+ let _ = worker_threads.remove(idx).join();
+ }
+ now = Instant::now();
+ }
+
+ // Any threads that have not yet joined will just be detached.
+ if !worker_threads.is_empty() {
+ return Err(ShutdownTimedOut(worker_threads.len()));
+ }
+
+ Ok(())
+ } else {
+ // Block indefinitely until all worker threads exit.
+ for handle in worker_threads.drain() {
+ let _ = handle.join();
+ }
+
+ Ok(())
+ }
+ }
+}
+
+impl Default for BlockingPool {
+ fn default() -> BlockingPool {
+ BlockingPool::new(256, Duration::from_secs(10))
+ }
+}
+
+impl Drop for BlockingPool {
+ fn drop(&mut self) {
+ if let Err(e) = self.shutdown(Some(Instant::now() + DEFAULT_SHUTDOWN_TIMEOUT)) {
+ warn!("{}", e);
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::{
+ sync::{Arc, Barrier},
+ thread,
+ time::{Duration, Instant},
+ };
+
+ use futures::{stream::FuturesUnordered, StreamExt};
+ use sync::{Condvar, Mutex};
+
+ use super::super::super::{block_on, BlockingPool};
+
+ #[test]
+ fn blocking_sleep() {
+ let pool = BlockingPool::default();
+
+ let res = block_on(pool.spawn(|| 42));
+ assert_eq!(res, 42);
+ }
+
+ #[test]
+ fn fast_tasks_with_short_keepalive() {
+ let pool = BlockingPool::new(256, Duration::from_millis(1));
+
+ let streams = FuturesUnordered::new();
+ for _ in 0..2 {
+ for _ in 0..256 {
+ let task = pool.spawn(|| ());
+ streams.push(task);
+ }
+
+ thread::sleep(Duration::from_millis(1));
+ }
+
+ block_on(streams.collect::<Vec<_>>());
+
+ // The test passes if there are no panics, which would happen if one of the worker threads
+ // triggered an underflow on `pool.inner.state.num_idle`.
+ }
+
+ #[test]
+ fn more_tasks_than_threads() {
+ let pool = BlockingPool::new(4, Duration::from_secs(10));
+
+ let stream = (0..19)
+ .map(|_| pool.spawn(|| thread::sleep(Duration::from_millis(5))))
+ .collect::<FuturesUnordered<_>>();
+
+ let results = block_on(stream.collect::<Vec<_>>());
+ assert_eq!(results.len(), 19);
+ }
+
+ #[test]
+ fn shutdown() {
+ let pool = BlockingPool::default();
+
+ let stream = (0..19)
+ .map(|_| pool.spawn(|| thread::sleep(Duration::from_millis(5))))
+ .collect::<FuturesUnordered<_>>();
+
+ let results = block_on(stream.collect::<Vec<_>>());
+ assert_eq!(results.len(), 19);
+
+ pool.shutdown(Some(Instant::now() + Duration::from_secs(10)))
+ .unwrap();
+ let state = pool.inner.state.lock();
+ assert_eq!(state.num_threads, 0);
+ }
+
+ #[test]
+ fn keepalive_timeout() {
+ // Set the keepalive to a very low value so that threads will exit soon after they run out
+ // of work.
+ let pool = BlockingPool::new(7, Duration::from_millis(1));
+
+ let stream = (0..19)
+ .map(|_| pool.spawn(|| thread::sleep(Duration::from_millis(5))))
+ .collect::<FuturesUnordered<_>>();
+
+ let results = block_on(stream.collect::<Vec<_>>());
+ assert_eq!(results.len(), 19);
+
+ // Wait for all threads to exit.
+ let deadline = Instant::now() + Duration::from_secs(10);
+ while Instant::now() < deadline {
+ thread::sleep(Duration::from_millis(100));
+ let state = pool.inner.state.lock();
+ if state.num_threads == 0 {
+ break;
+ }
+ }
+
+ {
+ let state = pool.inner.state.lock();
+ assert_eq!(state.num_threads, 0);
+ assert_eq!(state.num_idle, 0);
+ }
+ }
+
+ #[test]
+ #[should_panic]
+ fn shutdown_with_pending_work() {
+ let pool = BlockingPool::new(1, Duration::from_secs(10));
+
+ let mu = Arc::new(Mutex::new(false));
+ let cv = Arc::new(Condvar::new());
+
+ // First spawn a thread that blocks the pool.
+ let task_mu = mu.clone();
+ let task_cv = cv.clone();
+ pool.spawn(move || {
+ let mut ready = task_mu.lock();
+ while !*ready {
+ ready = task_cv.wait(ready);
+ }
+ })
+ .detach();
+
+ // This task will never finish because we will shut down the pool first.
+ let unfinished = pool.spawn(|| 5);
+
+ // Spawn a thread to unblock the work we started earlier once it sees that the pool is
+ // shutting down.
+ let inner = pool.inner.clone();
+ thread::spawn(move || {
+ let mut state = inner.state.lock();
+ while !state.shutting_down {
+ state = inner.condvar.wait(state);
+ }
+
+ *mu.lock() = true;
+ cv.notify_all();
+ });
+ pool.shutdown(None).unwrap();
+
+ // This should panic.
+ assert_eq!(block_on(unfinished), 5);
+ }
+
+ #[test]
+ fn unfinished_worker_thread() {
+ let pool = BlockingPool::default();
+
+ let ready = Arc::new(Mutex::new(false));
+ let cv = Arc::new(Condvar::new());
+ let barrier = Arc::new(Barrier::new(2));
+
+ let thread_ready = ready.clone();
+ let thread_barrier = barrier.clone();
+ let thread_cv = cv.clone();
+
+ let task = pool.spawn(move || {
+ thread_barrier.wait();
+ let mut ready = thread_ready.lock();
+ while !*ready {
+ ready = thread_cv.wait(ready);
+ }
+ });
+
+ // Wait to shut down the pool until after the worker thread has started.
+ barrier.wait();
+ pool.shutdown(Some(Instant::now() + Duration::from_millis(5)))
+ .unwrap_err();
+
+ let num_threads = pool.inner.state.lock().num_threads;
+ assert_eq!(num_threads, 1);
+
+ // Now wake up the blocked task so we don't leak the thread.
+ *ready.lock() = true;
+ cv.notify_all();
+
+ block_on(task);
+
+ let deadline = Instant::now() + Duration::from_secs(10);
+ while Instant::now() < deadline {
+ thread::sleep(Duration::from_millis(100));
+ let state = pool.inner.state.lock();
+ if state.num_threads == 0 {
+ break;
+ }
+ }
+
+ {
+ let state = pool.inner.state.lock();
+ assert_eq!(state.num_threads, 0);
+ assert_eq!(state.num_idle, 0);
+ }
+ }
+}
diff --git a/common/cros_async/src/complete.rs b/common/cros_async/src/complete.rs
new file mode 100644
index 000000000..9fb9273f4
--- /dev/null
+++ b/common/cros_async/src/complete.rs
@@ -0,0 +1,91 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Need non-snake case so the macro can re-use type names for variables.
+#![allow(non_snake_case)]
+
+use std::{
+ future::Future,
+ pin::Pin,
+ task::{Context, Poll},
+};
+
+use futures::future::{maybe_done, MaybeDone};
+use pin_utils::unsafe_pinned;
+
+// Macro-generate future combinators to allow for running different numbers of top-level futures in
+// this FutureList. Generates the implementation of `FutureList` for the completion types. For an
+// explicit example this is modeled after, see `UnitFutures`.
+macro_rules! generate {
+ ($(
+ $(#[$doc:meta])*
+ ($Complete:ident, <$($Fut:ident),*>),
+ )*) => ($(
+ #[must_use = "Combinations of futures don't do anything unless run in an executor."]
+ pub(crate) struct $Complete<$($Fut: Future),*> {
+ $($Fut: MaybeDone<$Fut>,)*
+ }
+
+ impl<$($Fut),*> $Complete<$($Fut),*>
+ where $(
+ $Fut: Future,
+ )*
+ {
+ // Safety:
+ // * No Drop impl
+ // * No Unpin impl
+ // * Not #[repr(packed)]
+ $(
+ unsafe_pinned!($Fut: MaybeDone<$Fut>);
+ )*
+
+ pub(crate) fn new($($Fut: $Fut),*) -> $Complete<$($Fut),*> {
+ $(
+ let $Fut = maybe_done($Fut);
+ )*
+ $Complete {
+ $($Fut),*
+ }
+ }
+ }
+
+ impl<$($Fut),*> Future for $Complete<$($Fut),*>
+ where $(
+ $Fut: Future,
+ )*
+ {
+ type Output = ($($Fut::Output),*);
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ let mut complete = true;
+ $(
+ complete &= self.as_mut().$Fut().poll(cx).is_ready();
+ )*
+
+ if complete {
+ $(
+ let $Fut = self.as_mut().$Fut().take_output().unwrap();
+ )*
+ Poll::Ready(($($Fut), *))
+ } else {
+ Poll::Pending
+ }
+ }
+ }
+ )*)
+}
+
+generate! {
+ /// _Future for the [`complete2`] function.
+ (Complete2, <_Fut1, _Fut2>),
+
+ /// _Future for the [`complete3`] function.
+ (Complete3, <_Fut1, _Fut2, _Fut3>),
+
+ /// _Future for the [`complete4`] function.
+ (Complete4, <_Fut1, _Fut2, _Fut3, _Fut4>),
+
+ /// _Future for the [`complete5`] function.
+ (Complete5, <_Fut1, _Fut2, _Fut3, _Fut4, _Fut5>),
+}
diff --git a/common/cros_async/src/event.rs b/common/cros_async/src/event.rs
new file mode 100644
index 000000000..318ad4ed0
--- /dev/null
+++ b/common/cros_async/src/event.rs
@@ -0,0 +1,85 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use sys_util::EventFd;
+
+use super::{AsyncResult, Executor, IntoAsync, IoSourceExt};
+
+/// An async version of `sys_util::EventFd`.
+pub struct EventAsync {
+ io_source: Box<dyn IoSourceExt<EventFd>>,
+}
+
+impl EventAsync {
+ pub fn new(event: EventFd, ex: &Executor) -> AsyncResult<EventAsync> {
+ ex.async_from(event)
+ .map(|io_source| EventAsync { io_source })
+ }
+
+ #[cfg(test)]
+ pub(crate) fn new_poll(event: EventFd, ex: &super::FdExecutor) -> AsyncResult<EventAsync> {
+ super::executor::async_poll_from(event, ex).map(|io_source| EventAsync { io_source })
+ }
+
+ #[cfg(test)]
+ pub(crate) fn new_uring(event: EventFd, ex: &super::URingExecutor) -> AsyncResult<EventAsync> {
+ super::executor::async_uring_from(event, ex).map(|io_source| EventAsync { io_source })
+ }
+
+ /// Gets the next value from the eventfd.
+ #[allow(dead_code)]
+ pub async fn next_val(&self) -> AsyncResult<u64> {
+ self.io_source.read_u64().await
+ }
+}
+
+impl IntoAsync for EventFd {}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use super::super::{uring_executor::use_uring, Executor, FdExecutor, URingExecutor};
+
+ #[test]
+ fn next_val_reads_value() {
+ async fn go(event: EventFd, ex: &Executor) -> u64 {
+ let event_async = EventAsync::new(event, ex).unwrap();
+ event_async.next_val().await.unwrap()
+ }
+
+ let eventfd = EventFd::new().unwrap();
+ eventfd.write(0xaa).unwrap();
+ let ex = Executor::new().unwrap();
+ let val = ex.run_until(go(eventfd, &ex)).unwrap();
+ assert_eq!(val, 0xaa);
+ }
+
+ #[test]
+ fn next_val_reads_value_poll_and_ring() {
+ if !use_uring() {
+ return;
+ }
+
+ async fn go(event_async: EventAsync) -> u64 {
+ event_async.next_val().await.unwrap()
+ }
+
+ let eventfd = EventFd::new().unwrap();
+ eventfd.write(0xaa).unwrap();
+ let uring_ex = URingExecutor::new().unwrap();
+ let val = uring_ex
+ .run_until(go(EventAsync::new_uring(eventfd, &uring_ex).unwrap()))
+ .unwrap();
+ assert_eq!(val, 0xaa);
+
+ let eventfd = EventFd::new().unwrap();
+ eventfd.write(0xaa).unwrap();
+ let poll_ex = FdExecutor::new().unwrap();
+ let val = poll_ex
+ .run_until(go(EventAsync::new_poll(eventfd, &poll_ex).unwrap()))
+ .unwrap();
+ assert_eq!(val, 0xaa);
+ }
+}
diff --git a/common/cros_async/src/executor.rs b/common/cros_async/src/executor.rs
new file mode 100644
index 000000000..7f6cfce93
--- /dev/null
+++ b/common/cros_async/src/executor.rs
@@ -0,0 +1,344 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::future::Future;
+
+use async_task::Task;
+
+use super::{
+ poll_source::Error as PollError, uring_executor::use_uring, AsyncResult, FdExecutor, IntoAsync,
+ IoSourceExt, PollSource, URingExecutor, UringSource,
+};
+
+pub(crate) fn async_uring_from<'a, F: IntoAsync + Send + 'a>(
+ f: F,
+ ex: &URingExecutor,
+) -> AsyncResult<Box<dyn IoSourceExt<F> + Send + 'a>> {
+ Ok(UringSource::new(f, ex).map(|u| Box::new(u) as Box<dyn IoSourceExt<F> + Send>)?)
+}
+
+/// Creates a concrete `IoSourceExt` using the fd_executor.
+pub(crate) fn async_poll_from<'a, F: IntoAsync + Send + 'a>(
+ f: F,
+ ex: &FdExecutor,
+) -> AsyncResult<Box<dyn IoSourceExt<F> + Send + 'a>> {
+ Ok(PollSource::new(f, ex).map(|u| Box::new(u) as Box<dyn IoSourceExt<F> + Send>)?)
+}
+
+/// An executor for scheduling tasks that poll futures to completion.
+///
+/// All asynchronous operations must run within an executor, which is capable of spawning futures as
+/// tasks. This executor also provides a mechanism for performing asynchronous I/O operations.
+///
+/// The returned type is a cheap, clonable handle to the underlying executor. Cloning it will only
+/// create a new reference, not a new executor.
+///
+/// # Examples
+///
+/// Concurrently wait for multiple files to become readable/writable and then read/write the data.
+///
+/// ```
+/// use std::cmp::min;
+/// use std::error::Error;
+/// use std::fs::{File, OpenOptions};
+///
+/// use cros_async::{AsyncResult, Executor, IoSourceExt, complete3};
+/// const CHUNK_SIZE: usize = 32;
+///
+/// // Write all bytes from `data` to `f`.
+/// async fn write_file(f: &dyn IoSourceExt<File>, mut data: Vec<u8>) -> AsyncResult<()> {
+/// while data.len() > 0 {
+/// let (count, mut buf) = f.write_from_vec(None, data).await?;
+///
+/// data = buf.split_off(count);
+/// }
+///
+/// Ok(())
+/// }
+///
+/// // Transfer `len` bytes of data from `from` to `to`.
+/// async fn transfer_data(
+/// from: Box<dyn IoSourceExt<File>>,
+/// to: Box<dyn IoSourceExt<File>>,
+/// len: usize,
+/// ) -> AsyncResult<usize> {
+/// let mut rem = len;
+///
+/// while rem > 0 {
+/// let buf = vec![0u8; min(rem, CHUNK_SIZE)];
+/// let (count, mut data) = from.read_to_vec(None, buf).await?;
+///
+/// if count == 0 {
+/// // End of file. Return the number of bytes transferred.
+/// return Ok(len - rem);
+/// }
+///
+/// data.truncate(count);
+/// write_file(&*to, data).await?;
+///
+/// rem = rem.saturating_sub(count);
+/// }
+///
+/// Ok(len)
+/// }
+///
+/// # fn do_it() -> Result<(), Box<dyn Error>> {
+/// let ex = Executor::new()?;
+///
+/// let (rx, tx) = sys_util::pipe(true)?;
+/// let zero = File::open("/dev/zero")?;
+/// let zero_bytes = CHUNK_SIZE * 7;
+/// let zero_to_pipe = transfer_data(
+/// ex.async_from(zero)?,
+/// ex.async_from(tx.try_clone()?)?,
+/// zero_bytes,
+/// );
+///
+/// let rand = File::open("/dev/urandom")?;
+/// let rand_bytes = CHUNK_SIZE * 19;
+/// let rand_to_pipe = transfer_data(ex.async_from(rand)?, ex.async_from(tx)?, rand_bytes);
+///
+/// let null = OpenOptions::new().write(true).open("/dev/null")?;
+/// let null_bytes = zero_bytes + rand_bytes;
+/// let pipe_to_null = transfer_data(ex.async_from(rx)?, ex.async_from(null)?, null_bytes);
+///
+/// ex.run_until(complete3(
+/// async { assert_eq!(pipe_to_null.await.unwrap(), null_bytes) },
+/// async { assert_eq!(zero_to_pipe.await.unwrap(), zero_bytes) },
+/// async { assert_eq!(rand_to_pipe.await.unwrap(), rand_bytes) },
+/// ))?;
+///
+/// # Ok(())
+/// # }
+///
+/// # do_it().unwrap();
+/// ```
+
+#[derive(Clone)]
+pub enum Executor {
+ Uring(URingExecutor),
+ Fd(FdExecutor),
+}
+
+impl Executor {
+ /// Create a new `Executor`.
+ pub fn new() -> AsyncResult<Self> {
+ if use_uring() {
+ Ok(URingExecutor::new().map(Executor::Uring)?)
+ } else {
+ Ok(FdExecutor::new()
+ .map(Executor::Fd)
+ .map_err(PollError::Executor)?)
+ }
+ }
+
+ /// Create a new `Box<dyn IoSourceExt<F>>` associated with `self`. Callers may then use the
+ /// returned `IoSourceExt` to directly start async operations without needing a separate
+ /// reference to the executor.
+ pub fn async_from<'a, F: IntoAsync + Send + 'a>(
+ &self,
+ f: F,
+ ) -> AsyncResult<Box<dyn IoSourceExt<F> + Send + 'a>> {
+ match self {
+ Executor::Uring(ex) => async_uring_from(f, ex),
+ Executor::Fd(ex) => async_poll_from(f, ex),
+ }
+ }
+
+ /// Spawn a new future for this executor to run to completion. Callers may use the returned
+ /// `Task` to await on the result of `f`. Dropping the returned `Task` will cancel `f`,
+ /// preventing it from being polled again. To drop a `Task` without canceling the future
+ /// associated with it use `Task::detach`. To cancel a task gracefully and wait until it is
+ /// fully destroyed, use `Task::cancel`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use cros_async::AsyncResult;
+ /// # fn example_spawn() -> AsyncResult<()> {
+ /// # use std::thread;
+ ///
+ /// # use cros_async::Executor;
+ /// use futures::executor::block_on;
+ ///
+ /// # let ex = Executor::new()?;
+ ///
+ /// # // Spawn a thread that runs the executor.
+ /// # let ex2 = ex.clone();
+ /// # thread::spawn(move || ex2.run());
+ ///
+ /// let task = ex.spawn(async { 7 + 13 });
+ ///
+ /// let result = block_on(task);
+ /// assert_eq!(result, 20);
+ /// # Ok(())
+ /// # }
+ ///
+ /// # example_spawn().unwrap();
+ /// ```
+ pub fn spawn<F>(&self, f: F) -> Task<F::Output>
+ where
+ F: Future + Send + 'static,
+ F::Output: Send + 'static,
+ {
+ match self {
+ Executor::Uring(ex) => ex.spawn(f),
+ Executor::Fd(ex) => ex.spawn(f),
+ }
+ }
+
+ /// Spawn a thread-local task for this executor to drive to completion. Like `spawn` but without
+ /// requiring `Send` on `F` or `F::Output`. This method should only be called from the same
+ /// thread where `run()` or `run_until()` is called.
+ ///
+ /// # Panics
+ ///
+ /// `Executor::run` and `Executor::run_util` will panic if they try to poll a future that was
+ /// added by calling `spawn_local` from a different thread.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use cros_async::AsyncResult;
+ /// # fn example_spawn_local() -> AsyncResult<()> {
+ /// # use cros_async::Executor;
+ ///
+ /// # let ex = Executor::new()?;
+ ///
+ /// let task = ex.spawn_local(async { 7 + 13 });
+ ///
+ /// let result = ex.run_until(task)?;
+ /// assert_eq!(result, 20);
+ /// # Ok(())
+ /// # }
+ ///
+ /// # example_spawn_local().unwrap();
+ /// ```
+ pub fn spawn_local<F>(&self, f: F) -> Task<F::Output>
+ where
+ F: Future + 'static,
+ F::Output: 'static,
+ {
+ match self {
+ Executor::Uring(ex) => ex.spawn_local(f),
+ Executor::Fd(ex) => ex.spawn_local(f),
+ }
+ }
+
+ /// Run the provided closure on a dedicated thread where blocking is allowed.
+ ///
+ /// Callers may `await` on the returned `Task` to wait for the result of `f`. Dropping or
+ /// canceling the returned `Task` may not cancel the operation if it was already started on a
+ /// worker thread.
+ ///
+ /// # Panics
+ ///
+ /// `await`ing the `Task` after the `Executor` is dropped will panic if the work was not already
+ /// completed.
+ ///
+ /// # Examples
+ ///
+ /// ```edition2018
+ /// # use cros_async::Executor;
+ ///
+ /// # async fn do_it(ex: &Executor) {
+ /// let res = ex.spawn_blocking(move || {
+ /// // Do some CPU-intensive or blocking work here.
+ ///
+ /// 42
+ /// }).await;
+ ///
+ /// assert_eq!(res, 42);
+ /// # }
+ ///
+ /// # let ex = Executor::new().unwrap();
+ /// # ex.run_until(do_it(&ex)).unwrap();
+ /// ```
+ pub fn spawn_blocking<F, R>(&self, f: F) -> Task<R>
+ where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+ {
+ match self {
+ Executor::Uring(ex) => ex.spawn_blocking(f),
+ Executor::Fd(ex) => ex.spawn_blocking(f),
+ }
+ }
+
+ /// Run the executor indefinitely, driving all spawned futures to completion. This method will
+ /// block the current thread and only return in the case of an error.
+ ///
+ /// # Panics
+ ///
+ /// Once this method has been called on a thread, it may only be called on that thread from that
+ /// point on. Attempting to call it from another thread will panic.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use cros_async::AsyncResult;
+ /// # fn example_run() -> AsyncResult<()> {
+ /// use std::thread;
+ ///
+ /// use cros_async::Executor;
+ /// use futures::executor::block_on;
+ ///
+ /// let ex = Executor::new()?;
+ ///
+ /// // Spawn a thread that runs the executor.
+ /// let ex2 = ex.clone();
+ /// thread::spawn(move || ex2.run());
+ ///
+ /// let task = ex.spawn(async { 7 + 13 });
+ ///
+ /// let result = block_on(task);
+ /// assert_eq!(result, 20);
+ /// # Ok(())
+ /// # }
+ ///
+ /// # example_run().unwrap();
+ /// ```
+ pub fn run(&self) -> AsyncResult<()> {
+ match self {
+ Executor::Uring(ex) => ex.run()?,
+ Executor::Fd(ex) => ex.run().map_err(PollError::Executor)?,
+ }
+
+ Ok(())
+ }
+
+ /// Drive all futures spawned in this executor until `f` completes. This method will block the
+ /// current thread only until `f` is complete and there may still be unfinished futures in the
+ /// executor.
+ ///
+ /// # Panics
+ ///
+ /// Once this method has been called on a thread, from then onwards it may only be called on
+ /// that thread. Attempting to call it from another thread will panic.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use cros_async::AsyncResult;
+ /// # fn example_run_until() -> AsyncResult<()> {
+ /// use cros_async::Executor;
+ ///
+ /// let ex = Executor::new()?;
+ ///
+ /// let task = ex.spawn_local(async { 7 + 13 });
+ ///
+ /// let result = ex.run_until(task)?;
+ /// assert_eq!(result, 20);
+ /// # Ok(())
+ /// # }
+ ///
+ /// # example_run_until().unwrap();
+ /// ```
+ pub fn run_until<F: Future>(&self, f: F) -> AsyncResult<F::Output> {
+ match self {
+ Executor::Uring(ex) => Ok(ex.run_until(f)?),
+ Executor::Fd(ex) => Ok(ex.run_until(f).map_err(PollError::Executor)?),
+ }
+ }
+}
diff --git a/common/cros_async/src/fd_executor.rs b/common/cros_async/src/fd_executor.rs
new file mode 100644
index 000000000..9439749f2
--- /dev/null
+++ b/common/cros_async/src/fd_executor.rs
@@ -0,0 +1,634 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! The executor runs all given futures to completion. Futures register wakers associated with file
+//! descriptors. The wakers will be called when the FD becomes readable or writable depending on
+//! the situation.
+//!
+//! `FdExecutor` is meant to be used with the `futures-rs` crate that provides combinators and
+//! utility functions to combine futures.
+
+use std::{
+ fs::File,
+ future::Future,
+ io, mem,
+ os::unix::io::{AsRawFd, FromRawFd, RawFd},
+ pin::Pin,
+ sync::{
+ atomic::{AtomicI32, Ordering},
+ Arc, Weak,
+ },
+ task::{Context, Poll, Waker},
+};
+
+use async_task::Task;
+use futures::task::noop_waker;
+use pin_utils::pin_mut;
+use remain::sorted;
+use slab::Slab;
+use sync::Mutex;
+use sys_util::{add_fd_flags, warn, EpollContext, EpollEvents, EventFd, WatchingEvents};
+use thiserror::Error as ThisError;
+
+use super::{
+ queue::RunnableQueue,
+ waker::{new_waker, WakerToken, WeakWake},
+ BlockingPool,
+};
+
+#[sorted]
+#[derive(Debug, ThisError)]
+pub enum Error {
+ /// Failed to clone the EventFd for waking the executor.
+ #[error("Failed to clone the EventFd for waking the executor: {0}")]
+ CloneEventFd(sys_util::Error),
+ /// Failed to create the EventFd for waking the executor.
+ #[error("Failed to create the EventFd for waking the executor: {0}")]
+ CreateEventFd(sys_util::Error),
+ /// Creating a context to wait on FDs failed.
+ #[error("An error creating the fd waiting context: {0}")]
+ CreatingContext(sys_util::Error),
+ /// Failed to copy the FD for the polling context.
+ #[error("Failed to copy the FD for the polling context: {0}")]
+ DuplicatingFd(sys_util::Error),
+ /// The Executor is gone.
+ #[error("The FDExecutor is gone")]
+ ExecutorGone,
+ /// PollContext failure.
+ #[error("PollContext failure: {0}")]
+ PollContextError(sys_util::Error),
+ /// An error occurred when setting the FD non-blocking.
+ #[error("An error occurred setting the FD non-blocking: {0}.")]
+ SettingNonBlocking(sys_util::Error),
+ /// Failed to submit the waker to the polling context.
+ #[error("An error adding to the Aio context: {0}")]
+ SubmittingWaker(sys_util::Error),
+ /// A Waker was canceled, but the operation isn't running.
+ #[error("Unknown waker")]
+ UnknownWaker,
+}
+pub type Result<T> = std::result::Result<T, Error>;
+
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ use Error::*;
+ match e {
+ CloneEventFd(e) => e.into(),
+ CreateEventFd(e) => e.into(),
+ DuplicatingFd(e) => e.into(),
+ ExecutorGone => io::Error::new(io::ErrorKind::Other, e),
+ CreatingContext(e) => e.into(),
+ PollContextError(e) => e.into(),
+ SettingNonBlocking(e) => e.into(),
+ SubmittingWaker(e) => e.into(),
+ UnknownWaker => io::Error::new(io::ErrorKind::Other, e),
+ }
+ }
+}
+
+// A poll operation that has been submitted and is potentially being waited on.
+struct OpData {
+ file: File,
+ waker: Option<Waker>,
+}
+
+// The current status of a submitted operation.
+enum OpStatus {
+ Pending(OpData),
+ Completed,
+}
+
+// An IO source previously registered with an FdExecutor. Used to initiate asynchronous IO with the
+// associated executor.
+pub struct RegisteredSource<F> {
+ source: F,
+ ex: Weak<RawExecutor>,
+}
+
+impl<F: AsRawFd> RegisteredSource<F> {
+ // Start an asynchronous operation to wait for this source to become readable. The returned
+ // future will not be ready until the source is readable.
+ pub fn wait_readable(&self) -> Result<PendingOperation> {
+ let ex = self.ex.upgrade().ok_or(Error::ExecutorGone)?;
+
+ let token =
+ ex.add_operation(self.source.as_raw_fd(), WatchingEvents::empty().set_read())?;
+
+ Ok(PendingOperation {
+ token: Some(token),
+ ex: self.ex.clone(),
+ })
+ }
+
+ // Start an asynchronous operation to wait for this source to become writable. The returned
+ // future will not be ready until the source is writable.
+ pub fn wait_writable(&self) -> Result<PendingOperation> {
+ let ex = self.ex.upgrade().ok_or(Error::ExecutorGone)?;
+
+ let token =
+ ex.add_operation(self.source.as_raw_fd(), WatchingEvents::empty().set_write())?;
+
+ Ok(PendingOperation {
+ token: Some(token),
+ ex: self.ex.clone(),
+ })
+ }
+}
+
+impl<F> RegisteredSource<F> {
+ // Consume this RegisteredSource and return the inner IO source.
+ pub fn into_source(self) -> F {
+ self.source
+ }
+}
+
+impl<F> AsRef<F> for RegisteredSource<F> {
+ fn as_ref(&self) -> &F {
+ &self.source
+ }
+}
+
+impl<F> AsMut<F> for RegisteredSource<F> {
+ fn as_mut(&mut self) -> &mut F {
+ &mut self.source
+ }
+}
+
+/// A token returned from `add_operation` that can be used to cancel the waker before it completes.
+/// Used to manage getting the result from the underlying executor for a completed operation.
+/// Dropping a `PendingOperation` will get the result from the executor.
+pub struct PendingOperation {
+ token: Option<WakerToken>,
+ ex: Weak<RawExecutor>,
+}
+
+impl Future for PendingOperation {
+ type Output = Result<()>;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ let token = self
+ .token
+ .as_ref()
+ .expect("PendingOperation polled after returning Poll::Ready");
+ if let Some(ex) = self.ex.upgrade() {
+ if ex.is_ready(token, cx) {
+ self.token = None;
+ Poll::Ready(Ok(()))
+ } else {
+ Poll::Pending
+ }
+ } else {
+ Poll::Ready(Err(Error::ExecutorGone))
+ }
+ }
+}
+
+impl Drop for PendingOperation {
+ fn drop(&mut self) {
+ if let Some(token) = self.token.take() {
+ if let Some(ex) = self.ex.upgrade() {
+ let _ = ex.cancel_operation(token);
+ }
+ }
+ }
+}
+
+// This function exists to guarantee that non-epoll futures will not starve until an epoll future is
+// ready to be polled again. The mechanism is very similar to the self-pipe trick used by C programs
+// to reliably mix select / poll with signal handling. This is how it works:
+//
+// * RawExecutor::new creates an eventfd, dupes it, and spawns this async function with the duped fd.
+// * The first time notify_task is polled it tries to read from the eventfd and if that fails, waits
+// for the fd to become readable.
+// * Meanwhile the RawExecutor keeps the original fd for the eventfd.
+// * Whenever RawExecutor::wake is called it will write to the eventfd if it determines that the
+// executor thread is currently blocked inside an io_epoll_enter call. This can happen when a
+// non-epoll future becomes ready to poll.
+// * The write to the eventfd causes the fd to become readable, which then allows the epoll() call
+// to return with at least one readable fd.
+// * The executor then polls the non-epoll future that became ready, any epoll futures that
+// completed, and the notify_task function, which then queues up another read on the eventfd and
+// the process can repeat.
+async fn notify_task(notify: EventFd, raw: Weak<RawExecutor>) {
+ add_fd_flags(notify.as_raw_fd(), libc::O_NONBLOCK)
+ .expect("Failed to set notify EventFd as non-blocking");
+
+ loop {
+ match notify.read() {
+ Ok(_) => {}
+ Err(e) if e.errno() == libc::EWOULDBLOCK => {}
+ Err(e) => panic!("Unexpected error while reading notify EventFd: {}", e),
+ }
+
+ if let Some(ex) = raw.upgrade() {
+ let token = ex
+ .add_operation(notify.as_raw_fd(), WatchingEvents::empty().set_read())
+ .expect("Failed to add notify EventFd to PollCtx");
+
+ // We don't want to hold an active reference to the executor in the .await below.
+ mem::drop(ex);
+
+ let op = PendingOperation {
+ token: Some(token),
+ ex: raw.clone(),
+ };
+
+ match op.await {
+ Ok(()) => {}
+ Err(Error::ExecutorGone) => break,
+ Err(e) => panic!("Unexpected error while waiting for notify EventFd: {}", e),
+ }
+ } else {
+ // The executor is gone so we should also exit.
+ break;
+ }
+ }
+}
+
+// Indicates that the executor is either within or about to make a PollContext::wait() call. When a
+// waker sees this value, it will write to the notify EventFd, which will cause the
+// PollContext::wait() call to return.
+const WAITING: i32 = 0x1d5b_c019u32 as i32;
+
+// Indicates that the executor is processing any futures that are ready to run.
+const PROCESSING: i32 = 0xd474_77bcu32 as i32;
+
+// Indicates that one or more futures may be ready to make progress.
+const WOKEN: i32 = 0x3e4d_3276u32 as i32;
+
+struct RawExecutor {
+ queue: RunnableQueue,
+ poll_ctx: EpollContext<usize>,
+ ops: Mutex<Slab<OpStatus>>,
+ blocking_pool: BlockingPool,
+ state: AtomicI32,
+ notify: EventFd,
+}
+
+impl RawExecutor {
+ fn new(notify: EventFd) -> Result<Self> {
+ Ok(RawExecutor {
+ queue: RunnableQueue::new(),
+ poll_ctx: EpollContext::new().map_err(Error::CreatingContext)?,
+ ops: Mutex::new(Slab::with_capacity(64)),
+ blocking_pool: Default::default(),
+ state: AtomicI32::new(PROCESSING),
+ notify,
+ })
+ }
+
+ fn add_operation(&self, fd: RawFd, events: WatchingEvents) -> Result<WakerToken> {
+ let duped_fd = unsafe {
+ // Safe because duplicating an FD doesn't affect memory safety, and the dup'd FD
+ // will only be added to the poll loop.
+ File::from_raw_fd(dup_fd(fd)?)
+ };
+ let mut ops = self.ops.lock();
+ let entry = ops.vacant_entry();
+ let next_token = entry.key();
+ self.poll_ctx
+ .add_fd_with_events(&duped_fd, events, next_token)
+ .map_err(Error::SubmittingWaker)?;
+ entry.insert(OpStatus::Pending(OpData {
+ file: duped_fd,
+ waker: None,
+ }));
+ Ok(WakerToken(next_token))
+ }
+
+ fn wake(&self) {
+ let oldstate = self.state.swap(WOKEN, Ordering::Release);
+ if oldstate == WAITING {
+ if let Err(e) = self.notify.write(1) {
+ warn!("Failed to notify executor that a future is ready: {}", e);
+ }
+ }
+ }
+
+ fn spawn<F>(self: &Arc<Self>, f: F) -> Task<F::Output>
+ where
+ F: Future + Send + 'static,
+ F::Output: Send + 'static,
+ {
+ let raw = Arc::downgrade(self);
+ let schedule = move |runnable| {
+ if let Some(r) = raw.upgrade() {
+ r.queue.push_back(runnable);
+ r.wake();
+ }
+ };
+ let (runnable, task) = async_task::spawn(f, schedule);
+ runnable.schedule();
+ task
+ }
+
+ fn spawn_local<F>(self: &Arc<Self>, f: F) -> Task<F::Output>
+ where
+ F: Future + 'static,
+ F::Output: 'static,
+ {
+ let raw = Arc::downgrade(self);
+ let schedule = move |runnable| {
+ if let Some(r) = raw.upgrade() {
+ r.queue.push_back(runnable);
+ r.wake();
+ }
+ };
+ let (runnable, task) = async_task::spawn_local(f, schedule);
+ runnable.schedule();
+ task
+ }
+
+ fn spawn_blocking<F, R>(self: &Arc<Self>, f: F) -> Task<R>
+ where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+ {
+ self.blocking_pool.spawn(f)
+ }
+
+ fn run<F: Future>(&self, cx: &mut Context, done: F) -> Result<F::Output> {
+ let events = EpollEvents::new();
+ pin_mut!(done);
+
+ loop {
+ self.state.store(PROCESSING, Ordering::Release);
+ for runnable in self.queue.iter() {
+ runnable.run();
+ }
+
+ if let Poll::Ready(val) = done.as_mut().poll(cx) {
+ return Ok(val);
+ }
+
+ let oldstate = self.state.compare_exchange(
+ PROCESSING,
+ WAITING,
+ Ordering::Acquire,
+ Ordering::Acquire,
+ );
+ if let Err(oldstate) = oldstate {
+ debug_assert_eq!(oldstate, WOKEN);
+ // One or more futures have become runnable.
+ continue;
+ }
+
+ let events = self
+ .poll_ctx
+ .wait(&events)
+ .map_err(Error::PollContextError)?;
+
+ // Set the state back to PROCESSING to prevent any tasks woken up by the loop below from
+ // writing to the eventfd.
+ self.state.store(PROCESSING, Ordering::Release);
+ for e in events.iter() {
+ let token = e.token();
+ let mut ops = self.ops.lock();
+
+ // The op could have been canceled and removed by another thread so ignore it if it
+ // doesn't exist.
+ if let Some(op) = ops.get_mut(token) {
+ let (file, waker) = match mem::replace(op, OpStatus::Completed) {
+ OpStatus::Pending(OpData { file, waker }) => (file, waker),
+ OpStatus::Completed => panic!("poll operation completed more than once"),
+ };
+
+ mem::drop(ops);
+
+ self.poll_ctx
+ .delete(&file)
+ .map_err(Error::PollContextError)?;
+
+ if let Some(waker) = waker {
+ waker.wake();
+ }
+ }
+ }
+ }
+ }
+
+ fn is_ready(&self, token: &WakerToken, cx: &mut Context) -> bool {
+ let mut ops = self.ops.lock();
+
+ let op = ops
+ .get_mut(token.0)
+ .expect("`is_ready` called on unknown operation");
+ match op {
+ OpStatus::Pending(data) => {
+ data.waker = Some(cx.waker().clone());
+ false
+ }
+ OpStatus::Completed => {
+ ops.remove(token.0);
+ true
+ }
+ }
+ }
+
+ // Remove the waker for the given token if it hasn't fired yet.
+ fn cancel_operation(&self, token: WakerToken) -> Result<()> {
+ match self.ops.lock().remove(token.0) {
+ OpStatus::Pending(data) => self
+ .poll_ctx
+ .delete(&data.file)
+ .map_err(Error::PollContextError),
+ OpStatus::Completed => Ok(()),
+ }
+ }
+}
+
+impl WeakWake for RawExecutor {
+ fn wake_by_ref(weak_self: &Weak<Self>) {
+ if let Some(arc_self) = weak_self.upgrade() {
+ RawExecutor::wake(&arc_self);
+ }
+ }
+}
+
+impl Drop for RawExecutor {
+ fn drop(&mut self) {
+ // Wake up the notify_task. We set the state to WAITING here so that wake() will write to
+ // the eventfd.
+ self.state.store(WAITING, Ordering::Release);
+ self.wake();
+
+ // Wake up any futures still waiting on poll operations as they are just going to get an
+ // ExecutorGone error now.
+ for op in self.ops.get_mut().drain() {
+ match op {
+ OpStatus::Pending(mut data) => {
+ if let Some(waker) = data.waker.take() {
+ waker.wake();
+ }
+
+ if let Err(e) = self.poll_ctx.delete(&data.file) {
+ warn!("Failed to remove file from EpollCtx: {}", e);
+ }
+ }
+ OpStatus::Completed => {}
+ }
+ }
+
+ // Now run the executor one more time to drive any remaining futures to completion.
+ let waker = noop_waker();
+ let mut cx = Context::from_waker(&waker);
+ if let Err(e) = self.run(&mut cx, async {}) {
+ warn!("Failed to drive FdExecutor to completion: {}", e);
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct FdExecutor {
+ raw: Arc<RawExecutor>,
+}
+
+impl FdExecutor {
+ pub fn new() -> Result<FdExecutor> {
+ let notify = EventFd::new().map_err(Error::CreateEventFd)?;
+ let raw = notify
+ .try_clone()
+ .map_err(Error::CloneEventFd)
+ .and_then(RawExecutor::new)
+ .map(Arc::new)?;
+
+ raw.spawn(notify_task(notify, Arc::downgrade(&raw)))
+ .detach();
+
+ Ok(FdExecutor { raw })
+ }
+
+ pub fn spawn<F>(&self, f: F) -> Task<F::Output>
+ where
+ F: Future + Send + 'static,
+ F::Output: Send + 'static,
+ {
+ self.raw.spawn(f)
+ }
+
+ pub fn spawn_local<F>(&self, f: F) -> Task<F::Output>
+ where
+ F: Future + 'static,
+ F::Output: 'static,
+ {
+ self.raw.spawn_local(f)
+ }
+
+ pub fn spawn_blocking<F, R>(&self, f: F) -> Task<R>
+ where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+ {
+ self.raw.spawn_blocking(f)
+ }
+
+ pub fn run(&self) -> Result<()> {
+ let waker = new_waker(Arc::downgrade(&self.raw));
+ let mut cx = Context::from_waker(&waker);
+
+ self.raw.run(&mut cx, super::empty::<()>())
+ }
+
+ pub fn run_until<F: Future>(&self, f: F) -> Result<F::Output> {
+ let waker = new_waker(Arc::downgrade(&self.raw));
+ let mut ctx = Context::from_waker(&waker);
+
+ self.raw.run(&mut ctx, f)
+ }
+
+ pub(crate) fn register_source<F: AsRawFd>(&self, f: F) -> Result<RegisteredSource<F>> {
+ add_fd_flags(f.as_raw_fd(), libc::O_NONBLOCK).map_err(Error::SettingNonBlocking)?;
+ Ok(RegisteredSource {
+ source: f,
+ ex: Arc::downgrade(&self.raw),
+ })
+ }
+}
+
+// Used to `dup` the FDs passed to the executor so there is a guarantee they aren't closed while
+// waiting in TLS to be added to the main polling context.
+unsafe fn dup_fd(fd: RawFd) -> Result<RawFd> {
+ let ret = libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0);
+ if ret < 0 {
+ Err(Error::DuplicatingFd(sys_util::Error::last()))
+ } else {
+ Ok(ret)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::{
+ cell::RefCell,
+ io::{Read, Write},
+ rc::Rc,
+ };
+
+ use futures::future::Either;
+
+ use super::*;
+
+ #[test]
+ fn test_it() {
+ async fn do_test(ex: &FdExecutor) {
+ let (r, _w) = sys_util::pipe(true).unwrap();
+ let done = Box::pin(async { 5usize });
+ let source = ex.register_source(r).unwrap();
+ let pending = source.wait_readable().unwrap();
+ match futures::future::select(pending, done).await {
+ Either::Right((5, pending)) => std::mem::drop(pending),
+ _ => panic!("unexpected select result"),
+ }
+ }
+
+ let ex = FdExecutor::new().unwrap();
+ ex.run_until(do_test(&ex)).unwrap();
+
+ // Example of starting the framework and running a future:
+ async fn my_async(x: Rc<RefCell<u64>>) {
+ x.replace(4);
+ }
+
+ let x = Rc::new(RefCell::new(0));
+ super::super::run_one_poll(my_async(x.clone())).unwrap();
+ assert_eq!(*x.borrow(), 4);
+ }
+
+ #[test]
+ fn drop_before_completion() {
+ const VALUE: u64 = 0x66ae_cb65_12fb_d260;
+
+ async fn write_value(mut tx: File) {
+ let buf = VALUE.to_ne_bytes();
+ tx.write_all(&buf[..]).expect("Failed to write to pipe");
+ }
+
+ async fn check_op(op: PendingOperation) {
+ let err = op.await.expect_err("Task completed successfully");
+ match err {
+ Error::ExecutorGone => {}
+ e => panic!("Unexpected error from task: {}", e),
+ }
+ }
+
+ let (mut rx, tx) = sys_util::pipe(true).expect("Pipe failed");
+
+ let ex = FdExecutor::new().unwrap();
+
+ let source = ex.register_source(tx.try_clone().unwrap()).unwrap();
+ let op = source.wait_writable().unwrap();
+
+ ex.spawn_local(write_value(tx)).detach();
+ ex.spawn_local(check_op(op)).detach();
+
+ // Now drop the executor. It should still run until the write to the pipe is complete.
+ mem::drop(ex);
+
+ let mut buf = 0u64.to_ne_bytes();
+ rx.read_exact(&mut buf[..])
+ .expect("Failed to read from pipe");
+
+ assert_eq!(u64::from_ne_bytes(buf), VALUE);
+ }
+}
diff --git a/common/cros_async/src/io_ext.rs b/common/cros_async/src/io_ext.rs
new file mode 100644
index 000000000..010b716e3
--- /dev/null
+++ b/common/cros_async/src/io_ext.rs
@@ -0,0 +1,479 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! # `IoSourceExt`
+//!
+//! User functions to asynchronously access files.
+//! Using `IoSource` directly is inconvenient and requires dealing with state
+//! machines for the backing uring, future libraries, etc. `IoSourceExt` instead
+//! provides users with a future that can be `await`ed from async context.
+//!
+//! Each member of `IoSourceExt` returns a future for the supported operation. One or more
+//! operation can be pending at a time.
+//!
+//! Operations can only access memory in a `Vec` or an implementor of `BackingMemory`. See the
+//! `URingExecutor` documentation for an explaination of why.
+
+use std::{
+ fs::File,
+ io,
+ ops::{Deref, DerefMut},
+ os::unix::io::{AsRawFd, RawFd},
+ sync::Arc,
+};
+
+use async_trait::async_trait;
+use remain::sorted;
+use sys_util::net::UnixSeqpacket;
+use thiserror::Error as ThisError;
+
+use super::{BackingMemory, MemRegion};
+
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum Error {
+ /// An error with a polled(FD) source.
+ #[error("An error with a poll source: {0}")]
+ Poll(#[from] super::poll_source::Error),
+ /// An error with a uring source.
+ #[error("An error with a uring source: {0}")]
+ Uring(#[from] super::uring_executor::Error),
+}
+pub type Result<T> = std::result::Result<T, Error>;
+
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ use Error::*;
+ match e {
+ Poll(e) => e.into(),
+ Uring(e) => e.into(),
+ }
+ }
+}
+
+/// Ergonomic methods for async reads.
+#[async_trait(?Send)]
+pub trait ReadAsync {
+ /// Reads from the iosource at `file_offset` and fill the given `vec`.
+ async fn read_to_vec<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ vec: Vec<u8>,
+ ) -> Result<(usize, Vec<u8>)>;
+
+ /// Reads to the given `mem` at the given offsets from the file starting at `file_offset`.
+ async fn read_to_mem<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ mem: Arc<dyn BackingMemory + Send + Sync>,
+ mem_offsets: &'a [MemRegion],
+ ) -> Result<usize>;
+
+ /// Wait for the FD of `self` to be readable.
+ async fn wait_readable(&self) -> Result<()>;
+
+ /// Reads a single u64 from the current offset.
+ async fn read_u64(&self) -> Result<u64>;
+}
+
+/// Ergonomic methods for async writes.
+#[async_trait(?Send)]
+pub trait WriteAsync {
+ /// Writes from the given `vec` to the file starting at `file_offset`.
+ async fn write_from_vec<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ vec: Vec<u8>,
+ ) -> Result<(usize, Vec<u8>)>;
+
+ /// Writes from the given `mem` from the given offsets to the file starting at `file_offset`.
+ async fn write_from_mem<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ mem: Arc<dyn BackingMemory + Send + Sync>,
+ mem_offsets: &'a [MemRegion],
+ ) -> Result<usize>;
+
+ /// See `fallocate(2)`. Note this op is synchronous when using the Polled backend.
+ async fn fallocate(&self, file_offset: u64, len: u64, mode: u32) -> Result<()>;
+
+ /// Sync all completed write operations to the backing storage.
+ async fn fsync(&self) -> Result<()>;
+}
+
+/// Subtrait for general async IO.
+#[async_trait(?Send)]
+pub trait IoSourceExt<F>: ReadAsync + WriteAsync {
+ /// Yields the underlying IO source.
+ fn into_source(self: Box<Self>) -> F;
+
+ /// Provides a mutable ref to the underlying IO source.
+ fn as_source_mut(&mut self) -> &mut F;
+
+ /// Provides a ref to the underlying IO source.
+ fn as_source(&self) -> &F;
+}
+
+/// Marker trait signifying that the implementor is suitable for use with
+/// cros_async. Examples of this include File, and sys_util::net::UnixSeqpacket.
+///
+/// (Note: it'd be really nice to implement a TryFrom for any implementors, and
+/// remove our factory functions. Unfortunately
+/// <https://github.com/rust-lang/rust/issues/50133> makes that too painful.)
+pub trait IntoAsync: AsRawFd {}
+
+impl IntoAsync for File {}
+impl IntoAsync for UnixSeqpacket {}
+impl IntoAsync for &UnixSeqpacket {}
+
+/// Simple wrapper struct to implement IntoAsync on foreign types.
+pub struct AsyncWrapper<T>(T);
+
+impl<T> AsyncWrapper<T> {
+ /// Create a new `AsyncWrapper` that wraps `val`.
+ pub fn new(val: T) -> Self {
+ AsyncWrapper(val)
+ }
+
+ /// Consumes the `AsyncWrapper`, returning the inner struct.
+ pub fn into_inner(self) -> T {
+ self.0
+ }
+}
+
+impl<T> Deref for AsyncWrapper<T> {
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ &self.0
+ }
+}
+
+impl<T> DerefMut for AsyncWrapper<T> {
+ fn deref_mut(&mut self) -> &mut T {
+ &mut self.0
+ }
+}
+
+impl<T: AsRawFd> AsRawFd for AsyncWrapper<T> {
+ fn as_raw_fd(&self) -> RawFd {
+ self.0.as_raw_fd()
+ }
+}
+
+impl<T: AsRawFd> IntoAsync for AsyncWrapper<T> {}
+
+#[cfg(test)]
+mod tests {
+ use std::{
+ fs::{File, OpenOptions},
+ future::Future,
+ os::unix::io::AsRawFd,
+ pin::Pin,
+ sync::Arc,
+ task::{Context, Poll, Waker},
+ thread,
+ };
+
+ use sync::Mutex;
+
+ use super::{
+ super::{
+ executor::{async_poll_from, async_uring_from},
+ mem::VecIoWrapper,
+ uring_executor::use_uring,
+ Executor, FdExecutor, MemRegion, PollSource, URingExecutor, UringSource,
+ },
+ *,
+ };
+
+ struct State {
+ should_quit: bool,
+ waker: Option<Waker>,
+ }
+
+ impl State {
+ fn wake(&mut self) {
+ self.should_quit = true;
+ let waker = self.waker.take();
+
+ if let Some(waker) = waker {
+ waker.wake();
+ }
+ }
+ }
+
+ struct Quit {
+ state: Arc<Mutex<State>>,
+ }
+
+ impl Future for Quit {
+ type Output = ();
+
+ fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> {
+ let mut state = self.state.lock();
+ if state.should_quit {
+ return Poll::Ready(());
+ }
+
+ state.waker = Some(cx.waker().clone());
+ Poll::Pending
+ }
+ }
+
+ #[test]
+ fn await_uring_from_poll() {
+ if !use_uring() {
+ return;
+ }
+ // Start a uring operation and then await the result from an FdExecutor.
+ async fn go(source: UringSource<File>) {
+ let v = vec![0xa4u8; 16];
+ let (len, vec) = source.read_to_vec(None, v).await.unwrap();
+ assert_eq!(len, 16);
+ assert!(vec.iter().all(|&b| b == 0));
+ }
+
+ let state = Arc::new(Mutex::new(State {
+ should_quit: false,
+ waker: None,
+ }));
+
+ let uring_ex = URingExecutor::new().unwrap();
+ let f = File::open("/dev/zero").unwrap();
+ let source = UringSource::new(f, &uring_ex).unwrap();
+
+ let quit = Quit {
+ state: state.clone(),
+ };
+ let handle = thread::spawn(move || uring_ex.run_until(quit));
+
+ let poll_ex = FdExecutor::new().unwrap();
+ poll_ex.run_until(go(source)).unwrap();
+
+ state.lock().wake();
+ handle.join().unwrap().unwrap();
+ }
+
+ #[test]
+ fn await_poll_from_uring() {
+ if !use_uring() {
+ return;
+ }
+ // Start a poll operation and then await the result from a URingExecutor.
+ async fn go(source: PollSource<File>) {
+ let v = vec![0x2cu8; 16];
+ let (len, vec) = source.read_to_vec(None, v).await.unwrap();
+ assert_eq!(len, 16);
+ assert!(vec.iter().all(|&b| b == 0));
+ }
+
+ let state = Arc::new(Mutex::new(State {
+ should_quit: false,
+ waker: None,
+ }));
+
+ let poll_ex = FdExecutor::new().unwrap();
+ let f = File::open("/dev/zero").unwrap();
+ let source = PollSource::new(f, &poll_ex).unwrap();
+
+ let quit = Quit {
+ state: state.clone(),
+ };
+ let handle = thread::spawn(move || poll_ex.run_until(quit));
+
+ let uring_ex = URingExecutor::new().unwrap();
+ uring_ex.run_until(go(source)).unwrap();
+
+ state.lock().wake();
+ handle.join().unwrap().unwrap();
+ }
+
+ #[test]
+ fn readvec() {
+ if !use_uring() {
+ return;
+ }
+ async fn go<F: AsRawFd>(async_source: Box<dyn IoSourceExt<F>>) {
+ let v = vec![0x55u8; 32];
+ let v_ptr = v.as_ptr();
+ let ret = async_source.read_to_vec(None, v).await.unwrap();
+ assert_eq!(ret.0, 32);
+ let ret_v = ret.1;
+ assert_eq!(v_ptr, ret_v.as_ptr());
+ assert!(ret_v.iter().all(|&b| b == 0));
+ }
+
+ let f = File::open("/dev/zero").unwrap();
+ let uring_ex = URingExecutor::new().unwrap();
+ let uring_source = async_uring_from(f, &uring_ex).unwrap();
+ uring_ex.run_until(go(uring_source)).unwrap();
+
+ let f = File::open("/dev/zero").unwrap();
+ let poll_ex = FdExecutor::new().unwrap();
+ let poll_source = async_poll_from(f, &poll_ex).unwrap();
+ poll_ex.run_until(go(poll_source)).unwrap();
+ }
+
+ #[test]
+ fn writevec() {
+ if !use_uring() {
+ return;
+ }
+ async fn go<F: AsRawFd>(async_source: Box<dyn IoSourceExt<F>>) {
+ let v = vec![0x55u8; 32];
+ let v_ptr = v.as_ptr();
+ let ret = async_source.write_from_vec(None, v).await.unwrap();
+ assert_eq!(ret.0, 32);
+ let ret_v = ret.1;
+ assert_eq!(v_ptr, ret_v.as_ptr());
+ }
+
+ let f = OpenOptions::new().write(true).open("/dev/null").unwrap();
+ let ex = URingExecutor::new().unwrap();
+ let uring_source = async_uring_from(f, &ex).unwrap();
+ ex.run_until(go(uring_source)).unwrap();
+
+ let f = OpenOptions::new().write(true).open("/dev/null").unwrap();
+ let poll_ex = FdExecutor::new().unwrap();
+ let poll_source = async_poll_from(f, &poll_ex).unwrap();
+ poll_ex.run_until(go(poll_source)).unwrap();
+ }
+
+ #[test]
+ fn readmem() {
+ if !use_uring() {
+ return;
+ }
+ async fn go<F: AsRawFd>(async_source: Box<dyn IoSourceExt<F>>) {
+ let mem = Arc::new(VecIoWrapper::from(vec![0x55u8; 8192]));
+ let ret = async_source
+ .read_to_mem(
+ None,
+ Arc::<VecIoWrapper>::clone(&mem),
+ &[
+ MemRegion { offset: 0, len: 32 },
+ MemRegion {
+ offset: 200,
+ len: 56,
+ },
+ ],
+ )
+ .await
+ .unwrap();
+ assert_eq!(ret, 32 + 56);
+ let vec: Vec<u8> = match Arc::try_unwrap(mem) {
+ Ok(v) => v.into(),
+ Err(_) => panic!("Too many vec refs"),
+ };
+ assert!(vec.iter().take(32).all(|&b| b == 0));
+ assert!(vec.iter().skip(32).take(168).all(|&b| b == 0x55));
+ assert!(vec.iter().skip(200).take(56).all(|&b| b == 0));
+ assert!(vec.iter().skip(256).all(|&b| b == 0x55));
+ }
+
+ let f = File::open("/dev/zero").unwrap();
+ let ex = URingExecutor::new().unwrap();
+ let uring_source = async_uring_from(f, &ex).unwrap();
+ ex.run_until(go(uring_source)).unwrap();
+
+ let f = File::open("/dev/zero").unwrap();
+ let poll_ex = FdExecutor::new().unwrap();
+ let poll_source = async_poll_from(f, &poll_ex).unwrap();
+ poll_ex.run_until(go(poll_source)).unwrap();
+ }
+
+ #[test]
+ fn writemem() {
+ if !use_uring() {
+ return;
+ }
+ async fn go<F: AsRawFd>(async_source: Box<dyn IoSourceExt<F>>) {
+ let mem = Arc::new(VecIoWrapper::from(vec![0x55u8; 8192]));
+ let ret = async_source
+ .write_from_mem(
+ None,
+ Arc::<VecIoWrapper>::clone(&mem),
+ &[MemRegion { offset: 0, len: 32 }],
+ )
+ .await
+ .unwrap();
+ assert_eq!(ret, 32);
+ }
+
+ let f = OpenOptions::new().write(true).open("/dev/null").unwrap();
+ let ex = URingExecutor::new().unwrap();
+ let uring_source = async_uring_from(f, &ex).unwrap();
+ ex.run_until(go(uring_source)).unwrap();
+
+ let f = OpenOptions::new().write(true).open("/dev/null").unwrap();
+ let poll_ex = FdExecutor::new().unwrap();
+ let poll_source = async_poll_from(f, &poll_ex).unwrap();
+ poll_ex.run_until(go(poll_source)).unwrap();
+ }
+
+ #[test]
+ fn read_u64s() {
+ if !use_uring() {
+ return;
+ }
+ async fn go(async_source: File, ex: URingExecutor) -> u64 {
+ let source = async_uring_from(async_source, &ex).unwrap();
+ source.read_u64().await.unwrap()
+ }
+
+ let f = File::open("/dev/zero").unwrap();
+ let ex = URingExecutor::new().unwrap();
+ let val = ex.run_until(go(f, ex.clone())).unwrap();
+ assert_eq!(val, 0);
+ }
+
+ #[test]
+ fn read_eventfds() {
+ if !use_uring() {
+ return;
+ }
+ use sys_util::EventFd;
+
+ async fn go<F: AsRawFd>(source: Box<dyn IoSourceExt<F>>) -> u64 {
+ source.read_u64().await.unwrap()
+ }
+
+ let eventfd = EventFd::new().unwrap();
+ eventfd.write(0x55).unwrap();
+ let ex = URingExecutor::new().unwrap();
+ let uring_source = async_uring_from(eventfd, &ex).unwrap();
+ let val = ex.run_until(go(uring_source)).unwrap();
+ assert_eq!(val, 0x55);
+
+ let eventfd = EventFd::new().unwrap();
+ eventfd.write(0xaa).unwrap();
+ let poll_ex = FdExecutor::new().unwrap();
+ let poll_source = async_poll_from(eventfd, &poll_ex).unwrap();
+ let val = poll_ex.run_until(go(poll_source)).unwrap();
+ assert_eq!(val, 0xaa);
+ }
+
+ #[test]
+ fn fsync() {
+ if !use_uring() {
+ return;
+ }
+ async fn go<F: AsRawFd>(source: Box<dyn IoSourceExt<F>>) {
+ let v = vec![0x55u8; 32];
+ let v_ptr = v.as_ptr();
+ let ret = source.write_from_vec(None, v).await.unwrap();
+ assert_eq!(ret.0, 32);
+ let ret_v = ret.1;
+ assert_eq!(v_ptr, ret_v.as_ptr());
+ source.fsync().await.unwrap();
+ }
+
+ let f = tempfile::tempfile().unwrap();
+ let ex = Executor::new().unwrap();
+ let source = ex.async_from(f).unwrap();
+
+ ex.run_until(go(source)).unwrap();
+ }
+}
diff --git a/common/cros_async/src/lib.rs b/common/cros_async/src/lib.rs
new file mode 100644
index 000000000..b353a2e03
--- /dev/null
+++ b/common/cros_async/src/lib.rs
@@ -0,0 +1,540 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! An Executor and future combinators based on operations that block on file descriptors.
+//!
+//! This crate is meant to be used with the `futures-rs` crate that provides further combinators
+//! and utility functions to combine and manage futures. All futures will run until they block on a
+//! file descriptor becoming readable or writable. Facilities are provided to register future
+//! wakers based on such events.
+//!
+//! # Running top-level futures.
+//!
+//! Use helper functions based the desired behavior of your application.
+//!
+//! ## Running one future.
+//!
+//! If there is only one top-level future to run, use the [`run_one`](fn.run_one.html) function.
+//!
+//! ## Completing one of several futures.
+//!
+//! If there are several top level tasks that should run until any one completes, use the "select"
+//! family of executor constructors. These return an [`Executor`](trait.Executor.html) whose `run`
+//! function will return when the first future completes. The uncompleted futures will also be
+//! returned so they can be run further or otherwise cleaned up. These functions are inspired by
+//! the `select_all` function from futures-rs, but built to be run inside an FD based executor and
+//! to poll only when necessary. See the docs for [`select2`](fn.select2.html),
+//! [`select3`](fn.select3.html), [`select4`](fn.select4.html), and [`select5`](fn.select5.html).
+//!
+//! ## Completing all of several futures.
+//!
+//! If there are several top level tasks that all need to be completed, use the "complete" family
+//! of executor constructors. These return an [`Executor`](trait.Executor.html) whose `run`
+//! function will return only once all the futures passed to it have completed. These functions are
+//! inspired by the `join_all` function from futures-rs, but built to be run inside an FD based
+//! executor and to poll only when necessary. See the docs for [`complete2`](fn.complete2.html),
+//! [`complete3`](fn.complete3.html), [`complete4`](fn.complete4.html), and
+//! [`complete5`](fn.complete5.html).
+//!
+//! # Implementing new FD-based futures.
+//!
+//! For URing implementations should provide an implementation of the `IoSource` trait.
+//! For the FD executor, new futures can use the existing ability to poll a source to build async
+//! functionality on top of.
+//!
+//! # Implementations
+//!
+//! Currently there are two paths for using the asynchronous IO. One uses a PollContext and drivers
+//! futures based on the FDs signaling they are ready for the opteration. This method will exist so
+//! long as kernels < 5.4 are supported.
+//! The other method submits operations to io_uring and is signaled when they complete. This is more
+//! efficient, but only supported on kernel 5.4+.
+//! If `IoSourceExt::new` is used to interface with async IO, then the correct backend will be chosen
+//! automatically.
+//!
+//! # Examples
+//!
+//! See the docs for `IoSourceExt` if support for kernels <5.4 is required. Focus on `UringSource` if
+//! all systems have support for io_uring.
+
+pub mod audio_streams_async;
+mod blocking;
+mod complete;
+mod event;
+mod executor;
+mod fd_executor;
+mod io_ext;
+pub mod mem;
+mod poll_source;
+mod queue;
+mod select;
+pub mod sync;
+mod timer;
+mod uring_executor;
+mod uring_source;
+mod waker;
+
+pub use blocking::{block_on, BlockingPool};
+pub use event::EventAsync;
+pub use executor::Executor;
+pub use fd_executor::FdExecutor;
+pub use io_ext::{
+ AsyncWrapper, Error as AsyncError, IntoAsync, IoSourceExt, ReadAsync, Result as AsyncResult,
+ WriteAsync,
+};
+pub use mem::{BackingMemory, MemRegion};
+pub use poll_source::PollSource;
+pub use select::SelectResult;
+pub use sys_util;
+pub use timer::TimerAsync;
+pub use uring_executor::URingExecutor;
+pub use uring_source::UringSource;
+
+use std::{
+ future::Future,
+ io,
+ marker::PhantomData,
+ pin::Pin,
+ task::{Context, Poll},
+};
+
+use remain::sorted;
+use thiserror::Error as ThisError;
+
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum Error {
+ /// Error from the FD executor.
+ #[error("Failure in the FD executor: {0}")]
+ FdExecutor(fd_executor::Error),
+ /// Error from TimerFd.
+ #[error("Failure in TimerAsync: {0}")]
+ TimerAsync(AsyncError),
+ /// Error from TimerFd.
+ #[error("Failure in TimerFd: {0}")]
+ TimerFd(sys_util::Error),
+ /// Error from the uring executor.
+ #[error("Failure in the uring executor: {0}")]
+ URingExecutor(uring_executor::Error),
+}
+pub type Result<T> = std::result::Result<T, Error>;
+
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ use Error::*;
+ match e {
+ FdExecutor(e) => e.into(),
+ URingExecutor(e) => e.into(),
+ TimerFd(e) => e.into(),
+ TimerAsync(e) => e.into(),
+ }
+ }
+}
+
+// A Future that never completes.
+pub struct Empty<T> {
+ phantom: PhantomData<T>,
+}
+
+impl<T> Future for Empty<T> {
+ type Output = T;
+
+ fn poll(self: Pin<&mut Self>, _: &mut Context) -> Poll<T> {
+ Poll::Pending
+ }
+}
+
+pub fn empty<T>() -> Empty<T> {
+ Empty {
+ phantom: PhantomData,
+ }
+}
+
+/// Creates an Executor that runs one future to completion.
+///
+/// # Example
+///
+/// ```
+/// use cros_async::run_one;
+///
+/// let fut = async { 55 };
+/// assert_eq!(55, run_one(fut).unwrap());
+/// ```
+pub fn run_one<F: Future>(fut: F) -> Result<F::Output> {
+ if uring_executor::use_uring() {
+ run_one_uring(fut)
+ } else {
+ run_one_poll(fut)
+ }
+}
+
+/// Creates a URingExecutor that runs one future to completion.
+///
+/// # Example
+///
+/// ```
+/// use cros_async::run_one_uring;
+///
+/// let fut = async { 55 };
+/// assert_eq!(55, run_one_uring(fut).unwrap());
+/// ```
+pub fn run_one_uring<F: Future>(fut: F) -> Result<F::Output> {
+ URingExecutor::new()
+ .and_then(|ex| ex.run_until(fut))
+ .map_err(Error::URingExecutor)
+}
+
+/// Creates a FdExecutor that runs one future to completion.
+///
+/// # Example
+///
+/// ```
+/// use cros_async::run_one_poll;
+///
+/// let fut = async { 55 };
+/// assert_eq!(55, run_one_poll(fut).unwrap());
+/// ```
+pub fn run_one_poll<F: Future>(fut: F) -> Result<F::Output> {
+ FdExecutor::new()
+ .and_then(|ex| ex.run_until(fut))
+ .map_err(Error::FdExecutor)
+}
+
+// Select helpers to run until any future completes.
+
+/// Creates a combinator that runs the two given futures until one completes, returning a tuple
+/// containing the result of the finished future and the still pending future.
+///
+/// # Example
+///
+/// ```
+/// use cros_async::{SelectResult, select2, run_one};
+/// use futures::future::pending;
+/// use futures::pin_mut;
+///
+/// let first = async {5};
+/// let second = async {let () = pending().await;};
+/// pin_mut!(first);
+/// pin_mut!(second);
+/// match run_one(select2(first, second)) {
+/// Ok((SelectResult::Finished(5), SelectResult::Pending(_second))) => (),
+/// _ => panic!("Select didn't return the first future"),
+/// };
+/// ```
+pub async fn select2<F1: Future + Unpin, F2: Future + Unpin>(
+ f1: F1,
+ f2: F2,
+) -> (SelectResult<F1>, SelectResult<F2>) {
+ select::Select2::new(f1, f2).await
+}
+
+/// Creates a combinator that runs the three given futures until one or more completes, returning a
+/// tuple containing the result of the finished future(s) and the still pending future(s).
+///
+/// # Example
+///
+/// ```
+/// use cros_async::{SelectResult, select3, run_one};
+/// use futures::future::pending;
+/// use futures::pin_mut;
+///
+/// let first = async {4};
+/// let second = async {let () = pending().await;};
+/// let third = async {5};
+/// pin_mut!(first);
+/// pin_mut!(second);
+/// pin_mut!(third);
+/// match run_one(select3(first, second, third)) {
+/// Ok((SelectResult::Finished(4),
+/// SelectResult::Pending(_second),
+/// SelectResult::Finished(5))) => (),
+/// _ => panic!("Select didn't return the futures"),
+/// };
+/// ```
+pub async fn select3<F1: Future + Unpin, F2: Future + Unpin, F3: Future + Unpin>(
+ f1: F1,
+ f2: F2,
+ f3: F3,
+) -> (SelectResult<F1>, SelectResult<F2>, SelectResult<F3>) {
+ select::Select3::new(f1, f2, f3).await
+}
+
+/// Creates a combinator that runs the four given futures until one or more completes, returning a
+/// tuple containing the result of the finished future(s) and the still pending future(s).
+///
+/// # Example
+///
+/// ```
+/// use cros_async::{SelectResult, select4, run_one};
+/// use futures::future::pending;
+/// use futures::pin_mut;
+///
+/// let first = async {4};
+/// let second = async {let () = pending().await;};
+/// let third = async {5};
+/// let fourth = async {let () = pending().await;};
+/// pin_mut!(first);
+/// pin_mut!(second);
+/// pin_mut!(third);
+/// pin_mut!(fourth);
+/// match run_one(select4(first, second, third, fourth)) {
+/// Ok((SelectResult::Finished(4), SelectResult::Pending(_second),
+/// SelectResult::Finished(5), SelectResult::Pending(_fourth))) => (),
+/// _ => panic!("Select didn't return the futures"),
+/// };
+/// ```
+pub async fn select4<
+ F1: Future + Unpin,
+ F2: Future + Unpin,
+ F3: Future + Unpin,
+ F4: Future + Unpin,
+>(
+ f1: F1,
+ f2: F2,
+ f3: F3,
+ f4: F4,
+) -> (
+ SelectResult<F1>,
+ SelectResult<F2>,
+ SelectResult<F3>,
+ SelectResult<F4>,
+) {
+ select::Select4::new(f1, f2, f3, f4).await
+}
+
+/// Creates a combinator that runs the five given futures until one or more completes, returning a
+/// tuple containing the result of the finished future(s) and the still pending future(s).
+///
+/// # Example
+///
+/// ```
+/// use cros_async::{SelectResult, select5, run_one};
+/// use futures::future::pending;
+/// use futures::pin_mut;
+///
+/// let first = async {4};
+/// let second = async {let () = pending().await;};
+/// let third = async {5};
+/// let fourth = async {let () = pending().await;};
+/// let fifth = async {6};
+/// pin_mut!(first);
+/// pin_mut!(second);
+/// pin_mut!(third);
+/// pin_mut!(fourth);
+/// pin_mut!(fifth);
+/// match run_one(select5(first, second, third, fourth, fifth)) {
+/// Ok((SelectResult::Finished(4), SelectResult::Pending(_second),
+/// SelectResult::Finished(5), SelectResult::Pending(_fourth),
+/// SelectResult::Finished(6))) => (),
+/// _ => panic!("Select didn't return the futures"),
+/// };
+/// ```
+pub async fn select5<
+ F1: Future + Unpin,
+ F2: Future + Unpin,
+ F3: Future + Unpin,
+ F4: Future + Unpin,
+ F5: Future + Unpin,
+>(
+ f1: F1,
+ f2: F2,
+ f3: F3,
+ f4: F4,
+ f5: F5,
+) -> (
+ SelectResult<F1>,
+ SelectResult<F2>,
+ SelectResult<F3>,
+ SelectResult<F4>,
+ SelectResult<F5>,
+) {
+ select::Select5::new(f1, f2, f3, f4, f5).await
+}
+
+/// Creates a combinator that runs the six given futures until one or more completes, returning a
+/// tuple containing the result of the finished future(s) and the still pending future(s).
+///
+/// # Example
+///
+/// ```
+/// use cros_async::{SelectResult, select6, run_one};
+/// use futures::future::pending;
+/// use futures::pin_mut;
+///
+/// let first = async {1};
+/// let second = async {let () = pending().await;};
+/// let third = async {3};
+/// let fourth = async {let () = pending().await;};
+/// let fifth = async {5};
+/// let sixth = async {6};
+/// pin_mut!(first);
+/// pin_mut!(second);
+/// pin_mut!(third);
+/// pin_mut!(fourth);
+/// pin_mut!(fifth);
+/// pin_mut!(sixth);
+/// match run_one(select6(first, second, third, fourth, fifth, sixth)) {
+/// Ok((SelectResult::Finished(1), SelectResult::Pending(_second),
+/// SelectResult::Finished(3), SelectResult::Pending(_fourth),
+/// SelectResult::Finished(5), SelectResult::Finished(6))) => (),
+/// _ => panic!("Select didn't return the futures"),
+/// };
+/// ```
+pub async fn select6<
+ F1: Future + Unpin,
+ F2: Future + Unpin,
+ F3: Future + Unpin,
+ F4: Future + Unpin,
+ F5: Future + Unpin,
+ F6: Future + Unpin,
+>(
+ f1: F1,
+ f2: F2,
+ f3: F3,
+ f4: F4,
+ f5: F5,
+ f6: F6,
+) -> (
+ SelectResult<F1>,
+ SelectResult<F2>,
+ SelectResult<F3>,
+ SelectResult<F4>,
+ SelectResult<F5>,
+ SelectResult<F6>,
+) {
+ select::Select6::new(f1, f2, f3, f4, f5, f6).await
+}
+
+pub async fn select7<
+ F1: Future + Unpin,
+ F2: Future + Unpin,
+ F3: Future + Unpin,
+ F4: Future + Unpin,
+ F5: Future + Unpin,
+ F6: Future + Unpin,
+ F7: Future + Unpin,
+>(
+ f1: F1,
+ f2: F2,
+ f3: F3,
+ f4: F4,
+ f5: F5,
+ f6: F6,
+ f7: F7,
+) -> (
+ SelectResult<F1>,
+ SelectResult<F2>,
+ SelectResult<F3>,
+ SelectResult<F4>,
+ SelectResult<F5>,
+ SelectResult<F6>,
+ SelectResult<F7>,
+) {
+ select::Select7::new(f1, f2, f3, f4, f5, f6, f7).await
+}
+// Combination helpers to run until all futures are complete.
+
+/// Creates a combinator that runs the two given futures to completion, returning a tuple of the
+/// outputs each yields.
+///
+/// # Example
+///
+/// ```
+/// use cros_async::{complete2, run_one};
+///
+/// let first = async {5};
+/// let second = async {6};
+/// assert_eq!(run_one(complete2(first, second)).unwrap_or((0,0)), (5,6));
+/// ```
+pub async fn complete2<F1, F2>(f1: F1, f2: F2) -> (F1::Output, F2::Output)
+where
+ F1: Future,
+ F2: Future,
+{
+ complete::Complete2::new(f1, f2).await
+}
+
+/// Creates a combinator that runs the three given futures to completion, returning a tuple of the
+/// outputs each yields.
+///
+/// # Example
+///
+/// ```
+/// use cros_async::{complete3, run_one};
+///
+/// let first = async {5};
+/// let second = async {6};
+/// let third = async {7};
+/// assert_eq!(run_one(complete3(first, second, third)).unwrap_or((0,0,0)), (5,6,7));
+/// ```
+pub async fn complete3<F1, F2, F3>(f1: F1, f2: F2, f3: F3) -> (F1::Output, F2::Output, F3::Output)
+where
+ F1: Future,
+ F2: Future,
+ F3: Future,
+{
+ complete::Complete3::new(f1, f2, f3).await
+}
+
+/// Creates a combinator that runs the four given futures to completion, returning a tuple of the
+/// outputs each yields.
+///
+/// # Example
+///
+/// ```
+/// use cros_async::{complete4, run_one};
+///
+/// let first = async {5};
+/// let second = async {6};
+/// let third = async {7};
+/// let fourth = async {8};
+/// assert_eq!(run_one(complete4(first, second, third, fourth)).unwrap_or((0,0,0,0)), (5,6,7,8));
+/// ```
+pub async fn complete4<F1, F2, F3, F4>(
+ f1: F1,
+ f2: F2,
+ f3: F3,
+ f4: F4,
+) -> (F1::Output, F2::Output, F3::Output, F4::Output)
+where
+ F1: Future,
+ F2: Future,
+ F3: Future,
+ F4: Future,
+{
+ complete::Complete4::new(f1, f2, f3, f4).await
+}
+
+/// Creates a combinator that runs the five given futures to completion, returning a tuple of the
+/// outputs each yields.
+///
+/// # Example
+///
+/// ```
+/// use cros_async::{complete5, run_one};
+///
+/// let first = async {5};
+/// let second = async {6};
+/// let third = async {7};
+/// let fourth = async {8};
+/// let fifth = async {9};
+/// assert_eq!(run_one(complete5(first, second, third, fourth, fifth)).unwrap_or((0,0,0,0,0)),
+/// (5,6,7,8,9));
+/// ```
+pub async fn complete5<F1, F2, F3, F4, F5>(
+ f1: F1,
+ f2: F2,
+ f3: F3,
+ f4: F4,
+ f5: F5,
+) -> (F1::Output, F2::Output, F3::Output, F4::Output, F5::Output)
+where
+ F1: Future,
+ F2: Future,
+ F3: Future,
+ F4: Future,
+ F5: Future,
+{
+ complete::Complete5::new(f1, f2, f3, f4, f5).await
+}
diff --git a/common/cros_async/src/mem.rs b/common/cros_async/src/mem.rs
new file mode 100644
index 000000000..691e629fe
--- /dev/null
+++ b/common/cros_async/src/mem.rs
@@ -0,0 +1,98 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use data_model::VolatileSlice;
+use remain::sorted;
+use thiserror::Error as ThisError;
+
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum Error {
+ /// Invalid offset or length given for an iovec in backing memory.
+ #[error("Invalid offset/len for getting a slice from {0} with len {1}.")]
+ InvalidOffset(u64, usize),
+}
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// Used to index subslices of backing memory. Like an iovec, but relative to the start of the
+/// memory region instead of an absolute pointer.
+/// The backing memory referenced by the region can be an array, an mmapped file, or guest memory.
+/// The offset is a u64 to allow having file or guest offsets >4GB when run on a 32bit host.
+#[derive(Copy, Clone, Debug)]
+pub struct MemRegion {
+ pub offset: u64,
+ pub len: usize,
+}
+
+/// Trait for memory that can yeild both iovecs in to the backing memory.
+/// Must be OK to modify the backing memory without owning a mut able reference. For example,
+/// this is safe for GuestMemory and VolatileSlices in crosvm as those types guarantee they are
+/// dealt with as volatile.
+pub unsafe trait BackingMemory {
+ /// Returns VolatileSlice pointing to the backing memory. This is most commonly unsafe.
+ /// To implement this safely the implementor must guarantee that the backing memory can be
+ /// modified out of band without affecting safety guarantees.
+ fn get_volatile_slice(&self, mem_range: MemRegion) -> Result<VolatileSlice>;
+}
+
+/// Wrapper to be used for passing a Vec in as backing memory for asynchronous operations. The
+/// wrapper owns a Vec according to the borrow checker. It is loaning this vec out to the kernel(or
+/// other modifiers) through the `BackingMemory` trait. This allows multiple modifiers of the array
+/// in the `Vec` while this struct is alive. The data in the Vec is loaned to the kernel not the
+/// data structure itself, the length, capacity, and pointer to memory cannot be modified.
+/// To ensure that those operations can be done safely, no access is allowed to the `Vec`'s memory
+/// starting at the time that `VecIoWrapper` is constructed until the time it is turned back in to a
+/// `Vec` using `to_inner`. The returned `Vec` is guaranteed to be valid as any combination of bits
+/// in a `Vec` of `u8` is valid.
+pub(crate) struct VecIoWrapper {
+ inner: Box<[u8]>,
+}
+
+impl From<Vec<u8>> for VecIoWrapper {
+ fn from(vec: Vec<u8>) -> Self {
+ VecIoWrapper { inner: vec.into() }
+ }
+}
+
+impl From<VecIoWrapper> for Vec<u8> {
+ fn from(v: VecIoWrapper) -> Vec<u8> {
+ v.inner.into()
+ }
+}
+
+impl VecIoWrapper {
+ /// Get the length of the Vec that is wrapped.
+ pub fn len(&self) -> usize {
+ self.inner.len()
+ }
+
+ // Check that the offsets are all valid in the backing vec.
+ fn check_addrs(&self, mem_range: &MemRegion) -> Result<()> {
+ let end = mem_range
+ .offset
+ .checked_add(mem_range.len as u64)
+ .ok_or(Error::InvalidOffset(mem_range.offset, mem_range.len))?;
+ if end > self.inner.len() as u64 {
+ return Err(Error::InvalidOffset(mem_range.offset, mem_range.len));
+ }
+ Ok(())
+ }
+}
+
+// Safe to implement BackingMemory as the vec is only accessible inside the wrapper and these iovecs
+// are the only thing allowed to modify it. Nothing else can get a reference to the vec until all
+// iovecs are dropped because they borrow Self. Nothing can borrow the owned inner vec until self
+// is consumed by `into`, which can't happen if there are outstanding mut borrows.
+unsafe impl BackingMemory for VecIoWrapper {
+ fn get_volatile_slice(&self, mem_range: MemRegion) -> Result<VolatileSlice<'_>> {
+ self.check_addrs(&mem_range)?;
+ // Safe because the mem_range range is valid in the backing memory as checked above.
+ unsafe {
+ Ok(VolatileSlice::from_raw_parts(
+ self.inner.as_ptr().add(mem_range.offset as usize) as *mut _,
+ mem_range.len,
+ ))
+ }
+ }
+}
diff --git a/common/cros_async/src/poll_source.rs b/common/cros_async/src/poll_source.rs
new file mode 100644
index 000000000..4a904ba79
--- /dev/null
+++ b/common/cros_async/src/poll_source.rs
@@ -0,0 +1,450 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! A wrapped IO source that uses FdExecutor to drive asynchronous completion. Used from
+//! `IoSourceExt::new` when uring isn't available in the kernel.
+
+use std::{
+ io,
+ ops::{Deref, DerefMut},
+ os::unix::io::AsRawFd,
+ sync::Arc,
+};
+
+use async_trait::async_trait;
+use data_model::VolatileSlice;
+use remain::sorted;
+use thiserror::Error as ThisError;
+
+use super::{
+ fd_executor::{
+ FdExecutor, RegisteredSource, {self},
+ },
+ mem::{BackingMemory, MemRegion},
+ AsyncError, AsyncResult, IoSourceExt, ReadAsync, WriteAsync,
+};
+
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum Error {
+ /// An error occurred attempting to register a waker with the executor.
+ #[error("An error occurred attempting to register a waker with the executor: {0}.")]
+ AddingWaker(fd_executor::Error),
+ /// An executor error occurred.
+ #[error("An executor error occurred: {0}")]
+ Executor(fd_executor::Error),
+ /// An error occurred when executing fallocate synchronously.
+ #[error("An error occurred when executing fallocate synchronously: {0}")]
+ Fallocate(sys_util::Error),
+ /// An error occurred when executing fsync synchronously.
+ #[error("An error occurred when executing fsync synchronously: {0}")]
+ Fsync(sys_util::Error),
+ /// An error occurred when reading the FD.
+ #[error("An error occurred when reading the FD: {0}.")]
+ Read(sys_util::Error),
+ /// Can't seek file.
+ #[error("An error occurred when seeking the FD: {0}.")]
+ Seeking(sys_util::Error),
+ /// An error occurred when writing the FD.
+ #[error("An error occurred when writing the FD: {0}.")]
+ Write(sys_util::Error),
+}
+pub type Result<T> = std::result::Result<T, Error>;
+
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ use Error::*;
+ match e {
+ AddingWaker(e) => e.into(),
+ Executor(e) => e.into(),
+ Fallocate(e) => e.into(),
+ Fsync(e) => e.into(),
+ Read(e) => e.into(),
+ Seeking(e) => e.into(),
+ Write(e) => e.into(),
+ }
+ }
+}
+
+/// Async wrapper for an IO source that uses the FD executor to drive async operations.
+/// Used by `IoSourceExt::new` when uring isn't available.
+pub struct PollSource<F>(RegisteredSource<F>);
+
+impl<F: AsRawFd> PollSource<F> {
+ /// Create a new `PollSource` from the given IO source.
+ pub fn new(f: F, ex: &FdExecutor) -> Result<Self> {
+ ex.register_source(f)
+ .map(PollSource)
+ .map_err(Error::Executor)
+ }
+
+ /// Return the inner source.
+ pub fn into_source(self) -> F {
+ self.0.into_source()
+ }
+}
+
+impl<F: AsRawFd> Deref for PollSource<F> {
+ type Target = F;
+
+ fn deref(&self) -> &Self::Target {
+ self.0.as_ref()
+ }
+}
+
+impl<F: AsRawFd> DerefMut for PollSource<F> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.0.as_mut()
+ }
+}
+
+#[async_trait(?Send)]
+impl<F: AsRawFd> ReadAsync for PollSource<F> {
+ /// Reads from the iosource at `file_offset` and fill the given `vec`.
+ async fn read_to_vec<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ mut vec: Vec<u8>,
+ ) -> AsyncResult<(usize, Vec<u8>)> {
+ loop {
+ // Safe because this will only modify `vec` and we check the return value.
+ let res = if let Some(offset) = file_offset {
+ unsafe {
+ libc::pread64(
+ self.as_raw_fd(),
+ vec.as_mut_ptr() as *mut libc::c_void,
+ vec.len(),
+ offset as libc::off64_t,
+ )
+ }
+ } else {
+ unsafe {
+ libc::read(
+ self.as_raw_fd(),
+ vec.as_mut_ptr() as *mut libc::c_void,
+ vec.len(),
+ )
+ }
+ };
+
+ if res >= 0 {
+ return Ok((res as usize, vec));
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK => {
+ let op = self.0.wait_readable().map_err(Error::AddingWaker)?;
+ op.await.map_err(Error::Executor)?;
+ }
+ e => return Err(Error::Read(e).into()),
+ }
+ }
+ }
+
+ /// Reads to the given `mem` at the given offsets from the file starting at `file_offset`.
+ async fn read_to_mem<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ mem: Arc<dyn BackingMemory + Send + Sync>,
+ mem_offsets: &'a [MemRegion],
+ ) -> AsyncResult<usize> {
+ let mut iovecs = mem_offsets
+ .iter()
+ .filter_map(|&mem_vec| mem.get_volatile_slice(mem_vec).ok())
+ .collect::<Vec<VolatileSlice>>();
+
+ loop {
+ // Safe because we trust the kernel not to write path the length given and the length is
+ // guaranteed to be valid from the pointer by io_slice_mut.
+ let res = if let Some(offset) = file_offset {
+ unsafe {
+ libc::preadv64(
+ self.as_raw_fd(),
+ iovecs.as_mut_ptr() as *mut _,
+ iovecs.len() as i32,
+ offset as libc::off64_t,
+ )
+ }
+ } else {
+ unsafe {
+ libc::readv(
+ self.as_raw_fd(),
+ iovecs.as_mut_ptr() as *mut _,
+ iovecs.len() as i32,
+ )
+ }
+ };
+
+ if res >= 0 {
+ return Ok(res as usize);
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK => {
+ let op = self.0.wait_readable().map_err(Error::AddingWaker)?;
+ op.await.map_err(Error::Executor)?;
+ }
+ e => return Err(Error::Read(e).into()),
+ }
+ }
+ }
+
+ /// Wait for the FD of `self` to be readable.
+ async fn wait_readable(&self) -> AsyncResult<()> {
+ let op = self.0.wait_readable().map_err(Error::AddingWaker)?;
+ op.await.map_err(Error::Executor)?;
+ Ok(())
+ }
+
+ async fn read_u64(&self) -> AsyncResult<u64> {
+ let mut buf = 0u64.to_ne_bytes();
+ loop {
+ // Safe because this will only modify `buf` and we check the return value.
+ let res = unsafe {
+ libc::read(
+ self.as_raw_fd(),
+ buf.as_mut_ptr() as *mut libc::c_void,
+ buf.len(),
+ )
+ };
+
+ if res >= 0 {
+ return Ok(u64::from_ne_bytes(buf));
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK => {
+ let op = self.0.wait_readable().map_err(Error::AddingWaker)?;
+ op.await.map_err(Error::Executor)?;
+ }
+ e => return Err(Error::Read(e).into()),
+ }
+ }
+ }
+}
+
+#[async_trait(?Send)]
+impl<F: AsRawFd> WriteAsync for PollSource<F> {
+ /// Writes from the given `vec` to the file starting at `file_offset`.
+ async fn write_from_vec<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ vec: Vec<u8>,
+ ) -> AsyncResult<(usize, Vec<u8>)> {
+ loop {
+ // Safe because this will not modify any memory and we check the return value.
+ let res = if let Some(offset) = file_offset {
+ unsafe {
+ libc::pwrite64(
+ self.as_raw_fd(),
+ vec.as_ptr() as *const libc::c_void,
+ vec.len(),
+ offset as libc::off64_t,
+ )
+ }
+ } else {
+ unsafe {
+ libc::write(
+ self.as_raw_fd(),
+ vec.as_ptr() as *const libc::c_void,
+ vec.len(),
+ )
+ }
+ };
+
+ if res >= 0 {
+ return Ok((res as usize, vec));
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK => {
+ let op = self.0.wait_writable().map_err(Error::AddingWaker)?;
+ op.await.map_err(Error::Executor)?;
+ }
+ e => return Err(Error::Write(e).into()),
+ }
+ }
+ }
+
+ /// Writes from the given `mem` from the given offsets to the file starting at `file_offset`.
+ async fn write_from_mem<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ mem: Arc<dyn BackingMemory + Send + Sync>,
+ mem_offsets: &'a [MemRegion],
+ ) -> AsyncResult<usize> {
+ let iovecs = mem_offsets
+ .iter()
+ .map(|&mem_vec| mem.get_volatile_slice(mem_vec))
+ .filter_map(|r| r.ok())
+ .collect::<Vec<VolatileSlice>>();
+
+ loop {
+ // Safe because we trust the kernel not to write path the length given and the length is
+ // guaranteed to be valid from the pointer by io_slice_mut.
+ let res = if let Some(offset) = file_offset {
+ unsafe {
+ libc::pwritev64(
+ self.as_raw_fd(),
+ iovecs.as_ptr() as *mut _,
+ iovecs.len() as i32,
+ offset as libc::off64_t,
+ )
+ }
+ } else {
+ unsafe {
+ libc::writev(
+ self.as_raw_fd(),
+ iovecs.as_ptr() as *mut _,
+ iovecs.len() as i32,
+ )
+ }
+ };
+
+ if res >= 0 {
+ return Ok(res as usize);
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK => {
+ let op = self.0.wait_writable().map_err(Error::AddingWaker)?;
+ op.await.map_err(Error::Executor)?;
+ }
+ e => return Err(Error::Write(e).into()),
+ }
+ }
+ }
+
+ /// See `fallocate(2)` for details.
+ async fn fallocate(&self, file_offset: u64, len: u64, mode: u32) -> AsyncResult<()> {
+ let ret = unsafe {
+ libc::fallocate64(
+ self.as_raw_fd(),
+ mode as libc::c_int,
+ file_offset as libc::off64_t,
+ len as libc::off64_t,
+ )
+ };
+ if ret == 0 {
+ Ok(())
+ } else {
+ Err(AsyncError::Poll(Error::Fallocate(sys_util::Error::last())))
+ }
+ }
+
+ /// Sync all completed write operations to the backing storage.
+ async fn fsync(&self) -> AsyncResult<()> {
+ let ret = unsafe { libc::fsync(self.as_raw_fd()) };
+ if ret == 0 {
+ Ok(())
+ } else {
+ Err(AsyncError::Poll(Error::Fsync(sys_util::Error::last())))
+ }
+ }
+}
+
+#[async_trait(?Send)]
+impl<F: AsRawFd> IoSourceExt<F> for PollSource<F> {
+ /// Yields the underlying IO source.
+ fn into_source(self: Box<Self>) -> F {
+ self.0.into_source()
+ }
+
+ /// Provides a mutable ref to the underlying IO source.
+ fn as_source_mut(&mut self) -> &mut F {
+ self
+ }
+
+ /// Provides a ref to the underlying IO source.
+ fn as_source(&self) -> &F {
+ self
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::{
+ fs::{File, OpenOptions},
+ path::PathBuf,
+ };
+
+ use super::*;
+
+ #[test]
+ fn readvec() {
+ async fn go(ex: &FdExecutor) {
+ let f = File::open("/dev/zero").unwrap();
+ let async_source = PollSource::new(f, ex).unwrap();
+ let v = vec![0x55u8; 32];
+ let v_ptr = v.as_ptr();
+ let ret = async_source.read_to_vec(None, v).await.unwrap();
+ assert_eq!(ret.0, 32);
+ let ret_v = ret.1;
+ assert_eq!(v_ptr, ret_v.as_ptr());
+ assert!(ret_v.iter().all(|&b| b == 0));
+ }
+
+ let ex = FdExecutor::new().unwrap();
+ ex.run_until(go(&ex)).unwrap();
+ }
+
+ #[test]
+ fn writevec() {
+ async fn go(ex: &FdExecutor) {
+ let f = OpenOptions::new().write(true).open("/dev/null").unwrap();
+ let async_source = PollSource::new(f, ex).unwrap();
+ let v = vec![0x55u8; 32];
+ let v_ptr = v.as_ptr();
+ let ret = async_source.write_from_vec(None, v).await.unwrap();
+ assert_eq!(ret.0, 32);
+ let ret_v = ret.1;
+ assert_eq!(v_ptr, ret_v.as_ptr());
+ }
+
+ let ex = FdExecutor::new().unwrap();
+ ex.run_until(go(&ex)).unwrap();
+ }
+
+ #[test]
+ fn fallocate() {
+ async fn go(ex: &FdExecutor) {
+ let dir = tempfile::TempDir::new().unwrap();
+ let mut file_path = PathBuf::from(dir.path());
+ file_path.push("test");
+
+ let f = OpenOptions::new()
+ .create(true)
+ .write(true)
+ .open(&file_path)
+ .unwrap();
+ let source = PollSource::new(f, ex).unwrap();
+ source.fallocate(0, 4096, 0).await.unwrap();
+
+ let meta_data = std::fs::metadata(&file_path).unwrap();
+ assert_eq!(meta_data.len(), 4096);
+ }
+
+ let ex = FdExecutor::new().unwrap();
+ ex.run_until(go(&ex)).unwrap();
+ }
+
+ #[test]
+ fn memory_leak() {
+ // This test needs to run under ASAN to detect memory leaks.
+
+ async fn owns_poll_source(source: PollSource<File>) {
+ let _ = source.wait_readable().await;
+ }
+
+ let (rx, _tx) = sys_util::pipe(true).unwrap();
+ let ex = FdExecutor::new().unwrap();
+ let source = PollSource::new(rx, &ex).unwrap();
+ ex.spawn_local(owns_poll_source(source)).detach();
+
+ // Drop `ex` without running. This would cause a memory leak if PollSource owned a strong
+ // reference to the executor because it owns a reference to the future that owns PollSource
+ // (via its Runnable). The strong reference prevents the drop impl from running, which would
+ // otherwise poll the future and have it return with an error.
+ }
+}
diff --git a/common/cros_async/src/queue.rs b/common/cros_async/src/queue.rs
new file mode 100644
index 000000000..f95de0629
--- /dev/null
+++ b/common/cros_async/src/queue.rs
@@ -0,0 +1,66 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::collections::VecDeque;
+
+use async_task::Runnable;
+use sync::Mutex;
+
+/// A queue of `Runnables`. Intended to be used by executors to keep track of futures that have been
+/// scheduled to run.
+pub struct RunnableQueue {
+ runnables: Mutex<VecDeque<Runnable>>,
+}
+
+impl RunnableQueue {
+ /// Create a new, empty `RunnableQueue`.
+ pub fn new() -> RunnableQueue {
+ RunnableQueue {
+ runnables: Mutex::new(VecDeque::new()),
+ }
+ }
+
+ /// Schedule `runnable` to run in the future by adding it to this `RunnableQueue`.
+ pub fn push_back(&self, runnable: Runnable) {
+ self.runnables.lock().push_back(runnable);
+ }
+
+ /// Remove and return the first `Runnable` in this `RunnableQueue` or `None` if it is empty.
+ pub fn pop_front(&self) -> Option<Runnable> {
+ self.runnables.lock().pop_front()
+ }
+
+ /// Create an iterator over this `RunnableQueue` that repeatedly calls `pop_front()` until it is
+ /// empty.
+ pub fn iter(&self) -> RunnableQueueIter {
+ self.into_iter()
+ }
+}
+
+impl Default for RunnableQueue {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<'q> IntoIterator for &'q RunnableQueue {
+ type Item = Runnable;
+ type IntoIter = RunnableQueueIter<'q>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ RunnableQueueIter { queue: self }
+ }
+}
+
+/// An iterator over a `RunnableQueue`.
+pub struct RunnableQueueIter<'q> {
+ queue: &'q RunnableQueue,
+}
+
+impl<'q> Iterator for RunnableQueueIter<'q> {
+ type Item = Runnable;
+ fn next(&mut self) -> Option<Self::Item> {
+ self.queue.pop_front()
+ }
+}
diff --git a/common/cros_async/src/select.rs b/common/cros_async/src/select.rs
new file mode 100644
index 000000000..acf83e768
--- /dev/null
+++ b/common/cros_async/src/select.rs
@@ -0,0 +1,92 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Need non-snake case so the macro can re-use type names for variables.
+#![allow(non_snake_case)]
+
+use std::{
+ future::Future,
+ pin::Pin,
+ task::{Context, Poll},
+};
+
+use futures::future::{maybe_done, FutureExt, MaybeDone};
+
+pub enum SelectResult<F: Future> {
+ Pending(F),
+ Finished(F::Output),
+}
+
+// Macro-generate future combinators to allow for running different numbers of top-level futures in
+// this FutureList. Generates the implementation of `FutureList` for the select types. For an
+// explicit example this is modeled after, see `UnitFutures`.
+macro_rules! generate {
+ ($(
+ $(#[$doc:meta])*
+ ($Select:ident, <$($Fut:ident),*>),
+ )*) => ($(
+
+ paste::item! {
+ pub(crate) struct $Select<$($Fut: Future + Unpin),*> {
+ $($Fut: MaybeDone<$Fut>,)*
+ }
+ }
+
+ impl<$($Fut: Future + Unpin),*> $Select<$($Fut),*> {
+ paste::item! {
+ pub(crate) fn new($($Fut: $Fut),*) -> $Select<$($Fut),*> {
+ $Select {
+ $($Fut: maybe_done($Fut),)*
+ }
+ }
+ }
+ }
+
+ impl<$($Fut: Future + Unpin),*> Future for $Select<$($Fut),*> {
+ type Output = ($(SelectResult<$Fut>),*);
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ let mut complete = false;
+ $(
+ let $Fut = Pin::new(&mut self.$Fut);
+ // The future impls `Unpin`, use `poll_unpin` to avoid wrapping it in
+ // `Pin` to call `poll`.
+ complete |= self.$Fut.poll_unpin(cx).is_ready();
+ )*
+
+ if complete {
+ Poll::Ready(($(
+ match std::mem::replace(&mut self.$Fut, MaybeDone::Gone) {
+ MaybeDone::Future(f) => SelectResult::Pending(f),
+ MaybeDone::Done(o) => SelectResult::Finished(o),
+ MaybeDone::Gone => unreachable!(),
+ }
+ ), *))
+ } else {
+ Poll::Pending
+ }
+ }
+ }
+ )*)
+}
+
+generate! {
+ /// _Future for the [`select2`] function.
+ (Select2, <_Fut1, _Fut2>),
+
+ /// _Future for the [`select3`] function.
+ (Select3, <_Fut1, _Fut2, _Fut3>),
+
+ /// _Future for the [`select4`] function.
+ (Select4, <_Fut1, _Fut2, _Fut3, _Fut4>),
+
+ /// _Future for the [`select5`] function.
+ (Select5, <_Fut1, _Fut2, _Fut3, _Fut4, _Fut5>),
+
+ /// _Future for the [`select6`] function.
+ (Select6, <_Fut1, _Fut2, _Fut3, _Fut4, _Fut5, _Fut6>),
+
+ /// _Future for the [`select7`] function.
+ (Select7, <_Fut1, _Fut2, _Fut3, _Fut4, _Fut5, _Fut6, _Fut7>),
+}
diff --git a/common/cros_async/src/sync.rs b/common/cros_async/src/sync.rs
new file mode 100644
index 000000000..e2c1a1452
--- /dev/null
+++ b/common/cros_async/src/sync.rs
@@ -0,0 +1,12 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod cv;
+mod mu;
+mod spin;
+mod waiter;
+
+pub use cv::Condvar;
+pub use mu::Mutex;
+pub use spin::SpinLock;
diff --git a/common/cros_async/src/sync/cv.rs b/common/cros_async/src/sync/cv.rs
new file mode 100644
index 000000000..46b96dd18
--- /dev/null
+++ b/common/cros_async/src/sync/cv.rs
@@ -0,0 +1,1179 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ cell::UnsafeCell,
+ hint, mem,
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc,
+ },
+};
+
+use super::super::sync::{
+ mu::{MutexGuard, MutexReadGuard, RawMutex},
+ waiter::{Kind as WaiterKind, Waiter, WaiterAdapter, WaiterList, WaitingFor},
+};
+
+const SPINLOCK: usize = 1 << 0;
+const HAS_WAITERS: usize = 1 << 1;
+
+/// A primitive to wait for an event to occur without consuming CPU time.
+///
+/// Condition variables are used in combination with a `Mutex` when a thread wants to wait for some
+/// condition to become true. The condition must always be verified while holding the `Mutex` lock.
+/// It is an error to use a `Condvar` with more than one `Mutex` while there are threads waiting on
+/// the `Condvar`.
+///
+/// # Examples
+///
+/// ```edition2018
+/// use std::sync::Arc;
+/// use std::thread;
+/// use std::sync::mpsc::channel;
+///
+/// use cros_async::{
+/// block_on,
+/// sync::{Condvar, Mutex},
+/// };
+///
+/// const N: usize = 13;
+///
+/// // Spawn a few threads to increment a shared variable (non-atomically), and
+/// // let all threads waiting on the Condvar know once the increments are done.
+/// let data = Arc::new(Mutex::new(0));
+/// let cv = Arc::new(Condvar::new());
+///
+/// for _ in 0..N {
+/// let (data, cv) = (data.clone(), cv.clone());
+/// thread::spawn(move || {
+/// let mut data = block_on(data.lock());
+/// *data += 1;
+/// if *data == N {
+/// cv.notify_all();
+/// }
+/// });
+/// }
+///
+/// let mut val = block_on(data.lock());
+/// while *val != N {
+/// val = block_on(cv.wait(val));
+/// }
+/// ```
+#[repr(align(128))]
+pub struct Condvar {
+ state: AtomicUsize,
+ waiters: UnsafeCell<WaiterList>,
+ mu: UnsafeCell<usize>,
+}
+
+impl Condvar {
+ /// Creates a new condition variable ready to be waited on and notified.
+ pub fn new() -> Condvar {
+ Condvar {
+ state: AtomicUsize::new(0),
+ waiters: UnsafeCell::new(WaiterList::new(WaiterAdapter::new())),
+ mu: UnsafeCell::new(0),
+ }
+ }
+
+ /// Block the current thread until this `Condvar` is notified by another thread.
+ ///
+ /// This method will atomically unlock the `Mutex` held by `guard` and then block the current
+ /// thread. Any call to `notify_one` or `notify_all` after the `Mutex` is unlocked may wake up
+ /// the thread.
+ ///
+ /// To allow for more efficient scheduling, this call may return even when the programmer
+ /// doesn't expect the thread to be woken. Therefore, calls to `wait()` should be used inside a
+ /// loop that checks the predicate before continuing.
+ ///
+ /// Callers that are not in an async context may wish to use the `block_on` method to block the
+ /// thread until the `Condvar` is notified.
+ ///
+ /// # Panics
+ ///
+ /// This method will panic if used with more than one `Mutex` at the same time.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use std::sync::Arc;
+ /// # use std::thread;
+ ///
+ /// # use cros_async::{
+ /// # block_on,
+ /// # sync::{Condvar, Mutex},
+ /// # };
+ ///
+ /// # let mu = Arc::new(Mutex::new(false));
+ /// # let cv = Arc::new(Condvar::new());
+ /// # let (mu2, cv2) = (mu.clone(), cv.clone());
+ ///
+ /// # let t = thread::spawn(move || {
+ /// # *block_on(mu2.lock()) = true;
+ /// # cv2.notify_all();
+ /// # });
+ ///
+ /// let mut ready = block_on(mu.lock());
+ /// while !*ready {
+ /// ready = block_on(cv.wait(ready));
+ /// }
+ ///
+ /// # t.join().expect("failed to join thread");
+ /// ```
+ // Clippy doesn't like the lifetime parameters here but doing what it suggests leads to code
+ // that doesn't compile.
+ #[allow(clippy::needless_lifetimes)]
+ pub async fn wait<'g, T>(&self, guard: MutexGuard<'g, T>) -> MutexGuard<'g, T> {
+ let waiter = Arc::new(Waiter::new(
+ WaiterKind::Exclusive,
+ cancel_waiter,
+ self as *const Condvar as usize,
+ WaitingFor::Condvar,
+ ));
+
+ self.add_waiter(waiter.clone(), guard.as_raw_mutex());
+
+ // Get a reference to the mutex and then drop the lock.
+ let mu = guard.into_inner();
+
+ // Wait to be woken up.
+ waiter.wait().await;
+
+ // Now re-acquire the lock.
+ mu.lock_from_cv().await
+ }
+
+ /// Like `wait()` but takes and returns a `MutexReadGuard` instead.
+ // Clippy doesn't like the lifetime parameters here but doing what it suggests leads to code
+ // that doesn't compile.
+ #[allow(clippy::needless_lifetimes)]
+ pub async fn wait_read<'g, T>(&self, guard: MutexReadGuard<'g, T>) -> MutexReadGuard<'g, T> {
+ let waiter = Arc::new(Waiter::new(
+ WaiterKind::Shared,
+ cancel_waiter,
+ self as *const Condvar as usize,
+ WaitingFor::Condvar,
+ ));
+
+ self.add_waiter(waiter.clone(), guard.as_raw_mutex());
+
+ // Get a reference to the mutex and then drop the lock.
+ let mu = guard.into_inner();
+
+ // Wait to be woken up.
+ waiter.wait().await;
+
+ // Now re-acquire the lock.
+ mu.read_lock_from_cv().await
+ }
+
+ fn add_waiter(&self, waiter: Arc<Waiter>, raw_mutex: &RawMutex) {
+ // Acquire the spin lock.
+ let mut oldstate = self.state.load(Ordering::Relaxed);
+ while (oldstate & SPINLOCK) != 0
+ || self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ oldstate | SPINLOCK | HAS_WAITERS,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ hint::spin_loop();
+ oldstate = self.state.load(Ordering::Relaxed);
+ }
+
+ // Safe because the spin lock guarantees exclusive access and the reference does not escape
+ // this function.
+ let mu = unsafe { &mut *self.mu.get() };
+ let muptr = raw_mutex as *const RawMutex as usize;
+
+ match *mu {
+ 0 => *mu = muptr,
+ p if p == muptr => {}
+ _ => panic!("Attempting to use Condvar with more than one Mutex at the same time"),
+ }
+
+ // Safe because the spin lock guarantees exclusive access.
+ unsafe { (*self.waiters.get()).push_back(waiter) };
+
+ // Release the spin lock. Use a direct store here because no other thread can modify
+ // `self.state` while we hold the spin lock. Keep the `HAS_WAITERS` bit that we set earlier
+ // because we just added a waiter.
+ self.state.store(HAS_WAITERS, Ordering::Release);
+ }
+
+ /// Notify at most one thread currently waiting on the `Condvar`.
+ ///
+ /// If there is a thread currently waiting on the `Condvar` it will be woken up from its call to
+ /// `wait`.
+ ///
+ /// Unlike more traditional condition variable interfaces, this method requires a reference to
+ /// the `Mutex` associated with this `Condvar`. This is because it is inherently racy to call
+ /// `notify_one` or `notify_all` without first acquiring the `Mutex` lock. Additionally, taking
+ /// a reference to the `Mutex` here allows us to make some optimizations that can improve
+ /// performance by reducing unnecessary wakeups.
+ pub fn notify_one(&self) {
+ let mut oldstate = self.state.load(Ordering::Relaxed);
+ if (oldstate & HAS_WAITERS) == 0 {
+ // No waiters.
+ return;
+ }
+
+ while (oldstate & SPINLOCK) != 0
+ || self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ oldstate | SPINLOCK,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ hint::spin_loop();
+ oldstate = self.state.load(Ordering::Relaxed);
+ }
+
+ // Safe because the spin lock guarantees exclusive access and the reference does not escape
+ // this function.
+ let waiters = unsafe { &mut *self.waiters.get() };
+ let wake_list = get_wake_list(waiters);
+
+ let newstate = if waiters.is_empty() {
+ // Also clear the mutex associated with this Condvar since there are no longer any
+ // waiters. Safe because the spin lock guarantees exclusive access.
+ unsafe { *self.mu.get() = 0 };
+
+ // We are releasing the spin lock and there are no more waiters so we can clear all bits
+ // in `self.state`.
+ 0
+ } else {
+ // There are still waiters so we need to keep the HAS_WAITERS bit in the state.
+ HAS_WAITERS
+ };
+
+ // Release the spin lock.
+ self.state.store(newstate, Ordering::Release);
+
+ // Now wake any waiters in the wake list.
+ for w in wake_list {
+ w.wake();
+ }
+ }
+
+ /// Notify all threads currently waiting on the `Condvar`.
+ ///
+ /// All threads currently waiting on the `Condvar` will be woken up from their call to `wait`.
+ ///
+ /// Unlike more traditional condition variable interfaces, this method requires a reference to
+ /// the `Mutex` associated with this `Condvar`. This is because it is inherently racy to call
+ /// `notify_one` or `notify_all` without first acquiring the `Mutex` lock. Additionally, taking
+ /// a reference to the `Mutex` here allows us to make some optimizations that can improve
+ /// performance by reducing unnecessary wakeups.
+ pub fn notify_all(&self) {
+ let mut oldstate = self.state.load(Ordering::Relaxed);
+ if (oldstate & HAS_WAITERS) == 0 {
+ // No waiters.
+ return;
+ }
+
+ while (oldstate & SPINLOCK) != 0
+ || self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ oldstate | SPINLOCK,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ hint::spin_loop();
+ oldstate = self.state.load(Ordering::Relaxed);
+ }
+
+ // Safe because the spin lock guarantees exclusive access to `self.waiters`.
+ let wake_list = unsafe { (*self.waiters.get()).take() };
+
+ // Clear the mutex associated with this Condvar since there are no longer any waiters. Safe
+ // because we the spin lock guarantees exclusive access.
+ unsafe { *self.mu.get() = 0 };
+
+ // Mark any waiters left as no longer waiting for the Condvar.
+ for w in &wake_list {
+ w.set_waiting_for(WaitingFor::None);
+ }
+
+ // Release the spin lock. We can clear all bits in the state since we took all the waiters.
+ self.state.store(0, Ordering::Release);
+
+ // Now wake any waiters in the wake list.
+ for w in wake_list {
+ w.wake();
+ }
+ }
+
+ fn cancel_waiter(&self, waiter: &Waiter, wake_next: bool) {
+ let mut oldstate = self.state.load(Ordering::Relaxed);
+ while oldstate & SPINLOCK != 0
+ || self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ oldstate | SPINLOCK,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ hint::spin_loop();
+ oldstate = self.state.load(Ordering::Relaxed);
+ }
+
+ // Safe because the spin lock provides exclusive access and the reference does not escape
+ // this function.
+ let waiters = unsafe { &mut *self.waiters.get() };
+
+ let waiting_for = waiter.is_waiting_for();
+ // Don't drop the old waiter now as we're still holding the spin lock.
+ let old_waiter = if waiter.is_linked() && waiting_for == WaitingFor::Condvar {
+ // Safe because we know that the waiter is still linked and is waiting for the Condvar,
+ // which guarantees that it is still in `self.waiters`.
+ let mut cursor = unsafe { waiters.cursor_mut_from_ptr(waiter as *const Waiter) };
+ cursor.remove()
+ } else {
+ None
+ };
+
+ let wake_list = if wake_next || waiting_for == WaitingFor::None {
+ // Either the waiter was already woken or it's been removed from the condvar's waiter
+ // list and is going to be woken. Either way, we need to wake up another thread.
+ get_wake_list(waiters)
+ } else {
+ WaiterList::new(WaiterAdapter::new())
+ };
+
+ let set_on_release = if waiters.is_empty() {
+ // Clear the mutex associated with this Condvar since there are no longer any waiters. Safe
+ // because we the spin lock guarantees exclusive access.
+ unsafe { *self.mu.get() = 0 };
+
+ 0
+ } else {
+ HAS_WAITERS
+ };
+
+ self.state.store(set_on_release, Ordering::Release);
+
+ // Now wake any waiters still left in the wake list.
+ for w in wake_list {
+ w.wake();
+ }
+
+ mem::drop(old_waiter);
+ }
+}
+
+unsafe impl Send for Condvar {}
+unsafe impl Sync for Condvar {}
+
+impl Default for Condvar {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+// Scan `waiters` and return all waiters that should be woken up.
+//
+// If the first waiter is trying to acquire a shared lock, then all waiters in the list that are
+// waiting for a shared lock are also woken up. In addition one writer is woken up, if possible.
+//
+// If the first waiter is trying to acquire an exclusive lock, then only that waiter is returned and
+// the rest of the list is not scanned.
+fn get_wake_list(waiters: &mut WaiterList) -> WaiterList {
+ let mut to_wake = WaiterList::new(WaiterAdapter::new());
+ let mut cursor = waiters.front_mut();
+
+ let mut waking_readers = false;
+ let mut all_readers = true;
+ while let Some(w) = cursor.get() {
+ match w.kind() {
+ WaiterKind::Exclusive if !waking_readers => {
+ // This is the first waiter and it's a writer. No need to check the other waiters.
+ // Also mark the waiter as having been removed from the Condvar's waiter list.
+ let waiter = cursor.remove().unwrap();
+ waiter.set_waiting_for(WaitingFor::None);
+ to_wake.push_back(waiter);
+ break;
+ }
+
+ WaiterKind::Shared => {
+ // This is a reader and the first waiter in the list was not a writer so wake up all
+ // the readers in the wait list.
+ let waiter = cursor.remove().unwrap();
+ waiter.set_waiting_for(WaitingFor::None);
+ to_wake.push_back(waiter);
+ waking_readers = true;
+ }
+
+ WaiterKind::Exclusive => {
+ debug_assert!(waking_readers);
+ if all_readers {
+ // We are waking readers but we need to ensure that at least one writer is woken
+ // up. Since we haven't yet woken up a writer, wake up this one.
+ let waiter = cursor.remove().unwrap();
+ waiter.set_waiting_for(WaitingFor::None);
+ to_wake.push_back(waiter);
+ all_readers = false;
+ } else {
+ // We are waking readers and have already woken one writer. Skip this one.
+ cursor.move_next();
+ }
+ }
+ }
+ }
+
+ to_wake
+}
+
+fn cancel_waiter(cv: usize, waiter: &Waiter, wake_next: bool) {
+ let condvar = cv as *const Condvar;
+
+ // Safe because the thread that owns the waiter being canceled must also own a reference to the
+ // Condvar, which guarantees that this pointer is valid.
+ unsafe { (*condvar).cancel_waiter(waiter, wake_next) }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use std::{
+ future::Future,
+ mem, ptr,
+ rc::Rc,
+ sync::{
+ mpsc::{channel, Sender},
+ Arc,
+ },
+ task::{Context, Poll},
+ thread::{
+ JoinHandle, {self},
+ },
+ time::Duration,
+ };
+
+ use futures::{
+ channel::oneshot,
+ select,
+ task::{waker_ref, ArcWake},
+ FutureExt,
+ };
+ use futures_executor::{LocalPool, LocalSpawner, ThreadPool};
+ use futures_util::task::LocalSpawnExt;
+
+ use super::super::super::{block_on, sync::Mutex};
+
+ // Dummy waker used when we want to manually drive futures.
+ struct TestWaker;
+ impl ArcWake for TestWaker {
+ fn wake_by_ref(_arc_self: &Arc<Self>) {}
+ }
+
+ #[test]
+ fn smoke() {
+ let cv = Condvar::new();
+ cv.notify_one();
+ cv.notify_all();
+ }
+
+ #[test]
+ fn notify_one() {
+ let mu = Arc::new(Mutex::new(()));
+ let cv = Arc::new(Condvar::new());
+
+ let mu2 = mu.clone();
+ let cv2 = cv.clone();
+
+ let guard = block_on(mu.lock());
+ thread::spawn(move || {
+ let _g = block_on(mu2.lock());
+ cv2.notify_one();
+ });
+
+ let guard = block_on(cv.wait(guard));
+ mem::drop(guard);
+ }
+
+ #[test]
+ fn multi_mutex() {
+ const NUM_THREADS: usize = 5;
+
+ let mu = Arc::new(Mutex::new(false));
+ let cv = Arc::new(Condvar::new());
+
+ let mut threads = Vec::with_capacity(NUM_THREADS);
+ for _ in 0..NUM_THREADS {
+ let mu = mu.clone();
+ let cv = cv.clone();
+
+ threads.push(thread::spawn(move || {
+ let mut ready = block_on(mu.lock());
+ while !*ready {
+ ready = block_on(cv.wait(ready));
+ }
+ }));
+ }
+
+ let mut g = block_on(mu.lock());
+ *g = true;
+ mem::drop(g);
+ cv.notify_all();
+
+ threads
+ .into_iter()
+ .try_for_each(JoinHandle::join)
+ .expect("Failed to join threads");
+
+ // Now use the Condvar with a different mutex.
+ let alt_mu = Arc::new(Mutex::new(None));
+ let alt_mu2 = alt_mu.clone();
+ let cv2 = cv.clone();
+ let handle = thread::spawn(move || {
+ let mut g = block_on(alt_mu2.lock());
+ while g.is_none() {
+ g = block_on(cv2.wait(g));
+ }
+ });
+
+ let mut alt_g = block_on(alt_mu.lock());
+ *alt_g = Some(());
+ mem::drop(alt_g);
+ cv.notify_all();
+
+ handle
+ .join()
+ .expect("Failed to join thread alternate mutex");
+ }
+
+ #[test]
+ fn notify_one_single_thread_async() {
+ async fn notify(mu: Rc<Mutex<()>>, cv: Rc<Condvar>) {
+ let _g = mu.lock().await;
+ cv.notify_one();
+ }
+
+ async fn wait(mu: Rc<Mutex<()>>, cv: Rc<Condvar>, spawner: LocalSpawner) {
+ let mu2 = Rc::clone(&mu);
+ let cv2 = Rc::clone(&cv);
+
+ let g = mu.lock().await;
+ // Has to be spawned _after_ acquiring the lock to prevent a race
+ // where the notify happens before the waiter has acquired the lock.
+ spawner
+ .spawn_local(notify(mu2, cv2))
+ .expect("Failed to spawn `notify` task");
+ let _g = cv.wait(g).await;
+ }
+
+ let mut ex = LocalPool::new();
+ let spawner = ex.spawner();
+
+ let mu = Rc::new(Mutex::new(()));
+ let cv = Rc::new(Condvar::new());
+
+ spawner
+ .spawn_local(wait(mu, cv, spawner.clone()))
+ .expect("Failed to spawn `wait` task");
+
+ ex.run();
+ }
+
+ #[test]
+ fn notify_one_multi_thread_async() {
+ async fn notify(mu: Arc<Mutex<()>>, cv: Arc<Condvar>) {
+ let _g = mu.lock().await;
+ cv.notify_one();
+ }
+
+ async fn wait(mu: Arc<Mutex<()>>, cv: Arc<Condvar>, tx: Sender<()>, pool: ThreadPool) {
+ let mu2 = Arc::clone(&mu);
+ let cv2 = Arc::clone(&cv);
+
+ let g = mu.lock().await;
+ // Has to be spawned _after_ acquiring the lock to prevent a race
+ // where the notify happens before the waiter has acquired the lock.
+ pool.spawn_ok(notify(mu2, cv2));
+ let _g = cv.wait(g).await;
+
+ tx.send(()).expect("Failed to send completion notification");
+ }
+
+ let ex = ThreadPool::new().expect("Failed to create ThreadPool");
+
+ let mu = Arc::new(Mutex::new(()));
+ let cv = Arc::new(Condvar::new());
+
+ let (tx, rx) = channel();
+ ex.spawn_ok(wait(mu, cv, tx, ex.clone()));
+
+ rx.recv_timeout(Duration::from_secs(5))
+ .expect("Failed to receive completion notification");
+ }
+
+ #[test]
+ fn notify_one_with_cancel() {
+ const TASKS: usize = 17;
+ const OBSERVERS: usize = 7;
+ const ITERATIONS: usize = 103;
+
+ async fn observe(mu: &Arc<Mutex<usize>>, cv: &Arc<Condvar>) {
+ let mut count = mu.read_lock().await;
+ while *count == 0 {
+ count = cv.wait_read(count).await;
+ }
+ let _ = unsafe { ptr::read_volatile(&*count as *const usize) };
+ }
+
+ async fn decrement(mu: &Arc<Mutex<usize>>, cv: &Arc<Condvar>) {
+ let mut count = mu.lock().await;
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+ *count -= 1;
+ }
+
+ async fn increment(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>, done: Sender<()>) {
+ for _ in 0..TASKS * OBSERVERS * ITERATIONS {
+ *mu.lock().await += 1;
+ cv.notify_one();
+ }
+
+ done.send(()).expect("Failed to send completion message");
+ }
+
+ async fn observe_either(
+ mu: Arc<Mutex<usize>>,
+ cv: Arc<Condvar>,
+ alt_mu: Arc<Mutex<usize>>,
+ alt_cv: Arc<Condvar>,
+ done: Sender<()>,
+ ) {
+ for _ in 0..ITERATIONS {
+ select! {
+ () = observe(&mu, &cv).fuse() => {},
+ () = observe(&alt_mu, &alt_cv).fuse() => {},
+ }
+ }
+
+ done.send(()).expect("Failed to send completion message");
+ }
+
+ async fn decrement_either(
+ mu: Arc<Mutex<usize>>,
+ cv: Arc<Condvar>,
+ alt_mu: Arc<Mutex<usize>>,
+ alt_cv: Arc<Condvar>,
+ done: Sender<()>,
+ ) {
+ for _ in 0..ITERATIONS {
+ select! {
+ () = decrement(&mu, &cv).fuse() => {},
+ () = decrement(&alt_mu, &alt_cv).fuse() => {},
+ }
+ }
+
+ done.send(()).expect("Failed to send completion message");
+ }
+
+ let ex = ThreadPool::new().expect("Failed to create ThreadPool");
+
+ let mu = Arc::new(Mutex::new(0usize));
+ let alt_mu = Arc::new(Mutex::new(0usize));
+
+ let cv = Arc::new(Condvar::new());
+ let alt_cv = Arc::new(Condvar::new());
+
+ let (tx, rx) = channel();
+ for _ in 0..TASKS {
+ ex.spawn_ok(decrement_either(
+ Arc::clone(&mu),
+ Arc::clone(&cv),
+ Arc::clone(&alt_mu),
+ Arc::clone(&alt_cv),
+ tx.clone(),
+ ));
+ }
+
+ for _ in 0..OBSERVERS {
+ ex.spawn_ok(observe_either(
+ Arc::clone(&mu),
+ Arc::clone(&cv),
+ Arc::clone(&alt_mu),
+ Arc::clone(&alt_cv),
+ tx.clone(),
+ ));
+ }
+
+ ex.spawn_ok(increment(Arc::clone(&mu), Arc::clone(&cv), tx.clone()));
+ ex.spawn_ok(increment(Arc::clone(&alt_mu), Arc::clone(&alt_cv), tx));
+
+ for _ in 0..TASKS + OBSERVERS + 2 {
+ if let Err(e) = rx.recv_timeout(Duration::from_secs(20)) {
+ panic!("Error while waiting for threads to complete: {}", e);
+ }
+ }
+
+ assert_eq!(
+ *block_on(mu.read_lock()) + *block_on(alt_mu.read_lock()),
+ (TASKS * OBSERVERS * ITERATIONS * 2) - (TASKS * ITERATIONS)
+ );
+ assert_eq!(cv.state.load(Ordering::Relaxed), 0);
+ assert_eq!(alt_cv.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn notify_all_with_cancel() {
+ const TASKS: usize = 17;
+ const ITERATIONS: usize = 103;
+
+ async fn decrement(mu: &Arc<Mutex<usize>>, cv: &Arc<Condvar>) {
+ let mut count = mu.lock().await;
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+ *count -= 1;
+ }
+
+ async fn increment(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>, done: Sender<()>) {
+ for _ in 0..TASKS * ITERATIONS {
+ *mu.lock().await += 1;
+ cv.notify_all();
+ }
+
+ done.send(()).expect("Failed to send completion message");
+ }
+
+ async fn decrement_either(
+ mu: Arc<Mutex<usize>>,
+ cv: Arc<Condvar>,
+ alt_mu: Arc<Mutex<usize>>,
+ alt_cv: Arc<Condvar>,
+ done: Sender<()>,
+ ) {
+ for _ in 0..ITERATIONS {
+ select! {
+ () = decrement(&mu, &cv).fuse() => {},
+ () = decrement(&alt_mu, &alt_cv).fuse() => {},
+ }
+ }
+
+ done.send(()).expect("Failed to send completion message");
+ }
+
+ let ex = ThreadPool::new().expect("Failed to create ThreadPool");
+
+ let mu = Arc::new(Mutex::new(0usize));
+ let alt_mu = Arc::new(Mutex::new(0usize));
+
+ let cv = Arc::new(Condvar::new());
+ let alt_cv = Arc::new(Condvar::new());
+
+ let (tx, rx) = channel();
+ for _ in 0..TASKS {
+ ex.spawn_ok(decrement_either(
+ Arc::clone(&mu),
+ Arc::clone(&cv),
+ Arc::clone(&alt_mu),
+ Arc::clone(&alt_cv),
+ tx.clone(),
+ ));
+ }
+
+ ex.spawn_ok(increment(Arc::clone(&mu), Arc::clone(&cv), tx.clone()));
+ ex.spawn_ok(increment(Arc::clone(&alt_mu), Arc::clone(&alt_cv), tx));
+
+ for _ in 0..TASKS + 2 {
+ if let Err(e) = rx.recv_timeout(Duration::from_secs(10)) {
+ panic!("Error while waiting for threads to complete: {}", e);
+ }
+ }
+
+ assert_eq!(
+ *block_on(mu.read_lock()) + *block_on(alt_mu.read_lock()),
+ TASKS * ITERATIONS
+ );
+ assert_eq!(cv.state.load(Ordering::Relaxed), 0);
+ assert_eq!(alt_cv.state.load(Ordering::Relaxed), 0);
+ }
+ #[test]
+ fn notify_all() {
+ const THREADS: usize = 13;
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+ let (tx, rx) = channel();
+
+ let mut threads = Vec::with_capacity(THREADS);
+ for _ in 0..THREADS {
+ let mu2 = mu.clone();
+ let cv2 = cv.clone();
+ let tx2 = tx.clone();
+
+ threads.push(thread::spawn(move || {
+ let mut count = block_on(mu2.lock());
+ *count += 1;
+ if *count == THREADS {
+ tx2.send(()).unwrap();
+ }
+
+ while *count != 0 {
+ count = block_on(cv2.wait(count));
+ }
+ }));
+ }
+
+ mem::drop(tx);
+
+ // Wait till all threads have started.
+ rx.recv_timeout(Duration::from_secs(5)).unwrap();
+
+ let mut count = block_on(mu.lock());
+ *count = 0;
+ mem::drop(count);
+ cv.notify_all();
+
+ for t in threads {
+ t.join().unwrap();
+ }
+ }
+
+ #[test]
+ fn notify_all_single_thread_async() {
+ const TASKS: usize = 13;
+
+ async fn reset(mu: Rc<Mutex<usize>>, cv: Rc<Condvar>) {
+ let mut count = mu.lock().await;
+ *count = 0;
+ cv.notify_all();
+ }
+
+ async fn watcher(mu: Rc<Mutex<usize>>, cv: Rc<Condvar>, spawner: LocalSpawner) {
+ let mut count = mu.lock().await;
+ *count += 1;
+ if *count == TASKS {
+ spawner
+ .spawn_local(reset(mu.clone(), cv.clone()))
+ .expect("Failed to spawn reset task");
+ }
+
+ while *count != 0 {
+ count = cv.wait(count).await;
+ }
+ }
+
+ let mut ex = LocalPool::new();
+ let spawner = ex.spawner();
+
+ let mu = Rc::new(Mutex::new(0));
+ let cv = Rc::new(Condvar::new());
+
+ for _ in 0..TASKS {
+ spawner
+ .spawn_local(watcher(mu.clone(), cv.clone(), spawner.clone()))
+ .expect("Failed to spawn watcher task");
+ }
+
+ ex.run();
+ }
+
+ #[test]
+ fn notify_all_multi_thread_async() {
+ const TASKS: usize = 13;
+
+ async fn reset(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.lock().await;
+ *count = 0;
+ cv.notify_all();
+ }
+
+ async fn watcher(
+ mu: Arc<Mutex<usize>>,
+ cv: Arc<Condvar>,
+ pool: ThreadPool,
+ tx: Sender<()>,
+ ) {
+ let mut count = mu.lock().await;
+ *count += 1;
+ if *count == TASKS {
+ pool.spawn_ok(reset(mu.clone(), cv.clone()));
+ }
+
+ while *count != 0 {
+ count = cv.wait(count).await;
+ }
+
+ tx.send(()).expect("Failed to send completion notification");
+ }
+
+ let pool = ThreadPool::new().expect("Failed to create ThreadPool");
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let (tx, rx) = channel();
+ for _ in 0..TASKS {
+ pool.spawn_ok(watcher(mu.clone(), cv.clone(), pool.clone(), tx.clone()));
+ }
+
+ for _ in 0..TASKS {
+ rx.recv_timeout(Duration::from_secs(5))
+ .expect("Failed to receive completion notification");
+ }
+ }
+
+ #[test]
+ fn wake_all_readers() {
+ async fn read(mu: Arc<Mutex<bool>>, cv: Arc<Condvar>) {
+ let mut ready = mu.read_lock().await;
+ while !*ready {
+ ready = cv.wait_read(ready).await;
+ }
+ }
+
+ let mu = Arc::new(Mutex::new(false));
+ let cv = Arc::new(Condvar::new());
+ let mut readers = [
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ ];
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ // First have all the readers wait on the Condvar.
+ for r in &mut readers {
+ if let Poll::Ready(()) = r.as_mut().poll(&mut cx) {
+ panic!("reader unexpectedly ready");
+ }
+ }
+
+ assert_eq!(cv.state.load(Ordering::Relaxed) & HAS_WAITERS, HAS_WAITERS);
+
+ // Now make the condition true and notify the condvar. Even though we will call notify_one,
+ // all the readers should be woken up.
+ *block_on(mu.lock()) = true;
+ cv.notify_one();
+
+ assert_eq!(cv.state.load(Ordering::Relaxed), 0);
+
+ // All readers should now be able to complete.
+ for r in &mut readers {
+ if r.as_mut().poll(&mut cx).is_pending() {
+ panic!("reader unable to complete");
+ }
+ }
+ }
+
+ #[test]
+ fn cancel_before_notify() {
+ async fn dec(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.lock().await;
+
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+
+ *count -= 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let mut fut1 = Box::pin(dec(mu.clone(), cv.clone()));
+ let mut fut2 = Box::pin(dec(mu.clone(), cv.clone()));
+
+ if let Poll::Ready(()) = fut1.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ if let Poll::Ready(()) = fut2.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ assert_eq!(cv.state.load(Ordering::Relaxed) & HAS_WAITERS, HAS_WAITERS);
+
+ *block_on(mu.lock()) = 2;
+ // Drop fut1 before notifying the cv.
+ mem::drop(fut1);
+ cv.notify_one();
+
+ // fut2 should now be ready to complete.
+ assert_eq!(cv.state.load(Ordering::Relaxed), 0);
+
+ if fut2.as_mut().poll(&mut cx).is_pending() {
+ panic!("future unable to complete");
+ }
+
+ assert_eq!(*block_on(mu.lock()), 1);
+ }
+
+ #[test]
+ fn cancel_after_notify_one() {
+ async fn dec(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.lock().await;
+
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+
+ *count -= 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let mut fut1 = Box::pin(dec(mu.clone(), cv.clone()));
+ let mut fut2 = Box::pin(dec(mu.clone(), cv.clone()));
+
+ if let Poll::Ready(()) = fut1.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ if let Poll::Ready(()) = fut2.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ assert_eq!(cv.state.load(Ordering::Relaxed) & HAS_WAITERS, HAS_WAITERS);
+
+ *block_on(mu.lock()) = 2;
+ cv.notify_one();
+
+ // fut1 should now be ready to complete. Drop it before polling. This should wake up fut2.
+ mem::drop(fut1);
+ assert_eq!(cv.state.load(Ordering::Relaxed), 0);
+
+ if fut2.as_mut().poll(&mut cx).is_pending() {
+ panic!("future unable to complete");
+ }
+
+ assert_eq!(*block_on(mu.lock()), 1);
+ }
+
+ #[test]
+ fn cancel_after_notify_all() {
+ async fn dec(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.lock().await;
+
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+
+ *count -= 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let mut fut1 = Box::pin(dec(mu.clone(), cv.clone()));
+ let mut fut2 = Box::pin(dec(mu.clone(), cv.clone()));
+
+ if let Poll::Ready(()) = fut1.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ if let Poll::Ready(()) = fut2.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ assert_eq!(cv.state.load(Ordering::Relaxed) & HAS_WAITERS, HAS_WAITERS);
+
+ let mut count = block_on(mu.lock());
+ *count = 2;
+
+ // Notify the cv while holding the lock. This should wake up both waiters.
+ cv.notify_all();
+ assert_eq!(cv.state.load(Ordering::Relaxed), 0);
+
+ mem::drop(count);
+
+ mem::drop(fut1);
+
+ if fut2.as_mut().poll(&mut cx).is_pending() {
+ panic!("future unable to complete");
+ }
+
+ assert_eq!(*block_on(mu.lock()), 1);
+ }
+
+ #[test]
+ fn timed_wait() {
+ async fn wait_deadline(
+ mu: Arc<Mutex<usize>>,
+ cv: Arc<Condvar>,
+ timeout: oneshot::Receiver<()>,
+ ) {
+ let mut count = mu.lock().await;
+
+ if *count == 0 {
+ let mut rx = timeout.fuse();
+
+ while *count == 0 {
+ select! {
+ res = rx => {
+ if let Err(e) = res {
+ panic!("Error while receiving timeout notification: {}", e);
+ }
+
+ return;
+ },
+ c = cv.wait(count).fuse() => count = c,
+ }
+ }
+ }
+
+ *count += 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let (tx, rx) = oneshot::channel();
+ let mut wait = Box::pin(wait_deadline(mu.clone(), cv.clone(), rx));
+
+ if let Poll::Ready(()) = wait.as_mut().poll(&mut cx) {
+ panic!("wait_deadline unexpectedly ready");
+ }
+
+ assert_eq!(cv.state.load(Ordering::Relaxed), HAS_WAITERS);
+
+ // Signal the channel, which should cancel the wait.
+ tx.send(()).expect("Failed to send wakeup");
+
+ // Wait for the timer to run out.
+ if wait.as_mut().poll(&mut cx).is_pending() {
+ panic!("wait_deadline unable to complete in time");
+ }
+
+ assert_eq!(cv.state.load(Ordering::Relaxed), 0);
+ assert_eq!(*block_on(mu.lock()), 0);
+ }
+}
diff --git a/common/cros_async/src/sync/mu.rs b/common/cros_async/src/sync/mu.rs
new file mode 100644
index 000000000..e1f408dfa
--- /dev/null
+++ b/common/cros_async/src/sync/mu.rs
@@ -0,0 +1,2305 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ cell::UnsafeCell,
+ hint, mem,
+ ops::{Deref, DerefMut},
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc,
+ },
+ thread::yield_now,
+};
+
+use super::super::sync::waiter::{
+ Kind as WaiterKind, Waiter, WaiterAdapter, WaiterList, WaitingFor,
+};
+
+// Set when the mutex is exclusively locked.
+const LOCKED: usize = 1 << 0;
+// Set when there are one or more threads waiting to acquire the lock.
+const HAS_WAITERS: usize = 1 << 1;
+// Set when a thread has been woken up from the wait queue. Cleared when that thread either acquires
+// the lock or adds itself back into the wait queue. Used to prevent unnecessary wake ups when a
+// thread has been removed from the wait queue but has not gotten CPU time yet.
+const DESIGNATED_WAKER: usize = 1 << 2;
+// Used to provide exclusive access to the `waiters` field in `Mutex`. Should only be held while
+// modifying the waiter list.
+const SPINLOCK: usize = 1 << 3;
+// Set when a thread that wants an exclusive lock adds itself to the wait queue. New threads
+// attempting to acquire a shared lock will be preventing from getting it when this bit is set.
+// However, this bit is ignored once a thread has gone through the wait queue at least once.
+const WRITER_WAITING: usize = 1 << 4;
+// Set when a thread has gone through the wait queue many times but has failed to acquire the lock
+// every time it is woken up. When this bit is set, all other threads are prevented from acquiring
+// the lock until the thread that set the `LONG_WAIT` bit has acquired the lock.
+const LONG_WAIT: usize = 1 << 5;
+// The bit that is added to the mutex state in order to acquire a shared lock. Since more than one
+// thread can acquire a shared lock, we cannot use a single bit. Instead we use all the remaining
+// bits in the state to track the number of threads that have acquired a shared lock.
+const READ_LOCK: usize = 1 << 8;
+// Mask used for checking if any threads currently hold a shared lock.
+const READ_MASK: usize = !0xff;
+
+// The number of times the thread should just spin and attempt to re-acquire the lock.
+const SPIN_THRESHOLD: usize = 7;
+
+// The number of times the thread needs to go through the wait queue before it sets the `LONG_WAIT`
+// bit and forces all other threads to wait for it to acquire the lock. This value is set relatively
+// high so that we don't lose the benefit of having running threads unless it is absolutely
+// necessary.
+const LONG_WAIT_THRESHOLD: usize = 19;
+
+// Common methods between shared and exclusive locks.
+trait Kind {
+ // The bits that must be zero for the thread to acquire this kind of lock. If any of these bits
+ // are not zero then the thread will first spin and retry a few times before adding itself to
+ // the wait queue.
+ fn zero_to_acquire() -> usize;
+
+ // The bit that must be added in order to acquire this kind of lock. This should either be
+ // `LOCKED` or `READ_LOCK`.
+ fn add_to_acquire() -> usize;
+
+ // The bits that should be set when a thread adds itself to the wait queue while waiting to
+ // acquire this kind of lock.
+ fn set_when_waiting() -> usize;
+
+ // The bits that should be cleared when a thread acquires this kind of lock.
+ fn clear_on_acquire() -> usize;
+
+ // The waiter that a thread should use when waiting to acquire this kind of lock.
+ fn new_waiter(raw: &RawMutex) -> Arc<Waiter>;
+}
+
+// A lock type for shared read-only access to the data. More than one thread may hold this kind of
+// lock simultaneously.
+struct Shared;
+
+impl Kind for Shared {
+ fn zero_to_acquire() -> usize {
+ LOCKED | WRITER_WAITING | LONG_WAIT
+ }
+
+ fn add_to_acquire() -> usize {
+ READ_LOCK
+ }
+
+ fn set_when_waiting() -> usize {
+ 0
+ }
+
+ fn clear_on_acquire() -> usize {
+ 0
+ }
+
+ fn new_waiter(raw: &RawMutex) -> Arc<Waiter> {
+ Arc::new(Waiter::new(
+ WaiterKind::Shared,
+ cancel_waiter,
+ raw as *const RawMutex as usize,
+ WaitingFor::Mutex,
+ ))
+ }
+}
+
+// A lock type for mutually exclusive read-write access to the data. Only one thread can hold this
+// kind of lock at a time.
+struct Exclusive;
+
+impl Kind for Exclusive {
+ fn zero_to_acquire() -> usize {
+ LOCKED | READ_MASK | LONG_WAIT
+ }
+
+ fn add_to_acquire() -> usize {
+ LOCKED
+ }
+
+ fn set_when_waiting() -> usize {
+ WRITER_WAITING
+ }
+
+ fn clear_on_acquire() -> usize {
+ WRITER_WAITING
+ }
+
+ fn new_waiter(raw: &RawMutex) -> Arc<Waiter> {
+ Arc::new(Waiter::new(
+ WaiterKind::Exclusive,
+ cancel_waiter,
+ raw as *const RawMutex as usize,
+ WaitingFor::Mutex,
+ ))
+ }
+}
+
+// Scan `waiters` and return the ones that should be woken up. Also returns any bits that should be
+// set in the mutex state when the current thread releases the spin lock protecting the waiter list.
+//
+// If the first waiter is trying to acquire a shared lock, then all waiters in the list that are
+// waiting for a shared lock are also woken up. If any waiters waiting for an exclusive lock are
+// found when iterating through the list, then the returned `usize` contains the `WRITER_WAITING`
+// bit, which should be set when the thread releases the spin lock.
+//
+// If the first waiter is trying to acquire an exclusive lock, then only that waiter is returned and
+// no bits are set in the returned `usize`.
+fn get_wake_list(waiters: &mut WaiterList) -> (WaiterList, usize) {
+ let mut to_wake = WaiterList::new(WaiterAdapter::new());
+ let mut set_on_release = 0;
+ let mut cursor = waiters.front_mut();
+
+ let mut waking_readers = false;
+ while let Some(w) = cursor.get() {
+ match w.kind() {
+ WaiterKind::Exclusive if !waking_readers => {
+ // This is the first waiter and it's a writer. No need to check the other waiters.
+ let waiter = cursor.remove().unwrap();
+ waiter.set_waiting_for(WaitingFor::None);
+ to_wake.push_back(waiter);
+ break;
+ }
+
+ WaiterKind::Shared => {
+ // This is a reader and the first waiter in the list was not a writer so wake up all
+ // the readers in the wait list.
+ let waiter = cursor.remove().unwrap();
+ waiter.set_waiting_for(WaitingFor::None);
+ to_wake.push_back(waiter);
+ waking_readers = true;
+ }
+
+ WaiterKind::Exclusive => {
+ // We found a writer while looking for more readers to wake up. Set the
+ // `WRITER_WAITING` bit to prevent any new readers from acquiring the lock. All
+ // readers currently in the wait list will ignore this bit since they already waited
+ // once.
+ set_on_release |= WRITER_WAITING;
+ cursor.move_next();
+ }
+ }
+ }
+
+ (to_wake, set_on_release)
+}
+
+#[inline]
+fn cpu_relax(iterations: usize) {
+ for _ in 0..iterations {
+ hint::spin_loop();
+ }
+}
+
+pub(crate) struct RawMutex {
+ state: AtomicUsize,
+ waiters: UnsafeCell<WaiterList>,
+}
+
+impl RawMutex {
+ pub fn new() -> RawMutex {
+ RawMutex {
+ state: AtomicUsize::new(0),
+ waiters: UnsafeCell::new(WaiterList::new(WaiterAdapter::new())),
+ }
+ }
+
+ #[inline]
+ pub async fn lock(&self) {
+ match self
+ .state
+ .compare_exchange_weak(0, LOCKED, Ordering::Acquire, Ordering::Relaxed)
+ {
+ Ok(_) => {}
+ Err(oldstate) => {
+ // If any bits that should be zero are not zero or if we fail to acquire the lock
+ // with a single compare_exchange then go through the slow path.
+ if (oldstate & Exclusive::zero_to_acquire()) != 0
+ || self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ (oldstate + Exclusive::add_to_acquire())
+ & !Exclusive::clear_on_acquire(),
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ self.lock_slow::<Exclusive>(0, 0).await;
+ }
+ }
+ }
+ }
+
+ #[inline]
+ pub async fn read_lock(&self) {
+ match self
+ .state
+ .compare_exchange_weak(0, READ_LOCK, Ordering::Acquire, Ordering::Relaxed)
+ {
+ Ok(_) => {}
+ Err(oldstate) => {
+ if (oldstate & Shared::zero_to_acquire()) != 0
+ || self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ (oldstate + Shared::add_to_acquire()) & !Shared::clear_on_acquire(),
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ self.lock_slow::<Shared>(0, 0).await;
+ }
+ }
+ }
+ }
+
+ // Slow path for acquiring the lock. `clear` should contain any bits that need to be cleared
+ // when the lock is acquired. Any bits set in `zero_mask` are cleared from the bits returned by
+ // `K::zero_to_acquire()`.
+ #[cold]
+ async fn lock_slow<K: Kind>(&self, mut clear: usize, zero_mask: usize) {
+ let mut zero_to_acquire = K::zero_to_acquire() & !zero_mask;
+
+ let mut spin_count = 0;
+ let mut wait_count = 0;
+ let mut waiter = None;
+ loop {
+ let oldstate = self.state.load(Ordering::Relaxed);
+ // If all the bits in `zero_to_acquire` are actually zero then try to acquire the lock
+ // directly.
+ if (oldstate & zero_to_acquire) == 0 {
+ if self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ (oldstate + K::add_to_acquire()) & !(clear | K::clear_on_acquire()),
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_ok()
+ {
+ return;
+ }
+ } else if (oldstate & SPINLOCK) == 0 {
+ // The mutex is locked and the spin lock is available. Try to add this thread
+ // to the waiter queue.
+ let w = waiter.get_or_insert_with(|| K::new_waiter(self));
+ w.reset(WaitingFor::Mutex);
+
+ if self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ (oldstate | SPINLOCK | HAS_WAITERS | K::set_when_waiting()) & !clear,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_ok()
+ {
+ let mut set_on_release = 0;
+
+ // Safe because we have acquired the spin lock and it provides exclusive
+ // access to the waiter queue.
+ if wait_count < LONG_WAIT_THRESHOLD {
+ // Add the waiter to the back of the queue.
+ unsafe { (*self.waiters.get()).push_back(w.clone()) };
+ } else {
+ // This waiter has gone through the queue too many times. Put it in the
+ // front of the queue and block all other threads from acquiring the lock
+ // until this one has acquired it at least once.
+ unsafe { (*self.waiters.get()).push_front(w.clone()) };
+
+ // Set the LONG_WAIT bit to prevent all other threads from acquiring the
+ // lock.
+ set_on_release |= LONG_WAIT;
+
+ // Make sure we clear the LONG_WAIT bit when we do finally get the lock.
+ clear |= LONG_WAIT;
+
+ // Since we set the LONG_WAIT bit we shouldn't allow that bit to prevent us
+ // from acquiring the lock.
+ zero_to_acquire &= !LONG_WAIT;
+ }
+
+ // Release the spin lock.
+ let mut state = oldstate;
+ loop {
+ match self.state.compare_exchange_weak(
+ state,
+ (state | set_on_release) & !SPINLOCK,
+ Ordering::Release,
+ Ordering::Relaxed,
+ ) {
+ Ok(_) => break,
+ Err(w) => state = w,
+ }
+ }
+
+ // Now wait until we are woken.
+ w.wait().await;
+
+ // The `DESIGNATED_WAKER` bit gets set when this thread is woken up by the
+ // thread that originally held the lock. While this bit is set, no other waiters
+ // will be woken up so it's important to clear it the next time we try to
+ // acquire the main lock or the spin lock.
+ clear |= DESIGNATED_WAKER;
+
+ // Now that the thread has waited once, we no longer care if there is a writer
+ // waiting. Only the limits of mutual exclusion can prevent us from acquiring
+ // the lock.
+ zero_to_acquire &= !WRITER_WAITING;
+
+ // Reset the spin count since we just went through the wait queue.
+ spin_count = 0;
+
+ // Increment the wait count since we went through the wait queue.
+ wait_count += 1;
+
+ // Skip the `cpu_relax` below.
+ continue;
+ }
+ }
+
+ // Both the lock and the spin lock are held by one or more other threads. First, we'll
+ // spin a few times in case we can acquire the lock or the spin lock. If that fails then
+ // we yield because we might be preventing the threads that do hold the 2 locks from
+ // getting cpu time.
+ if spin_count < SPIN_THRESHOLD {
+ cpu_relax(1 << spin_count);
+ spin_count += 1;
+ } else {
+ yield_now();
+ }
+ }
+ }
+
+ #[inline]
+ pub fn unlock(&self) {
+ // Fast path, if possible. We can directly clear the locked bit since we have exclusive
+ // access to the mutex.
+ let oldstate = self.state.fetch_sub(LOCKED, Ordering::Release);
+
+ // Panic if we just tried to unlock a mutex that wasn't held by this thread. This shouldn't
+ // really be possible since `unlock` is not a public method.
+ debug_assert_eq!(
+ oldstate & READ_MASK,
+ 0,
+ "`unlock` called on mutex held in read-mode"
+ );
+ debug_assert_ne!(
+ oldstate & LOCKED,
+ 0,
+ "`unlock` called on mutex not held in write-mode"
+ );
+
+ if (oldstate & HAS_WAITERS) != 0 && (oldstate & DESIGNATED_WAKER) == 0 {
+ // The oldstate has waiters but no designated waker has been chosen yet.
+ self.unlock_slow();
+ }
+ }
+
+ #[inline]
+ pub fn read_unlock(&self) {
+ // Fast path, if possible. We can directly subtract the READ_LOCK bit since we had
+ // previously added it.
+ let oldstate = self.state.fetch_sub(READ_LOCK, Ordering::Release);
+
+ debug_assert_eq!(
+ oldstate & LOCKED,
+ 0,
+ "`read_unlock` called on mutex held in write-mode"
+ );
+ debug_assert_ne!(
+ oldstate & READ_MASK,
+ 0,
+ "`read_unlock` called on mutex not held in read-mode"
+ );
+
+ if (oldstate & HAS_WAITERS) != 0
+ && (oldstate & DESIGNATED_WAKER) == 0
+ && (oldstate & READ_MASK) == READ_LOCK
+ {
+ // There are waiters, no designated waker has been chosen yet, and the last reader is
+ // unlocking so we have to take the slow path.
+ self.unlock_slow();
+ }
+ }
+
+ #[cold]
+ fn unlock_slow(&self) {
+ let mut spin_count = 0;
+
+ loop {
+ let oldstate = self.state.load(Ordering::Relaxed);
+ if (oldstate & HAS_WAITERS) == 0 || (oldstate & DESIGNATED_WAKER) != 0 {
+ // No more waiters or a designated waker has been chosen. Nothing left for us to do.
+ return;
+ } else if (oldstate & SPINLOCK) == 0 {
+ // The spin lock is not held by another thread. Try to acquire it. Also set the
+ // `DESIGNATED_WAKER` bit since we are likely going to wake up one or more threads.
+ if self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ oldstate | SPINLOCK | DESIGNATED_WAKER,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_ok()
+ {
+ // Acquired the spinlock. Try to wake a waiter. We may also end up wanting to
+ // clear the HAS_WAITER and DESIGNATED_WAKER bits so start collecting the bits
+ // to be cleared.
+ let mut clear = SPINLOCK;
+
+ // Safe because the spinlock guarantees exclusive access to the waiter list and
+ // the reference does not escape this function.
+ let waiters = unsafe { &mut *self.waiters.get() };
+ let (wake_list, set_on_release) = get_wake_list(waiters);
+
+ // If the waiter list is now empty, clear the HAS_WAITERS bit.
+ if waiters.is_empty() {
+ clear |= HAS_WAITERS;
+ }
+
+ if wake_list.is_empty() {
+ // Since we are not going to wake any waiters clear the DESIGNATED_WAKER bit
+ // that we set when we acquired the spin lock.
+ clear |= DESIGNATED_WAKER;
+ }
+
+ // Release the spin lock and clear any other bits as necessary. Also, set any
+ // bits returned by `get_wake_list`. For now, this is just the `WRITER_WAITING`
+ // bit, which needs to be set when we are waking up a bunch of readers and there
+ // are still writers in the wait queue. This will prevent any readers that
+ // aren't in `wake_list` from acquiring the read lock.
+ let mut state = oldstate;
+ loop {
+ match self.state.compare_exchange_weak(
+ state,
+ (state | set_on_release) & !clear,
+ Ordering::Release,
+ Ordering::Relaxed,
+ ) {
+ Ok(_) => break,
+ Err(w) => state = w,
+ }
+ }
+
+ // Now wake the waiters, if any.
+ for w in wake_list {
+ w.wake();
+ }
+
+ // We're done.
+ return;
+ }
+ }
+
+ // Spin and try again. It's ok to block here as we have already released the lock.
+ if spin_count < SPIN_THRESHOLD {
+ cpu_relax(1 << spin_count);
+ spin_count += 1;
+ } else {
+ yield_now();
+ }
+ }
+ }
+
+ fn cancel_waiter(&self, waiter: &Waiter, wake_next: bool) {
+ let mut oldstate = self.state.load(Ordering::Relaxed);
+ while oldstate & SPINLOCK != 0
+ || self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ oldstate | SPINLOCK,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ hint::spin_loop();
+ oldstate = self.state.load(Ordering::Relaxed);
+ }
+
+ // Safe because the spin lock provides exclusive access and the reference does not escape
+ // this function.
+ let waiters = unsafe { &mut *self.waiters.get() };
+
+ let mut clear = SPINLOCK;
+
+ // If we are about to remove the first waiter in the wait list, then clear the LONG_WAIT
+ // bit. Also clear the bit if we are going to be waking some other waiters. In this case the
+ // waiter that set the bit may have already been removed from the waiter list (and could be
+ // the one that is currently being dropped). If it is still in the waiter list then clearing
+ // this bit may starve it for one more iteration through the lock_slow() loop, whereas not
+ // clearing this bit could cause a deadlock if the waiter that set it is the one that is
+ // being dropped.
+ if wake_next
+ || waiters
+ .front()
+ .get()
+ .map(|front| std::ptr::eq(front, waiter))
+ .unwrap_or(false)
+ {
+ clear |= LONG_WAIT;
+ }
+
+ let waiting_for = waiter.is_waiting_for();
+
+ // Don't drop the old waiter while holding the spin lock.
+ let old_waiter = if waiter.is_linked() && waiting_for == WaitingFor::Mutex {
+ // We know that the waiter is still linked and is waiting for the mutex, which
+ // guarantees that it is still linked into `self.waiters`.
+ let mut cursor = unsafe { waiters.cursor_mut_from_ptr(waiter as *const Waiter) };
+ cursor.remove()
+ } else {
+ None
+ };
+
+ let (wake_list, set_on_release) = if wake_next || waiting_for == WaitingFor::None {
+ // Either the waiter was already woken or it's been removed from the mutex's waiter
+ // list and is going to be woken. Either way, we need to wake up another thread.
+ get_wake_list(waiters)
+ } else {
+ (WaiterList::new(WaiterAdapter::new()), 0)
+ };
+
+ if waiters.is_empty() {
+ clear |= HAS_WAITERS;
+ }
+
+ if wake_list.is_empty() {
+ // We're not waking any other threads so clear the DESIGNATED_WAKER bit. In the worst
+ // case this leads to an additional thread being woken up but we risk a deadlock if we
+ // don't clear it.
+ clear |= DESIGNATED_WAKER;
+ }
+
+ if let WaiterKind::Exclusive = waiter.kind() {
+ // The waiter being dropped is a writer so clear the writer waiting bit for now. If we
+ // found more writers in the list while fetching waiters to wake up then this bit will
+ // be set again via `set_on_release`.
+ clear |= WRITER_WAITING;
+ }
+
+ while self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ (oldstate & !clear) | set_on_release,
+ Ordering::Release,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ hint::spin_loop();
+ oldstate = self.state.load(Ordering::Relaxed);
+ }
+
+ for w in wake_list {
+ w.wake();
+ }
+
+ mem::drop(old_waiter);
+ }
+}
+
+unsafe impl Send for RawMutex {}
+unsafe impl Sync for RawMutex {}
+
+fn cancel_waiter(raw: usize, waiter: &Waiter, wake_next: bool) {
+ let raw_mutex = raw as *const RawMutex;
+
+ // Safe because the thread that owns the waiter that is being canceled must
+ // also own a reference to the mutex, which ensures that this pointer is
+ // valid.
+ unsafe { (*raw_mutex).cancel_waiter(waiter, wake_next) }
+}
+
+/// A high-level primitive that provides safe, mutable access to a shared resource.
+///
+/// Unlike more traditional mutexes, `Mutex` can safely provide both shared, immutable access (via
+/// `read_lock()`) as well as exclusive, mutable access (via `lock()`) to an underlying resource
+/// with no loss of performance.
+///
+/// # Poisoning
+///
+/// `Mutex` does not support lock poisoning so if a thread panics while holding the lock, the
+/// poisoned data will be accessible by other threads in your program. If you need to guarantee that
+/// other threads cannot access poisoned data then you may wish to wrap this `Mutex` inside another
+/// type that provides the poisoning feature. See the implementation of `std::sync::Mutex` for an
+/// example of this.
+///
+///
+/// # Fairness
+///
+/// This `Mutex` implementation does not guarantee that threads will acquire the lock in the same
+/// order that they call `lock()` or `read_lock()`. However it will attempt to prevent long-term
+/// starvation: if a thread repeatedly fails to acquire the lock beyond a threshold then all other
+/// threads will fail to acquire the lock until the starved thread has acquired it.
+///
+/// Similarly, this `Mutex` will attempt to balance reader and writer threads: once there is a
+/// writer thread waiting to acquire the lock no new reader threads will be allowed to acquire it.
+/// However, any reader threads that were already waiting will still be allowed to acquire it.
+///
+/// # Examples
+///
+/// ```edition2018
+/// use std::sync::Arc;
+/// use std::thread;
+/// use std::sync::mpsc::channel;
+///
+/// use cros_async::{block_on, sync::Mutex};
+///
+/// const N: usize = 10;
+///
+/// // Spawn a few threads to increment a shared variable (non-atomically), and
+/// // let the main thread know once all increments are done.
+/// //
+/// // Here we're using an Arc to share memory among threads, and the data inside
+/// // the Arc is protected with a mutex.
+/// let data = Arc::new(Mutex::new(0));
+///
+/// let (tx, rx) = channel();
+/// for _ in 0..N {
+/// let (data, tx) = (Arc::clone(&data), tx.clone());
+/// thread::spawn(move || {
+/// // The shared state can only be accessed once the lock is held.
+/// // Our non-atomic increment is safe because we're the only thread
+/// // which can access the shared state when the lock is held.
+/// let mut data = block_on(data.lock());
+/// *data += 1;
+/// if *data == N {
+/// tx.send(()).unwrap();
+/// }
+/// // the lock is unlocked here when `data` goes out of scope.
+/// });
+/// }
+///
+/// rx.recv().unwrap();
+/// ```
+#[repr(align(128))]
+pub struct Mutex<T: ?Sized> {
+ raw: RawMutex,
+ value: UnsafeCell<T>,
+}
+
+impl<T> Mutex<T> {
+ /// Create a new, unlocked `Mutex` ready for use.
+ pub fn new(v: T) -> Mutex<T> {
+ Mutex {
+ raw: RawMutex::new(),
+ value: UnsafeCell::new(v),
+ }
+ }
+
+ /// Consume the `Mutex` and return the contained value. This method does not perform any locking
+ /// as the compiler will guarantee that there are no other references to `self` and the caller
+ /// owns the `Mutex`.
+ pub fn into_inner(self) -> T {
+ // Don't need to acquire the lock because the compiler guarantees that there are
+ // no references to `self`.
+ self.value.into_inner()
+ }
+}
+
+impl<T: ?Sized> Mutex<T> {
+ /// Acquires exclusive, mutable access to the resource protected by the `Mutex`, blocking the
+ /// current thread until it is able to do so. Upon returning, the current thread will be the
+ /// only thread with access to the resource. The `Mutex` will be released when the returned
+ /// `MutexGuard` is dropped.
+ ///
+ /// Calling `lock()` while holding a `MutexGuard` or a `MutexReadGuard` will cause a deadlock.
+ ///
+ /// Callers that are not in an async context may wish to use the `block_on` method to block the
+ /// thread until the `Mutex` is acquired.
+ #[inline]
+ pub async fn lock(&self) -> MutexGuard<'_, T> {
+ self.raw.lock().await;
+
+ // Safe because we have exclusive access to `self.value`.
+ MutexGuard {
+ mu: self,
+ value: unsafe { &mut *self.value.get() },
+ }
+ }
+
+ /// Acquires shared, immutable access to the resource protected by the `Mutex`, blocking the
+ /// current thread until it is able to do so. Upon returning there may be other threads that
+ /// also have immutable access to the resource but there will not be any threads that have
+ /// mutable access to the resource. When the returned `MutexReadGuard` is dropped the thread
+ /// releases its access to the resource.
+ ///
+ /// Calling `read_lock()` while holding a `MutexReadGuard` may deadlock. Calling `read_lock()`
+ /// while holding a `MutexGuard` will deadlock.
+ ///
+ /// Callers that are not in an async context may wish to use the `block_on` method to block the
+ /// thread until the `Mutex` is acquired.
+ #[inline]
+ pub async fn read_lock(&self) -> MutexReadGuard<'_, T> {
+ self.raw.read_lock().await;
+
+ // Safe because we have shared read-only access to `self.value`.
+ MutexReadGuard {
+ mu: self,
+ value: unsafe { &*self.value.get() },
+ }
+ }
+
+ // Called from `Condvar::wait` when the thread wants to reacquire the lock.
+ #[inline]
+ pub(crate) async fn lock_from_cv(&self) -> MutexGuard<'_, T> {
+ self.raw.lock_slow::<Exclusive>(DESIGNATED_WAKER, 0).await;
+
+ // Safe because we have exclusive access to `self.value`.
+ MutexGuard {
+ mu: self,
+ value: unsafe { &mut *self.value.get() },
+ }
+ }
+
+ // Like `lock_from_cv` but for acquiring a shared lock.
+ #[inline]
+ pub(crate) async fn read_lock_from_cv(&self) -> MutexReadGuard<'_, T> {
+ // Threads that have waited in the Condvar's waiter list don't have to care if there is a
+ // writer waiting since they have already waited once.
+ self.raw
+ .lock_slow::<Shared>(DESIGNATED_WAKER, WRITER_WAITING)
+ .await;
+
+ // Safe because we have exclusive access to `self.value`.
+ MutexReadGuard {
+ mu: self,
+ value: unsafe { &*self.value.get() },
+ }
+ }
+
+ #[inline]
+ fn unlock(&self) {
+ self.raw.unlock();
+ }
+
+ #[inline]
+ fn read_unlock(&self) {
+ self.raw.read_unlock();
+ }
+
+ pub fn get_mut(&mut self) -> &mut T {
+ // Safe because the compiler statically guarantees that are no other references to `self`.
+ // This is also why we don't need to acquire the lock first.
+ unsafe { &mut *self.value.get() }
+ }
+}
+
+unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
+unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
+
+impl<T: ?Sized + Default> Default for Mutex<T> {
+ fn default() -> Self {
+ Self::new(Default::default())
+ }
+}
+
+impl<T> From<T> for Mutex<T> {
+ fn from(source: T) -> Self {
+ Self::new(source)
+ }
+}
+
+/// An RAII implementation of a "scoped exclusive lock" for a `Mutex`. When this structure is
+/// dropped, the lock will be released. The resource protected by the `Mutex` can be accessed via
+/// the `Deref` and `DerefMut` implementations of this structure.
+pub struct MutexGuard<'a, T: ?Sized + 'a> {
+ mu: &'a Mutex<T>,
+ value: &'a mut T,
+}
+
+impl<'a, T: ?Sized> MutexGuard<'a, T> {
+ pub(crate) fn into_inner(self) -> &'a Mutex<T> {
+ self.mu
+ }
+
+ pub(crate) fn as_raw_mutex(&self) -> &RawMutex {
+ &self.mu.raw
+ }
+}
+
+impl<'a, T: ?Sized> Deref for MutexGuard<'a, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ self.value
+ }
+}
+
+impl<'a, T: ?Sized> DerefMut for MutexGuard<'a, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.value
+ }
+}
+
+impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> {
+ fn drop(&mut self) {
+ self.mu.unlock()
+ }
+}
+
+/// An RAII implementation of a "scoped shared lock" for a `Mutex`. When this structure is dropped,
+/// the lock will be released. The resource protected by the `Mutex` can be accessed via the `Deref`
+/// implementation of this structure.
+pub struct MutexReadGuard<'a, T: ?Sized + 'a> {
+ mu: &'a Mutex<T>,
+ value: &'a T,
+}
+
+impl<'a, T: ?Sized> MutexReadGuard<'a, T> {
+ pub(crate) fn into_inner(self) -> &'a Mutex<T> {
+ self.mu
+ }
+
+ pub(crate) fn as_raw_mutex(&self) -> &RawMutex {
+ &self.mu.raw
+ }
+}
+
+impl<'a, T: ?Sized> Deref for MutexReadGuard<'a, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ self.value
+ }
+}
+
+impl<'a, T: ?Sized> Drop for MutexReadGuard<'a, T> {
+ fn drop(&mut self) {
+ self.mu.read_unlock()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use std::{
+ future::Future,
+ mem,
+ pin::Pin,
+ rc::Rc,
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ mpsc::{channel, Sender},
+ Arc,
+ },
+ task::{Context, Poll, Waker},
+ thread,
+ time::Duration,
+ };
+
+ use futures::{
+ channel::oneshot,
+ pending, select,
+ task::{waker_ref, ArcWake},
+ FutureExt,
+ };
+ use futures_executor::{LocalPool, ThreadPool};
+ use futures_util::task::LocalSpawnExt;
+
+ use super::super::super::{
+ block_on,
+ sync::{Condvar, SpinLock},
+ };
+
+ #[derive(Debug, Eq, PartialEq)]
+ struct NonCopy(u32);
+
+ // Dummy waker used when we want to manually drive futures.
+ struct TestWaker;
+ impl ArcWake for TestWaker {
+ fn wake_by_ref(_arc_self: &Arc<Self>) {}
+ }
+
+ #[test]
+ fn it_works() {
+ let mu = Mutex::new(NonCopy(13));
+
+ assert_eq!(*block_on(mu.lock()), NonCopy(13));
+ }
+
+ #[test]
+ fn smoke() {
+ let mu = Mutex::new(NonCopy(7));
+
+ mem::drop(block_on(mu.lock()));
+ mem::drop(block_on(mu.lock()));
+ }
+
+ #[test]
+ fn rw_smoke() {
+ let mu = Mutex::new(NonCopy(7));
+
+ mem::drop(block_on(mu.lock()));
+ mem::drop(block_on(mu.read_lock()));
+ mem::drop((block_on(mu.read_lock()), block_on(mu.read_lock())));
+ mem::drop(block_on(mu.lock()));
+ }
+
+ #[test]
+ fn async_smoke() {
+ async fn lock(mu: Rc<Mutex<NonCopy>>) {
+ mu.lock().await;
+ }
+
+ async fn read_lock(mu: Rc<Mutex<NonCopy>>) {
+ mu.read_lock().await;
+ }
+
+ async fn double_read_lock(mu: Rc<Mutex<NonCopy>>) {
+ let first = mu.read_lock().await;
+ mu.read_lock().await;
+
+ // Make sure first lives past the second read lock.
+ first.as_raw_mutex();
+ }
+
+ let mu = Rc::new(Mutex::new(NonCopy(7)));
+
+ let mut ex = LocalPool::new();
+ let spawner = ex.spawner();
+
+ spawner
+ .spawn_local(lock(Rc::clone(&mu)))
+ .expect("Failed to spawn future");
+ spawner
+ .spawn_local(read_lock(Rc::clone(&mu)))
+ .expect("Failed to spawn future");
+ spawner
+ .spawn_local(double_read_lock(Rc::clone(&mu)))
+ .expect("Failed to spawn future");
+ spawner
+ .spawn_local(lock(Rc::clone(&mu)))
+ .expect("Failed to spawn future");
+
+ ex.run();
+ }
+
+ #[test]
+ fn send() {
+ let mu = Mutex::new(NonCopy(19));
+
+ thread::spawn(move || {
+ let value = block_on(mu.lock());
+ assert_eq!(*value, NonCopy(19));
+ })
+ .join()
+ .unwrap();
+ }
+
+ #[test]
+ fn arc_nested() {
+ // Tests nested mutexes and access to underlying data.
+ let mu = Mutex::new(1);
+ let arc = Arc::new(Mutex::new(mu));
+ thread::spawn(move || {
+ let nested = block_on(arc.lock());
+ let lock2 = block_on(nested.lock());
+ assert_eq!(*lock2, 1);
+ })
+ .join()
+ .unwrap();
+ }
+
+ #[test]
+ fn arc_access_in_unwind() {
+ let arc = Arc::new(Mutex::new(1));
+ let arc2 = arc.clone();
+ thread::spawn(move || {
+ struct Unwinder {
+ i: Arc<Mutex<i32>>,
+ }
+ impl Drop for Unwinder {
+ fn drop(&mut self) {
+ *block_on(self.i.lock()) += 1;
+ }
+ }
+ let _u = Unwinder { i: arc2 };
+ panic!();
+ })
+ .join()
+ .expect_err("thread did not panic");
+ let lock = block_on(arc.lock());
+ assert_eq!(*lock, 2);
+ }
+
+ #[test]
+ fn unsized_value() {
+ let mutex: &Mutex<[i32]> = &Mutex::new([1, 2, 3]);
+ {
+ let b = &mut *block_on(mutex.lock());
+ b[0] = 4;
+ b[2] = 5;
+ }
+ let expected: &[i32] = &[4, 2, 5];
+ assert_eq!(&*block_on(mutex.lock()), expected);
+ }
+ #[test]
+ fn high_contention() {
+ const THREADS: usize = 17;
+ const ITERATIONS: usize = 103;
+
+ let mut threads = Vec::with_capacity(THREADS);
+
+ let mu = Arc::new(Mutex::new(0usize));
+ for _ in 0..THREADS {
+ let mu2 = mu.clone();
+ threads.push(thread::spawn(move || {
+ for _ in 0..ITERATIONS {
+ *block_on(mu2.lock()) += 1;
+ }
+ }));
+ }
+
+ for t in threads.into_iter() {
+ t.join().unwrap();
+ }
+
+ assert_eq!(*block_on(mu.read_lock()), THREADS * ITERATIONS);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn high_contention_with_cancel() {
+ const TASKS: usize = 17;
+ const ITERATIONS: usize = 103;
+
+ async fn increment(mu: Arc<Mutex<usize>>, alt_mu: Arc<Mutex<usize>>, tx: Sender<()>) {
+ for _ in 0..ITERATIONS {
+ select! {
+ mut count = mu.lock().fuse() => *count += 1,
+ mut count = alt_mu.lock().fuse() => *count += 1,
+ }
+ }
+ tx.send(()).expect("Failed to send completion signal");
+ }
+
+ let ex = ThreadPool::new().expect("Failed to create ThreadPool");
+
+ let mu = Arc::new(Mutex::new(0usize));
+ let alt_mu = Arc::new(Mutex::new(0usize));
+
+ let (tx, rx) = channel();
+ for _ in 0..TASKS {
+ ex.spawn_ok(increment(Arc::clone(&mu), Arc::clone(&alt_mu), tx.clone()));
+ }
+
+ for _ in 0..TASKS {
+ if let Err(e) = rx.recv_timeout(Duration::from_secs(10)) {
+ panic!("Error while waiting for threads to complete: {}", e);
+ }
+ }
+
+ assert_eq!(
+ *block_on(mu.read_lock()) + *block_on(alt_mu.read_lock()),
+ TASKS * ITERATIONS
+ );
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ assert_eq!(alt_mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn single_thread_async() {
+ const TASKS: usize = 17;
+ const ITERATIONS: usize = 103;
+
+ // Async closures are unstable.
+ async fn increment(mu: Rc<Mutex<usize>>) {
+ for _ in 0..ITERATIONS {
+ *mu.lock().await += 1;
+ }
+ }
+
+ let mut ex = LocalPool::new();
+ let spawner = ex.spawner();
+
+ let mu = Rc::new(Mutex::new(0usize));
+ for _ in 0..TASKS {
+ spawner
+ .spawn_local(increment(Rc::clone(&mu)))
+ .expect("Failed to spawn task");
+ }
+
+ ex.run();
+
+ assert_eq!(*block_on(mu.read_lock()), TASKS * ITERATIONS);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn multi_thread_async() {
+ const TASKS: usize = 17;
+ const ITERATIONS: usize = 103;
+
+ // Async closures are unstable.
+ async fn increment(mu: Arc<Mutex<usize>>, tx: Sender<()>) {
+ for _ in 0..ITERATIONS {
+ *mu.lock().await += 1;
+ }
+ tx.send(()).expect("Failed to send completion signal");
+ }
+
+ let ex = ThreadPool::new().expect("Failed to create ThreadPool");
+
+ let mu = Arc::new(Mutex::new(0usize));
+ let (tx, rx) = channel();
+ for _ in 0..TASKS {
+ ex.spawn_ok(increment(Arc::clone(&mu), tx.clone()));
+ }
+
+ for _ in 0..TASKS {
+ rx.recv_timeout(Duration::from_secs(5))
+ .expect("Failed to receive completion signal");
+ }
+ assert_eq!(*block_on(mu.read_lock()), TASKS * ITERATIONS);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn get_mut() {
+ let mut mu = Mutex::new(NonCopy(13));
+ *mu.get_mut() = NonCopy(17);
+
+ assert_eq!(mu.into_inner(), NonCopy(17));
+ }
+
+ #[test]
+ fn into_inner() {
+ let mu = Mutex::new(NonCopy(29));
+ assert_eq!(mu.into_inner(), NonCopy(29));
+ }
+
+ #[test]
+ fn into_inner_drop() {
+ struct NeedsDrop(Arc<AtomicUsize>);
+ impl Drop for NeedsDrop {
+ fn drop(&mut self) {
+ self.0.fetch_add(1, Ordering::AcqRel);
+ }
+ }
+
+ let value = Arc::new(AtomicUsize::new(0));
+ let needs_drop = Mutex::new(NeedsDrop(value.clone()));
+ assert_eq!(value.load(Ordering::Acquire), 0);
+
+ {
+ let inner = needs_drop.into_inner();
+ assert_eq!(inner.0.load(Ordering::Acquire), 0);
+ }
+
+ assert_eq!(value.load(Ordering::Acquire), 1);
+ }
+
+ #[test]
+ fn rw_arc() {
+ const THREADS: isize = 7;
+ const ITERATIONS: isize = 13;
+
+ let mu = Arc::new(Mutex::new(0isize));
+ let mu2 = mu.clone();
+
+ let (tx, rx) = channel();
+ thread::spawn(move || {
+ let mut guard = block_on(mu2.lock());
+ for _ in 0..ITERATIONS {
+ let tmp = *guard;
+ *guard = -1;
+ thread::yield_now();
+ *guard = tmp + 1;
+ }
+ tx.send(()).unwrap();
+ });
+
+ let mut readers = Vec::with_capacity(10);
+ for _ in 0..THREADS {
+ let mu3 = mu.clone();
+ let handle = thread::spawn(move || {
+ let guard = block_on(mu3.read_lock());
+ assert!(*guard >= 0);
+ });
+
+ readers.push(handle);
+ }
+
+ // Wait for the readers to finish their checks.
+ for r in readers {
+ r.join().expect("One or more readers saw a negative value");
+ }
+
+ // Wait for the writer to finish.
+ rx.recv_timeout(Duration::from_secs(5)).unwrap();
+ assert_eq!(*block_on(mu.read_lock()), ITERATIONS);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn rw_single_thread_async() {
+ // A Future that returns `Poll::pending` the first time it is polled and `Poll::Ready` every
+ // time after that.
+ struct TestFuture {
+ polled: bool,
+ waker: Arc<SpinLock<Option<Waker>>>,
+ }
+
+ impl Future for TestFuture {
+ type Output = ();
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ if self.polled {
+ Poll::Ready(())
+ } else {
+ self.polled = true;
+ *self.waker.lock() = Some(cx.waker().clone());
+ Poll::Pending
+ }
+ }
+ }
+
+ fn wake_future(waker: Arc<SpinLock<Option<Waker>>>) {
+ loop {
+ if let Some(w) = waker.lock().take() {
+ w.wake();
+ return;
+ }
+
+ // This sleep cannot be moved into an else branch because we would end up holding
+ // the lock while sleeping due to rust's drop ordering rules.
+ thread::sleep(Duration::from_millis(10));
+ }
+ }
+
+ async fn writer(mu: Rc<Mutex<isize>>) {
+ let mut guard = mu.lock().await;
+ for _ in 0..ITERATIONS {
+ let tmp = *guard;
+ *guard = -1;
+ let waker = Arc::new(SpinLock::new(None));
+ let waker2 = Arc::clone(&waker);
+ thread::spawn(move || wake_future(waker2));
+ let fut = TestFuture {
+ polled: false,
+ waker,
+ };
+ fut.await;
+ *guard = tmp + 1;
+ }
+ }
+
+ async fn reader(mu: Rc<Mutex<isize>>) {
+ let guard = mu.read_lock().await;
+ assert!(*guard >= 0);
+ }
+
+ const TASKS: isize = 7;
+ const ITERATIONS: isize = 13;
+
+ let mu = Rc::new(Mutex::new(0isize));
+ let mut ex = LocalPool::new();
+ let spawner = ex.spawner();
+
+ spawner
+ .spawn_local(writer(Rc::clone(&mu)))
+ .expect("Failed to spawn writer");
+
+ for _ in 0..TASKS {
+ spawner
+ .spawn_local(reader(Rc::clone(&mu)))
+ .expect("Failed to spawn reader");
+ }
+
+ ex.run();
+
+ assert_eq!(*block_on(mu.read_lock()), ITERATIONS);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn rw_multi_thread_async() {
+ async fn writer(mu: Arc<Mutex<isize>>, tx: Sender<()>) {
+ let mut guard = mu.lock().await;
+ for _ in 0..ITERATIONS {
+ let tmp = *guard;
+ *guard = -1;
+ thread::yield_now();
+ *guard = tmp + 1;
+ }
+
+ mem::drop(guard);
+ tx.send(()).unwrap();
+ }
+
+ async fn reader(mu: Arc<Mutex<isize>>, tx: Sender<()>) {
+ let guard = mu.read_lock().await;
+ assert!(*guard >= 0);
+
+ mem::drop(guard);
+ tx.send(()).expect("Failed to send completion message");
+ }
+
+ const TASKS: isize = 7;
+ const ITERATIONS: isize = 13;
+
+ let mu = Arc::new(Mutex::new(0isize));
+ let ex = ThreadPool::new().expect("Failed to create ThreadPool");
+
+ let (txw, rxw) = channel();
+ ex.spawn_ok(writer(Arc::clone(&mu), txw));
+
+ let (txr, rxr) = channel();
+ for _ in 0..TASKS {
+ ex.spawn_ok(reader(Arc::clone(&mu), txr.clone()));
+ }
+
+ // Wait for the readers to finish their checks.
+ for _ in 0..TASKS {
+ rxr.recv_timeout(Duration::from_secs(5))
+ .expect("Failed to receive completion message from reader");
+ }
+
+ // Wait for the writer to finish.
+ rxw.recv_timeout(Duration::from_secs(5))
+ .expect("Failed to receive completion message from writer");
+
+ assert_eq!(*block_on(mu.read_lock()), ITERATIONS);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn wake_all_readers() {
+ async fn read(mu: Arc<Mutex<()>>) {
+ let g = mu.read_lock().await;
+ pending!();
+ mem::drop(g);
+ }
+
+ async fn write(mu: Arc<Mutex<()>>) {
+ mu.lock().await;
+ }
+
+ let mu = Arc::new(Mutex::new(()));
+ let mut futures: [Pin<Box<dyn Future<Output = ()>>>; 5] = [
+ Box::pin(read(mu.clone())),
+ Box::pin(read(mu.clone())),
+ Box::pin(read(mu.clone())),
+ Box::pin(write(mu.clone())),
+ Box::pin(read(mu.clone())),
+ ];
+ const NUM_READERS: usize = 4;
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ // Acquire the lock so that the futures cannot get it.
+ let g = block_on(mu.lock());
+
+ for r in &mut futures {
+ if let Poll::Ready(()) = r.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & HAS_WAITERS,
+ HAS_WAITERS
+ );
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING,
+ WRITER_WAITING
+ );
+
+ // Drop the lock. This should allow all readers to make progress. Since they already waited
+ // once they should ignore the WRITER_WAITING bit that is currently set.
+ mem::drop(g);
+ for r in &mut futures {
+ if let Poll::Ready(()) = r.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ }
+
+ // Check that all readers were able to acquire the lock.
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & READ_MASK,
+ READ_LOCK * NUM_READERS
+ );
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING,
+ WRITER_WAITING
+ );
+
+ let mut needs_poll = None;
+
+ // All the readers can now finish but the writer needs to be polled again.
+ for (i, r) in futures.iter_mut().enumerate() {
+ match r.as_mut().poll(&mut cx) {
+ Poll::Ready(()) => {}
+ Poll::Pending => {
+ if needs_poll.is_some() {
+ panic!("More than one future unable to complete");
+ }
+ needs_poll = Some(i);
+ }
+ }
+ }
+
+ if futures[needs_poll.expect("Writer unexpectedly able to complete")]
+ .as_mut()
+ .poll(&mut cx)
+ .is_pending()
+ {
+ panic!("Writer unable to complete");
+ }
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn long_wait() {
+ async fn tight_loop(mu: Arc<Mutex<bool>>) {
+ loop {
+ let ready = mu.lock().await;
+ if *ready {
+ break;
+ }
+ pending!();
+ }
+ }
+
+ async fn mark_ready(mu: Arc<Mutex<bool>>) {
+ *mu.lock().await = true;
+ }
+
+ let mu = Arc::new(Mutex::new(false));
+ let mut tl = Box::pin(tight_loop(mu.clone()));
+ let mut mark = Box::pin(mark_ready(mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ for _ in 0..=LONG_WAIT_THRESHOLD {
+ if let Poll::Ready(()) = tl.as_mut().poll(&mut cx) {
+ panic!("tight_loop unexpectedly ready");
+ }
+
+ if let Poll::Ready(()) = mark.as_mut().poll(&mut cx) {
+ panic!("mark_ready unexpectedly ready");
+ }
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed),
+ LOCKED | HAS_WAITERS | WRITER_WAITING | LONG_WAIT
+ );
+
+ // This time the tight loop will fail to acquire the lock.
+ if let Poll::Ready(()) = tl.as_mut().poll(&mut cx) {
+ panic!("tight_loop unexpectedly ready");
+ }
+
+ // Which will finally allow the mark_ready function to make progress.
+ if mark.as_mut().poll(&mut cx).is_pending() {
+ panic!("mark_ready not able to make progress");
+ }
+
+ // Now the tight loop will finish.
+ if tl.as_mut().poll(&mut cx).is_pending() {
+ panic!("tight_loop not able to finish");
+ }
+
+ assert!(*block_on(mu.lock()));
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn cancel_long_wait_before_wake() {
+ async fn tight_loop(mu: Arc<Mutex<bool>>) {
+ loop {
+ let ready = mu.lock().await;
+ if *ready {
+ break;
+ }
+ pending!();
+ }
+ }
+
+ async fn mark_ready(mu: Arc<Mutex<bool>>) {
+ *mu.lock().await = true;
+ }
+
+ let mu = Arc::new(Mutex::new(false));
+ let mut tl = Box::pin(tight_loop(mu.clone()));
+ let mut mark = Box::pin(mark_ready(mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ for _ in 0..=LONG_WAIT_THRESHOLD {
+ if let Poll::Ready(()) = tl.as_mut().poll(&mut cx) {
+ panic!("tight_loop unexpectedly ready");
+ }
+
+ if let Poll::Ready(()) = mark.as_mut().poll(&mut cx) {
+ panic!("mark_ready unexpectedly ready");
+ }
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed),
+ LOCKED | HAS_WAITERS | WRITER_WAITING | LONG_WAIT
+ );
+
+ // Now drop the mark_ready future, which should clear the LONG_WAIT bit.
+ mem::drop(mark);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), LOCKED);
+
+ mem::drop(tl);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn cancel_long_wait_after_wake() {
+ async fn tight_loop(mu: Arc<Mutex<bool>>) {
+ loop {
+ let ready = mu.lock().await;
+ if *ready {
+ break;
+ }
+ pending!();
+ }
+ }
+
+ async fn mark_ready(mu: Arc<Mutex<bool>>) {
+ *mu.lock().await = true;
+ }
+
+ let mu = Arc::new(Mutex::new(false));
+ let mut tl = Box::pin(tight_loop(mu.clone()));
+ let mut mark = Box::pin(mark_ready(mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ for _ in 0..=LONG_WAIT_THRESHOLD {
+ if let Poll::Ready(()) = tl.as_mut().poll(&mut cx) {
+ panic!("tight_loop unexpectedly ready");
+ }
+
+ if let Poll::Ready(()) = mark.as_mut().poll(&mut cx) {
+ panic!("mark_ready unexpectedly ready");
+ }
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed),
+ LOCKED | HAS_WAITERS | WRITER_WAITING | LONG_WAIT
+ );
+
+ // This time the tight loop will fail to acquire the lock.
+ if let Poll::Ready(()) = tl.as_mut().poll(&mut cx) {
+ panic!("tight_loop unexpectedly ready");
+ }
+
+ // Now drop the mark_ready future, which should clear the LONG_WAIT bit.
+ mem::drop(mark);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed) & LONG_WAIT, 0);
+
+ // Since the lock is not held, we should be able to spawn a future to set the ready flag.
+ block_on(mark_ready(mu.clone()));
+
+ // Now the tight loop will finish.
+ if tl.as_mut().poll(&mut cx).is_pending() {
+ panic!("tight_loop not able to finish");
+ }
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn designated_waker() {
+ async fn inc(mu: Arc<Mutex<usize>>) {
+ *mu.lock().await += 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+
+ let mut futures = [
+ Box::pin(inc(mu.clone())),
+ Box::pin(inc(mu.clone())),
+ Box::pin(inc(mu.clone())),
+ ];
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let count = block_on(mu.lock());
+
+ // Poll 2 futures. Since neither will be able to acquire the lock, they should get added to
+ // the waiter list.
+ if let Poll::Ready(()) = futures[0].as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ if let Poll::Ready(()) = futures[1].as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed),
+ LOCKED | HAS_WAITERS | WRITER_WAITING,
+ );
+
+ // Now drop the lock. This should set the DESIGNATED_WAKER bit and wake up the first future
+ // in the wait list.
+ mem::drop(count);
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed),
+ DESIGNATED_WAKER | HAS_WAITERS | WRITER_WAITING,
+ );
+
+ // Now poll the third future. It should be able to acquire the lock immediately.
+ if futures[2].as_mut().poll(&mut cx).is_pending() {
+ panic!("future unable to complete");
+ }
+ assert_eq!(*block_on(mu.lock()), 1);
+
+ // There should still be a waiter in the wait list and the DESIGNATED_WAKER bit should still
+ // be set.
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & DESIGNATED_WAKER,
+ DESIGNATED_WAKER
+ );
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & HAS_WAITERS,
+ HAS_WAITERS
+ );
+
+ // Now let the future that was woken up run.
+ if futures[0].as_mut().poll(&mut cx).is_pending() {
+ panic!("future unable to complete");
+ }
+ assert_eq!(*block_on(mu.lock()), 2);
+
+ if futures[1].as_mut().poll(&mut cx).is_pending() {
+ panic!("future unable to complete");
+ }
+ assert_eq!(*block_on(mu.lock()), 3);
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn cancel_designated_waker() {
+ async fn inc(mu: Arc<Mutex<usize>>) {
+ *mu.lock().await += 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+
+ let mut fut = Box::pin(inc(mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let count = block_on(mu.lock());
+
+ if let Poll::Ready(()) = fut.as_mut().poll(&mut cx) {
+ panic!("Future unexpectedly ready when lock is held");
+ }
+
+ // Drop the lock. This will wake up the future.
+ mem::drop(count);
+
+ // Now drop the future without polling. This should clear all the state in the mutex.
+ mem::drop(fut);
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn cancel_before_wake() {
+ async fn inc(mu: Arc<Mutex<usize>>) {
+ *mu.lock().await += 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+
+ let mut fut1 = Box::pin(inc(mu.clone()));
+
+ let mut fut2 = Box::pin(inc(mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ // First acquire the lock.
+ let count = block_on(mu.lock());
+
+ // Now poll the futures. Since the lock is acquired they will both get queued in the waiter
+ // list.
+ match fut1.as_mut().poll(&mut cx) {
+ Poll::Pending => {}
+ Poll::Ready(()) => panic!("Future is unexpectedly ready"),
+ }
+
+ match fut2.as_mut().poll(&mut cx) {
+ Poll::Pending => {}
+ Poll::Ready(()) => panic!("Future is unexpectedly ready"),
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING,
+ WRITER_WAITING
+ );
+
+ // Drop fut1. This should remove it from the waiter list but shouldn't wake fut2.
+ mem::drop(fut1);
+
+ // There should be no designated waker.
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed) & DESIGNATED_WAKER, 0);
+
+ // Since the waiter was a writer, we should clear the WRITER_WAITING bit.
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING, 0);
+
+ match fut2.as_mut().poll(&mut cx) {
+ Poll::Pending => {}
+ Poll::Ready(()) => panic!("Future is unexpectedly ready"),
+ }
+
+ // Now drop the lock. This should mark fut2 as ready to make progress.
+ mem::drop(count);
+
+ match fut2.as_mut().poll(&mut cx) {
+ Poll::Pending => panic!("Future is not ready to make progress"),
+ Poll::Ready(()) => {}
+ }
+
+ // Verify that we only incremented the count once.
+ assert_eq!(*block_on(mu.lock()), 1);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn cancel_after_wake() {
+ async fn inc(mu: Arc<Mutex<usize>>) {
+ *mu.lock().await += 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+
+ let mut fut1 = Box::pin(inc(mu.clone()));
+
+ let mut fut2 = Box::pin(inc(mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ // First acquire the lock.
+ let count = block_on(mu.lock());
+
+ // Now poll the futures. Since the lock is acquired they will both get queued in the waiter
+ // list.
+ match fut1.as_mut().poll(&mut cx) {
+ Poll::Pending => {}
+ Poll::Ready(()) => panic!("Future is unexpectedly ready"),
+ }
+
+ match fut2.as_mut().poll(&mut cx) {
+ Poll::Pending => {}
+ Poll::Ready(()) => panic!("Future is unexpectedly ready"),
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING,
+ WRITER_WAITING
+ );
+
+ // Drop the lock. This should mark fut1 as ready to make progress.
+ mem::drop(count);
+
+ // Now drop fut1. This should make fut2 ready to make progress.
+ mem::drop(fut1);
+
+ // Since there was still another waiter in the list we shouldn't have cleared the
+ // DESIGNATED_WAKER bit.
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & DESIGNATED_WAKER,
+ DESIGNATED_WAKER
+ );
+
+ // Since the waiter was a writer, we should clear the WRITER_WAITING bit.
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING, 0);
+
+ match fut2.as_mut().poll(&mut cx) {
+ Poll::Pending => panic!("Future is not ready to make progress"),
+ Poll::Ready(()) => {}
+ }
+
+ // Verify that we only incremented the count once.
+ assert_eq!(*block_on(mu.lock()), 1);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn timeout() {
+ async fn timed_lock(timer: oneshot::Receiver<()>, mu: Arc<Mutex<()>>) {
+ select! {
+ res = timer.fuse() => {
+ match res {
+ Ok(()) => {},
+ Err(e) => panic!("Timer unexpectedly canceled: {}", e),
+ }
+ }
+ _ = mu.lock().fuse() => panic!("Successfuly acquired lock"),
+ }
+ }
+
+ let mu = Arc::new(Mutex::new(()));
+ let (tx, rx) = oneshot::channel();
+
+ let mut timeout = Box::pin(timed_lock(rx, mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ // Acquire the lock.
+ let g = block_on(mu.lock());
+
+ // Poll the future.
+ if let Poll::Ready(()) = timeout.as_mut().poll(&mut cx) {
+ panic!("timed_lock unexpectedly ready");
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & HAS_WAITERS,
+ HAS_WAITERS
+ );
+
+ // Signal the channel, which should cancel the lock.
+ tx.send(()).expect("Failed to send wakeup");
+
+ // Now the future should have completed without acquiring the lock.
+ if timeout.as_mut().poll(&mut cx).is_pending() {
+ panic!("timed_lock not ready after timeout");
+ }
+
+ // The mutex state should not show any waiters.
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed) & HAS_WAITERS, 0);
+
+ mem::drop(g);
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn writer_waiting() {
+ async fn read_zero(mu: Arc<Mutex<usize>>) {
+ let val = mu.read_lock().await;
+ pending!();
+
+ assert_eq!(*val, 0);
+ }
+
+ async fn inc(mu: Arc<Mutex<usize>>) {
+ *mu.lock().await += 1;
+ }
+
+ async fn read_one(mu: Arc<Mutex<usize>>) {
+ let val = mu.read_lock().await;
+
+ assert_eq!(*val, 1);
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+
+ let mut r1 = Box::pin(read_zero(mu.clone()));
+ let mut r2 = Box::pin(read_zero(mu.clone()));
+
+ let mut w = Box::pin(inc(mu.clone()));
+ let mut r3 = Box::pin(read_one(mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ if let Poll::Ready(()) = r1.as_mut().poll(&mut cx) {
+ panic!("read_zero unexpectedly ready");
+ }
+ if let Poll::Ready(()) = r2.as_mut().poll(&mut cx) {
+ panic!("read_zero unexpectedly ready");
+ }
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & READ_MASK,
+ 2 * READ_LOCK
+ );
+
+ if let Poll::Ready(()) = w.as_mut().poll(&mut cx) {
+ panic!("inc unexpectedly ready");
+ }
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING,
+ WRITER_WAITING
+ );
+
+ // The WRITER_WAITING bit should prevent the next reader from acquiring the lock.
+ if let Poll::Ready(()) = r3.as_mut().poll(&mut cx) {
+ panic!("read_one unexpectedly ready");
+ }
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & READ_MASK,
+ 2 * READ_LOCK
+ );
+
+ if r1.as_mut().poll(&mut cx).is_pending() {
+ panic!("read_zero unable to complete");
+ }
+ if r2.as_mut().poll(&mut cx).is_pending() {
+ panic!("read_zero unable to complete");
+ }
+ if w.as_mut().poll(&mut cx).is_pending() {
+ panic!("inc unable to complete");
+ }
+ if r3.as_mut().poll(&mut cx).is_pending() {
+ panic!("read_one unable to complete");
+ }
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn notify_one() {
+ async fn read(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.read_lock().await;
+ while *count == 0 {
+ count = cv.wait_read(count).await;
+ }
+ }
+
+ async fn write(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.lock().await;
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+
+ *count -= 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let mut readers = [
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ ];
+ let mut writer = Box::pin(write(mu.clone(), cv.clone()));
+
+ for r in &mut readers {
+ if let Poll::Ready(()) = r.as_mut().poll(&mut cx) {
+ panic!("reader unexpectedly ready");
+ }
+ }
+ if let Poll::Ready(()) = writer.as_mut().poll(&mut cx) {
+ panic!("writer unexpectedly ready");
+ }
+
+ let mut count = block_on(mu.lock());
+ *count = 1;
+
+ // This should wake all readers + one writer.
+ cv.notify_one();
+
+ // Poll the readers and the writer so they add themselves to the mutex's waiter list.
+ for r in &mut readers {
+ if r.as_mut().poll(&mut cx).is_ready() {
+ panic!("reader unexpectedly ready");
+ }
+ }
+
+ if writer.as_mut().poll(&mut cx).is_ready() {
+ panic!("writer unexpectedly ready");
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & HAS_WAITERS,
+ HAS_WAITERS
+ );
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING,
+ WRITER_WAITING
+ );
+
+ mem::drop(count);
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & (HAS_WAITERS | WRITER_WAITING),
+ HAS_WAITERS | WRITER_WAITING
+ );
+
+ for r in &mut readers {
+ if r.as_mut().poll(&mut cx).is_pending() {
+ panic!("reader unable to complete");
+ }
+ }
+
+ if writer.as_mut().poll(&mut cx).is_pending() {
+ panic!("writer unable to complete");
+ }
+
+ assert_eq!(*block_on(mu.read_lock()), 0);
+ }
+
+ #[test]
+ fn notify_when_unlocked() {
+ async fn dec(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.lock().await;
+
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+
+ *count -= 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let mut futures = [
+ Box::pin(dec(mu.clone(), cv.clone())),
+ Box::pin(dec(mu.clone(), cv.clone())),
+ Box::pin(dec(mu.clone(), cv.clone())),
+ Box::pin(dec(mu.clone(), cv.clone())),
+ ];
+
+ for f in &mut futures {
+ if let Poll::Ready(()) = f.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ }
+
+ *block_on(mu.lock()) = futures.len();
+ cv.notify_all();
+
+ // Since we haven't polled `futures` yet, the mutex should not have any waiters.
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed) & HAS_WAITERS, 0);
+
+ for f in &mut futures {
+ if f.as_mut().poll(&mut cx).is_pending() {
+ panic!("future unexpectedly ready");
+ }
+ }
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn notify_reader_writer() {
+ async fn read(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.read_lock().await;
+ while *count == 0 {
+ count = cv.wait_read(count).await;
+ }
+
+ // Yield once while holding the read lock, which should prevent the writer from waking
+ // up.
+ pending!();
+ }
+
+ async fn write(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.lock().await;
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+
+ *count -= 1;
+ }
+
+ async fn lock(mu: Arc<Mutex<usize>>) {
+ mem::drop(mu.lock().await);
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let mut futures: [Pin<Box<dyn Future<Output = ()>>>; 5] = [
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(write(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ ];
+ const NUM_READERS: usize = 4;
+
+ let mut l = Box::pin(lock(mu.clone()));
+
+ for f in &mut futures {
+ if let Poll::Ready(()) = f.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ }
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+
+ let mut count = block_on(mu.lock());
+ *count = 1;
+
+ // Now poll the lock function. Since the lock is held by us, it will get queued on the
+ // waiter list.
+ if let Poll::Ready(()) = l.as_mut().poll(&mut cx) {
+ panic!("lock() unexpectedly ready");
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & (HAS_WAITERS | WRITER_WAITING),
+ HAS_WAITERS | WRITER_WAITING
+ );
+
+ // Wake up waiters while holding the lock.
+ cv.notify_all();
+
+ // Drop the lock. This should wake up the lock function.
+ mem::drop(count);
+
+ if l.as_mut().poll(&mut cx).is_pending() {
+ panic!("lock() unable to complete");
+ }
+
+ // Since we haven't polled `futures` yet, the mutex state should now be empty.
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+
+ // Poll everything again. The readers should be able to make progress (but not complete) but
+ // the writer should be blocked.
+ for f in &mut futures {
+ if let Poll::Ready(()) = f.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & READ_MASK,
+ READ_LOCK * NUM_READERS
+ );
+
+ // All the readers can now finish but the writer needs to be polled again.
+ let mut needs_poll = None;
+ for (i, r) in futures.iter_mut().enumerate() {
+ match r.as_mut().poll(&mut cx) {
+ Poll::Ready(()) => {}
+ Poll::Pending => {
+ if needs_poll.is_some() {
+ panic!("More than one future unable to complete");
+ }
+ needs_poll = Some(i);
+ }
+ }
+ }
+
+ if futures[needs_poll.expect("Writer unexpectedly able to complete")]
+ .as_mut()
+ .poll(&mut cx)
+ .is_pending()
+ {
+ panic!("Writer unable to complete");
+ }
+
+ assert_eq!(*block_on(mu.lock()), 0);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn notify_readers_with_read_lock() {
+ async fn read(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.read_lock().await;
+ while *count == 0 {
+ count = cv.wait_read(count).await;
+ }
+
+ // Yield once while holding the read lock.
+ pending!();
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let mut futures = [
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ ];
+
+ for f in &mut futures {
+ if let Poll::Ready(()) = f.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ }
+
+ // Increment the count and then grab a read lock.
+ *block_on(mu.lock()) = 1;
+
+ let g = block_on(mu.read_lock());
+
+ // Notify the condvar while holding the read lock. This should wake up all the waiters.
+ cv.notify_all();
+
+ // Since the lock is held in shared mode, all the readers should immediately be able to
+ // acquire the read lock.
+ for f in &mut futures {
+ if let Poll::Ready(()) = f.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ }
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed) & HAS_WAITERS, 0);
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & READ_MASK,
+ READ_LOCK * (futures.len() + 1)
+ );
+
+ mem::drop(g);
+
+ for f in &mut futures {
+ if f.as_mut().poll(&mut cx).is_pending() {
+ panic!("future unable to complete");
+ }
+ }
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+}
diff --git a/common/cros_async/src/sync/spin.rs b/common/cros_async/src/sync/spin.rs
new file mode 100644
index 000000000..7faec6dc2
--- /dev/null
+++ b/common/cros_async/src/sync/spin.rs
@@ -0,0 +1,284 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ cell::UnsafeCell,
+ hint,
+ ops::{Deref, DerefMut},
+ sync::atomic::{AtomicBool, Ordering},
+};
+
+const UNLOCKED: bool = false;
+const LOCKED: bool = true;
+
+/// A primitive that provides safe, mutable access to a shared resource.
+///
+/// Unlike `Mutex`, a `SpinLock` will not voluntarily yield its CPU time until the resource is
+/// available and will instead keep spinning until the resource is acquired. For the vast majority
+/// of cases, `Mutex` is a better choice than `SpinLock`. If a `SpinLock` must be used then users
+/// should try to do as little work as possible while holding the `SpinLock` and avoid any sort of
+/// blocking at all costs as it can severely penalize performance.
+///
+/// # Poisoning
+///
+/// This `SpinLock` does not implement lock poisoning so it is possible for threads to access
+/// poisoned data if a thread panics while holding the lock. If lock poisoning is needed, it can be
+/// implemented by wrapping the `SpinLock` in a new type that implements poisoning. See the
+/// implementation of `std::sync::Mutex` for an example of how to do this.
+#[repr(align(128))]
+pub struct SpinLock<T: ?Sized> {
+ lock: AtomicBool,
+ value: UnsafeCell<T>,
+}
+
+impl<T> SpinLock<T> {
+ /// Creates a new, unlocked `SpinLock` that's ready for use.
+ pub fn new(value: T) -> SpinLock<T> {
+ SpinLock {
+ lock: AtomicBool::new(UNLOCKED),
+ value: UnsafeCell::new(value),
+ }
+ }
+
+ /// Consumes the `SpinLock` and returns the value guarded by it. This method doesn't perform any
+ /// locking as the compiler guarantees that there are no references to `self`.
+ pub fn into_inner(self) -> T {
+ // No need to take the lock because the compiler can statically guarantee
+ // that there are no references to the SpinLock.
+ self.value.into_inner()
+ }
+}
+
+impl<T: ?Sized> SpinLock<T> {
+ /// Acquires exclusive, mutable access to the resource protected by the `SpinLock`, blocking the
+ /// current thread until it is able to do so. Upon returning, the current thread will be the
+ /// only thread with access to the resource. The `SpinLock` will be released when the returned
+ /// `SpinLockGuard` is dropped. Attempting to call `lock` while already holding the `SpinLock`
+ /// will cause a deadlock.
+ pub fn lock(&self) -> SpinLockGuard<T> {
+ loop {
+ let state = self.lock.load(Ordering::Relaxed);
+ if state == UNLOCKED
+ && self
+ .lock
+ .compare_exchange_weak(UNLOCKED, LOCKED, Ordering::Acquire, Ordering::Relaxed)
+ .is_ok()
+ {
+ break;
+ }
+ hint::spin_loop();
+ }
+
+ SpinLockGuard {
+ lock: self,
+ value: unsafe { &mut *self.value.get() },
+ }
+ }
+
+ fn unlock(&self) {
+ // Don't need to compare and swap because we exclusively hold the lock.
+ self.lock.store(UNLOCKED, Ordering::Release);
+ }
+
+ /// Returns a mutable reference to the contained value. This method doesn't perform any locking
+ /// as the compiler will statically guarantee that there are no other references to `self`.
+ pub fn get_mut(&mut self) -> &mut T {
+ // Safe because the compiler can statically guarantee that there are no other references to
+ // `self`. This is also why we don't need to acquire the lock.
+ unsafe { &mut *self.value.get() }
+ }
+}
+
+unsafe impl<T: ?Sized + Send> Send for SpinLock<T> {}
+unsafe impl<T: ?Sized + Send> Sync for SpinLock<T> {}
+
+impl<T: ?Sized + Default> Default for SpinLock<T> {
+ fn default() -> Self {
+ Self::new(Default::default())
+ }
+}
+
+impl<T> From<T> for SpinLock<T> {
+ fn from(source: T) -> Self {
+ Self::new(source)
+ }
+}
+
+/// An RAII implementation of a "scoped lock" for a `SpinLock`. When this structure is dropped, the
+/// lock will be released. The resource protected by the `SpinLock` can be accessed via the `Deref`
+/// and `DerefMut` implementations of this structure.
+pub struct SpinLockGuard<'a, T: 'a + ?Sized> {
+ lock: &'a SpinLock<T>,
+ value: &'a mut T,
+}
+
+impl<'a, T: ?Sized> Deref for SpinLockGuard<'a, T> {
+ type Target = T;
+ fn deref(&self) -> &T {
+ self.value
+ }
+}
+
+impl<'a, T: ?Sized> DerefMut for SpinLockGuard<'a, T> {
+ fn deref_mut(&mut self) -> &mut T {
+ self.value
+ }
+}
+
+impl<'a, T: ?Sized> Drop for SpinLockGuard<'a, T> {
+ fn drop(&mut self) {
+ self.lock.unlock();
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use std::{
+ mem,
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc,
+ },
+ thread,
+ };
+
+ #[derive(PartialEq, Eq, Debug)]
+ struct NonCopy(u32);
+
+ #[test]
+ fn it_works() {
+ let sl = SpinLock::new(NonCopy(13));
+
+ assert_eq!(*sl.lock(), NonCopy(13));
+ }
+
+ #[test]
+ fn smoke() {
+ let sl = SpinLock::new(NonCopy(7));
+
+ mem::drop(sl.lock());
+ mem::drop(sl.lock());
+ }
+
+ #[test]
+ fn send() {
+ let sl = SpinLock::new(NonCopy(19));
+
+ thread::spawn(move || {
+ let value = sl.lock();
+ assert_eq!(*value, NonCopy(19));
+ })
+ .join()
+ .unwrap();
+ }
+
+ #[test]
+ fn high_contention() {
+ const THREADS: usize = 23;
+ const ITERATIONS: usize = 101;
+
+ let mut threads = Vec::with_capacity(THREADS);
+
+ let sl = Arc::new(SpinLock::new(0usize));
+ for _ in 0..THREADS {
+ let sl2 = sl.clone();
+ threads.push(thread::spawn(move || {
+ for _ in 0..ITERATIONS {
+ *sl2.lock() += 1;
+ }
+ }));
+ }
+
+ for t in threads.into_iter() {
+ t.join().unwrap();
+ }
+
+ assert_eq!(*sl.lock(), THREADS * ITERATIONS);
+ }
+
+ #[test]
+ fn get_mut() {
+ let mut sl = SpinLock::new(NonCopy(13));
+ *sl.get_mut() = NonCopy(17);
+
+ assert_eq!(sl.into_inner(), NonCopy(17));
+ }
+
+ #[test]
+ fn into_inner() {
+ let sl = SpinLock::new(NonCopy(29));
+ assert_eq!(sl.into_inner(), NonCopy(29));
+ }
+
+ #[test]
+ fn into_inner_drop() {
+ struct NeedsDrop(Arc<AtomicUsize>);
+ impl Drop for NeedsDrop {
+ fn drop(&mut self) {
+ self.0.fetch_add(1, Ordering::AcqRel);
+ }
+ }
+
+ let value = Arc::new(AtomicUsize::new(0));
+ let needs_drop = SpinLock::new(NeedsDrop(value.clone()));
+ assert_eq!(value.load(Ordering::Acquire), 0);
+
+ {
+ let inner = needs_drop.into_inner();
+ assert_eq!(inner.0.load(Ordering::Acquire), 0);
+ }
+
+ assert_eq!(value.load(Ordering::Acquire), 1);
+ }
+
+ #[test]
+ fn arc_nested() {
+ // Tests nested sltexes and access to underlying data.
+ let sl = SpinLock::new(1);
+ let arc = Arc::new(SpinLock::new(sl));
+ thread::spawn(move || {
+ let nested = arc.lock();
+ let lock2 = nested.lock();
+ assert_eq!(*lock2, 1);
+ })
+ .join()
+ .unwrap();
+ }
+
+ #[test]
+ fn arc_access_in_unwind() {
+ let arc = Arc::new(SpinLock::new(1));
+ let arc2 = arc.clone();
+ thread::spawn(move || {
+ struct Unwinder {
+ i: Arc<SpinLock<i32>>,
+ }
+ impl Drop for Unwinder {
+ fn drop(&mut self) {
+ *self.i.lock() += 1;
+ }
+ }
+ let _u = Unwinder { i: arc2 };
+ panic!();
+ })
+ .join()
+ .expect_err("thread did not panic");
+ let lock = arc.lock();
+ assert_eq!(*lock, 2);
+ }
+
+ #[test]
+ fn unsized_value() {
+ let sltex: &SpinLock<[i32]> = &SpinLock::new([1, 2, 3]);
+ {
+ let b = &mut *sltex.lock();
+ b[0] = 4;
+ b[2] = 5;
+ }
+ let expected: &[i32] = &[4, 2, 5];
+ assert_eq!(&*sltex.lock(), expected);
+ }
+}
diff --git a/common/cros_async/src/sync/waiter.rs b/common/cros_async/src/sync/waiter.rs
new file mode 100644
index 000000000..e161c623d
--- /dev/null
+++ b/common/cros_async/src/sync/waiter.rs
@@ -0,0 +1,288 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ cell::UnsafeCell,
+ future::Future,
+ mem,
+ pin::Pin,
+ ptr::NonNull,
+ sync::{
+ atomic::{AtomicBool, AtomicU8, Ordering},
+ Arc,
+ },
+ task::{Context, Poll, Waker},
+};
+
+use intrusive_collections::{
+ intrusive_adapter,
+ linked_list::{LinkedList, LinkedListOps},
+ DefaultLinkOps, LinkOps,
+};
+
+use super::super::sync::SpinLock;
+
+// An atomic version of a LinkedListLink. See https://github.com/Amanieu/intrusive-rs/issues/47 for
+// more details.
+#[repr(align(128))]
+pub struct AtomicLink {
+ prev: UnsafeCell<Option<NonNull<AtomicLink>>>,
+ next: UnsafeCell<Option<NonNull<AtomicLink>>>,
+ linked: AtomicBool,
+}
+
+impl AtomicLink {
+ fn new() -> AtomicLink {
+ AtomicLink {
+ linked: AtomicBool::new(false),
+ prev: UnsafeCell::new(None),
+ next: UnsafeCell::new(None),
+ }
+ }
+
+ fn is_linked(&self) -> bool {
+ self.linked.load(Ordering::Relaxed)
+ }
+}
+
+impl DefaultLinkOps for AtomicLink {
+ type Ops = AtomicLinkOps;
+
+ const NEW: Self::Ops = AtomicLinkOps;
+}
+
+// Safe because the only way to mutate `AtomicLink` is via the `LinkedListOps` trait whose methods
+// are all unsafe and require that the caller has first called `acquire_link` (and had it return
+// true) to use them safely.
+unsafe impl Send for AtomicLink {}
+unsafe impl Sync for AtomicLink {}
+
+#[derive(Copy, Clone, Default)]
+pub struct AtomicLinkOps;
+
+unsafe impl LinkOps for AtomicLinkOps {
+ type LinkPtr = NonNull<AtomicLink>;
+
+ unsafe fn acquire_link(&mut self, ptr: Self::LinkPtr) -> bool {
+ !ptr.as_ref().linked.swap(true, Ordering::Acquire)
+ }
+
+ unsafe fn release_link(&mut self, ptr: Self::LinkPtr) {
+ ptr.as_ref().linked.store(false, Ordering::Release)
+ }
+}
+
+unsafe impl LinkedListOps for AtomicLinkOps {
+ unsafe fn next(&self, ptr: Self::LinkPtr) -> Option<Self::LinkPtr> {
+ *ptr.as_ref().next.get()
+ }
+
+ unsafe fn prev(&self, ptr: Self::LinkPtr) -> Option<Self::LinkPtr> {
+ *ptr.as_ref().prev.get()
+ }
+
+ unsafe fn set_next(&mut self, ptr: Self::LinkPtr, next: Option<Self::LinkPtr>) {
+ *ptr.as_ref().next.get() = next;
+ }
+
+ unsafe fn set_prev(&mut self, ptr: Self::LinkPtr, prev: Option<Self::LinkPtr>) {
+ *ptr.as_ref().prev.get() = prev;
+ }
+}
+
+#[derive(Clone, Copy)]
+pub enum Kind {
+ Shared,
+ Exclusive,
+}
+
+enum State {
+ Init,
+ Waiting(Waker),
+ Woken,
+ Finished,
+ Processing,
+}
+
+// Indicates the queue to which the waiter belongs. It is the responsibility of the Mutex and
+// Condvar implementations to update this value when adding/removing a Waiter from their respective
+// waiter lists.
+#[repr(u8)]
+#[derive(Debug, Eq, PartialEq)]
+pub enum WaitingFor {
+ // The waiter is either not linked into a waiter list or it is linked into a temporary list.
+ None = 0,
+ // The waiter is linked into the Mutex's waiter list.
+ Mutex = 1,
+ // The waiter is linked into the Condvar's waiter list.
+ Condvar = 2,
+}
+
+// Represents a thread currently blocked on a Condvar or on acquiring a Mutex.
+pub struct Waiter {
+ link: AtomicLink,
+ state: SpinLock<State>,
+ cancel: fn(usize, &Waiter, bool),
+ cancel_data: usize,
+ kind: Kind,
+ waiting_for: AtomicU8,
+}
+
+impl Waiter {
+ // Create a new, initialized Waiter.
+ //
+ // `kind` should indicate whether this waiter represent a thread that is waiting for a shared
+ // lock or an exclusive lock.
+ //
+ // `cancel` is the function that is called when a `WaitFuture` (returned by the `wait()`
+ // function) is dropped before it can complete. `cancel_data` is used as the first parameter of
+ // the `cancel` function. The second parameter is the `Waiter` that was canceled and the third
+ // parameter indicates whether the `WaitFuture` was dropped after it was woken (but before it
+ // was polled to completion). A value of `false` for the third parameter may already be stale
+ // by the time the cancel function runs and so does not guarantee that the waiter was not woken.
+ // In this case, implementations should still check if the Waiter was woken. However, a value of
+ // `true` guarantees that the waiter was already woken up so no additional checks are necessary.
+ // In this case, the cancel implementation should wake up the next waiter in its wait list, if
+ // any.
+ //
+ // `waiting_for` indicates the waiter list to which this `Waiter` will be added. See the
+ // documentation of the `WaitingFor` enum for the meaning of the different values.
+ pub fn new(
+ kind: Kind,
+ cancel: fn(usize, &Waiter, bool),
+ cancel_data: usize,
+ waiting_for: WaitingFor,
+ ) -> Waiter {
+ Waiter {
+ link: AtomicLink::new(),
+ state: SpinLock::new(State::Init),
+ cancel,
+ cancel_data,
+ kind,
+ waiting_for: AtomicU8::new(waiting_for as u8),
+ }
+ }
+
+ // The kind of lock that this `Waiter` is waiting to acquire.
+ pub fn kind(&self) -> Kind {
+ self.kind
+ }
+
+ // Returns true if this `Waiter` is currently linked into a waiter list.
+ pub fn is_linked(&self) -> bool {
+ self.link.is_linked()
+ }
+
+ // Indicates the waiter list to which this `Waiter` belongs.
+ pub fn is_waiting_for(&self) -> WaitingFor {
+ match self.waiting_for.load(Ordering::Acquire) {
+ 0 => WaitingFor::None,
+ 1 => WaitingFor::Mutex,
+ 2 => WaitingFor::Condvar,
+ v => panic!("Unknown value for `WaitingFor`: {}", v),
+ }
+ }
+
+ // Change the waiter list to which this `Waiter` belongs. This will panic if called when the
+ // `Waiter` is still linked into a waiter list.
+ pub fn set_waiting_for(&self, waiting_for: WaitingFor) {
+ self.waiting_for.store(waiting_for as u8, Ordering::Release);
+ }
+
+ // Reset the Waiter back to its initial state. Panics if this `Waiter` is still linked into a
+ // waiter list.
+ pub fn reset(&self, waiting_for: WaitingFor) {
+ debug_assert!(!self.is_linked(), "Cannot reset `Waiter` while linked");
+ self.set_waiting_for(waiting_for);
+
+ let mut state = self.state.lock();
+ if let State::Waiting(waker) = mem::replace(&mut *state, State::Init) {
+ mem::drop(state);
+ mem::drop(waker);
+ }
+ }
+
+ // Wait until woken up by another thread.
+ pub fn wait(&self) -> WaitFuture<'_> {
+ WaitFuture { waiter: self }
+ }
+
+ // Wake up the thread associated with this `Waiter`. Panics if `waiting_for()` does not return
+ // `WaitingFor::None` or if `is_linked()` returns true.
+ pub fn wake(&self) {
+ debug_assert!(!self.is_linked(), "Cannot wake `Waiter` while linked");
+ debug_assert_eq!(self.is_waiting_for(), WaitingFor::None);
+
+ let mut state = self.state.lock();
+
+ if let State::Waiting(waker) = mem::replace(&mut *state, State::Woken) {
+ mem::drop(state);
+ waker.wake();
+ }
+ }
+}
+
+pub struct WaitFuture<'w> {
+ waiter: &'w Waiter,
+}
+
+impl<'w> Future for WaitFuture<'w> {
+ type Output = ();
+
+ fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ let mut state = self.waiter.state.lock();
+
+ match mem::replace(&mut *state, State::Processing) {
+ State::Init => {
+ *state = State::Waiting(cx.waker().clone());
+
+ Poll::Pending
+ }
+ State::Waiting(old_waker) => {
+ *state = State::Waiting(cx.waker().clone());
+ mem::drop(state);
+ mem::drop(old_waker);
+
+ Poll::Pending
+ }
+ State::Woken => {
+ *state = State::Finished;
+ Poll::Ready(())
+ }
+ State::Finished => {
+ panic!("Future polled after returning Poll::Ready");
+ }
+ State::Processing => {
+ panic!("Unexpected waker state");
+ }
+ }
+ }
+}
+
+impl<'w> Drop for WaitFuture<'w> {
+ fn drop(&mut self) {
+ let state = self.waiter.state.lock();
+
+ match *state {
+ State::Finished => {}
+ State::Processing => panic!("Unexpected waker state"),
+ State::Woken => {
+ mem::drop(state);
+
+ // We were woken but not polled. Wake up the next waiter.
+ (self.waiter.cancel)(self.waiter.cancel_data, self.waiter, true);
+ }
+ _ => {
+ mem::drop(state);
+
+ // Not woken. No need to wake up any waiters.
+ (self.waiter.cancel)(self.waiter.cancel_data, self.waiter, false);
+ }
+ }
+ }
+}
+
+intrusive_adapter!(pub WaiterAdapter = Arc<Waiter>: Waiter { link: AtomicLink });
+
+pub type WaiterList = LinkedList<WaiterAdapter>;
diff --git a/common/cros_async/src/timer.rs b/common/cros_async/src/timer.rs
new file mode 100644
index 000000000..2bc3ebdb0
--- /dev/null
+++ b/common/cros_async/src/timer.rs
@@ -0,0 +1,120 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::time::Duration;
+
+use sys_util::{Result as SysResult, TimerFd};
+
+use super::{AsyncResult, Error, Executor, IntoAsync, IoSourceExt};
+
+#[cfg(test)]
+use super::{FdExecutor, URingExecutor};
+
+/// An async version of sys_util::TimerFd.
+pub struct TimerAsync {
+ io_source: Box<dyn IoSourceExt<TimerFd>>,
+}
+
+impl TimerAsync {
+ pub fn new(timer: TimerFd, ex: &Executor) -> AsyncResult<TimerAsync> {
+ ex.async_from(timer)
+ .map(|io_source| TimerAsync { io_source })
+ }
+
+ #[cfg(test)]
+ pub(crate) fn new_poll(timer: TimerFd, ex: &FdExecutor) -> AsyncResult<TimerAsync> {
+ super::executor::async_poll_from(timer, ex).map(|io_source| TimerAsync { io_source })
+ }
+
+ #[cfg(test)]
+ pub(crate) fn new_uring(timer: TimerFd, ex: &URingExecutor) -> AsyncResult<TimerAsync> {
+ super::executor::async_uring_from(timer, ex).map(|io_source| TimerAsync { io_source })
+ }
+
+ /// Gets the next value from the timer.
+ pub async fn next_val(&self) -> AsyncResult<u64> {
+ self.io_source.read_u64().await
+ }
+
+ /// Async sleep for the given duration
+ pub async fn sleep(ex: &Executor, dur: Duration) -> std::result::Result<(), Error> {
+ let tfd = TimerFd::new().map_err(Error::TimerFd)?;
+ tfd.reset(dur, None).map_err(Error::TimerFd)?;
+ let t = TimerAsync::new(tfd, ex).map_err(Error::TimerAsync)?;
+ t.next_val().await.map_err(Error::TimerAsync)?;
+ Ok(())
+ }
+
+ /// Sets the timer to expire after `dur`. If `interval` is not `None` it represents
+ /// the period for repeated expirations after the initial expiration. Otherwise
+ /// the timer will expire just once. Cancels any existing duration and repeating interval.
+ pub fn reset(&mut self, dur: Duration, interval: Option<Duration>) -> SysResult<()> {
+ self.io_source.as_source_mut().reset(dur, interval)
+ }
+}
+
+impl IntoAsync for TimerFd {}
+
+#[cfg(test)]
+mod tests {
+ use super::{super::uring_executor::use_uring, *};
+ use std::time::{Duration, Instant};
+
+ #[test]
+ fn one_shot() {
+ if !use_uring() {
+ return;
+ }
+
+ async fn this_test(ex: &URingExecutor) {
+ let tfd = TimerFd::new().expect("failed to create timerfd");
+
+ let dur = Duration::from_millis(200);
+ let now = Instant::now();
+ tfd.reset(dur, None).expect("failed to arm timer");
+
+ let t = TimerAsync::new_uring(tfd, ex).unwrap();
+ let count = t.next_val().await.expect("unable to wait for timer");
+
+ assert_eq!(count, 1);
+ assert!(now.elapsed() >= dur);
+ }
+
+ let ex = URingExecutor::new().unwrap();
+ ex.run_until(this_test(&ex)).unwrap();
+ }
+
+ #[test]
+ fn one_shot_fd() {
+ async fn this_test(ex: &FdExecutor) {
+ let tfd = TimerFd::new().expect("failed to create timerfd");
+
+ let dur = Duration::from_millis(200);
+ let now = Instant::now();
+ tfd.reset(dur, None).expect("failed to arm timer");
+
+ let t = TimerAsync::new_poll(tfd, ex).unwrap();
+ let count = t.next_val().await.expect("unable to wait for timer");
+
+ assert_eq!(count, 1);
+ assert!(now.elapsed() >= dur);
+ }
+
+ let ex = FdExecutor::new().unwrap();
+ ex.run_until(this_test(&ex)).unwrap();
+ }
+
+ #[test]
+ fn timer() {
+ async fn this_test(ex: &Executor) {
+ let dur = Duration::from_millis(200);
+ let now = Instant::now();
+ TimerAsync::sleep(ex, dur).await.expect("unable to sleep");
+ assert!(now.elapsed() >= dur);
+ }
+
+ let ex = Executor::new().expect("creating an executor failed");
+ ex.run_until(this_test(&ex)).unwrap();
+ }
+}
diff --git a/common/cros_async/src/uring_executor.rs b/common/cros_async/src/uring_executor.rs
new file mode 100644
index 000000000..63c0bb745
--- /dev/null
+++ b/common/cros_async/src/uring_executor.rs
@@ -0,0 +1,1151 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! `URingExecutor`
+//!
+//! The executor runs all given futures to completion. Futures register wakers associated with
+//! io_uring operations. A waker is called when the set of uring ops the waker is waiting on
+//! completes.
+//!
+//! `URingExecutor` is meant to be used with the `futures-rs` crate that provides combinators and
+//! utility functions to combine futures. In general, the interface provided by `URingExecutor`
+//! shouldn't be used directly. Instead, use them by interacting with implementors of `IoSource`,
+//! and the high-level future functions.
+//!
+//!
+//! ## Read/Write buffer management.
+//!
+//! There are two key issues managing asynchronous IO buffers in rust.
+//! 1) The kernel has a mutable reference to the memory until the completion is returned. Rust must
+//! not have any references to it during that time.
+//! 2) The memory must remain valid as long as the kernel has a reference to it.
+//!
+//! ### The kernel's mutable borrow of the buffer
+//!
+//! Because the buffers used for read and write must be passed to the kernel for an unknown
+//! duration, the functions must maintain ownership of the memory. The core of this problem is that
+//! the lifetime of the future isn't tied to the scope in which the kernel can modify the buffer the
+//! future has a reference to. The buffer can be modified at any point from submission until the
+//! operation completes. The operation can't be synchronously canceled when the future is dropped,
+//! and Drop can't be used for safety guarantees. To ensure this never happens, only memory that
+//! implements `BackingMemory` is accepted. For implementors of `BackingMemory` the mut borrow
+//! isn't an issue because those are already Ok with external modifications to the memory (Like a
+//! `VolatileSlice`).
+//!
+//! ### Buffer lifetime
+//!
+//! What if the kernel's reference to the buffer outlives the buffer itself? This could happen if a
+//! read operation was submitted, then the memory is dropped. To solve this, the executor takes an
+//! Arc to the backing memory. Vecs being read to are also wrapped in an Arc before being passed to
+//! the executor. The executor holds the Arc and ensures all operations are complete before dropping
+//! it, that guarantees the memory is valid for the duration.
+//!
+//! The buffers _have_ to be on the heap. Because we don't have a way to cancel a future if it is
+//! dropped(can't rely on drop running), there is no way to ensure the kernel's buffer remains valid
+//! until the operation completes unless the executor holds an Arc to the memory on the heap.
+//!
+//! ## Using `Vec` for reads/writes.
+//!
+//! There is a convenience wrapper `VecIoWrapper` provided for fully owned vectors. This type
+//! ensures that only the kernel is allowed to access the `Vec` and wraps the the `Vec` in an Arc to
+//! ensure it lives long enough.
+
+use std::{
+ convert::TryInto,
+ ffi::CStr,
+ fs::File,
+ future::Future,
+ io,
+ mem::{
+ MaybeUninit, {self},
+ },
+ os::unix::io::{AsRawFd, FromRawFd, RawFd},
+ pin::Pin,
+ sync::{
+ atomic::{AtomicI32, Ordering},
+ Arc, Weak,
+ },
+ task::{Context, Poll, Waker},
+ thread::{
+ ThreadId, {self},
+ },
+};
+
+use async_task::Task;
+use futures::task::noop_waker;
+use io_uring::URingContext;
+use once_cell::sync::Lazy;
+use pin_utils::pin_mut;
+use remain::sorted;
+use slab::Slab;
+use sync::Mutex;
+use sys_util::{warn, WatchingEvents};
+use thiserror::Error as ThisError;
+
+use super::{
+ mem::{BackingMemory, MemRegion},
+ queue::RunnableQueue,
+ waker::{new_waker, WakerToken, WeakWake},
+ BlockingPool,
+};
+
+#[sorted]
+#[derive(Debug, ThisError)]
+pub enum Error {
+ /// Creating a context to wait on FDs failed.
+ #[error("Error creating the fd waiting context: {0}")]
+ CreatingContext(io_uring::Error),
+ /// Failed to copy the FD for the polling context.
+ #[error("Failed to copy the FD for the polling context: {0}")]
+ DuplicatingFd(sys_util::Error),
+ /// The Executor is gone.
+ #[error("The URingExecutor is gone")]
+ ExecutorGone,
+ /// Invalid offset or length given for an iovec in backing memory.
+ #[error("Invalid offset/len for getting an iovec")]
+ InvalidOffset,
+ /// Invalid FD source specified.
+ #[error("Invalid source, FD not registered for use")]
+ InvalidSource,
+ /// Error doing the IO.
+ #[error("Error during IO: {0}")]
+ Io(io::Error),
+ /// Failed to remove the waker remove the polling context.
+ #[error("Error removing from the URing context: {0}")]
+ RemovingWaker(io_uring::Error),
+ /// Failed to submit the operation to the polling context.
+ #[error("Error adding to the URing context: {0}")]
+ SubmittingOp(io_uring::Error),
+ /// URingContext failure.
+ #[error("URingContext failure: {0}")]
+ URingContextError(io_uring::Error),
+ /// Failed to submit or wait for io_uring events.
+ #[error("URing::enter: {0}")]
+ URingEnter(io_uring::Error),
+}
+pub type Result<T> = std::result::Result<T, Error>;
+
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ use Error::*;
+ match e {
+ DuplicatingFd(e) => e.into(),
+ ExecutorGone => io::Error::new(io::ErrorKind::Other, ExecutorGone),
+ InvalidOffset => io::Error::new(io::ErrorKind::InvalidInput, InvalidOffset),
+ InvalidSource => io::Error::new(io::ErrorKind::InvalidData, InvalidSource),
+ Io(e) => e,
+ CreatingContext(e) => e.into(),
+ RemovingWaker(e) => e.into(),
+ SubmittingOp(e) => e.into(),
+ URingContextError(e) => e.into(),
+ URingEnter(e) => e.into(),
+ }
+ }
+}
+
+static USE_URING: Lazy<bool> = Lazy::new(|| {
+ let mut utsname = MaybeUninit::zeroed();
+
+ // Safe because this will only modify `utsname` and we check the return value.
+ let res = unsafe { libc::uname(utsname.as_mut_ptr()) };
+ if res < 0 {
+ return false;
+ }
+
+ // Safe because the kernel has initialized `utsname`.
+ let utsname = unsafe { utsname.assume_init() };
+
+ // Safe because the pointer is valid and the kernel guarantees that this is a valid C string.
+ let release = unsafe { CStr::from_ptr(utsname.release.as_ptr()) };
+
+ let mut components = match release.to_str().map(|r| r.split('.').map(str::parse)) {
+ Ok(c) => c,
+ Err(_) => return false,
+ };
+
+ // Kernels older than 5.10 either didn't support io_uring or had bugs in the implementation.
+ match (components.next(), components.next()) {
+ (Some(Ok(major)), Some(Ok(minor))) if (major, minor) >= (5, 10) => {
+ // The kernel version is new enough so check if we can actually make a uring context.
+ URingContext::new(8).is_ok()
+ }
+ _ => false,
+ }
+});
+
+// Checks if the uring executor is available.
+// Caches the result so that the check is only run once.
+// Useful for falling back to the FD executor on pre-uring kernels.
+pub(crate) fn use_uring() -> bool {
+ *USE_URING
+}
+
+pub struct RegisteredSource {
+ tag: usize,
+ ex: Weak<RawExecutor>,
+}
+
+impl RegisteredSource {
+ pub fn start_read_to_mem(
+ &self,
+ file_offset: u64,
+ mem: Arc<dyn BackingMemory + Send + Sync>,
+ addrs: &[MemRegion],
+ ) -> Result<PendingOperation> {
+ let ex = self.ex.upgrade().ok_or(Error::ExecutorGone)?;
+ let token = ex.submit_read_to_vectored(self, mem, file_offset, addrs)?;
+
+ Ok(PendingOperation {
+ waker_token: Some(token),
+ ex: self.ex.clone(),
+ submitted: false,
+ })
+ }
+
+ pub fn start_write_from_mem(
+ &self,
+ file_offset: u64,
+ mem: Arc<dyn BackingMemory + Send + Sync>,
+ addrs: &[MemRegion],
+ ) -> Result<PendingOperation> {
+ let ex = self.ex.upgrade().ok_or(Error::ExecutorGone)?;
+ let token = ex.submit_write_from_vectored(self, mem, file_offset, addrs)?;
+
+ Ok(PendingOperation {
+ waker_token: Some(token),
+ ex: self.ex.clone(),
+ submitted: false,
+ })
+ }
+
+ pub fn start_fallocate(&self, offset: u64, len: u64, mode: u32) -> Result<PendingOperation> {
+ let ex = self.ex.upgrade().ok_or(Error::ExecutorGone)?;
+ let token = ex.submit_fallocate(self, offset, len, mode)?;
+
+ Ok(PendingOperation {
+ waker_token: Some(token),
+ ex: self.ex.clone(),
+ submitted: false,
+ })
+ }
+
+ pub fn start_fsync(&self) -> Result<PendingOperation> {
+ let ex = self.ex.upgrade().ok_or(Error::ExecutorGone)?;
+ let token = ex.submit_fsync(self)?;
+
+ Ok(PendingOperation {
+ waker_token: Some(token),
+ ex: self.ex.clone(),
+ submitted: false,
+ })
+ }
+
+ pub fn poll_fd_readable(&self) -> Result<PendingOperation> {
+ let events = WatchingEvents::empty().set_read();
+
+ let ex = self.ex.upgrade().ok_or(Error::ExecutorGone)?;
+ let token = ex.submit_poll(self, &events)?;
+
+ Ok(PendingOperation {
+ waker_token: Some(token),
+ ex: self.ex.clone(),
+ submitted: false,
+ })
+ }
+}
+
+impl Drop for RegisteredSource {
+ fn drop(&mut self) {
+ if let Some(ex) = self.ex.upgrade() {
+ let _ = ex.deregister_source(self);
+ }
+ }
+}
+
+// Indicates that the executor is either within or about to make an io_uring_enter syscall. When a
+// waker sees this value, it will add and submit a NOP to the uring, which will wake up the thread
+// blocked on the io_uring_enter syscall.
+const WAITING: i32 = 0xb80d_21b5u32 as i32;
+
+// Indicates that the executor is processing any futures that are ready to run.
+const PROCESSING: i32 = 0xdb31_83a3u32 as i32;
+
+// Indicates that one or more futures may be ready to make progress.
+const WOKEN: i32 = 0x0fc7_8f7eu32 as i32;
+
+// Number of entries in the ring.
+const NUM_ENTRIES: usize = 256;
+
+// An operation that has been submitted to the uring and is potentially being waited on.
+struct OpData {
+ _file: Arc<File>,
+ _mem: Option<Arc<dyn BackingMemory + Send + Sync>>,
+ waker: Option<Waker>,
+ canceled: bool,
+}
+
+// The current status of an operation that's been submitted to the uring.
+enum OpStatus {
+ Nop,
+ Pending(OpData),
+ Completed(Option<::std::io::Result<u32>>),
+}
+
+struct Ring {
+ ops: Slab<OpStatus>,
+ registered_sources: Slab<Arc<File>>,
+}
+
+struct RawExecutor {
+ // The URingContext needs to be first so that it is dropped first, closing the uring fd, and
+ // releasing the resources borrowed by the kernel before we free them.
+ ctx: URingContext,
+ queue: RunnableQueue,
+ ring: Mutex<Ring>,
+ blocking_pool: BlockingPool,
+ thread_id: Mutex<Option<ThreadId>>,
+ state: AtomicI32,
+}
+
+impl RawExecutor {
+ fn new() -> Result<RawExecutor> {
+ Ok(RawExecutor {
+ ctx: URingContext::new(NUM_ENTRIES).map_err(Error::CreatingContext)?,
+ queue: RunnableQueue::new(),
+ ring: Mutex::new(Ring {
+ ops: Slab::with_capacity(NUM_ENTRIES),
+ registered_sources: Slab::with_capacity(NUM_ENTRIES),
+ }),
+ blocking_pool: Default::default(),
+ thread_id: Mutex::new(None),
+ state: AtomicI32::new(PROCESSING),
+ })
+ }
+
+ fn wake(&self) {
+ let oldstate = self.state.swap(WOKEN, Ordering::Release);
+ if oldstate == WAITING {
+ let mut ring = self.ring.lock();
+ let entry = ring.ops.vacant_entry();
+ let next_op_token = entry.key();
+ if let Err(e) = self.ctx.add_nop(usize_to_u64(next_op_token)) {
+ warn!("Failed to add NOP for waking up executor: {}", e);
+ }
+ entry.insert(OpStatus::Nop);
+ mem::drop(ring);
+
+ match self.ctx.submit() {
+ Ok(()) => {}
+ // If the kernel's submit ring is full then we know we won't block when calling
+ // io_uring_enter, which is all we really care about.
+ Err(io_uring::Error::RingEnter(libc::EBUSY)) => {}
+ Err(e) => warn!("Failed to submit NOP for waking up executor: {}", e),
+ }
+ }
+ }
+
+ fn spawn<F>(self: &Arc<Self>, f: F) -> Task<F::Output>
+ where
+ F: Future + Send + 'static,
+ F::Output: Send + 'static,
+ {
+ let raw = Arc::downgrade(self);
+ let schedule = move |runnable| {
+ if let Some(r) = raw.upgrade() {
+ r.queue.push_back(runnable);
+ r.wake();
+ }
+ };
+ let (runnable, task) = async_task::spawn(f, schedule);
+ runnable.schedule();
+ task
+ }
+
+ fn spawn_local<F>(self: &Arc<Self>, f: F) -> Task<F::Output>
+ where
+ F: Future + 'static,
+ F::Output: 'static,
+ {
+ let raw = Arc::downgrade(self);
+ let schedule = move |runnable| {
+ if let Some(r) = raw.upgrade() {
+ r.queue.push_back(runnable);
+ r.wake();
+ }
+ };
+ let (runnable, task) = async_task::spawn_local(f, schedule);
+ runnable.schedule();
+ task
+ }
+
+ fn spawn_blocking<F, R>(self: &Arc<Self>, f: F) -> Task<R>
+ where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+ {
+ self.blocking_pool.spawn(f)
+ }
+
+ fn runs_tasks_on_current_thread(&self) -> bool {
+ let executor_thread = self.thread_id.lock();
+ executor_thread
+ .map(|id| id == thread::current().id())
+ .unwrap_or(false)
+ }
+
+ fn run<F: Future>(&self, cx: &mut Context, done: F) -> Result<F::Output> {
+ let current_thread = thread::current().id();
+ let mut thread_id = self.thread_id.lock();
+ assert_eq!(
+ *thread_id.get_or_insert(current_thread),
+ current_thread,
+ "`URingExecutor::run` cannot be called from more than one thread"
+ );
+ mem::drop(thread_id);
+
+ pin_mut!(done);
+ loop {
+ self.state.store(PROCESSING, Ordering::Release);
+ for runnable in self.queue.iter() {
+ runnable.run();
+ }
+
+ if let Poll::Ready(val) = done.as_mut().poll(cx) {
+ return Ok(val);
+ }
+
+ let oldstate = self.state.compare_exchange(
+ PROCESSING,
+ WAITING,
+ Ordering::Acquire,
+ Ordering::Acquire,
+ );
+ if let Err(oldstate) = oldstate {
+ debug_assert_eq!(oldstate, WOKEN);
+ // One or more futures have become runnable.
+ continue;
+ }
+
+ let events = self.ctx.wait().map_err(Error::URingEnter)?;
+
+ // Set the state back to PROCESSING to prevent any tasks woken up by the loop below from
+ // writing to the eventfd.
+ self.state.store(PROCESSING, Ordering::Release);
+
+ let mut ring = self.ring.lock();
+ for (raw_token, result) in events {
+ // While the `expect()` might fail on arbitrary `u64`s, the `raw_token` was
+ // something that we originally gave to the kernel and that was created from a
+ // `usize` so we should always be able to convert it back into a `usize`.
+ let token = raw_token
+ .try_into()
+ .expect("`u64` doesn't fit inside a `usize`");
+
+ let op = ring
+ .ops
+ .get_mut(token)
+ .expect("Received completion token for unexpected operation");
+ match mem::replace(op, OpStatus::Completed(Some(result))) {
+ // No one is waiting on a Nop.
+ OpStatus::Nop => mem::drop(ring.ops.remove(token)),
+ OpStatus::Pending(data) => {
+ if data.canceled {
+ // No one is waiting for this operation and the uring is done with
+ // it so it's safe to remove.
+ ring.ops.remove(token);
+ }
+ if let Some(waker) = data.waker {
+ waker.wake();
+ }
+ }
+ OpStatus::Completed(_) => panic!("uring operation completed more than once"),
+ }
+ }
+ }
+ }
+
+ fn get_result(&self, token: &WakerToken, cx: &mut Context) -> Option<io::Result<u32>> {
+ let mut ring = self.ring.lock();
+
+ let op = ring
+ .ops
+ .get_mut(token.0)
+ .expect("`get_result` called on unknown operation");
+ match op {
+ OpStatus::Nop => panic!("`get_result` called on nop"),
+ OpStatus::Pending(data) => {
+ if data.canceled {
+ panic!("`get_result` called on canceled operation");
+ }
+ data.waker = Some(cx.waker().clone());
+ None
+ }
+ OpStatus::Completed(res) => {
+ let out = res.take();
+ ring.ops.remove(token.0);
+ Some(out.expect("Missing result in completed operation"))
+ }
+ }
+ }
+
+ // Remove the waker for the given token if it hasn't fired yet.
+ fn cancel_operation(&self, token: WakerToken) {
+ let mut ring = self.ring.lock();
+ if let Some(op) = ring.ops.get_mut(token.0) {
+ match op {
+ OpStatus::Nop => panic!("`cancel_operation` called on nop"),
+ OpStatus::Pending(data) => {
+ if data.canceled {
+ panic!("uring operation canceled more than once");
+ }
+
+ // Clear the waker as it is no longer needed.
+ data.waker = None;
+ data.canceled = true;
+
+ // Keep the rest of the op data as the uring might still be accessing either
+ // the source of the backing memory so it needs to live until the kernel
+ // completes the operation. TODO: cancel the operation in the uring.
+ }
+ OpStatus::Completed(_) => {
+ ring.ops.remove(token.0);
+ }
+ }
+ }
+ }
+
+ fn register_source(&self, f: Arc<File>) -> usize {
+ self.ring.lock().registered_sources.insert(f)
+ }
+
+ fn deregister_source(&self, source: &RegisteredSource) {
+ // There isn't any need to pull pending ops out, the all have Arc's to the file and mem they
+ // need.let them complete. deregister with pending ops is not a common path no need to
+ // optimize that case yet.
+ self.ring.lock().registered_sources.remove(source.tag);
+ }
+
+ fn submit_poll(
+ &self,
+ source: &RegisteredSource,
+ events: &sys_util::WatchingEvents,
+ ) -> Result<WakerToken> {
+ let mut ring = self.ring.lock();
+ let src = ring
+ .registered_sources
+ .get(source.tag)
+ .map(Arc::clone)
+ .ok_or(Error::InvalidSource)?;
+ let entry = ring.ops.vacant_entry();
+ let next_op_token = entry.key();
+ self.ctx
+ .add_poll_fd(src.as_raw_fd(), events, usize_to_u64(next_op_token))
+ .map_err(Error::SubmittingOp)?;
+
+ entry.insert(OpStatus::Pending(OpData {
+ _file: src,
+ _mem: None,
+ waker: None,
+ canceled: false,
+ }));
+
+ Ok(WakerToken(next_op_token))
+ }
+
+ fn submit_fallocate(
+ &self,
+ source: &RegisteredSource,
+ offset: u64,
+ len: u64,
+ mode: u32,
+ ) -> Result<WakerToken> {
+ let mut ring = self.ring.lock();
+ let src = ring
+ .registered_sources
+ .get(source.tag)
+ .map(Arc::clone)
+ .ok_or(Error::InvalidSource)?;
+ let entry = ring.ops.vacant_entry();
+ let next_op_token = entry.key();
+ self.ctx
+ .add_fallocate(
+ src.as_raw_fd(),
+ offset,
+ len,
+ mode,
+ usize_to_u64(next_op_token),
+ )
+ .map_err(Error::SubmittingOp)?;
+
+ entry.insert(OpStatus::Pending(OpData {
+ _file: src,
+ _mem: None,
+ waker: None,
+ canceled: false,
+ }));
+
+ Ok(WakerToken(next_op_token))
+ }
+
+ fn submit_fsync(&self, source: &RegisteredSource) -> Result<WakerToken> {
+ let mut ring = self.ring.lock();
+ let src = ring
+ .registered_sources
+ .get(source.tag)
+ .map(Arc::clone)
+ .ok_or(Error::InvalidSource)?;
+ let entry = ring.ops.vacant_entry();
+ let next_op_token = entry.key();
+ self.ctx
+ .add_fsync(src.as_raw_fd(), usize_to_u64(next_op_token))
+ .map_err(Error::SubmittingOp)?;
+
+ entry.insert(OpStatus::Pending(OpData {
+ _file: src,
+ _mem: None,
+ waker: None,
+ canceled: false,
+ }));
+
+ Ok(WakerToken(next_op_token))
+ }
+
+ fn submit_read_to_vectored(
+ &self,
+ source: &RegisteredSource,
+ mem: Arc<dyn BackingMemory + Send + Sync>,
+ offset: u64,
+ addrs: &[MemRegion],
+ ) -> Result<WakerToken> {
+ if addrs
+ .iter()
+ .any(|&mem_range| mem.get_volatile_slice(mem_range).is_err())
+ {
+ return Err(Error::InvalidOffset);
+ }
+
+ let mut ring = self.ring.lock();
+ let src = ring
+ .registered_sources
+ .get(source.tag)
+ .map(Arc::clone)
+ .ok_or(Error::InvalidSource)?;
+
+ // We can't insert the OpData into the slab yet because `iovecs` borrows `mem` below.
+ let entry = ring.ops.vacant_entry();
+ let next_op_token = entry.key();
+
+ // The addresses have already been validated, so unwrapping them will succeed.
+ // validate their addresses before submitting.
+ let iovecs = addrs.iter().map(|&mem_range| {
+ *mem.get_volatile_slice(mem_range)
+ .unwrap()
+ .as_iobuf()
+ .as_ref()
+ });
+
+ unsafe {
+ // Safe because all the addresses are within the Memory that an Arc is kept for the
+ // duration to ensure the memory is valid while the kernel accesses it.
+ // Tested by `dont_drop_backing_mem_read` unit test.
+ self.ctx
+ .add_readv_iter(iovecs, src.as_raw_fd(), offset, usize_to_u64(next_op_token))
+ .map_err(Error::SubmittingOp)?;
+ }
+
+ entry.insert(OpStatus::Pending(OpData {
+ _file: src,
+ _mem: Some(mem),
+ waker: None,
+ canceled: false,
+ }));
+
+ Ok(WakerToken(next_op_token))
+ }
+
+ fn submit_write_from_vectored(
+ &self,
+ source: &RegisteredSource,
+ mem: Arc<dyn BackingMemory + Send + Sync>,
+ offset: u64,
+ addrs: &[MemRegion],
+ ) -> Result<WakerToken> {
+ if addrs
+ .iter()
+ .any(|&mem_range| mem.get_volatile_slice(mem_range).is_err())
+ {
+ return Err(Error::InvalidOffset);
+ }
+
+ let mut ring = self.ring.lock();
+ let src = ring
+ .registered_sources
+ .get(source.tag)
+ .map(Arc::clone)
+ .ok_or(Error::InvalidSource)?;
+
+ // We can't insert the OpData into the slab yet because `iovecs` borrows `mem` below.
+ let entry = ring.ops.vacant_entry();
+ let next_op_token = entry.key();
+
+ // The addresses have already been validated, so unwrapping them will succeed.
+ // validate their addresses before submitting.
+ let iovecs = addrs.iter().map(|&mem_range| {
+ *mem.get_volatile_slice(mem_range)
+ .unwrap()
+ .as_iobuf()
+ .as_ref()
+ });
+
+ unsafe {
+ // Safe because all the addresses are within the Memory that an Arc is kept for the
+ // duration to ensure the memory is valid while the kernel accesses it.
+ // Tested by `dont_drop_backing_mem_write` unit test.
+ self.ctx
+ .add_writev_iter(iovecs, src.as_raw_fd(), offset, usize_to_u64(next_op_token))
+ .map_err(Error::SubmittingOp)?;
+ }
+
+ entry.insert(OpStatus::Pending(OpData {
+ _file: src,
+ _mem: Some(mem),
+ waker: None,
+ canceled: false,
+ }));
+
+ Ok(WakerToken(next_op_token))
+ }
+}
+
+impl WeakWake for RawExecutor {
+ fn wake_by_ref(weak_self: &Weak<Self>) {
+ if let Some(arc_self) = weak_self.upgrade() {
+ RawExecutor::wake(&arc_self);
+ }
+ }
+}
+
+impl Drop for RawExecutor {
+ fn drop(&mut self) {
+ // Wake up any futures still waiting on uring operations.
+ let ring = self.ring.get_mut();
+ for (_, op) in ring.ops.iter_mut() {
+ match op {
+ OpStatus::Nop => {}
+ OpStatus::Pending(data) => {
+ // If the operation wasn't already canceled then wake up the future waiting on
+ // it. When polled that future will get an ExecutorGone error anyway so there's
+ // no point in waiting until the operation completes to wake it up.
+ if !data.canceled {
+ if let Some(waker) = data.waker.take() {
+ waker.wake();
+ }
+ }
+
+ data.canceled = true;
+ }
+ OpStatus::Completed(_) => {}
+ }
+ }
+
+ // Since the RawExecutor is wrapped in an Arc it may end up being dropped from a different
+ // thread than the one that called `run` or `run_until`. Since we know there are no other
+ // references, just clear the thread id so that we don't panic.
+ *self.thread_id.lock() = None;
+
+ // Now run the executor loop once more to poll any futures we just woke up.
+ let waker = noop_waker();
+ let mut cx = Context::from_waker(&waker);
+ let res = self.run(&mut cx, async {});
+
+ if let Err(e) = res {
+ warn!("Failed to drive uring to completion: {}", e);
+ }
+ }
+}
+
+/// An executor that uses io_uring for its asynchronous I/O operations. See the documentation of
+/// `Executor` for more details about the methods.
+#[derive(Clone)]
+pub struct URingExecutor {
+ raw: Arc<RawExecutor>,
+}
+
+impl URingExecutor {
+ pub fn new() -> Result<URingExecutor> {
+ let raw = RawExecutor::new().map(Arc::new)?;
+
+ Ok(URingExecutor { raw })
+ }
+
+ pub fn spawn<F>(&self, f: F) -> Task<F::Output>
+ where
+ F: Future + Send + 'static,
+ F::Output: Send + 'static,
+ {
+ self.raw.spawn(f)
+ }
+
+ pub fn spawn_local<F>(&self, f: F) -> Task<F::Output>
+ where
+ F: Future + 'static,
+ F::Output: 'static,
+ {
+ self.raw.spawn_local(f)
+ }
+
+ pub fn spawn_blocking<F, R>(&self, f: F) -> Task<R>
+ where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+ {
+ self.raw.spawn_blocking(f)
+ }
+
+ pub fn run(&self) -> Result<()> {
+ let waker = new_waker(Arc::downgrade(&self.raw));
+ let mut cx = Context::from_waker(&waker);
+
+ self.raw.run(&mut cx, super::empty::<()>())
+ }
+
+ pub fn run_until<F: Future>(&self, f: F) -> Result<F::Output> {
+ let waker = new_waker(Arc::downgrade(&self.raw));
+ let mut ctx = Context::from_waker(&waker);
+ self.raw.run(&mut ctx, f)
+ }
+
+ /// Register a file and memory pair for buffered asynchronous operation.
+ pub(crate) fn register_source<F: AsRawFd>(&self, fd: &F) -> Result<RegisteredSource> {
+ let duped_fd = unsafe {
+ // Safe because duplicating an FD doesn't affect memory safety, and the dup'd FD
+ // will only be added to the poll loop.
+ File::from_raw_fd(dup_fd(fd.as_raw_fd())?)
+ };
+
+ Ok(RegisteredSource {
+ tag: self.raw.register_source(Arc::new(duped_fd)),
+ ex: Arc::downgrade(&self.raw),
+ })
+ }
+}
+
+// Used to dup the FDs passed to the executor so there is a guarantee they aren't closed while
+// waiting in TLS to be added to the main polling context.
+unsafe fn dup_fd(fd: RawFd) -> Result<RawFd> {
+ let ret = libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0);
+ if ret < 0 {
+ Err(Error::DuplicatingFd(sys_util::Error::last()))
+ } else {
+ Ok(ret)
+ }
+}
+
+// Converts a `usize` into a `u64` and panics if the conversion fails.
+#[inline]
+fn usize_to_u64(val: usize) -> u64 {
+ val.try_into().expect("`usize` doesn't fit inside a `u64`")
+}
+
+pub struct PendingOperation {
+ waker_token: Option<WakerToken>,
+ ex: Weak<RawExecutor>,
+ submitted: bool,
+}
+
+impl Future for PendingOperation {
+ type Output = Result<u32>;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ let token = self
+ .waker_token
+ .as_ref()
+ .expect("PendingOperation polled after returning Poll::Ready");
+ if let Some(ex) = self.ex.upgrade() {
+ if let Some(result) = ex.get_result(token, cx) {
+ self.waker_token = None;
+ Poll::Ready(result.map_err(Error::Io))
+ } else {
+ // If we haven't submitted the operation yet, and the executor runs on a different
+ // thread then submit it now. Otherwise the executor will submit it automatically
+ // the next time it calls UringContext::wait.
+ if !self.submitted && !ex.runs_tasks_on_current_thread() {
+ match ex.ctx.submit() {
+ Ok(()) => self.submitted = true,
+ // If the kernel ring is full then wait until some ops are removed from the
+ // completion queue. This op should get submitted the next time the executor
+ // calls UringContext::wait.
+ Err(io_uring::Error::RingEnter(libc::EBUSY)) => {}
+ Err(e) => return Poll::Ready(Err(Error::URingEnter(e))),
+ }
+ }
+ Poll::Pending
+ }
+ } else {
+ Poll::Ready(Err(Error::ExecutorGone))
+ }
+ }
+}
+
+impl Drop for PendingOperation {
+ fn drop(&mut self) {
+ if let Some(waker_token) = self.waker_token.take() {
+ if let Some(ex) = self.ex.upgrade() {
+ ex.cancel_operation(waker_token);
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::{
+ future::Future,
+ io::{Read, Write},
+ mem,
+ pin::Pin,
+ task::{Context, Poll},
+ };
+
+ use futures::executor::block_on;
+
+ use super::{
+ super::mem::{BackingMemory, MemRegion, VecIoWrapper},
+ *,
+ };
+
+ // A future that returns ready when the uring queue is empty.
+ struct UringQueueEmpty<'a> {
+ ex: &'a URingExecutor,
+ }
+
+ impl<'a> Future for UringQueueEmpty<'a> {
+ type Output = ();
+
+ fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
+ if self.ex.raw.ring.lock().ops.is_empty() {
+ Poll::Ready(())
+ } else {
+ Poll::Pending
+ }
+ }
+ }
+
+ #[test]
+ fn dont_drop_backing_mem_read() {
+ if !use_uring() {
+ return;
+ }
+
+ // Create a backing memory wrapped in an Arc and check that the drop isn't called while the
+ // op is pending.
+ let bm =
+ Arc::new(VecIoWrapper::from(vec![0u8; 4096])) as Arc<dyn BackingMemory + Send + Sync>;
+
+ // Use pipes to create a future that will block forever.
+ let (rx, mut tx) = sys_util::pipe(true).unwrap();
+
+ // Set up the TLS for the uring_executor by creating one.
+ let ex = URingExecutor::new().unwrap();
+
+ // Register the receive side of the pipe with the executor.
+ let registered_source = ex.register_source(&rx).expect("register source failed");
+
+ // Submit the op to the kernel. Next, test that the source keeps its Arc open for the duration
+ // of the op.
+ let pending_op = registered_source
+ .start_read_to_mem(0, Arc::clone(&bm), &[MemRegion { offset: 0, len: 8 }])
+ .expect("failed to start read to mem");
+
+ // Here the Arc count must be two, one for `bm` and one to signify that the kernel has a
+ // reference while the op is active.
+ assert_eq!(Arc::strong_count(&bm), 2);
+
+ // Dropping the operation shouldn't reduce the Arc count, as the kernel could still be using
+ // it.
+ drop(pending_op);
+ assert_eq!(Arc::strong_count(&bm), 2);
+
+ // Finishing the operation should put the Arc count back to 1.
+ // write to the pipe to wake the read pipe and then wait for the uring result in the
+ // executor.
+ tx.write_all(&[0u8; 8]).expect("write failed");
+ ex.run_until(UringQueueEmpty { ex: &ex })
+ .expect("Failed to wait for read pipe ready");
+ assert_eq!(Arc::strong_count(&bm), 1);
+ }
+
+ #[test]
+ fn dont_drop_backing_mem_write() {
+ if !use_uring() {
+ return;
+ }
+
+ // Create a backing memory wrapped in an Arc and check that the drop isn't called while the
+ // op is pending.
+ let bm =
+ Arc::new(VecIoWrapper::from(vec![0u8; 4096])) as Arc<dyn BackingMemory + Send + Sync>;
+
+ // Use pipes to create a future that will block forever.
+ let (mut rx, tx) = sys_util::new_pipe_full().expect("Pipe failed");
+
+ // Set up the TLS for the uring_executor by creating one.
+ let ex = URingExecutor::new().unwrap();
+
+ // Register the receive side of the pipe with the executor.
+ let registered_source = ex.register_source(&tx).expect("register source failed");
+
+ // Submit the op to the kernel. Next, test that the source keeps its Arc open for the duration
+ // of the op.
+ let pending_op = registered_source
+ .start_write_from_mem(0, Arc::clone(&bm), &[MemRegion { offset: 0, len: 8 }])
+ .expect("failed to start write to mem");
+
+ // Here the Arc count must be two, one for `bm` and one to signify that the kernel has a
+ // reference while the op is active.
+ assert_eq!(Arc::strong_count(&bm), 2);
+
+ // Dropping the operation shouldn't reduce the Arc count, as the kernel could still be using
+ // it.
+ drop(pending_op);
+ assert_eq!(Arc::strong_count(&bm), 2);
+
+ // Finishing the operation should put the Arc count back to 1.
+ // write to the pipe to wake the read pipe and then wait for the uring result in the
+ // executor.
+ let mut buf = vec![0u8; sys_util::round_up_to_page_size(1)];
+ rx.read_exact(&mut buf).expect("read to empty failed");
+ ex.run_until(UringQueueEmpty { ex: &ex })
+ .expect("Failed to wait for write pipe ready");
+ assert_eq!(Arc::strong_count(&bm), 1);
+ }
+
+ #[test]
+ fn canceled_before_completion() {
+ if !use_uring() {
+ return;
+ }
+
+ async fn cancel_io(op: PendingOperation) {
+ mem::drop(op);
+ }
+
+ async fn check_result(op: PendingOperation, expected: u32) {
+ let actual = op.await.expect("operation failed to complete");
+ assert_eq!(expected, actual);
+ }
+
+ let bm =
+ Arc::new(VecIoWrapper::from(vec![0u8; 16])) as Arc<dyn BackingMemory + Send + Sync>;
+
+ let (rx, tx) = sys_util::pipe(true).expect("Pipe failed");
+
+ let ex = URingExecutor::new().unwrap();
+
+ let rx_source = ex.register_source(&rx).expect("register source failed");
+ let tx_source = ex.register_source(&tx).expect("register source failed");
+
+ let read_task = rx_source
+ .start_read_to_mem(0, Arc::clone(&bm), &[MemRegion { offset: 0, len: 8 }])
+ .expect("failed to start read to mem");
+
+ ex.spawn_local(cancel_io(read_task)).detach();
+
+ // Write to the pipe so that the kernel operation will complete.
+ let buf =
+ Arc::new(VecIoWrapper::from(vec![0xc2u8; 16])) as Arc<dyn BackingMemory + Send + Sync>;
+ let write_task = tx_source
+ .start_write_from_mem(0, Arc::clone(&buf), &[MemRegion { offset: 0, len: 8 }])
+ .expect("failed to start write from mem");
+
+ ex.run_until(check_result(write_task, 8))
+ .expect("Failed to run executor");
+ }
+
+ #[test]
+ fn drop_before_completion() {
+ if !use_uring() {
+ return;
+ }
+
+ const VALUE: u64 = 0xef6c_a8df_b842_eb9c;
+
+ async fn check_op(op: PendingOperation) {
+ let err = op.await.expect_err("Op completed successfully");
+ match err {
+ Error::ExecutorGone => {}
+ e => panic!("Unexpected error from op: {}", e),
+ }
+ }
+
+ let (mut rx, mut tx) = sys_util::pipe(true).expect("Pipe failed");
+
+ let ex = URingExecutor::new().unwrap();
+
+ let tx_source = ex.register_source(&tx).expect("Failed to register source");
+ let bm = Arc::new(VecIoWrapper::from(VALUE.to_ne_bytes().to_vec()));
+ let op = tx_source
+ .start_write_from_mem(
+ 0,
+ bm,
+ &[MemRegion {
+ offset: 0,
+ len: mem::size_of::<u64>(),
+ }],
+ )
+ .expect("Failed to start write from mem");
+
+ ex.spawn_local(check_op(op)).detach();
+
+ // Now drop the executor. It shouldn't run the write operation.
+ mem::drop(ex);
+
+ // Make sure the executor did not complete the uring operation.
+ let new_val = [0x2e; 8];
+ tx.write_all(&new_val).unwrap();
+
+ let mut buf = 0u64.to_ne_bytes();
+ rx.read_exact(&mut buf[..])
+ .expect("Failed to read from pipe");
+
+ assert_eq!(buf, new_val);
+ }
+
+ #[test]
+ fn drop_on_different_thread() {
+ if !use_uring() {
+ return;
+ }
+
+ let ex = URingExecutor::new().unwrap();
+
+ let ex2 = ex.clone();
+ let t = thread::spawn(move || ex2.run_until(async {}));
+
+ t.join().unwrap().unwrap();
+
+ // Leave an uncompleted operation in the queue so that the drop impl will try to drive it to
+ // completion.
+ let (_rx, tx) = sys_util::pipe(true).expect("Pipe failed");
+ let tx = ex.register_source(&tx).expect("Failed to register source");
+ let bm = Arc::new(VecIoWrapper::from(0xf2e96u64.to_ne_bytes().to_vec()));
+ let op = tx
+ .start_write_from_mem(
+ 0,
+ bm,
+ &[MemRegion {
+ offset: 0,
+ len: mem::size_of::<u64>(),
+ }],
+ )
+ .expect("Failed to start write from mem");
+
+ mem::drop(ex);
+
+ match block_on(op).expect_err("Pending operation completed after executor was dropped") {
+ Error::ExecutorGone => {}
+ e => panic!("Unexpected error after dropping executor: {}", e),
+ }
+ }
+}
diff --git a/common/cros_async/src/uring_source.rs b/common/cros_async/src/uring_source.rs
new file mode 100644
index 000000000..c5d44452c
--- /dev/null
+++ b/common/cros_async/src/uring_source.rs
@@ -0,0 +1,643 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ convert::TryInto,
+ io,
+ ops::{Deref, DerefMut},
+ os::unix::io::AsRawFd,
+ sync::Arc,
+};
+
+use async_trait::async_trait;
+
+use super::{
+ mem::{BackingMemory, MemRegion, VecIoWrapper},
+ uring_executor::{Error, RegisteredSource, Result, URingExecutor},
+ AsyncError, AsyncResult,
+};
+
+/// `UringSource` wraps FD backed IO sources for use with io_uring. It is a thin wrapper around
+/// registering an IO source with the uring that provides an `IoSource` implementation.
+/// Most useful functions are provided by 'IoSourceExt'.
+pub struct UringSource<F: AsRawFd> {
+ registered_source: RegisteredSource,
+ source: F,
+}
+
+impl<F: AsRawFd> UringSource<F> {
+ /// Creates a new `UringSource` that wraps the given `io_source` object.
+ pub fn new(io_source: F, ex: &URingExecutor) -> Result<UringSource<F>> {
+ let r = ex.register_source(&io_source)?;
+ Ok(UringSource {
+ registered_source: r,
+ source: io_source,
+ })
+ }
+
+ /// Consume `self` and return the object used to create it.
+ pub fn into_source(self) -> F {
+ self.source
+ }
+}
+
+#[async_trait(?Send)]
+impl<F: AsRawFd> super::ReadAsync for UringSource<F> {
+ /// Reads from the iosource at `file_offset` and fill the given `vec`.
+ async fn read_to_vec<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ vec: Vec<u8>,
+ ) -> AsyncResult<(usize, Vec<u8>)> {
+ let buf = Arc::new(VecIoWrapper::from(vec));
+ let op = self.registered_source.start_read_to_mem(
+ file_offset.unwrap_or(0),
+ buf.clone(),
+ &[MemRegion {
+ offset: 0,
+ len: buf.len(),
+ }],
+ )?;
+ let len = op.await?;
+ let bytes = if let Ok(v) = Arc::try_unwrap(buf) {
+ v.into()
+ } else {
+ panic!("too many refs on buf");
+ };
+
+ Ok((len as usize, bytes))
+ }
+
+ /// Wait for the FD of `self` to be readable.
+ async fn wait_readable(&self) -> AsyncResult<()> {
+ let op = self.registered_source.poll_fd_readable()?;
+ op.await?;
+ Ok(())
+ }
+
+ /// Reads a single u64 (e.g. from an eventfd).
+ async fn read_u64(&self) -> AsyncResult<u64> {
+ // This doesn't just forward to read_to_vec to avoid an unnecessary extra allocation from
+ // async-trait.
+ let buf = Arc::new(VecIoWrapper::from(0u64.to_ne_bytes().to_vec()));
+ let op = self.registered_source.start_read_to_mem(
+ 0,
+ buf.clone(),
+ &[MemRegion {
+ offset: 0,
+ len: buf.len(),
+ }],
+ )?;
+ let len = op.await?;
+ if len != buf.len() as u32 {
+ Err(AsyncError::Uring(Error::Io(io::Error::new(
+ io::ErrorKind::Other,
+ format!("expected to read {} bytes, but read {}", buf.len(), len),
+ ))))
+ } else {
+ let bytes: Vec<u8> = if let Ok(v) = Arc::try_unwrap(buf) {
+ v.into()
+ } else {
+ panic!("too many refs on buf");
+ };
+
+ // Will never panic because bytes is of the appropriate size.
+ Ok(u64::from_ne_bytes(bytes[..].try_into().unwrap()))
+ }
+ }
+
+ /// Reads to the given `mem` at the given offsets from the file starting at `file_offset`.
+ async fn read_to_mem<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ mem: Arc<dyn BackingMemory + Send + Sync>,
+ mem_offsets: &'a [MemRegion],
+ ) -> AsyncResult<usize> {
+ let op =
+ self.registered_source
+ .start_read_to_mem(file_offset.unwrap_or(0), mem, mem_offsets)?;
+ let len = op.await?;
+ Ok(len as usize)
+ }
+}
+
+#[async_trait(?Send)]
+impl<F: AsRawFd> super::WriteAsync for UringSource<F> {
+ /// Writes from the given `vec` to the file starting at `file_offset`.
+ async fn write_from_vec<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ vec: Vec<u8>,
+ ) -> AsyncResult<(usize, Vec<u8>)> {
+ let buf = Arc::new(VecIoWrapper::from(vec));
+ let op = self.registered_source.start_write_from_mem(
+ file_offset.unwrap_or(0),
+ buf.clone(),
+ &[MemRegion {
+ offset: 0,
+ len: buf.len(),
+ }],
+ )?;
+ let len = op.await?;
+ let bytes = if let Ok(v) = Arc::try_unwrap(buf) {
+ v.into()
+ } else {
+ panic!("too many refs on buf");
+ };
+
+ Ok((len as usize, bytes))
+ }
+
+ /// Writes from the given `mem` from the given offsets to the file starting at `file_offset`.
+ async fn write_from_mem<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ mem: Arc<dyn BackingMemory + Send + Sync>,
+ mem_offsets: &'a [MemRegion],
+ ) -> AsyncResult<usize> {
+ let op = self.registered_source.start_write_from_mem(
+ file_offset.unwrap_or(0),
+ mem,
+ mem_offsets,
+ )?;
+ let len = op.await?;
+ Ok(len as usize)
+ }
+
+ /// See `fallocate(2)`. Note this op is synchronous when using the Polled backend.
+ async fn fallocate(&self, file_offset: u64, len: u64, mode: u32) -> AsyncResult<()> {
+ let op = self
+ .registered_source
+ .start_fallocate(file_offset, len, mode)?;
+ let _ = op.await?;
+ Ok(())
+ }
+
+ /// Sync all completed write operations to the backing storage.
+ async fn fsync(&self) -> AsyncResult<()> {
+ let op = self.registered_source.start_fsync()?;
+ let _ = op.await?;
+ Ok(())
+ }
+}
+
+#[async_trait(?Send)]
+impl<F: AsRawFd> super::IoSourceExt<F> for UringSource<F> {
+ /// Yields the underlying IO source.
+ fn into_source(self: Box<Self>) -> F {
+ self.source
+ }
+
+ /// Provides a mutable ref to the underlying IO source.
+ fn as_source(&self) -> &F {
+ &self.source
+ }
+
+ /// Provides a ref to the underlying IO source.
+ fn as_source_mut(&mut self) -> &mut F {
+ &mut self.source
+ }
+}
+
+impl<F: AsRawFd> Deref for UringSource<F> {
+ type Target = F;
+
+ fn deref(&self) -> &Self::Target {
+ &self.source
+ }
+}
+
+impl<F: AsRawFd> DerefMut for UringSource<F> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.source
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::{
+ fs::{File, OpenOptions},
+ os::unix::io::AsRawFd,
+ path::PathBuf,
+ };
+
+ use super::super::{
+ io_ext::{ReadAsync, WriteAsync},
+ uring_executor::use_uring,
+ UringSource,
+ };
+
+ use super::*;
+
+ #[test]
+ fn read_to_mem() {
+ if !use_uring() {
+ return;
+ }
+
+ use super::super::mem::VecIoWrapper;
+ use std::io::Write;
+ use tempfile::tempfile;
+
+ let ex = URingExecutor::new().unwrap();
+ // Use guest memory as a test file, it implements AsRawFd.
+ let mut source = tempfile().unwrap();
+ let data = vec![0x55; 8192];
+ source.write_all(&data).unwrap();
+
+ let io_obj = UringSource::new(source, &ex).unwrap();
+
+ // Start with memory filled with 0x44s.
+ let buf: Arc<VecIoWrapper> = Arc::new(VecIoWrapper::from(vec![0x44; 8192]));
+
+ let fut = io_obj.read_to_mem(
+ None,
+ Arc::<VecIoWrapper>::clone(&buf),
+ &[MemRegion {
+ offset: 0,
+ len: 8192,
+ }],
+ );
+ assert_eq!(8192, ex.run_until(fut).unwrap().unwrap());
+ let vec: Vec<u8> = match Arc::try_unwrap(buf) {
+ Ok(v) => v.into(),
+ Err(_) => panic!("Too many vec refs"),
+ };
+ assert!(vec.iter().all(|&b| b == 0x55));
+ }
+
+ #[test]
+ fn readvec() {
+ if !use_uring() {
+ return;
+ }
+
+ async fn go(ex: &URingExecutor) {
+ let f = File::open("/dev/zero").unwrap();
+ let source = UringSource::new(f, ex).unwrap();
+ let v = vec![0x55u8; 32];
+ let v_ptr = v.as_ptr();
+ let ret = source.read_to_vec(None, v).await.unwrap();
+ assert_eq!(ret.0, 32);
+ let ret_v = ret.1;
+ assert_eq!(v_ptr, ret_v.as_ptr());
+ assert!(ret_v.iter().all(|&b| b == 0));
+ }
+
+ let ex = URingExecutor::new().unwrap();
+ ex.run_until(go(&ex)).unwrap();
+ }
+
+ #[test]
+ fn readmulti() {
+ if !use_uring() {
+ return;
+ }
+
+ async fn go(ex: &URingExecutor) {
+ let f = File::open("/dev/zero").unwrap();
+ let source = UringSource::new(f, ex).unwrap();
+ let v = vec![0x55u8; 32];
+ let v2 = vec![0x55u8; 32];
+ let (ret, ret2) = futures::future::join(
+ source.read_to_vec(None, v),
+ source.read_to_vec(Some(32), v2),
+ )
+ .await;
+
+ assert!(ret.unwrap().1.iter().all(|&b| b == 0));
+ assert!(ret2.unwrap().1.iter().all(|&b| b == 0));
+ }
+
+ let ex = URingExecutor::new().unwrap();
+ ex.run_until(go(&ex)).unwrap();
+ }
+
+ async fn read_u64<T: AsRawFd>(source: &UringSource<T>) -> u64 {
+ // Init a vec that translates to u64::max;
+ let u64_mem = vec![0xffu8; std::mem::size_of::<u64>()];
+ let (ret, u64_mem) = source.read_to_vec(None, u64_mem).await.unwrap();
+ assert_eq!(ret as usize, std::mem::size_of::<u64>());
+ let mut val = 0u64.to_ne_bytes();
+ val.copy_from_slice(&u64_mem);
+ u64::from_ne_bytes(val)
+ }
+
+ #[test]
+ fn u64_from_file() {
+ if !use_uring() {
+ return;
+ }
+
+ let f = File::open("/dev/zero").unwrap();
+ let ex = URingExecutor::new().unwrap();
+ let source = UringSource::new(f, &ex).unwrap();
+
+ assert_eq!(0u64, ex.run_until(read_u64(&source)).unwrap());
+ }
+
+ #[test]
+ fn event() {
+ if !use_uring() {
+ return;
+ }
+
+ use sys_util::EventFd;
+
+ async fn write_event(ev: EventFd, wait: EventFd, ex: &URingExecutor) {
+ let wait = UringSource::new(wait, ex).unwrap();
+ ev.write(55).unwrap();
+ read_u64(&wait).await;
+ ev.write(66).unwrap();
+ read_u64(&wait).await;
+ ev.write(77).unwrap();
+ read_u64(&wait).await;
+ }
+
+ async fn read_events(ev: EventFd, signal: EventFd, ex: &URingExecutor) {
+ let source = UringSource::new(ev, ex).unwrap();
+ assert_eq!(read_u64(&source).await, 55);
+ signal.write(1).unwrap();
+ assert_eq!(read_u64(&source).await, 66);
+ signal.write(1).unwrap();
+ assert_eq!(read_u64(&source).await, 77);
+ signal.write(1).unwrap();
+ }
+
+ let event = EventFd::new().unwrap();
+ let signal_wait = EventFd::new().unwrap();
+ let ex = URingExecutor::new().unwrap();
+ let write_task = write_event(
+ event.try_clone().unwrap(),
+ signal_wait.try_clone().unwrap(),
+ &ex,
+ );
+ let read_task = read_events(event, signal_wait, &ex);
+ ex.run_until(futures::future::join(read_task, write_task))
+ .unwrap();
+ }
+
+ #[test]
+ fn pend_on_pipe() {
+ if !use_uring() {
+ return;
+ }
+
+ use std::io::Write;
+
+ use futures::future::Either;
+
+ async fn do_test(ex: &URingExecutor) {
+ let (read_source, mut w) = sys_util::pipe(true).unwrap();
+ let source = UringSource::new(read_source, ex).unwrap();
+ let done = Box::pin(async { 5usize });
+ let pending = Box::pin(read_u64(&source));
+ match futures::future::select(pending, done).await {
+ Either::Right((5, pending)) => {
+ // Write to the pipe so that the kernel will release the memory associated with
+ // the uring read operation.
+ w.write_all(&[0]).expect("failed to write to pipe");
+ ::std::mem::drop(pending);
+ }
+ _ => panic!("unexpected select result"),
+ };
+ }
+
+ let ex = URingExecutor::new().unwrap();
+ ex.run_until(do_test(&ex)).unwrap();
+ }
+
+ #[test]
+ fn readmem() {
+ if !use_uring() {
+ return;
+ }
+
+ async fn go(ex: &URingExecutor) {
+ let f = File::open("/dev/zero").unwrap();
+ let source = UringSource::new(f, ex).unwrap();
+ let v = vec![0x55u8; 64];
+ let vw = Arc::new(VecIoWrapper::from(v));
+ let ret = source
+ .read_to_mem(
+ None,
+ Arc::<VecIoWrapper>::clone(&vw),
+ &[MemRegion { offset: 0, len: 32 }],
+ )
+ .await
+ .unwrap();
+ assert_eq!(32, ret);
+ let vec: Vec<u8> = match Arc::try_unwrap(vw) {
+ Ok(v) => v.into(),
+ Err(_) => panic!("Too many vec refs"),
+ };
+ assert!(vec.iter().take(32).all(|&b| b == 0));
+ assert!(vec.iter().skip(32).all(|&b| b == 0x55));
+
+ // test second half of memory too.
+ let v = vec![0x55u8; 64];
+ let vw = Arc::new(VecIoWrapper::from(v));
+ let ret = source
+ .read_to_mem(
+ None,
+ Arc::<VecIoWrapper>::clone(&vw),
+ &[MemRegion {
+ offset: 32,
+ len: 32,
+ }],
+ )
+ .await
+ .unwrap();
+ assert_eq!(32, ret);
+ let v: Vec<u8> = match Arc::try_unwrap(vw) {
+ Ok(v) => v.into(),
+ Err(_) => panic!("Too many vec refs"),
+ };
+ assert!(v.iter().take(32).all(|&b| b == 0x55));
+ assert!(v.iter().skip(32).all(|&b| b == 0));
+ }
+
+ let ex = URingExecutor::new().unwrap();
+ ex.run_until(go(&ex)).unwrap();
+ }
+
+ #[test]
+ fn range_error() {
+ if !use_uring() {
+ return;
+ }
+
+ async fn go(ex: &URingExecutor) {
+ let f = File::open("/dev/zero").unwrap();
+ let source = UringSource::new(f, ex).unwrap();
+ let v = vec![0x55u8; 64];
+ let vw = Arc::new(VecIoWrapper::from(v));
+ let ret = source
+ .read_to_mem(
+ None,
+ Arc::<VecIoWrapper>::clone(&vw),
+ &[MemRegion {
+ offset: 32,
+ len: 33,
+ }],
+ )
+ .await;
+ assert!(ret.is_err());
+ }
+
+ let ex = URingExecutor::new().unwrap();
+ ex.run_until(go(&ex)).unwrap();
+ }
+
+ #[test]
+ fn fallocate() {
+ if !use_uring() {
+ return;
+ }
+
+ async fn go(ex: &URingExecutor) {
+ let dir = tempfile::TempDir::new().unwrap();
+ let mut file_path = PathBuf::from(dir.path());
+ file_path.push("test");
+
+ let f = OpenOptions::new()
+ .create(true)
+ .write(true)
+ .open(&file_path)
+ .unwrap();
+ let source = UringSource::new(f, ex).unwrap();
+ if let Err(e) = source.fallocate(0, 4096, 0).await {
+ match e {
+ super::super::io_ext::Error::Uring(
+ super::super::uring_executor::Error::Io(io_err),
+ ) => {
+ if io_err.kind() == std::io::ErrorKind::InvalidInput {
+ // Skip the test on kernels before fallocate support.
+ return;
+ }
+ }
+ _ => panic!("Unexpected uring error on fallocate: {}", e),
+ }
+ }
+
+ let meta_data = std::fs::metadata(&file_path).unwrap();
+ assert_eq!(meta_data.len(), 4096);
+ }
+
+ let ex = URingExecutor::new().unwrap();
+ ex.run_until(go(&ex)).unwrap();
+ }
+
+ #[test]
+ fn fsync() {
+ if !use_uring() {
+ return;
+ }
+
+ async fn go(ex: &URingExecutor) {
+ let f = tempfile::tempfile().unwrap();
+ let source = UringSource::new(f, ex).unwrap();
+ source.fsync().await.unwrap();
+ }
+
+ let ex = URingExecutor::new().unwrap();
+ ex.run_until(go(&ex)).unwrap();
+ }
+
+ #[test]
+ fn wait_read() {
+ if !use_uring() {
+ return;
+ }
+
+ async fn go(ex: &URingExecutor) {
+ let f = File::open("/dev/zero").unwrap();
+ let source = UringSource::new(f, ex).unwrap();
+ source.wait_readable().await.unwrap();
+ }
+
+ let ex = URingExecutor::new().unwrap();
+ ex.run_until(go(&ex)).unwrap();
+ }
+
+ #[test]
+ fn writemem() {
+ if !use_uring() {
+ return;
+ }
+
+ async fn go(ex: &URingExecutor) {
+ let f = OpenOptions::new()
+ .create(true)
+ .write(true)
+ .open("/tmp/write_from_vec")
+ .unwrap();
+ let source = UringSource::new(f, ex).unwrap();
+ let v = vec![0x55u8; 64];
+ let vw = Arc::new(super::super::mem::VecIoWrapper::from(v));
+ let ret = source
+ .write_from_mem(None, vw, &[MemRegion { offset: 0, len: 32 }])
+ .await
+ .unwrap();
+ assert_eq!(32, ret);
+ }
+
+ let ex = URingExecutor::new().unwrap();
+ ex.run_until(go(&ex)).unwrap();
+ }
+
+ #[test]
+ fn writevec() {
+ if !use_uring() {
+ return;
+ }
+
+ async fn go(ex: &URingExecutor) {
+ let f = OpenOptions::new()
+ .create(true)
+ .truncate(true)
+ .write(true)
+ .open("/tmp/write_from_vec")
+ .unwrap();
+ let source = UringSource::new(f, ex).unwrap();
+ let v = vec![0x55u8; 32];
+ let v_ptr = v.as_ptr();
+ let (ret, ret_v) = source.write_from_vec(None, v).await.unwrap();
+ assert_eq!(32, ret);
+ assert_eq!(v_ptr, ret_v.as_ptr());
+ }
+
+ let ex = URingExecutor::new().unwrap();
+ ex.run_until(go(&ex)).unwrap();
+ }
+
+ #[test]
+ fn writemulti() {
+ if !use_uring() {
+ return;
+ }
+
+ async fn go(ex: &URingExecutor) {
+ let f = OpenOptions::new()
+ .create(true)
+ .truncate(true)
+ .write(true)
+ .open("/tmp/write_from_vec")
+ .unwrap();
+ let source = UringSource::new(f, ex).unwrap();
+ let v = vec![0x55u8; 32];
+ let v2 = vec![0x55u8; 32];
+ let (r, r2) = futures::future::join(
+ source.write_from_vec(None, v),
+ source.write_from_vec(Some(32), v2),
+ )
+ .await;
+ assert_eq!(32, r.unwrap().0);
+ assert_eq!(32, r2.unwrap().0);
+ }
+
+ let ex = URingExecutor::new().unwrap();
+ ex.run_until(go(&ex)).unwrap();
+ }
+}
diff --git a/common/cros_async/src/waker.rs b/common/cros_async/src/waker.rs
new file mode 100644
index 000000000..5daeb5771
--- /dev/null
+++ b/common/cros_async/src/waker.rs
@@ -0,0 +1,70 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ mem::{drop, ManuallyDrop},
+ sync::Weak,
+ task::{RawWaker, RawWakerVTable, Waker},
+};
+
+/// Wrapper around a usize used as a token to uniquely identify a pending waker.
+#[derive(Debug)]
+pub(crate) struct WakerToken(pub(crate) usize);
+
+/// Like `futures::task::ArcWake` but uses `Weak<T>` instead of `Arc<T>`.
+pub(crate) trait WeakWake: Send + Sync {
+ fn wake_by_ref(weak_self: &Weak<Self>);
+
+ fn wake(weak_self: Weak<Self>) {
+ Self::wake_by_ref(&weak_self)
+ }
+}
+
+fn waker_vtable<W: WeakWake>() -> &'static RawWakerVTable {
+ &RawWakerVTable::new(
+ clone_weak_raw::<W>,
+ wake_weak_raw::<W>,
+ wake_by_ref_weak_raw::<W>,
+ drop_weak_raw::<W>,
+ )
+}
+
+unsafe fn clone_weak_raw<W: WeakWake>(data: *const ()) -> RawWaker {
+ // Get a handle to the Weak<T> but wrap it in a ManuallyDrop so that we don't reduce the
+ // refcount at the end of this function.
+ let weak = ManuallyDrop::new(Weak::<W>::from_raw(data as *const W));
+
+ // Now increase the weak count and keep it in a ManuallyDrop so that it doesn't get decreased
+ // at the end of this function.
+ let _weak_clone: ManuallyDrop<_> = weak.clone();
+
+ RawWaker::new(data, waker_vtable::<W>())
+}
+
+unsafe fn wake_weak_raw<W: WeakWake>(data: *const ()) {
+ let weak: Weak<W> = Weak::from_raw(data as *const W);
+
+ WeakWake::wake(weak)
+}
+
+unsafe fn wake_by_ref_weak_raw<W: WeakWake>(data: *const ()) {
+ // Get a handle to the Weak<T> but wrap it in a ManuallyDrop so that we don't reduce the
+ // refcount at the end of this function.
+ let weak = ManuallyDrop::new(Weak::<W>::from_raw(data as *const W));
+
+ WeakWake::wake_by_ref(&weak)
+}
+
+unsafe fn drop_weak_raw<W: WeakWake>(data: *const ()) {
+ drop(Weak::from_raw(data as *const W))
+}
+
+pub(crate) fn new_waker<W: WeakWake>(w: Weak<W>) -> Waker {
+ unsafe {
+ Waker::from_raw(RawWaker::new(
+ w.into_raw() as *const (),
+ waker_vtable::<W>(),
+ ))
+ }
+}
diff --git a/common/cros_asyncv2/Cargo.toml b/common/cros_asyncv2/Cargo.toml
new file mode 100644
index 000000000..0dac52ba7
--- /dev/null
+++ b/common/cros_asyncv2/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "cros_async"
+version = "0.2.0"
+edition = "2021"
+
+[features]
+uring = ["io-uring"]
+
+[dependencies]
+anyhow = "1"
+async-task = "4"
+data_model = { path = "../data_model" } # provided by ebuild
+futures = { version = "0.3", default-features = false, features = ["alloc"] }
+intrusive-collections = "0.9"
+io-uring = { version = "0.5", optional = true, features = ["unstable"] }
+memoffset = "0.6"
+once_cell = "1.7"
+slab = "0.4"
+smallvec = { version = "1.6.1", default-features = false, features = ["union"] }
+sync = { path = "../sync" } # provided by ebuild
+thiserror = "1"
+
+[target.'cfg(unix)'.dependencies]
+libc = "0.2"
+mio = { version = "0.7", features = ["os-ext"] }
+sys_util = { path = "../sys_util" } # provided by ebuild
+
+[dev-dependencies]
+futures = { version = "*", features = ["executor"] }
+futures-executor = { version = "0.3", features = ["thread-pool"] }
+futures-util = "0.3"
+tempfile = "3"
+
+[workspace]
diff --git a/common/cros_asyncv2/cargo2android.json b/common/cros_asyncv2/cargo2android.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/common/cros_asyncv2/cargo2android.json
@@ -0,0 +1 @@
+{} \ No newline at end of file
diff --git a/common/cros_asyncv2/src/blocking.rs b/common/cros_asyncv2/src/blocking.rs
new file mode 100644
index 000000000..f6430a78b
--- /dev/null
+++ b/common/cros_asyncv2/src/blocking.rs
@@ -0,0 +1,9 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod block_on;
+mod pool;
+
+pub use block_on::*;
+pub use pool::*;
diff --git a/cros_async/src/sync/blocking.rs b/common/cros_asyncv2/src/blocking/block_on.rs
index ce02e4dd7..bbd5110fc 100644
--- a/cros_async/src/sync/blocking.rs
+++ b/common/cros_asyncv2/src/blocking/block_on.rs
@@ -2,14 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::future::Future;
-use std::ptr;
-use std::sync::atomic::{AtomicI32, Ordering};
-use std::sync::Arc;
-use std::task::{Context, Poll};
-
-use futures::pin_mut;
-use futures::task::{waker_ref, ArcWake};
+use std::{
+ future::Future,
+ ptr,
+ sync::atomic::{AtomicI32, Ordering},
+ sync::Arc,
+ task::{Context, Poll},
+};
+
+use futures::{
+ pin_mut,
+ task::{waker_ref, ArcWake},
+};
// Randomly generated values to indicate the state of the current thread.
const WAITING: i32 = 0x25de_74d1;
diff --git a/common/cros_asyncv2/src/blocking/pool.rs b/common/cros_asyncv2/src/blocking/pool.rs
new file mode 100644
index 000000000..3f677455b
--- /dev/null
+++ b/common/cros_asyncv2/src/blocking/pool.rs
@@ -0,0 +1,522 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ collections::VecDeque,
+ mem,
+ sync::{
+ mpsc::{channel, Receiver, Sender},
+ Arc,
+ },
+ thread::{self, JoinHandle},
+ time::{Duration, Instant},
+};
+
+use async_task::{Runnable, Task};
+use slab::Slab;
+use sync::{Condvar, Mutex};
+use sys_util::{error, warn};
+
+const DEFAULT_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(10);
+
+struct State {
+ tasks: VecDeque<Runnable>,
+ num_threads: usize,
+ num_idle: usize,
+ num_notified: usize,
+ worker_threads: Slab<JoinHandle<()>>,
+ exited_threads: Option<Receiver<usize>>,
+ exit: Sender<usize>,
+ shutting_down: bool,
+}
+
+fn run_blocking_thread(idx: usize, inner: Arc<Inner>, exit: Sender<usize>) {
+ let mut state = inner.state.lock();
+ while !state.shutting_down {
+ if let Some(runnable) = state.tasks.pop_front() {
+ drop(state);
+ runnable.run();
+ state = inner.state.lock();
+ continue;
+ }
+
+ // No more tasks so wait for more work.
+ state.num_idle += 1;
+
+ let (guard, result) = inner
+ .condvar
+ .wait_timeout_while(state, inner.keepalive, |s| {
+ !s.shutting_down && s.num_notified == 0
+ });
+ state = guard;
+
+ // If `state.num_notified > 0` then this was a real wakeup.
+ if state.num_notified > 0 {
+ state.num_notified -= 1;
+ continue;
+ }
+
+ // Only decrement the idle count if we timed out. Otherwise, it was decremented when new
+ // work was added to `state.tasks`.
+ if result.timed_out() {
+ state.num_idle = state
+ .num_idle
+ .checked_sub(1)
+ .expect("`num_idle` underflow on timeout");
+ break;
+ }
+ }
+
+ state.num_threads -= 1;
+
+ // If we're shutting down then the BlockingPool will take care of joining all the threads.
+ // Otherwise, we need to join the last worker thread that exited here.
+ let last_exited_thread = if let Some(exited_threads) = state.exited_threads.as_mut() {
+ exited_threads
+ .try_recv()
+ .map(|idx| state.worker_threads.remove(idx))
+ .ok()
+ } else {
+ None
+ };
+
+ // Drop the lock before trying to join the last exited thread.
+ drop(state);
+
+ if let Some(handle) = last_exited_thread {
+ let _ = handle.join();
+ }
+
+ if let Err(e) = exit.send(idx) {
+ error!("Failed to send thread exit event on channel: {}", e);
+ }
+}
+
+struct Inner {
+ state: Mutex<State>,
+ condvar: Condvar,
+ max_threads: usize,
+ keepalive: Duration,
+}
+
+impl Inner {
+ fn schedule(self: &Arc<Inner>, runnable: Runnable) {
+ let mut state = self.state.lock();
+
+ // If we're shutting down then nothing is going to run this task.
+ if state.shutting_down {
+ return;
+ }
+
+ state.tasks.push_back(runnable);
+
+ if state.num_idle == 0 {
+ // There are no idle threads. Spawn a new one if possible.
+ if state.num_threads < self.max_threads {
+ state.num_threads += 1;
+ let exit = state.exit.clone();
+ let entry = state.worker_threads.vacant_entry();
+ let idx = entry.key();
+ let inner = self.clone();
+ entry.insert(
+ thread::Builder::new()
+ .name(format!("blockingPool{}", idx))
+ .spawn(move || run_blocking_thread(idx, inner, exit))
+ .unwrap(),
+ );
+ }
+ } else {
+ // We have idle threads, wake one up.
+ state.num_idle -= 1;
+ state.num_notified += 1;
+ self.condvar.notify_one();
+ }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error("{0} BlockingPool threads did not exit in time and will be detached")]
+pub struct ShutdownTimedOut(usize);
+
+/// A thread pool for running work that may block.
+///
+/// It is generally discouraged to do any blocking work inside an async function. However, this is
+/// sometimes unavoidable when dealing with interfaces that don't provide async variants. In this
+/// case callers may use the `BlockingPool` to run the blocking work on a different thread and
+/// `await` for its result to finish, which will prevent blocking the main thread of the
+/// application.
+///
+/// Since the blocking work is sent to another thread, users should be careful when using the
+/// `BlockingPool` for latency-sensitive operations. Additionally, the `BlockingPool` is intended to
+/// be used for work that will eventually complete on its own. Users who want to spawn a thread
+/// should just use `thread::spawn` directly.
+///
+/// There is no way to cancel work once it has been picked up by one of the worker threads in the
+/// `BlockingPool`. Dropping or shutting down the pool will block up to a timeout (default 10
+/// seconds) to wait for any active blocking work to finish. Any threads running tasks that have not
+/// completed by that time will be detached.
+///
+/// # Examples
+///
+/// Spawn a task to run in the `BlockingPool` and await on its result.
+///
+/// ```edition2018
+/// use cros_async::BlockingPool;
+///
+/// # async fn do_it() {
+/// let pool = BlockingPool::default();
+///
+/// let res = pool.spawn(move || {
+/// // Do some CPU-intensive or blocking work here.
+///
+/// 42
+/// }).await;
+///
+/// assert_eq!(res, 42);
+/// # }
+/// # cros_async::block_on(do_it());
+/// ```
+pub struct BlockingPool {
+ inner: Arc<Inner>,
+}
+
+impl BlockingPool {
+ /// Create a new `BlockingPool`.
+ ///
+ /// The `BlockingPool` will never spawn more than `max_threads` threads to do work, regardless
+ /// of the number of tasks that are added to it. This value should be set relatively low (for
+ /// example, the number of CPUs on the machine) if the pool is intended to run CPU intensive
+ /// work or it should be set relatively high (128 or more) if the pool is intended to be used
+ /// for various IO operations that cannot be completed asynchronously. The default value is 256.
+ ///
+ /// Worker threads are spawned on demand when new work is added to the pool and will
+ /// automatically exit after being idle for some time so there is no overhead for setting
+ /// `max_threads` to a large value when there is little to no work assigned to the
+ /// `BlockingPool`. `keepalive` determines the idle duration after which the worker thread will
+ /// exit. The default value is 10 seconds.
+ pub fn new(max_threads: usize, keepalive: Duration) -> BlockingPool {
+ let (exit, exited_threads) = channel();
+ BlockingPool {
+ inner: Arc::new(Inner {
+ state: Mutex::new(State {
+ tasks: VecDeque::new(),
+ num_threads: 0,
+ num_idle: 0,
+ num_notified: 0,
+ worker_threads: Slab::new(),
+ exited_threads: Some(exited_threads),
+ exit,
+ shutting_down: false,
+ }),
+ condvar: Condvar::new(),
+ max_threads,
+ keepalive,
+ }),
+ }
+ }
+
+ /// Like new but with pre-allocating capacity for up to `max_threads`.
+ pub fn with_capacity(max_threads: usize, keepalive: Duration) -> BlockingPool {
+ let (exit, exited_threads) = channel();
+ BlockingPool {
+ inner: Arc::new(Inner {
+ state: Mutex::new(State {
+ tasks: VecDeque::new(),
+ num_threads: 0,
+ num_idle: 0,
+ num_notified: 0,
+ worker_threads: Slab::with_capacity(max_threads),
+ exited_threads: Some(exited_threads),
+ exit,
+ shutting_down: false,
+ }),
+ condvar: Condvar::new(),
+ max_threads,
+ keepalive,
+ }),
+ }
+ }
+
+ /// Spawn a task to run in the `BlockingPool`.
+ ///
+ /// Callers may `await` the returned `Task` to be notified when the work is completed.
+ ///
+ /// # Panics
+ ///
+ /// `await`ing a `Task` after dropping the `BlockingPool` or calling `BlockingPool::shutdown`
+ /// will panic if the work was not completed before the pool was shut down.
+ pub fn spawn<F, R>(&self, f: F) -> Task<R>
+ where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+ {
+ let raw = Arc::downgrade(&self.inner);
+ let schedule = move |runnable| {
+ if let Some(i) = raw.upgrade() {
+ i.schedule(runnable);
+ }
+ };
+
+ let (runnable, task) = async_task::spawn(async move { f() }, schedule);
+ runnable.schedule();
+
+ task
+ }
+
+ /// Shut down the `BlockingPool`.
+ ///
+ /// If `deadline` is provided then this will block until either all worker threads exit or the
+ /// deadline is exceeded. If `deadline` is not given then this will block indefinitely until all
+ /// worker threads exit. Any work that was added to the `BlockingPool` but not yet picked up by
+ /// a worker thread will not complete and `await`ing on the `Task` for that work will panic.
+ pub fn shutdown(&self, deadline: Option<Instant>) -> Result<(), ShutdownTimedOut> {
+ let mut state = self.inner.state.lock();
+
+ if state.shutting_down {
+ // We've already shut down this BlockingPool.
+ return Ok(());
+ }
+
+ state.shutting_down = true;
+ let exited_threads = state.exited_threads.take().expect("exited_threads missing");
+ let unfinished_tasks = mem::take(&mut state.tasks);
+ let mut worker_threads = mem::take(&mut state.worker_threads);
+ drop(state);
+
+ self.inner.condvar.notify_all();
+
+ // Cancel any unfinished work after releasing the lock.
+ drop(unfinished_tasks);
+
+ // Now wait for all worker threads to exit.
+ if let Some(deadline) = deadline {
+ let mut now = Instant::now();
+ while now < deadline && !worker_threads.is_empty() {
+ if let Ok(idx) = exited_threads.recv_timeout(deadline - now) {
+ let _ = worker_threads.remove(idx).join();
+ }
+ now = Instant::now();
+ }
+
+ // Any threads that have not yet joined will just be detached.
+ if !worker_threads.is_empty() {
+ return Err(ShutdownTimedOut(worker_threads.len()));
+ }
+
+ Ok(())
+ } else {
+ // Block indefinitely until all worker threads exit.
+ for handle in worker_threads.drain() {
+ let _ = handle.join();
+ }
+
+ Ok(())
+ }
+ }
+}
+
+impl Default for BlockingPool {
+ fn default() -> BlockingPool {
+ BlockingPool::new(256, Duration::from_secs(10))
+ }
+}
+
+impl Drop for BlockingPool {
+ fn drop(&mut self) {
+ if let Err(e) = self.shutdown(Some(Instant::now() + DEFAULT_SHUTDOWN_TIMEOUT)) {
+ warn!("{}", e);
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::{
+ sync::{Arc, Barrier},
+ thread,
+ time::{Duration, Instant},
+ };
+
+ use futures::{stream::FuturesUnordered, StreamExt};
+ use sync::{Condvar, Mutex};
+
+ use crate::{block_on, BlockingPool};
+
+ #[test]
+ fn blocking_sleep() {
+ let pool = BlockingPool::default();
+
+ let res = block_on(pool.spawn(|| 42));
+ assert_eq!(res, 42);
+ }
+
+ #[test]
+ fn fast_tasks_with_short_keepalive() {
+ let pool = BlockingPool::new(256, Duration::from_millis(1));
+
+ let streams = FuturesUnordered::new();
+ for _ in 0..2 {
+ for _ in 0..256 {
+ let task = pool.spawn(|| ());
+ streams.push(task);
+ }
+
+ thread::sleep(Duration::from_millis(1));
+ }
+
+ block_on(streams.collect::<Vec<_>>());
+
+ // The test passes if there are no panics, which would happen if one of the worker threads
+ // triggered an underflow on `pool.inner.state.num_idle`.
+ }
+
+ #[test]
+ fn more_tasks_than_threads() {
+ let pool = BlockingPool::new(4, Duration::from_secs(10));
+
+ let stream = (0..19)
+ .map(|_| pool.spawn(|| thread::sleep(Duration::from_millis(5))))
+ .collect::<FuturesUnordered<_>>();
+
+ let results = block_on(stream.collect::<Vec<_>>());
+ assert_eq!(results.len(), 19);
+ }
+
+ #[test]
+ fn shutdown() {
+ let pool = BlockingPool::default();
+
+ let stream = (0..19)
+ .map(|_| pool.spawn(|| thread::sleep(Duration::from_millis(5))))
+ .collect::<FuturesUnordered<_>>();
+
+ let results = block_on(stream.collect::<Vec<_>>());
+ assert_eq!(results.len(), 19);
+
+ pool.shutdown(Some(Instant::now() + Duration::from_secs(10)))
+ .unwrap();
+ let state = pool.inner.state.lock();
+ assert_eq!(state.num_threads, 0);
+ }
+
+ #[test]
+ fn keepalive_timeout() {
+ // Set the keepalive to a very low value so that threads will exit soon after they run out
+ // of work.
+ let pool = BlockingPool::new(7, Duration::from_millis(1));
+
+ let stream = (0..19)
+ .map(|_| pool.spawn(|| thread::sleep(Duration::from_millis(5))))
+ .collect::<FuturesUnordered<_>>();
+
+ let results = block_on(stream.collect::<Vec<_>>());
+ assert_eq!(results.len(), 19);
+
+ // Wait for all threads to exit.
+ let deadline = Instant::now() + Duration::from_secs(10);
+ while Instant::now() < deadline {
+ thread::sleep(Duration::from_millis(100));
+ let state = pool.inner.state.lock();
+ if state.num_threads == 0 {
+ break;
+ }
+ }
+
+ {
+ let state = pool.inner.state.lock();
+ assert_eq!(state.num_threads, 0);
+ assert_eq!(state.num_idle, 0);
+ }
+ }
+
+ #[test]
+ #[should_panic]
+ fn shutdown_with_pending_work() {
+ let pool = BlockingPool::new(1, Duration::from_secs(10));
+
+ let mu = Arc::new(Mutex::new(false));
+ let cv = Arc::new(Condvar::new());
+
+ // First spawn a thread that blocks the pool.
+ let task_mu = mu.clone();
+ let task_cv = cv.clone();
+ pool.spawn(move || {
+ let mut ready = task_mu.lock();
+ while !*ready {
+ ready = task_cv.wait(ready);
+ }
+ })
+ .detach();
+
+ // This task will never finish because we will shut down the pool first.
+ let unfinished = pool.spawn(|| 5);
+
+ // Spawn a thread to unblock the work we started earlier once it sees that the pool is
+ // shutting down.
+ let inner = pool.inner.clone();
+ thread::spawn(move || {
+ let mut state = inner.state.lock();
+ while !state.shutting_down {
+ state = inner.condvar.wait(state);
+ }
+
+ *mu.lock() = true;
+ cv.notify_all();
+ });
+ pool.shutdown(None).unwrap();
+
+ // This should panic.
+ assert_eq!(block_on(unfinished), 5);
+ }
+
+ #[test]
+ fn unfinished_worker_thread() {
+ let pool = BlockingPool::default();
+
+ let ready = Arc::new(Mutex::new(false));
+ let cv = Arc::new(Condvar::new());
+ let barrier = Arc::new(Barrier::new(2));
+
+ let thread_ready = ready.clone();
+ let thread_barrier = barrier.clone();
+ let thread_cv = cv.clone();
+
+ let task = pool.spawn(move || {
+ thread_barrier.wait();
+ let mut ready = thread_ready.lock();
+ while !*ready {
+ ready = thread_cv.wait(ready);
+ }
+ });
+
+ // Wait to shut down the pool until after the worker thread has started.
+ barrier.wait();
+ pool.shutdown(Some(Instant::now() + Duration::from_millis(5)))
+ .unwrap_err();
+
+ let num_threads = pool.inner.state.lock().num_threads;
+ assert_eq!(num_threads, 1);
+
+ // Now wake up the blocked task so we don't leak the thread.
+ *ready.lock() = true;
+ cv.notify_all();
+
+ block_on(task);
+
+ let deadline = Instant::now() + Duration::from_secs(10);
+ while Instant::now() < deadline {
+ thread::sleep(Duration::from_millis(100));
+ let state = pool.inner.state.lock();
+ if state.num_threads == 0 {
+ break;
+ }
+ }
+
+ {
+ let state = pool.inner.state.lock();
+ assert_eq!(state.num_threads, 0);
+ assert_eq!(state.num_idle, 0);
+ }
+ }
+}
diff --git a/common/cros_asyncv2/src/enter.rs b/common/cros_asyncv2/src/enter.rs
new file mode 100644
index 000000000..e243ea5f9
--- /dev/null
+++ b/common/cros_asyncv2/src/enter.rs
@@ -0,0 +1,67 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::Cell;
+
+use thiserror::Error as ThisError;
+
+thread_local! (static EXECUTOR_ACTIVE: Cell<bool> = Cell::new(false));
+
+#[derive(ThisError, Debug)]
+#[error("Nested execution is not supported")]
+struct NestedExecutionNotSupported;
+
+#[derive(Debug)]
+pub struct ExecutionGuard;
+
+impl Drop for ExecutionGuard {
+ fn drop(&mut self) {
+ EXECUTOR_ACTIVE.with(|active| {
+ assert!(active.get());
+ active.set(false);
+ })
+ }
+}
+
+pub fn enter() -> anyhow::Result<ExecutionGuard> {
+ EXECUTOR_ACTIVE.with(|active| {
+ if active.get() {
+ Err(NestedExecutionNotSupported.into())
+ } else {
+ active.set(true);
+
+ Ok(ExecutionGuard)
+ }
+ })
+}
+
+#[cfg(test)]
+mod test {
+ use crate::Executor;
+
+ use super::NestedExecutionNotSupported;
+
+ #[test]
+ fn nested_execution() {
+ Executor::new()
+ .run_until(async {
+ let e = Executor::new()
+ .run_until(async {})
+ .expect_err("nested execution successful");
+ e.downcast::<NestedExecutionNotSupported>()
+ .expect("unexpected error type");
+ })
+ .unwrap();
+
+ let ex = Executor::new();
+ ex.run_until(async {
+ let e = ex
+ .run_until(async {})
+ .expect_err("nested execution successful");
+ e.downcast::<NestedExecutionNotSupported>()
+ .expect("unexpected error type");
+ })
+ .unwrap();
+ }
+}
diff --git a/common/cros_asyncv2/src/event.rs b/common/cros_asyncv2/src/event.rs
new file mode 100644
index 000000000..33e0b4e4c
--- /dev/null
+++ b/common/cros_asyncv2/src/event.rs
@@ -0,0 +1,108 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::convert::TryFrom;
+
+use crate::sys;
+
+/// An asynchronous version of a `base::Event`.
+#[derive(Debug)]
+pub struct Event {
+ inner: sys::Event,
+}
+
+impl Event {
+ /// Creates a new `Event` in an unsignaled state.
+ pub fn new() -> anyhow::Result<Event> {
+ sys::Event::new().map(|inner| Event { inner })
+ }
+
+ /// Wait until the event is signaled.
+ ///
+ /// Blocks until the internal counter for the `Event` reaches a nonzero value, at which point
+ /// the internal counter for the `Event` is reset to 0 and the previous value is returned.
+ pub async fn next_val(&self) -> anyhow::Result<u64> {
+ self.inner.next_val().await
+ }
+
+ /// Trigger the event, waking up any task that was blocked on it.
+ pub async fn notify(&self) -> anyhow::Result<()> {
+ self.inner.notify().await
+ }
+
+ /// Attempt to clone the `Event`.
+ ///
+ /// If successful, the returned `Event` will have its own unique OS handle for the underlying
+ /// event.
+ pub fn try_clone(&self) -> anyhow::Result<Event> {
+ self.inner.try_clone().map(|inner| Event { inner })
+ }
+}
+
+#[cfg(unix)]
+impl TryFrom<sys_util::EventFd> for Event {
+ type Error = anyhow::Error;
+
+ fn try_from(evt: sys_util::EventFd) -> anyhow::Result<Event> {
+ sys::Event::try_from(evt).map(|inner| Event { inner })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use std::convert::TryInto;
+
+ use futures::channel::oneshot::{channel, Receiver, Sender};
+
+ use crate::Executor;
+
+ #[test]
+ fn next_val_with_nonzero_count() {
+ let ex = Executor::new();
+
+ let event = Event::new().unwrap();
+ ex.run_until(event.notify()).unwrap().unwrap();
+ let count = ex.run_until(event.next_val()).unwrap().unwrap();
+ assert_eq!(count, 1);
+ }
+
+ #[test]
+ #[cfg(unix)]
+ fn next_val_reads_value() {
+ use sys_util::EventFd;
+ async fn go(event: Event) -> u64 {
+ event.next_val().await.unwrap()
+ }
+
+ let eventfd = EventFd::new().unwrap();
+ eventfd.write(0xaa).unwrap();
+ let ex = Executor::new();
+ let val = ex.run_until(go(eventfd.try_into().unwrap())).unwrap();
+ assert_eq!(val, 0xaa);
+ }
+
+ #[test]
+ fn write_wakes_up_task() {
+ async fn wakeup(event: Event, ready: Receiver<()>) {
+ ready.await.expect("failed to wait for wakeup");
+ event.notify().await.unwrap();
+ }
+
+ async fn go(event: Event, ready: Sender<()>) -> u64 {
+ ready.send(()).expect("failed to wake up notifier");
+ event.next_val().await.unwrap()
+ }
+
+ let event = Event::new().unwrap();
+ let ex = Executor::new();
+ let (tx, rx) = channel();
+ ex.spawn_local(wakeup(event.try_clone().unwrap(), rx))
+ .detach();
+
+ let val = ex.run_until(go(event, tx)).unwrap();
+ assert_eq!(val, 0x1);
+ }
+}
diff --git a/common/cros_asyncv2/src/executor.rs b/common/cros_asyncv2/src/executor.rs
new file mode 100644
index 000000000..f13c32ed1
--- /dev/null
+++ b/common/cros_asyncv2/src/executor.rs
@@ -0,0 +1,893 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ cmp::Reverse,
+ collections::{BTreeMap, VecDeque},
+ future::{pending, Future},
+ num::Wrapping,
+ sync::Arc,
+ task::{self, Poll, Waker},
+ thread::{self, ThreadId},
+ time::{Duration, Instant},
+};
+
+use anyhow::Result;
+use async_task::{Runnable, Task};
+use futures::{pin_mut, task::WakerRef};
+use once_cell::unsync::Lazy;
+use smallvec::SmallVec;
+use sync::Mutex;
+
+use crate::{enter::enter, sys, BlockingPool};
+
+thread_local! (static LOCAL_CONTEXT: Lazy<Arc<Mutex<Context>>> = Lazy::new(Default::default));
+
+#[derive(Default)]
+struct Context {
+ queue: VecDeque<Runnable>,
+ timers: BTreeMap<Reverse<Instant>, SmallVec<[Waker; 2]>>,
+ waker: Option<Waker>,
+}
+
+#[derive(Default)]
+struct Shared {
+ queue: VecDeque<Runnable>,
+ idle_workers: VecDeque<(ThreadId, Waker)>,
+ blocking_pool: BlockingPool,
+}
+
+pub(crate) fn add_timer(deadline: Instant, waker: &Waker) {
+ LOCAL_CONTEXT.with(|local_ctx| {
+ let mut ctx = local_ctx.lock();
+ let wakers = ctx.timers.entry(Reverse(deadline)).or_default();
+ if wakers.iter().all(|w| !w.will_wake(waker)) {
+ wakers.push(waker.clone());
+ }
+ });
+}
+
+/// An executor for scheduling tasks that poll futures to completion.
+///
+/// All asynchronous operations must run within an executor, which is capable of spawning futures as
+/// tasks. This executor also provides a mechanism for performing asynchronous I/O operations.
+///
+/// The returned type is a cheap, clonable handle to the underlying executor. Cloning it will only
+/// create a new reference, not a new executor.
+///
+/// # Examples
+///
+/// Concurrently wait for multiple files to become readable/writable and then read/write the data.
+///
+/// ```
+/// use std::{
+/// cmp::min,
+/// convert::TryFrom,
+/// fs::OpenOptions,
+/// };
+///
+/// use anyhow::Result;
+/// use cros_async::{Executor, File};
+/// use futures::future::join3;
+///
+/// const CHUNK_SIZE: usize = 32;
+///
+/// // Transfer `len` bytes of data from `from` to `to`.
+/// async fn transfer_data(from: File, to: File, len: usize) -> Result<usize> {
+/// let mut rem = len;
+/// let mut buf = [0u8; CHUNK_SIZE];
+/// while rem > 0 {
+/// let count = from.read(&mut buf, None).await?;
+///
+/// if count == 0 {
+/// // End of file. Return the number of bytes transferred.
+/// return Ok(len - rem);
+/// }
+///
+/// to.write_all(&buf[..count], None).await?;
+///
+/// rem = rem.saturating_sub(count);
+/// }
+///
+/// Ok(len)
+/// }
+///
+/// # fn do_it() -> Result<()> {
+/// let (rx, tx) = sys_util::pipe(true)?;
+/// let zero = File::open("/dev/zero")?;
+/// let zero_bytes = CHUNK_SIZE * 7;
+/// let zero_to_pipe = transfer_data(
+/// zero,
+/// File::try_from(tx.try_clone()?)?,
+/// zero_bytes,
+/// );
+///
+/// let rand = File::open("/dev/urandom")?;
+/// let rand_bytes = CHUNK_SIZE * 19;
+/// let rand_to_pipe = transfer_data(
+/// rand,
+/// File::try_from(tx)?,
+/// rand_bytes
+/// );
+///
+/// let null = OpenOptions::new().write(true).open("/dev/null")?;
+/// let null_bytes = zero_bytes + rand_bytes;
+/// let pipe_to_null = transfer_data(
+/// File::try_from(rx)?,
+/// File::try_from(null)?,
+/// null_bytes
+/// );
+///
+/// Executor::new().run_until(join3(
+/// async { assert_eq!(pipe_to_null.await.unwrap(), null_bytes) },
+/// async { assert_eq!(zero_to_pipe.await.unwrap(), zero_bytes) },
+/// async { assert_eq!(rand_to_pipe.await.unwrap(), rand_bytes) },
+/// ))?;
+///
+/// # Ok(())
+/// # }
+///
+/// # do_it().unwrap();
+/// ```
+#[derive(Clone, Default)]
+pub struct Executor {
+ shared: Arc<Mutex<Shared>>,
+}
+
+impl Executor {
+ /// Create a new `Executor`.
+ pub fn new() -> Executor {
+ Default::default()
+ }
+
+ /// Spawn a new future for this executor to run to completion. Callers may use the returned
+ /// `Task` to await on the result of `f`. Dropping the returned `Task` will cancel `f`,
+ /// preventing it from being polled again. To drop a `Task` without canceling the future
+ /// associated with it use [`Task::detach`]. To cancel a task gracefully and wait until it is
+ /// fully destroyed, use [`Task::cancel`].
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use anyhow::Result;
+ /// # fn example_spawn() -> Result<()> {
+ /// # use std::thread;
+ /// #
+ /// # use cros_async::Executor;
+ /// #
+ /// # let ex = Executor::new();
+ /// #
+ /// # // Spawn a thread that runs the executor.
+ /// # let ex2 = ex.clone();
+ /// # thread::spawn(move || ex2.run());
+ /// #
+ /// let task = ex.spawn(async { 7 + 13 });
+ ///
+ /// let result = ex.run_until(task)?;
+ /// assert_eq!(result, 20);
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # example_spawn().unwrap();
+ /// ```
+ pub fn spawn<F>(&self, f: F) -> Task<F::Output>
+ where
+ F: Future + Send + 'static,
+ F::Output: Send + 'static,
+ {
+ let weak_shared = Arc::downgrade(&self.shared);
+ let schedule = move |runnable| {
+ if let Some(shared) = weak_shared.upgrade() {
+ let waker = {
+ let mut s = shared.lock();
+ s.queue.push_back(runnable);
+ s.idle_workers.pop_front()
+ };
+
+ if let Some((_, w)) = waker {
+ w.wake();
+ }
+ }
+ };
+ let (runnable, task) = async_task::spawn(f, schedule);
+ runnable.schedule();
+ task
+ }
+
+ /// Spawn a thread-local task for this executor to drive to completion. Like `spawn` but without
+ /// requiring `Send` on `F` or `F::Output`. This method should only be called from the same
+ /// thread where `run()` or `run_until()` is called.
+ ///
+ /// # Panics
+ ///
+ /// `Executor::run` and `Executor::run_util` will panic if they try to poll a future that was
+ /// added by calling `spawn_local` from a different thread.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use anyhow::Result;
+ /// # fn example_spawn_local() -> Result<()> {
+ /// # use cros_async::Executor;
+ /// #
+ /// # let ex = Executor::new();
+ /// #
+ /// let task = ex.spawn_local(async { 7 + 13 });
+ ///
+ /// let result = ex.run_until(task)?;
+ /// assert_eq!(result, 20);
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # example_spawn_local().unwrap();
+ /// ```
+ pub fn spawn_local<F>(&self, f: F) -> Task<F::Output>
+ where
+ F: Future + 'static,
+ F::Output: 'static,
+ {
+ let weak_ctx = LOCAL_CONTEXT.with(|ctx| Arc::downgrade(ctx));
+ let schedule = move |runnable| {
+ if let Some(local_ctx) = weak_ctx.upgrade() {
+ let waker = {
+ let mut ctx = local_ctx.lock();
+ ctx.queue.push_back(runnable);
+ ctx.waker.take()
+ };
+
+ if let Some(w) = waker {
+ w.wake();
+ }
+ }
+ };
+ let (runnable, task) = async_task::spawn_local(f, schedule);
+ runnable.schedule();
+ task
+ }
+
+ /// Run the provided closure on a dedicated thread where blocking is allowed.
+ ///
+ /// Callers may `await` on the returned `Task` to wait for the result of `f`. Dropping or
+ /// canceling the returned `Task` may not cancel the operation if it was already started on a
+ /// worker thread.
+ ///
+ /// # Panics
+ ///
+ /// `await`ing the `Task` after the `Executor` is dropped will panic if the work was not already
+ /// completed.
+ ///
+ /// # Examples
+ ///
+ /// ```edition2018
+ /// # use cros_async::Executor;
+ /// #
+ /// # async fn do_it(ex: &Executor) {
+ /// let res = ex.spawn_blocking(move || {
+ /// // Do some CPU-intensive or blocking work here.
+ ///
+ /// 42
+ /// }).await;
+ ///
+ /// assert_eq!(res, 42);
+ /// # }
+ /// #
+ /// # let ex = Executor::new();
+ /// # ex.run_until(do_it(&ex)).unwrap();
+ /// ```
+ pub fn spawn_blocking<F, R>(&self, f: F) -> Task<R>
+ where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+ {
+ self.shared.lock().blocking_pool.spawn(f)
+ }
+
+ /// Run the executor indefinitely, driving all spawned futures to completion. This method will
+ /// block the current thread and only return in the case of an error.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use anyhow::Result;
+ /// # fn example_run() -> Result<()> {
+ /// use std::thread;
+ ///
+ /// use cros_async::Executor;
+ ///
+ /// let ex = Executor::new();
+ ///
+ /// // Spawn a thread that runs the executor.
+ /// let ex2 = ex.clone();
+ /// thread::spawn(move || ex2.run());
+ ///
+ /// let task = ex.spawn(async { 7 + 13 });
+ ///
+ /// let result = ex.run_until(task)?;
+ /// assert_eq!(result, 20);
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # example_run().unwrap();
+ /// ```
+ #[inline]
+ pub fn run(&self) -> Result<()> {
+ self.run_until(pending())
+ }
+
+ /// Drive all futures spawned in this executor until `f` completes. This method will block the
+ /// current thread only until `f` is complete and there may still be unfinished futures in the
+ /// executor.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use anyhow::Result;
+ /// # fn example_run_until() -> Result<()> {
+ /// use cros_async::Executor;
+ ///
+ /// let ex = Executor::new();
+ ///
+ /// let task = ex.spawn_local(async { 7 + 13 });
+ ///
+ /// let result = ex.run_until(task)?;
+ /// assert_eq!(result, 20);
+ /// # Ok(())
+ /// # }
+ /// #
+ /// # example_run_until().unwrap();
+ /// ```
+ pub fn run_until<F: Future>(&self, done: F) -> Result<F::Output> {
+ // Prevent nested execution.
+ let _guard = enter()?;
+
+ pin_mut!(done);
+
+ let current_thread = thread::current().id();
+ let state = sys::platform_state()?;
+ let waker = state.waker_ref();
+ let mut cx = task::Context::from_waker(&waker);
+ let mut done_polled = false;
+
+ LOCAL_CONTEXT.with(|local_ctx| {
+ let next_local = || local_ctx.lock().queue.pop_front();
+ let next_global = || self.shared.lock().queue.pop_front();
+
+ let mut tick = Wrapping(0u32);
+
+ loop {
+ tick += Wrapping(1);
+
+ // If there are always tasks available to run in either the local or the global
+ // queue then we may go a long time without fetching completed events from the
+ // underlying platform driver. Poll the driver once in a while to prevent this from
+ // happening.
+ if tick.0 % 31 == 0 {
+ // A zero timeout will fetch new events without blocking.
+ self.get_events(&state, Some(Duration::from_millis(0)))?;
+ }
+
+ let was_woken = state.start_processing();
+ if was_woken || !done_polled {
+ done_polled = true;
+ if let Poll::Ready(v) = done.as_mut().poll(&mut cx) {
+ return Ok(v);
+ }
+ }
+
+ // If there are always tasks in the local queue then any tasks in the global queue
+ // will get starved. Pull tasks out of the global queue every once in a while even
+ // when there are still local tasks available to prevent this.
+ let next_runnable = if tick.0 % 13 == 0 {
+ next_global().or_else(next_local)
+ } else {
+ next_local().or_else(next_global)
+ };
+
+ if let Some(runnable) = next_runnable {
+ runnable.run();
+ continue;
+ }
+
+ // We're about to block so first check that new tasks have not snuck in and set the
+ // waker so that we can be woken up when tasks are re-scheduled.
+ let deadline = {
+ let mut ctx = local_ctx.lock();
+ if !ctx.queue.is_empty() {
+ // Some more tasks managed to sneak in. Go back to the start of the loop.
+ continue;
+ }
+
+ // There are no more tasks to run so set the waker.
+ if ctx.waker.is_none() {
+ ctx.waker = Some(cx.waker().clone());
+ }
+
+ // TODO: Replace with `last_entry` once it is stabilized.
+ ctx.timers.keys().next_back().cloned()
+ };
+ {
+ let mut shared = self.shared.lock();
+ if !shared.queue.is_empty() {
+ // More tasks were added to the global queue. Go back to the start of the loop.
+ continue;
+ }
+
+ // We're going to block so add ourselves to the idle worker list.
+ shared
+ .idle_workers
+ .push_back((current_thread, cx.waker().clone()));
+ };
+
+ // Now wait to be woken up.
+ let timeout = deadline.map(|d| d.0.saturating_duration_since(Instant::now()));
+ self.get_events(&state, timeout)?;
+
+ // Remove from idle workers.
+ {
+ let mut shared = self.shared.lock();
+ if let Some(idx) = shared
+ .idle_workers
+ .iter()
+ .position(|(id, _)| id == &current_thread)
+ {
+ shared.idle_workers.swap_remove_back(idx);
+ }
+ }
+
+ // Reset the ticks since we just fetched new events from the platform driver.
+ tick = Wrapping(0);
+ }
+ })
+ }
+
+ fn get_events<S: PlatformState>(
+ &self,
+ state: &S,
+ timeout: Option<Duration>,
+ ) -> anyhow::Result<()> {
+ state.wait(timeout)?;
+
+ // Timer maintenance.
+ let expired = LOCAL_CONTEXT.with(|local_ctx| {
+ let mut ctx = local_ctx.lock();
+ let now = Instant::now();
+ ctx.timers.split_off(&Reverse(now))
+ });
+
+ // We cannot wake the timers while holding the lock because the schedule function for the
+ // task that's waiting on the timer may try to acquire the lock.
+ for (deadline, wakers) in expired {
+ debug_assert!(deadline.0 <= Instant::now());
+ for w in wakers {
+ w.wake();
+ }
+ }
+
+ Ok(())
+ }
+}
+
+// A trait that represents any thread-local platform-specific state that needs to be held on behalf
+// of the `Executor`.
+pub(crate) trait PlatformState {
+ // Indicates that the `Executor` is about to start processing futures that have been woken up.
+ //
+ // Implementations may use this as an indicator to skip unnecessary work when new tasks are
+ // woken up as the `Executor` will eventually get around to processing them on its own.
+ //
+ // `start_processing` must return true if one or more futures were woken up since the last call
+ // to `start_processing`. Otherwise it may return false.
+ fn start_processing(&self) -> bool;
+
+ // Returns a `WakerRef` that can be used to wake up the current thread.
+ fn waker_ref(&self) -> WakerRef;
+
+ // Waits for one or more futures to be woken up.
+ //
+ // This method should check with the underlying OS if any asynchronous IO operations have
+ // completed and then wake up the associated futures.
+ //
+ // If `timeout` is provided then this method should block until either one or more futures are
+ // woken up or the timeout duration elapses. If `timeout` has a zero duration then this method
+ // should fetch completed asynchronous IO operations and then immediately return.
+ //
+ // If `timeout` is not provided then this method should block until one or more futures are
+ // woken up.
+ fn wait(&self, timeout: Option<Duration>) -> anyhow::Result<()>;
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use std::{
+ convert::TryFrom,
+ fs::OpenOptions,
+ mem,
+ pin::Pin,
+ thread::{self, JoinHandle},
+ time::Instant,
+ };
+
+ use futures::{
+ channel::{mpsc, oneshot},
+ future::{join3, select, Either},
+ sink::SinkExt,
+ stream::{self, FuturesUnordered, StreamExt},
+ };
+
+ use crate::{File, OwnedIoBuf};
+
+ #[test]
+ fn basic() {
+ async fn do_it() {
+ let (r, _w) = sys_util::pipe(true).unwrap();
+ let done = async { 5usize };
+
+ let rx = File::try_from(r).unwrap();
+ let mut buf = 0u64.to_ne_bytes();
+ let pending = rx.read(&mut buf, None);
+ pin_mut!(pending, done);
+
+ match select(pending, done).await {
+ Either::Right((5, pending)) => drop(pending),
+ _ => panic!("unexpected select result"),
+ }
+ }
+
+ Executor::new().run_until(do_it()).unwrap();
+ }
+
+ #[derive(Default)]
+ struct QuitShared {
+ wakers: Vec<task::Waker>,
+ should_quit: bool,
+ }
+
+ #[derive(Clone, Default)]
+ struct Quit {
+ shared: Arc<Mutex<QuitShared>>,
+ }
+
+ impl Quit {
+ fn quit(self) {
+ let wakers = {
+ let mut shared = self.shared.lock();
+ shared.should_quit = true;
+ mem::take(&mut shared.wakers)
+ };
+
+ for w in wakers {
+ w.wake();
+ }
+ }
+ }
+
+ impl Future for Quit {
+ type Output = ();
+
+ fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
+ let mut shared = self.shared.lock();
+ if shared.should_quit {
+ return Poll::Ready(());
+ }
+
+ if shared.wakers.iter().all(|w| !w.will_wake(cx.waker())) {
+ shared.wakers.push(cx.waker().clone());
+ }
+
+ Poll::Pending
+ }
+ }
+
+ #[test]
+ fn outer_future_is_send() {
+ const NUM_THREADS: usize = 3;
+ const CHUNK_SIZE: usize = 32;
+
+ async fn read_iobuf(
+ ex: &Executor,
+ f: File,
+ buf: OwnedIoBuf,
+ ) -> (anyhow::Result<usize>, OwnedIoBuf, File) {
+ let (tx, rx) = oneshot::channel();
+ ex.spawn_local(async move {
+ let (res, buf) = f.read_iobuf(buf, None).await;
+ let _ = tx.send((res, buf, f));
+ })
+ .detach();
+ rx.await.unwrap()
+ }
+
+ async fn write_iobuf(
+ ex: &Executor,
+ f: File,
+ buf: OwnedIoBuf,
+ ) -> (anyhow::Result<usize>, OwnedIoBuf, File) {
+ let (tx, rx) = oneshot::channel();
+ ex.spawn_local(async move {
+ let (res, buf) = f.write_iobuf(buf, None).await;
+ let _ = tx.send((res, buf, f));
+ })
+ .detach();
+ rx.await.unwrap()
+ }
+
+ async fn transfer_data(
+ ex: Executor,
+ mut from: File,
+ mut to: File,
+ len: usize,
+ ) -> Result<usize> {
+ let mut rem = len;
+ let mut buf = OwnedIoBuf::new(vec![0xa2u8; CHUNK_SIZE]);
+ while rem > 0 {
+ let (res, data, f) = read_iobuf(&ex, from, buf).await;
+ let count = res?;
+ buf = data;
+ from = f;
+ if count == 0 {
+ // End of file. Return the number of bytes transferred.
+ return Ok(len - rem);
+ }
+ assert_eq!(count, CHUNK_SIZE);
+
+ let (res, data, t) = write_iobuf(&ex, to, buf).await;
+ let count = res?;
+ buf = data;
+ to = t;
+ assert_eq!(count, CHUNK_SIZE);
+
+ rem = rem.saturating_sub(count);
+ }
+
+ Ok(len)
+ }
+
+ fn do_it() -> anyhow::Result<()> {
+ let ex = Executor::new();
+ let (rx, tx) = sys_util::pipe(true)?;
+ let zero = File::open("/dev/zero")?;
+ let zero_bytes = CHUNK_SIZE * 7;
+ let zero_to_pipe = ex.spawn(transfer_data(
+ ex.clone(),
+ zero,
+ File::try_from(tx.try_clone()?)?,
+ zero_bytes,
+ ));
+
+ let rand = File::open("/dev/urandom")?;
+ let rand_bytes = CHUNK_SIZE * 19;
+ let rand_to_pipe = ex.spawn(transfer_data(
+ ex.clone(),
+ rand,
+ File::try_from(tx)?,
+ rand_bytes,
+ ));
+
+ let null = OpenOptions::new().write(true).open("/dev/null")?;
+ let null_bytes = zero_bytes + rand_bytes;
+ let pipe_to_null = ex.spawn(transfer_data(
+ ex.clone(),
+ File::try_from(rx)?,
+ File::try_from(null)?,
+ null_bytes,
+ ));
+
+ let mut threads = Vec::with_capacity(NUM_THREADS);
+ let quit = Quit::default();
+ for _ in 0..NUM_THREADS {
+ let thread_ex = ex.clone();
+ let thread_quit = quit.clone();
+ threads.push(thread::spawn(move || thread_ex.run_until(thread_quit)))
+ }
+ ex.run_until(join3(
+ async { assert_eq!(pipe_to_null.await.unwrap(), null_bytes) },
+ async { assert_eq!(zero_to_pipe.await.unwrap(), zero_bytes) },
+ async { assert_eq!(rand_to_pipe.await.unwrap(), rand_bytes) },
+ ))?;
+
+ quit.quit();
+ for t in threads {
+ t.join().unwrap().unwrap();
+ }
+
+ Ok(())
+ }
+
+ do_it().unwrap();
+ }
+
+ #[test]
+ fn thread_pool() {
+ const NUM_THREADS: usize = 8;
+ const NUM_CHANNELS: usize = 19;
+ const NUM_ITERATIONS: usize = 71;
+
+ let ex = Executor::new();
+
+ let tasks = FuturesUnordered::new();
+ let (mut tx, mut rx) = mpsc::channel(10);
+ tasks.push(ex.spawn(async move {
+ for i in 0..NUM_ITERATIONS {
+ tx.send(i).await?;
+ }
+
+ Ok::<(), anyhow::Error>(())
+ }));
+
+ for _ in 0..NUM_CHANNELS {
+ let (mut task_tx, task_rx) = mpsc::channel(10);
+ tasks.push(ex.spawn(async move {
+ while let Some(v) = rx.next().await {
+ task_tx.send(v).await?;
+ }
+
+ Ok::<(), anyhow::Error>(())
+ }));
+
+ rx = task_rx;
+ }
+
+ tasks.push(ex.spawn(async move {
+ let mut zip = rx.zip(stream::iter(0..NUM_ITERATIONS));
+ while let Some((l, r)) = zip.next().await {
+ assert_eq!(l, r);
+ }
+
+ Ok::<(), anyhow::Error>(())
+ }));
+
+ let quit = Quit::default();
+ let mut threads = Vec::with_capacity(NUM_THREADS);
+ for _ in 0..NUM_THREADS {
+ let thread_ex = ex.clone();
+ let thread_quit = quit.clone();
+ threads.push(thread::spawn(move || thread_ex.run_until(thread_quit)));
+ }
+
+ let results = ex
+ .run_until(tasks.collect::<Vec<anyhow::Result<()>>>())
+ .unwrap();
+ results
+ .into_iter()
+ .collect::<anyhow::Result<Vec<()>>>()
+ .unwrap();
+
+ quit.quit();
+ for t in threads {
+ t.join().unwrap().unwrap();
+ }
+ }
+
+ // Sends a message on `tx` once there is an idle worker in `Executor` or 5 seconds have passed.
+ // Sends true if this function observed an idle worker and false otherwise.
+ fn notify_on_idle_worker(ex: Executor, tx: oneshot::Sender<bool>) {
+ let deadline = Instant::now() + Duration::from_secs(5);
+ while Instant::now() < deadline {
+ // Wait for the main thread to add itself to the idle worker list.
+ if !ex.shared.lock().idle_workers.is_empty() {
+ break;
+ }
+
+ thread::sleep(Duration::from_millis(10));
+ }
+
+ if Instant::now() <= deadline {
+ tx.send(true).unwrap();
+ } else {
+ tx.send(false).unwrap();
+ }
+ }
+
+ #[test]
+ fn wakeup_run_until() {
+ let (tx, rx) = oneshot::channel();
+
+ let ex = Executor::new();
+
+ let thread_ex = ex.clone();
+ let waker_thread = thread::spawn(move || notify_on_idle_worker(thread_ex, tx));
+
+ // Since we're using `run_until` the wakeup path won't use the regular scheduling functions.
+ let success = ex.run_until(rx).unwrap().unwrap();
+ assert!(success);
+ assert!(ex.shared.lock().idle_workers.is_empty());
+
+ waker_thread.join().unwrap();
+ }
+
+ #[test]
+ fn wakeup_local_task() {
+ let (tx, rx) = oneshot::channel();
+
+ let ex = Executor::new();
+
+ let thread_ex = ex.clone();
+ let waker_thread = thread::spawn(move || notify_on_idle_worker(thread_ex, tx));
+
+ // By using `spawn_local`, the wakeup path will go via LOCAL_CTX.
+ let task = ex.spawn_local(rx);
+ let success = ex.run_until(task).unwrap().unwrap();
+ assert!(success);
+ assert!(ex.shared.lock().idle_workers.is_empty());
+
+ waker_thread.join().unwrap();
+ }
+
+ #[test]
+ fn wakeup_global_task() {
+ let (tx, rx) = oneshot::channel();
+
+ let ex = Executor::new();
+
+ let thread_ex = ex.clone();
+ let waker_thread = thread::spawn(move || notify_on_idle_worker(thread_ex, tx));
+
+ // By using `spawn`, the wakeup path will go via `ex.shared`.
+ let task = ex.spawn(rx);
+ let success = ex.run_until(task).unwrap().unwrap();
+ assert!(success);
+ assert!(ex.shared.lock().idle_workers.is_empty());
+
+ waker_thread.join().unwrap();
+ }
+
+ #[test]
+ fn wake_up_correct_worker() {
+ struct ThreadData {
+ id: ThreadId,
+ sender: mpsc::Sender<()>,
+ handle: JoinHandle<anyhow::Result<()>>,
+ }
+
+ const NUM_THREADS: usize = 7;
+ const NUM_ITERATIONS: usize = 119;
+
+ let ex = Executor::new();
+
+ let (tx, mut rx) = mpsc::channel(0);
+ let mut threads = Vec::with_capacity(NUM_THREADS);
+ for _ in 0..NUM_THREADS {
+ let (sender, mut receiver) = mpsc::channel(0);
+ let mut thread_tx = tx.clone();
+ let thread_ex = ex.clone();
+ let handle = thread::spawn(move || {
+ let id = thread::current().id();
+ thread_ex
+ .run_until(async move {
+ while let Some(()) = receiver.next().await {
+ thread_tx.send(id).await?;
+ }
+
+ Ok(())
+ })
+ .unwrap()
+ });
+
+ let id = handle.thread().id();
+ threads.push(ThreadData { id, sender, handle });
+ }
+
+ ex.run_until(async {
+ for i in 0..NUM_ITERATIONS {
+ let data = &mut threads[i % NUM_THREADS];
+ data.sender.send(()).await?;
+ assert_eq!(rx.next().await.unwrap(), data.id);
+ }
+
+ Ok::<(), anyhow::Error>(())
+ })
+ .unwrap()
+ .unwrap();
+
+ for t in threads {
+ let ThreadData { id, sender, handle } = t;
+
+ // Dropping the sender will close the channel and cause the thread to exit.
+ drop((id, sender));
+ handle.join().unwrap().unwrap();
+ }
+ }
+}
diff --git a/common/cros_asyncv2/src/file.rs b/common/cros_asyncv2/src/file.rs
new file mode 100644
index 000000000..66be6450f
--- /dev/null
+++ b/common/cros_asyncv2/src/file.rs
@@ -0,0 +1,462 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ convert::{TryFrom, TryInto},
+ fs::File as StdFile,
+ io,
+ path::Path,
+};
+
+use sys_util::AsRawDescriptor;
+
+use crate::{sys, AsIoBufs};
+
+static DOWNCAST_ERROR: &str = "inner error not downcastable to `io::Error`";
+
+/// A reference to an open file on the filesystem.
+///
+/// `File` provides asynchronous functions for reading and writing data. When a `File` is dropped,
+/// the underlying OS handle will be closed once there are no more pending I/O operations on it.
+///
+/// # Errors
+///
+/// Many `File` methods return an `anyhow::Result`. However, it is guaranteed that if an error is
+/// returned, that error is downcastable to an `io::Error`.
+///
+/// # Examples
+///
+/// Create a new file and write data to it.
+///
+/// ```
+/// # use cros_async::{Executor, File};
+/// # use tempfile::TempDir;
+/// # async fn write_all() -> anyhow::Result<()>{
+/// # let dir = TempDir::new()?;
+/// # let path = dir.path().join("test");
+/// let mut f = File::create(path)?;
+/// f.write_all(b"Hello, world!", None).await
+/// # }
+/// #
+/// # Executor::new().run_until(write_all()).unwrap();
+/// ```
+///
+/// Extract an `io::Error` from a failed operation.
+///
+/// ```
+/// # use std::io;
+/// # use cros_async::File;
+/// # fn extract_io_error() -> io::Error {
+/// if let Err(e) = File::open("nonexistent-filesystem-path") {
+/// e.downcast::<io::Error>().expect("Error not downcastable to `io::Error`")
+/// } else {
+/// panic!("nonexistent path exists");
+/// }
+/// # }
+///
+/// # let _ = extract_io_error();
+/// ```
+#[derive(Debug)]
+pub struct File {
+ inner: sys::File,
+}
+
+impl File {
+ /// Attempt to open a file in read-only mode.
+ ///
+ /// This is a convenience wrapper for the standard library `File::open` method.
+ pub fn open<P: AsRef<Path>>(p: P) -> anyhow::Result<File> {
+ sys::File::open(p).map(|inner| File { inner })
+ }
+
+ /// Attempt to open a file in write-only mode.
+ ///
+ /// This function will create a file if it does not exist, and will truncate it if it does. It
+ /// is a convenience wrapper for the `File::create` method from the standard library.
+ pub fn create<P: AsRef<Path>>(p: P) -> anyhow::Result<File> {
+ sys::File::create(p).map(|inner| File { inner })
+ }
+
+ /// Create a `File` from a standard library file.
+ ///
+ /// The conversion may fail if the underlying OS handle cannot be prepared for asynchronous
+ /// operation. For example on epoll-based systems, this requires making the handle non-blocking.
+ pub fn from_std(f: StdFile) -> anyhow::Result<File> {
+ File::try_from(f)
+ }
+
+ /// Convert a `File` back into a standard library file.
+ ///
+ /// The conversion may fail if there are still pending asynchronous operations on the underlying
+ /// OS handle. In this case, the original `File` is returned as the error.
+ pub fn into_std(self) -> Result<StdFile, File> {
+ self.try_into()
+ }
+
+ /// Read up to `buf.len()` bytes from the file starting at `offset` into `buf`, returning the
+ /// number of bytes read.
+ ///
+ /// If `offset` is `None` then the bytes are read starting from the kernel offset for the
+ /// underlying OS file handle. Callers should take care when calling this method from multiple
+ /// threads without providing `offset` as the order in which the operations will execute is
+ /// undefined.
+ ///
+ /// When using I/O drivers like `io_uring`, data may be copied from an internal buffer into
+ /// `buf` so this function is best suited for reading small amounts of data. Callers that wish
+ /// to avoid copying data may want to use `File::read_iobuf` instead. Additionally, dropping
+ /// this async fn after it has started may not cancel the underlying asynchronous operation.
+ pub async fn read(&self, buf: &mut [u8], offset: Option<u64>) -> anyhow::Result<usize> {
+ self.inner.read(buf, offset).await
+ }
+
+ /// Read exactly `buf.len()` bytes from the file starting at `offset` into `buf`.
+ ///
+ /// This method calls `File::read` in a loop until `buf.len()` bytes have been read from the
+ /// underlying file. Callers should take care when calling this method from multiple threads
+ /// without providing `offset` as the order in which data is read is undefined and the data
+ /// returned in `buf` may not be from contiguous regions in the underlying file.
+ ///
+ /// When using I/O drivers like `io_uring`, data may be copied from an internal buffer into
+ /// `buf`. Callers that wish to avoid copying data may want to use `File::read_iobuf` instead.
+ /// Additionally, dropping this async fn after it has started may not cancel the underlying
+ /// asynchronous operation.
+ ///
+ /// # Errors
+ ///
+ /// This function will directly return any non-`io::ErrorKind::Interrupted` errors. In this
+ /// case, the number of bytes read from the underlying file and the kernel offset are undefined.
+ pub async fn read_exact(
+ &self,
+ mut buf: &mut [u8],
+ mut offset: Option<u64>,
+ ) -> anyhow::Result<()> {
+ if let Some(off) = offset {
+ debug_assert!(u64::try_from(buf.len())
+ .ok()
+ .and_then(|len| len.checked_add(off))
+ .is_some());
+ }
+
+ while !buf.is_empty() {
+ match self.read(buf, offset).await {
+ Ok(0) => return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into()),
+ Ok(n) => {
+ buf = &mut buf[n..];
+ if let Some(off) = offset {
+ offset = Some(off + n as u64);
+ }
+ }
+ Err(e) => {
+ let err = e.downcast_ref::<io::Error>().expect(DOWNCAST_ERROR);
+ if err.kind() != io::ErrorKind::Interrupted {
+ return Err(e);
+ }
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Reads data from the underlying data starting at `offset` into an owned buffer.
+ ///
+ /// This method is like `File::read` but takes ownership of the buffer. When using I/O drivers
+ /// like `io_uring`, this can improve performance by avoiding the need to first read the data
+ /// into an internal buffer. Dropping this async fn may not cancel the underlying I/O operation.
+ ///
+ /// # Examples
+ ///
+ /// Read file data into a `Vec<u8>`.
+ ///
+ /// ```
+ /// # async fn read_to_vec() -> anyhow::Result<()> {
+ /// use cros_async::{File, OwnedIoBuf};
+ ///
+ /// let f = File::open("/dev/zero")?;
+ /// let buf = OwnedIoBuf::new(vec![0xcc; 64]);
+ ///
+ /// let (res, buf) = f.read_iobuf(buf, None).await;
+ /// let count = res?;
+ ///
+ /// let orig: Vec<u8> = buf.into_inner();
+ /// assert!(count <= 64);
+ /// assert_eq!(&orig[..count], &[0u8; 64][..count]);
+ /// # Ok(())
+ /// # }
+ /// # cros_async::Executor::new().run_until(read_to_vec()).unwrap();
+ /// ```
+ pub async fn read_iobuf<B: AsIoBufs + Unpin + 'static>(
+ &self,
+ buf: B,
+ offset: Option<u64>,
+ ) -> (anyhow::Result<usize>, B) {
+ self.inner.read_iobuf(buf, offset).await
+ }
+
+ /// Write up to `buf.len()` bytes from `buf` to the file starting at `offset`, returning the
+ /// number of bytes written.
+ ///
+ /// If `offset` is `None` then the bytes are written starting from the kernel offset for the
+ /// underlying OS file handle. Callers should take care when calling this method from multiple
+ /// threads without providing `offset` as the order in which the operations will execute is
+ /// undefined.
+ ///
+ /// When using I/O drivers like `io_uring`, data may be copied into an internal buffer from
+ /// `buf` so this function is best suited for writing small amounts of data. Callers that wish
+ /// to avoid copying data may want to use `File::write_iobuf` instead. Additionally, dropping
+ /// this async fn after it has started may not cancel the underlying asynchronous operation.
+ pub async fn write(&self, buf: &[u8], offset: Option<u64>) -> anyhow::Result<usize> {
+ self.inner.write(buf, offset).await
+ }
+
+ /// Write all the data from `buf` into the underlying file starting at `offset`.
+ ///
+ /// This method calls `File::write` in a loop until `buf.len()` bytes have been written to the
+ /// underlying file. Callers should take care when calling this method from multiple threads
+ /// without providing `offset` as the order in which data is written is undefined and the data
+ /// written to the file may not be contiguous with respect to `buf`.
+ ///
+ /// When using I/O drivers like `io_uring`, data may be copied into an internal buffer from
+ /// `buf`. Callers that wish to avoid copying data may want to use `File::write_iobuf` instead.
+ /// Additionally, dropping this async fn after it has started may not cancel the underlying
+ /// asynchronous operation.
+ ///
+ /// # Errors
+ ///
+ /// This function will directly return any non-`io::ErrorKind::Interrupted` errors. In this
+ /// case, the number of bytes written to the underlying file and the kernel offset are undefined.
+ pub async fn write_all(&self, mut buf: &[u8], mut offset: Option<u64>) -> anyhow::Result<()> {
+ if let Some(off) = offset {
+ debug_assert!(u64::try_from(buf.len())
+ .ok()
+ .and_then(|len| len.checked_add(off))
+ .is_some());
+ }
+
+ while !buf.is_empty() {
+ match self.write(buf, offset).await {
+ Ok(0) => return Err(io::Error::from(io::ErrorKind::WriteZero).into()),
+ Ok(n) => {
+ buf = &buf[n..];
+ if let Some(off) = offset {
+ offset = Some(off + n as u64);
+ }
+ }
+ Err(e) => {
+ let err = e.downcast_ref::<io::Error>().expect(DOWNCAST_ERROR);
+ if err.kind() != io::ErrorKind::Interrupted {
+ return Err(e);
+ }
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Writes data from an owned buffer into the underlying file at `offset`.
+ ///
+ /// This method is like `File::write` but takes ownership of the buffer. When using I/O drivers
+ /// like `io_uring`, this can improve performance by avoiding the need to first copy the data
+ /// into an internal buffer. Dropping this async fn may not cancel the underlying I/O operation.
+ ///
+ /// # Examples
+ ///
+ /// Write file data from a `Vec<u8>`.
+ ///
+ /// ```
+ /// # async fn read_to_vec() -> anyhow::Result<()> {
+ /// use cros_async::{File, OwnedIoBuf};
+ ///
+ /// let f = File::open("/dev/null")?;
+ /// let buf = OwnedIoBuf::new(vec![0xcc; 64]);
+ ///
+ /// let (res, buf) = f.write_iobuf(buf, None).await;
+ /// let count = res?;
+ ///
+ /// let orig: Vec<u8> = buf.into_inner();
+ /// assert!(count <= 64);
+ /// # Ok(())
+ /// # }
+ /// # cros_async::Executor::new().run_until(read_to_vec()).unwrap();
+ /// ```
+ pub async fn write_iobuf<B: AsIoBufs + Unpin + 'static>(
+ &self,
+ buf: B,
+ offset: Option<u64>,
+ ) -> (anyhow::Result<usize>, B) {
+ self.inner.write_iobuf(buf, offset).await
+ }
+
+ /// Creates a hole in the underlying file of `len` bytes starting at `offset`.
+ pub async fn punch_hole(&self, offset: u64, len: u64) -> anyhow::Result<()> {
+ self.inner.punch_hole(offset, len).await
+ }
+
+ /// Writes `len` bytes of zeroes to the underlying file at `offset`.
+ pub async fn write_zeroes(&self, offset: u64, len: u64) -> anyhow::Result<()> {
+ self.inner.write_zeroes(offset, len).await
+ }
+
+ /// Allocates `len` bytes of disk storage for the underlying file starting at `offset`.
+ pub async fn allocate(&self, offset: u64, len: u64) -> anyhow::Result<()> {
+ self.inner.allocate(offset, len).await
+ }
+
+ /// Returns the current length of the file in bytes.
+ pub async fn get_len(&self) -> anyhow::Result<u64> {
+ self.inner.get_len().await
+ }
+
+ /// Sets the current length of the file in bytes.
+ pub async fn set_len(&self, len: u64) -> anyhow::Result<()> {
+ self.inner.set_len(len).await
+ }
+
+ /// Sync all buffered data and metadata for the underlying file to the disk.
+ pub async fn sync_all(&self) -> anyhow::Result<()> {
+ self.inner.sync_all().await
+ }
+
+ /// Like `File::sync_all` but may not sync file metadata to the disk.
+ pub async fn sync_data(&self) -> anyhow::Result<()> {
+ self.inner.sync_data().await
+ }
+
+ /// Try to clone this `File`.
+ ///
+ /// If successful, the returned `File` will have its own unique OS handle for the underlying
+ /// file.
+ pub fn try_clone(&self) -> anyhow::Result<File> {
+ self.inner.try_clone().map(|inner| File { inner })
+ }
+}
+
+impl TryFrom<StdFile> for File {
+ type Error = anyhow::Error;
+
+ fn try_from(f: StdFile) -> anyhow::Result<Self> {
+ sys::File::try_from(f).map(|inner| File { inner })
+ }
+}
+
+impl TryFrom<File> for StdFile {
+ type Error = File;
+ fn try_from(f: File) -> Result<StdFile, File> {
+ StdFile::try_from(f.inner).map_err(|inner| File { inner })
+ }
+}
+
+impl AsRawDescriptor for File {
+ fn as_raw_descriptor(&self) -> sys_util::RawDescriptor {
+ self.inner.as_raw_descriptor()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use std::{
+ fs::{File as StdFile, OpenOptions},
+ path::PathBuf,
+ thread,
+ };
+
+ use anyhow::Context;
+ use futures::channel::oneshot::channel;
+
+ use crate::{Executor, OwnedIoBuf};
+
+ #[test]
+ fn readvec() {
+ async fn go() {
+ let f = StdFile::open("/dev/zero")
+ .context("failed to open /dev/zero")
+ .and_then(File::try_from)
+ .unwrap();
+ let v = OwnedIoBuf::new(vec![0x55u8; 32]);
+
+ let (res, v) = f.read_iobuf(v, None).await;
+ let count = res.unwrap();
+ assert_eq!(count, v.len());
+ assert!(v.iter().all(|&b| b == 0));
+ }
+
+ let ex = Executor::new();
+ ex.run_until(go()).unwrap();
+ }
+
+ #[test]
+ fn writevec() {
+ async fn go() {
+ let f = OpenOptions::new()
+ .write(true)
+ .open("/dev/null")
+ .context("failed to open /dev/null")
+ .and_then(File::try_from)
+ .unwrap();
+ let v = OwnedIoBuf::new(vec![0x55u8; 32]);
+ let (res, _v) = f.write_iobuf(v, None).await;
+ let count = res.unwrap();
+ assert_eq!(count, 32);
+ }
+
+ let ex = Executor::new();
+ ex.run_until(go()).unwrap();
+ }
+
+ #[test]
+ fn file_is_send() {
+ const TRANSFER_COUNT: usize = 24;
+
+ let (tx, rx) = channel::<File>();
+ let ex = Executor::new();
+
+ let ex2 = ex.clone();
+ let worker_thread = thread::spawn(move || {
+ ex2.run_until(async move {
+ let f = rx.await.unwrap();
+ let buf = [0xa2; TRANSFER_COUNT];
+ f.write_all(&buf, None).await.unwrap();
+ })
+ .unwrap();
+ });
+
+ let (pipe_out, pipe_in) = sys_util::pipe(true).unwrap();
+ ex.run_until(async move {
+ tx.send(File::try_from(pipe_in).unwrap()).unwrap();
+
+ let pipe = File::try_from(pipe_out).unwrap();
+ let mut buf = [0u8; TRANSFER_COUNT];
+ pipe.read_exact(&mut buf, None).await.unwrap();
+ assert_eq!(buf, [0xa2; TRANSFER_COUNT]);
+ })
+ .unwrap();
+
+ worker_thread.join().unwrap();
+ }
+
+ #[test]
+ fn fallocate() {
+ let ex = Executor::new();
+ ex.run_until(async {
+ let dir = tempfile::TempDir::new().unwrap();
+ let mut file_path = PathBuf::from(dir.path());
+ file_path.push("test");
+
+ let f = OpenOptions::new()
+ .create(true)
+ .write(true)
+ .open(&file_path)
+ .unwrap();
+ let source = File::try_from(f).unwrap();
+ source.allocate(0, 4096).await.unwrap();
+
+ let meta_data = std::fs::metadata(&file_path).unwrap();
+ assert_eq!(meta_data.len(), 4096);
+ })
+ .unwrap();
+ }
+}
diff --git a/common/cros_asyncv2/src/iobuf.rs b/common/cros_asyncv2/src/iobuf.rs
new file mode 100644
index 000000000..16afa899f
--- /dev/null
+++ b/common/cros_asyncv2/src/iobuf.rs
@@ -0,0 +1,190 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::ops::{Deref, DerefMut};
+
+use data_model::IoBufMut;
+
+/// A trait for describing regions of memory to be used for IO.
+///
+/// # Safety
+///
+/// Types that implement this trait _must_ guarantee that the memory regions described by
+/// `as_iobufs()` are valid for the lifetime of the borrow.
+pub unsafe trait AsIoBufs {
+ /// Returns a slice describing regions of memory on which to perform some IO. The returned
+ /// memory region descriptions may be passed on to the OS kernel so implmentations must
+ /// guarantee that the memory regions are valid for the lifetime of the borrow.
+ fn as_iobufs(&mut self) -> &[IoBufMut];
+}
+
+/// An owned buffer for IO.
+///
+/// This type will be most commonly used to wrap a `Vec<u8>`.
+///
+/// # Examples
+///
+/// ```
+/// # async fn do_it() -> anyhow::Result<()> {
+/// use cros_async::{Executor, File, OwnedIoBuf};
+/// use std::{convert::TryFrom, fs::OpenOptions};
+///
+/// let urandom = File::open("/dev/urandom")?;
+/// let buf = OwnedIoBuf::new(vec![0; 256]);
+///
+/// let (res, mut buf) = urandom.read_iobuf(buf, None).await;
+/// let count = res?;
+/// buf.truncate(count);
+///
+/// let null = OpenOptions::new()
+/// .write(true)
+/// .open("/dev/null")
+/// .map_err(From::from)
+/// .and_then(File::try_from)?;
+/// while buf.len() > 0 {
+/// let (res, data) = null.write_iobuf(buf, None).await;
+/// let bytes_written = res?;
+/// buf = data;
+/// buf.advance(bytes_written);
+/// }
+/// # Ok(())
+/// # }
+/// # cros_async::Executor::new().run_until(do_it()).unwrap().unwrap();
+/// ```
+pub struct OwnedIoBuf {
+ buf: Box<[u8]>,
+ // This IoBufMut has a static lifetime because it points to the data owned by `buf` so the
+ // pointer is always valid for the lifetime of this struct.
+ iobuf: [IoBufMut<'static>; 1],
+}
+
+impl OwnedIoBuf {
+ /// Create a new owned IO buffer.
+ pub fn new<B: Into<Box<[u8]>>>(buf: B) -> OwnedIoBuf {
+ let mut buf = buf.into();
+ let iobuf = unsafe { IoBufMut::from_raw_parts(buf.as_mut_ptr(), buf.len()) };
+ OwnedIoBuf {
+ buf,
+ iobuf: [iobuf],
+ }
+ }
+
+ /// Returns the length of the IO buffer.
+ pub fn len(&self) -> usize {
+ self.iobuf[0].len()
+ }
+
+ /// Returns true if the length of the buffer is 0.
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ /// Advances the beginning of the buffer by `count` bytes.
+ ///
+ /// Panics if `count > self.len()`.
+ pub fn advance(&mut self, count: usize) {
+ self.iobuf[0].advance(count)
+ }
+
+ /// Change the size of the buffer used for IO.
+ ///
+ /// Has no effect if `len > self.len()`.
+ pub fn truncate(&mut self, len: usize) {
+ self.iobuf[0].truncate(len)
+ }
+
+ /// Reset the buffer to its full length.
+ pub fn reset(&mut self) {
+ self.iobuf[0] = unsafe { IoBufMut::from_raw_parts(self.buf.as_mut_ptr(), self.buf.len()) };
+ }
+
+ /// Convert this `OwnedIoBuf` back into the inner type.
+ pub fn into_inner<C: From<Box<[u8]>>>(self) -> C {
+ self.buf.into()
+ }
+}
+
+impl Deref for OwnedIoBuf {
+ type Target = [u8];
+ fn deref(&self) -> &Self::Target {
+ &self.buf
+ }
+}
+
+impl DerefMut for OwnedIoBuf {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.buf
+ }
+}
+
+// Safe because the pointer and length in the returned `&[IoBufMut]` are valid for the lifetime of
+// the OwnedIoBuf.
+unsafe impl AsIoBufs for OwnedIoBuf {
+ fn as_iobufs(&mut self) -> &[IoBufMut] {
+ &self.iobuf
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn len() {
+ let buf = OwnedIoBuf::new(vec![0xcc; 64]);
+
+ assert_eq!(buf.len(), 64);
+ }
+
+ #[test]
+ fn advance() {
+ let mut buf = OwnedIoBuf::new(vec![0xcc; 64]);
+
+ buf.advance(17);
+ assert_eq!(buf.len(), 47);
+ assert_eq!(buf.iobuf[0].as_ptr(), unsafe { buf.buf.as_ptr().add(17) });
+
+ buf.advance(9);
+ assert_eq!(buf.len(), 38);
+ assert_eq!(buf.iobuf[0].as_ptr(), unsafe { buf.buf.as_ptr().add(26) });
+
+ buf.advance(38);
+ assert_eq!(buf.len(), 0);
+ assert_eq!(buf.iobuf[0].as_ptr(), unsafe { buf.buf.as_ptr().add(64) });
+ }
+
+ #[test]
+ fn truncate() {
+ let mut buf = OwnedIoBuf::new(vec![0xcc; 64]);
+
+ buf.truncate(99);
+ assert_eq!(buf.len(), 64);
+
+ buf.truncate(64);
+ assert_eq!(buf.len(), 64);
+
+ buf.truncate(22);
+ assert_eq!(buf.len(), 22);
+
+ buf.truncate(0);
+ assert_eq!(buf.len(), 0);
+ }
+
+ #[test]
+ fn reset() {
+ let mut buf = OwnedIoBuf::new(vec![0xcc; 64]);
+
+ buf.truncate(22);
+ assert_eq!(buf.len(), 22);
+ assert_eq!(buf.iobuf[0].as_ptr(), buf.buf.as_ptr());
+
+ buf.advance(17);
+ assert_eq!(buf.len(), 5);
+ assert_eq!(buf.iobuf[0].as_ptr(), unsafe { buf.buf.as_ptr().add(17) });
+
+ buf.reset();
+ assert_eq!(buf.len(), 64);
+ assert_eq!(buf.iobuf[0].as_ptr(), buf.buf.as_ptr());
+ }
+}
diff --git a/common/cros_asyncv2/src/lib.rs b/common/cros_asyncv2/src/lib.rs
new file mode 100644
index 000000000..711d1ca99
--- /dev/null
+++ b/common/cros_asyncv2/src/lib.rs
@@ -0,0 +1,108 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! An Executor and various primitives for running asynchronous code.
+//!
+//! This crate is meant to be used with the `futures-rs` crate that provides further combinators
+//! and utility functions to combine and manage futures.
+//!
+//! # Running top-level futures.
+//!
+//! If there is only one top-level future to run, use the [`Executor::run_until`] method.
+//!
+//! # Implementations
+//!
+//! There are currently two paths for asynchronous IO: epoll and io_uring. The io_uring backend may
+//! be disabled at compile time. When io_uring support is enabled, the framework performs a runtime
+//! check to determine the backend to use.
+//!
+//! The backend selection should be transparent to the users of this crate.
+//!
+//! # Concrete async types
+//!
+//! This crate provides asynchronous versions of various IO types, like [`File`] and [`Event`].
+//! Users should use these high-level types to perform asynchronous IO operations.
+//!
+//! # Thread pools
+//!
+//! The [`Executor`] supports driving futures simultaneously from multiple threads. To use a thread
+//! pool, simply spawn the desired number of threads and call [`Executor::run`] or
+//! [`Executor::run_until`] on each thread. Futures spawned via [`Executor::spawn`] may then run on
+//! any of the threads in the thread pool.
+//!
+//! # Global executor
+//!
+//! This crate deliberately doesn't define a global executor. Instead all methods are implemented
+//! directly on the [`Executor`] type. Users who want to use a single global executor for their
+//! whole program can easily do so:
+//!
+//! ```
+//! use cros_async::Executor;
+//! use once_cell::sync::Lazy;
+//! static GLOBAL_EXECUTOR: Lazy<Executor> = Lazy::new(Executor::new);
+//!
+//! let val = GLOBAL_EXECUTOR.run_until(async { 11 + 23 }).unwrap();
+//! assert_eq!(val, 34);
+//! ```
+//!
+//! # Dealing with `!Send` futures
+//!
+//! Almost all the functions of the async types in this crate are `!Send`. This status is
+//! infectious: when one of these futures are `await`ed from another future, that future also
+//! becomes `!Send`, which can be quite inconvenient. One way to isolate the `!Send` future is to
+//! use [`Executor::spawn_local`] with a [`oneshot::channel`](futures::channel::oneshot::channel):
+//!
+//! ```
+//! use std::mem::size_of;
+//!
+//! use futures::channel::oneshot::channel;
+//! use cros_async::{Executor, File};
+//!
+//! async fn outer_future_implements_send(ex: Executor, f: File) -> u64 {
+//! let (tx, rx) = channel();
+//! let mut buf = 0x77u64.to_ne_bytes();
+//! ex.spawn_local(async move {
+//! let res = f.read(&mut buf, None).await;
+//! let _ = tx.send((res, buf));
+//! }).detach();
+//!
+//! let (res, buf) = rx.await.expect("task canceled after detach()");
+//! assert_eq!(res.unwrap(), size_of::<u64>());
+//! u64::from_ne_bytes(buf)
+//! }
+//!
+//! let ex = Executor::new();
+//! let f = File::open("/dev/zero").unwrap();
+//!
+//! // `spawn` requires that the future implements `Send`.
+//! let task = ex.spawn(outer_future_implements_send(ex.clone(), f));
+//!
+//! let val = ex.run_until(task).unwrap();
+//! assert_eq!(val, 0);
+//! ```
+//!
+//! A cancelable version of the inner future can be implemented using
+//! [`abortable`](futures::future::abortable). However keep in mind that on backends like io_uring,
+//! cancelling the future may not cancel the underlying IO operation.
+
+mod blocking;
+mod enter;
+mod event;
+mod executor;
+mod file;
+mod iobuf;
+pub mod sync;
+mod timer;
+
+#[cfg_attr(unix, path = "unix/mod.rs")]
+mod sys;
+
+pub use blocking::{block_on, BlockingPool};
+pub use event::Event;
+pub use executor::Executor;
+pub use file::File;
+pub use iobuf::{AsIoBufs, OwnedIoBuf};
+#[cfg(unix)]
+pub use sys::{Descriptor, SeqPacket as UnixSeqPacket, SeqPacketListener as UnixSeqPacketListener};
+pub use timer::{with_deadline, TimedOut, Timer};
diff --git a/common/cros_asyncv2/src/sync.rs b/common/cros_asyncv2/src/sync.rs
new file mode 100644
index 000000000..e2c1a1452
--- /dev/null
+++ b/common/cros_asyncv2/src/sync.rs
@@ -0,0 +1,12 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod cv;
+mod mu;
+mod spin;
+mod waiter;
+
+pub use cv::Condvar;
+pub use mu::Mutex;
+pub use spin::SpinLock;
diff --git a/common/cros_asyncv2/src/sync/cv.rs b/common/cros_asyncv2/src/sync/cv.rs
new file mode 100644
index 000000000..cc7512129
--- /dev/null
+++ b/common/cros_asyncv2/src/sync/cv.rs
@@ -0,0 +1,1166 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::UnsafeCell;
+use std::hint;
+use std::mem;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::Arc;
+
+use crate::sync::mu::{MutexGuard, MutexReadGuard, RawMutex};
+use crate::sync::waiter::{Kind as WaiterKind, Waiter, WaiterAdapter, WaiterList, WaitingFor};
+
+const SPINLOCK: usize = 1 << 0;
+const HAS_WAITERS: usize = 1 << 1;
+
+/// A primitive to wait for an event to occur without consuming CPU time.
+///
+/// Condition variables are used in combination with a `Mutex` when a thread wants to wait for some
+/// condition to become true. The condition must always be verified while holding the `Mutex` lock.
+/// It is an error to use a `Condvar` with more than one `Mutex` while there are threads waiting on
+/// the `Condvar`.
+///
+/// # Examples
+///
+/// ```edition2018
+/// use std::sync::Arc;
+/// use std::thread;
+/// use std::sync::mpsc::channel;
+///
+/// use cros_async::{
+/// block_on,
+/// sync::{Condvar, Mutex},
+/// };
+///
+/// const N: usize = 13;
+///
+/// // Spawn a few threads to increment a shared variable (non-atomically), and
+/// // let all threads waiting on the Condvar know once the increments are done.
+/// let data = Arc::new(Mutex::new(0));
+/// let cv = Arc::new(Condvar::new());
+///
+/// for _ in 0..N {
+/// let (data, cv) = (data.clone(), cv.clone());
+/// thread::spawn(move || {
+/// let mut data = block_on(data.lock());
+/// *data += 1;
+/// if *data == N {
+/// cv.notify_all();
+/// }
+/// });
+/// }
+///
+/// let mut val = block_on(data.lock());
+/// while *val != N {
+/// val = block_on(cv.wait(val));
+/// }
+/// ```
+#[repr(align(128))]
+pub struct Condvar {
+ state: AtomicUsize,
+ waiters: UnsafeCell<WaiterList>,
+ mu: UnsafeCell<usize>,
+}
+
+impl Condvar {
+ /// Creates a new condition variable ready to be waited on and notified.
+ pub fn new() -> Condvar {
+ Condvar {
+ state: AtomicUsize::new(0),
+ waiters: UnsafeCell::new(WaiterList::new(WaiterAdapter::new())),
+ mu: UnsafeCell::new(0),
+ }
+ }
+
+ /// Block the current thread until this `Condvar` is notified by another thread.
+ ///
+ /// This method will atomically unlock the `Mutex` held by `guard` and then block the current
+ /// thread. Any call to `notify_one` or `notify_all` after the `Mutex` is unlocked may wake up
+ /// the thread.
+ ///
+ /// To allow for more efficient scheduling, this call may return even when the programmer
+ /// doesn't expect the thread to be woken. Therefore, calls to `wait()` should be used inside a
+ /// loop that checks the predicate before continuing.
+ ///
+ /// Callers that are not in an async context may wish to use the `block_on` method to block the
+ /// thread until the `Condvar` is notified.
+ ///
+ /// # Panics
+ ///
+ /// This method will panic if used with more than one `Mutex` at the same time.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use std::sync::Arc;
+ /// # use std::thread;
+ ///
+ /// # use cros_async::{
+ /// # block_on,
+ /// # sync::{Condvar, Mutex},
+ /// # };
+ ///
+ /// # let mu = Arc::new(Mutex::new(false));
+ /// # let cv = Arc::new(Condvar::new());
+ /// # let (mu2, cv2) = (mu.clone(), cv.clone());
+ ///
+ /// # let t = thread::spawn(move || {
+ /// # *block_on(mu2.lock()) = true;
+ /// # cv2.notify_all();
+ /// # });
+ ///
+ /// let mut ready = block_on(mu.lock());
+ /// while !*ready {
+ /// ready = block_on(cv.wait(ready));
+ /// }
+ ///
+ /// # t.join().expect("failed to join thread");
+ /// ```
+ // Clippy doesn't like the lifetime parameters here but doing what it suggests leads to code
+ // that doesn't compile.
+ #[allow(clippy::needless_lifetimes)]
+ pub async fn wait<'g, T>(&self, guard: MutexGuard<'g, T>) -> MutexGuard<'g, T> {
+ let waiter = Arc::new(Waiter::new(
+ WaiterKind::Exclusive,
+ cancel_waiter,
+ self as *const Condvar as usize,
+ WaitingFor::Condvar,
+ ));
+
+ self.add_waiter(waiter.clone(), guard.as_raw_mutex());
+
+ // Get a reference to the mutex and then drop the lock.
+ let mu = guard.into_inner();
+
+ // Wait to be woken up.
+ waiter.wait().await;
+
+ // Now re-acquire the lock.
+ mu.lock_from_cv().await
+ }
+
+ /// Like `wait()` but takes and returns a `MutexReadGuard` instead.
+ // Clippy doesn't like the lifetime parameters here but doing what it suggests leads to code
+ // that doesn't compile.
+ #[allow(clippy::needless_lifetimes)]
+ pub async fn wait_read<'g, T>(&self, guard: MutexReadGuard<'g, T>) -> MutexReadGuard<'g, T> {
+ let waiter = Arc::new(Waiter::new(
+ WaiterKind::Shared,
+ cancel_waiter,
+ self as *const Condvar as usize,
+ WaitingFor::Condvar,
+ ));
+
+ self.add_waiter(waiter.clone(), guard.as_raw_mutex());
+
+ // Get a reference to the mutex and then drop the lock.
+ let mu = guard.into_inner();
+
+ // Wait to be woken up.
+ waiter.wait().await;
+
+ // Now re-acquire the lock.
+ mu.read_lock_from_cv().await
+ }
+
+ fn add_waiter(&self, waiter: Arc<Waiter>, raw_mutex: &RawMutex) {
+ // Acquire the spin lock.
+ let mut oldstate = self.state.load(Ordering::Relaxed);
+ while (oldstate & SPINLOCK) != 0
+ || self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ oldstate | SPINLOCK | HAS_WAITERS,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ hint::spin_loop();
+ oldstate = self.state.load(Ordering::Relaxed);
+ }
+
+ // Safe because the spin lock guarantees exclusive access and the reference does not escape
+ // this function.
+ let mu = unsafe { &mut *self.mu.get() };
+ let muptr = raw_mutex as *const RawMutex as usize;
+
+ match *mu {
+ 0 => *mu = muptr,
+ p if p == muptr => {}
+ _ => panic!("Attempting to use Condvar with more than one Mutex at the same time"),
+ }
+
+ // Safe because the spin lock guarantees exclusive access.
+ unsafe { (*self.waiters.get()).push_back(waiter) };
+
+ // Release the spin lock. Use a direct store here because no other thread can modify
+ // `self.state` while we hold the spin lock. Keep the `HAS_WAITERS` bit that we set earlier
+ // because we just added a waiter.
+ self.state.store(HAS_WAITERS, Ordering::Release);
+ }
+
+ /// Notify at most one thread currently waiting on the `Condvar`.
+ ///
+ /// If there is a thread currently waiting on the `Condvar` it will be woken up from its call to
+ /// `wait`.
+ ///
+ /// Unlike more traditional condition variable interfaces, this method requires a reference to
+ /// the `Mutex` associated with this `Condvar`. This is because it is inherently racy to call
+ /// `notify_one` or `notify_all` without first acquiring the `Mutex` lock. Additionally, taking
+ /// a reference to the `Mutex` here allows us to make some optimizations that can improve
+ /// performance by reducing unnecessary wakeups.
+ pub fn notify_one(&self) {
+ let mut oldstate = self.state.load(Ordering::Relaxed);
+ if (oldstate & HAS_WAITERS) == 0 {
+ // No waiters.
+ return;
+ }
+
+ while (oldstate & SPINLOCK) != 0
+ || self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ oldstate | SPINLOCK,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ hint::spin_loop();
+ oldstate = self.state.load(Ordering::Relaxed);
+ }
+
+ // Safe because the spin lock guarantees exclusive access and the reference does not escape
+ // this function.
+ let waiters = unsafe { &mut *self.waiters.get() };
+ let wake_list = get_wake_list(waiters);
+
+ let newstate = if waiters.is_empty() {
+ // Also clear the mutex associated with this Condvar since there are no longer any
+ // waiters. Safe because the spin lock guarantees exclusive access.
+ unsafe { *self.mu.get() = 0 };
+
+ // We are releasing the spin lock and there are no more waiters so we can clear all bits
+ // in `self.state`.
+ 0
+ } else {
+ // There are still waiters so we need to keep the HAS_WAITERS bit in the state.
+ HAS_WAITERS
+ };
+
+ // Release the spin lock.
+ self.state.store(newstate, Ordering::Release);
+
+ // Now wake any waiters in the wake list.
+ for w in wake_list {
+ w.wake();
+ }
+ }
+
+ /// Notify all threads currently waiting on the `Condvar`.
+ ///
+ /// All threads currently waiting on the `Condvar` will be woken up from their call to `wait`.
+ ///
+ /// Unlike more traditional condition variable interfaces, this method requires a reference to
+ /// the `Mutex` associated with this `Condvar`. This is because it is inherently racy to call
+ /// `notify_one` or `notify_all` without first acquiring the `Mutex` lock. Additionally, taking
+ /// a reference to the `Mutex` here allows us to make some optimizations that can improve
+ /// performance by reducing unnecessary wakeups.
+ pub fn notify_all(&self) {
+ let mut oldstate = self.state.load(Ordering::Relaxed);
+ if (oldstate & HAS_WAITERS) == 0 {
+ // No waiters.
+ return;
+ }
+
+ while (oldstate & SPINLOCK) != 0
+ || self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ oldstate | SPINLOCK,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ hint::spin_loop();
+ oldstate = self.state.load(Ordering::Relaxed);
+ }
+
+ // Safe because the spin lock guarantees exclusive access to `self.waiters`.
+ let wake_list = unsafe { (*self.waiters.get()).take() };
+
+ // Clear the mutex associated with this Condvar since there are no longer any waiters. Safe
+ // because we the spin lock guarantees exclusive access.
+ unsafe { *self.mu.get() = 0 };
+
+ // Mark any waiters left as no longer waiting for the Condvar.
+ for w in &wake_list {
+ w.set_waiting_for(WaitingFor::None);
+ }
+
+ // Release the spin lock. We can clear all bits in the state since we took all the waiters.
+ self.state.store(0, Ordering::Release);
+
+ // Now wake any waiters in the wake list.
+ for w in wake_list {
+ w.wake();
+ }
+ }
+
+ fn cancel_waiter(&self, waiter: &Waiter, wake_next: bool) {
+ let mut oldstate = self.state.load(Ordering::Relaxed);
+ while oldstate & SPINLOCK != 0
+ || self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ oldstate | SPINLOCK,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ hint::spin_loop();
+ oldstate = self.state.load(Ordering::Relaxed);
+ }
+
+ // Safe because the spin lock provides exclusive access and the reference does not escape
+ // this function.
+ let waiters = unsafe { &mut *self.waiters.get() };
+
+ let waiting_for = waiter.is_waiting_for();
+ // Don't drop the old waiter now as we're still holding the spin lock.
+ let old_waiter = if waiter.is_linked() && waiting_for == WaitingFor::Condvar {
+ // Safe because we know that the waiter is still linked and is waiting for the Condvar,
+ // which guarantees that it is still in `self.waiters`.
+ let mut cursor = unsafe { waiters.cursor_mut_from_ptr(waiter as *const Waiter) };
+ cursor.remove()
+ } else {
+ None
+ };
+
+ let wake_list = if wake_next || waiting_for == WaitingFor::None {
+ // Either the waiter was already woken or it's been removed from the condvar's waiter
+ // list and is going to be woken. Either way, we need to wake up another thread.
+ get_wake_list(waiters)
+ } else {
+ WaiterList::new(WaiterAdapter::new())
+ };
+
+ let set_on_release = if waiters.is_empty() {
+ // Clear the mutex associated with this Condvar since there are no longer any waiters. Safe
+ // because we the spin lock guarantees exclusive access.
+ unsafe { *self.mu.get() = 0 };
+
+ 0
+ } else {
+ HAS_WAITERS
+ };
+
+ self.state.store(set_on_release, Ordering::Release);
+
+ // Now wake any waiters still left in the wake list.
+ for w in wake_list {
+ w.wake();
+ }
+
+ mem::drop(old_waiter);
+ }
+}
+
+unsafe impl Send for Condvar {}
+unsafe impl Sync for Condvar {}
+
+impl Default for Condvar {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+// Scan `waiters` and return all waiters that should be woken up.
+//
+// If the first waiter is trying to acquire a shared lock, then all waiters in the list that are
+// waiting for a shared lock are also woken up. In addition one writer is woken up, if possible.
+//
+// If the first waiter is trying to acquire an exclusive lock, then only that waiter is returned and
+// the rest of the list is not scanned.
+fn get_wake_list(waiters: &mut WaiterList) -> WaiterList {
+ let mut to_wake = WaiterList::new(WaiterAdapter::new());
+ let mut cursor = waiters.front_mut();
+
+ let mut waking_readers = false;
+ let mut all_readers = true;
+ while let Some(w) = cursor.get() {
+ match w.kind() {
+ WaiterKind::Exclusive if !waking_readers => {
+ // This is the first waiter and it's a writer. No need to check the other waiters.
+ // Also mark the waiter as having been removed from the Condvar's waiter list.
+ let waiter = cursor.remove().unwrap();
+ waiter.set_waiting_for(WaitingFor::None);
+ to_wake.push_back(waiter);
+ break;
+ }
+
+ WaiterKind::Shared => {
+ // This is a reader and the first waiter in the list was not a writer so wake up all
+ // the readers in the wait list.
+ let waiter = cursor.remove().unwrap();
+ waiter.set_waiting_for(WaitingFor::None);
+ to_wake.push_back(waiter);
+ waking_readers = true;
+ }
+
+ WaiterKind::Exclusive => {
+ debug_assert!(waking_readers);
+ if all_readers {
+ // We are waking readers but we need to ensure that at least one writer is woken
+ // up. Since we haven't yet woken up a writer, wake up this one.
+ let waiter = cursor.remove().unwrap();
+ waiter.set_waiting_for(WaitingFor::None);
+ to_wake.push_back(waiter);
+ all_readers = false;
+ } else {
+ // We are waking readers and have already woken one writer. Skip this one.
+ cursor.move_next();
+ }
+ }
+ }
+ }
+
+ to_wake
+}
+
+fn cancel_waiter(cv: usize, waiter: &Waiter, wake_next: bool) {
+ let condvar = cv as *const Condvar;
+
+ // Safe because the thread that owns the waiter being canceled must also own a reference to the
+ // Condvar, which guarantees that this pointer is valid.
+ unsafe { (*condvar).cancel_waiter(waiter, wake_next) }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use std::future::Future;
+ use std::mem;
+ use std::ptr;
+ use std::rc::Rc;
+ use std::sync::mpsc::{channel, Sender};
+ use std::sync::Arc;
+ use std::task::{Context, Poll};
+ use std::thread::{self, JoinHandle};
+ use std::time::Duration;
+
+ use futures::channel::oneshot;
+ use futures::task::{waker_ref, ArcWake};
+ use futures::{select, FutureExt};
+ use futures_executor::{LocalPool, LocalSpawner, ThreadPool};
+ use futures_util::task::LocalSpawnExt;
+
+ use crate::{block_on, sync::Mutex};
+
+ // Dummy waker used when we want to manually drive futures.
+ struct TestWaker;
+ impl ArcWake for TestWaker {
+ fn wake_by_ref(_arc_self: &Arc<Self>) {}
+ }
+
+ #[test]
+ fn smoke() {
+ let cv = Condvar::new();
+ cv.notify_one();
+ cv.notify_all();
+ }
+
+ #[test]
+ fn notify_one() {
+ let mu = Arc::new(Mutex::new(()));
+ let cv = Arc::new(Condvar::new());
+
+ let mu2 = mu.clone();
+ let cv2 = cv.clone();
+
+ let guard = block_on(mu.lock());
+ thread::spawn(move || {
+ let _g = block_on(mu2.lock());
+ cv2.notify_one();
+ });
+
+ let guard = block_on(cv.wait(guard));
+ mem::drop(guard);
+ }
+
+ #[test]
+ fn multi_mutex() {
+ const NUM_THREADS: usize = 5;
+
+ let mu = Arc::new(Mutex::new(false));
+ let cv = Arc::new(Condvar::new());
+
+ let mut threads = Vec::with_capacity(NUM_THREADS);
+ for _ in 0..NUM_THREADS {
+ let mu = mu.clone();
+ let cv = cv.clone();
+
+ threads.push(thread::spawn(move || {
+ let mut ready = block_on(mu.lock());
+ while !*ready {
+ ready = block_on(cv.wait(ready));
+ }
+ }));
+ }
+
+ let mut g = block_on(mu.lock());
+ *g = true;
+ mem::drop(g);
+ cv.notify_all();
+
+ threads
+ .into_iter()
+ .try_for_each(JoinHandle::join)
+ .expect("Failed to join threads");
+
+ // Now use the Condvar with a different mutex.
+ let alt_mu = Arc::new(Mutex::new(None));
+ let alt_mu2 = alt_mu.clone();
+ let cv2 = cv.clone();
+ let handle = thread::spawn(move || {
+ let mut g = block_on(alt_mu2.lock());
+ while g.is_none() {
+ g = block_on(cv2.wait(g));
+ }
+ });
+
+ let mut alt_g = block_on(alt_mu.lock());
+ *alt_g = Some(());
+ mem::drop(alt_g);
+ cv.notify_all();
+
+ handle
+ .join()
+ .expect("Failed to join thread alternate mutex");
+ }
+
+ #[test]
+ fn notify_one_single_thread_async() {
+ async fn notify(mu: Rc<Mutex<()>>, cv: Rc<Condvar>) {
+ let _g = mu.lock().await;
+ cv.notify_one();
+ }
+
+ async fn wait(mu: Rc<Mutex<()>>, cv: Rc<Condvar>, spawner: LocalSpawner) {
+ let mu2 = Rc::clone(&mu);
+ let cv2 = Rc::clone(&cv);
+
+ let g = mu.lock().await;
+ // Has to be spawned _after_ acquiring the lock to prevent a race
+ // where the notify happens before the waiter has acquired the lock.
+ spawner
+ .spawn_local(notify(mu2, cv2))
+ .expect("Failed to spawn `notify` task");
+ let _g = cv.wait(g).await;
+ }
+
+ let mut ex = LocalPool::new();
+ let spawner = ex.spawner();
+
+ let mu = Rc::new(Mutex::new(()));
+ let cv = Rc::new(Condvar::new());
+
+ spawner
+ .spawn_local(wait(mu, cv, spawner.clone()))
+ .expect("Failed to spawn `wait` task");
+
+ ex.run();
+ }
+
+ #[test]
+ fn notify_one_multi_thread_async() {
+ async fn notify(mu: Arc<Mutex<()>>, cv: Arc<Condvar>) {
+ let _g = mu.lock().await;
+ cv.notify_one();
+ }
+
+ async fn wait(mu: Arc<Mutex<()>>, cv: Arc<Condvar>, tx: Sender<()>, pool: ThreadPool) {
+ let mu2 = Arc::clone(&mu);
+ let cv2 = Arc::clone(&cv);
+
+ let g = mu.lock().await;
+ // Has to be spawned _after_ acquiring the lock to prevent a race
+ // where the notify happens before the waiter has acquired the lock.
+ pool.spawn_ok(notify(mu2, cv2));
+ let _g = cv.wait(g).await;
+
+ tx.send(()).expect("Failed to send completion notification");
+ }
+
+ let ex = ThreadPool::new().expect("Failed to create ThreadPool");
+
+ let mu = Arc::new(Mutex::new(()));
+ let cv = Arc::new(Condvar::new());
+
+ let (tx, rx) = channel();
+ ex.spawn_ok(wait(mu, cv, tx, ex.clone()));
+
+ rx.recv_timeout(Duration::from_secs(5))
+ .expect("Failed to receive completion notification");
+ }
+
+ #[test]
+ fn notify_one_with_cancel() {
+ const TASKS: usize = 17;
+ const OBSERVERS: usize = 7;
+ const ITERATIONS: usize = 103;
+
+ async fn observe(mu: &Arc<Mutex<usize>>, cv: &Arc<Condvar>) {
+ let mut count = mu.read_lock().await;
+ while *count == 0 {
+ count = cv.wait_read(count).await;
+ }
+ let _ = unsafe { ptr::read_volatile(&*count as *const usize) };
+ }
+
+ async fn decrement(mu: &Arc<Mutex<usize>>, cv: &Arc<Condvar>) {
+ let mut count = mu.lock().await;
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+ *count -= 1;
+ }
+
+ async fn increment(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>, done: Sender<()>) {
+ for _ in 0..TASKS * OBSERVERS * ITERATIONS {
+ *mu.lock().await += 1;
+ cv.notify_one();
+ }
+
+ done.send(()).expect("Failed to send completion message");
+ }
+
+ async fn observe_either(
+ mu: Arc<Mutex<usize>>,
+ cv: Arc<Condvar>,
+ alt_mu: Arc<Mutex<usize>>,
+ alt_cv: Arc<Condvar>,
+ done: Sender<()>,
+ ) {
+ for _ in 0..ITERATIONS {
+ select! {
+ () = observe(&mu, &cv).fuse() => {},
+ () = observe(&alt_mu, &alt_cv).fuse() => {},
+ }
+ }
+
+ done.send(()).expect("Failed to send completion message");
+ }
+
+ async fn decrement_either(
+ mu: Arc<Mutex<usize>>,
+ cv: Arc<Condvar>,
+ alt_mu: Arc<Mutex<usize>>,
+ alt_cv: Arc<Condvar>,
+ done: Sender<()>,
+ ) {
+ for _ in 0..ITERATIONS {
+ select! {
+ () = decrement(&mu, &cv).fuse() => {},
+ () = decrement(&alt_mu, &alt_cv).fuse() => {},
+ }
+ }
+
+ done.send(()).expect("Failed to send completion message");
+ }
+
+ let ex = ThreadPool::new().expect("Failed to create ThreadPool");
+
+ let mu = Arc::new(Mutex::new(0usize));
+ let alt_mu = Arc::new(Mutex::new(0usize));
+
+ let cv = Arc::new(Condvar::new());
+ let alt_cv = Arc::new(Condvar::new());
+
+ let (tx, rx) = channel();
+ for _ in 0..TASKS {
+ ex.spawn_ok(decrement_either(
+ Arc::clone(&mu),
+ Arc::clone(&cv),
+ Arc::clone(&alt_mu),
+ Arc::clone(&alt_cv),
+ tx.clone(),
+ ));
+ }
+
+ for _ in 0..OBSERVERS {
+ ex.spawn_ok(observe_either(
+ Arc::clone(&mu),
+ Arc::clone(&cv),
+ Arc::clone(&alt_mu),
+ Arc::clone(&alt_cv),
+ tx.clone(),
+ ));
+ }
+
+ ex.spawn_ok(increment(Arc::clone(&mu), Arc::clone(&cv), tx.clone()));
+ ex.spawn_ok(increment(Arc::clone(&alt_mu), Arc::clone(&alt_cv), tx));
+
+ for _ in 0..TASKS + OBSERVERS + 2 {
+ if let Err(e) = rx.recv_timeout(Duration::from_secs(20)) {
+ panic!("Error while waiting for threads to complete: {}", e);
+ }
+ }
+
+ assert_eq!(
+ *block_on(mu.read_lock()) + *block_on(alt_mu.read_lock()),
+ (TASKS * OBSERVERS * ITERATIONS * 2) - (TASKS * ITERATIONS)
+ );
+ assert_eq!(cv.state.load(Ordering::Relaxed), 0);
+ assert_eq!(alt_cv.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn notify_all_with_cancel() {
+ const TASKS: usize = 17;
+ const ITERATIONS: usize = 103;
+
+ async fn decrement(mu: &Arc<Mutex<usize>>, cv: &Arc<Condvar>) {
+ let mut count = mu.lock().await;
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+ *count -= 1;
+ }
+
+ async fn increment(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>, done: Sender<()>) {
+ for _ in 0..TASKS * ITERATIONS {
+ *mu.lock().await += 1;
+ cv.notify_all();
+ }
+
+ done.send(()).expect("Failed to send completion message");
+ }
+
+ async fn decrement_either(
+ mu: Arc<Mutex<usize>>,
+ cv: Arc<Condvar>,
+ alt_mu: Arc<Mutex<usize>>,
+ alt_cv: Arc<Condvar>,
+ done: Sender<()>,
+ ) {
+ for _ in 0..ITERATIONS {
+ select! {
+ () = decrement(&mu, &cv).fuse() => {},
+ () = decrement(&alt_mu, &alt_cv).fuse() => {},
+ }
+ }
+
+ done.send(()).expect("Failed to send completion message");
+ }
+
+ let ex = ThreadPool::new().expect("Failed to create ThreadPool");
+
+ let mu = Arc::new(Mutex::new(0usize));
+ let alt_mu = Arc::new(Mutex::new(0usize));
+
+ let cv = Arc::new(Condvar::new());
+ let alt_cv = Arc::new(Condvar::new());
+
+ let (tx, rx) = channel();
+ for _ in 0..TASKS {
+ ex.spawn_ok(decrement_either(
+ Arc::clone(&mu),
+ Arc::clone(&cv),
+ Arc::clone(&alt_mu),
+ Arc::clone(&alt_cv),
+ tx.clone(),
+ ));
+ }
+
+ ex.spawn_ok(increment(Arc::clone(&mu), Arc::clone(&cv), tx.clone()));
+ ex.spawn_ok(increment(Arc::clone(&alt_mu), Arc::clone(&alt_cv), tx));
+
+ for _ in 0..TASKS + 2 {
+ if let Err(e) = rx.recv_timeout(Duration::from_secs(10)) {
+ panic!("Error while waiting for threads to complete: {}", e);
+ }
+ }
+
+ assert_eq!(
+ *block_on(mu.read_lock()) + *block_on(alt_mu.read_lock()),
+ TASKS * ITERATIONS
+ );
+ assert_eq!(cv.state.load(Ordering::Relaxed), 0);
+ assert_eq!(alt_cv.state.load(Ordering::Relaxed), 0);
+ }
+ #[test]
+ fn notify_all() {
+ const THREADS: usize = 13;
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+ let (tx, rx) = channel();
+
+ let mut threads = Vec::with_capacity(THREADS);
+ for _ in 0..THREADS {
+ let mu2 = mu.clone();
+ let cv2 = cv.clone();
+ let tx2 = tx.clone();
+
+ threads.push(thread::spawn(move || {
+ let mut count = block_on(mu2.lock());
+ *count += 1;
+ if *count == THREADS {
+ tx2.send(()).unwrap();
+ }
+
+ while *count != 0 {
+ count = block_on(cv2.wait(count));
+ }
+ }));
+ }
+
+ mem::drop(tx);
+
+ // Wait till all threads have started.
+ rx.recv_timeout(Duration::from_secs(5)).unwrap();
+
+ let mut count = block_on(mu.lock());
+ *count = 0;
+ mem::drop(count);
+ cv.notify_all();
+
+ for t in threads {
+ t.join().unwrap();
+ }
+ }
+
+ #[test]
+ fn notify_all_single_thread_async() {
+ const TASKS: usize = 13;
+
+ async fn reset(mu: Rc<Mutex<usize>>, cv: Rc<Condvar>) {
+ let mut count = mu.lock().await;
+ *count = 0;
+ cv.notify_all();
+ }
+
+ async fn watcher(mu: Rc<Mutex<usize>>, cv: Rc<Condvar>, spawner: LocalSpawner) {
+ let mut count = mu.lock().await;
+ *count += 1;
+ if *count == TASKS {
+ spawner
+ .spawn_local(reset(mu.clone(), cv.clone()))
+ .expect("Failed to spawn reset task");
+ }
+
+ while *count != 0 {
+ count = cv.wait(count).await;
+ }
+ }
+
+ let mut ex = LocalPool::new();
+ let spawner = ex.spawner();
+
+ let mu = Rc::new(Mutex::new(0));
+ let cv = Rc::new(Condvar::new());
+
+ for _ in 0..TASKS {
+ spawner
+ .spawn_local(watcher(mu.clone(), cv.clone(), spawner.clone()))
+ .expect("Failed to spawn watcher task");
+ }
+
+ ex.run();
+ }
+
+ #[test]
+ fn notify_all_multi_thread_async() {
+ const TASKS: usize = 13;
+
+ async fn reset(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.lock().await;
+ *count = 0;
+ cv.notify_all();
+ }
+
+ async fn watcher(
+ mu: Arc<Mutex<usize>>,
+ cv: Arc<Condvar>,
+ pool: ThreadPool,
+ tx: Sender<()>,
+ ) {
+ let mut count = mu.lock().await;
+ *count += 1;
+ if *count == TASKS {
+ pool.spawn_ok(reset(mu.clone(), cv.clone()));
+ }
+
+ while *count != 0 {
+ count = cv.wait(count).await;
+ }
+
+ tx.send(()).expect("Failed to send completion notification");
+ }
+
+ let pool = ThreadPool::new().expect("Failed to create ThreadPool");
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let (tx, rx) = channel();
+ for _ in 0..TASKS {
+ pool.spawn_ok(watcher(mu.clone(), cv.clone(), pool.clone(), tx.clone()));
+ }
+
+ for _ in 0..TASKS {
+ rx.recv_timeout(Duration::from_secs(5))
+ .expect("Failed to receive completion notification");
+ }
+ }
+
+ #[test]
+ fn wake_all_readers() {
+ async fn read(mu: Arc<Mutex<bool>>, cv: Arc<Condvar>) {
+ let mut ready = mu.read_lock().await;
+ while !*ready {
+ ready = cv.wait_read(ready).await;
+ }
+ }
+
+ let mu = Arc::new(Mutex::new(false));
+ let cv = Arc::new(Condvar::new());
+ let mut readers = [
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ ];
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ // First have all the readers wait on the Condvar.
+ for r in &mut readers {
+ if let Poll::Ready(()) = r.as_mut().poll(&mut cx) {
+ panic!("reader unexpectedly ready");
+ }
+ }
+
+ assert_eq!(cv.state.load(Ordering::Relaxed) & HAS_WAITERS, HAS_WAITERS);
+
+ // Now make the condition true and notify the condvar. Even though we will call notify_one,
+ // all the readers should be woken up.
+ *block_on(mu.lock()) = true;
+ cv.notify_one();
+
+ assert_eq!(cv.state.load(Ordering::Relaxed), 0);
+
+ // All readers should now be able to complete.
+ for r in &mut readers {
+ if r.as_mut().poll(&mut cx).is_pending() {
+ panic!("reader unable to complete");
+ }
+ }
+ }
+
+ #[test]
+ fn cancel_before_notify() {
+ async fn dec(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.lock().await;
+
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+
+ *count -= 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let mut fut1 = Box::pin(dec(mu.clone(), cv.clone()));
+ let mut fut2 = Box::pin(dec(mu.clone(), cv.clone()));
+
+ if let Poll::Ready(()) = fut1.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ if let Poll::Ready(()) = fut2.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ assert_eq!(cv.state.load(Ordering::Relaxed) & HAS_WAITERS, HAS_WAITERS);
+
+ *block_on(mu.lock()) = 2;
+ // Drop fut1 before notifying the cv.
+ mem::drop(fut1);
+ cv.notify_one();
+
+ // fut2 should now be ready to complete.
+ assert_eq!(cv.state.load(Ordering::Relaxed), 0);
+
+ if fut2.as_mut().poll(&mut cx).is_pending() {
+ panic!("future unable to complete");
+ }
+
+ assert_eq!(*block_on(mu.lock()), 1);
+ }
+
+ #[test]
+ fn cancel_after_notify_one() {
+ async fn dec(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.lock().await;
+
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+
+ *count -= 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let mut fut1 = Box::pin(dec(mu.clone(), cv.clone()));
+ let mut fut2 = Box::pin(dec(mu.clone(), cv.clone()));
+
+ if let Poll::Ready(()) = fut1.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ if let Poll::Ready(()) = fut2.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ assert_eq!(cv.state.load(Ordering::Relaxed) & HAS_WAITERS, HAS_WAITERS);
+
+ *block_on(mu.lock()) = 2;
+ cv.notify_one();
+
+ // fut1 should now be ready to complete. Drop it before polling. This should wake up fut2.
+ mem::drop(fut1);
+ assert_eq!(cv.state.load(Ordering::Relaxed), 0);
+
+ if fut2.as_mut().poll(&mut cx).is_pending() {
+ panic!("future unable to complete");
+ }
+
+ assert_eq!(*block_on(mu.lock()), 1);
+ }
+
+ #[test]
+ fn cancel_after_notify_all() {
+ async fn dec(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.lock().await;
+
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+
+ *count -= 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let mut fut1 = Box::pin(dec(mu.clone(), cv.clone()));
+ let mut fut2 = Box::pin(dec(mu.clone(), cv.clone()));
+
+ if let Poll::Ready(()) = fut1.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ if let Poll::Ready(()) = fut2.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ assert_eq!(cv.state.load(Ordering::Relaxed) & HAS_WAITERS, HAS_WAITERS);
+
+ let mut count = block_on(mu.lock());
+ *count = 2;
+
+ // Notify the cv while holding the lock. This should wake up both waiters.
+ cv.notify_all();
+ assert_eq!(cv.state.load(Ordering::Relaxed), 0);
+
+ mem::drop(count);
+
+ mem::drop(fut1);
+
+ if fut2.as_mut().poll(&mut cx).is_pending() {
+ panic!("future unable to complete");
+ }
+
+ assert_eq!(*block_on(mu.lock()), 1);
+ }
+
+ #[test]
+ fn timed_wait() {
+ async fn wait_deadline(
+ mu: Arc<Mutex<usize>>,
+ cv: Arc<Condvar>,
+ timeout: oneshot::Receiver<()>,
+ ) {
+ let mut count = mu.lock().await;
+
+ if *count == 0 {
+ let mut rx = timeout.fuse();
+
+ while *count == 0 {
+ select! {
+ res = rx => {
+ if let Err(e) = res {
+ panic!("Error while receiving timeout notification: {}", e);
+ }
+
+ return;
+ },
+ c = cv.wait(count).fuse() => count = c,
+ }
+ }
+ }
+
+ *count += 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let (tx, rx) = oneshot::channel();
+ let mut wait = Box::pin(wait_deadline(mu.clone(), cv.clone(), rx));
+
+ if let Poll::Ready(()) = wait.as_mut().poll(&mut cx) {
+ panic!("wait_deadline unexpectedly ready");
+ }
+
+ assert_eq!(cv.state.load(Ordering::Relaxed), HAS_WAITERS);
+
+ // Signal the channel, which should cancel the wait.
+ tx.send(()).expect("Failed to send wakeup");
+
+ // Wait for the timer to run out.
+ if wait.as_mut().poll(&mut cx).is_pending() {
+ panic!("wait_deadline unable to complete in time");
+ }
+
+ assert_eq!(cv.state.load(Ordering::Relaxed), 0);
+ assert_eq!(*block_on(mu.lock()), 0);
+ }
+}
diff --git a/common/cros_asyncv2/src/sync/mu.rs b/common/cros_asyncv2/src/sync/mu.rs
new file mode 100644
index 000000000..b4e41f77f
--- /dev/null
+++ b/common/cros_asyncv2/src/sync/mu.rs
@@ -0,0 +1,2293 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::UnsafeCell;
+use std::hint;
+use std::mem;
+use std::ops::{Deref, DerefMut};
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::Arc;
+use std::thread::yield_now;
+
+use crate::sync::waiter::{Kind as WaiterKind, Waiter, WaiterAdapter, WaiterList, WaitingFor};
+
+// Set when the mutex is exclusively locked.
+const LOCKED: usize = 1 << 0;
+// Set when there are one or more threads waiting to acquire the lock.
+const HAS_WAITERS: usize = 1 << 1;
+// Set when a thread has been woken up from the wait queue. Cleared when that thread either acquires
+// the lock or adds itself back into the wait queue. Used to prevent unnecessary wake ups when a
+// thread has been removed from the wait queue but has not gotten CPU time yet.
+const DESIGNATED_WAKER: usize = 1 << 2;
+// Used to provide exclusive access to the `waiters` field in `Mutex`. Should only be held while
+// modifying the waiter list.
+const SPINLOCK: usize = 1 << 3;
+// Set when a thread that wants an exclusive lock adds itself to the wait queue. New threads
+// attempting to acquire a shared lock will be preventing from getting it when this bit is set.
+// However, this bit is ignored once a thread has gone through the wait queue at least once.
+const WRITER_WAITING: usize = 1 << 4;
+// Set when a thread has gone through the wait queue many times but has failed to acquire the lock
+// every time it is woken up. When this bit is set, all other threads are prevented from acquiring
+// the lock until the thread that set the `LONG_WAIT` bit has acquired the lock.
+const LONG_WAIT: usize = 1 << 5;
+// The bit that is added to the mutex state in order to acquire a shared lock. Since more than one
+// thread can acquire a shared lock, we cannot use a single bit. Instead we use all the remaining
+// bits in the state to track the number of threads that have acquired a shared lock.
+const READ_LOCK: usize = 1 << 8;
+// Mask used for checking if any threads currently hold a shared lock.
+const READ_MASK: usize = !0xff;
+
+// The number of times the thread should just spin and attempt to re-acquire the lock.
+const SPIN_THRESHOLD: usize = 7;
+
+// The number of times the thread needs to go through the wait queue before it sets the `LONG_WAIT`
+// bit and forces all other threads to wait for it to acquire the lock. This value is set relatively
+// high so that we don't lose the benefit of having running threads unless it is absolutely
+// necessary.
+const LONG_WAIT_THRESHOLD: usize = 19;
+
+// Common methods between shared and exclusive locks.
+trait Kind {
+ // The bits that must be zero for the thread to acquire this kind of lock. If any of these bits
+ // are not zero then the thread will first spin and retry a few times before adding itself to
+ // the wait queue.
+ fn zero_to_acquire() -> usize;
+
+ // The bit that must be added in order to acquire this kind of lock. This should either be
+ // `LOCKED` or `READ_LOCK`.
+ fn add_to_acquire() -> usize;
+
+ // The bits that should be set when a thread adds itself to the wait queue while waiting to
+ // acquire this kind of lock.
+ fn set_when_waiting() -> usize;
+
+ // The bits that should be cleared when a thread acquires this kind of lock.
+ fn clear_on_acquire() -> usize;
+
+ // The waiter that a thread should use when waiting to acquire this kind of lock.
+ fn new_waiter(raw: &RawMutex) -> Arc<Waiter>;
+}
+
+// A lock type for shared read-only access to the data. More than one thread may hold this kind of
+// lock simultaneously.
+struct Shared;
+
+impl Kind for Shared {
+ fn zero_to_acquire() -> usize {
+ LOCKED | WRITER_WAITING | LONG_WAIT
+ }
+
+ fn add_to_acquire() -> usize {
+ READ_LOCK
+ }
+
+ fn set_when_waiting() -> usize {
+ 0
+ }
+
+ fn clear_on_acquire() -> usize {
+ 0
+ }
+
+ fn new_waiter(raw: &RawMutex) -> Arc<Waiter> {
+ Arc::new(Waiter::new(
+ WaiterKind::Shared,
+ cancel_waiter,
+ raw as *const RawMutex as usize,
+ WaitingFor::Mutex,
+ ))
+ }
+}
+
+// A lock type for mutually exclusive read-write access to the data. Only one thread can hold this
+// kind of lock at a time.
+struct Exclusive;
+
+impl Kind for Exclusive {
+ fn zero_to_acquire() -> usize {
+ LOCKED | READ_MASK | LONG_WAIT
+ }
+
+ fn add_to_acquire() -> usize {
+ LOCKED
+ }
+
+ fn set_when_waiting() -> usize {
+ WRITER_WAITING
+ }
+
+ fn clear_on_acquire() -> usize {
+ WRITER_WAITING
+ }
+
+ fn new_waiter(raw: &RawMutex) -> Arc<Waiter> {
+ Arc::new(Waiter::new(
+ WaiterKind::Exclusive,
+ cancel_waiter,
+ raw as *const RawMutex as usize,
+ WaitingFor::Mutex,
+ ))
+ }
+}
+
+// Scan `waiters` and return the ones that should be woken up. Also returns any bits that should be
+// set in the mutex state when the current thread releases the spin lock protecting the waiter list.
+//
+// If the first waiter is trying to acquire a shared lock, then all waiters in the list that are
+// waiting for a shared lock are also woken up. If any waiters waiting for an exclusive lock are
+// found when iterating through the list, then the returned `usize` contains the `WRITER_WAITING`
+// bit, which should be set when the thread releases the spin lock.
+//
+// If the first waiter is trying to acquire an exclusive lock, then only that waiter is returned and
+// no bits are set in the returned `usize`.
+fn get_wake_list(waiters: &mut WaiterList) -> (WaiterList, usize) {
+ let mut to_wake = WaiterList::new(WaiterAdapter::new());
+ let mut set_on_release = 0;
+ let mut cursor = waiters.front_mut();
+
+ let mut waking_readers = false;
+ while let Some(w) = cursor.get() {
+ match w.kind() {
+ WaiterKind::Exclusive if !waking_readers => {
+ // This is the first waiter and it's a writer. No need to check the other waiters.
+ let waiter = cursor.remove().unwrap();
+ waiter.set_waiting_for(WaitingFor::None);
+ to_wake.push_back(waiter);
+ break;
+ }
+
+ WaiterKind::Shared => {
+ // This is a reader and the first waiter in the list was not a writer so wake up all
+ // the readers in the wait list.
+ let waiter = cursor.remove().unwrap();
+ waiter.set_waiting_for(WaitingFor::None);
+ to_wake.push_back(waiter);
+ waking_readers = true;
+ }
+
+ WaiterKind::Exclusive => {
+ // We found a writer while looking for more readers to wake up. Set the
+ // `WRITER_WAITING` bit to prevent any new readers from acquiring the lock. All
+ // readers currently in the wait list will ignore this bit since they already waited
+ // once.
+ set_on_release |= WRITER_WAITING;
+ cursor.move_next();
+ }
+ }
+ }
+
+ (to_wake, set_on_release)
+}
+
+#[inline]
+fn cpu_relax(iterations: usize) {
+ for _ in 0..iterations {
+ hint::spin_loop();
+ }
+}
+
+pub(crate) struct RawMutex {
+ state: AtomicUsize,
+ waiters: UnsafeCell<WaiterList>,
+}
+
+impl RawMutex {
+ pub fn new() -> RawMutex {
+ RawMutex {
+ state: AtomicUsize::new(0),
+ waiters: UnsafeCell::new(WaiterList::new(WaiterAdapter::new())),
+ }
+ }
+
+ #[inline]
+ pub async fn lock(&self) {
+ match self
+ .state
+ .compare_exchange_weak(0, LOCKED, Ordering::Acquire, Ordering::Relaxed)
+ {
+ Ok(_) => {}
+ Err(oldstate) => {
+ // If any bits that should be zero are not zero or if we fail to acquire the lock
+ // with a single compare_exchange then go through the slow path.
+ if (oldstate & Exclusive::zero_to_acquire()) != 0
+ || self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ (oldstate + Exclusive::add_to_acquire())
+ & !Exclusive::clear_on_acquire(),
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ self.lock_slow::<Exclusive>(0, 0).await;
+ }
+ }
+ }
+ }
+
+ #[inline]
+ pub async fn read_lock(&self) {
+ match self
+ .state
+ .compare_exchange_weak(0, READ_LOCK, Ordering::Acquire, Ordering::Relaxed)
+ {
+ Ok(_) => {}
+ Err(oldstate) => {
+ if (oldstate & Shared::zero_to_acquire()) != 0
+ || self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ (oldstate + Shared::add_to_acquire()) & !Shared::clear_on_acquire(),
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ self.lock_slow::<Shared>(0, 0).await;
+ }
+ }
+ }
+ }
+
+ // Slow path for acquiring the lock. `clear` should contain any bits that need to be cleared
+ // when the lock is acquired. Any bits set in `zero_mask` are cleared from the bits returned by
+ // `K::zero_to_acquire()`.
+ #[cold]
+ async fn lock_slow<K: Kind>(&self, mut clear: usize, zero_mask: usize) {
+ let mut zero_to_acquire = K::zero_to_acquire() & !zero_mask;
+
+ let mut spin_count = 0;
+ let mut wait_count = 0;
+ let mut waiter = None;
+ loop {
+ let oldstate = self.state.load(Ordering::Relaxed);
+ // If all the bits in `zero_to_acquire` are actually zero then try to acquire the lock
+ // directly.
+ if (oldstate & zero_to_acquire) == 0 {
+ if self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ (oldstate + K::add_to_acquire()) & !(clear | K::clear_on_acquire()),
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_ok()
+ {
+ return;
+ }
+ } else if (oldstate & SPINLOCK) == 0 {
+ // The mutex is locked and the spin lock is available. Try to add this thread
+ // to the waiter queue.
+ let w = waiter.get_or_insert_with(|| K::new_waiter(self));
+ w.reset(WaitingFor::Mutex);
+
+ if self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ (oldstate | SPINLOCK | HAS_WAITERS | K::set_when_waiting()) & !clear,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_ok()
+ {
+ let mut set_on_release = 0;
+
+ // Safe because we have acquired the spin lock and it provides exclusive
+ // access to the waiter queue.
+ if wait_count < LONG_WAIT_THRESHOLD {
+ // Add the waiter to the back of the queue.
+ unsafe { (*self.waiters.get()).push_back(w.clone()) };
+ } else {
+ // This waiter has gone through the queue too many times. Put it in the
+ // front of the queue and block all other threads from acquiring the lock
+ // until this one has acquired it at least once.
+ unsafe { (*self.waiters.get()).push_front(w.clone()) };
+
+ // Set the LONG_WAIT bit to prevent all other threads from acquiring the
+ // lock.
+ set_on_release |= LONG_WAIT;
+
+ // Make sure we clear the LONG_WAIT bit when we do finally get the lock.
+ clear |= LONG_WAIT;
+
+ // Since we set the LONG_WAIT bit we shouldn't allow that bit to prevent us
+ // from acquiring the lock.
+ zero_to_acquire &= !LONG_WAIT;
+ }
+
+ // Release the spin lock.
+ let mut state = oldstate;
+ loop {
+ match self.state.compare_exchange_weak(
+ state,
+ (state | set_on_release) & !SPINLOCK,
+ Ordering::Release,
+ Ordering::Relaxed,
+ ) {
+ Ok(_) => break,
+ Err(w) => state = w,
+ }
+ }
+
+ // Now wait until we are woken.
+ w.wait().await;
+
+ // The `DESIGNATED_WAKER` bit gets set when this thread is woken up by the
+ // thread that originally held the lock. While this bit is set, no other waiters
+ // will be woken up so it's important to clear it the next time we try to
+ // acquire the main lock or the spin lock.
+ clear |= DESIGNATED_WAKER;
+
+ // Now that the thread has waited once, we no longer care if there is a writer
+ // waiting. Only the limits of mutual exclusion can prevent us from acquiring
+ // the lock.
+ zero_to_acquire &= !WRITER_WAITING;
+
+ // Reset the spin count since we just went through the wait queue.
+ spin_count = 0;
+
+ // Increment the wait count since we went through the wait queue.
+ wait_count += 1;
+
+ // Skip the `cpu_relax` below.
+ continue;
+ }
+ }
+
+ // Both the lock and the spin lock are held by one or more other threads. First, we'll
+ // spin a few times in case we can acquire the lock or the spin lock. If that fails then
+ // we yield because we might be preventing the threads that do hold the 2 locks from
+ // getting cpu time.
+ if spin_count < SPIN_THRESHOLD {
+ cpu_relax(1 << spin_count);
+ spin_count += 1;
+ } else {
+ yield_now();
+ }
+ }
+ }
+
+ #[inline]
+ pub fn unlock(&self) {
+ // Fast path, if possible. We can directly clear the locked bit since we have exclusive
+ // access to the mutex.
+ let oldstate = self.state.fetch_sub(LOCKED, Ordering::Release);
+
+ // Panic if we just tried to unlock a mutex that wasn't held by this thread. This shouldn't
+ // really be possible since `unlock` is not a public method.
+ debug_assert_eq!(
+ oldstate & READ_MASK,
+ 0,
+ "`unlock` called on mutex held in read-mode"
+ );
+ debug_assert_ne!(
+ oldstate & LOCKED,
+ 0,
+ "`unlock` called on mutex not held in write-mode"
+ );
+
+ if (oldstate & HAS_WAITERS) != 0 && (oldstate & DESIGNATED_WAKER) == 0 {
+ // The oldstate has waiters but no designated waker has been chosen yet.
+ self.unlock_slow();
+ }
+ }
+
+ #[inline]
+ pub fn read_unlock(&self) {
+ // Fast path, if possible. We can directly subtract the READ_LOCK bit since we had
+ // previously added it.
+ let oldstate = self.state.fetch_sub(READ_LOCK, Ordering::Release);
+
+ debug_assert_eq!(
+ oldstate & LOCKED,
+ 0,
+ "`read_unlock` called on mutex held in write-mode"
+ );
+ debug_assert_ne!(
+ oldstate & READ_MASK,
+ 0,
+ "`read_unlock` called on mutex not held in read-mode"
+ );
+
+ if (oldstate & HAS_WAITERS) != 0
+ && (oldstate & DESIGNATED_WAKER) == 0
+ && (oldstate & READ_MASK) == READ_LOCK
+ {
+ // There are waiters, no designated waker has been chosen yet, and the last reader is
+ // unlocking so we have to take the slow path.
+ self.unlock_slow();
+ }
+ }
+
+ #[cold]
+ fn unlock_slow(&self) {
+ let mut spin_count = 0;
+
+ loop {
+ let oldstate = self.state.load(Ordering::Relaxed);
+ if (oldstate & HAS_WAITERS) == 0 || (oldstate & DESIGNATED_WAKER) != 0 {
+ // No more waiters or a designated waker has been chosen. Nothing left for us to do.
+ return;
+ } else if (oldstate & SPINLOCK) == 0 {
+ // The spin lock is not held by another thread. Try to acquire it. Also set the
+ // `DESIGNATED_WAKER` bit since we are likely going to wake up one or more threads.
+ if self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ oldstate | SPINLOCK | DESIGNATED_WAKER,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_ok()
+ {
+ // Acquired the spinlock. Try to wake a waiter. We may also end up wanting to
+ // clear the HAS_WAITER and DESIGNATED_WAKER bits so start collecting the bits
+ // to be cleared.
+ let mut clear = SPINLOCK;
+
+ // Safe because the spinlock guarantees exclusive access to the waiter list and
+ // the reference does not escape this function.
+ let waiters = unsafe { &mut *self.waiters.get() };
+ let (wake_list, set_on_release) = get_wake_list(waiters);
+
+ // If the waiter list is now empty, clear the HAS_WAITERS bit.
+ if waiters.is_empty() {
+ clear |= HAS_WAITERS;
+ }
+
+ if wake_list.is_empty() {
+ // Since we are not going to wake any waiters clear the DESIGNATED_WAKER bit
+ // that we set when we acquired the spin lock.
+ clear |= DESIGNATED_WAKER;
+ }
+
+ // Release the spin lock and clear any other bits as necessary. Also, set any
+ // bits returned by `get_wake_list`. For now, this is just the `WRITER_WAITING`
+ // bit, which needs to be set when we are waking up a bunch of readers and there
+ // are still writers in the wait queue. This will prevent any readers that
+ // aren't in `wake_list` from acquiring the read lock.
+ let mut state = oldstate;
+ loop {
+ match self.state.compare_exchange_weak(
+ state,
+ (state | set_on_release) & !clear,
+ Ordering::Release,
+ Ordering::Relaxed,
+ ) {
+ Ok(_) => break,
+ Err(w) => state = w,
+ }
+ }
+
+ // Now wake the waiters, if any.
+ for w in wake_list {
+ w.wake();
+ }
+
+ // We're done.
+ return;
+ }
+ }
+
+ // Spin and try again. It's ok to block here as we have already released the lock.
+ if spin_count < SPIN_THRESHOLD {
+ cpu_relax(1 << spin_count);
+ spin_count += 1;
+ } else {
+ yield_now();
+ }
+ }
+ }
+
+ fn cancel_waiter(&self, waiter: &Waiter, wake_next: bool) {
+ let mut oldstate = self.state.load(Ordering::Relaxed);
+ while oldstate & SPINLOCK != 0
+ || self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ oldstate | SPINLOCK,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ hint::spin_loop();
+ oldstate = self.state.load(Ordering::Relaxed);
+ }
+
+ // Safe because the spin lock provides exclusive access and the reference does not escape
+ // this function.
+ let waiters = unsafe { &mut *self.waiters.get() };
+
+ let mut clear = SPINLOCK;
+
+ // If we are about to remove the first waiter in the wait list, then clear the LONG_WAIT
+ // bit. Also clear the bit if we are going to be waking some other waiters. In this case the
+ // waiter that set the bit may have already been removed from the waiter list (and could be
+ // the one that is currently being dropped). If it is still in the waiter list then clearing
+ // this bit may starve it for one more iteration through the lock_slow() loop, whereas not
+ // clearing this bit could cause a deadlock if the waiter that set it is the one that is
+ // being dropped.
+ if wake_next
+ || waiters
+ .front()
+ .get()
+ .map(|front| std::ptr::eq(front, waiter))
+ .unwrap_or(false)
+ {
+ clear |= LONG_WAIT;
+ }
+
+ let waiting_for = waiter.is_waiting_for();
+
+ // Don't drop the old waiter while holding the spin lock.
+ let old_waiter = if waiter.is_linked() && waiting_for == WaitingFor::Mutex {
+ // We know that the waiter is still linked and is waiting for the mutex, which
+ // guarantees that it is still linked into `self.waiters`.
+ let mut cursor = unsafe { waiters.cursor_mut_from_ptr(waiter as *const Waiter) };
+ cursor.remove()
+ } else {
+ None
+ };
+
+ let (wake_list, set_on_release) = if wake_next || waiting_for == WaitingFor::None {
+ // Either the waiter was already woken or it's been removed from the mutex's waiter
+ // list and is going to be woken. Either way, we need to wake up another thread.
+ get_wake_list(waiters)
+ } else {
+ (WaiterList::new(WaiterAdapter::new()), 0)
+ };
+
+ if waiters.is_empty() {
+ clear |= HAS_WAITERS;
+ }
+
+ if wake_list.is_empty() {
+ // We're not waking any other threads so clear the DESIGNATED_WAKER bit. In the worst
+ // case this leads to an additional thread being woken up but we risk a deadlock if we
+ // don't clear it.
+ clear |= DESIGNATED_WAKER;
+ }
+
+ if let WaiterKind::Exclusive = waiter.kind() {
+ // The waiter being dropped is a writer so clear the writer waiting bit for now. If we
+ // found more writers in the list while fetching waiters to wake up then this bit will
+ // be set again via `set_on_release`.
+ clear |= WRITER_WAITING;
+ }
+
+ while self
+ .state
+ .compare_exchange_weak(
+ oldstate,
+ (oldstate & !clear) | set_on_release,
+ Ordering::Release,
+ Ordering::Relaxed,
+ )
+ .is_err()
+ {
+ hint::spin_loop();
+ oldstate = self.state.load(Ordering::Relaxed);
+ }
+
+ for w in wake_list {
+ w.wake();
+ }
+
+ mem::drop(old_waiter);
+ }
+}
+
+unsafe impl Send for RawMutex {}
+unsafe impl Sync for RawMutex {}
+
+fn cancel_waiter(raw: usize, waiter: &Waiter, wake_next: bool) {
+ let raw_mutex = raw as *const RawMutex;
+
+ // Safe because the thread that owns the waiter that is being canceled must
+ // also own a reference to the mutex, which ensures that this pointer is
+ // valid.
+ unsafe { (*raw_mutex).cancel_waiter(waiter, wake_next) }
+}
+
+/// A high-level primitive that provides safe, mutable access to a shared resource.
+///
+/// Unlike more traditional mutexes, `Mutex` can safely provide both shared, immutable access (via
+/// `read_lock()`) as well as exclusive, mutable access (via `lock()`) to an underlying resource
+/// with no loss of performance.
+///
+/// # Poisoning
+///
+/// `Mutex` does not support lock poisoning so if a thread panics while holding the lock, the
+/// poisoned data will be accessible by other threads in your program. If you need to guarantee that
+/// other threads cannot access poisoned data then you may wish to wrap this `Mutex` inside another
+/// type that provides the poisoning feature. See the implementation of `std::sync::Mutex` for an
+/// example of this.
+///
+///
+/// # Fairness
+///
+/// This `Mutex` implementation does not guarantee that threads will acquire the lock in the same
+/// order that they call `lock()` or `read_lock()`. However it will attempt to prevent long-term
+/// starvation: if a thread repeatedly fails to acquire the lock beyond a threshold then all other
+/// threads will fail to acquire the lock until the starved thread has acquired it.
+///
+/// Similarly, this `Mutex` will attempt to balance reader and writer threads: once there is a
+/// writer thread waiting to acquire the lock no new reader threads will be allowed to acquire it.
+/// However, any reader threads that were already waiting will still be allowed to acquire it.
+///
+/// # Examples
+///
+/// ```edition2018
+/// use std::sync::Arc;
+/// use std::thread;
+/// use std::sync::mpsc::channel;
+///
+/// use cros_async::{block_on, sync::Mutex};
+///
+/// const N: usize = 10;
+///
+/// // Spawn a few threads to increment a shared variable (non-atomically), and
+/// // let the main thread know once all increments are done.
+/// //
+/// // Here we're using an Arc to share memory among threads, and the data inside
+/// // the Arc is protected with a mutex.
+/// let data = Arc::new(Mutex::new(0));
+///
+/// let (tx, rx) = channel();
+/// for _ in 0..N {
+/// let (data, tx) = (Arc::clone(&data), tx.clone());
+/// thread::spawn(move || {
+/// // The shared state can only be accessed once the lock is held.
+/// // Our non-atomic increment is safe because we're the only thread
+/// // which can access the shared state when the lock is held.
+/// let mut data = block_on(data.lock());
+/// *data += 1;
+/// if *data == N {
+/// tx.send(()).unwrap();
+/// }
+/// // the lock is unlocked here when `data` goes out of scope.
+/// });
+/// }
+///
+/// rx.recv().unwrap();
+/// ```
+#[repr(align(128))]
+pub struct Mutex<T: ?Sized> {
+ raw: RawMutex,
+ value: UnsafeCell<T>,
+}
+
+impl<T> Mutex<T> {
+ /// Create a new, unlocked `Mutex` ready for use.
+ pub fn new(v: T) -> Mutex<T> {
+ Mutex {
+ raw: RawMutex::new(),
+ value: UnsafeCell::new(v),
+ }
+ }
+
+ /// Consume the `Mutex` and return the contained value. This method does not perform any locking
+ /// as the compiler will guarantee that there are no other references to `self` and the caller
+ /// owns the `Mutex`.
+ pub fn into_inner(self) -> T {
+ // Don't need to acquire the lock because the compiler guarantees that there are
+ // no references to `self`.
+ self.value.into_inner()
+ }
+}
+
+impl<T: ?Sized> Mutex<T> {
+ /// Acquires exclusive, mutable access to the resource protected by the `Mutex`, blocking the
+ /// current thread until it is able to do so. Upon returning, the current thread will be the
+ /// only thread with access to the resource. The `Mutex` will be released when the returned
+ /// `MutexGuard` is dropped.
+ ///
+ /// Calling `lock()` while holding a `MutexGuard` or a `MutexReadGuard` will cause a deadlock.
+ ///
+ /// Callers that are not in an async context may wish to use the `block_on` method to block the
+ /// thread until the `Mutex` is acquired.
+ #[inline]
+ pub async fn lock(&self) -> MutexGuard<'_, T> {
+ self.raw.lock().await;
+
+ // Safe because we have exclusive access to `self.value`.
+ MutexGuard {
+ mu: self,
+ value: unsafe { &mut *self.value.get() },
+ }
+ }
+
+ /// Acquires shared, immutable access to the resource protected by the `Mutex`, blocking the
+ /// current thread until it is able to do so. Upon returning there may be other threads that
+ /// also have immutable access to the resource but there will not be any threads that have
+ /// mutable access to the resource. When the returned `MutexReadGuard` is dropped the thread
+ /// releases its access to the resource.
+ ///
+ /// Calling `read_lock()` while holding a `MutexReadGuard` may deadlock. Calling `read_lock()`
+ /// while holding a `MutexGuard` will deadlock.
+ ///
+ /// Callers that are not in an async context may wish to use the `block_on` method to block the
+ /// thread until the `Mutex` is acquired.
+ #[inline]
+ pub async fn read_lock(&self) -> MutexReadGuard<'_, T> {
+ self.raw.read_lock().await;
+
+ // Safe because we have shared read-only access to `self.value`.
+ MutexReadGuard {
+ mu: self,
+ value: unsafe { &*self.value.get() },
+ }
+ }
+
+ // Called from `Condvar::wait` when the thread wants to reacquire the lock.
+ #[inline]
+ pub(crate) async fn lock_from_cv(&self) -> MutexGuard<'_, T> {
+ self.raw.lock_slow::<Exclusive>(DESIGNATED_WAKER, 0).await;
+
+ // Safe because we have exclusive access to `self.value`.
+ MutexGuard {
+ mu: self,
+ value: unsafe { &mut *self.value.get() },
+ }
+ }
+
+ // Like `lock_from_cv` but for acquiring a shared lock.
+ #[inline]
+ pub(crate) async fn read_lock_from_cv(&self) -> MutexReadGuard<'_, T> {
+ // Threads that have waited in the Condvar's waiter list don't have to care if there is a
+ // writer waiting since they have already waited once.
+ self.raw
+ .lock_slow::<Shared>(DESIGNATED_WAKER, WRITER_WAITING)
+ .await;
+
+ // Safe because we have exclusive access to `self.value`.
+ MutexReadGuard {
+ mu: self,
+ value: unsafe { &*self.value.get() },
+ }
+ }
+
+ #[inline]
+ fn unlock(&self) {
+ self.raw.unlock();
+ }
+
+ #[inline]
+ fn read_unlock(&self) {
+ self.raw.read_unlock();
+ }
+
+ pub fn get_mut(&mut self) -> &mut T {
+ // Safe because the compiler statically guarantees that are no other references to `self`.
+ // This is also why we don't need to acquire the lock first.
+ unsafe { &mut *self.value.get() }
+ }
+}
+
+unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
+unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
+
+impl<T: ?Sized + Default> Default for Mutex<T> {
+ fn default() -> Self {
+ Self::new(Default::default())
+ }
+}
+
+impl<T> From<T> for Mutex<T> {
+ fn from(source: T) -> Self {
+ Self::new(source)
+ }
+}
+
+/// An RAII implementation of a "scoped exclusive lock" for a `Mutex`. When this structure is
+/// dropped, the lock will be released. The resource protected by the `Mutex` can be accessed via
+/// the `Deref` and `DerefMut` implementations of this structure.
+pub struct MutexGuard<'a, T: ?Sized + 'a> {
+ mu: &'a Mutex<T>,
+ value: &'a mut T,
+}
+
+impl<'a, T: ?Sized> MutexGuard<'a, T> {
+ pub(crate) fn into_inner(self) -> &'a Mutex<T> {
+ self.mu
+ }
+
+ pub(crate) fn as_raw_mutex(&self) -> &RawMutex {
+ &self.mu.raw
+ }
+}
+
+impl<'a, T: ?Sized> Deref for MutexGuard<'a, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ self.value
+ }
+}
+
+impl<'a, T: ?Sized> DerefMut for MutexGuard<'a, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ self.value
+ }
+}
+
+impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> {
+ fn drop(&mut self) {
+ self.mu.unlock()
+ }
+}
+
+/// An RAII implementation of a "scoped shared lock" for a `Mutex`. When this structure is dropped,
+/// the lock will be released. The resource protected by the `Mutex` can be accessed via the `Deref`
+/// implementation of this structure.
+pub struct MutexReadGuard<'a, T: ?Sized + 'a> {
+ mu: &'a Mutex<T>,
+ value: &'a T,
+}
+
+impl<'a, T: ?Sized> MutexReadGuard<'a, T> {
+ pub(crate) fn into_inner(self) -> &'a Mutex<T> {
+ self.mu
+ }
+
+ pub(crate) fn as_raw_mutex(&self) -> &RawMutex {
+ &self.mu.raw
+ }
+}
+
+impl<'a, T: ?Sized> Deref for MutexReadGuard<'a, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ self.value
+ }
+}
+
+impl<'a, T: ?Sized> Drop for MutexReadGuard<'a, T> {
+ fn drop(&mut self) {
+ self.mu.read_unlock()
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use std::future::Future;
+ use std::mem;
+ use std::pin::Pin;
+ use std::rc::Rc;
+ use std::sync::atomic::{AtomicUsize, Ordering};
+ use std::sync::mpsc::{channel, Sender};
+ use std::sync::Arc;
+ use std::task::{Context, Poll, Waker};
+ use std::thread;
+ use std::time::Duration;
+
+ use futures::channel::oneshot;
+ use futures::task::{waker_ref, ArcWake};
+ use futures::{pending, select, FutureExt};
+ use futures_executor::{LocalPool, ThreadPool};
+ use futures_util::task::LocalSpawnExt;
+
+ use crate::{
+ block_on,
+ sync::{Condvar, SpinLock},
+ };
+
+ #[derive(Debug, Eq, PartialEq)]
+ struct NonCopy(u32);
+
+ // Dummy waker used when we want to manually drive futures.
+ struct TestWaker;
+ impl ArcWake for TestWaker {
+ fn wake_by_ref(_arc_self: &Arc<Self>) {}
+ }
+
+ #[test]
+ fn it_works() {
+ let mu = Mutex::new(NonCopy(13));
+
+ assert_eq!(*block_on(mu.lock()), NonCopy(13));
+ }
+
+ #[test]
+ fn smoke() {
+ let mu = Mutex::new(NonCopy(7));
+
+ mem::drop(block_on(mu.lock()));
+ mem::drop(block_on(mu.lock()));
+ }
+
+ #[test]
+ fn rw_smoke() {
+ let mu = Mutex::new(NonCopy(7));
+
+ mem::drop(block_on(mu.lock()));
+ mem::drop(block_on(mu.read_lock()));
+ mem::drop((block_on(mu.read_lock()), block_on(mu.read_lock())));
+ mem::drop(block_on(mu.lock()));
+ }
+
+ #[test]
+ fn async_smoke() {
+ async fn lock(mu: Rc<Mutex<NonCopy>>) {
+ mu.lock().await;
+ }
+
+ async fn read_lock(mu: Rc<Mutex<NonCopy>>) {
+ mu.read_lock().await;
+ }
+
+ async fn double_read_lock(mu: Rc<Mutex<NonCopy>>) {
+ let first = mu.read_lock().await;
+ mu.read_lock().await;
+
+ // Make sure first lives past the second read lock.
+ first.as_raw_mutex();
+ }
+
+ let mu = Rc::new(Mutex::new(NonCopy(7)));
+
+ let mut ex = LocalPool::new();
+ let spawner = ex.spawner();
+
+ spawner
+ .spawn_local(lock(Rc::clone(&mu)))
+ .expect("Failed to spawn future");
+ spawner
+ .spawn_local(read_lock(Rc::clone(&mu)))
+ .expect("Failed to spawn future");
+ spawner
+ .spawn_local(double_read_lock(Rc::clone(&mu)))
+ .expect("Failed to spawn future");
+ spawner
+ .spawn_local(lock(Rc::clone(&mu)))
+ .expect("Failed to spawn future");
+
+ ex.run();
+ }
+
+ #[test]
+ fn send() {
+ let mu = Mutex::new(NonCopy(19));
+
+ thread::spawn(move || {
+ let value = block_on(mu.lock());
+ assert_eq!(*value, NonCopy(19));
+ })
+ .join()
+ .unwrap();
+ }
+
+ #[test]
+ fn arc_nested() {
+ // Tests nested mutexes and access to underlying data.
+ let mu = Mutex::new(1);
+ let arc = Arc::new(Mutex::new(mu));
+ thread::spawn(move || {
+ let nested = block_on(arc.lock());
+ let lock2 = block_on(nested.lock());
+ assert_eq!(*lock2, 1);
+ })
+ .join()
+ .unwrap();
+ }
+
+ #[test]
+ fn arc_access_in_unwind() {
+ let arc = Arc::new(Mutex::new(1));
+ let arc2 = arc.clone();
+ thread::spawn(move || {
+ struct Unwinder {
+ i: Arc<Mutex<i32>>,
+ }
+ impl Drop for Unwinder {
+ fn drop(&mut self) {
+ *block_on(self.i.lock()) += 1;
+ }
+ }
+ let _u = Unwinder { i: arc2 };
+ panic!();
+ })
+ .join()
+ .expect_err("thread did not panic");
+ let lock = block_on(arc.lock());
+ assert_eq!(*lock, 2);
+ }
+
+ #[test]
+ fn unsized_value() {
+ let mutex: &Mutex<[i32]> = &Mutex::new([1, 2, 3]);
+ {
+ let b = &mut *block_on(mutex.lock());
+ b[0] = 4;
+ b[2] = 5;
+ }
+ let expected: &[i32] = &[4, 2, 5];
+ assert_eq!(&*block_on(mutex.lock()), expected);
+ }
+ #[test]
+ fn high_contention() {
+ const THREADS: usize = 17;
+ const ITERATIONS: usize = 103;
+
+ let mut threads = Vec::with_capacity(THREADS);
+
+ let mu = Arc::new(Mutex::new(0usize));
+ for _ in 0..THREADS {
+ let mu2 = mu.clone();
+ threads.push(thread::spawn(move || {
+ for _ in 0..ITERATIONS {
+ *block_on(mu2.lock()) += 1;
+ }
+ }));
+ }
+
+ for t in threads.into_iter() {
+ t.join().unwrap();
+ }
+
+ assert_eq!(*block_on(mu.read_lock()), THREADS * ITERATIONS);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn high_contention_with_cancel() {
+ const TASKS: usize = 17;
+ const ITERATIONS: usize = 103;
+
+ async fn increment(mu: Arc<Mutex<usize>>, alt_mu: Arc<Mutex<usize>>, tx: Sender<()>) {
+ for _ in 0..ITERATIONS {
+ select! {
+ mut count = mu.lock().fuse() => *count += 1,
+ mut count = alt_mu.lock().fuse() => *count += 1,
+ }
+ }
+ tx.send(()).expect("Failed to send completion signal");
+ }
+
+ let ex = ThreadPool::new().expect("Failed to create ThreadPool");
+
+ let mu = Arc::new(Mutex::new(0usize));
+ let alt_mu = Arc::new(Mutex::new(0usize));
+
+ let (tx, rx) = channel();
+ for _ in 0..TASKS {
+ ex.spawn_ok(increment(Arc::clone(&mu), Arc::clone(&alt_mu), tx.clone()));
+ }
+
+ for _ in 0..TASKS {
+ if let Err(e) = rx.recv_timeout(Duration::from_secs(10)) {
+ panic!("Error while waiting for threads to complete: {}", e);
+ }
+ }
+
+ assert_eq!(
+ *block_on(mu.read_lock()) + *block_on(alt_mu.read_lock()),
+ TASKS * ITERATIONS
+ );
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ assert_eq!(alt_mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn single_thread_async() {
+ const TASKS: usize = 17;
+ const ITERATIONS: usize = 103;
+
+ // Async closures are unstable.
+ async fn increment(mu: Rc<Mutex<usize>>) {
+ for _ in 0..ITERATIONS {
+ *mu.lock().await += 1;
+ }
+ }
+
+ let mut ex = LocalPool::new();
+ let spawner = ex.spawner();
+
+ let mu = Rc::new(Mutex::new(0usize));
+ for _ in 0..TASKS {
+ spawner
+ .spawn_local(increment(Rc::clone(&mu)))
+ .expect("Failed to spawn task");
+ }
+
+ ex.run();
+
+ assert_eq!(*block_on(mu.read_lock()), TASKS * ITERATIONS);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn multi_thread_async() {
+ const TASKS: usize = 17;
+ const ITERATIONS: usize = 103;
+
+ // Async closures are unstable.
+ async fn increment(mu: Arc<Mutex<usize>>, tx: Sender<()>) {
+ for _ in 0..ITERATIONS {
+ *mu.lock().await += 1;
+ }
+ tx.send(()).expect("Failed to send completion signal");
+ }
+
+ let ex = ThreadPool::new().expect("Failed to create ThreadPool");
+
+ let mu = Arc::new(Mutex::new(0usize));
+ let (tx, rx) = channel();
+ for _ in 0..TASKS {
+ ex.spawn_ok(increment(Arc::clone(&mu), tx.clone()));
+ }
+
+ for _ in 0..TASKS {
+ rx.recv_timeout(Duration::from_secs(5))
+ .expect("Failed to receive completion signal");
+ }
+ assert_eq!(*block_on(mu.read_lock()), TASKS * ITERATIONS);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn get_mut() {
+ let mut mu = Mutex::new(NonCopy(13));
+ *mu.get_mut() = NonCopy(17);
+
+ assert_eq!(mu.into_inner(), NonCopy(17));
+ }
+
+ #[test]
+ fn into_inner() {
+ let mu = Mutex::new(NonCopy(29));
+ assert_eq!(mu.into_inner(), NonCopy(29));
+ }
+
+ #[test]
+ fn into_inner_drop() {
+ struct NeedsDrop(Arc<AtomicUsize>);
+ impl Drop for NeedsDrop {
+ fn drop(&mut self) {
+ self.0.fetch_add(1, Ordering::AcqRel);
+ }
+ }
+
+ let value = Arc::new(AtomicUsize::new(0));
+ let needs_drop = Mutex::new(NeedsDrop(value.clone()));
+ assert_eq!(value.load(Ordering::Acquire), 0);
+
+ {
+ let inner = needs_drop.into_inner();
+ assert_eq!(inner.0.load(Ordering::Acquire), 0);
+ }
+
+ assert_eq!(value.load(Ordering::Acquire), 1);
+ }
+
+ #[test]
+ fn rw_arc() {
+ const THREADS: isize = 7;
+ const ITERATIONS: isize = 13;
+
+ let mu = Arc::new(Mutex::new(0isize));
+ let mu2 = mu.clone();
+
+ let (tx, rx) = channel();
+ thread::spawn(move || {
+ let mut guard = block_on(mu2.lock());
+ for _ in 0..ITERATIONS {
+ let tmp = *guard;
+ *guard = -1;
+ thread::yield_now();
+ *guard = tmp + 1;
+ }
+ tx.send(()).unwrap();
+ });
+
+ let mut readers = Vec::with_capacity(10);
+ for _ in 0..THREADS {
+ let mu3 = mu.clone();
+ let handle = thread::spawn(move || {
+ let guard = block_on(mu3.read_lock());
+ assert!(*guard >= 0);
+ });
+
+ readers.push(handle);
+ }
+
+ // Wait for the readers to finish their checks.
+ for r in readers {
+ r.join().expect("One or more readers saw a negative value");
+ }
+
+ // Wait for the writer to finish.
+ rx.recv_timeout(Duration::from_secs(5)).unwrap();
+ assert_eq!(*block_on(mu.read_lock()), ITERATIONS);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn rw_single_thread_async() {
+ // A Future that returns `Poll::pending` the first time it is polled and `Poll::Ready` every
+ // time after that.
+ struct TestFuture {
+ polled: bool,
+ waker: Arc<SpinLock<Option<Waker>>>,
+ }
+
+ impl Future for TestFuture {
+ type Output = ();
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ if self.polled {
+ Poll::Ready(())
+ } else {
+ self.polled = true;
+ *self.waker.lock() = Some(cx.waker().clone());
+ Poll::Pending
+ }
+ }
+ }
+
+ fn wake_future(waker: Arc<SpinLock<Option<Waker>>>) {
+ loop {
+ if let Some(w) = waker.lock().take() {
+ w.wake();
+ return;
+ }
+
+ // This sleep cannot be moved into an else branch because we would end up holding
+ // the lock while sleeping due to rust's drop ordering rules.
+ thread::sleep(Duration::from_millis(10));
+ }
+ }
+
+ async fn writer(mu: Rc<Mutex<isize>>) {
+ let mut guard = mu.lock().await;
+ for _ in 0..ITERATIONS {
+ let tmp = *guard;
+ *guard = -1;
+ let waker = Arc::new(SpinLock::new(None));
+ let waker2 = Arc::clone(&waker);
+ thread::spawn(move || wake_future(waker2));
+ let fut = TestFuture {
+ polled: false,
+ waker,
+ };
+ fut.await;
+ *guard = tmp + 1;
+ }
+ }
+
+ async fn reader(mu: Rc<Mutex<isize>>) {
+ let guard = mu.read_lock().await;
+ assert!(*guard >= 0);
+ }
+
+ const TASKS: isize = 7;
+ const ITERATIONS: isize = 13;
+
+ let mu = Rc::new(Mutex::new(0isize));
+ let mut ex = LocalPool::new();
+ let spawner = ex.spawner();
+
+ spawner
+ .spawn_local(writer(Rc::clone(&mu)))
+ .expect("Failed to spawn writer");
+
+ for _ in 0..TASKS {
+ spawner
+ .spawn_local(reader(Rc::clone(&mu)))
+ .expect("Failed to spawn reader");
+ }
+
+ ex.run();
+
+ assert_eq!(*block_on(mu.read_lock()), ITERATIONS);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn rw_multi_thread_async() {
+ async fn writer(mu: Arc<Mutex<isize>>, tx: Sender<()>) {
+ let mut guard = mu.lock().await;
+ for _ in 0..ITERATIONS {
+ let tmp = *guard;
+ *guard = -1;
+ thread::yield_now();
+ *guard = tmp + 1;
+ }
+
+ mem::drop(guard);
+ tx.send(()).unwrap();
+ }
+
+ async fn reader(mu: Arc<Mutex<isize>>, tx: Sender<()>) {
+ let guard = mu.read_lock().await;
+ assert!(*guard >= 0);
+
+ mem::drop(guard);
+ tx.send(()).expect("Failed to send completion message");
+ }
+
+ const TASKS: isize = 7;
+ const ITERATIONS: isize = 13;
+
+ let mu = Arc::new(Mutex::new(0isize));
+ let ex = ThreadPool::new().expect("Failed to create ThreadPool");
+
+ let (txw, rxw) = channel();
+ ex.spawn_ok(writer(Arc::clone(&mu), txw));
+
+ let (txr, rxr) = channel();
+ for _ in 0..TASKS {
+ ex.spawn_ok(reader(Arc::clone(&mu), txr.clone()));
+ }
+
+ // Wait for the readers to finish their checks.
+ for _ in 0..TASKS {
+ rxr.recv_timeout(Duration::from_secs(5))
+ .expect("Failed to receive completion message from reader");
+ }
+
+ // Wait for the writer to finish.
+ rxw.recv_timeout(Duration::from_secs(5))
+ .expect("Failed to receive completion message from writer");
+
+ assert_eq!(*block_on(mu.read_lock()), ITERATIONS);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn wake_all_readers() {
+ async fn read(mu: Arc<Mutex<()>>) {
+ let g = mu.read_lock().await;
+ pending!();
+ mem::drop(g);
+ }
+
+ async fn write(mu: Arc<Mutex<()>>) {
+ mu.lock().await;
+ }
+
+ let mu = Arc::new(Mutex::new(()));
+ let mut futures: [Pin<Box<dyn Future<Output = ()>>>; 5] = [
+ Box::pin(read(mu.clone())),
+ Box::pin(read(mu.clone())),
+ Box::pin(read(mu.clone())),
+ Box::pin(write(mu.clone())),
+ Box::pin(read(mu.clone())),
+ ];
+ const NUM_READERS: usize = 4;
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ // Acquire the lock so that the futures cannot get it.
+ let g = block_on(mu.lock());
+
+ for r in &mut futures {
+ if let Poll::Ready(()) = r.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & HAS_WAITERS,
+ HAS_WAITERS
+ );
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING,
+ WRITER_WAITING
+ );
+
+ // Drop the lock. This should allow all readers to make progress. Since they already waited
+ // once they should ignore the WRITER_WAITING bit that is currently set.
+ mem::drop(g);
+ for r in &mut futures {
+ if let Poll::Ready(()) = r.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ }
+
+ // Check that all readers were able to acquire the lock.
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & READ_MASK,
+ READ_LOCK * NUM_READERS
+ );
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING,
+ WRITER_WAITING
+ );
+
+ let mut needs_poll = None;
+
+ // All the readers can now finish but the writer needs to be polled again.
+ for (i, r) in futures.iter_mut().enumerate() {
+ match r.as_mut().poll(&mut cx) {
+ Poll::Ready(()) => {}
+ Poll::Pending => {
+ if needs_poll.is_some() {
+ panic!("More than one future unable to complete");
+ }
+ needs_poll = Some(i);
+ }
+ }
+ }
+
+ if futures[needs_poll.expect("Writer unexpectedly able to complete")]
+ .as_mut()
+ .poll(&mut cx)
+ .is_pending()
+ {
+ panic!("Writer unable to complete");
+ }
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn long_wait() {
+ async fn tight_loop(mu: Arc<Mutex<bool>>) {
+ loop {
+ let ready = mu.lock().await;
+ if *ready {
+ break;
+ }
+ pending!();
+ }
+ }
+
+ async fn mark_ready(mu: Arc<Mutex<bool>>) {
+ *mu.lock().await = true;
+ }
+
+ let mu = Arc::new(Mutex::new(false));
+ let mut tl = Box::pin(tight_loop(mu.clone()));
+ let mut mark = Box::pin(mark_ready(mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ for _ in 0..=LONG_WAIT_THRESHOLD {
+ if let Poll::Ready(()) = tl.as_mut().poll(&mut cx) {
+ panic!("tight_loop unexpectedly ready");
+ }
+
+ if let Poll::Ready(()) = mark.as_mut().poll(&mut cx) {
+ panic!("mark_ready unexpectedly ready");
+ }
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed),
+ LOCKED | HAS_WAITERS | WRITER_WAITING | LONG_WAIT
+ );
+
+ // This time the tight loop will fail to acquire the lock.
+ if let Poll::Ready(()) = tl.as_mut().poll(&mut cx) {
+ panic!("tight_loop unexpectedly ready");
+ }
+
+ // Which will finally allow the mark_ready function to make progress.
+ if mark.as_mut().poll(&mut cx).is_pending() {
+ panic!("mark_ready not able to make progress");
+ }
+
+ // Now the tight loop will finish.
+ if tl.as_mut().poll(&mut cx).is_pending() {
+ panic!("tight_loop not able to finish");
+ }
+
+ assert!(*block_on(mu.lock()));
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn cancel_long_wait_before_wake() {
+ async fn tight_loop(mu: Arc<Mutex<bool>>) {
+ loop {
+ let ready = mu.lock().await;
+ if *ready {
+ break;
+ }
+ pending!();
+ }
+ }
+
+ async fn mark_ready(mu: Arc<Mutex<bool>>) {
+ *mu.lock().await = true;
+ }
+
+ let mu = Arc::new(Mutex::new(false));
+ let mut tl = Box::pin(tight_loop(mu.clone()));
+ let mut mark = Box::pin(mark_ready(mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ for _ in 0..=LONG_WAIT_THRESHOLD {
+ if let Poll::Ready(()) = tl.as_mut().poll(&mut cx) {
+ panic!("tight_loop unexpectedly ready");
+ }
+
+ if let Poll::Ready(()) = mark.as_mut().poll(&mut cx) {
+ panic!("mark_ready unexpectedly ready");
+ }
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed),
+ LOCKED | HAS_WAITERS | WRITER_WAITING | LONG_WAIT
+ );
+
+ // Now drop the mark_ready future, which should clear the LONG_WAIT bit.
+ mem::drop(mark);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), LOCKED);
+
+ mem::drop(tl);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn cancel_long_wait_after_wake() {
+ async fn tight_loop(mu: Arc<Mutex<bool>>) {
+ loop {
+ let ready = mu.lock().await;
+ if *ready {
+ break;
+ }
+ pending!();
+ }
+ }
+
+ async fn mark_ready(mu: Arc<Mutex<bool>>) {
+ *mu.lock().await = true;
+ }
+
+ let mu = Arc::new(Mutex::new(false));
+ let mut tl = Box::pin(tight_loop(mu.clone()));
+ let mut mark = Box::pin(mark_ready(mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ for _ in 0..=LONG_WAIT_THRESHOLD {
+ if let Poll::Ready(()) = tl.as_mut().poll(&mut cx) {
+ panic!("tight_loop unexpectedly ready");
+ }
+
+ if let Poll::Ready(()) = mark.as_mut().poll(&mut cx) {
+ panic!("mark_ready unexpectedly ready");
+ }
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed),
+ LOCKED | HAS_WAITERS | WRITER_WAITING | LONG_WAIT
+ );
+
+ // This time the tight loop will fail to acquire the lock.
+ if let Poll::Ready(()) = tl.as_mut().poll(&mut cx) {
+ panic!("tight_loop unexpectedly ready");
+ }
+
+ // Now drop the mark_ready future, which should clear the LONG_WAIT bit.
+ mem::drop(mark);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed) & LONG_WAIT, 0);
+
+ // Since the lock is not held, we should be able to spawn a future to set the ready flag.
+ block_on(mark_ready(mu.clone()));
+
+ // Now the tight loop will finish.
+ if tl.as_mut().poll(&mut cx).is_pending() {
+ panic!("tight_loop not able to finish");
+ }
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn designated_waker() {
+ async fn inc(mu: Arc<Mutex<usize>>) {
+ *mu.lock().await += 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+
+ let mut futures = [
+ Box::pin(inc(mu.clone())),
+ Box::pin(inc(mu.clone())),
+ Box::pin(inc(mu.clone())),
+ ];
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let count = block_on(mu.lock());
+
+ // Poll 2 futures. Since neither will be able to acquire the lock, they should get added to
+ // the waiter list.
+ if let Poll::Ready(()) = futures[0].as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ if let Poll::Ready(()) = futures[1].as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed),
+ LOCKED | HAS_WAITERS | WRITER_WAITING,
+ );
+
+ // Now drop the lock. This should set the DESIGNATED_WAKER bit and wake up the first future
+ // in the wait list.
+ mem::drop(count);
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed),
+ DESIGNATED_WAKER | HAS_WAITERS | WRITER_WAITING,
+ );
+
+ // Now poll the third future. It should be able to acquire the lock immediately.
+ if futures[2].as_mut().poll(&mut cx).is_pending() {
+ panic!("future unable to complete");
+ }
+ assert_eq!(*block_on(mu.lock()), 1);
+
+ // There should still be a waiter in the wait list and the DESIGNATED_WAKER bit should still
+ // be set.
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & DESIGNATED_WAKER,
+ DESIGNATED_WAKER
+ );
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & HAS_WAITERS,
+ HAS_WAITERS
+ );
+
+ // Now let the future that was woken up run.
+ if futures[0].as_mut().poll(&mut cx).is_pending() {
+ panic!("future unable to complete");
+ }
+ assert_eq!(*block_on(mu.lock()), 2);
+
+ if futures[1].as_mut().poll(&mut cx).is_pending() {
+ panic!("future unable to complete");
+ }
+ assert_eq!(*block_on(mu.lock()), 3);
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn cancel_designated_waker() {
+ async fn inc(mu: Arc<Mutex<usize>>) {
+ *mu.lock().await += 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+
+ let mut fut = Box::pin(inc(mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let count = block_on(mu.lock());
+
+ if let Poll::Ready(()) = fut.as_mut().poll(&mut cx) {
+ panic!("Future unexpectedly ready when lock is held");
+ }
+
+ // Drop the lock. This will wake up the future.
+ mem::drop(count);
+
+ // Now drop the future without polling. This should clear all the state in the mutex.
+ mem::drop(fut);
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn cancel_before_wake() {
+ async fn inc(mu: Arc<Mutex<usize>>) {
+ *mu.lock().await += 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+
+ let mut fut1 = Box::pin(inc(mu.clone()));
+
+ let mut fut2 = Box::pin(inc(mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ // First acquire the lock.
+ let count = block_on(mu.lock());
+
+ // Now poll the futures. Since the lock is acquired they will both get queued in the waiter
+ // list.
+ match fut1.as_mut().poll(&mut cx) {
+ Poll::Pending => {}
+ Poll::Ready(()) => panic!("Future is unexpectedly ready"),
+ }
+
+ match fut2.as_mut().poll(&mut cx) {
+ Poll::Pending => {}
+ Poll::Ready(()) => panic!("Future is unexpectedly ready"),
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING,
+ WRITER_WAITING
+ );
+
+ // Drop fut1. This should remove it from the waiter list but shouldn't wake fut2.
+ mem::drop(fut1);
+
+ // There should be no designated waker.
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed) & DESIGNATED_WAKER, 0);
+
+ // Since the waiter was a writer, we should clear the WRITER_WAITING bit.
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING, 0);
+
+ match fut2.as_mut().poll(&mut cx) {
+ Poll::Pending => {}
+ Poll::Ready(()) => panic!("Future is unexpectedly ready"),
+ }
+
+ // Now drop the lock. This should mark fut2 as ready to make progress.
+ mem::drop(count);
+
+ match fut2.as_mut().poll(&mut cx) {
+ Poll::Pending => panic!("Future is not ready to make progress"),
+ Poll::Ready(()) => {}
+ }
+
+ // Verify that we only incremented the count once.
+ assert_eq!(*block_on(mu.lock()), 1);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn cancel_after_wake() {
+ async fn inc(mu: Arc<Mutex<usize>>) {
+ *mu.lock().await += 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+
+ let mut fut1 = Box::pin(inc(mu.clone()));
+
+ let mut fut2 = Box::pin(inc(mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ // First acquire the lock.
+ let count = block_on(mu.lock());
+
+ // Now poll the futures. Since the lock is acquired they will both get queued in the waiter
+ // list.
+ match fut1.as_mut().poll(&mut cx) {
+ Poll::Pending => {}
+ Poll::Ready(()) => panic!("Future is unexpectedly ready"),
+ }
+
+ match fut2.as_mut().poll(&mut cx) {
+ Poll::Pending => {}
+ Poll::Ready(()) => panic!("Future is unexpectedly ready"),
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING,
+ WRITER_WAITING
+ );
+
+ // Drop the lock. This should mark fut1 as ready to make progress.
+ mem::drop(count);
+
+ // Now drop fut1. This should make fut2 ready to make progress.
+ mem::drop(fut1);
+
+ // Since there was still another waiter in the list we shouldn't have cleared the
+ // DESIGNATED_WAKER bit.
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & DESIGNATED_WAKER,
+ DESIGNATED_WAKER
+ );
+
+ // Since the waiter was a writer, we should clear the WRITER_WAITING bit.
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING, 0);
+
+ match fut2.as_mut().poll(&mut cx) {
+ Poll::Pending => panic!("Future is not ready to make progress"),
+ Poll::Ready(()) => {}
+ }
+
+ // Verify that we only incremented the count once.
+ assert_eq!(*block_on(mu.lock()), 1);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn timeout() {
+ async fn timed_lock(timer: oneshot::Receiver<()>, mu: Arc<Mutex<()>>) {
+ select! {
+ res = timer.fuse() => {
+ match res {
+ Ok(()) => {},
+ Err(e) => panic!("Timer unexpectedly canceled: {}", e),
+ }
+ }
+ _ = mu.lock().fuse() => panic!("Successfuly acquired lock"),
+ }
+ }
+
+ let mu = Arc::new(Mutex::new(()));
+ let (tx, rx) = oneshot::channel();
+
+ let mut timeout = Box::pin(timed_lock(rx, mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ // Acquire the lock.
+ let g = block_on(mu.lock());
+
+ // Poll the future.
+ if let Poll::Ready(()) = timeout.as_mut().poll(&mut cx) {
+ panic!("timed_lock unexpectedly ready");
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & HAS_WAITERS,
+ HAS_WAITERS
+ );
+
+ // Signal the channel, which should cancel the lock.
+ tx.send(()).expect("Failed to send wakeup");
+
+ // Now the future should have completed without acquiring the lock.
+ if timeout.as_mut().poll(&mut cx).is_pending() {
+ panic!("timed_lock not ready after timeout");
+ }
+
+ // The mutex state should not show any waiters.
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed) & HAS_WAITERS, 0);
+
+ mem::drop(g);
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn writer_waiting() {
+ async fn read_zero(mu: Arc<Mutex<usize>>) {
+ let val = mu.read_lock().await;
+ pending!();
+
+ assert_eq!(*val, 0);
+ }
+
+ async fn inc(mu: Arc<Mutex<usize>>) {
+ *mu.lock().await += 1;
+ }
+
+ async fn read_one(mu: Arc<Mutex<usize>>) {
+ let val = mu.read_lock().await;
+
+ assert_eq!(*val, 1);
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+
+ let mut r1 = Box::pin(read_zero(mu.clone()));
+ let mut r2 = Box::pin(read_zero(mu.clone()));
+
+ let mut w = Box::pin(inc(mu.clone()));
+ let mut r3 = Box::pin(read_one(mu.clone()));
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ if let Poll::Ready(()) = r1.as_mut().poll(&mut cx) {
+ panic!("read_zero unexpectedly ready");
+ }
+ if let Poll::Ready(()) = r2.as_mut().poll(&mut cx) {
+ panic!("read_zero unexpectedly ready");
+ }
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & READ_MASK,
+ 2 * READ_LOCK
+ );
+
+ if let Poll::Ready(()) = w.as_mut().poll(&mut cx) {
+ panic!("inc unexpectedly ready");
+ }
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING,
+ WRITER_WAITING
+ );
+
+ // The WRITER_WAITING bit should prevent the next reader from acquiring the lock.
+ if let Poll::Ready(()) = r3.as_mut().poll(&mut cx) {
+ panic!("read_one unexpectedly ready");
+ }
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & READ_MASK,
+ 2 * READ_LOCK
+ );
+
+ if r1.as_mut().poll(&mut cx).is_pending() {
+ panic!("read_zero unable to complete");
+ }
+ if r2.as_mut().poll(&mut cx).is_pending() {
+ panic!("read_zero unable to complete");
+ }
+ if w.as_mut().poll(&mut cx).is_pending() {
+ panic!("inc unable to complete");
+ }
+ if r3.as_mut().poll(&mut cx).is_pending() {
+ panic!("read_one unable to complete");
+ }
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn notify_one() {
+ async fn read(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.read_lock().await;
+ while *count == 0 {
+ count = cv.wait_read(count).await;
+ }
+ }
+
+ async fn write(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.lock().await;
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+
+ *count -= 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let mut readers = [
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ ];
+ let mut writer = Box::pin(write(mu.clone(), cv.clone()));
+
+ for r in &mut readers {
+ if let Poll::Ready(()) = r.as_mut().poll(&mut cx) {
+ panic!("reader unexpectedly ready");
+ }
+ }
+ if let Poll::Ready(()) = writer.as_mut().poll(&mut cx) {
+ panic!("writer unexpectedly ready");
+ }
+
+ let mut count = block_on(mu.lock());
+ *count = 1;
+
+ // This should wake all readers + one writer.
+ cv.notify_one();
+
+ // Poll the readers and the writer so they add themselves to the mutex's waiter list.
+ for r in &mut readers {
+ if r.as_mut().poll(&mut cx).is_ready() {
+ panic!("reader unexpectedly ready");
+ }
+ }
+
+ if writer.as_mut().poll(&mut cx).is_ready() {
+ panic!("writer unexpectedly ready");
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & HAS_WAITERS,
+ HAS_WAITERS
+ );
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & WRITER_WAITING,
+ WRITER_WAITING
+ );
+
+ mem::drop(count);
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & (HAS_WAITERS | WRITER_WAITING),
+ HAS_WAITERS | WRITER_WAITING
+ );
+
+ for r in &mut readers {
+ if r.as_mut().poll(&mut cx).is_pending() {
+ panic!("reader unable to complete");
+ }
+ }
+
+ if writer.as_mut().poll(&mut cx).is_pending() {
+ panic!("writer unable to complete");
+ }
+
+ assert_eq!(*block_on(mu.read_lock()), 0);
+ }
+
+ #[test]
+ fn notify_when_unlocked() {
+ async fn dec(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.lock().await;
+
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+
+ *count -= 1;
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let mut futures = [
+ Box::pin(dec(mu.clone(), cv.clone())),
+ Box::pin(dec(mu.clone(), cv.clone())),
+ Box::pin(dec(mu.clone(), cv.clone())),
+ Box::pin(dec(mu.clone(), cv.clone())),
+ ];
+
+ for f in &mut futures {
+ if let Poll::Ready(()) = f.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ }
+
+ *block_on(mu.lock()) = futures.len();
+ cv.notify_all();
+
+ // Since we haven't polled `futures` yet, the mutex should not have any waiters.
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed) & HAS_WAITERS, 0);
+
+ for f in &mut futures {
+ if f.as_mut().poll(&mut cx).is_pending() {
+ panic!("future unexpectedly ready");
+ }
+ }
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn notify_reader_writer() {
+ async fn read(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.read_lock().await;
+ while *count == 0 {
+ count = cv.wait_read(count).await;
+ }
+
+ // Yield once while holding the read lock, which should prevent the writer from waking
+ // up.
+ pending!();
+ }
+
+ async fn write(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.lock().await;
+ while *count == 0 {
+ count = cv.wait(count).await;
+ }
+
+ *count -= 1;
+ }
+
+ async fn lock(mu: Arc<Mutex<usize>>) {
+ mem::drop(mu.lock().await);
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let mut futures: [Pin<Box<dyn Future<Output = ()>>>; 5] = [
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(write(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ ];
+ const NUM_READERS: usize = 4;
+
+ let mut l = Box::pin(lock(mu.clone()));
+
+ for f in &mut futures {
+ if let Poll::Ready(()) = f.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ }
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+
+ let mut count = block_on(mu.lock());
+ *count = 1;
+
+ // Now poll the lock function. Since the lock is held by us, it will get queued on the
+ // waiter list.
+ if let Poll::Ready(()) = l.as_mut().poll(&mut cx) {
+ panic!("lock() unexpectedly ready");
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & (HAS_WAITERS | WRITER_WAITING),
+ HAS_WAITERS | WRITER_WAITING
+ );
+
+ // Wake up waiters while holding the lock.
+ cv.notify_all();
+
+ // Drop the lock. This should wake up the lock function.
+ mem::drop(count);
+
+ if l.as_mut().poll(&mut cx).is_pending() {
+ panic!("lock() unable to complete");
+ }
+
+ // Since we haven't polled `futures` yet, the mutex state should now be empty.
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+
+ // Poll everything again. The readers should be able to make progress (but not complete) but
+ // the writer should be blocked.
+ for f in &mut futures {
+ if let Poll::Ready(()) = f.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ }
+
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & READ_MASK,
+ READ_LOCK * NUM_READERS
+ );
+
+ // All the readers can now finish but the writer needs to be polled again.
+ let mut needs_poll = None;
+ for (i, r) in futures.iter_mut().enumerate() {
+ match r.as_mut().poll(&mut cx) {
+ Poll::Ready(()) => {}
+ Poll::Pending => {
+ if needs_poll.is_some() {
+ panic!("More than one future unable to complete");
+ }
+ needs_poll = Some(i);
+ }
+ }
+ }
+
+ if futures[needs_poll.expect("Writer unexpectedly able to complete")]
+ .as_mut()
+ .poll(&mut cx)
+ .is_pending()
+ {
+ panic!("Writer unable to complete");
+ }
+
+ assert_eq!(*block_on(mu.lock()), 0);
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+
+ #[test]
+ fn notify_readers_with_read_lock() {
+ async fn read(mu: Arc<Mutex<usize>>, cv: Arc<Condvar>) {
+ let mut count = mu.read_lock().await;
+ while *count == 0 {
+ count = cv.wait_read(count).await;
+ }
+
+ // Yield once while holding the read lock.
+ pending!();
+ }
+
+ let mu = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let arc_waker = Arc::new(TestWaker);
+ let waker = waker_ref(&arc_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ let mut futures = [
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ Box::pin(read(mu.clone(), cv.clone())),
+ ];
+
+ for f in &mut futures {
+ if let Poll::Ready(()) = f.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ }
+
+ // Increment the count and then grab a read lock.
+ *block_on(mu.lock()) = 1;
+
+ let g = block_on(mu.read_lock());
+
+ // Notify the condvar while holding the read lock. This should wake up all the waiters.
+ cv.notify_all();
+
+ // Since the lock is held in shared mode, all the readers should immediately be able to
+ // acquire the read lock.
+ for f in &mut futures {
+ if let Poll::Ready(()) = f.as_mut().poll(&mut cx) {
+ panic!("future unexpectedly ready");
+ }
+ }
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed) & HAS_WAITERS, 0);
+ assert_eq!(
+ mu.raw.state.load(Ordering::Relaxed) & READ_MASK,
+ READ_LOCK * (futures.len() + 1)
+ );
+
+ mem::drop(g);
+
+ for f in &mut futures {
+ if f.as_mut().poll(&mut cx).is_pending() {
+ panic!("future unable to complete");
+ }
+ }
+
+ assert_eq!(mu.raw.state.load(Ordering::Relaxed), 0);
+ }
+}
diff --git a/common/cros_asyncv2/src/sync/spin.rs b/common/cros_asyncv2/src/sync/spin.rs
new file mode 100644
index 000000000..f313721f8
--- /dev/null
+++ b/common/cros_asyncv2/src/sync/spin.rs
@@ -0,0 +1,278 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::UnsafeCell;
+use std::hint;
+use std::ops::{Deref, DerefMut};
+use std::sync::atomic::{AtomicBool, Ordering};
+
+const UNLOCKED: bool = false;
+const LOCKED: bool = true;
+
+/// A primitive that provides safe, mutable access to a shared resource.
+///
+/// Unlike `Mutex`, a `SpinLock` will not voluntarily yield its CPU time until the resource is
+/// available and will instead keep spinning until the resource is acquired. For the vast majority
+/// of cases, `Mutex` is a better choice than `SpinLock`. If a `SpinLock` must be used then users
+/// should try to do as little work as possible while holding the `SpinLock` and avoid any sort of
+/// blocking at all costs as it can severely penalize performance.
+///
+/// # Poisoning
+///
+/// This `SpinLock` does not implement lock poisoning so it is possible for threads to access
+/// poisoned data if a thread panics while holding the lock. If lock poisoning is needed, it can be
+/// implemented by wrapping the `SpinLock` in a new type that implements poisoning. See the
+/// implementation of `std::sync::Mutex` for an example of how to do this.
+#[repr(align(128))]
+pub struct SpinLock<T: ?Sized> {
+ lock: AtomicBool,
+ value: UnsafeCell<T>,
+}
+
+impl<T> SpinLock<T> {
+ /// Creates a new, unlocked `SpinLock` that's ready for use.
+ pub fn new(value: T) -> SpinLock<T> {
+ SpinLock {
+ lock: AtomicBool::new(UNLOCKED),
+ value: UnsafeCell::new(value),
+ }
+ }
+
+ /// Consumes the `SpinLock` and returns the value guarded by it. This method doesn't perform any
+ /// locking as the compiler guarantees that there are no references to `self`.
+ pub fn into_inner(self) -> T {
+ // No need to take the lock because the compiler can statically guarantee
+ // that there are no references to the SpinLock.
+ self.value.into_inner()
+ }
+}
+
+impl<T: ?Sized> SpinLock<T> {
+ /// Acquires exclusive, mutable access to the resource protected by the `SpinLock`, blocking the
+ /// current thread until it is able to do so. Upon returning, the current thread will be the
+ /// only thread with access to the resource. The `SpinLock` will be released when the returned
+ /// `SpinLockGuard` is dropped. Attempting to call `lock` while already holding the `SpinLock`
+ /// will cause a deadlock.
+ pub fn lock(&self) -> SpinLockGuard<T> {
+ loop {
+ let state = self.lock.load(Ordering::Relaxed);
+ if state == UNLOCKED
+ && self
+ .lock
+ .compare_exchange_weak(UNLOCKED, LOCKED, Ordering::Acquire, Ordering::Relaxed)
+ .is_ok()
+ {
+ break;
+ }
+ hint::spin_loop();
+ }
+
+ SpinLockGuard {
+ lock: self,
+ value: unsafe { &mut *self.value.get() },
+ }
+ }
+
+ fn unlock(&self) {
+ // Don't need to compare and swap because we exclusively hold the lock.
+ self.lock.store(UNLOCKED, Ordering::Release);
+ }
+
+ /// Returns a mutable reference to the contained value. This method doesn't perform any locking
+ /// as the compiler will statically guarantee that there are no other references to `self`.
+ pub fn get_mut(&mut self) -> &mut T {
+ // Safe because the compiler can statically guarantee that there are no other references to
+ // `self`. This is also why we don't need to acquire the lock.
+ unsafe { &mut *self.value.get() }
+ }
+}
+
+unsafe impl<T: ?Sized + Send> Send for SpinLock<T> {}
+unsafe impl<T: ?Sized + Send> Sync for SpinLock<T> {}
+
+impl<T: ?Sized + Default> Default for SpinLock<T> {
+ fn default() -> Self {
+ Self::new(Default::default())
+ }
+}
+
+impl<T> From<T> for SpinLock<T> {
+ fn from(source: T) -> Self {
+ Self::new(source)
+ }
+}
+
+/// An RAII implementation of a "scoped lock" for a `SpinLock`. When this structure is dropped, the
+/// lock will be released. The resource protected by the `SpinLock` can be accessed via the `Deref`
+/// and `DerefMut` implementations of this structure.
+pub struct SpinLockGuard<'a, T: 'a + ?Sized> {
+ lock: &'a SpinLock<T>,
+ value: &'a mut T,
+}
+
+impl<'a, T: ?Sized> Deref for SpinLockGuard<'a, T> {
+ type Target = T;
+ fn deref(&self) -> &T {
+ self.value
+ }
+}
+
+impl<'a, T: ?Sized> DerefMut for SpinLockGuard<'a, T> {
+ fn deref_mut(&mut self) -> &mut T {
+ self.value
+ }
+}
+
+impl<'a, T: ?Sized> Drop for SpinLockGuard<'a, T> {
+ fn drop(&mut self) {
+ self.lock.unlock();
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use std::mem;
+ use std::sync::atomic::{AtomicUsize, Ordering};
+ use std::sync::Arc;
+ use std::thread;
+
+ #[derive(PartialEq, Eq, Debug)]
+ struct NonCopy(u32);
+
+ #[test]
+ fn it_works() {
+ let sl = SpinLock::new(NonCopy(13));
+
+ assert_eq!(*sl.lock(), NonCopy(13));
+ }
+
+ #[test]
+ fn smoke() {
+ let sl = SpinLock::new(NonCopy(7));
+
+ mem::drop(sl.lock());
+ mem::drop(sl.lock());
+ }
+
+ #[test]
+ fn send() {
+ let sl = SpinLock::new(NonCopy(19));
+
+ thread::spawn(move || {
+ let value = sl.lock();
+ assert_eq!(*value, NonCopy(19));
+ })
+ .join()
+ .unwrap();
+ }
+
+ #[test]
+ fn high_contention() {
+ const THREADS: usize = 23;
+ const ITERATIONS: usize = 101;
+
+ let mut threads = Vec::with_capacity(THREADS);
+
+ let sl = Arc::new(SpinLock::new(0usize));
+ for _ in 0..THREADS {
+ let sl2 = sl.clone();
+ threads.push(thread::spawn(move || {
+ for _ in 0..ITERATIONS {
+ *sl2.lock() += 1;
+ }
+ }));
+ }
+
+ for t in threads.into_iter() {
+ t.join().unwrap();
+ }
+
+ assert_eq!(*sl.lock(), THREADS * ITERATIONS);
+ }
+
+ #[test]
+ fn get_mut() {
+ let mut sl = SpinLock::new(NonCopy(13));
+ *sl.get_mut() = NonCopy(17);
+
+ assert_eq!(sl.into_inner(), NonCopy(17));
+ }
+
+ #[test]
+ fn into_inner() {
+ let sl = SpinLock::new(NonCopy(29));
+ assert_eq!(sl.into_inner(), NonCopy(29));
+ }
+
+ #[test]
+ fn into_inner_drop() {
+ struct NeedsDrop(Arc<AtomicUsize>);
+ impl Drop for NeedsDrop {
+ fn drop(&mut self) {
+ self.0.fetch_add(1, Ordering::AcqRel);
+ }
+ }
+
+ let value = Arc::new(AtomicUsize::new(0));
+ let needs_drop = SpinLock::new(NeedsDrop(value.clone()));
+ assert_eq!(value.load(Ordering::Acquire), 0);
+
+ {
+ let inner = needs_drop.into_inner();
+ assert_eq!(inner.0.load(Ordering::Acquire), 0);
+ }
+
+ assert_eq!(value.load(Ordering::Acquire), 1);
+ }
+
+ #[test]
+ fn arc_nested() {
+ // Tests nested sltexes and access to underlying data.
+ let sl = SpinLock::new(1);
+ let arc = Arc::new(SpinLock::new(sl));
+ thread::spawn(move || {
+ let nested = arc.lock();
+ let lock2 = nested.lock();
+ assert_eq!(*lock2, 1);
+ })
+ .join()
+ .unwrap();
+ }
+
+ #[test]
+ fn arc_access_in_unwind() {
+ let arc = Arc::new(SpinLock::new(1));
+ let arc2 = arc.clone();
+ thread::spawn(move || {
+ struct Unwinder {
+ i: Arc<SpinLock<i32>>,
+ }
+ impl Drop for Unwinder {
+ fn drop(&mut self) {
+ *self.i.lock() += 1;
+ }
+ }
+ let _u = Unwinder { i: arc2 };
+ panic!();
+ })
+ .join()
+ .expect_err("thread did not panic");
+ let lock = arc.lock();
+ assert_eq!(*lock, 2);
+ }
+
+ #[test]
+ fn unsized_value() {
+ let sltex: &SpinLock<[i32]> = &SpinLock::new([1, 2, 3]);
+ {
+ let b = &mut *sltex.lock();
+ b[0] = 4;
+ b[2] = 5;
+ }
+ let expected: &[i32] = &[4, 2, 5];
+ assert_eq!(&*sltex.lock(), expected);
+ }
+}
diff --git a/common/cros_asyncv2/src/sync/waiter.rs b/common/cros_asyncv2/src/sync/waiter.rs
new file mode 100644
index 000000000..072a0f506
--- /dev/null
+++ b/common/cros_asyncv2/src/sync/waiter.rs
@@ -0,0 +1,281 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::UnsafeCell;
+use std::future::Future;
+use std::mem;
+use std::pin::Pin;
+use std::ptr::NonNull;
+use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
+use std::sync::Arc;
+use std::task::{Context, Poll, Waker};
+
+use intrusive_collections::linked_list::{LinkedList, LinkedListOps};
+use intrusive_collections::{intrusive_adapter, DefaultLinkOps, LinkOps};
+
+use crate::sync::SpinLock;
+
+// An atomic version of a LinkedListLink. See https://github.com/Amanieu/intrusive-rs/issues/47 for
+// more details.
+#[repr(align(128))]
+pub struct AtomicLink {
+ prev: UnsafeCell<Option<NonNull<AtomicLink>>>,
+ next: UnsafeCell<Option<NonNull<AtomicLink>>>,
+ linked: AtomicBool,
+}
+
+impl AtomicLink {
+ fn new() -> AtomicLink {
+ AtomicLink {
+ linked: AtomicBool::new(false),
+ prev: UnsafeCell::new(None),
+ next: UnsafeCell::new(None),
+ }
+ }
+
+ fn is_linked(&self) -> bool {
+ self.linked.load(Ordering::Relaxed)
+ }
+}
+
+impl DefaultLinkOps for AtomicLink {
+ type Ops = AtomicLinkOps;
+
+ const NEW: Self::Ops = AtomicLinkOps;
+}
+
+// Safe because the only way to mutate `AtomicLink` is via the `LinkedListOps` trait whose methods
+// are all unsafe and require that the caller has first called `acquire_link` (and had it return
+// true) to use them safely.
+unsafe impl Send for AtomicLink {}
+unsafe impl Sync for AtomicLink {}
+
+#[derive(Copy, Clone, Default)]
+pub struct AtomicLinkOps;
+
+unsafe impl LinkOps for AtomicLinkOps {
+ type LinkPtr = NonNull<AtomicLink>;
+
+ unsafe fn acquire_link(&mut self, ptr: Self::LinkPtr) -> bool {
+ !ptr.as_ref().linked.swap(true, Ordering::Acquire)
+ }
+
+ unsafe fn release_link(&mut self, ptr: Self::LinkPtr) {
+ ptr.as_ref().linked.store(false, Ordering::Release)
+ }
+}
+
+unsafe impl LinkedListOps for AtomicLinkOps {
+ unsafe fn next(&self, ptr: Self::LinkPtr) -> Option<Self::LinkPtr> {
+ *ptr.as_ref().next.get()
+ }
+
+ unsafe fn prev(&self, ptr: Self::LinkPtr) -> Option<Self::LinkPtr> {
+ *ptr.as_ref().prev.get()
+ }
+
+ unsafe fn set_next(&mut self, ptr: Self::LinkPtr, next: Option<Self::LinkPtr>) {
+ *ptr.as_ref().next.get() = next;
+ }
+
+ unsafe fn set_prev(&mut self, ptr: Self::LinkPtr, prev: Option<Self::LinkPtr>) {
+ *ptr.as_ref().prev.get() = prev;
+ }
+}
+
+#[derive(Clone, Copy)]
+pub enum Kind {
+ Shared,
+ Exclusive,
+}
+
+enum State {
+ Init,
+ Waiting(Waker),
+ Woken,
+ Finished,
+ Processing,
+}
+
+// Indicates the queue to which the waiter belongs. It is the responsibility of the Mutex and
+// Condvar implementations to update this value when adding/removing a Waiter from their respective
+// waiter lists.
+#[repr(u8)]
+#[derive(Debug, Eq, PartialEq)]
+pub enum WaitingFor {
+ // The waiter is either not linked into a waiter list or it is linked into a temporary list.
+ None = 0,
+ // The waiter is linked into the Mutex's waiter list.
+ Mutex = 1,
+ // The waiter is linked into the Condvar's waiter list.
+ Condvar = 2,
+}
+
+// Represents a thread currently blocked on a Condvar or on acquiring a Mutex.
+pub struct Waiter {
+ link: AtomicLink,
+ state: SpinLock<State>,
+ cancel: fn(usize, &Waiter, bool),
+ cancel_data: usize,
+ kind: Kind,
+ waiting_for: AtomicU8,
+}
+
+impl Waiter {
+ // Create a new, initialized Waiter.
+ //
+ // `kind` should indicate whether this waiter represent a thread that is waiting for a shared
+ // lock or an exclusive lock.
+ //
+ // `cancel` is the function that is called when a `WaitFuture` (returned by the `wait()`
+ // function) is dropped before it can complete. `cancel_data` is used as the first parameter of
+ // the `cancel` function. The second parameter is the `Waiter` that was canceled and the third
+ // parameter indicates whether the `WaitFuture` was dropped after it was woken (but before it
+ // was polled to completion). A value of `false` for the third parameter may already be stale
+ // by the time the cancel function runs and so does not guarantee that the waiter was not woken.
+ // In this case, implementations should still check if the Waiter was woken. However, a value of
+ // `true` guarantees that the waiter was already woken up so no additional checks are necessary.
+ // In this case, the cancel implementation should wake up the next waiter in its wait list, if
+ // any.
+ //
+ // `waiting_for` indicates the waiter list to which this `Waiter` will be added. See the
+ // documentation of the `WaitingFor` enum for the meaning of the different values.
+ pub fn new(
+ kind: Kind,
+ cancel: fn(usize, &Waiter, bool),
+ cancel_data: usize,
+ waiting_for: WaitingFor,
+ ) -> Waiter {
+ Waiter {
+ link: AtomicLink::new(),
+ state: SpinLock::new(State::Init),
+ cancel,
+ cancel_data,
+ kind,
+ waiting_for: AtomicU8::new(waiting_for as u8),
+ }
+ }
+
+ // The kind of lock that this `Waiter` is waiting to acquire.
+ pub fn kind(&self) -> Kind {
+ self.kind
+ }
+
+ // Returns true if this `Waiter` is currently linked into a waiter list.
+ pub fn is_linked(&self) -> bool {
+ self.link.is_linked()
+ }
+
+ // Indicates the waiter list to which this `Waiter` belongs.
+ pub fn is_waiting_for(&self) -> WaitingFor {
+ match self.waiting_for.load(Ordering::Acquire) {
+ 0 => WaitingFor::None,
+ 1 => WaitingFor::Mutex,
+ 2 => WaitingFor::Condvar,
+ v => panic!("Unknown value for `WaitingFor`: {}", v),
+ }
+ }
+
+ // Change the waiter list to which this `Waiter` belongs. This will panic if called when the
+ // `Waiter` is still linked into a waiter list.
+ pub fn set_waiting_for(&self, waiting_for: WaitingFor) {
+ self.waiting_for.store(waiting_for as u8, Ordering::Release);
+ }
+
+ // Reset the Waiter back to its initial state. Panics if this `Waiter` is still linked into a
+ // waiter list.
+ pub fn reset(&self, waiting_for: WaitingFor) {
+ debug_assert!(!self.is_linked(), "Cannot reset `Waiter` while linked");
+ self.set_waiting_for(waiting_for);
+
+ let mut state = self.state.lock();
+ if let State::Waiting(waker) = mem::replace(&mut *state, State::Init) {
+ mem::drop(state);
+ mem::drop(waker);
+ }
+ }
+
+ // Wait until woken up by another thread.
+ pub fn wait(&self) -> WaitFuture<'_> {
+ WaitFuture { waiter: self }
+ }
+
+ // Wake up the thread associated with this `Waiter`. Panics if `waiting_for()` does not return
+ // `WaitingFor::None` or if `is_linked()` returns true.
+ pub fn wake(&self) {
+ debug_assert!(!self.is_linked(), "Cannot wake `Waiter` while linked");
+ debug_assert_eq!(self.is_waiting_for(), WaitingFor::None);
+
+ let mut state = self.state.lock();
+
+ if let State::Waiting(waker) = mem::replace(&mut *state, State::Woken) {
+ mem::drop(state);
+ waker.wake();
+ }
+ }
+}
+
+pub struct WaitFuture<'w> {
+ waiter: &'w Waiter,
+}
+
+impl<'w> Future for WaitFuture<'w> {
+ type Output = ();
+
+ fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ let mut state = self.waiter.state.lock();
+
+ match mem::replace(&mut *state, State::Processing) {
+ State::Init => {
+ *state = State::Waiting(cx.waker().clone());
+
+ Poll::Pending
+ }
+ State::Waiting(old_waker) => {
+ *state = State::Waiting(cx.waker().clone());
+ mem::drop(state);
+ mem::drop(old_waker);
+
+ Poll::Pending
+ }
+ State::Woken => {
+ *state = State::Finished;
+ Poll::Ready(())
+ }
+ State::Finished => {
+ panic!("Future polled after returning Poll::Ready");
+ }
+ State::Processing => {
+ panic!("Unexpected waker state");
+ }
+ }
+ }
+}
+
+impl<'w> Drop for WaitFuture<'w> {
+ fn drop(&mut self) {
+ let state = self.waiter.state.lock();
+
+ match *state {
+ State::Finished => {}
+ State::Processing => panic!("Unexpected waker state"),
+ State::Woken => {
+ mem::drop(state);
+
+ // We were woken but not polled. Wake up the next waiter.
+ (self.waiter.cancel)(self.waiter.cancel_data, self.waiter, true);
+ }
+ _ => {
+ mem::drop(state);
+
+ // Not woken. No need to wake up any waiters.
+ (self.waiter.cancel)(self.waiter.cancel_data, self.waiter, false);
+ }
+ }
+ }
+}
+
+intrusive_adapter!(pub WaiterAdapter = Arc<Waiter>: Waiter { link: AtomicLink });
+
+pub type WaiterList = LinkedList<WaiterAdapter>;
diff --git a/common/cros_asyncv2/src/timer.rs b/common/cros_asyncv2/src/timer.rs
new file mode 100644
index 000000000..dff7fc923
--- /dev/null
+++ b/common/cros_asyncv2/src/timer.rs
@@ -0,0 +1,309 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ future::Future,
+ io,
+ pin::Pin,
+ task::{Context, Poll},
+ time::Instant,
+};
+
+use futures::{
+ future::{select, Either},
+ pin_mut,
+};
+use thiserror::Error as ThisError;
+
+use crate::executor;
+
+/// A timer that expires at a specific time.
+#[derive(Debug, Clone)]
+pub struct Timer {
+ deadline: Instant,
+}
+
+impl Timer {
+ /// Start a timer that will expire at `deadline`.
+ ///
+ /// This function only guarantees that the timer will not expire before `deadline`. The actual
+ /// elapsed time may be much longer depending on various factors such as the current load in the
+ /// application as well as the OS scheduler.
+ ///
+ /// If `deadline` is in the future then any tasks await-ing on this `Timer` will only be
+ /// notified if it is created on a thread that is currently running or will run the
+ /// `Executor::run` or `Executor::run_until` methods.
+ ///
+ /// The returned `Timer` can be cheaply cloned and all clones will share the same deadline.
+ ///
+ /// # Examples
+ ///
+ /// Put the current task to sleep for 10 milliseconds.
+ ///
+ /// ```
+ /// # use std::time::{Duration, Instant};
+ /// # use cros_async::{Timer, Executor};
+ /// #
+ /// # async fn sleep() {
+ /// Timer::new(Instant::now() + Duration::from_millis(10)).await;
+ /// # }
+ /// #
+ /// # let ex = Executor::new();
+ /// # let start = Instant::now();
+ /// # ex.run_until(sleep()).unwrap();
+ /// # assert!(start.elapsed() >= Duration::from_millis(10));
+ /// ```
+ pub fn new(deadline: Instant) -> Timer {
+ Timer { deadline }
+ }
+
+ /// Returns the time at which this `Timer` expires.
+ pub fn deadline(&self) -> Instant {
+ self.deadline
+ }
+}
+
+impl Future for Timer {
+ type Output = ();
+
+ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ if self.deadline <= Instant::now() {
+ Poll::Ready(())
+ } else {
+ executor::add_timer(self.deadline, cx.waker());
+
+ Poll::Pending
+ }
+ }
+}
+
+/// The error returned from `with_deadline` when the deadline expires before the future completes.
+///
+/// # Examples
+///
+/// Convert the `TimedOut` error into an `io::Error`.
+///
+/// ```
+/// # use std::{
+/// # future::{pending, Future},
+/// # io,
+/// # time::Instant
+/// # };
+/// use cros_async::with_deadline;
+///
+/// async fn deadline_with_io_error<F: Future>(deadline: Instant, f: F) -> io::Result<F::Output> {
+/// with_deadline(deadline, f)
+/// .await
+/// .map_err(io::Error::from)
+/// }
+/// # let err = cros_async::Executor::new()
+/// # .run_until(deadline_with_io_error(Instant::now(), pending::<()>()))
+/// # .unwrap()
+/// # .unwrap_err();
+/// # assert_eq!(err.kind(), io::ErrorKind::TimedOut);
+/// ```
+#[derive(Debug, ThisError)]
+#[error("Operation timed out")]
+pub struct TimedOut;
+
+impl From<TimedOut> for io::Error {
+ fn from(_: TimedOut) -> Self {
+ io::Error::from(io::ErrorKind::TimedOut)
+ }
+}
+
+/// Add a deadline to an asynchronous operation.
+///
+/// Returns the output of the asynchronous operation if it completes before the deadline
+/// expires. Otherwise returns a `TimedOut` error.
+///
+/// If the deadline expires before the asynchronous operation completes then `f` is dropped.
+/// However, this may not cancel any underlying asynchronous I/O operations registered with the OS.
+///
+/// # Examples
+///
+/// Set a timeout for reading from a data source.
+///
+/// ```
+/// use std::time::{Duration, Instant};
+///
+/// use cros_async::{with_deadline, File};
+///
+/// async fn read_with_timeout(
+/// rx: &File,
+/// buf: &mut [u8],
+/// timeout: Duration,
+/// ) -> anyhow::Result<usize> {
+/// with_deadline(Instant::now() + timeout, rx.read(buf, None)).await?
+/// }
+/// #
+/// # use std::io;
+/// # use cros_async::{Executor, TimedOut};
+/// #
+/// # let ex = Executor::new();
+/// # let (rx, _tx) = sys_util::pipe(true).unwrap();
+/// # let rx = cros_async::File::from_std(rx).unwrap();
+/// # let mut buf = 0u64.to_ne_bytes();
+/// #
+/// # let _err = ex
+/// # .run_until(read_with_timeout(&rx, &mut buf, Duration::from_millis(10)))
+/// # .unwrap()
+/// # .unwrap_err()
+/// # .downcast::<TimedOut>()
+/// # .unwrap();
+/// ```
+pub async fn with_deadline<F: Future>(deadline: Instant, f: F) -> Result<F::Output, TimedOut> {
+ let timer = Timer::new(deadline);
+ pin_mut!(timer, f);
+ match select(timer, f).await {
+ Either::Left(((), _)) => Err(TimedOut),
+ Either::Right((v, _)) => Ok(v),
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use std::{mem, sync::Arc, task, thread, time::Duration};
+
+ use futures::{future::join5, stream::FuturesUnordered, StreamExt};
+ use sync::Mutex;
+
+ use crate::Executor;
+
+ #[test]
+ fn basic() {
+ let ex = Executor::new();
+
+ let dur = Duration::from_millis(5);
+ let start = Instant::now();
+ let sleep = Timer::new(start + dur);
+
+ ex.run_until(sleep).unwrap();
+
+ assert!(start.elapsed() >= dur);
+ }
+
+ #[test]
+ fn multiple() {
+ let ex = Executor::new();
+
+ let start = Instant::now();
+ let t1 = Timer::new(start + Duration::from_millis(10));
+ let t2 = Timer::new(start + Duration::from_secs(10));
+
+ match ex.run_until(select(t1, t2)).unwrap() {
+ Either::Left(_) => {
+ let elapsed = start.elapsed();
+ assert!(elapsed >= Duration::from_millis(10));
+ assert!(elapsed < Duration::from_secs(10));
+ }
+ Either::Right(_) => panic!("Longer deadline finished first"),
+ }
+ }
+
+ #[test]
+ fn run_until_identical_deadline() {
+ let ex = Executor::new();
+
+ let start = Instant::now();
+ let deadline = start + Duration::from_millis(10);
+ let t1 = Timer::new(deadline);
+ let t2 = Timer::new(deadline);
+ let t3 = Timer::new(deadline);
+ let t4 = Timer::new(deadline);
+ let t5 = Timer::new(deadline);
+
+ ex.run_until(join5(t1, t2, t3, t4, t5)).unwrap();
+ assert!(deadline <= Instant::now());
+ }
+
+ #[test]
+ fn spawn_identical_deadline() {
+ let ex = Executor::new();
+
+ let start = Instant::now();
+ let deadline = start + Duration::from_millis(10);
+ let t1 = ex.spawn(Timer::new(deadline));
+ let t2 = ex.spawn(Timer::new(deadline));
+ let t3 = ex.spawn(Timer::new(deadline));
+ let t4 = ex.spawn(Timer::new(deadline));
+ let t5 = ex.spawn(Timer::new(deadline));
+
+ ex.run_until(join5(t1, t2, t3, t4, t5)).unwrap();
+ assert!(deadline <= Instant::now());
+ }
+
+ #[derive(Default)]
+ struct QuitShared {
+ wakers: Vec<task::Waker>,
+ should_quit: bool,
+ }
+
+ #[derive(Clone, Default)]
+ struct Quit {
+ shared: Arc<Mutex<QuitShared>>,
+ }
+
+ impl Quit {
+ fn quit(self) {
+ let wakers = {
+ let mut shared = self.shared.lock();
+ shared.should_quit = true;
+ mem::take(&mut shared.wakers)
+ };
+
+ for w in wakers {
+ w.wake();
+ }
+ }
+ }
+
+ impl Future for Quit {
+ type Output = ();
+
+ fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
+ let mut shared = self.shared.lock();
+ if shared.should_quit {
+ return Poll::Ready(());
+ }
+
+ if shared.wakers.iter().all(|w| !w.will_wake(cx.waker())) {
+ shared.wakers.push(cx.waker().clone());
+ }
+
+ Poll::Pending
+ }
+ }
+
+ #[test]
+ fn multiple_threads() {
+ const NUM_THREADS: usize = 7;
+ const NUM_TIMERS: usize = 19;
+
+ let ex = Executor::new();
+ let quit = Quit::default();
+ let mut threads = Vec::with_capacity(NUM_THREADS);
+ for _ in 0..NUM_THREADS {
+ let thread_ex = ex.clone();
+ let thread_quit = quit.clone();
+ threads.push(thread::spawn(move || thread_ex.run_until(thread_quit)));
+ }
+
+ let start = Instant::now();
+ let timers = FuturesUnordered::new();
+ let deadline = start + Duration::from_millis(10);
+ for _ in 0..NUM_TIMERS {
+ timers.push(ex.spawn(Timer::new(deadline)));
+ }
+
+ ex.run_until(timers.collect::<Vec<()>>()).unwrap();
+ quit.quit();
+
+ for t in threads {
+ t.join().unwrap().unwrap();
+ }
+ }
+}
diff --git a/common/cros_asyncv2/src/unix/descriptor.rs b/common/cros_asyncv2/src/unix/descriptor.rs
new file mode 100644
index 000000000..906d0a053
--- /dev/null
+++ b/common/cros_asyncv2/src/unix/descriptor.rs
@@ -0,0 +1,35 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::sync::Arc;
+
+use sys_util::SafeDescriptor;
+
+use super::io_driver;
+
+/// An async version of SafeDescriptor.
+// TODO: Remove this once we have converted all users to use the appropriate concrete async types.
+pub struct Descriptor(Arc<SafeDescriptor>);
+
+impl Descriptor {
+ /// Creates a new `AsyncDescriptor` in a nonblocking state.
+ pub fn new(fd: SafeDescriptor) -> anyhow::Result<Descriptor> {
+ io_driver::prepare(&fd)?;
+ Ok(Descriptor(Arc::new(fd)))
+ }
+
+ /// Waits for `self` to become readable. This function is edge-triggered rather than
+ /// level-triggered so callers should make sure that the underlying descriptor has been fully
+ /// drained before calling this method.
+ pub async fn wait_readable(&self) -> anyhow::Result<()> {
+ io_driver::wait_readable(&self.0).await
+ }
+
+ /// Waits for `self` to become writable. This function is edge-triggered rather than
+ /// level-triggered so callers should make sure that the underlying descriptor is actually full
+ /// before calling this method.
+ pub async fn wait_writable(&self) -> anyhow::Result<()> {
+ io_driver::wait_writable(&self.0).await
+ }
+}
diff --git a/common/cros_asyncv2/src/unix/event.rs b/common/cros_asyncv2/src/unix/event.rs
new file mode 100644
index 000000000..275544fe8
--- /dev/null
+++ b/common/cros_asyncv2/src/unix/event.rs
@@ -0,0 +1,67 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{convert::TryFrom, io, mem::size_of, sync::Arc};
+
+use anyhow::{ensure, Context};
+use sys_util::{EventFd, SafeDescriptor};
+
+use super::io_driver;
+
+#[derive(Debug)]
+pub struct Event {
+ fd: Arc<SafeDescriptor>,
+}
+
+impl Event {
+ pub fn new() -> anyhow::Result<Event> {
+ EventFd::new()
+ .map_err(io::Error::from)
+ .context("failed to create eventfd")
+ .and_then(Event::try_from)
+ }
+
+ pub async fn next_val(&self) -> anyhow::Result<u64> {
+ let mut buf = 0u64.to_ne_bytes();
+ let count = io_driver::read(&self.fd, &mut buf, None).await?;
+
+ ensure!(
+ count == size_of::<u64>(),
+ io::Error::from(io::ErrorKind::UnexpectedEof)
+ );
+
+ Ok(u64::from_ne_bytes(buf))
+ }
+
+ pub async fn notify(&self) -> anyhow::Result<()> {
+ let buf = 1u64.to_ne_bytes();
+ let count = io_driver::write(&self.fd, &buf, None).await?;
+
+ ensure!(
+ count == size_of::<u64>(),
+ io::Error::from(io::ErrorKind::WriteZero)
+ );
+
+ Ok(())
+ }
+
+ pub fn try_clone(&self) -> anyhow::Result<Event> {
+ self.fd
+ .try_clone()
+ .map(|fd| Event { fd: Arc::new(fd) })
+ .map_err(io::Error::from)
+ .map_err(From::from)
+ }
+}
+
+impl TryFrom<EventFd> for Event {
+ type Error = anyhow::Error;
+
+ fn try_from(evt: EventFd) -> anyhow::Result<Event> {
+ io_driver::prepare(&evt)?;
+ Ok(Event {
+ fd: Arc::new(SafeDescriptor::from(evt)),
+ })
+ }
+}
diff --git a/common/cros_asyncv2/src/unix/file.rs b/common/cros_asyncv2/src/unix/file.rs
new file mode 100644
index 000000000..a6fbee315
--- /dev/null
+++ b/common/cros_asyncv2/src/unix/file.rs
@@ -0,0 +1,145 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{cmp::min, convert::TryFrom, fs::File as StdFile, io, path::Path, sync::Arc};
+
+use sys_util::{AsRawDescriptor, SafeDescriptor};
+
+use super::io_driver;
+use crate::{AsIoBufs, OwnedIoBuf};
+
+#[derive(Debug)]
+pub struct File {
+ fd: Arc<SafeDescriptor>,
+}
+
+impl File {
+ pub fn open<P: AsRef<Path>>(p: P) -> anyhow::Result<File> {
+ let f = StdFile::open(p)?;
+ File::try_from(f)
+ }
+
+ pub fn create<P: AsRef<Path>>(p: P) -> anyhow::Result<File> {
+ let f = StdFile::create(p)?;
+ File::try_from(f)
+ }
+
+ pub async fn read(&self, buf: &mut [u8], offset: Option<u64>) -> anyhow::Result<usize> {
+ io_driver::read(&self.fd, buf, offset).await
+ }
+
+ pub async fn read_iobuf<B: AsIoBufs + Unpin + 'static>(
+ &self,
+ buf: B,
+ offset: Option<u64>,
+ ) -> (anyhow::Result<usize>, B) {
+ io_driver::read_iobuf(&self.fd, buf, offset).await
+ }
+
+ pub async fn write(&self, buf: &[u8], offset: Option<u64>) -> anyhow::Result<usize> {
+ io_driver::write(&self.fd, buf, offset).await
+ }
+
+ pub async fn write_iobuf<B: AsIoBufs + Unpin + 'static>(
+ &self,
+ buf: B,
+ offset: Option<u64>,
+ ) -> (anyhow::Result<usize>, B) {
+ io_driver::write_iobuf(&self.fd, buf, offset).await
+ }
+
+ pub async fn punch_hole(&self, offset: u64, len: u64) -> anyhow::Result<()> {
+ io_driver::fallocate(
+ &self.fd,
+ offset,
+ len,
+ (libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE) as u32,
+ )
+ .await
+ }
+
+ pub async fn write_zeroes(&self, offset: u64, len: u64) -> anyhow::Result<()> {
+ let res = io_driver::fallocate(
+ &self.fd,
+ offset,
+ len,
+ (libc::FALLOC_FL_ZERO_RANGE | libc::FALLOC_FL_KEEP_SIZE) as u32,
+ )
+ .await;
+ match res {
+ Ok(()) => Ok(()),
+ Err(_) => {
+ // Fall back to writing zeros if fallocate doesn't work.
+ let buf_size = min(len, 0x10000);
+ let mut buf = OwnedIoBuf::new(vec![0u8; buf_size as usize]);
+ let mut nwritten = 0;
+ while nwritten < len {
+ buf.reset();
+ let remaining = len - nwritten;
+ let write_size = min(remaining, buf_size) as usize;
+ buf.truncate(write_size);
+
+ let (res, b) = self.write_iobuf(buf, Some(offset + nwritten as u64)).await;
+ nwritten += res? as u64;
+ buf = b;
+ }
+ Ok(())
+ }
+ }
+ }
+
+ pub async fn allocate(&self, offset: u64, len: u64) -> anyhow::Result<()> {
+ io_driver::fallocate(&self.fd, offset, len, 0).await
+ }
+
+ pub async fn get_len(&self) -> anyhow::Result<u64> {
+ io_driver::stat(&self.fd).await.map(|st| st.st_size as u64)
+ }
+
+ pub async fn set_len(&self, len: u64) -> anyhow::Result<()> {
+ io_driver::ftruncate(&self.fd, len).await
+ }
+
+ pub async fn sync_all(&self) -> anyhow::Result<()> {
+ io_driver::fsync(&self.fd, false).await
+ }
+
+ pub async fn sync_data(&self) -> anyhow::Result<()> {
+ io_driver::fsync(&self.fd, true).await
+ }
+
+ pub fn try_clone(&self) -> anyhow::Result<File> {
+ self.fd
+ .try_clone()
+ .map(|fd| File { fd: Arc::new(fd) })
+ .map_err(io::Error::from)
+ .map_err(From::from)
+ }
+}
+
+impl TryFrom<StdFile> for File {
+ type Error = anyhow::Error;
+
+ fn try_from(f: StdFile) -> anyhow::Result<Self> {
+ io_driver::prepare(&f)?;
+ Ok(File {
+ fd: Arc::new(f.into()),
+ })
+ }
+}
+
+impl TryFrom<File> for StdFile {
+ type Error = File;
+ fn try_from(f: File) -> Result<StdFile, File> {
+ Arc::try_unwrap(f.fd)
+ .map(From::from)
+ .map_err(|fd| File { fd })
+ }
+}
+
+impl AsRawDescriptor for File {
+ fn as_raw_descriptor(&self) -> sys_util::RawDescriptor {
+ self.fd.as_raw_descriptor()
+ }
+}
diff --git a/common/cros_asyncv2/src/unix/io_driver.rs b/common/cros_asyncv2/src/unix/io_driver.rs
new file mode 100644
index 000000000..b41fef6e2
--- /dev/null
+++ b/common/cros_asyncv2/src/unix/io_driver.rs
@@ -0,0 +1,350 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ os::unix::io::RawFd,
+ sync::{
+ atomic::{AtomicI32, Ordering},
+ Arc,
+ },
+ time::Duration,
+};
+
+use futures::task::{waker_ref, ArcWake, WakerRef};
+use once_cell::sync::OnceCell;
+use sys_util::{error, AsRawDescriptor, SafeDescriptor};
+
+use crate::{executor, AsIoBufs};
+
+mod cmsg;
+mod mio;
+
+#[cfg(feature = "uring")]
+#[allow(unused_variables)]
+mod uring;
+
+#[cfg(feature = "uring")]
+use uring::use_uring;
+
+// Indicates that the IO driver is either within or about to make a blocking syscall. When a waker
+// sees this value, it will wake the associated waker, which will cause the blocking syscall to
+// return.
+const WAITING: i32 = 0x1d5b_c019u32 as i32;
+
+// Indicates that the executor is processing any futures that are ready to run.
+const PROCESSING: i32 = 0xd474_77bcu32 as i32;
+
+// Indicates that one or more futures may be ready to make progress.
+const WOKEN: i32 = 0x3e4d_3276u32 as i32;
+
+enum PlatformWaker {
+ Mio(::mio::Waker),
+ #[cfg(feature = "uring")]
+ Uring(uring::Waker),
+}
+
+impl PlatformWaker {
+ fn new() -> anyhow::Result<Self> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::new_waker().map(PlatformWaker::Uring);
+ }
+
+ mio::new_waker().map(PlatformWaker::Mio)
+ }
+
+ fn wake(&self) -> anyhow::Result<()> {
+ use PlatformWaker::*;
+ match self {
+ Mio(waker) => waker.wake().map_err(From::from),
+ #[cfg(feature = "uring")]
+ Uring(waker) => waker.wake(),
+ }
+ }
+}
+
+struct State {
+ waker: PlatformWaker,
+ state: AtomicI32,
+}
+
+impl State {
+ fn new() -> anyhow::Result<Arc<State>> {
+ Ok(Arc::new(State {
+ waker: PlatformWaker::new()?,
+ state: AtomicI32::new(PROCESSING),
+ }))
+ }
+}
+
+impl ArcWake for State {
+ fn wake_by_ref(arc_self: &Arc<Self>) {
+ let oldstate = arc_self.state.swap(WOKEN, Ordering::AcqRel);
+ if oldstate == WAITING {
+ if let Err(e) = arc_self.waker.wake() {
+ error!("Failed to wake executor thread: {:#}", e);
+ }
+ }
+ }
+}
+
+impl executor::PlatformState for Arc<State> {
+ fn start_processing(&self) -> bool {
+ self.state.swap(PROCESSING, Ordering::AcqRel) == WOKEN
+ }
+
+ fn waker_ref(&self) -> WakerRef {
+ waker_ref(self)
+ }
+
+ fn wait(&self, timeout: Option<Duration>) -> anyhow::Result<()> {
+ let oldstate =
+ self.state
+ .compare_exchange(PROCESSING, WAITING, Ordering::AcqRel, Ordering::Acquire);
+ if let Err(oldstate) = oldstate {
+ debug_assert_eq!(oldstate, WOKEN);
+ return Ok(());
+ }
+
+ // Wait for more events.
+ wait(timeout)?;
+
+ // Set the state to `WOKEN` to prevent unnecessary writes to the inner `PlatformWaker`.
+ match self.state.swap(WOKEN, Ordering::AcqRel) {
+ WOKEN => {} // One or more futures have become runnable.
+ WAITING => {} // Timed out or IO completed.
+ n => panic!("Unexpected waker state: {}", n),
+ }
+
+ // Wake up any tasks that are ready.
+ dispatch()
+ }
+}
+
+thread_local! (static THREAD_STATE: OnceCell<Arc<State>> = OnceCell::new() );
+
+fn wait(timeout: Option<Duration>) -> anyhow::Result<()> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::wait(timeout);
+ }
+
+ mio::wait(timeout)
+}
+
+fn dispatch() -> anyhow::Result<()> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::dispatch();
+ }
+
+ mio::dispatch()
+}
+
+pub(crate) fn platform_state() -> anyhow::Result<impl executor::PlatformState> {
+ THREAD_STATE.with(|state| state.get_or_try_init(State::new).map(Arc::clone))
+}
+
+pub fn prepare(fd: &dyn AsRawDescriptor) -> anyhow::Result<()> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::prepare(fd);
+ }
+
+ mio::prepare(fd)
+}
+
+pub async fn read(
+ desc: &Arc<SafeDescriptor>,
+ buf: &mut [u8],
+ offset: Option<u64>,
+) -> anyhow::Result<usize> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::read(desc, buf, offset).await;
+ }
+
+ mio::read(desc, buf, offset).await
+}
+
+pub async fn read_iobuf<B: AsIoBufs + Unpin + 'static>(
+ desc: &Arc<SafeDescriptor>,
+ buf: B,
+ offset: Option<u64>,
+) -> (anyhow::Result<usize>, B) {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::read_iobuf(desc, buf, offset).await;
+ }
+
+ mio::read_iobuf(desc, buf, offset).await
+}
+
+pub async fn write(
+ desc: &Arc<SafeDescriptor>,
+ buf: &[u8],
+ offset: Option<u64>,
+) -> anyhow::Result<usize> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::write(desc, buf, offset).await;
+ }
+
+ mio::write(desc, buf, offset).await
+}
+
+pub async fn write_iobuf<B: AsIoBufs + Unpin + 'static>(
+ desc: &Arc<SafeDescriptor>,
+ buf: B,
+ offset: Option<u64>,
+) -> (anyhow::Result<usize>, B) {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::write_iobuf(desc, buf, offset).await;
+ }
+
+ mio::write_iobuf(desc, buf, offset).await
+}
+
+pub async fn fallocate(
+ desc: &Arc<SafeDescriptor>,
+ file_offset: u64,
+ len: u64,
+ mode: u32,
+) -> anyhow::Result<()> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::fallocate(desc, file_offset, len, mode).await;
+ }
+
+ mio::fallocate(desc, file_offset, len, mode).await
+}
+
+pub async fn ftruncate(desc: &Arc<SafeDescriptor>, len: u64) -> anyhow::Result<()> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::ftruncate(desc, len).await;
+ }
+
+ mio::ftruncate(desc, len).await
+}
+
+pub async fn stat(desc: &Arc<SafeDescriptor>) -> anyhow::Result<libc::stat64> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::stat(desc).await;
+ }
+
+ mio::stat(desc).await
+}
+
+pub async fn fsync(desc: &Arc<SafeDescriptor>, datasync: bool) -> anyhow::Result<()> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::fsync(desc, datasync).await;
+ }
+
+ mio::fsync(desc, datasync).await
+}
+
+pub async fn connect(
+ desc: &Arc<SafeDescriptor>,
+ addr: libc::sockaddr_un,
+ len: libc::socklen_t,
+) -> anyhow::Result<()> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::connect(desc, addr, len).await;
+ }
+
+ mio::connect(desc, addr, len).await
+}
+
+pub async fn next_packet_size(desc: &Arc<SafeDescriptor>) -> anyhow::Result<usize> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::next_packet_size(desc).await;
+ }
+
+ mio::next_packet_size(desc).await
+}
+
+pub async fn sendmsg(
+ desc: &Arc<SafeDescriptor>,
+ buf: &[u8],
+ fds: &[RawFd],
+) -> anyhow::Result<usize> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::sendmsg(desc, buf, fds).await;
+ }
+
+ mio::sendmsg(desc, buf, fds).await
+}
+
+pub async fn recvmsg(
+ desc: &Arc<SafeDescriptor>,
+ buf: &mut [u8],
+ fds: &mut [RawFd],
+) -> anyhow::Result<(usize, usize)> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::recvmsg(desc, buf, fds).await;
+ }
+
+ mio::recvmsg(desc, buf, fds).await
+}
+
+pub async fn send_iobuf_with_fds<B: AsIoBufs + Unpin + 'static>(
+ desc: &Arc<SafeDescriptor>,
+ buf: B,
+ fds: &[RawFd],
+) -> (anyhow::Result<usize>, B) {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::send_iobuf_with_fds(desc, buf, fds).await;
+ }
+
+ mio::send_iobuf_with_fds(desc, buf, fds).await
+}
+
+pub async fn recv_iobuf_with_fds<B: AsIoBufs + Unpin + 'static>(
+ desc: &Arc<SafeDescriptor>,
+ buf: B,
+ fds: &mut [RawFd],
+) -> (anyhow::Result<(usize, usize)>, B) {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::recv_iobuf_with_fds(desc, buf, fds).await;
+ }
+
+ mio::recv_iobuf_with_fds(desc, buf, fds).await
+}
+
+pub async fn accept(desc: &Arc<SafeDescriptor>) -> anyhow::Result<SafeDescriptor> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::accept(desc).await;
+ }
+
+ mio::accept(desc).await
+}
+
+pub async fn wait_readable(desc: &Arc<SafeDescriptor>) -> anyhow::Result<()> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::wait_readable(desc).await;
+ }
+
+ mio::wait_readable(desc).await
+}
+
+pub async fn wait_writable(desc: &Arc<SafeDescriptor>) -> anyhow::Result<()> {
+ #[cfg(feature = "uring")]
+ if use_uring() {
+ return uring::wait_writable(desc).await;
+ }
+
+ mio::wait_writable(desc).await
+}
diff --git a/common/cros_asyncv2/src/unix/io_driver/cmsg.rs b/common/cros_asyncv2/src/unix/io_driver/cmsg.rs
new file mode 100644
index 000000000..34d4c8cc1
--- /dev/null
+++ b/common/cros_asyncv2/src/unix/io_driver/cmsg.rs
@@ -0,0 +1,203 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ alloc::Layout,
+ cmp::min,
+ convert::TryFrom,
+ io,
+ mem::{align_of, size_of},
+ os::unix::io::RawFd,
+};
+
+use anyhow::anyhow;
+use sys_util::LayoutAllocation;
+
+// Allocates a buffer to hold a `libc::cmsghdr` with `cap` bytes of data.
+//
+// Returns the `LayoutAllocation` for the buffer as well as the size of the allocation, which is
+// guaranteed to be at least `size_of::<libc::cmsghdr>() + cap` bytes.
+pub fn allocate_cmsg_buffer(cap: u32) -> anyhow::Result<(LayoutAllocation, usize)> {
+ // Not sure why this is unsafe.
+ let cmsg_cap = usize::try_from(unsafe { libc::CMSG_SPACE(cap) })
+ .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
+ let alloc = Layout::from_size_align(cmsg_cap, align_of::<libc::cmsghdr>())
+ .map(LayoutAllocation::zeroed)
+ .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
+
+ Ok((alloc, cmsg_cap))
+}
+
+// Adds a control message with the file descriptors from `fds` to `msg`.
+// Note: this doesn't append but expects no cmsg already set and puts `fds` as a
+// single `cmsg` inside passed `msg`
+//
+// Returns the `LayoutAllocation` backing the control message.
+pub fn add_fds_to_message(
+ msg: &mut libc::msghdr,
+ fds: &[RawFd],
+) -> anyhow::Result<LayoutAllocation> {
+ let fd_len = fds
+ .len()
+ .checked_mul(size_of::<RawFd>())
+ .and_then(|l| u32::try_from(l).ok())
+ .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput))?;
+
+ let (cmsg_buffer, cmsg_cap) = allocate_cmsg_buffer(fd_len)?;
+
+ if !msg.msg_control.is_null() {
+ anyhow::bail!("msg already contains cmsg");
+ }
+
+ msg.msg_control = cmsg_buffer.as_ptr();
+ msg.msg_controllen = cmsg_cap;
+
+ unsafe {
+ // Safety:
+ // * CMSG_FIRSTHDR will either return a null pointer or a pointer to `msg.msg_control`.
+ // * `msg.msg_control` is properly aligned because `cmsg_buffer` is properly aligned.
+ // * The buffer is zeroed, which is a valid bit-pattern for `libc::cmsghdr`.
+ // * The reference does not escape this function.
+ let cmsg = libc::CMSG_FIRSTHDR(msg).as_mut().unwrap();
+ cmsg.cmsg_len = libc::CMSG_LEN(fd_len) as libc::size_t;
+ cmsg.cmsg_level = libc::SOL_SOCKET;
+ cmsg.cmsg_type = libc::SCM_RIGHTS;
+
+ // Safety: `libc::CMSG_DATA(cmsg)` and `fds` are valid for `fd_len` bytes of memory.
+ libc::memcpy(
+ libc::CMSG_DATA(cmsg).cast(),
+ fds.as_ptr().cast(),
+ fd_len as usize,
+ );
+ }
+
+ Ok(cmsg_buffer)
+}
+
+// Copies file descriptors from the control message in `msg` into `fds`.
+//
+// Returns the number of file descriptors that were copied from `msg`.
+pub fn take_fds_from_message(msg: &libc::msghdr, fds: &mut [RawFd]) -> anyhow::Result<usize> {
+ let cap = fds
+ .len()
+ .checked_mul(size_of::<RawFd>())
+ .ok_or_else(|| anyhow!(io::Error::from(io::ErrorKind::InvalidInput)))?;
+
+ let mut rem = cap;
+ let mut fd_pos = 0;
+ unsafe {
+ let mut cmsg = libc::CMSG_FIRSTHDR(msg);
+
+ // Safety:
+ // * CMSG_FIRSTHDR will either return a null pointer or a pointer to `msg.msg_control`.
+ // * `msg.msg_control` is properly aligned because it was allocated by `allocate_cmsg_buffer`.
+ // * The buffer was zero-initialized, which is a valid bit-pattern for `libc::cmsghdr`.
+ // * The reference does not escape this function.
+ while let Some(current) = cmsg.as_ref() {
+ if current.cmsg_level != libc::SOL_SOCKET || current.cmsg_type != libc::SCM_RIGHTS {
+ cmsg = libc::CMSG_NXTHDR(msg, cmsg);
+ continue;
+ }
+
+ let data_len = min(current.cmsg_len - libc::CMSG_LEN(0) as usize, rem);
+
+ // Safety: `fds` and `libc::CMSG_DATA(cmsg)` are valid for `data_len` bytes of memory.
+ libc::memcpy(
+ fds[fd_pos..].as_mut_ptr().cast(),
+ libc::CMSG_DATA(cmsg).cast(),
+ data_len,
+ );
+ rem -= data_len;
+ fd_pos += data_len / size_of::<RawFd>();
+ if rem == 0 {
+ break;
+ }
+
+ cmsg = libc::CMSG_NXTHDR(msg, cmsg);
+ }
+ }
+
+ Ok((cap - rem) / size_of::<RawFd>())
+}
+
+#[cfg(test)]
+mod tests {
+ use std::ptr;
+
+ use super::*;
+
+ #[test]
+ #[cfg(not(target_arch = "arm"))]
+ fn test_add_fds_to_message() {
+ let buf = [0xEAu8, 0xDD, 0xAA, 0xCC];
+ let mut iov = libc::iovec {
+ iov_base: buf.as_ptr() as *const libc::c_void as *mut libc::c_void,
+ iov_len: buf.len() as libc::size_t,
+ };
+
+ let fds = [0xDE, 0xAD, 0xBE, 0xEF];
+ let mut msg = libc::msghdr {
+ msg_name: ptr::null_mut(),
+ msg_namelen: 0,
+ msg_iov: &mut iov,
+ msg_iovlen: 1,
+ msg_flags: 0,
+ msg_control: ptr::null_mut(),
+ msg_controllen: 0,
+ };
+
+ let cmsg_buffer = add_fds_to_message(&mut msg, &fds[..]).unwrap();
+ let expected_cmsg = [
+ 32u8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0xDE, 0, 0, 0, 0xAD, 0, 0, 0, 0xBE,
+ 0, 0, 0, 0xEF, 0, 0, 0,
+ ];
+ assert_eq!(unsafe { cmsg_buffer.as_slice::<u8>(9999) }, &expected_cmsg);
+ assert_eq!(msg.msg_controllen, unsafe {
+ cmsg_buffer.as_slice::<u8>(9999).len()
+ });
+ assert_eq!(msg.msg_control, cmsg_buffer.as_ptr());
+
+ let mut extracted_fds = [0x0i32; 4];
+
+ assert_eq!(
+ 4,
+ take_fds_from_message(&msg, &mut extracted_fds[..]).unwrap()
+ );
+
+ assert_eq!(extracted_fds, fds);
+ }
+
+ #[test]
+ #[cfg(not(target_arch = "arm"))]
+ fn test_take_fds_from_message() {
+ let buf = [0xEAu8, 0xDD, 0xAA, 0xCC];
+ let mut iov = libc::iovec {
+ iov_base: buf.as_ptr() as *const libc::c_void as *mut libc::c_void,
+ iov_len: buf.len() as libc::size_t,
+ };
+
+ let mut cmsg = [
+ 32u8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0xDE, 0, 0, 0, 0xAD, 0, 0, 0, 0xBE,
+ 0, 0, 0, 0xEF, 0, 0, 0, 32u8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0xDE, 0, 0,
+ 0, 0xAD, 0, 0, 0, 0xBE, 0, 0, 0, 0xEF, 0, 0, 0,
+ ];
+
+ let msg = libc::msghdr {
+ msg_name: ptr::null_mut(),
+ msg_namelen: 0,
+ msg_iov: &mut iov,
+ msg_iovlen: 1,
+ msg_flags: 0,
+ msg_control: cmsg.as_mut_ptr() as *mut libc::c_void,
+ msg_controllen: cmsg.len(),
+ };
+
+ let mut extracted_fds = [0x0i32; 9];
+ assert_eq!(take_fds_from_message(&msg, &mut extracted_fds).unwrap(), 8);
+ assert_eq!(
+ extracted_fds,
+ [0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, 0x00]
+ );
+ }
+}
diff --git a/common/cros_asyncv2/src/unix/io_driver/mio.rs b/common/cros_asyncv2/src/unix/io_driver/mio.rs
new file mode 100644
index 000000000..82881991e
--- /dev/null
+++ b/common/cros_asyncv2/src/unix/io_driver/mio.rs
@@ -0,0 +1,747 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ cell::RefCell,
+ convert::TryFrom,
+ future::Future,
+ io,
+ mem::{replace, size_of, MaybeUninit},
+ os::{raw::c_int, unix::io::RawFd},
+ pin::Pin,
+ ptr,
+ rc::Rc,
+ sync::Arc,
+ task,
+ time::Duration,
+};
+
+use anyhow::{anyhow, bail, ensure, Context};
+use data_model::IoBufMut;
+use mio::{unix::SourceFd, Events, Interest, Token};
+use once_cell::unsync::{Lazy, OnceCell};
+use slab::Slab;
+use sys_util::{add_fd_flags, error, AsRawDescriptor, FromRawDescriptor, SafeDescriptor};
+use thiserror::Error as ThisError;
+
+use super::cmsg::*;
+use crate::AsIoBufs;
+
+// Tokens assigned to pending operations are based on their index in a Slab and we should run out of
+// memory well before we end up with `usize::MAX` pending operations.
+const WAKER_TOKEN: Token = Token(usize::MAX);
+
+struct DriverState {
+ poll: mio::Poll,
+ events: Events,
+}
+
+impl DriverState {
+ fn new() -> anyhow::Result<RefCell<DriverState>> {
+ let poll = mio::Poll::new().context("failed to create `mio::Poll`")?;
+
+ Ok(RefCell::new(DriverState {
+ poll,
+ events: Events::with_capacity(128),
+ }))
+ }
+}
+thread_local! (static DRIVER: OnceCell<RefCell<DriverState>> = OnceCell::new());
+
+fn with_driver<F, R>(f: F) -> anyhow::Result<R>
+where
+ F: FnOnce(&mut DriverState) -> anyhow::Result<R>,
+{
+ DRIVER.with(|driver_cell| {
+ let driver = driver_cell.get_or_try_init(DriverState::new)?;
+ f(&mut driver.borrow_mut())
+ })
+}
+
+pub fn new_waker() -> anyhow::Result<mio::Waker> {
+ with_driver(|driver| {
+ mio::Waker::new(driver.poll.registry(), WAKER_TOKEN)
+ .context("failed to create `mio::Waker`")
+ })
+}
+
+// Wait for more events.
+pub fn wait(timeout: Option<Duration>) -> anyhow::Result<()> {
+ with_driver(|driver| {
+ driver
+ .poll
+ .poll(&mut driver.events, timeout)
+ .context("failed to poll for events")
+ })
+}
+
+// Wake up any tasks that are ready.
+pub fn dispatch() -> anyhow::Result<()> {
+ with_driver(|driver| {
+ OPS.with(|ops| {
+ let mut ops = ops.borrow_mut();
+ for event in driver
+ .events
+ .into_iter()
+ .filter(|e| e.token() != WAKER_TOKEN)
+ {
+ let token = event.token();
+ let op = ops.get_mut(token.0).ok_or(InvalidToken(token.0))?;
+ match replace(op, OpStatus::Ready) {
+ OpStatus::New => {}
+ OpStatus::Waiting(waker) => waker.wake(),
+ // When under heavy load the Executor will try to fetch more events even when it
+ // hasn't finished processing its run queue in order to prevent starvation of
+ // tasks waiting on IO. So it's not unreasonable to see readiness more than
+ // once. That just means the Executor hasn't been able to poll the task waiting
+ // on the IO yet.
+ OpStatus::Ready => {}
+ }
+ }
+ Ok(())
+ })
+ })
+}
+
+enum OpStatus {
+ New,
+ Waiting(task::Waker),
+ Ready,
+}
+thread_local! (static OPS: Lazy<Rc<RefCell<Slab<OpStatus>>>> = Lazy::new(Default::default));
+
+#[derive(Debug, ThisError)]
+#[error("Invalid token in events: {0}")]
+struct InvalidToken(usize);
+
+struct Op<'a> {
+ ops: Rc<RefCell<Slab<OpStatus>>>,
+ desc: &'a Arc<SafeDescriptor>,
+ idx: usize,
+}
+
+impl<'a> Future for Op<'a> {
+ type Output = ();
+
+ fn poll(self: Pin<&mut Self>, cx: &mut task::Context) -> task::Poll<Self::Output> {
+ let mut ops = self.ops.borrow_mut();
+ let status = ops.get_mut(self.idx).expect("`OpStatus` missing");
+
+ match status {
+ OpStatus::New => *status = OpStatus::Waiting(cx.waker().clone()),
+ OpStatus::Waiting(w) if !w.will_wake(cx.waker()) => {
+ *status = OpStatus::Waiting(cx.waker().clone())
+ }
+ // If `cx.waker()` and the currently stored waker are the same then no need to do
+ // anything.
+ OpStatus::Waiting(_) => {}
+ OpStatus::Ready => {
+ return task::Poll::Ready(());
+ }
+ }
+
+ task::Poll::Pending
+ }
+}
+
+impl<'a> Drop for Op<'a> {
+ fn drop(&mut self) {
+ let mut ops = self.ops.borrow_mut();
+ ops.remove(self.idx);
+
+ let res = with_driver(|driver| {
+ driver
+ .poll
+ .registry()
+ .deregister(&mut SourceFd(&self.desc.as_raw_descriptor()))
+ .context("Failed to deregister descriptor")
+ });
+
+ if let Err(e) = res {
+ error!("{}", e);
+ }
+ }
+}
+
+async fn wait_for(desc: &Arc<SafeDescriptor>, interest: Interest) -> anyhow::Result<()> {
+ let ops = OPS.with(|ops| Rc::clone(ops));
+ let idx = {
+ let mut ops_ref = ops.borrow_mut();
+ let entry = ops_ref.vacant_entry();
+ let idx = entry.key();
+ with_driver(|driver| {
+ driver
+ .poll
+ .registry()
+ .register(
+ &mut SourceFd(&desc.as_raw_descriptor()),
+ Token(idx),
+ interest,
+ )
+ .context("failed to register interest for descriptor")
+ })?;
+
+ entry.insert(OpStatus::New);
+ idx
+ };
+ Op { ops, desc, idx }.await;
+ Ok(())
+}
+
+pub async fn read(
+ desc: &Arc<SafeDescriptor>,
+ buf: &mut [u8],
+ offset: Option<u64>,
+) -> anyhow::Result<usize> {
+ loop {
+ // Safe because this will only modify `buf` and we check the return value.
+ let res = if let Some(off) = offset {
+ unsafe {
+ libc::pread64(
+ desc.as_raw_descriptor(),
+ buf.as_mut_ptr() as *mut libc::c_void,
+ buf.len(),
+ off as libc::off64_t,
+ )
+ }
+ } else {
+ unsafe {
+ libc::read(
+ desc.as_raw_descriptor(),
+ buf.as_mut_ptr() as *mut libc::c_void,
+ buf.len(),
+ )
+ }
+ };
+
+ if res >= 0 {
+ return Ok(res as usize);
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK => wait_for(desc, Interest::READABLE).await?,
+ e => return Err(io::Error::from_raw_os_error(e.errno()).into()),
+ }
+ }
+}
+
+pub async fn read_iobuf<B: AsIoBufs + 'static>(
+ desc: &Arc<SafeDescriptor>,
+ mut buf: B,
+ offset: Option<u64>,
+) -> (anyhow::Result<usize>, B) {
+ loop {
+ // Safe because this will only modify `buf` and we check the return value.
+ let res = {
+ let iovecs = IoBufMut::as_iobufs(buf.as_iobufs());
+ if let Some(off) = offset {
+ unsafe {
+ libc::preadv64(
+ desc.as_raw_descriptor(),
+ iovecs.as_ptr(),
+ iovecs.len() as c_int,
+ off as libc::off64_t,
+ )
+ }
+ } else {
+ unsafe {
+ libc::readv(
+ desc.as_raw_descriptor(),
+ iovecs.as_ptr(),
+ iovecs.len() as c_int,
+ )
+ }
+ }
+ };
+
+ if res >= 0 {
+ return (Ok(res as usize), buf);
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK => {
+ if let Err(e) = wait_for(desc, Interest::READABLE).await {
+ return (Err(e), buf);
+ }
+ }
+ e => return (Err(io::Error::from_raw_os_error(e.errno()).into()), buf),
+ }
+ }
+}
+
+pub async fn write(
+ desc: &Arc<SafeDescriptor>,
+ buf: &[u8],
+ offset: Option<u64>,
+) -> anyhow::Result<usize> {
+ loop {
+ // Safe because this will only modify `buf` and we check the return value.
+ let res = if let Some(off) = offset {
+ unsafe {
+ libc::pwrite64(
+ desc.as_raw_descriptor(),
+ buf.as_ptr() as *const libc::c_void,
+ buf.len(),
+ off as libc::off64_t,
+ )
+ }
+ } else {
+ unsafe {
+ libc::write(
+ desc.as_raw_descriptor(),
+ buf.as_ptr() as *const libc::c_void,
+ buf.len(),
+ )
+ }
+ };
+
+ if res >= 0 {
+ return Ok(res as usize);
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK => wait_for(desc, Interest::WRITABLE).await?,
+ e => return Err(io::Error::from_raw_os_error(e.errno()).into()),
+ }
+ }
+}
+
+pub async fn write_iobuf<B: AsIoBufs + 'static>(
+ desc: &Arc<SafeDescriptor>,
+ mut buf: B,
+ offset: Option<u64>,
+) -> (anyhow::Result<usize>, B) {
+ loop {
+ // Safe because this will only modify `buf` and we check the return value.
+ let res = {
+ let iovecs = IoBufMut::as_iobufs(buf.as_iobufs());
+ if let Some(off) = offset {
+ unsafe {
+ libc::pwritev64(
+ desc.as_raw_descriptor(),
+ iovecs.as_ptr(),
+ iovecs.len() as c_int,
+ off as libc::off64_t,
+ )
+ }
+ } else {
+ unsafe {
+ libc::writev(
+ desc.as_raw_descriptor(),
+ iovecs.as_ptr(),
+ iovecs.len() as c_int,
+ )
+ }
+ }
+ };
+
+ if res >= 0 {
+ return (Ok(res as usize), buf);
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK => {
+ if let Err(e) = wait_for(desc, Interest::WRITABLE).await {
+ return (Err(e), buf);
+ }
+ }
+ e => return (Err(io::Error::from_raw_os_error(e.errno()).into()), buf),
+ }
+ }
+}
+
+pub async fn fallocate(
+ desc: &Arc<SafeDescriptor>,
+ file_offset: u64,
+ len: u64,
+ mode: u32,
+) -> anyhow::Result<()> {
+ let ret = unsafe {
+ libc::fallocate64(
+ desc.as_raw_descriptor(),
+ mode as libc::c_int,
+ file_offset as libc::off64_t,
+ len as libc::off64_t,
+ )
+ };
+
+ if ret == 0 {
+ Ok(())
+ } else {
+ Err(io::Error::last_os_error().into())
+ }
+}
+
+pub async fn ftruncate(desc: &Arc<SafeDescriptor>, len: u64) -> anyhow::Result<()> {
+ let ret = unsafe { libc::ftruncate64(desc.as_raw_descriptor(), len as libc::off64_t) };
+
+ if ret == 0 {
+ Ok(())
+ } else {
+ Err(io::Error::last_os_error().into())
+ }
+}
+
+pub async fn stat(desc: &Arc<SafeDescriptor>) -> anyhow::Result<libc::stat64> {
+ let mut st = MaybeUninit::zeroed();
+
+ // Safe because this will only modify `st` and we check the return value.
+ let ret = unsafe { libc::fstat64(desc.as_raw_descriptor(), st.as_mut_ptr()) };
+
+ if ret == 0 {
+ // Safe because the kernel guarantees that `st` is now initialized.
+ Ok(unsafe { st.assume_init() })
+ } else {
+ Err(io::Error::last_os_error().into())
+ }
+}
+
+pub async fn fsync(desc: &Arc<SafeDescriptor>, datasync: bool) -> anyhow::Result<()> {
+ // TODO: If there is a lot of buffered data then this may take a long time and block the thread.
+ // Consider offloading to a BlockingPool if this becomes an issue.
+ let ret = unsafe {
+ if datasync {
+ libc::fdatasync(desc.as_raw_descriptor())
+ } else {
+ libc::fsync(desc.as_raw_descriptor())
+ }
+ };
+
+ if ret == 0 {
+ Ok(())
+ } else {
+ Err(io::Error::last_os_error().into())
+ }
+}
+
+pub async fn connect(
+ desc: &Arc<SafeDescriptor>,
+ addr: libc::sockaddr_un,
+ len: libc::socklen_t,
+) -> anyhow::Result<()> {
+ ensure!(
+ len <= size_of::<libc::sockaddr_un>() as libc::socklen_t,
+ io::Error::from_raw_os_error(libc::EINVAL)
+ );
+
+ // Safe because this will only read `len` bytes from `addr`, does not modify any memory, and we
+ // check the return value.
+ let ret =
+ unsafe { libc::connect(desc.as_raw_descriptor(), &addr as *const _ as *const _, len) };
+ if ret >= 0 {
+ return Ok(());
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK || e.errno() == libc::EINPROGRESS => {
+ wait_for(desc, Interest::WRITABLE).await?;
+
+ let mut result: c_int = 0;
+ let mut result_len = size_of::<c_int>() as libc::socklen_t;
+ // Safe because this will only modify `result` and we check the return value.
+ let ret = unsafe {
+ libc::getsockopt(
+ desc.as_raw_descriptor(),
+ libc::SOL_SOCKET,
+ libc::SO_ERROR,
+ &mut result as *mut _ as *mut libc::c_void,
+ &mut result_len,
+ )
+ };
+ if ret < 0 {
+ bail!(io::Error::last_os_error());
+ }
+
+ if result == 0 {
+ Ok(())
+ } else {
+ Err(anyhow!(io::Error::from_raw_os_error(result)))
+ }
+ }
+ e => Err(anyhow!(io::Error::from(e))),
+ }
+}
+
+pub async fn next_packet_size(desc: &Arc<SafeDescriptor>) -> anyhow::Result<usize> {
+ #[cfg(not(debug_assertions))]
+ let buf = ptr::null_mut();
+ // Work around for qemu's syscall translation which will reject null pointers in recvfrom.
+ // This only matters for running the unit tests for a non-native architecture. See the
+ // upstream thread for the qemu fix:
+ // https://lists.nongnu.org/archive/html/qemu-devel/2021-03/msg09027.html
+ #[cfg(debug_assertions)]
+ let buf = ptr::NonNull::dangling().as_ptr();
+
+ loop {
+ // Safe because this will not modify any memory and we check the return value.
+ let ret = unsafe {
+ libc::recvfrom(
+ desc.as_raw_descriptor(),
+ buf,
+ 0,
+ libc::MSG_TRUNC | libc::MSG_PEEK | libc::MSG_DONTWAIT,
+ ptr::null_mut(),
+ ptr::null_mut(),
+ )
+ };
+
+ if ret >= 0 {
+ return Ok(ret as usize);
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK || e.errno() == libc::EAGAIN => {
+ wait_for(desc, Interest::READABLE).await?;
+ }
+ e => bail!(io::Error::from(e)),
+ }
+ }
+}
+
+pub async fn sendmsg(
+ desc: &Arc<SafeDescriptor>,
+ buf: &[u8],
+ fds: &[RawFd],
+) -> anyhow::Result<usize> {
+ let mut iov = libc::iovec {
+ iov_base: buf.as_ptr() as *const libc::c_void as *mut libc::c_void,
+ iov_len: buf.len() as libc::size_t,
+ };
+
+ let mut msg = libc::msghdr {
+ msg_name: ptr::null_mut(),
+ msg_namelen: 0,
+ msg_iov: &mut iov,
+ msg_iovlen: 1,
+ msg_flags: 0,
+ msg_control: ptr::null_mut(),
+ msg_controllen: 0,
+ };
+
+ let _cmsg_buffer = if !fds.is_empty() {
+ Some(add_fds_to_message(&mut msg, fds)?)
+ } else {
+ None
+ };
+
+ loop {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe {
+ libc::sendmsg(
+ desc.as_raw_descriptor(),
+ &msg,
+ libc::MSG_NOSIGNAL | libc::MSG_DONTWAIT,
+ )
+ };
+
+ if ret >= 0 {
+ return Ok(ret as usize);
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK || e.errno() == libc::EAGAIN => {
+ wait_for(desc, Interest::WRITABLE).await?;
+ }
+ e => return Err(anyhow!(io::Error::from(e))),
+ }
+ }
+}
+
+pub async fn recvmsg(
+ desc: &Arc<SafeDescriptor>,
+ buf: &mut [u8],
+ fds: &mut [RawFd],
+) -> anyhow::Result<(usize, usize)> {
+ let mut iov = libc::iovec {
+ iov_base: buf.as_mut_ptr().cast(),
+ iov_len: buf.len() as libc::size_t,
+ };
+
+ let fd_cap = fds
+ .len()
+ .checked_mul(size_of::<RawFd>())
+ .and_then(|l| u32::try_from(l).ok())
+ .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput))?;
+ let (cmsg_buffer, cmsg_cap) = allocate_cmsg_buffer(fd_cap)?;
+
+ let mut msg = libc::msghdr {
+ msg_name: ptr::null_mut(),
+ msg_namelen: 0,
+ msg_iov: &mut iov,
+ msg_iovlen: 1,
+ msg_flags: 0,
+ msg_control: cmsg_buffer.as_ptr(),
+ msg_controllen: cmsg_cap,
+ };
+
+ let buflen = loop {
+ // Safe because this will only modify `buf` and `cmsg_buffer` and we check the return value.
+ let ret = unsafe {
+ libc::recvmsg(
+ desc.as_raw_descriptor(),
+ &mut msg,
+ libc::MSG_NOSIGNAL | libc::MSG_DONTWAIT,
+ )
+ };
+
+ if ret >= 0 {
+ break ret as usize;
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK || e.errno() == libc::EAGAIN => {
+ wait_for(desc, Interest::READABLE).await?;
+ }
+ e => return Err(anyhow!(io::Error::from(e))),
+ }
+ };
+
+ let fd_count = take_fds_from_message(&msg, fds)?;
+
+ Ok((buflen, fd_count))
+}
+
+pub async fn send_iobuf_with_fds<B: AsIoBufs + 'static>(
+ desc: &Arc<SafeDescriptor>,
+ mut buf: B,
+ fds: &[RawFd],
+) -> (anyhow::Result<usize>, B) {
+ let inner = async {
+ let iovs = IoBufMut::as_iobufs(buf.as_iobufs());
+
+ let mut msg = libc::msghdr {
+ msg_name: ptr::null_mut(),
+ msg_namelen: 0,
+ msg_iov: iovs.as_ptr() as *mut libc::iovec,
+ msg_iovlen: iovs.len(),
+ msg_flags: 0,
+ msg_control: ptr::null_mut(),
+ msg_controllen: 0,
+ };
+
+ let _cmsg_buffer = if !fds.is_empty() {
+ Some(add_fds_to_message(&mut msg, fds)?)
+ } else {
+ None
+ };
+
+ loop {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe {
+ libc::sendmsg(
+ desc.as_raw_descriptor(),
+ &msg,
+ libc::MSG_NOSIGNAL | libc::MSG_DONTWAIT,
+ )
+ };
+
+ if ret >= 0 {
+ return Ok(ret as usize);
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK || e.errno() == libc::EAGAIN => {
+ wait_for(desc, Interest::WRITABLE).await?;
+ }
+ e => return Err(anyhow!(io::Error::from(e))),
+ }
+ }
+ };
+
+ (inner.await, buf)
+}
+
+pub async fn recv_iobuf_with_fds<B: AsIoBufs + 'static>(
+ desc: &Arc<SafeDescriptor>,
+ mut buf: B,
+ fds: &mut [RawFd],
+) -> (anyhow::Result<(usize, usize)>, B) {
+ let inner = async {
+ let iovs = IoBufMut::as_iobufs(buf.as_iobufs());
+
+ let fd_cap = fds
+ .len()
+ .checked_mul(size_of::<RawFd>())
+ .and_then(|l| u32::try_from(l).ok())
+ .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput))?;
+ let (cmsg_buffer, cmsg_cap) = allocate_cmsg_buffer(fd_cap)?;
+
+ let mut msg = libc::msghdr {
+ msg_name: ptr::null_mut(),
+ msg_namelen: 0,
+ msg_iov: iovs.as_ptr() as *mut libc::iovec,
+ msg_iovlen: iovs.len(),
+ msg_flags: 0,
+ msg_control: cmsg_buffer.as_ptr(),
+ msg_controllen: cmsg_cap,
+ };
+
+ let buflen = loop {
+ // Safe because this will only modify `buf` and `cmsg_buffer` and we check the return value.
+ let ret = unsafe {
+ libc::recvmsg(
+ desc.as_raw_descriptor(),
+ &mut msg,
+ libc::MSG_NOSIGNAL | libc::MSG_DONTWAIT,
+ )
+ };
+
+ if ret >= 0 {
+ break ret as usize;
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK || e.errno() == libc::EAGAIN => {
+ wait_for(desc, Interest::READABLE).await?;
+ }
+ e => return Err(anyhow!(io::Error::from(e))),
+ }
+ };
+
+ let fd_count = take_fds_from_message(&msg, fds)?;
+ Ok((buflen, fd_count))
+ };
+
+ (inner.await, buf)
+}
+
+pub async fn accept(desc: &Arc<SafeDescriptor>) -> anyhow::Result<SafeDescriptor> {
+ loop {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe {
+ libc::accept4(
+ desc.as_raw_descriptor(),
+ ptr::null_mut(),
+ ptr::null_mut(),
+ libc::SOCK_CLOEXEC,
+ )
+ };
+
+ if ret >= 0 {
+ // Safe because we own this fd.
+ return Ok(unsafe { SafeDescriptor::from_raw_descriptor(ret) });
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK || e.errno() == libc::EAGAIN => {
+ wait_for(desc, Interest::READABLE).await?;
+ }
+ e => return Err(anyhow!(io::Error::from(e))),
+ }
+ }
+}
+
+pub async fn wait_readable(desc: &Arc<SafeDescriptor>) -> anyhow::Result<()> {
+ wait_for(desc, Interest::READABLE).await
+}
+
+pub async fn wait_writable(desc: &Arc<SafeDescriptor>) -> anyhow::Result<()> {
+ wait_for(desc, Interest::WRITABLE).await
+}
+
+pub fn prepare(fd: &dyn AsRawDescriptor) -> anyhow::Result<()> {
+ add_fd_flags(fd.as_raw_descriptor(), libc::O_NONBLOCK)
+ .map_err(io::Error::from)
+ .context("failed to make descriptor non-blocking")
+}
diff --git a/common/cros_asyncv2/src/unix/io_driver/uring.rs b/common/cros_asyncv2/src/unix/io_driver/uring.rs
new file mode 100644
index 000000000..731ef3803
--- /dev/null
+++ b/common/cros_asyncv2/src/unix/io_driver/uring.rs
@@ -0,0 +1,1142 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ alloc::Layout,
+ any::Any,
+ cell::RefCell,
+ cmp::min,
+ convert::{TryFrom, TryInto},
+ ffi::CStr,
+ future::Future,
+ io,
+ mem::{replace, size_of, MaybeUninit},
+ os::unix::io::{AsRawFd, RawFd},
+ pin::Pin,
+ ptr,
+ rc::Rc,
+ sync::Arc,
+ task::{self, Poll},
+ time::Duration,
+};
+
+use anyhow::{anyhow, bail, ensure, Context};
+use data_model::IoBufMut;
+use io_uring::{
+ cqueue::{self, buffer_select},
+ opcode, squeue,
+ types::{Fd, FsyncFlags, SubmitArgs, Timespec},
+ Builder, IoUring, Probe,
+};
+use once_cell::sync::{Lazy, OnceCell};
+use slab::Slab;
+use sys_util::{
+ error, warn, AsRawDescriptor, EventFd, FromRawDescriptor, LayoutAllocation, SafeDescriptor,
+};
+use thiserror::Error as ThisError;
+
+use super::cmsg::*;
+use crate::{AsIoBufs, OwnedIoBuf};
+
+// For now all buffers live in the same buffer group.
+const BUFFER_GROUP: u16 = 0;
+// The top 8 bits of the buffer id encode the index of the LayoutAllocation and the bottom 8 bits
+// encode the index of the buffer within that allocation.
+const ALLOC_IDX_SHIFT: usize = 8;
+const BUFFERS_PER_ALLOC: u16 = 32;
+const BUFFER_IDX_MASK: u16 = (1 << ALLOC_IDX_SHIFT) - 1;
+
+// Number of entries in the ring.
+const NUM_ENTRIES: u32 = 256;
+
+// The user_data for the waker. Since our user_data is based on the index in a Slab we'll run out of
+// memory well before a real operation gets usize::MAX as the index.
+const WAKER_DATA: usize = usize::MAX;
+
+// The global IoUring instance. Each thread-local IoUring shares its kernel backend with this
+// instance.
+static GLOBAL_URING: OnceCell<IoUring> = OnceCell::new();
+static URING_STATUS: Lazy<UringStatus> = Lazy::new(|| {
+ let mut utsname = MaybeUninit::zeroed();
+
+ // Safe because this will only modify `utsname` and we check the return value.
+ let res = unsafe { libc::uname(utsname.as_mut_ptr()) };
+ if res < 0 {
+ return UringStatus::Disabled;
+ }
+
+ // Safe because the kernel has initialized `utsname`.
+ let utsname = unsafe { utsname.assume_init() };
+
+ // Safe because the pointer is valid and the kernel guarantees that this is a valid C string.
+ let release = unsafe { CStr::from_ptr(utsname.release.as_ptr()) };
+
+ let mut components = match release.to_str().map(|r| r.split('.').map(str::parse)) {
+ Ok(c) => c,
+ Err(_) => return UringStatus::Disabled,
+ };
+
+ // Kernels older than 5.10 either didn't support io_uring or had bugs in the implementation.
+ match (components.next(), components.next()) {
+ (Some(Ok(major)), Some(Ok(minor))) if (major, minor) >= (5, 10) => {
+ // The kernel version is new enough so check if we can actually make a uring context.
+ if probe_uring().is_ok() {
+ UringStatus::Enabled(major, minor)
+ } else {
+ UringStatus::Disabled
+ }
+ }
+ _ => UringStatus::Disabled,
+ }
+});
+static EXT_ARG_SUPPORTED: Lazy<bool> = Lazy::new(
+ || matches!(&*URING_STATUS, UringStatus::Enabled(major, minor) if (*major, *minor) >= (5, 11)),
+);
+
+#[derive(Debug)]
+enum UringStatus {
+ Enabled(usize, usize),
+ Disabled,
+}
+
+thread_local! (static THREAD_STATE: OnceCell<Rc<RefCell<State>>> = OnceCell::new());
+fn new_state() -> anyhow::Result<Rc<RefCell<State>>> {
+ State::new().map(RefCell::new).map(Rc::new)
+}
+
+fn with_state<F, R>(f: F) -> anyhow::Result<R>
+where
+ F: FnOnce(&mut State) -> anyhow::Result<R>,
+{
+ THREAD_STATE.with(|thread_state| {
+ let state = thread_state.get_or_try_init(new_state)?;
+ f(&mut state.borrow_mut())
+ })
+}
+
+fn clone_state() -> anyhow::Result<Rc<RefCell<State>>> {
+ THREAD_STATE.with(|thread_state| thread_state.get_or_try_init(new_state).map(Rc::clone))
+}
+
+#[derive(Debug, ThisError)]
+enum ErrorContext {
+ #[error("`io_uring_enter` failed")]
+ EnterFailed,
+ #[error("failed to return buffer to kernel")]
+ ReturnBuffer,
+ #[error("`SubmissionQueue` full")]
+ SubmissionQueueFull,
+}
+
+fn probe_uring() -> anyhow::Result<()> {
+ const REQUIRED_OPS: &[u8] = &[
+ opcode::Accept::CODE,
+ opcode::AsyncCancel::CODE,
+ opcode::Connect::CODE,
+ opcode::Fallocate::CODE,
+ opcode::Fsync::CODE,
+ opcode::PollAdd::CODE,
+ opcode::ProvideBuffers::CODE,
+ opcode::Read::CODE,
+ opcode::Readv::CODE,
+ opcode::RecvMsg::CODE,
+ opcode::SendMsg::CODE,
+ opcode::Write::CODE,
+ opcode::Writev::CODE,
+ ];
+ let uring = IoUring::new(8)?;
+ let mut probe = Probe::new();
+ uring.submitter().register_probe(&mut probe)?;
+ if REQUIRED_OPS
+ .iter()
+ .all(|&opcode| probe.is_supported(opcode))
+ {
+ Ok(())
+ } else {
+ bail!("Not all required uring operations supported")
+ }
+}
+
+pub fn use_uring() -> bool {
+ match &*URING_STATUS {
+ UringStatus::Enabled(_, _) => true,
+ UringStatus::Disabled => false,
+ }
+}
+
+struct State {
+ uring: IoUring,
+ waker: Waker,
+ ops: Slab<OpStatus>,
+ buffers: [LayoutAllocation; 5],
+}
+
+impl State {
+ fn new() -> anyhow::Result<State> {
+ let global_uring = GLOBAL_URING.get_or_try_init(|| IoUring::new(NUM_ENTRIES))?;
+
+ // The `setup_attach_wq` call here ensures that each thread shares the same backend in the
+ // kernel but has its own separate completion and submission queues, avoiding the need to do
+ // expensive synchronization when touching those queues in userspace.
+ let uring = Builder::default()
+ .setup_attach_wq(global_uring.as_raw_fd())
+ .build(NUM_ENTRIES)?;
+ let waker = Waker::new()?;
+ let ops = Slab::new();
+ let buffers = [
+ new_buffer_allocation(64),
+ new_buffer_allocation(128),
+ new_buffer_allocation(256),
+ new_buffer_allocation(512),
+ new_buffer_allocation(1024),
+ ];
+ let mut state = State {
+ uring,
+ waker,
+ ops,
+ buffers,
+ };
+
+ for (idx, alloc) in state.buffers.iter().enumerate() {
+ let layout = alloc.layout();
+ debug_assert_eq!(layout.size(), layout.align() * BUFFERS_PER_ALLOC as usize);
+
+ // We can't use `State::provide_buffers` directly here because `state` is already
+ // borrowed by the for loop.
+ let entry = opcode::ProvideBuffers::new(
+ alloc.as_ptr(),
+ layout.align() as i32,
+ BUFFERS_PER_ALLOC,
+ BUFFER_GROUP,
+ pack_buffer_id(idx, 0),
+ )
+ .build()
+ .user_data(idx as u64);
+
+ // Safety: The allocation is valid for `layout.align() * BUFFERS_PER_ALLOC` bytes of
+ // memory and is valid for the lifetime of the `IoUring` because it lives in the same
+ // struct.
+ unsafe { state.uring.submission().push(&entry) }
+ .context("failed to submit `ProvideBuffers` operation")?;
+ }
+
+ // Wait for all the `ProvideBuffers` operations to finish.
+ let count = state
+ .uring
+ .submit_and_wait(state.buffers.len())
+ .context(ErrorContext::EnterFailed)?;
+ debug_assert_eq!(count, state.buffers.len());
+
+ for entry in state.uring.completion() {
+ if entry.result() < 0 {
+ return Err(io::Error::from_raw_os_error(-entry.result()))
+ .context("failed to provide buffers to io_uring");
+ }
+ }
+
+ // Now start the waker that other threads can use to break us out of an `io_uring_enter`
+ // syscall.
+ state.submit_waker()?;
+
+ Ok(state)
+ }
+
+ fn getevents(&mut self) -> anyhow::Result<()> {
+ let (submitter, squeue, _) = self.uring.split();
+ let to_submit = squeue.len();
+ let min_complete = 0;
+
+ // This flag should really be provided by the `io_uring` crate directly.
+ const IORING_ENTER_GETEVENTS: u32 = 1 << 0;
+
+ // We need to manually call `Submitter::enter` here because `submit_and_wait` will only add
+ // the `IORING_ENTER_GETEVENTS` flag when `want > 0`.
+ // Safety: the kernel will only ready `to_submit` entries from the submission queue,
+ // which have all been initialized.
+ unsafe {
+ submitter.enter::<libc::sigset_t>(
+ to_submit as u32,
+ min_complete,
+ IORING_ENTER_GETEVENTS,
+ None,
+ )
+ }
+ .map(drop)
+ .context(ErrorContext::EnterFailed)
+ }
+
+ fn submit_timer(&mut self, ts: Box<Timespec>) -> anyhow::Result<()> {
+ let slot = self.ops.vacant_entry();
+ let entry = opcode::Timeout::new(&*ts)
+ .build()
+ .user_data(slot.key() as u64);
+
+ slot.insert(OpStatus::System(ts));
+
+ // Safety: the entry is valid and we can guarantee that the Timespec will live for the
+ // lifetime of the operation.
+ unsafe { self.submit_entry(&entry) }
+ }
+
+ fn wait(&mut self, timeout: Option<Duration>) -> anyhow::Result<()> {
+ if let Some(timeout) = timeout {
+ if timeout > Duration::from_secs(0) {
+ let ts = Timespec::new()
+ .sec(timeout.as_secs())
+ .nsec(timeout.subsec_nanos());
+ if *EXT_ARG_SUPPORTED {
+ let args = SubmitArgs::new().timespec(&ts);
+ self.uring
+ .submitter()
+ .submit_with_args(1, &args)
+ .map(drop)
+ .context(ErrorContext::EnterFailed)
+ } else {
+ // Since `IORING_ENTER_EXT_ARG` is not supported we need to add a `Timeout`
+ // operation and then do a regular wait.
+ self.submit_timer(Box::new(ts))?;
+ self.uring
+ .submit_and_wait(1)
+ .map(drop)
+ .context(ErrorContext::EnterFailed)
+ }
+ } else {
+ // A zero timeout means we should submit new operations and fetch any completed
+ // operations without blocking.
+ self.getevents()
+ }
+ } else {
+ self.uring
+ .submit_and_wait(1)
+ .map(drop)
+ .context(ErrorContext::EnterFailed)
+ }
+ }
+
+ // Dispatches all completed IO operations. Returns true if one of the completed operations was the
+ // thread waker.
+ fn dispatch(&mut self) -> anyhow::Result<()> {
+ let mut waker_entry = None;
+ let mut needs_cleanup = Vec::new();
+ for entry in self.uring.completion() {
+ let idx = entry.user_data() as usize;
+ if idx == WAKER_DATA {
+ waker_entry = Some(entry);
+ continue;
+ }
+ let status = replace(&mut self.ops[idx], OpStatus::Ready(entry));
+ match status {
+ OpStatus::New(_) => {
+ panic!("Received completion for operation that has not been started")
+ }
+ OpStatus::Waiting(w) => w.wake(),
+ OpStatus::Ready(_) => panic!("Received completion for finished operation"),
+ OpStatus::Canceled(cleanup, _) => {
+ let entry = if let OpStatus::Ready(entry) = self.ops.remove(idx) {
+ entry
+ } else {
+ panic!();
+ };
+ if let Some(c) = cleanup {
+ needs_cleanup.push((c, entry));
+ }
+ }
+ OpStatus::System(_) => drop(self.ops.remove(idx)),
+ OpStatus::Processing | OpStatus::Finished => {
+ panic!("Unexpected state for `OpStatus`")
+ }
+ }
+ }
+
+ if !needs_cleanup.is_empty() || waker_entry.is_some() {
+ // When there is a completion queue overflow, we can end up in an infinite loop:
+ // submit_entry() -> cq_overflow() -> dispatch() -> provide_buffers() / submit_waker()
+ // -> submit_entry(). Now that we've drained the completion queue, submit any pending
+ // operations in the submission queue to break the loop.
+ if self.uring.submission().cq_overflow() {
+ self.uring.submit()?;
+ }
+ }
+
+ if let Some(entry) = waker_entry {
+ // We were woken up so return the buffer to the kernel and resubmit the waker.
+ let SelectedBuffer { ptr, len, cap, bid } = self
+ .get_selected_buffer(entry)
+ .context("failed to read from waker")?;
+ debug_assert_eq!(len, size_of::<u64>());
+
+ // Safety: this was a buffer that we previously provided so we know that it is valid and
+ // lives at least as long as the `IoUring`.
+ unsafe { self.provide_buffers(ptr, cap as i32, 1, BUFFER_GROUP, bid) }
+ .context(ErrorContext::ReturnBuffer)?;
+
+ self.submit_waker()?;
+ }
+
+ for (cleanup, entry) in needs_cleanup {
+ cleanup(self, entry);
+ }
+
+ Ok(())
+ }
+
+ // Safety: This function has the same safety requirements as `SubmissionQueue::push`, namely that
+ // the parameters of `entry` are valid and will be valid for the entire duration of the operation.
+ unsafe fn submit_entry(&mut self, entry: &squeue::Entry) -> anyhow::Result<()> {
+ if self.uring.submission().push(entry).is_err() {
+ if self.uring.submission().cq_overflow() {
+ self.dispatch()
+ .context("failed to dispatch completed ops during cqueue overflow")?;
+ }
+ self.uring.submit().context(ErrorContext::EnterFailed)?;
+ self.uring
+ .submission()
+ .push(entry)
+ .map_err(|_| io::Error::from_raw_os_error(libc::EBUSY))
+ .context(ErrorContext::SubmissionQueueFull)
+ } else {
+ Ok(())
+ }
+ }
+
+ fn submit_waker(&mut self) -> anyhow::Result<()> {
+ let entry = opcode::Read::new(
+ Fd(self.waker.0.as_raw_fd()),
+ ptr::null_mut(),
+ size_of::<u64>() as u32,
+ )
+ .buf_group(BUFFER_GROUP)
+ .build()
+ .user_data(WAKER_DATA as u64)
+ .flags(squeue::Flags::BUFFER_SELECT);
+
+ // Safety: the entry is valid and doesn't reference any memory.
+ unsafe { self.submit_entry(&entry) }
+ }
+
+ // Safety: `buffer` must be a valid pointer to `len * nbufs` bytes of memory and must live at
+ // least as long as `self`.
+ unsafe fn provide_buffers(
+ &mut self,
+ buffer: *mut u8,
+ len: i32,
+ nbufs: u16,
+ bgid: u16,
+ bid: u16,
+ ) -> anyhow::Result<()> {
+ let slot = self.ops.vacant_entry();
+ let idx = slot.key();
+ let entry = opcode::ProvideBuffers::new(buffer, len, nbufs, bgid, bid)
+ .build()
+ .user_data(idx as u64);
+
+ slot.insert(OpStatus::System(Box::new(())));
+
+ // Safety: `buffer` is a valid pointer to `len * nbufs` bytes of memory and will be valid
+ // for the lifetime of the `IoUring` because it lives at least as long as `self`.
+ self.submit_entry(&entry)
+ }
+
+ // Returns the buffer selected by the kernel for `entry`. Panics if no buffer was selected by
+ // the kernel.
+ fn get_selected_buffer(&self, entry: cqueue::Entry) -> anyhow::Result<SelectedBuffer> {
+ let len = entry_to_result(entry.clone())?;
+
+ let bid = buffer_select(entry.flags()).expect("No buffer selected");
+ let (alloc_idx, buffer_idx) = unpack_buffer_id(bid);
+ let alloc = &self.buffers[alloc_idx];
+ let layout = alloc.layout();
+ let cap = layout.align();
+
+ debug_assert!(len <= cap);
+ debug_assert!(buffer_idx * layout.align() <= layout.size() - len);
+
+ // Safety: the allocation is valid for at least `buffer_idx * layout.align()` bytes of
+ // memory.
+ let ptr = unsafe { alloc.as_ptr::<u8>().add(buffer_idx * layout.align()) };
+ Ok(SelectedBuffer { ptr, len, cap, bid })
+ }
+
+ // Copies data from the kernel-selected buffer into the user-provided buffer and returns the
+ // selected buffer to the kernel. Panics if no buffer was selected by the kernel.
+ fn copy_from_selected_buffer(
+ &mut self,
+ entry: cqueue::Entry,
+ buf: &mut [u8],
+ ) -> anyhow::Result<usize> {
+ let SelectedBuffer { ptr, len, cap, bid } = self.get_selected_buffer(entry)?;
+ let count = min(len, buf.len());
+
+ // Safety: both pointers point to at least `count` bytes of allocated memory.
+ unsafe { ptr::copy_nonoverlapping(ptr, buf.as_mut_ptr(), count) };
+
+ // Now that we've copied the data out we need to return the buffer to the kernel.
+ // Safety: this is a buffer that was previously registered with the kernel and the caller
+ // that registered it was required to guarantee that it lives as long as the `IoUring`.
+ // We're reusing that guarantee here.
+ unsafe { self.provide_buffers(ptr, cap as i32, 1, BUFFER_GROUP, bid) }
+ .context(ErrorContext::ReturnBuffer)?;
+
+ Ok(count)
+ }
+
+ fn cancel_op(&mut self, idx: usize) -> anyhow::Result<()> {
+ // We're still waiting for the underlying IO to complete so try to cancel it if we can.
+ let slot = self.ops.vacant_entry();
+ let cancel = opcode::AsyncCancel::new(idx as u64)
+ .build()
+ .user_data(slot.key() as u64);
+
+ slot.insert(OpStatus::System(Box::new(())));
+
+ // Safety: The entry is valid and doesn't reference any memory.
+ unsafe { self.submit_entry(&cancel) }.context("failed to submit async cancellation")
+ }
+
+ // TODO: Do we actually need any of this? Once the IoUring is dropped, the fd should be closed
+ // so it doesn't seem necessary for us to actually drain it. It would be weird if the kernel
+ // kept around references to memory once the uring fd is gone.
+ // fn shutdown(&mut self, deadline: Instant) -> anyhow::Result<()> {
+ // // Every async operation owns a reference to the `State` and either removes itself from
+ // // `self.ops` or changes its status to `Canceled` when it is dropped so `self.ops` shouldn't
+ // // contain anything other than canceled and system operations.
+ // let pending = self
+ // .ops
+ // .iter_mut()
+ // .filter_map(|(idx, op)| match replace(op, OpStatus::Processing) {
+ // OpStatus::System(data) => {
+ // *op = OpStatus::Canceled(data);
+ // Some(idx)
+ // }
+ // OpStatus::Canceled(data) => {
+ // *op = OpStatus::Canceled(data);
+ // None
+ // }
+ // _ => panic!(
+ // "Thread state dropped while there are still non-canceled operations pending"
+ // ),
+ // })
+ // .collect::<Vec<_>>();
+
+ // for idx in pending {
+ // self.cancel_op(idx)?;
+ // }
+
+ // // Wait for all the canceled operations to finish.
+ // if !self.ops.is_empty() {
+ // self.wait(
+ // self.ops.len(),
+ // Some(deadline.saturating_duration_since(Instant::now())),
+ // )?;
+ // }
+ // self.dispatch()?;
+
+ // let ext_arg_supported = *EXT_ARG_SUPPORTED;
+ // // When `IORING_ENTER_EXT_ARG` is not supported, there may still be a timer op left in
+ // // `self.ops`.
+ // if (ext_arg_supported && !self.ops.is_empty()) || (!ext_arg_supported && self.ops.len() > 1)
+ // {
+ // return Err(anyhow!(io::Error::from_raw_os_error(libc::ETIMEDOUT))).context(format!(
+ // "Still waiting for {} operations to finish",
+ // self.ops.len()
+ // ));
+ // }
+
+ // // The `Waker` is the last pending operation.
+ // self.waker.wake().context("failed to wake Waker")?;
+ // self.wait(1, Some(deadline.saturating_duration_since(Instant::now())))?;
+
+ // Ok(())
+ // }
+}
+
+// TODO: Do we actually need this? See State::shutdown above.
+// impl Drop for State {
+// fn drop(&mut self) {
+// // How long we should wait to drain the `IoUring` before giving up.
+// const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(10);
+// if let Err(e) = self.shutdown(Instant::now() + SHUTDOWN_TIMEOUT) {
+// process::abort();
+// }
+// }
+// }
+
+struct SelectedBuffer {
+ ptr: *mut u8,
+ len: usize,
+ cap: usize,
+ bid: u16,
+}
+
+fn new_buffer_allocation(size: usize) -> LayoutAllocation {
+ let layout = Layout::from_size_align(size * usize::from(BUFFERS_PER_ALLOC), size)
+ .expect("Invalid layout");
+ LayoutAllocation::uninitialized(layout)
+}
+
+fn pack_buffer_id(alloc_idx: usize, buffer_idx: usize) -> u16 {
+ debug_assert!(alloc_idx << ALLOC_IDX_SHIFT <= u16::MAX as usize);
+ debug_assert_eq!(buffer_idx & usize::from(BUFFER_IDX_MASK), buffer_idx);
+ ((alloc_idx << ALLOC_IDX_SHIFT) | buffer_idx) as u16
+}
+
+// Returns the index of the `LayoutAllocation` and the index of the buffer within that allocation.
+fn unpack_buffer_id(bid: u16) -> (usize, usize) {
+ let alloc_idx = (bid >> ALLOC_IDX_SHIFT).into();
+ let buffer_idx = (bid & BUFFER_IDX_MASK).into();
+ (alloc_idx, buffer_idx)
+}
+
+pub struct Waker(EventFd);
+impl Waker {
+ fn new() -> anyhow::Result<Waker> {
+ EventFd::new()
+ .map(Waker)
+ .map_err(|e| anyhow!(io::Error::from(e)))
+ }
+
+ fn try_clone(&self) -> anyhow::Result<Waker> {
+ self.0
+ .try_clone()
+ .map(Waker)
+ .map_err(|e| anyhow!(io::Error::from(e)))
+ }
+
+ pub fn wake(&self) -> anyhow::Result<()> {
+ self.0
+ .write(1)
+ .map(drop)
+ .map_err(|e| anyhow!(io::Error::from(e)))
+ }
+}
+
+pub fn new_waker() -> anyhow::Result<Waker> {
+ with_state(|state| state.waker.try_clone())
+}
+
+// Wait for more events.
+pub fn wait(timeout: Option<Duration>) -> anyhow::Result<()> {
+ with_state(|state| state.wait(timeout))
+}
+
+// Wake up any tasks that are ready.
+pub fn dispatch() -> anyhow::Result<()> {
+ with_state(|state| state.dispatch())
+}
+
+enum OpStatus {
+ New(squeue::Entry),
+ Waiting(task::Waker),
+ Ready(cqueue::Entry),
+ Canceled(Option<fn(&mut State, cqueue::Entry)>, Box<dyn Any>),
+ System(Box<dyn Any>),
+ Processing,
+ Finished,
+}
+
+struct Op<'a, B: Unpin + 'static> {
+ state: Rc<RefCell<State>>,
+ desc: &'a Arc<SafeDescriptor>,
+ cleanup: Option<fn(&mut State, cqueue::Entry)>,
+ buf: Option<B>,
+ idx: usize,
+}
+
+impl<'a, B: Unpin + 'static> Future for Op<'a, B> {
+ type Output = (anyhow::Result<cqueue::Entry>, Option<B>);
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
+ let mut state = self.state.borrow_mut();
+ let status = replace(&mut state.ops[self.idx], OpStatus::Processing);
+
+ match status {
+ // We don't want to submit the operation to the kernel until this future is polled
+ // because polling requires pinning and pinning guarantees that our drop impl will be
+ // called, which is necessary to ensure that resources shared with the kernel will live
+ // for the lifetime of the operation.
+ OpStatus::New(entry) => {
+ // Safety: The parameters in `Entry` are owned by `Op` and will be transferred to
+ // the thread state if this `Op` is dropped, guaranteeing that they are valid for
+ // the lifetime of the operation. Also see above for the drop guarantee.
+ let res = unsafe { state.submit_entry(&entry) };
+
+ if let Err(e) = res {
+ drop(state);
+ return Poll::Ready((Err(e), self.buf.take()));
+ }
+
+ state.ops[self.idx] = OpStatus::Waiting(cx.waker().clone());
+ }
+ OpStatus::Waiting(w) if !w.will_wake(cx.waker()) => {
+ state.ops[self.idx] = OpStatus::Waiting(cx.waker().clone())
+ }
+ // If `cx.waker()` and the currently stored waker are the same then no need to do
+ // anything.
+ OpStatus::Waiting(w) => state.ops[self.idx] = OpStatus::Waiting(w),
+ OpStatus::Ready(entry) => {
+ state.ops[self.idx] = OpStatus::Finished;
+ drop(state);
+
+ let buf = self.buf.take();
+ return Poll::Ready((Ok(entry), buf));
+ }
+ OpStatus::Canceled(_, _) => panic!("`Op` polled after drop"),
+ OpStatus::System(_) | OpStatus::Processing => panic!("Unexpected state for `OpStatus`"),
+ OpStatus::Finished => panic!("`Op` polled after returning `Poll::Ready`"),
+ }
+
+ Poll::Pending
+ }
+}
+
+impl<'a, B: Unpin + 'static> Drop for Op<'a, B> {
+ fn drop(&mut self) {
+ let mut state = self.state.borrow_mut();
+ let status = replace(&mut state.ops[self.idx], OpStatus::Processing);
+
+ if let OpStatus::Waiting(_) = status {
+ // If we're still waiting for the IO to finish then we cannot free the resources until
+ // the operation is complete.
+ if let Err(e) = state.cancel_op(self.idx) {
+ warn!("Failed to cancel dropped operation: {:#}", e);
+ }
+
+ // Now take ownership of any resources associated with the canceled operation.
+ state.ops[self.idx] = OpStatus::Canceled(
+ self.cleanup.take(),
+ Box::new((self.desc.clone(), self.buf.take())),
+ )
+ } else {
+ // We have not shared any resources with the kernel so we can clean up the `OpStatus` now.
+ state.ops.remove(self.idx);
+ }
+ }
+}
+
+fn start_op<B: Unpin + 'static>(
+ state: Rc<RefCell<State>>,
+ entry: squeue::Entry,
+ desc: &Arc<SafeDescriptor>,
+ cleanup: Option<fn(&mut State, cqueue::Entry)>,
+ buf: Option<B>,
+) -> Op<B> {
+ let idx = {
+ let mut state = state.borrow_mut();
+ let slot = state.ops.vacant_entry();
+ let idx = slot.key();
+ slot.insert(OpStatus::New(entry.user_data(idx as u64)));
+ idx
+ };
+ Op {
+ state,
+ desc,
+ cleanup,
+ buf,
+ idx,
+ }
+}
+
+fn entry_to_result(entry: cqueue::Entry) -> anyhow::Result<usize> {
+ let res = entry.result();
+ if res < 0 {
+ Err(anyhow!(io::Error::from_raw_os_error(-res)))
+ } else {
+ Ok(res as usize)
+ }
+}
+
+fn return_selected_buffer(state: &mut State, entry: cqueue::Entry) {
+ let inner = || {
+ let SelectedBuffer {
+ ptr,
+ len: _,
+ cap,
+ bid,
+ } = state.get_selected_buffer(entry)?;
+
+ // Safety: we are returning a buffer that was previously provided to the kernel so we know
+ // it must live as long as the `IoUring`.
+ unsafe { state.provide_buffers(ptr, cap as i32, 1, BUFFER_GROUP, bid) }
+ };
+
+ if let Err(e) = inner() {
+ warn!(
+ "Failed to return selected buffer to kernel; buffer will be leaked: {:#}",
+ e
+ );
+ }
+}
+
+pub async fn read(
+ desc: &Arc<SafeDescriptor>,
+ buf: &mut [u8],
+ offset: Option<u64>,
+) -> anyhow::Result<usize> {
+ let len = buf
+ .len()
+ .try_into()
+ .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
+ let mut read =
+ opcode::Read::new(Fd(desc.as_raw_fd()), ptr::null_mut(), len).buf_group(BUFFER_GROUP);
+ if let Some(offset) = offset {
+ read = read.offset(offset as libc::off64_t);
+ }
+ let entry = read.build().flags(squeue::Flags::BUFFER_SELECT);
+ let state = clone_state()?;
+ let (res, _) = start_op(state, entry, desc, Some(return_selected_buffer), None::<()>).await;
+ with_state(|state| state.copy_from_selected_buffer(res?, buf))
+}
+
+pub async fn read_iobuf<B: AsIoBufs + Unpin + 'static>(
+ desc: &Arc<SafeDescriptor>,
+ mut buf: B,
+ offset: Option<u64>,
+) -> (anyhow::Result<usize>, B) {
+ let iobufs = IoBufMut::as_iobufs(buf.as_iobufs());
+ let mut readv = opcode::Readv::new(Fd(desc.as_raw_fd()), iobufs.as_ptr(), iobufs.len() as u32);
+ if let Some(off) = offset {
+ readv = readv.offset(off as libc::off64_t);
+ }
+
+ let state = match clone_state() {
+ Ok(s) => s,
+ Err(e) => return (Err(e), buf),
+ };
+ let (res, buf) = start_op(state, readv.build(), desc, None, Some(buf)).await;
+ (res.and_then(entry_to_result), buf.unwrap())
+}
+
+pub async fn write(
+ desc: &Arc<SafeDescriptor>,
+ buf: &[u8],
+ offset: Option<u64>,
+) -> anyhow::Result<usize> {
+ // TODO: Maybe we should do something smarter here with a shared buffer pool like we do for
+ // `read`.
+ let (res, _) = write_iobuf(desc, OwnedIoBuf::new(buf.to_vec()), offset).await;
+ res
+}
+
+pub async fn write_iobuf<B: AsIoBufs + Unpin + 'static>(
+ desc: &Arc<SafeDescriptor>,
+ mut buf: B,
+ offset: Option<u64>,
+) -> (anyhow::Result<usize>, B) {
+ let iobufs = IoBufMut::as_iobufs(buf.as_iobufs());
+ let mut writev =
+ opcode::Writev::new(Fd(desc.as_raw_fd()), iobufs.as_ptr(), iobufs.len() as u32);
+ if let Some(off) = offset {
+ writev = writev.offset(off as libc::off64_t);
+ }
+
+ let state = match clone_state() {
+ Ok(s) => s,
+ Err(e) => return (Err(e), buf),
+ };
+ let (res, buf) = start_op(state, writev.build(), desc, None, Some(buf)).await;
+ (res.and_then(entry_to_result), buf.unwrap())
+}
+
+pub async fn fallocate(
+ desc: &Arc<SafeDescriptor>,
+ file_offset: u64,
+ len: u64,
+ mode: u32,
+) -> anyhow::Result<()> {
+ let entry = opcode::Fallocate::new(Fd(desc.as_raw_fd()), len as libc::off64_t)
+ .offset(file_offset as libc::off64_t)
+ .mode(mode as libc::c_int)
+ .build();
+ let state = clone_state()?;
+ let (res, _) = start_op(state, entry, desc, None, None::<()>).await;
+ res.and_then(entry_to_result).map(drop)
+}
+
+pub async fn ftruncate(desc: &Arc<SafeDescriptor>, len: u64) -> anyhow::Result<()> {
+ let ret = unsafe { libc::ftruncate64(desc.as_raw_descriptor(), len as libc::off64_t) };
+
+ if ret == 0 {
+ Ok(())
+ } else {
+ Err(io::Error::last_os_error().into())
+ }
+}
+
+pub async fn stat(desc: &Arc<SafeDescriptor>) -> anyhow::Result<libc::stat64> {
+ // TODO: use opcode::Statx
+ let mut st = MaybeUninit::zeroed();
+
+ // Safe because this will only modify `st` and we check the return value.
+ let ret = unsafe { libc::fstat64(desc.as_raw_descriptor(), st.as_mut_ptr()) };
+
+ if ret == 0 {
+ // Safe because the kernel guarantees that `st` is now initialized.
+ Ok(unsafe { st.assume_init() })
+ } else {
+ Err(io::Error::last_os_error().into())
+ }
+}
+
+pub async fn fsync(desc: &Arc<SafeDescriptor>, datasync: bool) -> anyhow::Result<()> {
+ let mut entry = opcode::Fsync::new(Fd(desc.as_raw_fd()));
+ if datasync {
+ entry = entry.flags(FsyncFlags::DATASYNC);
+ }
+ let state = clone_state()?;
+ let (res, _) = start_op(state, entry.build(), desc, None, None::<()>).await;
+ res.and_then(entry_to_result).map(drop)
+}
+
+pub async fn connect(
+ desc: &Arc<SafeDescriptor>,
+ addr: libc::sockaddr_un,
+ len: libc::socklen_t,
+) -> anyhow::Result<()> {
+ ensure!(
+ len <= size_of::<libc::sockaddr_un>() as libc::socklen_t,
+ io::Error::from_raw_os_error(libc::EINVAL)
+ );
+ // TODO: Figure out a way to get rid of this box.
+ let addr = Box::new(addr);
+
+ let entry = opcode::Connect::new(
+ Fd(desc.as_raw_fd()),
+ &*addr as *const libc::sockaddr_un as *const _,
+ len,
+ )
+ .build();
+ let state = clone_state()?;
+ let (res, _) = start_op(state, entry, desc, None, Some(addr)).await;
+
+ res.and_then(entry_to_result).map(drop)
+}
+
+pub async fn next_packet_size(desc: &Arc<SafeDescriptor>) -> anyhow::Result<usize> {
+ // For some reason, this always returns 0 under uring so use epoll-style for now. TODO: Figure
+ // out how we can go back to using uring.
+ #[cfg(not(debug_assertions))]
+ let buf = ptr::null_mut();
+ // Work around for qemu's syscall translation which will reject null pointers in recvfrom.
+ // This only matters for running the unit tests for a non-native architecture. See the
+ // upstream thread for the qemu fix:
+ // https://lists.nongnu.org/archive/html/qemu-devel/2021-03/msg09027.html
+ #[cfg(debug_assertions)]
+ let buf = ptr::NonNull::dangling().as_ptr();
+
+ loop {
+ // Safe because this will not modify any memory and we check the return value.
+ let ret = unsafe {
+ libc::recvfrom(
+ desc.as_raw_descriptor(),
+ buf,
+ 0,
+ libc::MSG_TRUNC | libc::MSG_PEEK | libc::MSG_DONTWAIT,
+ ptr::null_mut(),
+ ptr::null_mut(),
+ )
+ };
+
+ if ret >= 0 {
+ return Ok(ret as usize);
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK || e.errno() == libc::EAGAIN => {
+ wait_readable(desc).await?;
+ }
+ e => bail!(io::Error::from(e)),
+ }
+ }
+}
+
+pub async fn sendmsg(
+ desc: &Arc<SafeDescriptor>,
+ buf: &[u8],
+ fds: &[RawFd],
+) -> anyhow::Result<usize> {
+ // TODO: Consider using a shared buffer pool.
+ let (res, _) = send_iobuf_with_fds(desc, OwnedIoBuf::new(buf.to_vec()), fds).await;
+ res
+}
+
+pub async fn recvmsg(
+ desc: &Arc<SafeDescriptor>,
+ buf: &mut [u8],
+ fds: &mut [RawFd],
+) -> anyhow::Result<(usize, usize)> {
+ // TODO: The io_uring crate doesn't support using BUFFER_SELECT for recvmsg even though it's
+ // supported by the kernel.
+ let (res, src) = recv_iobuf_with_fds(desc, OwnedIoBuf::new(vec![0u8; buf.len()]), fds).await;
+ let (buflen, fd_count) = res?;
+ let count = min(buflen, buf.len());
+ buf[..count].copy_from_slice(&src[..count]);
+ Ok((count, fd_count))
+}
+
+pub async fn send_iobuf_with_fds<B: AsIoBufs + Unpin + 'static>(
+ desc: &Arc<SafeDescriptor>,
+ mut buf: B,
+ fds: &[RawFd],
+) -> (anyhow::Result<usize>, B) {
+ let iovs = IoBufMut::as_iobufs(buf.as_iobufs());
+ let mut msg = libc::msghdr {
+ msg_name: ptr::null_mut(),
+ msg_namelen: 0,
+ msg_iov: iovs.as_ptr() as *mut libc::iovec,
+ msg_iovlen: iovs.len(),
+ msg_flags: libc::MSG_NOSIGNAL,
+ msg_control: ptr::null_mut(),
+ msg_controllen: 0,
+ };
+
+ // `IORING_OP_SENDMSG` internally uses the `__sys_sendmsg_sock` kernel function, which disallows
+ // control messages. In that case we fall back to epoll-style async operations.
+ if !fds.is_empty() {
+ let inner = async {
+ let cmsg_buffer = add_fds_to_message(&mut msg, fds)?;
+
+ loop {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe {
+ libc::sendmsg(
+ desc.as_raw_descriptor(),
+ &msg,
+ libc::MSG_NOSIGNAL | libc::MSG_DONTWAIT,
+ )
+ };
+
+ if ret >= 0 {
+ return Ok(ret as usize);
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK || e.errno() == libc::EAGAIN => {
+ wait_writable(desc).await?;
+ }
+ e => return Err(anyhow!(io::Error::from(e))),
+ }
+ }
+ };
+ (inner.await, buf)
+ } else {
+ let msg = Box::new(msg);
+ let entry = opcode::SendMsg::new(Fd(desc.as_raw_descriptor()), &*msg).build();
+ let state = match clone_state() {
+ Ok(s) => s,
+ Err(e) => return (Err(e), buf),
+ };
+ let (res, data) = start_op(state, entry, desc, None, Some((buf, msg))).await;
+ (res.and_then(entry_to_result), data.unwrap().0)
+ }
+}
+
+pub async fn recv_iobuf_with_fds<B: AsIoBufs + Unpin + 'static>(
+ desc: &Arc<SafeDescriptor>,
+ mut buf: B,
+ fds: &mut [RawFd],
+) -> (anyhow::Result<(usize, usize)>, B) {
+ let iovs = IoBufMut::as_iobufs(buf.as_iobufs());
+ // `IORING_OP_RECVMSG` internally uses the `__sys_recvmsg_sock` kernel function, which disallows
+ // control messages. In that case we fall back to epoll-style async operations.
+ if !fds.is_empty() {
+ let inner = async {
+ let fd_cap = fds
+ .len()
+ .checked_mul(size_of::<RawFd>())
+ .and_then(|l| u32::try_from(l).ok())
+ .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidInput))?;
+ let (cmsg_buffer, cmsg_cap) = allocate_cmsg_buffer(fd_cap)?;
+ let mut msg = libc::msghdr {
+ msg_name: ptr::null_mut(),
+ msg_namelen: 0,
+ msg_iov: iovs.as_ptr() as *mut libc::iovec,
+ msg_iovlen: iovs.len(),
+ msg_flags: 0,
+ msg_control: cmsg_buffer.as_ptr(),
+ msg_controllen: cmsg_cap,
+ };
+
+ let buflen = loop {
+ // Safe because this will only modify `buf` and `cmsg_buffer` and we check the return value.
+ let ret = unsafe {
+ libc::recvmsg(
+ desc.as_raw_descriptor(),
+ &mut msg,
+ libc::MSG_NOSIGNAL | libc::MSG_DONTWAIT,
+ )
+ };
+
+ if ret >= 0 {
+ break ret as usize;
+ }
+
+ match sys_util::Error::last() {
+ e if e.errno() == libc::EWOULDBLOCK || e.errno() == libc::EAGAIN => {
+ wait_readable(desc).await?;
+ }
+ e => return Err(anyhow!(io::Error::from(e))),
+ }
+ };
+
+ let fd_count = take_fds_from_message(&msg, fds)?;
+ Ok((buflen, fd_count))
+ };
+ (inner.await, buf)
+ } else {
+ let mut msg = Box::new(libc::msghdr {
+ msg_name: ptr::null_mut(),
+ msg_namelen: 0,
+ msg_iov: iovs.as_ptr() as *mut libc::iovec,
+ msg_iovlen: iovs.len(),
+ msg_flags: libc::MSG_NOSIGNAL,
+ msg_control: ptr::null_mut(),
+ msg_controllen: 0,
+ });
+
+ let entry = opcode::RecvMsg::new(Fd(desc.as_raw_descriptor()), &mut *msg).build();
+ let state = match clone_state() {
+ Ok(s) => s,
+ Err(e) => return (Err(e), buf),
+ };
+ let (res, data) = start_op(state, entry, desc, None, Some((buf, msg))).await;
+ let (buf, msg) = data.unwrap();
+
+ let inner = || {
+ let buflen = res.and_then(entry_to_result)?;
+ let fd_count = take_fds_from_message(&msg, fds)?;
+ Ok((buflen, fd_count))
+ };
+ (inner(), buf)
+ }
+}
+
+pub async fn accept(desc: &Arc<SafeDescriptor>) -> anyhow::Result<SafeDescriptor> {
+ let entry = opcode::Accept::new(Fd(desc.as_raw_fd()), ptr::null_mut(), ptr::null_mut())
+ .flags(libc::SOCK_CLOEXEC)
+ .build();
+ let state = clone_state()?;
+ let (res, _) = start_op(state, entry, desc, None, None::<()>).await;
+
+ // Safe because we own this fd.
+ res.and_then(entry_to_result)
+ .map(|fd| unsafe { SafeDescriptor::from_raw_descriptor(fd as _) })
+}
+
+pub async fn wait_readable(desc: &Arc<SafeDescriptor>) -> anyhow::Result<()> {
+ let entry = opcode::PollAdd::new(Fd(desc.as_raw_fd()), libc::POLLIN as u32).build();
+ let state = clone_state()?;
+ let (res, _) = start_op(state, entry, desc, None, None::<()>).await;
+ res.and_then(entry_to_result).map(drop)
+}
+
+pub async fn wait_writable(desc: &Arc<SafeDescriptor>) -> anyhow::Result<()> {
+ let entry = opcode::PollAdd::new(Fd(desc.as_raw_fd()), libc::POLLOUT as u32).build();
+ let state = clone_state()?;
+ let (res, _) = start_op(state, entry, desc, None, None::<()>).await;
+ res.and_then(entry_to_result).map(drop)
+}
+
+pub fn prepare(_fd: &dyn AsRawDescriptor) -> anyhow::Result<()> {
+ Ok(())
+}
diff --git a/common/cros_asyncv2/src/unix/mod.rs b/common/cros_asyncv2/src/unix/mod.rs
new file mode 100644
index 000000000..bf75d5452
--- /dev/null
+++ b/common/cros_asyncv2/src/unix/mod.rs
@@ -0,0 +1,21 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod descriptor;
+mod event;
+mod file;
+mod io_driver;
+mod seqpacket;
+
+pub use descriptor::*;
+pub use event::*;
+pub use file::*;
+pub use seqpacket::*;
+
+use crate::executor;
+
+#[inline]
+pub(crate) fn platform_state() -> anyhow::Result<impl executor::PlatformState> {
+ io_driver::platform_state()
+}
diff --git a/common/cros_asyncv2/src/unix/seqpacket.rs b/common/cros_asyncv2/src/unix/seqpacket.rs
new file mode 100644
index 000000000..1c2c483c5
--- /dev/null
+++ b/common/cros_asyncv2/src/unix/seqpacket.rs
@@ -0,0 +1,840 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ convert::TryFrom,
+ ffi::OsString,
+ fs::remove_file,
+ io,
+ mem::size_of,
+ ops::Deref,
+ os::unix::{
+ ffi::{OsStrExt, OsStringExt},
+ io::{AsRawFd, RawFd},
+ },
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+
+use anyhow::{anyhow, bail, Context};
+use memoffset::offset_of;
+use sys_util::{warn, AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor};
+use thiserror::Error as ThisError;
+
+use crate::{AsIoBufs, OwnedIoBuf};
+
+use super::io_driver;
+
+#[derive(Debug, ThisError)]
+#[error("Failed to prepare socket fd")]
+struct PrepareSocket;
+
+fn sockaddr_un<P: AsRef<Path>>(path: P) -> anyhow::Result<(libc::sockaddr_un, libc::socklen_t)> {
+ let mut addr = libc::sockaddr_un {
+ sun_family: libc::AF_UNIX as libc::sa_family_t,
+ sun_path: [0; 108],
+ };
+
+ // Check if the input path is valid. Since
+ // * The pathname in sun_path should be null-terminated.
+ // * The length of the pathname, including the terminating null byte,
+ // should not exceed the size of sun_path.
+ //
+ // and our input is a `Path`, we only need to check
+ // * If the string size of `Path` should less than sizeof(sun_path)
+ // and make sure `sun_path` ends with '\0' by initialized the sun_path with zeros.
+ //
+ // Empty path name is valid since abstract socket address has sun_paht[0] = '\0'
+ let bytes = path.as_ref().as_os_str().as_bytes();
+ if bytes.len() >= addr.sun_path.len() {
+ bail!(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "Input path size should be less than the length of sun_path.",
+ ));
+ };
+
+ // Copy data from `path` to `addr.sun_path`
+ for (dst, src) in addr.sun_path.iter_mut().zip(bytes) {
+ *dst = *src as libc::c_char;
+ }
+
+ // The addrlen argument that describes the enclosing sockaddr_un structure
+ // should have a value of at least:
+ //
+ // offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path) + 1
+ //
+ // or, more simply, addrlen can be specified as sizeof(struct sockaddr_un).
+ let len = offset_of!(libc::sockaddr_un, sun_path) + bytes.len() + 1;
+ Ok((addr, len as libc::socklen_t))
+}
+
+/// A Unix `SOCK_SEQPACKET`.
+#[derive(Debug)]
+pub struct SeqPacket {
+ fd: Arc<SafeDescriptor>,
+}
+
+impl SeqPacket {
+ /// Open a `SOCK_SEQPACKET` connection to socket named by `path`.
+ pub async fn connect<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let fd =
+ unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_SEQPACKET | libc::SOCK_CLOEXEC, 0) };
+ if fd < 0 {
+ return Err(io::Error::last_os_error())
+ .context("failed to create SOCK_SEQPACKET socket");
+ }
+
+ // Safe because we just opened this socket and we know it is valid.
+ let fd = Arc::new(unsafe { SafeDescriptor::from_raw_descriptor(fd) });
+ io_driver::prepare(&*fd).context(PrepareSocket)?;
+
+ let (addr, len) = sockaddr_un(path).context("failed to create `sockaddr_un`")?;
+ io_driver::connect(&fd, addr, len)
+ .await
+ .context("failed to connect socket")?;
+
+ Ok(SeqPacket { fd })
+ }
+
+ /// Creates a pair of connected `SOCK_SEQPACKET` sockets.
+ ///
+ /// Both returned file descriptors have the `CLOEXEC` flag set.s
+ pub fn pair() -> anyhow::Result<(Self, Self)> {
+ let mut fds = [0, 0];
+ // Safe because we give enough space to store all the fds and we check the return value.
+ let ret = unsafe {
+ libc::socketpair(
+ libc::AF_UNIX,
+ libc::SOCK_SEQPACKET | libc::SOCK_CLOEXEC,
+ 0,
+ &mut fds[0],
+ )
+ };
+
+ if ret == 0 {
+ // Safe because we just created these sockets and we know they are valid.
+ let (s1, s2) = unsafe {
+ (
+ Arc::new(SafeDescriptor::from_raw_descriptor(fds[0])),
+ Arc::new(SafeDescriptor::from_raw_descriptor(fds[1])),
+ )
+ };
+
+ io_driver::prepare(&*s1).context(PrepareSocket)?;
+ io_driver::prepare(&*s2).context(PrepareSocket)?;
+
+ Ok((Self { fd: s1 }, Self { fd: s2 }))
+ } else {
+ Err(anyhow!(io::Error::last_os_error()))
+ }
+ }
+
+ /// Gets the number of bytes in the next packet. This blocks as if `recv` were called,
+ /// respecting the blocking and timeout settings of the underlying socket.
+ pub async fn next_packet_size(&self) -> anyhow::Result<usize> {
+ io_driver::next_packet_size(&self.fd).await
+ }
+
+ /// Clone the underlying FD.
+ pub fn try_clone(&self) -> anyhow::Result<Self> {
+ self.fd
+ .try_clone()
+ .map(|fd| Self { fd: Arc::new(fd) })
+ .map_err(From::from)
+ }
+
+ /// Writes data from `buf` to the socket.
+ ///
+ /// Returns the number of bytes written to the socket. Note that when using I/O drivers like
+ /// io_uring the data will be copied into an intermediate buffer before it is written to the
+ /// socket and so this function is best suited for sending small amounts of data. Callers that
+ /// want to avoid the intermediate buffer should use `send_iobuf` instead.
+ pub async fn send(&self, buf: &[u8]) -> anyhow::Result<usize> {
+ io_driver::write(&self.fd, buf, None).await
+ }
+
+ /// Writes `buf` with the provided file descriptors to the socket.
+ ///
+ /// Returns the number of bytes written to the socket. Like with `send`, this method may copy
+ /// both the data and the fds into an intermediate buffer before writing them to the socket.
+ /// Callers that want to avoid copying should use `send_iobuf_with_fds` instead.
+ pub async fn send_with_fds(&self, buf: &[u8], fds: &[RawFd]) -> anyhow::Result<usize> {
+ io_driver::sendmsg(&self.fd, buf, fds).await
+ }
+
+ /// Writes data from `buf` to the socket.
+ ///
+ /// This function is like `send` but takes an owned buffer instead, avoiding the need to first
+ /// copy the data into an intermediate buffer.
+ pub async fn send_iobuf<B: AsIoBufs + Unpin + 'static>(
+ &self,
+ buf: B,
+ ) -> (anyhow::Result<usize>, B) {
+ io_driver::write_iobuf(&self.fd, buf, None).await
+ }
+
+ /// Writes data from `buf` with the provided file descriptors to the socket.
+ ///
+ /// Like `send_with_fds` but doesn't require copying the data into an intermediate buffer first.
+ /// Returns the number of bytes written to the socket.
+ pub async fn send_iobuf_with_fds<B: AsIoBufs + Unpin + 'static>(
+ &self,
+ buf: B,
+ fds: &[RawFd],
+ ) -> (anyhow::Result<usize>, B) {
+ io_driver::send_iobuf_with_fds(&self.fd, buf, fds).await
+ }
+
+ /// Reads data from the socket into `buf`.
+ ///
+ /// Returns the number of bytes read from the socket. Note that when using I/O drivers like
+ /// io_uring the data will first be read into an intermediate buffer before it is copied into
+ /// `buf` and so this function is best suited for reading small amounts of data. Callers that
+ /// want to avoid the intermediate buffer should use `recv_iobuf` instead.
+ pub async fn recv(&self, buf: &mut [u8]) -> anyhow::Result<usize> {
+ io_driver::read(&self.fd, buf, None).await
+ }
+
+ /// Reads data from the socket into `buf` and any file descriptors into `fds`.
+ ///
+ /// Returns the number of bytes read from the socket and the number of file descriptors
+ /// received. Note that when using I/O drivers like io_uring the data will first be read into an
+ /// intermediate buffer before it is copied into `buf` and so this function is best suited for
+ /// reading small amounts of data. Callers that want to avoid the intermediate buffer should use
+ /// `recv_iobuf_with_fds` instead.
+ pub async fn recv_with_fds(
+ &self,
+ buf: &mut [u8],
+ fds: &mut [RawFd],
+ ) -> anyhow::Result<(usize, usize)> {
+ io_driver::recvmsg(&self.fd, buf, fds).await
+ }
+
+ /// Reads data from the socket into `buf`.
+ ///
+ /// This function is like `recv` but takes an owned buffer instead, avoiding the need to first
+ /// copy the data into an intermediate buffer.
+ pub async fn recv_iobuf<B: AsIoBufs + Unpin + 'static>(
+ &self,
+ buf: B,
+ ) -> (anyhow::Result<usize>, B) {
+ io_driver::read_iobuf(&self.fd, buf, None).await
+ }
+
+ /// Reads data from the socket into `buf` and any file descriptors into `fds`.
+ ///
+ /// Like `recv_with_fds` but doesn't require copying the data into an intermediate buffer first.
+ /// Returns the number of bytes read from the socket as well as the number of file descriptors
+ /// received.
+ pub async fn recv_iobuf_with_fds<B: AsIoBufs + Unpin + 'static>(
+ &self,
+ buf: B,
+ fds: &mut [RawFd],
+ ) -> (anyhow::Result<(usize, usize)>, B) {
+ io_driver::recv_iobuf_with_fds(&self.fd, buf, fds).await
+ }
+
+ /// Reads data from the socket into a `Vec<u8>`.
+ pub async fn recv_as_vec(&self) -> anyhow::Result<Vec<u8>> {
+ let len = self.next_packet_size().await?;
+ let (res, mut buf) = self.recv_iobuf(OwnedIoBuf::new(vec![0u8; len])).await;
+ let count = res?;
+ buf.truncate(count);
+ Ok(buf.into_inner())
+ }
+
+ /// Reads data and file descriptors from the socket.
+ pub async fn recv_as_vec_with_fds(&self) -> anyhow::Result<(Vec<u8>, Vec<RawFd>)> {
+ let len = self.next_packet_size().await?;
+ let mut fds = vec![0; sys_util::SCM_SOCKET_MAX_FD_COUNT];
+ let (res, mut buf) = self
+ .recv_iobuf_with_fds(OwnedIoBuf::new(vec![0u8; len]), &mut fds)
+ .await;
+ let (data_len, fd_len) = res?;
+ buf.truncate(data_len);
+ fds.truncate(fd_len);
+
+ Ok((buf.into_inner(), fds))
+ }
+}
+
+impl TryFrom<sys_util::net::UnixSeqpacket> for SeqPacket {
+ type Error = anyhow::Error;
+
+ fn try_from(value: sys_util::net::UnixSeqpacket) -> anyhow::Result<Self> {
+ // Safe because `value` owns the fd.
+ let fd =
+ Arc::new(unsafe { SafeDescriptor::from_raw_descriptor(value.into_raw_descriptor()) });
+ io_driver::prepare(&*fd)?;
+ Ok(Self { fd })
+ }
+}
+
+impl TryFrom<SeqPacket> for sys_util::net::UnixSeqpacket {
+ type Error = SeqPacket;
+
+ fn try_from(value: SeqPacket) -> Result<Self, Self::Error> {
+ Arc::try_unwrap(value.fd)
+ .map(|fd| unsafe {
+ sys_util::net::UnixSeqpacket::from_raw_descriptor(fd.into_raw_descriptor())
+ })
+ .map_err(|fd| SeqPacket { fd })
+ }
+}
+
+impl AsRawDescriptor for SeqPacket {
+ fn as_raw_descriptor(&self) -> sys_util::RawDescriptor {
+ self.fd.as_raw_descriptor()
+ }
+}
+
+impl AsRawFd for SeqPacket {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd.as_raw_fd()
+ }
+}
+
+/// Like a `UnixListener` but for accepting `SOCK_SEQPACKET` sockets.
+pub struct SeqPacketListener {
+ fd: Arc<SafeDescriptor>,
+}
+
+impl SeqPacketListener {
+ pub fn bind<P: AsRef<Path>>(path: P) -> anyhow::Result<Self> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let fd =
+ unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_SEQPACKET | libc::SOCK_CLOEXEC, 0) };
+ if fd < 0 {
+ return Err(io::Error::last_os_error())
+ .context("failed to create SOCK_SEQPACKET socket");
+ }
+
+ // Safe because we just opened this socket and we know it is valid.
+ let fd = Arc::new(unsafe { SafeDescriptor::from_raw_descriptor(fd) });
+ io_driver::prepare(&*fd).context(PrepareSocket)?;
+
+ let (addr, len) = sockaddr_un(path).context("failed to create `sockaddr_un`")?;
+ // Safe connect since we handle the error and use the right length generated from
+ // `sockaddr_un`.
+ unsafe {
+ let ret = libc::bind(fd.as_raw_descriptor(), &addr as *const _ as *const _, len);
+ if ret < 0 {
+ return Err(anyhow!(io::Error::last_os_error()));
+ }
+ let ret = libc::listen(fd.as_raw_descriptor(), 128);
+ if ret < 0 {
+ return Err(anyhow!(io::Error::last_os_error()));
+ }
+ }
+
+ Ok(Self { fd })
+ }
+
+ /// Accepts a new incoming connection and returns the socket associated with that connection.
+ pub async fn accept(&self) -> anyhow::Result<SeqPacket> {
+ let fd = io_driver::accept(&self.fd)
+ .await
+ .map(Arc::new)
+ .context("failed to accept connection")?;
+ io_driver::prepare(&*fd).context(PrepareSocket)?;
+ Ok(SeqPacket { fd })
+ }
+
+ /// Gets the path that this listener is bound to.
+ pub fn path(&self) -> anyhow::Result<PathBuf> {
+ let mut addr = libc::sockaddr_un {
+ sun_family: libc::AF_UNIX as libc::sa_family_t,
+ sun_path: [0; 108],
+ };
+ let sun_path_offset = offset_of!(libc::sockaddr_un, sun_path) as libc::socklen_t;
+ let mut len = size_of::<libc::sockaddr_un>() as libc::socklen_t;
+ // Safe because the length given matches the length of the data of the given pointer, and we
+ // check the return value.
+ let ret = unsafe {
+ libc::getsockname(
+ self.fd.as_raw_descriptor(),
+ &mut addr as *mut libc::sockaddr_un as *mut libc::sockaddr,
+ &mut len,
+ )
+ };
+ if ret < 0 {
+ return Err(anyhow!(io::Error::last_os_error()));
+ }
+ if addr.sun_family != libc::AF_UNIX as libc::sa_family_t
+ || addr.sun_path[0] == 0
+ || len < 1 + sun_path_offset
+ {
+ return Err(anyhow!(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "getsockname on socket returned invalid value",
+ )));
+ }
+
+ let path = OsString::from_vec(
+ addr.sun_path[..(len - sun_path_offset - 1) as usize]
+ .iter()
+ .map(|&c| c as _)
+ .collect(),
+ );
+ Ok(path.into())
+ }
+}
+
+impl AsRawDescriptor for SeqPacketListener {
+ fn as_raw_descriptor(&self) -> sys_util::RawDescriptor {
+ self.fd.as_raw_descriptor()
+ }
+}
+
+impl AsRawFd for SeqPacketListener {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd.as_raw_fd()
+ }
+}
+
+/// Used to attempt to clean up a `SeqPacketListener` after it is dropped.
+pub struct UnlinkSeqPacketListener(pub SeqPacketListener);
+impl AsRef<SeqPacketListener> for UnlinkSeqPacketListener {
+ fn as_ref(&self) -> &SeqPacketListener {
+ &self.0
+ }
+}
+
+impl AsRawFd for UnlinkSeqPacketListener {
+ fn as_raw_fd(&self) -> RawFd {
+ self.0.as_raw_fd()
+ }
+}
+
+impl Deref for UnlinkSeqPacketListener {
+ type Target = SeqPacketListener;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl Drop for UnlinkSeqPacketListener {
+ fn drop(&mut self) {
+ if let Ok(path) = self.0.path() {
+ if let Err(e) = remove_file(path) {
+ warn!("failed to remove socket file: {:?}", e);
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use std::{
+ env,
+ fs::File,
+ io::Write,
+ time::{Duration, Instant},
+ };
+
+ use sys_util::{AsRawDescriptor, EventFd};
+
+ use crate::{with_deadline, Executor};
+
+ #[test]
+ fn send_recv_no_fd() {
+ Executor::new()
+ .run_until(async {
+ let (s1, s2) = SeqPacket::pair().expect("failed to create socket pair");
+
+ let send_buf = [1u8, 1, 2, 21, 34, 55];
+ let write_count = s1
+ .send_with_fds(&send_buf, &[])
+ .await
+ .expect("failed to send data");
+
+ assert_eq!(write_count, 6);
+
+ let mut buf = [0; 6];
+ let mut files = [0; 1];
+ let (read_count, file_count) = s2
+ .recv_with_fds(&mut buf[..], &mut files)
+ .await
+ .expect("failed to recv data");
+
+ assert_eq!(read_count, 6);
+ assert_eq!(file_count, 0);
+ assert_eq!(buf, [1, 1, 2, 21, 34, 55]);
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn send_recv_iobuf_no_fd() {
+ Executor::new()
+ .run_until(async {
+ let (s1, s2) = SeqPacket::pair().expect("failed to create socket pair");
+
+ let send_buf = [1u8, 1, 2, 21, 34, 55];
+ let (res, _) = s1
+ .send_iobuf_with_fds(OwnedIoBuf::new(Vec::from(send_buf)), &[])
+ .await;
+ let write_count = res.expect("failed to send data");
+ assert_eq!(write_count, 6);
+
+ let iobuf = OwnedIoBuf::new(vec![0; 6]);
+ let mut files = [0; 1];
+ let (res, iobuf) = s2.recv_iobuf_with_fds(iobuf, &mut files).await;
+ let (read_count, file_count) = res.expect("failed to recv data");
+
+ assert_eq!(read_count, 6);
+ assert_eq!(file_count, 0);
+ assert_eq!(&*iobuf, &[1, 1, 2, 21, 34, 55]);
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn send_recv_only_fd() {
+ Executor::new()
+ .run_until(async {
+ let (s1, s2) = SeqPacket::pair().expect("failed to create socket pair");
+
+ let evt = EventFd::new().expect("failed to create eventfd");
+ let write_count = s1
+ .send_with_fds(&[], &[evt.as_raw_descriptor()])
+ .await
+ .expect("failed to send fd");
+
+ assert_eq!(write_count, 0);
+
+ let mut files = [-1; 2];
+ let (read_count, file_count) = s2
+ .recv_with_fds(&mut [], &mut files)
+ .await
+ .expect("failed to recv fd");
+
+ assert_eq!(read_count, 0);
+ assert_eq!(file_count, 1);
+ assert!(files[0] >= 0);
+ assert_ne!(files[0], s1.as_raw_descriptor());
+ assert_ne!(files[0], s2.as_raw_descriptor());
+ assert_ne!(files[0], evt.as_raw_descriptor());
+
+ let mut file = unsafe { File::from_raw_descriptor(files[0]) };
+ file.write_all(&1203u64.to_ne_bytes())
+ .expect("failed to write to sent fd");
+
+ assert_eq!(evt.read().expect("failed to read from eventfd"), 1203);
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn send_recv_iobuf_only_fd() {
+ Executor::new()
+ .run_until(async {
+ let (s1, s2) = SeqPacket::pair().expect("failed to create socket pair");
+
+ let evt = EventFd::new().expect("failed to create eventfd");
+ let (res, _) = s1
+ .send_iobuf_with_fds(OwnedIoBuf::new(vec![]), &[evt.as_raw_descriptor()])
+ .await;
+ let write_count = res.expect("failed to send fd");
+ assert_eq!(write_count, 0);
+
+ let mut files = [-1; 2];
+ let (res, _) = s2
+ .recv_iobuf_with_fds(OwnedIoBuf::new(vec![]), &mut files)
+ .await;
+ let (read_count, file_count) = res.expect("failed to recv fd");
+
+ assert_eq!(read_count, 0);
+ assert_eq!(file_count, 1);
+ assert!(files[0] >= 0);
+ assert_ne!(files[0], s1.as_raw_descriptor());
+ assert_ne!(files[0], s2.as_raw_descriptor());
+ assert_ne!(files[0], evt.as_raw_descriptor());
+
+ let mut file = unsafe { File::from_raw_descriptor(files[0]) };
+ file.write_all(&1203u64.to_ne_bytes())
+ .expect("failed to write to sent fd");
+
+ assert_eq!(evt.read().expect("failed to read from eventfd"), 1203);
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn send_recv_with_fd() {
+ Executor::new()
+ .run_until(async {
+ let (s1, s2) = SeqPacket::pair().expect("failed to create socket pair");
+
+ let evt = EventFd::new().expect("failed to create eventfd");
+ let write_count = s1
+ .send_with_fds(&[237], &[evt.as_raw_descriptor()])
+ .await
+ .expect("failed to send fd");
+
+ assert_eq!(write_count, 1);
+
+ let mut files = [-1; 2];
+ let mut buf = [0u8];
+ let (read_count, file_count) = s2
+ .recv_with_fds(&mut buf, &mut files)
+ .await
+ .expect("failed to recv fd");
+
+ assert_eq!(read_count, 1);
+ assert_eq!(buf[0], 237);
+ assert_eq!(file_count, 1);
+ assert!(files[0] >= 0);
+ assert_ne!(files[0], s1.as_raw_descriptor());
+ assert_ne!(files[0], s2.as_raw_descriptor());
+ assert_ne!(files[0], evt.as_raw_descriptor());
+
+ let mut file = unsafe { File::from_raw_descriptor(files[0]) };
+
+ file.write_all(&1203u64.to_ne_bytes())
+ .expect("failed to write to sent fd");
+
+ assert_eq!(evt.read().expect("failed to read from eventfd"), 1203);
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn send_recv_iobuf_with_fd() {
+ Executor::new()
+ .run_until(async {
+ let (s1, s2) = SeqPacket::pair().expect("failed to create socket pair");
+
+ let evt = EventFd::new().expect("failed to create eventfd");
+ let (res, _) = s1
+ .send_iobuf_with_fds(OwnedIoBuf::new(vec![237]), &[evt.as_raw_descriptor()])
+ .await;
+ let write_count = res.expect("failed to send fd");
+
+ assert_eq!(write_count, 1);
+
+ let mut files = [-1; 2];
+ let iobuf = OwnedIoBuf::new(vec![0]);
+ let (res, iobuf) = s2.recv_iobuf_with_fds(iobuf, &mut files).await;
+ let (read_count, file_count) = res.expect("failed to recv fd");
+
+ assert_eq!(read_count, 1);
+ assert_eq!(iobuf[0], 237);
+ assert_eq!(file_count, 1);
+ assert!(files[0] >= 0);
+ assert_ne!(files[0], s1.as_raw_descriptor());
+ assert_ne!(files[0], s2.as_raw_descriptor());
+ assert_ne!(files[0], evt.as_raw_descriptor());
+
+ let mut file = unsafe { File::from_raw_descriptor(files[0]) };
+
+ file.write_all(&1203u64.to_ne_bytes())
+ .expect("failed to write to sent fd");
+
+ assert_eq!(evt.read().expect("failed to read from eventfd"), 1203);
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn sockaddr_un_zero_length_input() {
+ let _res = sockaddr_un(Path::new("")).expect("sockaddr_un failed");
+ }
+
+ #[test]
+ fn sockaddr_un_long_input_err() {
+ let res = sockaddr_un(Path::new(&"a".repeat(108)));
+ assert!(res.is_err());
+ }
+
+ #[test]
+ fn sockaddr_un_long_input_pass() {
+ let _res = sockaddr_un(Path::new(&"a".repeat(107))).expect("sockaddr_un failed");
+ }
+
+ #[test]
+ fn sockaddr_un_len_check() {
+ let (_addr, len) = sockaddr_un(Path::new(&"a".repeat(50))).expect("sockaddr_un failed");
+ assert_eq!(
+ len,
+ (offset_of!(libc::sockaddr_un, sun_path) + 50 + 1) as u32
+ );
+ }
+
+ #[test]
+ fn sockaddr_un_pass() {
+ let path_size = 50;
+ let (addr, len) =
+ sockaddr_un(Path::new(&"a".repeat(path_size))).expect("sockaddr_un failed");
+ assert_eq!(
+ len,
+ (offset_of!(libc::sockaddr_un, sun_path) + path_size + 1) as u32
+ );
+ assert_eq!(addr.sun_family, libc::AF_UNIX as libc::sa_family_t);
+
+ // Check `sun_path` in returned `sockaddr_un`
+ let mut ref_sun_path = [0; 108];
+ for path in ref_sun_path.iter_mut().take(path_size) {
+ *path = 'a' as libc::c_char;
+ }
+
+ for (addr_char, ref_char) in addr.sun_path.iter().zip(ref_sun_path.iter()) {
+ assert_eq!(addr_char, ref_char);
+ }
+ }
+
+ #[test]
+ fn unix_seqpacket_path_not_exists() {
+ Executor::new()
+ .run_until(async {
+ let res = SeqPacket::connect("/path/not/exists").await;
+ assert!(res.is_err());
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn unix_seqpacket_listener_path() {
+ let mut socket_path = env::temp_dir();
+ socket_path.push("unix_seqpacket_listener_path");
+ let listener = UnlinkSeqPacketListener(
+ SeqPacketListener::bind(&socket_path).expect("failed to create SeqPacketListener"),
+ );
+ let listener_path = listener.path().expect("failed to get socket listener path");
+ assert_eq!(socket_path, listener_path);
+ }
+
+ #[test]
+ fn unix_seqpacket_path_exists_pass() {
+ Executor::new()
+ .run_until(async {
+ let mut socket_path = env::temp_dir();
+ socket_path.push("path_to_socket");
+ let _listener = UnlinkSeqPacketListener(
+ SeqPacketListener::bind(&socket_path)
+ .expect("failed to create SeqPacketListener"),
+ );
+ let _res = SeqPacket::connect(socket_path.as_path())
+ .await
+ .expect("SeqPacket::connect failed");
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn unix_seqpacket_path_listener_accept() {
+ Executor::new()
+ .run_until(async {
+ let mut socket_path = env::temp_dir();
+ socket_path.push("path_listerner_accept");
+ let listener = UnlinkSeqPacketListener(
+ SeqPacketListener::bind(&socket_path)
+ .expect("failed to create SeqPacketListener"),
+ );
+ let s1 = SeqPacket::connect(&socket_path)
+ .await
+ .expect("SeqPacket::connect failed");
+
+ let s2 = listener.accept().await.expect("SeqPacket::accept failed");
+
+ let data1 = &[0, 1, 2, 3, 4];
+ let data2 = &[10, 11, 12, 13, 14];
+ s2.send(data2).await.expect("failed to send data2");
+ s1.send(data1).await.expect("failed to send data1");
+ let recv_data = &mut [0; 5];
+ s2.recv(recv_data).await.expect("failed to recv data");
+ assert_eq!(data1, recv_data);
+ s1.recv(recv_data).await.expect("failed to recv data");
+ assert_eq!(data2, recv_data);
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn unix_seqpacket_send_recv() {
+ Executor::new()
+ .run_until(async {
+ let (s1, s2) = SeqPacket::pair().expect("failed to create socket pair");
+ let data1 = &[0, 1, 2, 3, 4];
+ let data2 = &[10, 11, 12, 13, 14];
+ s2.send(data2).await.expect("failed to send data2");
+ s1.send(data1).await.expect("failed to send data1");
+ let recv_data = &mut [0; 5];
+ s2.recv(recv_data).await.expect("failed to recv data");
+ assert_eq!(data1, recv_data);
+ s1.recv(recv_data).await.expect("failed to recv data");
+ assert_eq!(data2, recv_data);
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn unix_seqpacket_send_fragments() {
+ Executor::new()
+ .run_until(async {
+ let (s1, s2) = SeqPacket::pair().expect("failed to create socket pair");
+ let data1 = &[0, 1, 2, 3, 4];
+ let data2 = &[10, 11, 12, 13, 14, 15, 16];
+ s1.send(data1).await.expect("failed to send data1");
+ s1.send(data2).await.expect("failed to send data2");
+
+ let recv_data = &mut [0; 32];
+ let size = s2.recv(recv_data).await.expect("failed to recv data");
+ assert_eq!(size, data1.len());
+ assert_eq!(data1, &recv_data[0..size]);
+
+ let size = s2.recv(recv_data).await.expect("failed to recv data");
+ assert_eq!(size, data2.len());
+ assert_eq!(data2, &recv_data[0..size]);
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn unix_seqpacket_next_packet_size() {
+ Executor::new()
+ .run_until(async {
+ let (s1, s2) = SeqPacket::pair().expect("failed to create socket pair");
+ let data1 = &[0, 1, 2, 3, 4];
+ s1.send(data1).await.expect("failed to send data");
+
+ assert_eq!(s2.next_packet_size().await.unwrap(), 5);
+ assert!(with_deadline(
+ Instant::now() + Duration::from_micros(1),
+ s1.next_packet_size()
+ )
+ .await
+ .is_err());
+
+ drop(s2);
+ assert_eq!(
+ s1.next_packet_size()
+ .await
+ .unwrap_err()
+ .downcast::<io::Error>()
+ .unwrap()
+ .kind(),
+ io::ErrorKind::ConnectionReset
+ );
+ })
+ .unwrap();
+ }
+
+ #[test]
+ fn unix_seqpacket_recv_as_vec() {
+ Executor::new()
+ .run_until(async {
+ let (s1, s2) = SeqPacket::pair().expect("failed to create socket pair");
+ let data1 = &[0, 1, 2, 3, 4];
+ s1.send(data1).await.expect("failed to send data");
+
+ let recv_data = s2.recv_as_vec().await.expect("failed to recv data");
+ assert_eq!(&recv_data, &*data1);
+ })
+ .unwrap();
+ }
+}
diff --git a/data_model/Android.bp b/common/data_model/Android.bp
index c885de7a0..57187b446 100644
--- a/data_model/Android.bp
+++ b/common/data_model/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -10,32 +10,28 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "data_model_defaults",
+rust_test {
+ name: "data_model_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "data_model",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2021",
rustlibs: [
"libassertions",
+ "libcfg_if",
"liblibc",
"libserde",
+ "libthiserror",
],
-}
-
-rust_test_host {
- name: "data_model_host_test_src_lib",
- defaults: ["data_model_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "data_model_device_test_src_lib",
- defaults: ["data_model_defaults"],
+ proc_macros: ["libremain"],
}
rust_library {
@@ -43,21 +39,16 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "data_model",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libassertions",
+ "libcfg_if",
"liblibc",
"libserde",
+ "libthiserror",
],
+ proc_macros: ["libremain"],
}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// libc-0.2.93 "default,std"
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// syn-1.0.70 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// unicode-xid-0.2.1 "default"
diff --git a/data_model/Cargo.toml b/common/data_model/Cargo.toml
index 51c85a692..d3e3f3ed8 100644
--- a/data_model/Cargo.toml
+++ b/common/data_model/Cargo.toml
@@ -2,12 +2,21 @@
name = "data_model"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
include = ["src/**/*", "Cargo.toml"]
[dependencies]
assertions = { path = "../assertions" } # provided by ebuild
+cfg-if = "1.0.0"
libc = "*"
+remain = "0.2"
serde = { version = "1", features = ["derive"] }
+thiserror = "1.0.20"
+
+[target.'cfg(windows)'.dependencies]
+winapi = { version = "*", features = ["everything", "std", "impl-default"] }
+
+[target.'cfg(unix)'.dependencies]
+libc = "*"
[workspace]
diff --git a/data_model/src/endian.rs b/common/data_model/src/endian.rs
index 6d3645cce..6d3645cce 100644
--- a/data_model/src/endian.rs
+++ b/common/data_model/src/endian.rs
diff --git a/data_model/src/flexible_array.rs b/common/data_model/src/flexible_array.rs
index c0689dceb..ac1226d0e 100644
--- a/data_model/src/flexible_array.rs
+++ b/common/data_model/src/flexible_array.rs
@@ -41,13 +41,13 @@ pub fn vec_with_array_field<T: Default, F>(count: usize) -> Vec<T> {
/// The following code provides generic helpers for creating and accessing flexible array structs.
/// A complete definition of flexible array structs is found in the ISO 9899 specification
-/// (http://www.iso-9899.info/n1570.html). A flexible array struct is of the form:
+/// <http://www.iso-9899.info/n1570.html>. A flexible array struct is of the form:
///
/// ```ignore
/// #[repr(C)]
/// struct T {
/// some_data: u32,
-/// nent: u32,
+/// nents: u32,
/// entries: __IncompleteArrayField<S>,
/// }
/// ```
@@ -55,7 +55,7 @@ pub fn vec_with_array_field<T: Default, F>(count: usize) -> Vec<T> {
///
/// - `T` is the flexible array struct type
/// - `S` is the flexible array type
-/// - `nent` is the flexible array length
+/// - `nents` is the flexible array length
/// - `entries` is the flexible array member
///
/// These structures are used by the kernel API.
@@ -64,7 +64,7 @@ pub fn vec_with_array_field<T: Default, F>(count: usize) -> Vec<T> {
///
/// When implemented for `T`, this trait allows the caller to set number of `S` entries and
/// retrieve a slice of `S` entries. Trait methods must only be called by the FlexibleArrayWrapper
-/// type.
+/// type. Don't implement this trait directly, use the flexible_array! macro to avoid duplication.
pub trait FlexibleArray<S> {
/// Implementations must set flexible array length in the flexible array struct to the value
/// specified by `len`. Appropriate conversions (i.e, usize to u32) are allowed so long as
@@ -74,9 +74,39 @@ pub trait FlexibleArray<S> {
/// conversions (i.e, usize to u32) are allowed so long as they don't overflow or underflow.
fn get_len(&self) -> usize;
/// Implementations must return a slice of flexible array member of length `len`.
- fn get_slice(&self, len: usize) -> &[S];
+ /// # Safety
+ /// Do not use this function directly, as the FlexibleArrayWrapper will guarantee safety.
+ unsafe fn get_slice(&self, len: usize) -> &[S];
/// Implementations must return a mutable slice of flexible array member of length `len`.
- fn get_mut_slice(&mut self, len: usize) -> &mut [S];
+ /// # Safety
+ /// Do not use this function directly, as the FlexibleArrayWrapper will guarantee safety.
+ unsafe fn get_mut_slice(&mut self, len: usize) -> &mut [S];
+}
+
+/// Always use this macro for implementing the FlexibleArray<`S`> trait for a given `T`. There
+/// exists an 1:1 mapping of macro identifiers to the definitions in the FlexibleArray<`S`>
+/// documentation, so refer to that for more information.
+#[macro_export]
+macro_rules! flexible_array_impl {
+ ($T:ident, $S:ident, $nents:ident, $entries:ident) => {
+ impl FlexibleArray<$S> for $T {
+ fn set_len(&mut self, len: usize) {
+ self.$nents = ::std::convert::TryInto::try_into(len).unwrap();
+ }
+
+ fn get_len(&self) -> usize {
+ self.$nents as usize
+ }
+
+ unsafe fn get_slice(&self, len: usize) -> &[$S] {
+ self.$entries.as_slice(len)
+ }
+
+ unsafe fn get_mut_slice(&mut self, len: usize) -> &mut [$S] {
+ self.$entries.as_mut_slice(len)
+ }
+ }
+ };
}
pub struct FlexibleArrayWrapper<T, S> {
@@ -121,14 +151,16 @@ where
/// mut_entries_slice instead.
pub fn entries_slice(&self) -> &[S] {
let valid_length = self.get_valid_len();
- self.entries[0].get_slice(valid_length)
+ // Safe because the length has been validated.
+ unsafe { self.entries[0].get_slice(valid_length) }
}
/// Returns a mutable slice of the flexible array member, for modifying.
pub fn mut_entries_slice(&mut self) -> &mut [S] {
let valid_length = self.get_valid_len();
self.entries[0].set_len(valid_length);
- self.entries[0].get_mut_slice(valid_length)
+ // Safe because the length has been validated.
+ unsafe { self.entries[0].get_mut_slice(valid_length) }
}
/// Get a pointer so it can be passed to the kernel. Callers must not access the flexible
diff --git a/data_model/src/lib.rs b/common/data_model/src/lib.rs
index 9684c179b..4585b1941 100644
--- a/data_model/src/lib.rs
+++ b/common/data_model/src/lib.rs
@@ -3,7 +3,7 @@
// found in the LICENSE file.
use std::io;
-use std::mem::{align_of, size_of};
+use std::mem::{size_of, MaybeUninit};
use std::slice::{from_raw_parts, from_raw_parts_mut};
/// Types for which it is safe to initialize from raw data.
@@ -19,7 +19,7 @@ use std::slice::{from_raw_parts, from_raw_parts_mut};
/// It is unsafe for `T` to be `DataInit` if `T` contains implicit padding. (LLVM considers access
/// to implicit padding to be undefined behavior, which can cause UB when working with `T`.
/// For details on structure padding in Rust, see
-/// https://doc.rust-lang.org/reference/type-layout.html#the-c-representation
+/// <https://doc.rust-lang.org/reference/type-layout.html#the-c-representation>.
pub unsafe trait DataInit: Copy + Send + Sync {
/// Converts a slice of raw data into a reference of `Self`.
///
@@ -74,21 +74,16 @@ pub unsafe trait DataInit: Copy + Send + Sync {
/// Creates an instance of `Self` by copying raw data from an io::Read stream.
fn from_reader<R: io::Read>(mut read: R) -> io::Result<Self> {
- // Allocate a Vec<u8> with enough extra space for the worst-case alignment offset.
- let mut data = vec![0u8; size_of::<Self>() + align_of::<Self>()];
-
- // Get a u8 slice within data with sufficient alignment for Self.
- let align_offset = data.as_ptr().align_offset(align_of::<Self>());
- let mut aligned_data = &mut data[align_offset..align_offset + size_of::<Self>()];
-
- read.read_exact(&mut aligned_data)?;
- match Self::from_slice(&aligned_data) {
- Some(obj) => Ok(*obj),
- None => Err(io::Error::new(
- io::ErrorKind::InvalidData,
- "from_slice failed",
- )),
- }
+ // Allocate on the stack via `MaybeUninit` to ensure proper alignment.
+ let mut out = MaybeUninit::zeroed();
+
+ // Safe because the pointer is valid and points to `size_of::<Self>()` bytes of zeroes,
+ // which is a properly initialized value for `u8`.
+ let buf = unsafe { from_raw_parts_mut(out.as_mut_ptr() as *mut u8, size_of::<Self>()) };
+ read.read_exact(buf)?;
+
+ // Safe because any bit pattern is considered a valid value for `Self`.
+ Ok(unsafe { out.assume_init() })
}
/// Converts a reference to `self` into a slice of bytes.
@@ -192,4 +187,4 @@ mod flexible_array;
pub use flexible_array::{vec_with_array_field, FlexibleArray, FlexibleArrayWrapper};
mod sys;
-pub use sys::IoBufMut;
+pub use sys::{create_iobuf, IoBuf, IoBufMut};
diff --git a/common/data_model/src/sys.rs b/common/data_model/src/sys.rs
new file mode 100644
index 000000000..2bf172271
--- /dev/null
+++ b/common/data_model/src/sys.rs
@@ -0,0 +1,16 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+//
+//! A wrapper module for platform dependent code.
+
+cfg_if::cfg_if! {
+ if #[cfg(unix)] {
+ mod unix;
+ pub use unix::*;
+ } else if #[cfg(windows)] {
+ mod windows;
+ pub use windows::*;
+ } else {
+ compile_error!("Unsupported platform");
+ }
+}
diff --git a/data_model/src/sys.rs b/common/data_model/src/sys/unix.rs
index 2f8c2323d..312668cdd 100644
--- a/data_model/src/sys.rs
+++ b/common/data_model/src/sys/unix.rs
@@ -9,6 +9,17 @@ use std::slice;
use libc::iovec;
+/// Cross platform binary compatible iovec.
+pub type IoBuf = iovec;
+
+/// Cross platform stub to create a platform specific IoBuf.
+pub fn create_iobuf(addr: *mut u8, len: usize) -> IoBuf {
+ iovec {
+ iov_base: addr as *mut c_void,
+ iov_len: len,
+ }
+}
+
/// This type is essentialy `std::io::IoBufMut`, and guaranteed to be ABI-compatible with
/// `libc::iovec`; however, it does NOT automatically deref to `&mut [u8]`, which is critical
/// because it can point to guest memory. (Guest memory is implicitly mutably borrowed by the
@@ -43,6 +54,40 @@ impl<'a> IoBufMut<'a> {
}
}
+ /// Creates a `IoBufMut` from an IoBuf.
+ ///
+ /// # Safety
+ ///
+ /// In order to use this method safely, `iobuf` must be valid for reads and writes through its
+ /// length and should live for the entire duration of lifetime `'a`.
+ pub unsafe fn from_iobuf(iobuf: IoBuf) -> IoBufMut<'a> {
+ IoBufMut {
+ iov: iobuf,
+ phantom: PhantomData,
+ }
+ }
+
+ /// Advance the internal position of the buffer.
+ ///
+ /// Panics if `count > self.len()`.
+ pub fn advance(&mut self, count: usize) {
+ assert!(count <= self.len());
+
+ self.iov.iov_len -= count;
+ // Safe because we've checked that `count <= self.len()` so both the starting and resulting
+ // pointer are within the bounds of the allocation.
+ self.iov.iov_base = unsafe { self.iov.iov_base.add(count) };
+ }
+
+ /// Shorten the length of the buffer.
+ ///
+ /// Has no effect if `len > self.len()`.
+ pub fn truncate(&mut self, len: usize) {
+ if len < self.len() {
+ self.iov.iov_len = len;
+ }
+ }
+
#[inline]
pub fn len(&self) -> usize {
self.iov.iov_len as usize
diff --git a/common/data_model/src/sys/windows.rs b/common/data_model/src/sys/windows.rs
new file mode 100644
index 000000000..4e3ae832d
--- /dev/null
+++ b/common/data_model/src/sys/windows.rs
@@ -0,0 +1,133 @@
+use std::fmt::{self, Debug};
+use std::marker::PhantomData;
+use std::slice;
+
+use winapi::shared::ws2def::WSABUF;
+
+/// Cross platform binary compatible iovec.
+pub type IoBuf = WSABUF;
+
+/// Cross platform stub to create a platform specific IoBuf.
+pub fn create_iobuf(addr: *mut u8, len: usize) -> IoBuf {
+ WSABUF {
+ buf: addr as *mut i8,
+ len: len as u32,
+ }
+}
+
+/// This type is essentialy `std::io::IoBufMut`, and guaranteed to be ABI-compatible with
+/// `WSABUF`; however, it does NOT automatically deref to `&mut [u8]`, which is critical
+/// because it can point to guest memory. (Guest memory is implicitly mutably borrowed by the
+/// guest, so another mutable borrow would violate Rust assumptions about references.)
+#[derive(Copy, Clone)]
+#[repr(transparent)]
+pub struct IoBufMut<'a> {
+ buf: WSABUF,
+ phantom: PhantomData<&'a mut [u8]>,
+}
+
+impl<'a> IoBufMut<'a> {
+ pub fn new(buf: &mut [u8]) -> IoBufMut<'a> {
+ // Safe because buf's memory is of the supplied length, and
+ // guaranteed to exist for the lifetime of the returned value.
+ unsafe { Self::from_raw_parts(buf.as_mut_ptr(), buf.len()) }
+ }
+
+ /// Creates a `IoBufMut` from a pointer and a length.
+ ///
+ /// # Safety
+ ///
+ /// In order to use this method safely, `addr` must be valid for reads and writes of `len` bytes
+ /// and should live for the entire duration of lifetime `'a`.
+ pub unsafe fn from_raw_parts(addr: *mut u8, len: usize) -> IoBufMut<'a> {
+ IoBufMut {
+ buf: WSABUF {
+ buf: addr as *mut i8,
+ len: len as u32,
+ },
+ phantom: PhantomData,
+ }
+ }
+
+ /// Creates a `IoBufMut` from an IoBuf.
+ ///
+ /// # Safety
+ ///
+ /// In order to use this method safely, `iobuf` must be valid for reads and writes through its
+ /// length and should live for the entire duration of lifetime `'a`.
+ pub unsafe fn from_iobuf(iobuf: IoBuf) -> IoBufMut<'a> {
+ IoBufMut {
+ buf: iobuf,
+ phantom: PhantomData,
+ }
+ }
+
+ /// Advance the internal position of the buffer.
+ ///
+ /// Panics if `count > self.len()`.
+ pub fn advance(&mut self, _count: usize) {
+ unimplemented!()
+ }
+
+ /// Shorten the length of the buffer.
+ ///
+ /// Has no effect if `len > self.len()`.
+ pub fn truncate(&mut self, _len: usize) {
+ unimplemented!()
+ }
+
+ #[inline]
+ pub fn len(&self) -> usize {
+ self.buf.len as usize
+ }
+
+ /// Gets a const pointer to this slice's memory.
+ #[inline]
+ pub fn as_ptr(&self) -> *const u8 {
+ self.buf.buf as *const u8
+ }
+
+ /// Gets a mutable pointer to this slice's memory.
+ #[inline]
+ pub fn as_mut_ptr(&self) -> *mut u8 {
+ self.buf.buf as *mut u8
+ }
+
+ /// Converts a slice of `IoBufMut`s into a slice of `iovec`s.
+ #[inline]
+ pub fn as_iobufs<'slice>(iovs: &'slice [IoBufMut<'_>]) -> &'slice [IoBuf] {
+ // Safe because `IoBufMut` is ABI-compatible with `WSABUF`.
+ unsafe { slice::from_raw_parts(iovs.as_ptr() as *const WSABUF, iovs.len()) }
+ }
+}
+
+impl<'a> AsRef<WSABUF> for IoBufMut<'a> {
+ fn as_ref(&self) -> &WSABUF {
+ &self.buf
+ }
+}
+
+impl<'a> AsMut<WSABUF> for IoBufMut<'a> {
+ fn as_mut(&mut self) -> &mut WSABUF {
+ &mut self.buf
+ }
+}
+
+struct DebugWSABUF(WSABUF);
+impl Debug for DebugWSABUF {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("WSABUF")
+ .field("buf", &self.0.buf)
+ .field("len", &self.0.len)
+ .finish()
+ }
+}
+
+impl<'a> Debug for IoBufMut<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("IoBufMut")
+ .field("buf", &DebugWSABUF(self.buf))
+ .field("phantom", &self.phantom)
+ .finish()
+ }
+}
diff --git a/data_model/src/volatile_memory.rs b/common/data_model/src/volatile_memory.rs
index 00f85a7fd..23bb550f6 100644
--- a/data_model/src/volatile_memory.rs
+++ b/common/data_model/src/volatile_memory.rs
@@ -20,7 +20,6 @@
//! not reordered or elided the access.
use std::cmp::min;
-use std::fmt::{self, Debug, Display};
use std::marker::PhantomData;
use std::mem::size_of;
use std::ptr::{copy, read_volatile, write_bytes, write_volatile};
@@ -28,33 +27,22 @@ use std::result;
use std::slice;
use std::usize;
-use libc::iovec;
+use remain::sorted;
+use thiserror::Error;
use crate::{sys::IoBufMut, DataInit};
-#[derive(Eq, PartialEq, Debug)]
+#[sorted]
+#[derive(Error, Eq, PartialEq, Debug)]
pub enum VolatileMemoryError {
/// `addr` is out of bounds of the volatile memory slice.
+ #[error("address 0x{addr:x} is out of bounds")]
OutOfBounds { addr: usize },
/// Taking a slice at `base` with `offset` would overflow `usize`.
+ #[error("address 0x{base:x} offset by 0x{offset:x} would overflow")]
Overflow { base: usize, offset: usize },
}
-impl Display for VolatileMemoryError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::VolatileMemoryError::*;
-
- match self {
- OutOfBounds { addr } => write!(f, "address 0x{:x} is out of bounds", addr),
- Overflow { base, offset } => write!(
- f,
- "address 0x{:x} offset by 0x{:x} would overflow",
- base, offset
- ),
- }
- }
-}
-
pub type VolatileMemoryResult<T> = result::Result<T, VolatileMemoryError>;
use crate::VolatileMemoryError as Error;
@@ -137,18 +125,32 @@ impl<'a> VolatileSlice<'a> {
self.0.len()
}
- /// Returns this `VolatileSlice` as an `iovec`.
- pub fn as_iobuf(&self) -> &iovec {
- self.0.as_ref()
+ /// Advance the starting position of this slice.
+ ///
+ /// Panics if `count > self.size()`.
+ pub fn advance(&mut self, count: usize) {
+ self.0.advance(count)
}
- /// Converts a slice of `VolatileSlice`s into a slice of `iovec`s
+ /// Shorten the length of the slice.
+ ///
+ /// Has no effect if `len > self.size()`.
+ pub fn truncate(&mut self, len: usize) {
+ self.0.truncate(len)
+ }
+
+ /// Returns this `VolatileSlice` as an `IoBufMut`.
+ pub fn as_iobuf(&self) -> &IoBufMut {
+ &self.0
+ }
+
+ /// Converts a slice of `VolatileSlice`s into a slice of `IoBufMut`s
#[allow(clippy::wrong_self_convention)]
- pub fn as_iobufs<'slice>(iovs: &'slice [VolatileSlice<'_>]) -> &'slice [iovec] {
+ pub fn as_iobufs<'mem, 'slice>(
+ iovs: &'slice [VolatileSlice<'mem>],
+ ) -> &'slice [IoBufMut<'mem>] {
// Safe because `VolatileSlice` is ABI-compatible with `IoBufMut`.
- IoBufMut::as_iobufs(unsafe {
- slice::from_raw_parts(iovs.as_ptr() as *const IoBufMut, iovs.len())
- })
+ unsafe { slice::from_raw_parts(iovs.as_ptr() as *const IoBufMut, iovs.len()) }
}
/// Creates a copy of this slice with the address increased by `count` bytes, and the size
diff --git a/io_uring/.build_test_skip b/common/io_uring/.build_test_skip
index e69de29bb..e69de29bb 100644
--- a/io_uring/.build_test_skip
+++ b/common/io_uring/.build_test_skip
diff --git a/common/io_uring/Cargo.toml b/common/io_uring/Cargo.toml
new file mode 100644
index 000000000..47b31382f
--- /dev/null
+++ b/common/io_uring/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "io_uring"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2021"
+
+[dependencies]
+data_model = { path = "../data_model" } # provided by ebuild
+libc = "0.2.93"
+remain = "0.2"
+sync = { path = "../sync" } # provided by ebuild
+sys_util = { path = "../sys_util" } # provided by ebuild
+thiserror = "1"
+
+[dev-dependencies]
+tempfile = "3"
+
+[workspace]
diff --git a/common/io_uring/DEPRECATED.md b/common/io_uring/DEPRECATED.md
new file mode 100644
index 000000000..b3496d1bc
--- /dev/null
+++ b/common/io_uring/DEPRECATED.md
@@ -0,0 +1,4 @@
+Use crosvm/io_uring instead.
+
+Code in this directory is not used by crosvm, it is only used in ChromeOS and will move to a
+separate ChromeOS repository soon.
diff --git a/sys_util/TEST_MAPPING b/common/io_uring/TEST_MAPPING
index a903dee1e..6d1dee17d 100644
--- a/sys_util/TEST_MAPPING
+++ b/common/io_uring/TEST_MAPPING
@@ -4,10 +4,10 @@
// "presubmit": [
// {
// "host": true,
-// "name": "sys_util_host_test_src_lib"
+// "name": "io_uring_test_src_lib"
// },
// {
-// "name": "sys_util_device_test_src_lib"
+// "name": "io_uring_test_src_lib"
// }
// ]
}
diff --git a/common/io_uring/bindgen.sh b/common/io_uring/bindgen.sh
new file mode 100755
index 000000000..81c26310c
--- /dev/null
+++ b/common/io_uring/bindgen.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Regenerate io_uring bindgen bindings.
+
+set -euo pipefail
+cd "$(dirname "${BASH_SOURCE[0]}")/../.."
+
+source tools/impl/bindgen-common.sh
+
+bindgen_generate \
+ --allowlist-type='io_uring_.*' \
+ --allowlist-var='IO_URING_.*' \
+ --allowlist-var='IORING_.*' \
+ "${BINDGEN_LINUX}/include/uapi/linux/io_uring.h" \
+ | replace_linux_int_types | rustfmt \
+ > common/io_uring/src/bindings.rs
diff --git a/common/io_uring/cargo2android.json b/common/io_uring/cargo2android.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/common/io_uring/cargo2android.json
@@ -0,0 +1 @@
+{}
diff --git a/common/io_uring/src/bindings.rs b/common/io_uring/src/bindings.rs
new file mode 100644
index 000000000..c6e52f41f
--- /dev/null
+++ b/common/io_uring/src/bindings.rs
@@ -0,0 +1,353 @@
+/* automatically generated by tools/bindgen-all-the-things */
+
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+
+#[repr(C)]
+#[derive(Default)]
+pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
+impl<T> __IncompleteArrayField<T> {
+ #[inline]
+ pub const fn new() -> Self {
+ __IncompleteArrayField(::std::marker::PhantomData, [])
+ }
+ #[inline]
+ pub fn as_ptr(&self) -> *const T {
+ self as *const _ as *const T
+ }
+ #[inline]
+ pub fn as_mut_ptr(&mut self) -> *mut T {
+ self as *mut _ as *mut T
+ }
+ #[inline]
+ pub unsafe fn as_slice(&self, len: usize) -> &[T] {
+ ::std::slice::from_raw_parts(self.as_ptr(), len)
+ }
+ #[inline]
+ pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
+ ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
+ }
+}
+impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
+ fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+ fmt.write_str("__IncompleteArrayField")
+ }
+}
+pub const IORING_SETUP_IOPOLL: u32 = 1;
+pub const IORING_SETUP_SQPOLL: u32 = 2;
+pub const IORING_SETUP_SQ_AFF: u32 = 4;
+pub const IORING_SETUP_CQSIZE: u32 = 8;
+pub const IORING_SETUP_CLAMP: u32 = 16;
+pub const IORING_SETUP_ATTACH_WQ: u32 = 32;
+pub const IORING_SETUP_R_DISABLED: u32 = 64;
+pub const IORING_FSYNC_DATASYNC: u32 = 1;
+pub const IORING_TIMEOUT_ABS: u32 = 1;
+pub const IORING_CQE_F_BUFFER: u32 = 1;
+pub const IORING_OFF_SQ_RING: u32 = 0;
+pub const IORING_OFF_CQ_RING: u32 = 134217728;
+pub const IORING_OFF_SQES: u32 = 268435456;
+pub const IORING_SQ_NEED_WAKEUP: u32 = 1;
+pub const IORING_SQ_CQ_OVERFLOW: u32 = 2;
+pub const IORING_CQ_EVENTFD_DISABLED: u32 = 1;
+pub const IORING_ENTER_GETEVENTS: u32 = 1;
+pub const IORING_ENTER_SQ_WAKEUP: u32 = 2;
+pub const IORING_ENTER_SQ_WAIT: u32 = 4;
+pub const IORING_FEAT_SINGLE_MMAP: u32 = 1;
+pub const IORING_FEAT_NODROP: u32 = 2;
+pub const IORING_FEAT_SUBMIT_STABLE: u32 = 4;
+pub const IORING_FEAT_RW_CUR_POS: u32 = 8;
+pub const IORING_FEAT_CUR_PERSONALITY: u32 = 16;
+pub const IORING_FEAT_FAST_POLL: u32 = 32;
+pub const IORING_FEAT_POLL_32BITS: u32 = 64;
+pub const IO_URING_OP_SUPPORTED: u32 = 1;
+pub type __kernel_rwf_t = ::std::os::raw::c_int;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct io_uring_sqe {
+ pub opcode: u8,
+ pub flags: u8,
+ pub ioprio: u16,
+ pub fd: i32,
+ pub __bindgen_anon_1: io_uring_sqe__bindgen_ty_1,
+ pub __bindgen_anon_2: io_uring_sqe__bindgen_ty_2,
+ pub len: u32,
+ pub __bindgen_anon_3: io_uring_sqe__bindgen_ty_3,
+ pub user_data: u64,
+ pub __bindgen_anon_4: io_uring_sqe__bindgen_ty_4,
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union io_uring_sqe__bindgen_ty_1 {
+ pub off: u64,
+ pub addr2: u64,
+}
+impl Default for io_uring_sqe__bindgen_ty_1 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union io_uring_sqe__bindgen_ty_2 {
+ pub addr: u64,
+ pub splice_off_in: u64,
+}
+impl Default for io_uring_sqe__bindgen_ty_2 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union io_uring_sqe__bindgen_ty_3 {
+ pub rw_flags: __kernel_rwf_t,
+ pub fsync_flags: u32,
+ pub poll_events: u16,
+ pub poll32_events: u32,
+ pub sync_range_flags: u32,
+ pub msg_flags: u32,
+ pub timeout_flags: u32,
+ pub accept_flags: u32,
+ pub cancel_flags: u32,
+ pub open_flags: u32,
+ pub statx_flags: u32,
+ pub fadvise_advice: u32,
+ pub splice_flags: u32,
+}
+impl Default for io_uring_sqe__bindgen_ty_3 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union io_uring_sqe__bindgen_ty_4 {
+ pub __bindgen_anon_1: io_uring_sqe__bindgen_ty_4__bindgen_ty_1,
+ pub __pad2: [u64; 3usize],
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct io_uring_sqe__bindgen_ty_4__bindgen_ty_1 {
+ pub __bindgen_anon_1: io_uring_sqe__bindgen_ty_4__bindgen_ty_1__bindgen_ty_1,
+ pub personality: u16,
+ pub splice_fd_in: i32,
+}
+#[repr(C, packed)]
+#[derive(Copy, Clone)]
+pub union io_uring_sqe__bindgen_ty_4__bindgen_ty_1__bindgen_ty_1 {
+ pub buf_index: u16,
+ pub buf_group: u16,
+}
+impl Default for io_uring_sqe__bindgen_ty_4__bindgen_ty_1__bindgen_ty_1 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+impl Default for io_uring_sqe__bindgen_ty_4__bindgen_ty_1 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+impl Default for io_uring_sqe__bindgen_ty_4 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+impl Default for io_uring_sqe {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub const IORING_OP_NOP: ::std::os::raw::c_uint = 0;
+pub const IORING_OP_READV: ::std::os::raw::c_uint = 1;
+pub const IORING_OP_WRITEV: ::std::os::raw::c_uint = 2;
+pub const IORING_OP_FSYNC: ::std::os::raw::c_uint = 3;
+pub const IORING_OP_READ_FIXED: ::std::os::raw::c_uint = 4;
+pub const IORING_OP_WRITE_FIXED: ::std::os::raw::c_uint = 5;
+pub const IORING_OP_POLL_ADD: ::std::os::raw::c_uint = 6;
+pub const IORING_OP_POLL_REMOVE: ::std::os::raw::c_uint = 7;
+pub const IORING_OP_SYNC_FILE_RANGE: ::std::os::raw::c_uint = 8;
+pub const IORING_OP_SENDMSG: ::std::os::raw::c_uint = 9;
+pub const IORING_OP_RECVMSG: ::std::os::raw::c_uint = 10;
+pub const IORING_OP_TIMEOUT: ::std::os::raw::c_uint = 11;
+pub const IORING_OP_TIMEOUT_REMOVE: ::std::os::raw::c_uint = 12;
+pub const IORING_OP_ACCEPT: ::std::os::raw::c_uint = 13;
+pub const IORING_OP_ASYNC_CANCEL: ::std::os::raw::c_uint = 14;
+pub const IORING_OP_LINK_TIMEOUT: ::std::os::raw::c_uint = 15;
+pub const IORING_OP_CONNECT: ::std::os::raw::c_uint = 16;
+pub const IORING_OP_FALLOCATE: ::std::os::raw::c_uint = 17;
+pub const IORING_OP_OPENAT: ::std::os::raw::c_uint = 18;
+pub const IORING_OP_CLOSE: ::std::os::raw::c_uint = 19;
+pub const IORING_OP_FILES_UPDATE: ::std::os::raw::c_uint = 20;
+pub const IORING_OP_STATX: ::std::os::raw::c_uint = 21;
+pub const IORING_OP_READ: ::std::os::raw::c_uint = 22;
+pub const IORING_OP_WRITE: ::std::os::raw::c_uint = 23;
+pub const IORING_OP_FADVISE: ::std::os::raw::c_uint = 24;
+pub const IORING_OP_MADVISE: ::std::os::raw::c_uint = 25;
+pub const IORING_OP_SEND: ::std::os::raw::c_uint = 26;
+pub const IORING_OP_RECV: ::std::os::raw::c_uint = 27;
+pub const IORING_OP_OPENAT2: ::std::os::raw::c_uint = 28;
+pub const IORING_OP_EPOLL_CTL: ::std::os::raw::c_uint = 29;
+pub const IORING_OP_SPLICE: ::std::os::raw::c_uint = 30;
+pub const IORING_OP_PROVIDE_BUFFERS: ::std::os::raw::c_uint = 31;
+pub const IORING_OP_REMOVE_BUFFERS: ::std::os::raw::c_uint = 32;
+pub const IORING_OP_TEE: ::std::os::raw::c_uint = 33;
+pub const IORING_OP_LAST: ::std::os::raw::c_uint = 34;
+pub type _bindgen_ty_2 = ::std::os::raw::c_uint;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct io_uring_cqe {
+ pub user_data: u64,
+ pub res: i32,
+ pub flags: u32,
+}
+pub const IORING_CQE_BUFFER_SHIFT: ::std::os::raw::c_uint = 16;
+pub type _bindgen_ty_3 = ::std::os::raw::c_uint;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct io_sqring_offsets {
+ pub head: u32,
+ pub tail: u32,
+ pub ring_mask: u32,
+ pub ring_entries: u32,
+ pub flags: u32,
+ pub dropped: u32,
+ pub array: u32,
+ pub resv1: u32,
+ pub resv2: u64,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct io_cqring_offsets {
+ pub head: u32,
+ pub tail: u32,
+ pub ring_mask: u32,
+ pub ring_entries: u32,
+ pub overflow: u32,
+ pub cqes: u32,
+ pub flags: u32,
+ pub resv1: u32,
+ pub resv2: u64,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct io_uring_params {
+ pub sq_entries: u32,
+ pub cq_entries: u32,
+ pub flags: u32,
+ pub sq_thread_cpu: u32,
+ pub sq_thread_idle: u32,
+ pub features: u32,
+ pub wq_fd: u32,
+ pub resv: [u32; 3usize],
+ pub sq_off: io_sqring_offsets,
+ pub cq_off: io_cqring_offsets,
+}
+pub const IORING_REGISTER_BUFFERS: ::std::os::raw::c_uint = 0;
+pub const IORING_UNREGISTER_BUFFERS: ::std::os::raw::c_uint = 1;
+pub const IORING_REGISTER_FILES: ::std::os::raw::c_uint = 2;
+pub const IORING_UNREGISTER_FILES: ::std::os::raw::c_uint = 3;
+pub const IORING_REGISTER_EVENTFD: ::std::os::raw::c_uint = 4;
+pub const IORING_UNREGISTER_EVENTFD: ::std::os::raw::c_uint = 5;
+pub const IORING_REGISTER_FILES_UPDATE: ::std::os::raw::c_uint = 6;
+pub const IORING_REGISTER_EVENTFD_ASYNC: ::std::os::raw::c_uint = 7;
+pub const IORING_REGISTER_PROBE: ::std::os::raw::c_uint = 8;
+pub const IORING_REGISTER_PERSONALITY: ::std::os::raw::c_uint = 9;
+pub const IORING_UNREGISTER_PERSONALITY: ::std::os::raw::c_uint = 10;
+pub const IORING_REGISTER_RESTRICTIONS: ::std::os::raw::c_uint = 11;
+pub const IORING_REGISTER_ENABLE_RINGS: ::std::os::raw::c_uint = 12;
+pub const IORING_REGISTER_LAST: ::std::os::raw::c_uint = 13;
+pub type _bindgen_ty_4 = ::std::os::raw::c_uint;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct io_uring_files_update {
+ pub offset: u32,
+ pub resv: u32,
+ pub fds: u64,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct io_uring_probe_op {
+ pub op: u8,
+ pub resv: u8,
+ pub flags: u16,
+ pub resv2: u32,
+}
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct io_uring_probe {
+ pub last_op: u8,
+ pub ops_len: u8,
+ pub resv: u16,
+ pub resv2: [u32; 3usize],
+ pub ops: __IncompleteArrayField<io_uring_probe_op>,
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct io_uring_restriction {
+ pub opcode: u16,
+ pub __bindgen_anon_1: io_uring_restriction__bindgen_ty_1,
+ pub resv: u8,
+ pub resv2: [u32; 3usize],
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union io_uring_restriction__bindgen_ty_1 {
+ pub register_op: u8,
+ pub sqe_op: u8,
+ pub sqe_flags: u8,
+}
+impl Default for io_uring_restriction__bindgen_ty_1 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+impl Default for io_uring_restriction {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub const IORING_RESTRICTION_REGISTER_OP: ::std::os::raw::c_uint = 0;
+pub const IORING_RESTRICTION_SQE_OP: ::std::os::raw::c_uint = 1;
+pub const IORING_RESTRICTION_SQE_FLAGS_ALLOWED: ::std::os::raw::c_uint = 2;
+pub const IORING_RESTRICTION_SQE_FLAGS_REQUIRED: ::std::os::raw::c_uint = 3;
+pub const IORING_RESTRICTION_LAST: ::std::os::raw::c_uint = 4;
+pub type _bindgen_ty_5 = ::std::os::raw::c_uint;
diff --git a/common/io_uring/src/lib.rs b/common/io_uring/src/lib.rs
new file mode 100644
index 000000000..071c50354
--- /dev/null
+++ b/common/io_uring/src/lib.rs
@@ -0,0 +1,9 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod bindings;
+mod syscalls;
+mod uring;
+
+pub use uring::*;
diff --git a/common/io_uring/src/syscalls.rs b/common/io_uring/src/syscalls.rs
new file mode 100644
index 000000000..f3a3b41a0
--- /dev/null
+++ b/common/io_uring/src/syscalls.rs
@@ -0,0 +1,41 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io::Error;
+use std::os::unix::io::RawFd;
+use std::ptr::null_mut;
+
+use libc::{c_int, c_long, c_void, syscall, SYS_io_uring_enter, SYS_io_uring_setup};
+
+use crate::bindings::*;
+
+/// Returns the system error as the result;
+pub type Result<T> = std::result::Result<T, c_int>;
+
+pub unsafe fn io_uring_setup(num_entries: usize, params: &io_uring_params) -> Result<RawFd> {
+ let ret = syscall(
+ SYS_io_uring_setup as c_long,
+ num_entries as c_int,
+ params as *const _,
+ );
+ if ret < 0 {
+ return Err(Error::last_os_error().raw_os_error().unwrap());
+ }
+ Ok(ret as RawFd)
+}
+
+pub unsafe fn io_uring_enter(fd: RawFd, to_submit: u64, to_wait: u64, flags: u32) -> Result<()> {
+ let ret = syscall(
+ SYS_io_uring_enter as c_long,
+ fd,
+ to_submit as c_int,
+ to_wait as c_int,
+ flags as c_int,
+ null_mut::<*mut c_void>(),
+ );
+ if ret < 0 {
+ return Err(Error::last_os_error().raw_os_error().unwrap());
+ }
+ Ok(())
+}
diff --git a/common/io_uring/src/uring.rs b/common/io_uring/src/uring.rs
new file mode 100644
index 000000000..4d17b3b92
--- /dev/null
+++ b/common/io_uring/src/uring.rs
@@ -0,0 +1,1564 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file makes several casts from u8 pointers into more-aligned pointer types.
+// We assume that the kernel will give us suitably aligned memory.
+#![allow(clippy::cast_ptr_alignment)]
+
+use std::collections::BTreeMap;
+use std::fs::File;
+use std::io;
+use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+use std::pin::Pin;
+use std::sync::atomic::{AtomicPtr, AtomicU32, AtomicU64, AtomicUsize, Ordering};
+
+use data_model::IoBufMut;
+use remain::sorted;
+use sync::Mutex;
+use sys_util::{MappedRegion, MemoryMapping, Protection, WatchingEvents};
+use thiserror::Error as ThisError;
+
+use crate::bindings::*;
+use crate::syscalls::*;
+
+/// Holds per-operation, user specified data. The usage is up to the caller. The most common use is
+/// for callers to identify each request.
+pub type UserData = u64;
+
+#[sorted]
+#[derive(Debug, ThisError)]
+pub enum Error {
+ /// Failed to map the completion ring.
+ #[error("Failed to mmap completion ring {0}")]
+ MappingCompleteRing(sys_util::MmapError),
+ /// Failed to map submit entries.
+ #[error("Failed to mmap submit entries {0}")]
+ MappingSubmitEntries(sys_util::MmapError),
+ /// Failed to map the submit ring.
+ #[error("Failed to mmap submit ring {0}")]
+ MappingSubmitRing(sys_util::MmapError),
+ /// Too many ops are already queued.
+ #[error("No space for more ring entries, try increasing the size passed to `new`")]
+ NoSpace,
+ /// The call to `io_uring_enter` failed with the given errno.
+ #[error("Failed to enter io uring: {0}")]
+ RingEnter(libc::c_int),
+ /// The call to `io_uring_setup` failed with the given errno.
+ #[error("Failed to setup io uring {0}")]
+ Setup(libc::c_int),
+}
+pub type Result<T> = std::result::Result<T, Error>;
+
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ use Error::*;
+ match e {
+ RingEnter(errno) => io::Error::from_raw_os_error(errno),
+ Setup(errno) => io::Error::from_raw_os_error(errno),
+ e => io::Error::new(io::ErrorKind::Other, e),
+ }
+ }
+}
+
+/// Basic statistics about the operations that have been submitted to the uring.
+#[derive(Default)]
+pub struct URingStats {
+ total_enter_calls: AtomicU64, // Number of times the uring has been entered.
+ total_ops: AtomicU64, // Total ops submitted to io_uring.
+ total_complete: AtomicU64, // Total ops completed by io_uring.
+}
+
+struct SubmitQueue {
+ submit_ring: SubmitQueueState,
+ submit_queue_entries: SubmitQueueEntries,
+ io_vecs: Pin<Box<[IoBufMut<'static>]>>,
+ submitting: usize, // The number of ops in the process of being submitted.
+ added: usize, // The number of ops added since the last call to `io_uring_enter`.
+ num_sqes: usize, // The total number of sqes allocated in shared memory.
+}
+
+// Helper functions to set io_uring_sqe bindgen union members in a less verbose manner.
+impl io_uring_sqe {
+ pub fn set_addr(&mut self, val: u64) {
+ self.__bindgen_anon_2.addr = val;
+ }
+ pub fn set_off(&mut self, val: u64) {
+ self.__bindgen_anon_1.off = val;
+ }
+
+ pub fn set_buf_index(&mut self, val: u16) {
+ self.__bindgen_anon_4
+ .__bindgen_anon_1
+ .__bindgen_anon_1
+ .buf_index = val;
+ }
+
+ pub fn set_rw_flags(&mut self, val: libc::c_int) {
+ self.__bindgen_anon_3.rw_flags = val;
+ }
+
+ pub fn set_poll_events(&mut self, val: u16) {
+ self.__bindgen_anon_3.poll_events = val;
+ }
+}
+
+impl SubmitQueue {
+ // Call `f` with the next available sqe or return an error if none are available.
+ // After `f` returns, the sqe is appended to the kernel's queue.
+ fn prep_next_sqe<F>(&mut self, mut f: F) -> Result<()>
+ where
+ F: FnMut(&mut io_uring_sqe, &mut libc::iovec),
+ {
+ if self.added == self.num_sqes {
+ return Err(Error::NoSpace);
+ }
+
+ // Find the next free submission entry in the submit ring and fill it with an iovec.
+ // The below raw pointer derefs are safe because the memory the pointers use lives as long
+ // as the mmap in self.
+ let tail = self.submit_ring.pointers.tail(Ordering::Relaxed);
+ let next_tail = tail.wrapping_add(1);
+ if next_tail == self.submit_ring.pointers.head(Ordering::Acquire) {
+ return Err(Error::NoSpace);
+ }
+ // `tail` is the next sqe to use.
+ let index = (tail & self.submit_ring.ring_mask) as usize;
+ let sqe = self.submit_queue_entries.get_mut(index).unwrap();
+
+ f(sqe, self.io_vecs[index].as_mut());
+
+ // Tells the kernel to use the new index when processing the entry at that index.
+ self.submit_ring.set_array_entry(index, index as u32);
+ // Ensure the above writes to sqe are seen before the tail is updated.
+ // set_tail uses Release ordering when storing to the ring.
+ self.submit_ring.pointers.set_tail(next_tail);
+
+ self.added += 1;
+
+ Ok(())
+ }
+
+ // Returns the number of entries that have been added to this SubmitQueue since the last time
+ // `prepare_submit` was called.
+ fn prepare_submit(&mut self) -> usize {
+ let out = self.added - self.submitting;
+ self.submitting = self.added;
+
+ out
+ }
+
+ // Indicates that we failed to submit `count` entries to the kernel and that they should be
+ // retried.
+ fn fail_submit(&mut self, count: usize) {
+ debug_assert!(count <= self.submitting);
+ self.submitting -= count;
+ }
+
+ // Indicates that `count` entries have been submitted to the kernel and so the space may be
+ // reused for new entries.
+ fn complete_submit(&mut self, count: usize) {
+ debug_assert!(count <= self.submitting);
+ self.submitting -= count;
+ self.added -= count;
+ }
+
+ unsafe fn add_rw_op(
+ &mut self,
+ ptr: *const u8,
+ len: usize,
+ fd: RawFd,
+ offset: u64,
+ user_data: UserData,
+ op: u8,
+ ) -> Result<()> {
+ self.prep_next_sqe(|sqe, iovec| {
+ iovec.iov_base = ptr as *const libc::c_void as *mut _;
+ iovec.iov_len = len;
+ sqe.opcode = op;
+ sqe.set_addr(iovec as *const _ as *const libc::c_void as u64);
+ sqe.len = 1;
+ sqe.set_off(offset);
+ sqe.set_buf_index(0);
+ sqe.ioprio = 0;
+ sqe.user_data = user_data;
+ sqe.flags = 0;
+ sqe.fd = fd;
+ })?;
+
+ Ok(())
+ }
+}
+
+/// Unsafe wrapper for the kernel's io_uring interface. Allows for queueing multiple I/O operations
+/// to the kernel and asynchronously handling the completion of these operations.
+/// Use the various `add_*` functions to configure operations, then call `wait` to start
+/// the operations and get any completed results. Each op is given a u64 user_data argument that is
+/// used to identify the result when returned in the iterator provided by `wait`.
+///
+/// # Example polling an FD for readable status.
+///
+/// ```
+/// # use std::fs::File;
+/// # use std::os::unix::io::AsRawFd;
+/// # use std::path::Path;
+/// # use sys_util::WatchingEvents;
+/// # use io_uring::URingContext;
+/// let f = File::open(Path::new("/dev/zero")).unwrap();
+/// let uring = URingContext::new(16).unwrap();
+/// uring
+/// .add_poll_fd(f.as_raw_fd(), &WatchingEvents::empty().set_read(), 454)
+/// .unwrap();
+/// let (user_data, res) = uring.wait().unwrap().next().unwrap();
+/// assert_eq!(user_data, 454 as io_uring::UserData);
+/// assert_eq!(res.unwrap(), 1 as u32);
+///
+/// ```
+pub struct URingContext {
+ ring_file: File, // Holds the io_uring context FD returned from io_uring_setup.
+ submit_ring: Mutex<SubmitQueue>,
+ complete_ring: CompleteQueueState,
+ in_flight: AtomicUsize, // The number of pending operations.
+ stats: URingStats,
+}
+
+impl URingContext {
+ /// Creates a `URingContext` where the underlying uring has a space for `num_entries`
+ /// simultaneous operations.
+ pub fn new(num_entries: usize) -> Result<URingContext> {
+ let ring_params = io_uring_params::default();
+ // The below unsafe block isolates the creation of the URingContext. Each step on it's own
+ // is unsafe. Using the uring FD for the mapping and the offsets returned by the kernel for
+ // base addresses maintains safety guarantees assuming the kernel API guarantees are
+ // trusted.
+ unsafe {
+ // Safe because the kernel is trusted to only modify params and `File` is created with
+ // an FD that it takes complete ownership of.
+ let fd = io_uring_setup(num_entries, &ring_params).map_err(Error::Setup)?;
+ let ring_file = File::from_raw_fd(fd);
+
+ // Mmap the submit and completion queues.
+ // Safe because we trust the kernel to set valid sizes in `io_uring_setup` and any error
+ // is checked.
+ let submit_ring = SubmitQueueState::new(
+ MemoryMapping::from_fd_offset_protection_populate(
+ &ring_file,
+ ring_params.sq_off.array as usize
+ + ring_params.sq_entries as usize * std::mem::size_of::<u32>(),
+ u64::from(IORING_OFF_SQ_RING),
+ Protection::read_write(),
+ true,
+ )
+ .map_err(Error::MappingSubmitRing)?,
+ &ring_params,
+ );
+
+ let num_sqe = ring_params.sq_entries as usize;
+ let submit_queue_entries = SubmitQueueEntries {
+ mmap: MemoryMapping::from_fd_offset_protection_populate(
+ &ring_file,
+ ring_params.sq_entries as usize * std::mem::size_of::<io_uring_sqe>(),
+ u64::from(IORING_OFF_SQES),
+ Protection::read_write(),
+ true,
+ )
+ .map_err(Error::MappingSubmitEntries)?,
+ len: num_sqe,
+ };
+
+ let complete_ring = CompleteQueueState::new(
+ MemoryMapping::from_fd_offset_protection_populate(
+ &ring_file,
+ ring_params.cq_off.cqes as usize
+ + ring_params.cq_entries as usize * std::mem::size_of::<io_uring_cqe>(),
+ u64::from(IORING_OFF_CQ_RING),
+ Protection::read_write(),
+ true,
+ )
+ .map_err(Error::MappingCompleteRing)?,
+ &ring_params,
+ );
+
+ Ok(URingContext {
+ ring_file,
+ submit_ring: Mutex::new(SubmitQueue {
+ submit_ring,
+ submit_queue_entries,
+ io_vecs: Pin::from(vec![IoBufMut::new(&mut []); num_sqe].into_boxed_slice()),
+ submitting: 0,
+ added: 0,
+ num_sqes: ring_params.sq_entries as usize,
+ }),
+ complete_ring,
+ in_flight: AtomicUsize::new(0),
+ stats: Default::default(),
+ })
+ }
+ }
+
+ /// Asynchronously writes to `fd` from the address given in `ptr`.
+ /// # Safety
+ /// `add_write` will write up to `len` bytes of data from the address given by `ptr`. This is
+ /// only safe if the caller guarantees that the memory lives until the transaction is complete
+ /// and that completion has been returned from the `wait` function. In addition there must not
+ /// be other references to the data pointed to by `ptr` until the operation completes. Ensure
+ /// that the fd remains open until the op completes as well.
+ pub unsafe fn add_write(
+ &self,
+ ptr: *const u8,
+ len: usize,
+ fd: RawFd,
+ offset: u64,
+ user_data: UserData,
+ ) -> Result<()> {
+ self.submit_ring
+ .lock()
+ .add_rw_op(ptr, len, fd, offset, user_data, IORING_OP_WRITEV as u8)
+ }
+
+ /// Asynchronously reads from `fd` to the address given in `ptr`.
+ /// # Safety
+ /// `add_read` will write up to `len` bytes of data to the address given by `ptr`. This is only
+ /// safe if the caller guarantees there are no other references to that memory and that the
+ /// memory lives until the transaction is complete and that completion has been returned from
+ /// the `wait` function. In addition there must not be any mutable references to the data
+ /// pointed to by `ptr` until the operation completes. Ensure that the fd remains open until
+ /// the op completes as well.
+ pub unsafe fn add_read(
+ &self,
+ ptr: *mut u8,
+ len: usize,
+ fd: RawFd,
+ offset: u64,
+ user_data: UserData,
+ ) -> Result<()> {
+ self.submit_ring
+ .lock()
+ .add_rw_op(ptr, len, fd, offset, user_data, IORING_OP_READV as u8)
+ }
+
+ /// # Safety
+ /// See 'writev' but accepts an iterator instead of a vector if there isn't already a vector in
+ /// existence.
+ pub unsafe fn add_writev_iter<I>(
+ &self,
+ iovecs: I,
+ fd: RawFd,
+ offset: u64,
+ user_data: UserData,
+ ) -> Result<()>
+ where
+ I: Iterator<Item = libc::iovec>,
+ {
+ self.add_writev(
+ Pin::from(
+ // Safe because the caller is required to guarantee that the memory pointed to by
+ // `iovecs` lives until the transaction is complete and the completion has been
+ // returned from `wait()`.
+ iovecs
+ .map(|iov| IoBufMut::from_raw_parts(iov.iov_base as *mut u8, iov.iov_len))
+ .collect::<Vec<_>>()
+ .into_boxed_slice(),
+ ),
+ fd,
+ offset,
+ user_data,
+ )
+ }
+
+ /// Asynchronously writes to `fd` from the addresses given in `iovecs`.
+ /// # Safety
+ /// `add_writev` will write to the address given by `iovecs`. This is only safe if the caller
+ /// guarantees there are no other references to that memory and that the memory lives until the
+ /// transaction is complete and that completion has been returned from the `wait` function. In
+ /// addition there must not be any mutable references to the data pointed to by `iovecs` until
+ /// the operation completes. Ensure that the fd remains open until the op completes as well.
+ /// The iovecs reference must be kept alive until the op returns.
+ pub unsafe fn add_writev(
+ &self,
+ iovecs: Pin<Box<[IoBufMut<'static>]>>,
+ fd: RawFd,
+ offset: u64,
+ user_data: UserData,
+ ) -> Result<()> {
+ self.submit_ring.lock().prep_next_sqe(|sqe, _iovec| {
+ sqe.opcode = IORING_OP_WRITEV as u8;
+ sqe.set_addr(iovecs.as_ptr() as *const _ as *const libc::c_void as u64);
+ sqe.len = iovecs.len() as u32;
+ sqe.set_off(offset);
+ sqe.set_buf_index(0);
+ sqe.ioprio = 0;
+ sqe.user_data = user_data;
+ sqe.flags = 0;
+ sqe.fd = fd;
+ })?;
+ self.complete_ring.add_op_data(user_data, iovecs);
+ Ok(())
+ }
+
+ /// # Safety
+ /// See 'readv' but accepts an iterator instead of a vector if there isn't already a vector in
+ /// existence.
+ pub unsafe fn add_readv_iter<I>(
+ &self,
+ iovecs: I,
+ fd: RawFd,
+ offset: u64,
+ user_data: UserData,
+ ) -> Result<()>
+ where
+ I: Iterator<Item = libc::iovec>,
+ {
+ self.add_readv(
+ Pin::from(
+ // Safe because the caller is required to guarantee that the memory pointed to by
+ // `iovecs` lives until the transaction is complete and the completion has been
+ // returned from `wait()`.
+ iovecs
+ .map(|iov| IoBufMut::from_raw_parts(iov.iov_base as *mut u8, iov.iov_len))
+ .collect::<Vec<_>>()
+ .into_boxed_slice(),
+ ),
+ fd,
+ offset,
+ user_data,
+ )
+ }
+
+ /// Asynchronously reads from `fd` to the addresses given in `iovecs`.
+ /// # Safety
+ /// `add_readv` will write to the address given by `iovecs`. This is only safe if the caller
+ /// guarantees there are no other references to that memory and that the memory lives until the
+ /// transaction is complete and that completion has been returned from the `wait` function. In
+ /// addition there must not be any references to the data pointed to by `iovecs` until the
+ /// operation completes. Ensure that the fd remains open until the op completes as well.
+ /// The iovecs reference must be kept alive until the op returns.
+ pub unsafe fn add_readv(
+ &self,
+ iovecs: Pin<Box<[IoBufMut<'static>]>>,
+ fd: RawFd,
+ offset: u64,
+ user_data: UserData,
+ ) -> Result<()> {
+ self.submit_ring.lock().prep_next_sqe(|sqe, _iovec| {
+ sqe.opcode = IORING_OP_READV as u8;
+ sqe.set_addr(iovecs.as_ptr() as *const _ as *const libc::c_void as u64);
+ sqe.len = iovecs.len() as u32;
+ sqe.set_off(offset);
+ sqe.set_buf_index(0);
+ sqe.ioprio = 0;
+ sqe.user_data = user_data;
+ sqe.flags = 0;
+ sqe.fd = fd;
+ })?;
+ self.complete_ring.add_op_data(user_data, iovecs);
+ Ok(())
+ }
+
+ /// Add a no-op operation that doesn't perform any IO. Useful for testing the performance of the
+ /// io_uring itself and for waking up a thread that's blocked inside a wait() call.
+ pub fn add_nop(&self, user_data: UserData) -> Result<()> {
+ self.submit_ring.lock().prep_next_sqe(|sqe, _iovec| {
+ sqe.opcode = IORING_OP_NOP as u8;
+ sqe.fd = -1;
+ sqe.user_data = user_data;
+
+ sqe.set_addr(0);
+ sqe.len = 0;
+ sqe.set_off(0);
+ sqe.set_buf_index(0);
+ sqe.set_rw_flags(0);
+ sqe.ioprio = 0;
+ sqe.flags = 0;
+ })
+ }
+
+ /// Syncs all completed operations, the ordering with in-flight async ops is not
+ /// defined.
+ pub fn add_fsync(&self, fd: RawFd, user_data: UserData) -> Result<()> {
+ self.submit_ring.lock().prep_next_sqe(|sqe, _iovec| {
+ sqe.opcode = IORING_OP_FSYNC as u8;
+ sqe.fd = fd;
+ sqe.user_data = user_data;
+
+ sqe.set_addr(0);
+ sqe.len = 0;
+ sqe.set_off(0);
+ sqe.set_buf_index(0);
+ sqe.set_rw_flags(0);
+ sqe.ioprio = 0;
+ sqe.flags = 0;
+ })
+ }
+
+ /// See the usage of `fallocate`, this asynchronously performs the same operations.
+ pub fn add_fallocate(
+ &self,
+ fd: RawFd,
+ offset: u64,
+ len: u64,
+ mode: u32,
+ user_data: UserData,
+ ) -> Result<()> {
+ // Note that len for fallocate in passed in the addr field of the sqe and the mode uses the
+ // len field.
+ self.submit_ring.lock().prep_next_sqe(|sqe, _iovec| {
+ sqe.opcode = IORING_OP_FALLOCATE as u8;
+
+ sqe.fd = fd;
+ sqe.set_addr(len);
+ sqe.len = mode;
+ sqe.set_off(offset);
+ sqe.user_data = user_data;
+
+ sqe.set_buf_index(0);
+ sqe.set_rw_flags(0);
+ sqe.ioprio = 0;
+ sqe.flags = 0;
+ })
+ }
+
+ /// Adds an FD to be polled based on the given flags.
+ /// The user must keep the FD open until the operation completion is returned from
+ /// `wait`.
+ /// Note that io_uring is always a one shot poll. After the fd is returned, it must be re-added
+ /// to get future events.
+ pub fn add_poll_fd(
+ &self,
+ fd: RawFd,
+ events: &WatchingEvents,
+ user_data: UserData,
+ ) -> Result<()> {
+ self.submit_ring.lock().prep_next_sqe(|sqe, _iovec| {
+ sqe.opcode = IORING_OP_POLL_ADD as u8;
+ sqe.fd = fd;
+ sqe.user_data = user_data;
+ sqe.set_poll_events(events.get_raw() as u16);
+
+ sqe.set_addr(0);
+ sqe.len = 0;
+ sqe.set_off(0);
+ sqe.set_buf_index(0);
+ sqe.ioprio = 0;
+ sqe.flags = 0;
+ })
+ }
+
+ /// Removes an FD that was previously added with `add_poll_fd`.
+ pub fn remove_poll_fd(
+ &self,
+ fd: RawFd,
+ events: &WatchingEvents,
+ user_data: UserData,
+ ) -> Result<()> {
+ self.submit_ring.lock().prep_next_sqe(|sqe, _iovec| {
+ sqe.opcode = IORING_OP_POLL_REMOVE as u8;
+ sqe.fd = fd;
+ sqe.user_data = user_data;
+ sqe.set_poll_events(events.get_raw() as u16);
+
+ sqe.set_addr(0);
+ sqe.len = 0;
+ sqe.set_off(0);
+ sqe.set_buf_index(0);
+ sqe.ioprio = 0;
+ sqe.flags = 0;
+ })
+ }
+
+ // Calls io_uring_enter, submitting any new sqes that have been added to the submit queue and
+ // waiting for `wait_nr` operations to complete.
+ fn enter(&self, wait_nr: u64) -> Result<()> {
+ let completed = self.complete_ring.num_completed();
+ self.stats
+ .total_complete
+ .fetch_add(completed as u64, Ordering::Relaxed);
+ self.in_flight.fetch_sub(completed, Ordering::Relaxed);
+
+ let added = self.submit_ring.lock().prepare_submit();
+ if added == 0 && wait_nr == 0 {
+ return Ok(());
+ }
+
+ self.stats.total_enter_calls.fetch_add(1, Ordering::Relaxed);
+ let flags = if wait_nr > 0 {
+ IORING_ENTER_GETEVENTS
+ } else {
+ 0
+ };
+ let res = unsafe {
+ // Safe because the only memory modified is in the completion queue.
+ io_uring_enter(self.ring_file.as_raw_fd(), added as u64, wait_nr, flags)
+ };
+
+ match res {
+ Ok(_) => {
+ self.submit_ring.lock().complete_submit(added);
+ self.stats
+ .total_ops
+ .fetch_add(added as u64, Ordering::Relaxed);
+
+ // Release store synchronizes with acquire load above.
+ self.in_flight.fetch_add(added, Ordering::Release);
+ }
+ Err(e) => {
+ self.submit_ring.lock().fail_submit(added);
+
+ if wait_nr == 0 || e != libc::EBUSY {
+ return Err(Error::RingEnter(e));
+ }
+
+ // An ebusy return means that some completed events must be processed before
+ // submitting more, wait for some to finish without pushing the new sqes in
+ // that case.
+ unsafe {
+ io_uring_enter(self.ring_file.as_raw_fd(), 0, wait_nr, flags)
+ .map_err(Error::RingEnter)?;
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Sends operations added with the `add_*` functions to the kernel.
+ pub fn submit(&self) -> Result<()> {
+ self.enter(0)
+ }
+
+ /// Sends operations added with the `add_*` functions to the kernel and return an iterator to any
+ /// completed operations. `wait` blocks until at least one completion is ready. If called
+ /// without any new events added, this simply waits for any existing events to complete and
+ /// returns as soon an one or more is ready.
+ pub fn wait(&self) -> Result<impl Iterator<Item = (UserData, std::io::Result<u32>)> + '_> {
+ // We only want to wait for events if there aren't already events in the completion queue.
+ let wait_nr = if self.complete_ring.num_ready() > 0 {
+ 0
+ } else {
+ 1
+ };
+
+ // The CompletionQueue will iterate all completed ops.
+ match self.enter(wait_nr) {
+ Ok(()) => Ok(&self.complete_ring),
+ // If we cannot submit any more entries then we need to pull stuff out of the completion
+ // ring, so just return the completion ring. This can only happen when `wait_nr` is 0 so
+ // we know there are already entries in the completion queue.
+ Err(Error::RingEnter(libc::EBUSY)) => Ok(&self.complete_ring),
+ Err(e) => Err(e),
+ }
+ }
+}
+
+impl AsRawFd for URingContext {
+ fn as_raw_fd(&self) -> RawFd {
+ self.ring_file.as_raw_fd()
+ }
+}
+
+struct SubmitQueueEntries {
+ mmap: MemoryMapping,
+ len: usize,
+}
+
+impl SubmitQueueEntries {
+ fn get_mut(&mut self, index: usize) -> Option<&mut io_uring_sqe> {
+ if index >= self.len {
+ return None;
+ }
+ let mut_ref = unsafe {
+ // Safe because the mut borrow of self resticts to one mutable reference at a time and
+ // we trust that the kernel has returned enough memory in io_uring_setup and mmap.
+ &mut *(self.mmap.as_ptr() as *mut io_uring_sqe).add(index)
+ };
+ // Clear any state.
+ *mut_ref = io_uring_sqe::default();
+ Some(mut_ref)
+ }
+}
+
+struct SubmitQueueState {
+ _mmap: MemoryMapping,
+ pointers: QueuePointers,
+ ring_mask: u32,
+ array: AtomicPtr<u32>,
+}
+
+impl SubmitQueueState {
+ // # Safety
+ // Safe iff `mmap` is created by mapping from a uring FD at the SQ_RING offset and params is
+ // the params struct passed to io_uring_setup.
+ unsafe fn new(mmap: MemoryMapping, params: &io_uring_params) -> SubmitQueueState {
+ let ptr = mmap.as_ptr();
+ // Transmutes are safe because a u32 is atomic on all supported architectures and the
+ // pointer will live until after self is dropped because the mmap is owned.
+ let head = ptr.add(params.sq_off.head as usize) as *const AtomicU32;
+ let tail = ptr.add(params.sq_off.tail as usize) as *const AtomicU32;
+ // This offset is guaranteed to be within the mmap so unwrap the result.
+ let ring_mask = mmap.read_obj(params.sq_off.ring_mask as usize).unwrap();
+ let array = AtomicPtr::new(ptr.add(params.sq_off.array as usize) as *mut u32);
+ SubmitQueueState {
+ _mmap: mmap,
+ pointers: QueuePointers { head, tail },
+ ring_mask,
+ array,
+ }
+ }
+
+ // Sets the kernel's array entry at the given `index` to `value`.
+ fn set_array_entry(&self, index: usize, value: u32) {
+ // Safe because self being constructed from the correct mmap guaratees that the memory is
+ // valid to written.
+ unsafe {
+ std::ptr::write_volatile(self.array.load(Ordering::Relaxed).add(index), value as u32);
+ }
+ }
+}
+
+#[derive(Default)]
+struct CompleteQueueData {
+ completed: usize,
+ //For ops that pass in arrays of iovecs, they need to be valid for the duration of the
+ //operation because the kernel might read them at any time.
+ pending_op_addrs: BTreeMap<UserData, Pin<Box<[IoBufMut<'static>]>>>,
+}
+
+struct CompleteQueueState {
+ mmap: MemoryMapping,
+ pointers: QueuePointers,
+ ring_mask: u32,
+ cqes_offset: u32,
+ data: Mutex<CompleteQueueData>,
+}
+
+impl CompleteQueueState {
+ /// # Safety
+ /// Safe iff `mmap` is created by mapping from a uring FD at the CQ_RING offset and params is
+ /// the params struct passed to io_uring_setup.
+ unsafe fn new(mmap: MemoryMapping, params: &io_uring_params) -> CompleteQueueState {
+ let ptr = mmap.as_ptr();
+ let head = ptr.add(params.cq_off.head as usize) as *const AtomicU32;
+ let tail = ptr.add(params.cq_off.tail as usize) as *const AtomicU32;
+ let ring_mask = mmap.read_obj(params.cq_off.ring_mask as usize).unwrap();
+ CompleteQueueState {
+ mmap,
+ pointers: QueuePointers { head, tail },
+ ring_mask,
+ cqes_offset: params.cq_off.cqes,
+ data: Default::default(),
+ }
+ }
+
+ fn add_op_data(&self, user_data: UserData, addrs: Pin<Box<[IoBufMut<'static>]>>) {
+ self.data.lock().pending_op_addrs.insert(user_data, addrs);
+ }
+
+ fn get_cqe(&self, head: u32) -> &io_uring_cqe {
+ unsafe {
+ // Safe because we trust that the kernel has returned enough memory in io_uring_setup
+ // and mmap and index is checked within range by the ring_mask.
+ let cqes = (self.mmap.as_ptr() as *const u8).add(self.cqes_offset as usize)
+ as *const io_uring_cqe;
+
+ let index = head & self.ring_mask;
+
+ &*cqes.add(index as usize)
+ }
+ }
+
+ fn num_ready(&self) -> u32 {
+ let tail = self.pointers.tail(Ordering::Acquire);
+ let head = self.pointers.head(Ordering::Relaxed);
+
+ tail.saturating_sub(head)
+ }
+
+ fn num_completed(&self) -> usize {
+ let mut data = self.data.lock();
+ ::std::mem::replace(&mut data.completed, 0)
+ }
+
+ fn pop_front(&self) -> Option<(UserData, std::io::Result<u32>)> {
+ // Take the lock on self.data first so that 2 threads don't try to pop the same completed op
+ // from the queue.
+ let mut data = self.data.lock();
+
+ // Safe because the pointers to the atomics are valid and the cqe must be in range
+ // because the kernel provided mask is applied to the index.
+ let head = self.pointers.head(Ordering::Relaxed);
+
+ // Synchronize the read of tail after the read of head.
+ if head == self.pointers.tail(Ordering::Acquire) {
+ return None;
+ }
+
+ data.completed += 1;
+
+ let cqe = self.get_cqe(head);
+ let user_data = cqe.user_data;
+ let res = cqe.res;
+
+ // free the addrs saved for this op.
+ let _ = data.pending_op_addrs.remove(&user_data);
+
+ // Store the new head and ensure the reads above complete before the kernel sees the
+ // update to head, `set_head` uses `Release` ordering
+ let new_head = head.wrapping_add(1);
+ self.pointers.set_head(new_head);
+
+ let io_res = match res {
+ r if r < 0 => Err(std::io::Error::from_raw_os_error(-r)),
+ r => Ok(r as u32),
+ };
+ Some((user_data, io_res))
+ }
+}
+
+// Return the completed ops with their result.
+impl<'c> Iterator for &'c CompleteQueueState {
+ type Item = (UserData, std::io::Result<u32>);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.pop_front()
+ }
+}
+
+struct QueuePointers {
+ head: *const AtomicU32,
+ tail: *const AtomicU32,
+}
+
+// Rust pointers don't implement Send or Sync but in this case both fields are atomics and so it's
+// safe to send the pointers between threads or access them concurrently from multiple threads.
+unsafe impl Send for QueuePointers {}
+unsafe impl Sync for QueuePointers {}
+
+impl QueuePointers {
+ // Loads the tail pointer atomically with the given ordering.
+ fn tail(&self, ordering: Ordering) -> u32 {
+ // Safe because self being constructed from the correct mmap guaratees that the memory is
+ // valid to read.
+ unsafe { (*self.tail).load(ordering) }
+ }
+
+ // Stores the new value of the tail in the submit queue. This allows the kernel to start
+ // processing entries that have been added up until the given tail pointer.
+ // Always stores with release ordering as that is the only valid way to use the pointer.
+ fn set_tail(&self, next_tail: u32) {
+ // Safe because self being constructed from the correct mmap guaratees that the memory is
+ // valid to read and it's used as an atomic to cover mutability concerns.
+ unsafe { (*self.tail).store(next_tail, Ordering::Release) }
+ }
+
+ // Loads the head pointer atomically with the given ordering.
+ fn head(&self, ordering: Ordering) -> u32 {
+ // Safe because self being constructed from the correct mmap guaratees that the memory is
+ // valid to read.
+ unsafe { (*self.head).load(ordering) }
+ }
+
+ // Stores the new value of the head in the submit queue. This allows the kernel to start
+ // processing entries that have been added up until the given head pointer.
+ // Always stores with release ordering as that is the only valid way to use the pointer.
+ fn set_head(&self, next_head: u32) {
+ // Safe because self being constructed from the correct mmap guaratees that the memory is
+ // valid to read and it's used as an atomic to cover mutability concerns.
+ unsafe { (*self.head).store(next_head, Ordering::Release) }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::collections::BTreeSet;
+ use std::fs::OpenOptions;
+ use std::io::{IoSlice, IoSliceMut};
+ use std::io::{Read, Seek, SeekFrom, Write};
+ use std::mem;
+ use std::path::{Path, PathBuf};
+ use std::sync::mpsc::channel;
+ use std::sync::{Arc, Barrier};
+ use std::thread;
+ use std::time::Duration;
+
+ use sync::{Condvar, Mutex};
+ use sys_util::{pipe, PollContext};
+ use tempfile::{tempfile, TempDir};
+
+ use super::*;
+
+ fn append_file_name(path: &Path, name: &str) -> PathBuf {
+ let mut joined = path.to_path_buf();
+ joined.push(name);
+ joined
+ }
+
+ fn check_one_read(
+ uring: &URingContext,
+ buf: &mut [u8],
+ fd: RawFd,
+ offset: u64,
+ user_data: UserData,
+ ) {
+ let (user_data_ret, res) = unsafe {
+ // Safe because the `wait` call waits until the kernel is done with `buf`.
+ uring
+ .add_read(buf.as_mut_ptr(), buf.len(), fd, offset, user_data)
+ .unwrap();
+ uring.wait().unwrap().next().unwrap()
+ };
+ assert_eq!(user_data_ret, user_data);
+ assert_eq!(res.unwrap(), buf.len() as u32);
+ }
+
+ fn check_one_readv(
+ uring: &URingContext,
+ buf: &mut [u8],
+ fd: RawFd,
+ offset: u64,
+ user_data: UserData,
+ ) {
+ let io_vecs = unsafe {
+ //safe to transmut from IoSlice to iovec.
+ vec![IoSliceMut::new(buf)]
+ .into_iter()
+ .map(|slice| std::mem::transmute::<IoSliceMut, libc::iovec>(slice))
+ };
+ let (user_data_ret, res) = unsafe {
+ // Safe because the `wait` call waits until the kernel is done with `buf`.
+ uring
+ .add_readv_iter(io_vecs, fd, offset, user_data)
+ .unwrap();
+ uring.wait().unwrap().next().unwrap()
+ };
+ assert_eq!(user_data_ret, user_data);
+ assert_eq!(res.unwrap(), buf.len() as u32);
+ }
+
+ fn create_test_file(size: u64) -> std::fs::File {
+ let f = tempfile().unwrap();
+ f.set_len(size).unwrap();
+ f
+ }
+
+ #[test]
+ // Queue as many reads as possible and then collect the completions.
+ fn read_parallel() {
+ const QUEUE_SIZE: usize = 10;
+ const BUF_SIZE: usize = 0x1000;
+
+ let uring = URingContext::new(QUEUE_SIZE).unwrap();
+ let mut buf = [0u8; BUF_SIZE * QUEUE_SIZE];
+ let f = create_test_file((BUF_SIZE * QUEUE_SIZE) as u64);
+
+ // check that the whole file can be read and that the queues wrapping is handled by reading
+ // double the quue depth of buffers.
+ for i in 0..QUEUE_SIZE * 64 {
+ let index = i as u64;
+ unsafe {
+ let offset = (i % QUEUE_SIZE) * BUF_SIZE;
+ match uring.add_read(
+ buf[offset..].as_mut_ptr(),
+ BUF_SIZE,
+ f.as_raw_fd(),
+ offset as u64,
+ index,
+ ) {
+ Ok(_) => (),
+ Err(Error::NoSpace) => {
+ let _ = uring.wait().unwrap().next().unwrap();
+ }
+ Err(_) => panic!("unexpected error from uring wait"),
+ }
+ }
+ }
+ }
+
+ #[test]
+ fn read_readv() {
+ let queue_size = 128;
+
+ let uring = URingContext::new(queue_size).unwrap();
+ let mut buf = [0u8; 0x1000];
+ let f = create_test_file(0x1000 * 2);
+
+ // check that the whole file can be read and that the queues wrapping is handled by reading
+ // double the quue depth of buffers.
+ for i in 0..queue_size * 2 {
+ let index = i as u64;
+ check_one_read(&uring, &mut buf, f.as_raw_fd(), (index % 2) * 0x1000, index);
+ check_one_readv(&uring, &mut buf, f.as_raw_fd(), (index % 2) * 0x1000, index);
+ }
+ }
+
+ #[test]
+ fn readv_vec() {
+ let queue_size = 128;
+ const BUF_SIZE: usize = 0x2000;
+
+ let uring = URingContext::new(queue_size).unwrap();
+ let mut buf = [0u8; BUF_SIZE];
+ let mut buf2 = [0u8; BUF_SIZE];
+ let mut buf3 = [0u8; BUF_SIZE];
+ let io_vecs = unsafe {
+ //safe to transmut from IoSlice to iovec.
+ vec![
+ IoSliceMut::new(&mut buf),
+ IoSliceMut::new(&mut buf2),
+ IoSliceMut::new(&mut buf3),
+ ]
+ .into_iter()
+ .map(|slice| std::mem::transmute::<IoSliceMut, libc::iovec>(slice))
+ .collect::<Vec<libc::iovec>>()
+ };
+ let total_len = io_vecs.iter().fold(0, |a, iovec| a + iovec.iov_len);
+ let f = create_test_file(total_len as u64 * 2);
+ let (user_data_ret, res) = unsafe {
+ // Safe because the `wait` call waits until the kernel is done with `buf`.
+ uring
+ .add_readv_iter(io_vecs.into_iter(), f.as_raw_fd(), 0, 55)
+ .unwrap();
+ uring.wait().unwrap().next().unwrap()
+ };
+ assert_eq!(user_data_ret, 55);
+ assert_eq!(res.unwrap(), total_len as u32);
+ }
+
+ #[test]
+ fn write_one_block() {
+ let uring = URingContext::new(16).unwrap();
+ let mut buf = [0u8; 4096];
+ let mut f = create_test_file(0);
+ f.write_all(&buf).unwrap();
+ f.write_all(&buf).unwrap();
+
+ unsafe {
+ // Safe because the `wait` call waits until the kernel is done mutating `buf`.
+ uring
+ .add_write(buf.as_mut_ptr(), buf.len(), f.as_raw_fd(), 0, 55)
+ .unwrap();
+ let (user_data, res) = uring.wait().unwrap().next().unwrap();
+ assert_eq!(user_data, 55_u64);
+ assert_eq!(res.unwrap(), buf.len() as u32);
+ }
+ }
+
+ #[test]
+ fn write_one_submit_poll() {
+ let uring = URingContext::new(16).unwrap();
+ let mut buf = [0u8; 4096];
+ let mut f = create_test_file(0);
+ f.write_all(&buf).unwrap();
+ f.write_all(&buf).unwrap();
+
+ let ctx: PollContext<u64> = PollContext::build_with(&[(&uring, 1)]).unwrap();
+ {
+ // Test that the uring context isn't readable before any events are complete.
+ let events = ctx.wait_timeout(Duration::from_millis(1)).unwrap();
+ assert!(events.iter_readable().next().is_none());
+ }
+
+ unsafe {
+ // Safe because the `wait` call waits until the kernel is done mutating `buf`.
+ uring
+ .add_write(buf.as_mut_ptr(), buf.len(), f.as_raw_fd(), 0, 55)
+ .unwrap();
+ uring.submit().unwrap();
+ // Poll for completion with epoll.
+ let events = ctx.wait().unwrap();
+ let event = events.iter_readable().next().unwrap();
+ assert_eq!(event.token(), 1);
+ let (user_data, res) = uring.wait().unwrap().next().unwrap();
+ assert_eq!(user_data, 55_u64);
+ assert_eq!(res.unwrap(), buf.len() as u32);
+ }
+ }
+
+ #[test]
+ fn writev_vec() {
+ let queue_size = 128;
+ const BUF_SIZE: usize = 0x2000;
+ const OFFSET: u64 = 0x2000;
+
+ let uring = URingContext::new(queue_size).unwrap();
+ let buf = [0xaau8; BUF_SIZE];
+ let buf2 = [0xffu8; BUF_SIZE];
+ let buf3 = [0x55u8; BUF_SIZE];
+ let io_vecs = unsafe {
+ //safe to transmut from IoSlice to iovec.
+ vec![IoSlice::new(&buf), IoSlice::new(&buf2), IoSlice::new(&buf3)]
+ .into_iter()
+ .map(|slice| std::mem::transmute::<IoSlice, libc::iovec>(slice))
+ .collect::<Vec<libc::iovec>>()
+ };
+ let total_len = io_vecs.iter().fold(0, |a, iovec| a + iovec.iov_len);
+ let mut f = create_test_file(total_len as u64 * 2);
+ let (user_data_ret, res) = unsafe {
+ // Safe because the `wait` call waits until the kernel is done with `buf`.
+ uring
+ .add_writev_iter(io_vecs.into_iter(), f.as_raw_fd(), OFFSET, 55)
+ .unwrap();
+ uring.wait().unwrap().next().unwrap()
+ };
+ assert_eq!(user_data_ret, 55);
+ assert_eq!(res.unwrap(), total_len as u32);
+
+ let mut read_back = [0u8; BUF_SIZE];
+ f.seek(SeekFrom::Start(OFFSET)).unwrap();
+ f.read_exact(&mut read_back).unwrap();
+ assert!(!read_back.iter().any(|&b| b != 0xaa));
+ f.read_exact(&mut read_back).unwrap();
+ assert!(!read_back.iter().any(|&b| b != 0xff));
+ f.read_exact(&mut read_back).unwrap();
+ assert!(!read_back.iter().any(|&b| b != 0x55));
+ }
+
+ #[test]
+ fn fallocate_fsync() {
+ let tempdir = TempDir::new().unwrap();
+ let file_path = append_file_name(tempdir.path(), "test");
+
+ {
+ let buf = [0u8; 4096];
+ let mut f = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create(true)
+ .truncate(true)
+ .open(&file_path)
+ .unwrap();
+ f.write_all(&buf).unwrap();
+ }
+
+ let init_size = std::fs::metadata(&file_path).unwrap().len() as usize;
+ let set_size = init_size + 1024 * 1024 * 50;
+ let f = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create(true)
+ .open(&file_path)
+ .unwrap();
+
+ let uring = URingContext::new(16).unwrap();
+ uring
+ .add_fallocate(f.as_raw_fd(), 0, set_size as u64, 0, 66)
+ .unwrap();
+ let (user_data, res) = uring.wait().unwrap().next().unwrap();
+ assert_eq!(user_data, 66_u64);
+ match res {
+ Err(e) => {
+ if e.kind() == std::io::ErrorKind::InvalidInput {
+ // skip on kernels that don't support fallocate.
+ return;
+ }
+ panic!("Unexpected fallocate error: {}", e);
+ }
+ Ok(val) => assert_eq!(val, 0_u32),
+ }
+
+ // Add a few writes and then fsync
+ let buf = [0u8; 4096];
+ let mut pending = std::collections::BTreeSet::new();
+ unsafe {
+ uring
+ .add_write(buf.as_ptr(), buf.len(), f.as_raw_fd(), 0, 67)
+ .unwrap();
+ pending.insert(67u64);
+ uring
+ .add_write(buf.as_ptr(), buf.len(), f.as_raw_fd(), 4096, 68)
+ .unwrap();
+ pending.insert(68);
+ uring
+ .add_write(buf.as_ptr(), buf.len(), f.as_raw_fd(), 8192, 69)
+ .unwrap();
+ pending.insert(69);
+ }
+ uring.add_fsync(f.as_raw_fd(), 70).unwrap();
+ pending.insert(70);
+
+ let mut wait_calls = 0;
+
+ while !pending.is_empty() && wait_calls < 5 {
+ let events = uring.wait().unwrap();
+ for (user_data, res) in events {
+ assert!(res.is_ok());
+ assert!(pending.contains(&user_data));
+ pending.remove(&user_data);
+ }
+ wait_calls += 1;
+ }
+ assert!(pending.is_empty());
+
+ uring
+ .add_fallocate(
+ f.as_raw_fd(),
+ init_size as u64,
+ (set_size - init_size) as u64,
+ (libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE) as u32,
+ 68,
+ )
+ .unwrap();
+ let (user_data, res) = uring.wait().unwrap().next().unwrap();
+ assert_eq!(user_data, 68_u64);
+ assert_eq!(res.unwrap(), 0_u32);
+
+ drop(f); // Close to ensure directory entires for metadata are updated.
+
+ let new_size = std::fs::metadata(&file_path).unwrap().len() as usize;
+ assert_eq!(new_size, set_size);
+ }
+
+ #[test]
+ fn dev_zero_readable() {
+ let f = File::open(Path::new("/dev/zero")).unwrap();
+ let uring = URingContext::new(16).unwrap();
+ uring
+ .add_poll_fd(f.as_raw_fd(), &WatchingEvents::empty().set_read(), 454)
+ .unwrap();
+ let (user_data, res) = uring.wait().unwrap().next().unwrap();
+ assert_eq!(user_data, 454_u64);
+ assert_eq!(res.unwrap(), 1_u32);
+ }
+
+ #[test]
+ fn queue_many_ebusy_retry() {
+ let num_entries = 16;
+ let f = File::open(Path::new("/dev/zero")).unwrap();
+ let uring = URingContext::new(num_entries).unwrap();
+ // Fill the sumbit ring.
+ for sqe_batch in 0..3 {
+ for i in 0..num_entries {
+ uring
+ .add_poll_fd(
+ f.as_raw_fd(),
+ &WatchingEvents::empty().set_read(),
+ (sqe_batch * num_entries + i) as u64,
+ )
+ .unwrap();
+ }
+ uring.submit().unwrap();
+ }
+ // Adding more than the number of cqes will cause the uring to return ebusy, make sure that
+ // is handled cleanly and wait still returns the completed entries.
+ uring
+ .add_poll_fd(
+ f.as_raw_fd(),
+ &WatchingEvents::empty().set_read(),
+ (num_entries * 3) as u64,
+ )
+ .unwrap();
+ // The first wait call should return the cques that are already filled.
+ {
+ let mut results = uring.wait().unwrap();
+ for _i in 0..num_entries * 2 {
+ assert_eq!(results.next().unwrap().1.unwrap(), 1_u32);
+ }
+ assert!(results.next().is_none());
+ }
+ // The second will finish submitting any more sqes and return the rest.
+ let mut results = uring.wait().unwrap();
+ for _i in 0..num_entries + 1 {
+ assert_eq!(results.next().unwrap().1.unwrap(), 1_u32);
+ }
+ assert!(results.next().is_none());
+ }
+
+ #[test]
+ fn wake_with_nop() {
+ const PIPE_READ: UserData = 0;
+ const NOP: UserData = 1;
+ const BUF_DATA: [u8; 16] = [0xf4; 16];
+
+ let uring = URingContext::new(4).map(Arc::new).unwrap();
+ let (pipe_out, mut pipe_in) = pipe(true).unwrap();
+ let (tx, rx) = channel();
+
+ let uring2 = uring.clone();
+ let wait_thread = thread::spawn(move || {
+ let mut buf = [0u8; BUF_DATA.len()];
+ unsafe {
+ uring2
+ .add_read(buf.as_mut_ptr(), buf.len(), pipe_out.as_raw_fd(), 0, 0)
+ .unwrap();
+ }
+
+ // This is still a bit racy as the other thread may end up adding the NOP before we make
+ // the syscall but I'm not aware of a mechanism that will notify the other thread
+ // exactly when we make the syscall.
+ tx.send(()).unwrap();
+ let mut events = uring2.wait().unwrap();
+ let (user_data, result) = events.next().unwrap();
+ assert_eq!(user_data, NOP);
+ assert_eq!(result.unwrap(), 0);
+
+ tx.send(()).unwrap();
+ let mut events = uring2.wait().unwrap();
+ let (user_data, result) = events.next().unwrap();
+ assert_eq!(user_data, PIPE_READ);
+ assert_eq!(result.unwrap(), buf.len() as u32);
+ assert_eq!(&buf, &BUF_DATA);
+ });
+
+ // Wait until the other thread is about to make the syscall.
+ rx.recv_timeout(Duration::from_secs(10)).unwrap();
+
+ // Now add a NOP operation. This should wake up the other thread even though it cannot yet
+ // read from the pipe.
+ uring.add_nop(NOP).unwrap();
+ uring.submit().unwrap();
+
+ // Wait for the other thread to process the NOP result.
+ rx.recv_timeout(Duration::from_secs(10)).unwrap();
+
+ // Now write to the pipe to finish the uring read.
+ pipe_in.write_all(&BUF_DATA).unwrap();
+
+ wait_thread.join().unwrap();
+ }
+
+ #[test]
+ fn complete_from_any_thread() {
+ let num_entries = 16;
+ let uring = URingContext::new(num_entries).map(Arc::new).unwrap();
+
+ // Fill the sumbit ring.
+ for sqe_batch in 0..3 {
+ for i in 0..num_entries {
+ uring.add_nop((sqe_batch * num_entries + i) as u64).unwrap();
+ }
+ uring.submit().unwrap();
+ }
+
+ // Spawn a bunch of threads that pull cqes out of the uring and make sure none of them see a
+ // duplicate.
+ const NUM_THREADS: usize = 7;
+ let completed = Arc::new(Mutex::new(BTreeSet::new()));
+ let cv = Arc::new(Condvar::new());
+ let barrier = Arc::new(Barrier::new(NUM_THREADS));
+
+ let mut threads = Vec::with_capacity(NUM_THREADS);
+ for _ in 0..NUM_THREADS {
+ let uring = uring.clone();
+ let completed = completed.clone();
+ let barrier = barrier.clone();
+ let cv = cv.clone();
+ threads.push(thread::spawn(move || {
+ barrier.wait();
+
+ 'wait: while completed.lock().len() < num_entries * 3 {
+ for (user_data, result) in uring.wait().unwrap() {
+ assert_eq!(result.unwrap(), 0);
+
+ let mut completed = completed.lock();
+ assert!(completed.insert(user_data));
+ if completed.len() >= num_entries * 3 {
+ break 'wait;
+ }
+ }
+ }
+
+ cv.notify_one();
+ }));
+ }
+
+ // Wait until all the operations have completed.
+ let mut c = completed.lock();
+ while c.len() < num_entries * 3 {
+ c = cv.wait(c);
+ }
+ mem::drop(c);
+
+ // Let the OS clean up the still-waiting threads after the test run.
+ }
+
+ #[test]
+ fn submit_from_any_thread() {
+ const NUM_THREADS: usize = 7;
+ const ITERATIONS: usize = 113;
+ const NUM_ENTRIES: usize = 16;
+
+ fn wait_for_completion_thread(in_flight: &Mutex<isize>, cv: &Condvar) {
+ let mut in_flight = in_flight.lock();
+ while *in_flight > NUM_ENTRIES as isize {
+ in_flight = cv.wait(in_flight);
+ }
+ }
+
+ let uring = URingContext::new(NUM_ENTRIES).map(Arc::new).unwrap();
+ let in_flight = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let mut threads = Vec::with_capacity(NUM_THREADS);
+ for idx in 0..NUM_THREADS {
+ let uring = uring.clone();
+ let in_flight = in_flight.clone();
+ let cv = cv.clone();
+ threads.push(thread::spawn(move || {
+ for iter in 0..ITERATIONS {
+ loop {
+ match uring.add_nop(((idx * NUM_THREADS) + iter) as UserData) {
+ Ok(()) => *in_flight.lock() += 1,
+ Err(Error::NoSpace) => {
+ wait_for_completion_thread(&in_flight, &cv);
+ continue;
+ }
+ Err(e) => panic!("Failed to add nop: {}", e),
+ }
+
+ // We don't need to wait for the completion queue if the submit fails with
+ // EBUSY because we already added the operation to the submit queue. It will
+ // get added eventually.
+ match uring.submit() {
+ Ok(()) => break,
+ Err(Error::RingEnter(libc::EBUSY)) => break,
+ Err(e) => panic!("Failed to submit ops: {}", e),
+ }
+ }
+ }
+ }));
+ }
+
+ let mut completed = 0;
+ while completed < NUM_THREADS * ITERATIONS {
+ for (_, res) in uring.wait().unwrap() {
+ assert_eq!(res.unwrap(), 0);
+ completed += 1;
+
+ let mut in_flight = in_flight.lock();
+ *in_flight -= 1;
+ let notify_submitters = *in_flight <= NUM_ENTRIES as isize;
+ mem::drop(in_flight);
+
+ if notify_submitters {
+ cv.notify_all();
+ }
+
+ if completed >= NUM_THREADS * ITERATIONS {
+ break;
+ }
+ }
+ }
+
+ for t in threads {
+ t.join().unwrap();
+ }
+
+ // Make sure we didn't submit more entries than expected.
+ assert_eq!(*in_flight.lock(), 0);
+ assert_eq!(uring.submit_ring.lock().added, 0);
+ assert_eq!(uring.complete_ring.num_ready(), 0);
+ assert_eq!(
+ uring.stats.total_ops.load(Ordering::Relaxed),
+ (NUM_THREADS * ITERATIONS) as u64
+ );
+ }
+
+ // TODO(b/183722981): Fix and re-enable test
+ #[test]
+ #[ignore]
+ fn multi_thread_submit_and_complete() {
+ const NUM_SUBMITTERS: usize = 7;
+ const NUM_COMPLETERS: usize = 3;
+ const ITERATIONS: usize = 113;
+ const NUM_ENTRIES: usize = 16;
+
+ fn wait_for_completion_thread(in_flight: &Mutex<isize>, cv: &Condvar) {
+ let mut in_flight = in_flight.lock();
+ while *in_flight > NUM_ENTRIES as isize {
+ in_flight = cv.wait(in_flight);
+ }
+ }
+
+ let uring = URingContext::new(NUM_ENTRIES).map(Arc::new).unwrap();
+ let in_flight = Arc::new(Mutex::new(0));
+ let cv = Arc::new(Condvar::new());
+
+ let mut threads = Vec::with_capacity(NUM_SUBMITTERS + NUM_COMPLETERS);
+ for idx in 0..NUM_SUBMITTERS {
+ let uring = uring.clone();
+ let in_flight = in_flight.clone();
+ let cv = cv.clone();
+ threads.push(thread::spawn(move || {
+ for iter in 0..ITERATIONS {
+ loop {
+ match uring.add_nop(((idx * NUM_SUBMITTERS) + iter) as UserData) {
+ Ok(()) => *in_flight.lock() += 1,
+ Err(Error::NoSpace) => {
+ wait_for_completion_thread(&in_flight, &cv);
+ continue;
+ }
+ Err(e) => panic!("Failed to add nop: {}", e),
+ }
+
+ // We don't need to wait for the completion queue if the submit fails with
+ // EBUSY because we already added the operation to the submit queue. It will
+ // get added eventually.
+ match uring.submit() {
+ Ok(()) => break,
+ Err(Error::RingEnter(libc::EBUSY)) => break,
+ Err(e) => panic!("Failed to submit ops: {}", e),
+ }
+ }
+ }
+ }));
+ }
+
+ let completed = Arc::new(AtomicUsize::new(0));
+ for _ in 0..NUM_COMPLETERS {
+ let uring = uring.clone();
+ let in_flight = in_flight.clone();
+ let cv = cv.clone();
+ let completed = completed.clone();
+ threads.push(thread::spawn(move || {
+ while completed.load(Ordering::Relaxed) < NUM_SUBMITTERS * ITERATIONS {
+ for (_, res) in uring.wait().unwrap() {
+ assert_eq!(res.unwrap(), 0);
+ completed.fetch_add(1, Ordering::Relaxed);
+
+ let mut in_flight = in_flight.lock();
+ *in_flight -= 1;
+ let notify_submitters = *in_flight <= NUM_ENTRIES as isize;
+ mem::drop(in_flight);
+
+ if notify_submitters {
+ cv.notify_all();
+ }
+
+ if completed.load(Ordering::Relaxed) >= NUM_SUBMITTERS * ITERATIONS {
+ break;
+ }
+ }
+ }
+ }));
+ }
+
+ for t in threads.drain(..NUM_SUBMITTERS) {
+ t.join().unwrap();
+ }
+
+ // Now that all submitters are finished, add NOPs to wake up any completers blocked on the
+ // syscall.
+ for i in 0..NUM_COMPLETERS {
+ uring
+ .add_nop((NUM_SUBMITTERS * ITERATIONS + i) as UserData)
+ .unwrap();
+ }
+ uring.submit().unwrap();
+
+ for t in threads {
+ t.join().unwrap();
+ }
+
+ // Make sure we didn't submit more entries than expected. Only the last few NOPs added to
+ // wake up the completer threads may still be in the completion ring.
+ assert!(uring.complete_ring.num_ready() <= NUM_COMPLETERS as u32);
+ assert_eq!(
+ in_flight.lock().abs() as u32 + uring.complete_ring.num_ready(),
+ NUM_COMPLETERS as u32
+ );
+ assert_eq!(uring.submit_ring.lock().added, 0);
+ assert_eq!(
+ uring.stats.total_ops.load(Ordering::Relaxed),
+ (NUM_SUBMITTERS * ITERATIONS + NUM_COMPLETERS) as u64
+ );
+ }
+}
diff --git a/tempfile/Android.bp b/common/p9/Android.bp
index 125d00ac8..edc74280f 100644
--- a/tempfile/Android.bp
+++ b/common/p9/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace --no-subdir.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -11,42 +11,38 @@ package {
}
rust_library {
- name: "libtempfile",
+ name: "libp9",
defaults: ["crosvm_defaults"],
host_supported: true,
- crate_name: "tempfile",
+ crate_name: "p9",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"liblibc",
+ "libsys_util",
],
+ proc_macros: ["libwire_format_derive"],
}
-rust_defaults {
- name: "tempfile_defaults",
+rust_test {
+ name: "p9_test_src_lib",
defaults: ["crosvm_defaults"],
- crate_name: "tempfile",
+ host_supported: true,
+ crate_name: "p9",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "liblibc",
- ],
-}
-
-rust_test_host {
- name: "tempfile_host_test_src_lib",
- defaults: ["tempfile_defaults"],
test_options: {
unit_test: true,
},
+ edition: "2021",
+ rustlibs: [
+ "liblibc",
+ "libsys_util",
+ ],
+ proc_macros: ["libwire_format_derive"],
}
-
-rust_test {
- name: "tempfile_device_test_src_lib",
- defaults: ["tempfile_defaults"],
-}
-
-// dependent_library ["feature_list"]
-// libc-0.2.93 "default,std"
diff --git a/common/p9/Cargo.toml b/common/p9/Cargo.toml
new file mode 100644
index 000000000..45c5d0dec
--- /dev/null
+++ b/common/p9/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "p9"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2021"
+
+[dependencies]
+libc = "*"
+sys_util = { path = "../sys_util" } # provided by ebuild
+wire_format_derive = { path = "wire_format_derive", version = "*" }
+
+[features]
+trace = []
+
+[workspace]
diff --git a/common/p9/OWNERS b/common/p9/OWNERS
new file mode 100644
index 000000000..7868b293a
--- /dev/null
+++ b/common/p9/OWNERS
@@ -0,0 +1,3 @@
+# This file exists so it can be passed to fuzzer_install in dev-rust/p9.
+denniskempin@google.com
+dverkamp@chromium.org
diff --git a/common/p9/README.md b/common/p9/README.md
new file mode 100644
index 000000000..52cd4e6bd
--- /dev/null
+++ b/common/p9/README.md
@@ -0,0 +1,19 @@
+# p9 - Server implementation of the [9p] file system protocol
+
+This directory contains the protocol definition and a server implementation of the [9p] file system
+protocol.
+
+- [wire_format_derive] - A [procedural macro] that derives the serialization and de-serialization
+ implementation for a struct into the [9p] wire format.
+- [src/protocol] - Defines all the messages used in the [9p] protocol. Also implements serialization
+ and de-serialization for some base types (integers, strings, vectors) that form the foundation of
+ all [9p] messages. Wire format implementations for all other messages are derived using the
+ `wire_format_derive` macro.
+- [src/server.rs] - Implements a full [9p] server, carrying out file system requests on behalf of
+ clients.
+
+[9p]: http://man.cat-v.org/plan_9/5/intro
+[procedural macro]: https://doc.rust-lang.org/proc_macro/index.html
+[src/protocol]: src/protocol/
+[src/server.rs]: src/server.rs
+[wire_format_derive]: wire_format_derive/
diff --git a/common/p9/fuzz/Cargo.toml b/common/p9/fuzz/Cargo.toml
new file mode 100644
index 000000000..76ccc60dc
--- /dev/null
+++ b/common/p9/fuzz/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "p9-fuzz"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2021"
+
+[dependencies]
+p9 = { path = "../" }
+cros_fuzz = { path = "../../cros-fuzz" } # provided by ebuild
+
+[workspace]
+members = ["."]
+
+[[bin]]
+name = "p9_tframe_decode_fuzzer"
+path = "tframe_decode.rs"
+
+[patch.crates-io]
+wire_format_derive = { path = "../wire_format_derive" }
diff --git a/common/p9/fuzz/cargo2android.json b/common/p9/fuzz/cargo2android.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/common/p9/fuzz/cargo2android.json
@@ -0,0 +1 @@
+{} \ No newline at end of file
diff --git a/common/p9/fuzz/tframe_decode.rs b/common/p9/fuzz/tframe_decode.rs
new file mode 100644
index 000000000..6cdeae3be
--- /dev/null
+++ b/common/p9/fuzz/tframe_decode.rs
@@ -0,0 +1,12 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#![no_main]
+
+use cros_fuzz::fuzz_target;
+use p9::fuzzing::tframe_decode;
+
+fuzz_target!(|bytes: &[u8]| {
+ tframe_decode(bytes);
+});
diff --git a/common/p9/src/fuzzing.rs b/common/p9/src/fuzzing.rs
new file mode 100644
index 000000000..47aa07057
--- /dev/null
+++ b/common/p9/src/fuzzing.rs
@@ -0,0 +1,13 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io::Cursor;
+
+use crate::protocol::{Tframe, WireFormat};
+
+pub fn tframe_decode(bytes: &[u8]) {
+ let mut cursor = Cursor::new(bytes);
+
+ while Tframe::decode(&mut cursor).is_ok() {}
+}
diff --git a/common/p9/src/lib.rs b/common/p9/src/lib.rs
new file mode 100644
index 000000000..b00c706a2
--- /dev/null
+++ b/common/p9/src/lib.rs
@@ -0,0 +1,16 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+extern crate libc;
+
+#[macro_use]
+extern crate wire_format_derive;
+
+mod protocol;
+mod server;
+
+#[cfg(fuzzing)]
+pub mod fuzzing;
+
+pub use server::*;
diff --git a/common/p9/src/protocol/messages.rs b/common/p9/src/protocol/messages.rs
new file mode 100644
index 000000000..b5a03c072
--- /dev/null
+++ b/common/p9/src/protocol/messages.rs
@@ -0,0 +1,840 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io::{self, ErrorKind, Read, Write};
+use std::mem;
+use std::string::String;
+use std::vec::Vec;
+
+use crate::protocol::wire_format::{Data, WireFormat};
+
+// Message type constants. Taken from "include/net/9p/9p.h" in the linux kernel
+// tree. The protocol specifies each R* message to be the corresponding T*
+// message plus one.
+const TLERROR: u8 = 6;
+const RLERROR: u8 = TLERROR + 1;
+const TSTATFS: u8 = 8;
+const RSTATFS: u8 = TSTATFS + 1;
+const TLOPEN: u8 = 12;
+const RLOPEN: u8 = TLOPEN + 1;
+const TLCREATE: u8 = 14;
+const RLCREATE: u8 = TLCREATE + 1;
+const TSYMLINK: u8 = 16;
+const RSYMLINK: u8 = TSYMLINK + 1;
+const TMKNOD: u8 = 18;
+const RMKNOD: u8 = TMKNOD + 1;
+const TRENAME: u8 = 20;
+const RRENAME: u8 = TRENAME + 1;
+const TREADLINK: u8 = 22;
+const RREADLINK: u8 = TREADLINK + 1;
+const TGETATTR: u8 = 24;
+const RGETATTR: u8 = TGETATTR + 1;
+const TSETATTR: u8 = 26;
+const RSETATTR: u8 = TSETATTR + 1;
+const TXATTRWALK: u8 = 30;
+const RXATTRWALK: u8 = TXATTRWALK + 1;
+const TXATTRCREATE: u8 = 32;
+const RXATTRCREATE: u8 = TXATTRCREATE + 1;
+const TREADDIR: u8 = 40;
+const RREADDIR: u8 = TREADDIR + 1;
+const TFSYNC: u8 = 50;
+const RFSYNC: u8 = TFSYNC + 1;
+const TLOCK: u8 = 52;
+const RLOCK: u8 = TLOCK + 1;
+const TGETLOCK: u8 = 54;
+const RGETLOCK: u8 = TGETLOCK + 1;
+const TLINK: u8 = 70;
+const RLINK: u8 = TLINK + 1;
+const TMKDIR: u8 = 72;
+const RMKDIR: u8 = TMKDIR + 1;
+const TRENAMEAT: u8 = 74;
+const RRENAMEAT: u8 = TRENAMEAT + 1;
+const TUNLINKAT: u8 = 76;
+const RUNLINKAT: u8 = TUNLINKAT + 1;
+const TVERSION: u8 = 100;
+const RVERSION: u8 = TVERSION + 1;
+const TAUTH: u8 = 102;
+const RAUTH: u8 = TAUTH + 1;
+const TATTACH: u8 = 104;
+const RATTACH: u8 = TATTACH + 1;
+const _TERROR: u8 = 106;
+const _RERROR: u8 = _TERROR + 1;
+const TFLUSH: u8 = 108;
+const RFLUSH: u8 = TFLUSH + 1;
+const TWALK: u8 = 110;
+const RWALK: u8 = TWALK + 1;
+const _TOPEN: u8 = 112;
+const _ROPEN: u8 = _TOPEN + 1;
+const _TCREATE: u8 = 114;
+const _RCREATE: u8 = _TCREATE + 1;
+const TREAD: u8 = 116;
+const RREAD: u8 = TREAD + 1;
+const TWRITE: u8 = 118;
+const RWRITE: u8 = TWRITE + 1;
+const TCLUNK: u8 = 120;
+const RCLUNK: u8 = TCLUNK + 1;
+const TREMOVE: u8 = 122;
+const RREMOVE: u8 = TREMOVE + 1;
+const _TSTAT: u8 = 124;
+const _RSTAT: u8 = _TSTAT + 1;
+const _TWSTAT: u8 = 126;
+const _RWSTAT: u8 = _TWSTAT + 1;
+
+/// A message sent from a 9P client to a 9P server.
+#[derive(Debug)]
+pub enum Tmessage {
+ Version(Tversion),
+ Flush(Tflush),
+ Walk(Twalk),
+ Read(Tread),
+ Write(Twrite),
+ Clunk(Tclunk),
+ Remove(Tremove),
+ Attach(Tattach),
+ Auth(Tauth),
+ Statfs(Tstatfs),
+ Lopen(Tlopen),
+ Lcreate(Tlcreate),
+ Symlink(Tsymlink),
+ Mknod(Tmknod),
+ Rename(Trename),
+ Readlink(Treadlink),
+ GetAttr(Tgetattr),
+ SetAttr(Tsetattr),
+ XattrWalk(Txattrwalk),
+ XattrCreate(Txattrcreate),
+ Readdir(Treaddir),
+ Fsync(Tfsync),
+ Lock(Tlock),
+ GetLock(Tgetlock),
+ Link(Tlink),
+ Mkdir(Tmkdir),
+ RenameAt(Trenameat),
+ UnlinkAt(Tunlinkat),
+}
+
+#[derive(Debug)]
+pub struct Tframe {
+ pub tag: u16,
+ pub msg: Tmessage,
+}
+
+impl WireFormat for Tframe {
+ fn byte_size(&self) -> u32 {
+ let msg_size = match self.msg {
+ Tmessage::Version(ref version) => version.byte_size(),
+ Tmessage::Flush(ref flush) => flush.byte_size(),
+ Tmessage::Walk(ref walk) => walk.byte_size(),
+ Tmessage::Read(ref read) => read.byte_size(),
+ Tmessage::Write(ref write) => write.byte_size(),
+ Tmessage::Clunk(ref clunk) => clunk.byte_size(),
+ Tmessage::Remove(ref remove) => remove.byte_size(),
+ Tmessage::Attach(ref attach) => attach.byte_size(),
+ Tmessage::Auth(ref auth) => auth.byte_size(),
+ Tmessage::Statfs(ref statfs) => statfs.byte_size(),
+ Tmessage::Lopen(ref lopen) => lopen.byte_size(),
+ Tmessage::Lcreate(ref lcreate) => lcreate.byte_size(),
+ Tmessage::Symlink(ref symlink) => symlink.byte_size(),
+ Tmessage::Mknod(ref mknod) => mknod.byte_size(),
+ Tmessage::Rename(ref rename) => rename.byte_size(),
+ Tmessage::Readlink(ref readlink) => readlink.byte_size(),
+ Tmessage::GetAttr(ref getattr) => getattr.byte_size(),
+ Tmessage::SetAttr(ref setattr) => setattr.byte_size(),
+ Tmessage::XattrWalk(ref xattrwalk) => xattrwalk.byte_size(),
+ Tmessage::XattrCreate(ref xattrcreate) => xattrcreate.byte_size(),
+ Tmessage::Readdir(ref readdir) => readdir.byte_size(),
+ Tmessage::Fsync(ref fsync) => fsync.byte_size(),
+ Tmessage::Lock(ref lock) => lock.byte_size(),
+ Tmessage::GetLock(ref getlock) => getlock.byte_size(),
+ Tmessage::Link(ref link) => link.byte_size(),
+ Tmessage::Mkdir(ref mkdir) => mkdir.byte_size(),
+ Tmessage::RenameAt(ref renameat) => renameat.byte_size(),
+ Tmessage::UnlinkAt(ref unlinkat) => unlinkat.byte_size(),
+ };
+
+ // size + type + tag + message size
+ (mem::size_of::<u32>() + mem::size_of::<u8>() + mem::size_of::<u16>()) as u32 + msg_size
+ }
+
+ fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+ self.byte_size().encode(writer)?;
+
+ let ty = match self.msg {
+ Tmessage::Version(_) => TVERSION,
+ Tmessage::Flush(_) => TFLUSH,
+ Tmessage::Walk(_) => TWALK,
+ Tmessage::Read(_) => TREAD,
+ Tmessage::Write(_) => TWRITE,
+ Tmessage::Clunk(_) => TCLUNK,
+ Tmessage::Remove(_) => TREMOVE,
+ Tmessage::Attach(_) => TATTACH,
+ Tmessage::Auth(_) => TAUTH,
+ Tmessage::Statfs(_) => TSTATFS,
+ Tmessage::Lopen(_) => TLOPEN,
+ Tmessage::Lcreate(_) => TLCREATE,
+ Tmessage::Symlink(_) => TSYMLINK,
+ Tmessage::Mknod(_) => TMKNOD,
+ Tmessage::Rename(_) => TRENAME,
+ Tmessage::Readlink(_) => TREADLINK,
+ Tmessage::GetAttr(_) => TGETATTR,
+ Tmessage::SetAttr(_) => TSETATTR,
+ Tmessage::XattrWalk(_) => TXATTRWALK,
+ Tmessage::XattrCreate(_) => TXATTRCREATE,
+ Tmessage::Readdir(_) => TREADDIR,
+ Tmessage::Fsync(_) => TFSYNC,
+ Tmessage::Lock(_) => TLOCK,
+ Tmessage::GetLock(_) => TGETLOCK,
+ Tmessage::Link(_) => TLINK,
+ Tmessage::Mkdir(_) => TMKDIR,
+ Tmessage::RenameAt(_) => TRENAMEAT,
+ Tmessage::UnlinkAt(_) => TUNLINKAT,
+ };
+
+ ty.encode(writer)?;
+ self.tag.encode(writer)?;
+
+ match self.msg {
+ Tmessage::Version(ref version) => version.encode(writer),
+ Tmessage::Flush(ref flush) => flush.encode(writer),
+ Tmessage::Walk(ref walk) => walk.encode(writer),
+ Tmessage::Read(ref read) => read.encode(writer),
+ Tmessage::Write(ref write) => write.encode(writer),
+ Tmessage::Clunk(ref clunk) => clunk.encode(writer),
+ Tmessage::Remove(ref remove) => remove.encode(writer),
+ Tmessage::Attach(ref attach) => attach.encode(writer),
+ Tmessage::Auth(ref auth) => auth.encode(writer),
+ Tmessage::Statfs(ref statfs) => statfs.encode(writer),
+ Tmessage::Lopen(ref lopen) => lopen.encode(writer),
+ Tmessage::Lcreate(ref lcreate) => lcreate.encode(writer),
+ Tmessage::Symlink(ref symlink) => symlink.encode(writer),
+ Tmessage::Mknod(ref mknod) => mknod.encode(writer),
+ Tmessage::Rename(ref rename) => rename.encode(writer),
+ Tmessage::Readlink(ref readlink) => readlink.encode(writer),
+ Tmessage::GetAttr(ref getattr) => getattr.encode(writer),
+ Tmessage::SetAttr(ref setattr) => setattr.encode(writer),
+ Tmessage::XattrWalk(ref xattrwalk) => xattrwalk.encode(writer),
+ Tmessage::XattrCreate(ref xattrcreate) => xattrcreate.encode(writer),
+ Tmessage::Readdir(ref readdir) => readdir.encode(writer),
+ Tmessage::Fsync(ref fsync) => fsync.encode(writer),
+ Tmessage::Lock(ref lock) => lock.encode(writer),
+ Tmessage::GetLock(ref getlock) => getlock.encode(writer),
+ Tmessage::Link(ref link) => link.encode(writer),
+ Tmessage::Mkdir(ref mkdir) => mkdir.encode(writer),
+ Tmessage::RenameAt(ref renameat) => renameat.encode(writer),
+ Tmessage::UnlinkAt(ref unlinkat) => unlinkat.encode(writer),
+ }
+ }
+
+ fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+ let byte_size: u32 = WireFormat::decode(reader)?;
+
+ // byte_size includes the size of byte_size so remove that from the
+ // expected length of the message. Also make sure that byte_size is at least
+ // that long to begin with.
+ if byte_size < mem::size_of::<u32>() as u32 {
+ return Err(io::Error::new(
+ ErrorKind::InvalidData,
+ format!("byte_size(= {}) is less than 4 bytes", byte_size),
+ ));
+ }
+
+ let reader = &mut reader.take((byte_size - mem::size_of::<u32>() as u32) as u64);
+
+ let mut ty = [0u8];
+ reader.read_exact(&mut ty)?;
+
+ let tag: u16 = WireFormat::decode(reader)?;
+
+ let msg = match ty[0] {
+ TVERSION => Ok(Tmessage::Version(WireFormat::decode(reader)?)),
+ TFLUSH => Ok(Tmessage::Flush(WireFormat::decode(reader)?)),
+ TWALK => Ok(Tmessage::Walk(WireFormat::decode(reader)?)),
+ TREAD => Ok(Tmessage::Read(WireFormat::decode(reader)?)),
+ TWRITE => Ok(Tmessage::Write(WireFormat::decode(reader)?)),
+ TCLUNK => Ok(Tmessage::Clunk(WireFormat::decode(reader)?)),
+ TREMOVE => Ok(Tmessage::Remove(WireFormat::decode(reader)?)),
+ TATTACH => Ok(Tmessage::Attach(WireFormat::decode(reader)?)),
+ TAUTH => Ok(Tmessage::Auth(WireFormat::decode(reader)?)),
+ TSTATFS => Ok(Tmessage::Statfs(WireFormat::decode(reader)?)),
+ TLOPEN => Ok(Tmessage::Lopen(WireFormat::decode(reader)?)),
+ TLCREATE => Ok(Tmessage::Lcreate(WireFormat::decode(reader)?)),
+ TSYMLINK => Ok(Tmessage::Symlink(WireFormat::decode(reader)?)),
+ TMKNOD => Ok(Tmessage::Mknod(WireFormat::decode(reader)?)),
+ TRENAME => Ok(Tmessage::Rename(WireFormat::decode(reader)?)),
+ TREADLINK => Ok(Tmessage::Readlink(WireFormat::decode(reader)?)),
+ TGETATTR => Ok(Tmessage::GetAttr(WireFormat::decode(reader)?)),
+ TSETATTR => Ok(Tmessage::SetAttr(WireFormat::decode(reader)?)),
+ TXATTRWALK => Ok(Tmessage::XattrWalk(WireFormat::decode(reader)?)),
+ TXATTRCREATE => Ok(Tmessage::XattrCreate(WireFormat::decode(reader)?)),
+ TREADDIR => Ok(Tmessage::Readdir(WireFormat::decode(reader)?)),
+ TFSYNC => Ok(Tmessage::Fsync(WireFormat::decode(reader)?)),
+ TLOCK => Ok(Tmessage::Lock(WireFormat::decode(reader)?)),
+ TGETLOCK => Ok(Tmessage::GetLock(WireFormat::decode(reader)?)),
+ TLINK => Ok(Tmessage::Link(WireFormat::decode(reader)?)),
+ TMKDIR => Ok(Tmessage::Mkdir(WireFormat::decode(reader)?)),
+ TRENAMEAT => Ok(Tmessage::RenameAt(WireFormat::decode(reader)?)),
+ TUNLINKAT => Ok(Tmessage::UnlinkAt(WireFormat::decode(reader)?)),
+ err => Err(io::Error::new(
+ ErrorKind::InvalidData,
+ format!("unknown message type {}", err),
+ )),
+ }?;
+
+ Ok(Tframe { tag, msg })
+ }
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tversion {
+ pub msize: u32,
+ pub version: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tflush {
+ pub oldtag: u16,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Twalk {
+ pub fid: u32,
+ pub newfid: u32,
+ pub wnames: Vec<String>,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tread {
+ pub fid: u32,
+ pub offset: u64,
+ pub count: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Twrite {
+ pub fid: u32,
+ pub offset: u64,
+ pub data: Data,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tclunk {
+ pub fid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tremove {
+ pub fid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tauth {
+ pub afid: u32,
+ pub uname: String,
+ pub aname: String,
+ pub n_uname: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tattach {
+ pub fid: u32,
+ pub afid: u32,
+ pub uname: String,
+ pub aname: String,
+ pub n_uname: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tstatfs {
+ pub fid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tlopen {
+ pub fid: u32,
+ pub flags: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tlcreate {
+ pub fid: u32,
+ pub name: String,
+ pub flags: u32,
+ pub mode: u32,
+ pub gid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tsymlink {
+ pub fid: u32,
+ pub name: String,
+ pub symtgt: String,
+ pub gid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tmknod {
+ pub dfid: u32,
+ pub name: String,
+ pub mode: u32,
+ pub major: u32,
+ pub minor: u32,
+ pub gid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Trename {
+ pub fid: u32,
+ pub dfid: u32,
+ pub name: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Treadlink {
+ pub fid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tgetattr {
+ pub fid: u32,
+ pub request_mask: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tsetattr {
+ pub fid: u32,
+ pub valid: u32,
+ pub mode: u32,
+ pub uid: u32,
+ pub gid: u32,
+ pub size: u64,
+ pub atime_sec: u64,
+ pub atime_nsec: u64,
+ pub mtime_sec: u64,
+ pub mtime_nsec: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Txattrwalk {
+ pub fid: u32,
+ pub newfid: u32,
+ pub name: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Txattrcreate {
+ pub fid: u32,
+ pub name: String,
+ pub attr_size: u64,
+ pub flags: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Treaddir {
+ pub fid: u32,
+ pub offset: u64,
+ pub count: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tfsync {
+ pub fid: u32,
+ pub datasync: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tlock {
+ pub fid: u32,
+ pub type_: u8,
+ pub flags: u32,
+ pub start: u64,
+ pub length: u64,
+ pub proc_id: u32,
+ pub client_id: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tgetlock {
+ pub fid: u32,
+ pub type_: u8,
+ pub start: u64,
+ pub length: u64,
+ pub proc_id: u32,
+ pub client_id: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tlink {
+ pub dfid: u32,
+ pub fid: u32,
+ pub name: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tmkdir {
+ pub dfid: u32,
+ pub name: String,
+ pub mode: u32,
+ pub gid: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Trenameat {
+ pub olddirfid: u32,
+ pub oldname: String,
+ pub newdirfid: u32,
+ pub newname: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Tunlinkat {
+ pub dirfd: u32,
+ pub name: String,
+ pub flags: u32,
+}
+
+/// A message sent from a 9P server to a 9P client in response to a request from
+/// that client. Encapsulates a full frame.
+#[derive(Debug)]
+pub enum Rmessage {
+ Version(Rversion),
+ Flush,
+ Walk(Rwalk),
+ Read(Rread),
+ Write(Rwrite),
+ Clunk,
+ Remove,
+ Attach(Rattach),
+ Auth(Rauth),
+ Statfs(Rstatfs),
+ Lopen(Rlopen),
+ Lcreate(Rlcreate),
+ Symlink(Rsymlink),
+ Mknod(Rmknod),
+ Rename,
+ Readlink(Rreadlink),
+ GetAttr(Rgetattr),
+ SetAttr,
+ XattrWalk(Rxattrwalk),
+ XattrCreate,
+ Readdir(Rreaddir),
+ Fsync,
+ Lock(Rlock),
+ GetLock(Rgetlock),
+ Link,
+ Mkdir(Rmkdir),
+ RenameAt,
+ UnlinkAt,
+ Lerror(Rlerror),
+}
+
+#[derive(Debug)]
+pub struct Rframe {
+ pub tag: u16,
+ pub msg: Rmessage,
+}
+
+impl WireFormat for Rframe {
+ fn byte_size(&self) -> u32 {
+ let msg_size = match self.msg {
+ Rmessage::Version(ref version) => version.byte_size(),
+ Rmessage::Flush => 0,
+ Rmessage::Walk(ref walk) => walk.byte_size(),
+ Rmessage::Read(ref read) => read.byte_size(),
+ Rmessage::Write(ref write) => write.byte_size(),
+ Rmessage::Clunk => 0,
+ Rmessage::Remove => 0,
+ Rmessage::Attach(ref attach) => attach.byte_size(),
+ Rmessage::Auth(ref auth) => auth.byte_size(),
+ Rmessage::Statfs(ref statfs) => statfs.byte_size(),
+ Rmessage::Lopen(ref lopen) => lopen.byte_size(),
+ Rmessage::Lcreate(ref lcreate) => lcreate.byte_size(),
+ Rmessage::Symlink(ref symlink) => symlink.byte_size(),
+ Rmessage::Mknod(ref mknod) => mknod.byte_size(),
+ Rmessage::Rename => 0,
+ Rmessage::Readlink(ref readlink) => readlink.byte_size(),
+ Rmessage::GetAttr(ref getattr) => getattr.byte_size(),
+ Rmessage::SetAttr => 0,
+ Rmessage::XattrWalk(ref xattrwalk) => xattrwalk.byte_size(),
+ Rmessage::XattrCreate => 0,
+ Rmessage::Readdir(ref readdir) => readdir.byte_size(),
+ Rmessage::Fsync => 0,
+ Rmessage::Lock(ref lock) => lock.byte_size(),
+ Rmessage::GetLock(ref getlock) => getlock.byte_size(),
+ Rmessage::Link => 0,
+ Rmessage::Mkdir(ref mkdir) => mkdir.byte_size(),
+ Rmessage::RenameAt => 0,
+ Rmessage::UnlinkAt => 0,
+ Rmessage::Lerror(ref lerror) => lerror.byte_size(),
+ };
+
+ // size + type + tag + message size
+ (mem::size_of::<u32>() + mem::size_of::<u8>() + mem::size_of::<u16>()) as u32 + msg_size
+ }
+
+ fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+ self.byte_size().encode(writer)?;
+
+ let ty = match self.msg {
+ Rmessage::Version(_) => RVERSION,
+ Rmessage::Flush => RFLUSH,
+ Rmessage::Walk(_) => RWALK,
+ Rmessage::Read(_) => RREAD,
+ Rmessage::Write(_) => RWRITE,
+ Rmessage::Clunk => RCLUNK,
+ Rmessage::Remove => RREMOVE,
+ Rmessage::Attach(_) => RATTACH,
+ Rmessage::Auth(_) => RAUTH,
+ Rmessage::Statfs(_) => RSTATFS,
+ Rmessage::Lopen(_) => RLOPEN,
+ Rmessage::Lcreate(_) => RLCREATE,
+ Rmessage::Symlink(_) => RSYMLINK,
+ Rmessage::Mknod(_) => RMKNOD,
+ Rmessage::Rename => RRENAME,
+ Rmessage::Readlink(_) => RREADLINK,
+ Rmessage::GetAttr(_) => RGETATTR,
+ Rmessage::SetAttr => RSETATTR,
+ Rmessage::XattrWalk(_) => RXATTRWALK,
+ Rmessage::XattrCreate => RXATTRCREATE,
+ Rmessage::Readdir(_) => RREADDIR,
+ Rmessage::Fsync => RFSYNC,
+ Rmessage::Lock(_) => RLOCK,
+ Rmessage::GetLock(_) => RGETLOCK,
+ Rmessage::Link => RLINK,
+ Rmessage::Mkdir(_) => RMKDIR,
+ Rmessage::RenameAt => RRENAMEAT,
+ Rmessage::UnlinkAt => RUNLINKAT,
+ Rmessage::Lerror(_) => RLERROR,
+ };
+
+ ty.encode(writer)?;
+ self.tag.encode(writer)?;
+
+ match self.msg {
+ Rmessage::Version(ref version) => version.encode(writer),
+ Rmessage::Flush => Ok(()),
+ Rmessage::Walk(ref walk) => walk.encode(writer),
+ Rmessage::Read(ref read) => read.encode(writer),
+ Rmessage::Write(ref write) => write.encode(writer),
+ Rmessage::Clunk => Ok(()),
+ Rmessage::Remove => Ok(()),
+ Rmessage::Attach(ref attach) => attach.encode(writer),
+ Rmessage::Auth(ref auth) => auth.encode(writer),
+ Rmessage::Statfs(ref statfs) => statfs.encode(writer),
+ Rmessage::Lopen(ref lopen) => lopen.encode(writer),
+ Rmessage::Lcreate(ref lcreate) => lcreate.encode(writer),
+ Rmessage::Symlink(ref symlink) => symlink.encode(writer),
+ Rmessage::Mknod(ref mknod) => mknod.encode(writer),
+ Rmessage::Rename => Ok(()),
+ Rmessage::Readlink(ref readlink) => readlink.encode(writer),
+ Rmessage::GetAttr(ref getattr) => getattr.encode(writer),
+ Rmessage::SetAttr => Ok(()),
+ Rmessage::XattrWalk(ref xattrwalk) => xattrwalk.encode(writer),
+ Rmessage::XattrCreate => Ok(()),
+ Rmessage::Readdir(ref readdir) => readdir.encode(writer),
+ Rmessage::Fsync => Ok(()),
+ Rmessage::Lock(ref lock) => lock.encode(writer),
+ Rmessage::GetLock(ref getlock) => getlock.encode(writer),
+ Rmessage::Link => Ok(()),
+ Rmessage::Mkdir(ref mkdir) => mkdir.encode(writer),
+ Rmessage::RenameAt => Ok(()),
+ Rmessage::UnlinkAt => Ok(()),
+ Rmessage::Lerror(ref lerror) => lerror.encode(writer),
+ }
+ }
+
+ fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+ let byte_size: u32 = WireFormat::decode(reader)?;
+
+ // byte_size includes the size of byte_size so remove that from the
+ // expected length of the message.
+ let reader = &mut reader.take((byte_size - mem::size_of::<u32>() as u32) as u64);
+
+ let mut ty = [0u8];
+ reader.read_exact(&mut ty)?;
+
+ let tag: u16 = WireFormat::decode(reader)?;
+
+ let msg = match ty[0] {
+ RVERSION => Ok(Rmessage::Version(WireFormat::decode(reader)?)),
+ RFLUSH => Ok(Rmessage::Flush),
+ RWALK => Ok(Rmessage::Walk(WireFormat::decode(reader)?)),
+ RREAD => Ok(Rmessage::Read(WireFormat::decode(reader)?)),
+ RWRITE => Ok(Rmessage::Write(WireFormat::decode(reader)?)),
+ RCLUNK => Ok(Rmessage::Clunk),
+ RREMOVE => Ok(Rmessage::Remove),
+ RATTACH => Ok(Rmessage::Attach(WireFormat::decode(reader)?)),
+ RAUTH => Ok(Rmessage::Auth(WireFormat::decode(reader)?)),
+ RSTATFS => Ok(Rmessage::Statfs(WireFormat::decode(reader)?)),
+ RLOPEN => Ok(Rmessage::Lopen(WireFormat::decode(reader)?)),
+ RLCREATE => Ok(Rmessage::Lcreate(WireFormat::decode(reader)?)),
+ RSYMLINK => Ok(Rmessage::Symlink(WireFormat::decode(reader)?)),
+ RMKNOD => Ok(Rmessage::Mknod(WireFormat::decode(reader)?)),
+ RRENAME => Ok(Rmessage::Rename),
+ RREADLINK => Ok(Rmessage::Readlink(WireFormat::decode(reader)?)),
+ RGETATTR => Ok(Rmessage::GetAttr(WireFormat::decode(reader)?)),
+ RSETATTR => Ok(Rmessage::SetAttr),
+ RXATTRWALK => Ok(Rmessage::XattrWalk(WireFormat::decode(reader)?)),
+ RXATTRCREATE => Ok(Rmessage::XattrCreate),
+ RREADDIR => Ok(Rmessage::Readdir(WireFormat::decode(reader)?)),
+ RFSYNC => Ok(Rmessage::Fsync),
+ RLOCK => Ok(Rmessage::Lock(WireFormat::decode(reader)?)),
+ RGETLOCK => Ok(Rmessage::GetLock(WireFormat::decode(reader)?)),
+ RLINK => Ok(Rmessage::Link),
+ RMKDIR => Ok(Rmessage::Mkdir(WireFormat::decode(reader)?)),
+ RRENAMEAT => Ok(Rmessage::RenameAt),
+ RUNLINKAT => Ok(Rmessage::UnlinkAt),
+ RLERROR => Ok(Rmessage::Lerror(WireFormat::decode(reader)?)),
+ err => Err(io::Error::new(
+ ErrorKind::InvalidData,
+ format!("unknown message type {}", err),
+ )),
+ }?;
+
+ Ok(Rframe { tag, msg })
+ }
+}
+
+#[derive(Debug, Copy, Clone, P9WireFormat)]
+pub struct Qid {
+ pub ty: u8,
+ pub version: u32,
+ pub path: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Dirent {
+ pub qid: Qid,
+ pub offset: u64,
+ pub ty: u8,
+ pub name: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rversion {
+ pub msize: u32,
+ pub version: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rwalk {
+ pub wqids: Vec<Qid>,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rread {
+ pub data: Data,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rwrite {
+ pub count: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rauth {
+ pub aqid: Qid,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rattach {
+ pub qid: Qid,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rlerror {
+ pub ecode: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rstatfs {
+ pub ty: u32,
+ pub bsize: u32,
+ pub blocks: u64,
+ pub bfree: u64,
+ pub bavail: u64,
+ pub files: u64,
+ pub ffree: u64,
+ pub fsid: u64,
+ pub namelen: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rlopen {
+ pub qid: Qid,
+ pub iounit: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rlcreate {
+ pub qid: Qid,
+ pub iounit: u32,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rsymlink {
+ pub qid: Qid,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rmknod {
+ pub qid: Qid,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rreadlink {
+ pub target: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rgetattr {
+ pub valid: u64,
+ pub qid: Qid,
+ pub mode: u32,
+ pub uid: u32,
+ pub gid: u32,
+ pub nlink: u64,
+ pub rdev: u64,
+ pub size: u64,
+ pub blksize: u64,
+ pub blocks: u64,
+ pub atime_sec: u64,
+ pub atime_nsec: u64,
+ pub mtime_sec: u64,
+ pub mtime_nsec: u64,
+ pub ctime_sec: u64,
+ pub ctime_nsec: u64,
+ pub btime_sec: u64,
+ pub btime_nsec: u64,
+ pub gen: u64,
+ pub data_version: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rxattrwalk {
+ pub size: u64,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rreaddir {
+ pub data: Data,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rlock {
+ pub status: u8,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rgetlock {
+ pub ty: u8,
+ pub start: u64,
+ pub length: u64,
+ pub proc_id: u32,
+ pub client_id: String,
+}
+
+#[derive(Debug, P9WireFormat)]
+pub struct Rmkdir {
+ pub qid: Qid,
+}
diff --git a/common/p9/src/protocol/mod.rs b/common/p9/src/protocol/mod.rs
new file mode 100644
index 000000000..9c278ee89
--- /dev/null
+++ b/common/p9/src/protocol/mod.rs
@@ -0,0 +1,9 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod messages;
+mod wire_format;
+
+pub use self::messages::*;
+pub use self::wire_format::{Data, WireFormat};
diff --git a/common/p9/src/protocol/wire_format.rs b/common/p9/src/protocol/wire_format.rs
new file mode 100644
index 000000000..1576bde43
--- /dev/null
+++ b/common/p9/src/protocol/wire_format.rs
@@ -0,0 +1,699 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::fmt;
+use std::io;
+use std::io::{ErrorKind, Read, Write};
+use std::mem;
+use std::ops::{Deref, DerefMut};
+use std::string::String;
+use std::vec::Vec;
+
+/// A type that can be encoded on the wire using the 9P protocol.
+pub trait WireFormat: std::marker::Sized {
+ /// Returns the number of bytes necessary to fully encode `self`.
+ fn byte_size(&self) -> u32;
+
+ /// Encodes `self` into `writer`.
+ fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()>;
+
+ /// Decodes `Self` from `reader`.
+ fn decode<R: Read>(reader: &mut R) -> io::Result<Self>;
+}
+
+// This doesn't really _need_ to be a macro but unfortunately there is no trait bound to
+// express "can be casted to another type", which means we can't write `T as u8` in a trait
+// based implementation. So instead we have this macro, which is implemented for all the
+// stable unsigned types with the added benefit of not being implemented for the signed
+// types which are not allowed by the protocol.
+macro_rules! uint_wire_format_impl {
+ ($Ty:ty) => {
+ impl WireFormat for $Ty {
+ fn byte_size(&self) -> u32 {
+ mem::size_of::<$Ty>() as u32
+ }
+
+ fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+ let mut buf = [0u8; mem::size_of::<$Ty>()];
+
+ // Encode the bytes into the buffer in little endian order.
+ for idx in 0..mem::size_of::<$Ty>() {
+ buf[idx] = (self >> (8 * idx)) as u8;
+ }
+
+ writer.write_all(&buf)
+ }
+
+ fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+ let mut buf = [0u8; mem::size_of::<$Ty>()];
+ reader.read_exact(&mut buf)?;
+
+ // Read bytes from the buffer in little endian order.
+ let mut result = 0;
+ for idx in 0..mem::size_of::<$Ty>() {
+ result |= (buf[idx] as $Ty) << (8 * idx);
+ }
+
+ Ok(result)
+ }
+ }
+ };
+}
+uint_wire_format_impl!(u8);
+uint_wire_format_impl!(u16);
+uint_wire_format_impl!(u32);
+uint_wire_format_impl!(u64);
+
+// The 9P protocol requires that strings are UTF-8 encoded. The wire format is a u16
+// count |N|, encoded in little endian, followed by |N| bytes of UTF-8 data.
+impl WireFormat for String {
+ fn byte_size(&self) -> u32 {
+ (mem::size_of::<u16>() + self.len()) as u32
+ }
+
+ fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+ if self.len() > std::u16::MAX as usize {
+ return Err(io::Error::new(
+ ErrorKind::InvalidInput,
+ "string is too long",
+ ));
+ }
+
+ (self.len() as u16).encode(writer)?;
+ writer.write_all(self.as_bytes())
+ }
+
+ fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+ let len: u16 = WireFormat::decode(reader)?;
+ let mut result = String::with_capacity(len as usize);
+ reader.take(len as u64).read_to_string(&mut result)?;
+ Ok(result)
+ }
+}
+
+// The wire format for repeated types is similar to that of strings: a little endian
+// encoded u16 |N|, followed by |N| instances of the given type.
+impl<T: WireFormat> WireFormat for Vec<T> {
+ fn byte_size(&self) -> u32 {
+ mem::size_of::<u16>() as u32 + self.iter().map(|elem| elem.byte_size()).sum::<u32>()
+ }
+
+ fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+ if self.len() > std::u16::MAX as usize {
+ return Err(io::Error::new(
+ ErrorKind::InvalidInput,
+ "too many elements in vector",
+ ));
+ }
+
+ (self.len() as u16).encode(writer)?;
+ for elem in self {
+ elem.encode(writer)?;
+ }
+
+ Ok(())
+ }
+
+ fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+ let len: u16 = WireFormat::decode(reader)?;
+ let mut result = Vec::with_capacity(len as usize);
+
+ for _ in 0..len {
+ result.push(WireFormat::decode(reader)?);
+ }
+
+ Ok(result)
+ }
+}
+
+/// A type that encodes an arbitrary number of bytes of data. Typically used for Rread
+/// Twrite messages. This differs from a `Vec<u8>` in that it encodes the number of bytes
+/// using a `u32` instead of a `u16`.
+#[derive(PartialEq)]
+pub struct Data(pub Vec<u8>);
+
+// The maximum length of a data buffer that we support. In practice the server's max message
+// size should prevent us from reading too much data so this check is mainly to ensure a
+// malicious client cannot trick us into allocating massive amounts of memory.
+const MAX_DATA_LENGTH: u32 = 32 * 1024 * 1024;
+
+impl fmt::Debug for Data {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ // There may be a lot of data and we don't want to spew it all out in a trace. Instead
+ // just print out the number of bytes in the buffer.
+ write!(f, "Data({} bytes)", self.len())
+ }
+}
+
+// Implement Deref and DerefMut so that we don't have to use self.0 everywhere.
+impl Deref for Data {
+ type Target = Vec<u8>;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+impl DerefMut for Data {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+// Same as Vec<u8> except that it encodes the length as a u32 instead of a u16.
+impl WireFormat for Data {
+ fn byte_size(&self) -> u32 {
+ mem::size_of::<u32>() as u32 + self.iter().map(|elem| elem.byte_size()).sum::<u32>()
+ }
+
+ fn encode<W: Write>(&self, writer: &mut W) -> io::Result<()> {
+ if self.len() > std::u32::MAX as usize {
+ return Err(io::Error::new(ErrorKind::InvalidInput, "data is too large"));
+ }
+ (self.len() as u32).encode(writer)?;
+ writer.write_all(self)
+ }
+
+ fn decode<R: Read>(reader: &mut R) -> io::Result<Self> {
+ let len: u32 = WireFormat::decode(reader)?;
+ if len > MAX_DATA_LENGTH {
+ return Err(io::Error::new(
+ ErrorKind::InvalidData,
+ format!("data length ({} bytes) is too large", len),
+ ));
+ }
+
+ let mut buf = Vec::with_capacity(len as usize);
+ reader.take(len as u64).read_to_end(&mut buf)?;
+
+ if buf.len() == len as usize {
+ Ok(Data(buf))
+ } else {
+ Err(io::Error::new(
+ ErrorKind::UnexpectedEof,
+ format!(
+ "unexpected end of data: want: {} bytes, got: {} bytes",
+ len,
+ buf.len()
+ ),
+ ))
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::io::Cursor;
+ use std::mem;
+ use std::string::String;
+
+ #[test]
+ fn integer_byte_size() {
+ assert_eq!(1, 0u8.byte_size());
+ assert_eq!(2, 0u16.byte_size());
+ assert_eq!(4, 0u32.byte_size());
+ assert_eq!(8, 0u64.byte_size());
+ }
+
+ #[test]
+ fn integer_decode() {
+ let buf: [u8; 8] = [0xef, 0xbe, 0xad, 0xde, 0x0d, 0xf0, 0xad, 0x8b];
+
+ assert_eq!(0xef_u8, WireFormat::decode(&mut Cursor::new(&buf)).unwrap());
+ assert_eq!(0xbeef_u16, u16::decode(&mut Cursor::new(&buf)).unwrap());
+ assert_eq!(0xdeadbeef_u32, u32::decode(&mut Cursor::new(&buf)).unwrap());
+ assert_eq!(
+ 0x8bad_f00d_dead_beef_u64,
+ u64::decode(&mut Cursor::new(&buf)).unwrap()
+ );
+ }
+
+ #[test]
+ fn integer_encode() {
+ let value: u64 = 0x8bad_f00d_dead_beef;
+ let expected: [u8; 8] = [0xef, 0xbe, 0xad, 0xde, 0x0d, 0xf0, 0xad, 0x8b];
+
+ let mut buf = vec![0; 8];
+
+ (value as u8).encode(&mut Cursor::new(&mut *buf)).unwrap();
+ assert_eq!(expected[0..1], buf[0..1]);
+
+ (value as u16).encode(&mut Cursor::new(&mut *buf)).unwrap();
+ assert_eq!(expected[0..2], buf[0..2]);
+
+ (value as u32).encode(&mut Cursor::new(&mut *buf)).unwrap();
+ assert_eq!(expected[0..4], buf[0..4]);
+
+ value.encode(&mut Cursor::new(&mut *buf)).unwrap();
+ assert_eq!(expected[0..8], buf[0..8]);
+ }
+
+ #[test]
+ fn string_byte_size() {
+ let values = [
+ String::from("Google Video"),
+ String::from("网页 图片 资讯更多 »"),
+ String::from("Παγκόσμιος Ιστός"),
+ String::from("Поиск страниц на русском"),
+ String::from("전체서비스"),
+ ];
+
+ let exp = values
+ .iter()
+ .map(|v| (mem::size_of::<u16>() + v.len()) as u32);
+
+ for (value, expected) in values.iter().zip(exp) {
+ assert_eq!(expected, value.byte_size());
+ }
+ }
+
+ #[test]
+ fn zero_length_string() {
+ let s = String::from("");
+ assert_eq!(s.byte_size(), mem::size_of::<u16>() as u32);
+
+ let mut buf = [0xffu8; 4];
+
+ s.encode(&mut Cursor::new(&mut buf[..]))
+ .expect("failed to encode empty string");
+ assert_eq!(&[0, 0, 0xff, 0xff], &buf);
+
+ assert_eq!(
+ s,
+ <String as WireFormat>::decode(&mut Cursor::new(&[0, 0, 0x61, 0x61][..]))
+ .expect("failed to decode empty string")
+ );
+ }
+
+ #[test]
+ fn string_encode() {
+ let values = [
+ String::from("Google Video"),
+ String::from("网页 图片 资讯更多 »"),
+ String::from("Παγκόσμιος Ιστός"),
+ String::from("Поиск страниц на русском"),
+ String::from("전체서비스"),
+ ];
+
+ let expected = values.iter().map(|v| {
+ let len = v.as_bytes().len();
+ let mut buf = Vec::with_capacity(len + mem::size_of::<u16>());
+
+ buf.push(len as u8);
+ buf.push((len >> 8) as u8);
+
+ buf.extend_from_slice(v.as_bytes());
+
+ buf
+ });
+
+ for (val, exp) in values.iter().zip(expected) {
+ let mut buf = vec![0; exp.len()];
+
+ WireFormat::encode(val, &mut Cursor::new(&mut *buf)).unwrap();
+ assert_eq!(exp, buf);
+ }
+ }
+
+ #[test]
+ fn string_decode() {
+ assert_eq!(
+ String::from("Google Video"),
+ <String as WireFormat>::decode(&mut Cursor::new(
+ &[
+ 0x0c, 0x00, 0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x20, 0x56, 0x69, 0x64, 0x65,
+ 0x6F,
+ ][..]
+ ))
+ .unwrap()
+ );
+ assert_eq!(
+ String::from("网页 图片 资讯更多 »"),
+ <String as WireFormat>::decode(&mut Cursor::new(
+ &[
+ 0x1d, 0x00, 0xE7, 0xBD, 0x91, 0xE9, 0xA1, 0xB5, 0x20, 0xE5, 0x9B, 0xBE, 0xE7,
+ 0x89, 0x87, 0x20, 0xE8, 0xB5, 0x84, 0xE8, 0xAE, 0xAF, 0xE6, 0x9B, 0xB4, 0xE5,
+ 0xA4, 0x9A, 0x20, 0xC2, 0xBB,
+ ][..]
+ ))
+ .unwrap()
+ );
+ assert_eq!(
+ String::from("Παγκόσμιος Ιστός"),
+ <String as WireFormat>::decode(&mut Cursor::new(
+ &[
+ 0x1f, 0x00, 0xCE, 0xA0, 0xCE, 0xB1, 0xCE, 0xB3, 0xCE, 0xBA, 0xCF, 0x8C, 0xCF,
+ 0x83, 0xCE, 0xBC, 0xCE, 0xB9, 0xCE, 0xBF, 0xCF, 0x82, 0x20, 0xCE, 0x99, 0xCF,
+ 0x83, 0xCF, 0x84, 0xCF, 0x8C, 0xCF, 0x82,
+ ][..]
+ ))
+ .unwrap()
+ );
+ assert_eq!(
+ String::from("Поиск страниц на русском"),
+ <String as WireFormat>::decode(&mut Cursor::new(
+ &[
+ 0x2d, 0x00, 0xD0, 0x9F, 0xD0, 0xBE, 0xD0, 0xB8, 0xD1, 0x81, 0xD0, 0xBA, 0x20,
+ 0xD1, 0x81, 0xD1, 0x82, 0xD1, 0x80, 0xD0, 0xB0, 0xD0, 0xBD, 0xD0, 0xB8, 0xD1,
+ 0x86, 0x20, 0xD0, 0xBD, 0xD0, 0xB0, 0x20, 0xD1, 0x80, 0xD1, 0x83, 0xD1, 0x81,
+ 0xD1, 0x81, 0xD0, 0xBA, 0xD0, 0xBE, 0xD0, 0xBC,
+ ][..]
+ ))
+ .unwrap()
+ );
+ assert_eq!(
+ String::from("전체서비스"),
+ <String as WireFormat>::decode(&mut Cursor::new(
+ &[
+ 0x0f, 0x00, 0xEC, 0xA0, 0x84, 0xEC, 0xB2, 0xB4, 0xEC, 0x84, 0x9C, 0xEB, 0xB9,
+ 0x84, 0xEC, 0x8A, 0xA4,
+ ][..]
+ ))
+ .unwrap()
+ );
+ }
+
+ #[test]
+ fn invalid_string_decode() {
+ let _ = <String as WireFormat>::decode(&mut Cursor::new(&[
+ 0x06, 0x00, 0xed, 0xa0, 0x80, 0xed, 0xbf, 0xbf,
+ ]))
+ .expect_err("surrogate code point");
+
+ let _ = <String as WireFormat>::decode(&mut Cursor::new(&[
+ 0x05, 0x00, 0xf8, 0x80, 0x80, 0x80, 0xbf,
+ ]))
+ .expect_err("overlong sequence");
+
+ let _ =
+ <String as WireFormat>::decode(&mut Cursor::new(&[0x04, 0x00, 0xf4, 0x90, 0x80, 0x80]))
+ .expect_err("out of range");
+
+ let _ =
+ <String as WireFormat>::decode(&mut Cursor::new(&[0x04, 0x00, 0x63, 0x61, 0x66, 0xe9]))
+ .expect_err("ISO-8859-1");
+
+ let _ =
+ <String as WireFormat>::decode(&mut Cursor::new(&[0x04, 0x00, 0xb0, 0xa1, 0xb0, 0xa2]))
+ .expect_err("EUC-KR");
+ }
+
+ #[test]
+ fn vector_encode() {
+ let values: Vec<u32> = vec![291, 18_916, 2_497, 22, 797_162, 2_119_732, 3_213_929_716];
+ let mut expected: Vec<u8> =
+ Vec::with_capacity(values.len() * mem::size_of::<u32>() + mem::size_of::<u16>());
+ expected.push(values.len() as u8);
+ expected.push((values.len() >> 8) as u8);
+
+ const MASK: u32 = 0xff;
+ for val in &values {
+ expected.push((val & MASK) as u8);
+ expected.push(((val >> 8) & MASK) as u8);
+ expected.push(((val >> 16) & MASK) as u8);
+ expected.push(((val >> 24) & MASK) as u8);
+ }
+
+ let mut actual: Vec<u8> = vec![0; expected.len()];
+
+ WireFormat::encode(&values, &mut Cursor::new(&mut *actual))
+ .expect("failed to encode vector");
+ assert_eq!(expected, actual);
+ }
+
+ #[test]
+ fn vector_decode() {
+ let expected: Vec<u32> = vec![
+ 2_498,
+ 24,
+ 897,
+ 4_097_789_579,
+ 8_498_119,
+ 684_279,
+ 961_189_198,
+ 7,
+ ];
+ let mut input: Vec<u8> =
+ Vec::with_capacity(expected.len() * mem::size_of::<u32>() + mem::size_of::<u16>());
+ input.push(expected.len() as u8);
+ input.push((expected.len() >> 8) as u8);
+
+ const MASK: u32 = 0xff;
+ for val in &expected {
+ input.push((val & MASK) as u8);
+ input.push(((val >> 8) & MASK) as u8);
+ input.push(((val >> 16) & MASK) as u8);
+ input.push(((val >> 24) & MASK) as u8);
+ }
+
+ assert_eq!(
+ expected,
+ <Vec<u32> as WireFormat>::decode(&mut Cursor::new(&*input))
+ .expect("failed to decode vector")
+ );
+ }
+
+ #[test]
+ fn data_encode() {
+ let values = Data(vec![169, 155, 79, 67, 182, 199, 25, 73, 129, 200]);
+ let mut expected: Vec<u8> =
+ Vec::with_capacity(values.len() * mem::size_of::<u8>() + mem::size_of::<u32>());
+ expected.push(values.len() as u8);
+ expected.push((values.len() >> 8) as u8);
+ expected.push((values.len() >> 16) as u8);
+ expected.push((values.len() >> 24) as u8);
+ expected.extend_from_slice(&values);
+
+ let mut actual: Vec<u8> = vec![0; expected.len()];
+
+ WireFormat::encode(&values, &mut Cursor::new(&mut *actual))
+ .expect("failed to encode datar");
+ assert_eq!(expected, actual);
+ }
+
+ #[test]
+ fn data_decode() {
+ let expected = Data(vec![219, 15, 8, 155, 194, 129, 79, 91, 46, 53, 173]);
+ let mut input: Vec<u8> =
+ Vec::with_capacity(expected.len() * mem::size_of::<u8>() + mem::size_of::<u32>());
+ input.push(expected.len() as u8);
+ input.push((expected.len() >> 8) as u8);
+ input.push((expected.len() >> 16) as u8);
+ input.push((expected.len() >> 24) as u8);
+ input.extend_from_slice(&expected);
+
+ assert_eq!(
+ expected,
+ <Data as WireFormat>::decode(&mut Cursor::new(&mut *input))
+ .expect("failed to decode data")
+ );
+ }
+
+ #[test]
+ fn error_cases() {
+ // string is too long.
+ let mut long_str = String::with_capacity(std::u16::MAX as usize);
+ while long_str.len() < std::u16::MAX as usize {
+ long_str.push_str("long");
+ }
+ long_str.push('!');
+
+ let count = long_str.len() + mem::size_of::<u16>();
+ let mut buf = vec![0; count];
+
+ long_str
+ .encode(&mut Cursor::new(&mut *buf))
+ .expect_err("long string");
+
+ // vector is too long.
+ let mut long_vec: Vec<u32> = Vec::with_capacity(std::u16::MAX as usize);
+ while long_vec.len() < std::u16::MAX as usize {
+ long_vec.push(0x8bad_f00d);
+ }
+ long_vec.push(0x00ba_b10c);
+
+ let count = long_vec.len() * mem::size_of::<u32>();
+ let mut buf = vec![0; count];
+
+ WireFormat::encode(&long_vec, &mut Cursor::new(&mut *buf)).expect_err("long vector");
+ }
+
+ #[derive(Debug, PartialEq, P9WireFormat)]
+ struct Item {
+ a: u64,
+ b: String,
+ c: Vec<u16>,
+ buf: Data,
+ }
+
+ #[test]
+ fn struct_encode() {
+ let item = Item {
+ a: 0xdead_10cc_00ba_b10c,
+ b: String::from("冻住,不许走!"),
+ c: vec![359, 492, 8891],
+ buf: Data(vec![254, 129, 0, 62, 49, 172]),
+ };
+
+ let mut expected: Vec<u8> = vec![0x0c, 0xb1, 0xba, 0x00, 0xcc, 0x10, 0xad, 0xde];
+ let strlen = item.b.len() as u16;
+ expected.push(strlen as u8);
+ expected.push((strlen >> 8) as u8);
+ expected.extend_from_slice(item.b.as_bytes());
+
+ let veclen = item.c.len() as u16;
+ expected.push(veclen as u8);
+ expected.push((veclen >> 8) as u8);
+ for val in &item.c {
+ expected.push(*val as u8);
+ expected.push((val >> 8) as u8);
+ }
+
+ let buflen = item.buf.len() as u32;
+ expected.push(buflen as u8);
+ expected.push((buflen >> 8) as u8);
+ expected.push((buflen >> 16) as u8);
+ expected.push((buflen >> 24) as u8);
+ expected.extend_from_slice(&item.buf);
+
+ let mut actual = vec![0; expected.len()];
+
+ WireFormat::encode(&item, &mut Cursor::new(&mut *actual)).expect("failed to encode item");
+
+ assert_eq!(expected, actual);
+ }
+
+ #[test]
+ fn struct_decode() {
+ let expected = Item {
+ a: 0xface_b00c_0404_4b1d,
+ b: String::from("Огонь по готовности!"),
+ c: vec![20067, 32449, 549, 4972, 77, 1987],
+ buf: Data(vec![126, 236, 79, 59, 6, 159]),
+ };
+
+ let mut input: Vec<u8> = vec![0x1d, 0x4b, 0x04, 0x04, 0x0c, 0xb0, 0xce, 0xfa];
+ let strlen = expected.b.len() as u16;
+ input.push(strlen as u8);
+ input.push((strlen >> 8) as u8);
+ input.extend_from_slice(expected.b.as_bytes());
+
+ let veclen = expected.c.len() as u16;
+ input.push(veclen as u8);
+ input.push((veclen >> 8) as u8);
+ for val in &expected.c {
+ input.push(*val as u8);
+ input.push((val >> 8) as u8);
+ }
+
+ let buflen = expected.buf.len() as u32;
+ input.push(buflen as u8);
+ input.push((buflen >> 8) as u8);
+ input.push((buflen >> 16) as u8);
+ input.push((buflen >> 24) as u8);
+ input.extend_from_slice(&expected.buf);
+
+ let actual: Item =
+ WireFormat::decode(&mut Cursor::new(input)).expect("failed to decode item");
+
+ assert_eq!(expected, actual);
+ }
+
+ #[derive(Debug, PartialEq, P9WireFormat)]
+ struct Nested {
+ item: Item,
+ val: Vec<u64>,
+ }
+
+ #[allow(clippy::vec_init_then_push)]
+ fn build_encoded_buffer(value: &Nested) -> Vec<u8> {
+ let mut result: Vec<u8> = Vec::new();
+
+ // encode a
+ result.push(value.item.a as u8);
+ result.push((value.item.a >> 8) as u8);
+ result.push((value.item.a >> 16) as u8);
+ result.push((value.item.a >> 24) as u8);
+ result.push((value.item.a >> 32) as u8);
+ result.push((value.item.a >> 40) as u8);
+ result.push((value.item.a >> 48) as u8);
+ result.push((value.item.a >> 56) as u8);
+
+ // encode b
+ result.push(value.item.b.len() as u8);
+ result.push((value.item.b.len() >> 8) as u8);
+ result.extend_from_slice(value.item.b.as_bytes());
+
+ // encode c
+ result.push(value.item.c.len() as u8);
+ result.push((value.item.c.len() >> 8) as u8);
+ for val in &value.item.c {
+ result.push((val & 0xffu16) as u8);
+ result.push(((val >> 8) & 0xffu16) as u8);
+ }
+
+ // encode buf
+ result.push(value.item.buf.len() as u8);
+ result.push((value.item.buf.len() >> 8) as u8);
+ result.push((value.item.buf.len() >> 16) as u8);
+ result.push((value.item.buf.len() >> 24) as u8);
+ result.extend_from_slice(&value.item.buf);
+
+ // encode val
+ result.push(value.val.len() as u8);
+ result.push((value.val.len() >> 8) as u8);
+ for val in &value.val {
+ result.push(*val as u8);
+ result.push((val >> 8) as u8);
+ result.push((val >> 16) as u8);
+ result.push((val >> 24) as u8);
+ result.push((val >> 32) as u8);
+ result.push((val >> 40) as u8);
+ result.push((val >> 48) as u8);
+ result.push((val >> 56) as u8);
+ }
+
+ result
+ }
+
+ #[test]
+ fn nested_encode() {
+ let value = Nested {
+ item: Item {
+ a: 0xcafe_d00d_8bad_f00d,
+ b: String::from("龍が我が敵を喰らう!"),
+ c: vec![2679, 55_919, 44, 38_819, 792],
+ buf: Data(vec![129, 55, 200, 93, 7, 68]),
+ },
+ val: vec![1954978, 59, 4519, 15679],
+ };
+
+ let expected = build_encoded_buffer(&value);
+
+ let mut actual = vec![0; expected.len()];
+
+ WireFormat::encode(&value, &mut Cursor::new(&mut *actual)).expect("failed to encode value");
+ assert_eq!(expected, actual);
+ }
+
+ #[test]
+ fn nested_decode() {
+ let expected = Nested {
+ item: Item {
+ a: 0x0ff1ce,
+ b: String::from("龍神の剣を喰らえ!"),
+ c: vec![21687, 159, 55, 9217, 192],
+ buf: Data(vec![189, 22, 7, 59, 235]),
+ },
+ val: vec![15679, 8619196, 319746, 123957, 77, 0, 492],
+ };
+
+ let input = build_encoded_buffer(&expected);
+
+ assert_eq!(
+ expected,
+ <Nested as WireFormat>::decode(&mut Cursor::new(&*input))
+ .expect("failed to decode value")
+ );
+ }
+}
diff --git a/common/p9/src/server/mod.rs b/common/p9/src/server/mod.rs
new file mode 100644
index 000000000..cbba232e6
--- /dev/null
+++ b/common/p9/src/server/mod.rs
@@ -0,0 +1,994 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cmp::min;
+use std::collections::{btree_map, BTreeMap};
+use std::ffi::{CStr, CString};
+use std::fs::File;
+use std::io::{self, Cursor, Read, Write};
+use std::mem::{self, MaybeUninit};
+use std::ops::Deref;
+use std::os::unix::ffi::OsStrExt;
+use std::os::unix::fs::FileExt;
+use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+use std::path::Path;
+
+use sys_util::{read_dir::read_dir, syscall};
+
+use crate::protocol::*;
+
+// Tlopen and Tlcreate flags. Taken from "include/net/9p/9p.h" in the linux tree.
+const P9_RDONLY: u32 = 0o00000000;
+const P9_WRONLY: u32 = 0o00000001;
+const P9_RDWR: u32 = 0o00000002;
+const P9_NOACCESS: u32 = 0o00000003;
+const P9_CREATE: u32 = 0o00000100;
+const P9_EXCL: u32 = 0o00000200;
+const P9_NOCTTY: u32 = 0o00000400;
+const P9_TRUNC: u32 = 0o00001000;
+const P9_APPEND: u32 = 0o00002000;
+const P9_NONBLOCK: u32 = 0o00004000;
+const P9_DSYNC: u32 = 0o00010000;
+const P9_FASYNC: u32 = 0o00020000;
+const P9_DIRECT: u32 = 0o00040000;
+const P9_LARGEFILE: u32 = 0o00100000;
+const P9_DIRECTORY: u32 = 0o00200000;
+const P9_NOFOLLOW: u32 = 0o00400000;
+const P9_NOATIME: u32 = 0o01000000;
+const _P9_CLOEXEC: u32 = 0o02000000;
+const P9_SYNC: u32 = 0o04000000;
+
+// Mapping from 9P flags to libc flags.
+const MAPPED_FLAGS: [(u32, i32); 16] = [
+ (P9_WRONLY, libc::O_WRONLY),
+ (P9_RDWR, libc::O_RDWR),
+ (P9_CREATE, libc::O_CREAT),
+ (P9_EXCL, libc::O_EXCL),
+ (P9_NOCTTY, libc::O_NOCTTY),
+ (P9_TRUNC, libc::O_TRUNC),
+ (P9_APPEND, libc::O_APPEND),
+ (P9_NONBLOCK, libc::O_NONBLOCK),
+ (P9_DSYNC, libc::O_DSYNC),
+ (P9_FASYNC, 0), // Unsupported
+ (P9_DIRECT, libc::O_DIRECT),
+ (P9_LARGEFILE, libc::O_LARGEFILE),
+ (P9_DIRECTORY, libc::O_DIRECTORY),
+ (P9_NOFOLLOW, libc::O_NOFOLLOW),
+ (P9_NOATIME, libc::O_NOATIME),
+ (P9_SYNC, libc::O_SYNC),
+];
+
+// 9P Qid types. Taken from "include/net/9p/9p.h" in the linux tree.
+const P9_QTDIR: u8 = 0x80;
+const _P9_QTAPPEND: u8 = 0x40;
+const _P9_QTEXCL: u8 = 0x20;
+const _P9_QTMOUNT: u8 = 0x10;
+const _P9_QTAUTH: u8 = 0x08;
+const _P9_QTTMP: u8 = 0x04;
+const P9_QTSYMLINK: u8 = 0x02;
+const _P9_QTLINK: u8 = 0x01;
+const P9_QTFILE: u8 = 0x00;
+
+// Bitmask values for the getattr request.
+const _P9_GETATTR_MODE: u64 = 0x00000001;
+const _P9_GETATTR_NLINK: u64 = 0x00000002;
+const _P9_GETATTR_UID: u64 = 0x00000004;
+const _P9_GETATTR_GID: u64 = 0x00000008;
+const _P9_GETATTR_RDEV: u64 = 0x00000010;
+const _P9_GETATTR_ATIME: u64 = 0x00000020;
+const _P9_GETATTR_MTIME: u64 = 0x00000040;
+const _P9_GETATTR_CTIME: u64 = 0x00000080;
+const _P9_GETATTR_INO: u64 = 0x00000100;
+const _P9_GETATTR_SIZE: u64 = 0x00000200;
+const _P9_GETATTR_BLOCKS: u64 = 0x00000400;
+
+const _P9_GETATTR_BTIME: u64 = 0x00000800;
+const _P9_GETATTR_GEN: u64 = 0x00001000;
+const _P9_GETATTR_DATA_VERSION: u64 = 0x00002000;
+
+const P9_GETATTR_BASIC: u64 = 0x000007ff; /* Mask for fields up to BLOCKS */
+const _P9_GETATTR_ALL: u64 = 0x00003fff; /* Mask for All fields above */
+
+// Bitmask values for the setattr request.
+const P9_SETATTR_MODE: u32 = 0x00000001;
+const P9_SETATTR_UID: u32 = 0x00000002;
+const P9_SETATTR_GID: u32 = 0x00000004;
+const P9_SETATTR_SIZE: u32 = 0x00000008;
+const P9_SETATTR_ATIME: u32 = 0x00000010;
+const P9_SETATTR_MTIME: u32 = 0x00000020;
+const P9_SETATTR_CTIME: u32 = 0x00000040;
+const P9_SETATTR_ATIME_SET: u32 = 0x00000080;
+const P9_SETATTR_MTIME_SET: u32 = 0x00000100;
+
+// Minimum and maximum message size that we'll expect from the client.
+const MIN_MESSAGE_SIZE: u32 = 256;
+const MAX_MESSAGE_SIZE: u32 = ::std::u16::MAX as u32;
+
+#[derive(PartialEq, Eq)]
+enum FileType {
+ Regular,
+ Directory,
+ Other,
+}
+
+impl From<libc::mode_t> for FileType {
+ fn from(mode: libc::mode_t) -> Self {
+ match mode & libc::S_IFMT {
+ libc::S_IFREG => FileType::Regular,
+ libc::S_IFDIR => FileType::Directory,
+ _ => FileType::Other,
+ }
+ }
+}
+
+// Represents state that the server is holding on behalf of a client. Fids are somewhat like file
+// descriptors but are not restricted to open files and directories. Fids are identified by a unique
+// 32-bit number chosen by the client. Most messages sent by clients include a fid on which to
+// operate. The fid in a Tattach message represents the root of the file system tree that the client
+// is allowed to access. A client can create more fids by walking the directory tree from that fid.
+struct Fid {
+ path: File,
+ file: Option<File>,
+ filetype: FileType,
+}
+
+impl From<libc::stat64> for Qid {
+ fn from(st: libc::stat64) -> Qid {
+ let ty = match st.st_mode & libc::S_IFMT {
+ libc::S_IFDIR => P9_QTDIR,
+ libc::S_IFREG => P9_QTFILE,
+ libc::S_IFLNK => P9_QTSYMLINK,
+ _ => 0,
+ };
+
+ Qid {
+ ty,
+ // TODO: deal with the 2038 problem before 2038
+ version: st.st_mtime as u32,
+ path: st.st_ino,
+ }
+ }
+}
+
+fn statat(d: &File, name: &CStr, flags: libc::c_int) -> io::Result<libc::stat64> {
+ let mut st = MaybeUninit::<libc::stat64>::zeroed();
+
+ // Safe because the kernel will only write data in `st` and we check the return
+ // value.
+ let res = unsafe {
+ libc::fstatat64(
+ d.as_raw_fd(),
+ name.as_ptr(),
+ st.as_mut_ptr(),
+ flags | libc::AT_SYMLINK_NOFOLLOW,
+ )
+ };
+ if res >= 0 {
+ // Safe because the kernel guarantees that the struct is now fully initialized.
+ Ok(unsafe { st.assume_init() })
+ } else {
+ Err(io::Error::last_os_error())
+ }
+}
+
+fn stat(f: &File) -> io::Result<libc::stat64> {
+ // Safe because this is a constant value and a valid C string.
+ let pathname = unsafe { CStr::from_bytes_with_nul_unchecked(b"\0") };
+
+ statat(f, pathname, libc::AT_EMPTY_PATH)
+}
+
+fn string_to_cstring(s: String) -> io::Result<CString> {
+ CString::new(s).map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))
+}
+
+fn error_to_rmessage(err: io::Error) -> Rmessage {
+ let errno = if let Some(errno) = err.raw_os_error() {
+ errno
+ } else {
+ // Make a best-effort guess based on the kind.
+ match err.kind() {
+ io::ErrorKind::NotFound => libc::ENOENT,
+ io::ErrorKind::PermissionDenied => libc::EPERM,
+ io::ErrorKind::ConnectionRefused => libc::ECONNREFUSED,
+ io::ErrorKind::ConnectionReset => libc::ECONNRESET,
+ io::ErrorKind::ConnectionAborted => libc::ECONNABORTED,
+ io::ErrorKind::NotConnected => libc::ENOTCONN,
+ io::ErrorKind::AddrInUse => libc::EADDRINUSE,
+ io::ErrorKind::AddrNotAvailable => libc::EADDRNOTAVAIL,
+ io::ErrorKind::BrokenPipe => libc::EPIPE,
+ io::ErrorKind::AlreadyExists => libc::EEXIST,
+ io::ErrorKind::WouldBlock => libc::EWOULDBLOCK,
+ io::ErrorKind::InvalidInput => libc::EINVAL,
+ io::ErrorKind::InvalidData => libc::EINVAL,
+ io::ErrorKind::TimedOut => libc::ETIMEDOUT,
+ io::ErrorKind::WriteZero => libc::EIO,
+ io::ErrorKind::Interrupted => libc::EINTR,
+ io::ErrorKind::Other => libc::EIO,
+ io::ErrorKind::UnexpectedEof => libc::EIO,
+ _ => libc::EIO,
+ }
+ };
+
+ Rmessage::Lerror(Rlerror {
+ ecode: errno as u32,
+ })
+}
+
+// Sigh.. Cow requires the underlying type to implement Clone.
+enum MaybeOwned<'b, T> {
+ Borrowed(&'b T),
+ Owned(T),
+}
+
+impl<'a, T> Deref for MaybeOwned<'a, T> {
+ type Target = T;
+ fn deref(&self) -> &Self::Target {
+ use MaybeOwned::*;
+ match *self {
+ Borrowed(borrowed) => borrowed,
+ Owned(ref owned) => owned,
+ }
+ }
+}
+
+fn ebadf() -> io::Error {
+ io::Error::from_raw_os_error(libc::EBADF)
+}
+
+pub type ServerIdMap<T> = BTreeMap<T, T>;
+pub type ServerUidMap = ServerIdMap<libc::uid_t>;
+pub type ServerGidMap = ServerIdMap<libc::gid_t>;
+
+fn map_id_from_host<T: Clone + Ord>(map: &ServerIdMap<T>, id: T) -> T {
+ map.get(&id).map_or(id.clone(), |v| v.clone())
+}
+
+// Performs an ascii case insensitive lookup and returns an O_PATH fd for the entry, if found.
+fn ascii_casefold_lookup(proc: &File, parent: &File, name: &[u8]) -> io::Result<File> {
+ let mut dir = open_fid(proc, parent, P9_DIRECTORY)?;
+ let mut dirents = read_dir(&mut dir, 0)?;
+
+ while let Some(entry) = dirents.next().transpose()? {
+ if name.eq_ignore_ascii_case(entry.name.to_bytes()) {
+ return lookup(parent, entry.name);
+ }
+ }
+
+ Err(io::Error::from_raw_os_error(libc::ENOENT))
+}
+
+fn lookup(parent: &File, name: &CStr) -> io::Result<File> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let fd = syscall!(unsafe {
+ libc::openat(
+ parent.as_raw_fd(),
+ name.as_ptr(),
+ libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
+ )
+ })?;
+
+ // Safe because we just opened this fd.
+ Ok(unsafe { File::from_raw_fd(fd) })
+}
+
+fn do_walk(
+ proc: &File,
+ wnames: Vec<String>,
+ start: File,
+ ascii_casefold: bool,
+ mds: &mut Vec<libc::stat64>,
+) -> io::Result<File> {
+ let mut current = start;
+
+ for wname in wnames {
+ let name = string_to_cstring(wname)?;
+ current = lookup(&current, &name).or_else(|e| {
+ if ascii_casefold {
+ if let Some(libc::ENOENT) = e.raw_os_error() {
+ return ascii_casefold_lookup(proc, &current, name.to_bytes());
+ }
+ }
+
+ Err(e)
+ })?;
+ mds.push(stat(&current)?);
+ }
+
+ Ok(current)
+}
+
+fn open_fid(proc: &File, path: &File, p9_flags: u32) -> io::Result<File> {
+ let pathname = string_to_cstring(format!("self/fd/{}", path.as_raw_fd()))?;
+
+ // We always open files with O_CLOEXEC.
+ let mut flags: i32 = libc::O_CLOEXEC;
+ for &(p9f, of) in &MAPPED_FLAGS {
+ if (p9_flags & p9f) != 0 {
+ flags |= of;
+ }
+ }
+
+ if p9_flags & P9_NOACCESS == P9_RDONLY {
+ flags |= libc::O_RDONLY;
+ }
+
+ // Safe because this doesn't modify any memory and we check the return value. We need to
+ // clear the O_NOFOLLOW flag because we want to follow the proc symlink.
+ let fd = syscall!(unsafe {
+ libc::openat(
+ proc.as_raw_fd(),
+ pathname.as_ptr(),
+ flags & !libc::O_NOFOLLOW,
+ )
+ })?;
+
+ // Safe because we just opened this fd and we know it is valid.
+ Ok(unsafe { File::from_raw_fd(fd) })
+}
+
+#[derive(Clone)]
+pub struct Config {
+ pub root: Box<Path>,
+ pub msize: u32,
+
+ pub uid_map: ServerUidMap,
+ pub gid_map: ServerGidMap,
+
+ pub ascii_casefold: bool,
+}
+
+impl Default for Config {
+ fn default() -> Config {
+ Config {
+ root: Path::new("/").into(),
+ msize: MAX_MESSAGE_SIZE,
+ uid_map: Default::default(),
+ gid_map: Default::default(),
+ ascii_casefold: false,
+ }
+ }
+}
+pub struct Server {
+ fids: BTreeMap<u32, Fid>,
+ proc: File,
+ cfg: Config,
+}
+
+impl Server {
+ pub fn new<P: Into<Box<Path>>>(
+ root: P,
+ uid_map: ServerUidMap,
+ gid_map: ServerGidMap,
+ ) -> io::Result<Server> {
+ Server::with_config(Config {
+ root: root.into(),
+ msize: MAX_MESSAGE_SIZE,
+ uid_map,
+ gid_map,
+ ascii_casefold: false,
+ })
+ }
+
+ pub fn with_config(cfg: Config) -> io::Result<Server> {
+ // Safe because this is a valid c-string.
+ let proc_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(b"/proc\0") };
+
+ // Safe because this doesn't modify any memory and we check the return value.
+ let fd = syscall!(unsafe {
+ libc::openat(
+ libc::AT_FDCWD,
+ proc_cstr.as_ptr(),
+ libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
+ )
+ })?;
+
+ // Safe because we just opened this fd and we know it is valid.
+ let proc = unsafe { File::from_raw_fd(fd) };
+ Ok(Server {
+ fids: BTreeMap::new(),
+ proc,
+ cfg,
+ })
+ }
+
+ pub fn keep_fds(&self) -> Vec<RawFd> {
+ vec![self.proc.as_raw_fd()]
+ }
+
+ pub fn handle_message<R: Read, W: Write>(
+ &mut self,
+ reader: &mut R,
+ writer: &mut W,
+ ) -> io::Result<()> {
+ let Tframe { tag, msg } = WireFormat::decode(&mut reader.take(self.cfg.msize as u64))?;
+
+ let rmsg = match msg {
+ Tmessage::Version(ref version) => self.version(version).map(Rmessage::Version),
+ Tmessage::Flush(ref flush) => self.flush(flush).and(Ok(Rmessage::Flush)),
+ Tmessage::Walk(walk) => self.walk(walk).map(Rmessage::Walk),
+ Tmessage::Read(ref read) => self.read(read).map(Rmessage::Read),
+ Tmessage::Write(ref write) => self.write(write).map(Rmessage::Write),
+ Tmessage::Clunk(ref clunk) => self.clunk(clunk).and(Ok(Rmessage::Clunk)),
+ Tmessage::Remove(ref remove) => self.remove(remove).and(Ok(Rmessage::Remove)),
+ Tmessage::Attach(ref attach) => self.attach(attach).map(Rmessage::Attach),
+ Tmessage::Auth(ref auth) => self.auth(auth).map(Rmessage::Auth),
+ Tmessage::Statfs(ref statfs) => self.statfs(statfs).map(Rmessage::Statfs),
+ Tmessage::Lopen(ref lopen) => self.lopen(lopen).map(Rmessage::Lopen),
+ Tmessage::Lcreate(lcreate) => self.lcreate(lcreate).map(Rmessage::Lcreate),
+ Tmessage::Symlink(ref symlink) => self.symlink(symlink).map(Rmessage::Symlink),
+ Tmessage::Mknod(ref mknod) => self.mknod(mknod).map(Rmessage::Mknod),
+ Tmessage::Rename(ref rename) => self.rename(rename).and(Ok(Rmessage::Rename)),
+ Tmessage::Readlink(ref readlink) => self.readlink(readlink).map(Rmessage::Readlink),
+ Tmessage::GetAttr(ref get_attr) => self.get_attr(get_attr).map(Rmessage::GetAttr),
+ Tmessage::SetAttr(ref set_attr) => self.set_attr(set_attr).and(Ok(Rmessage::SetAttr)),
+ Tmessage::XattrWalk(ref xattr_walk) => {
+ self.xattr_walk(xattr_walk).map(Rmessage::XattrWalk)
+ }
+ Tmessage::XattrCreate(ref xattr_create) => self
+ .xattr_create(xattr_create)
+ .and(Ok(Rmessage::XattrCreate)),
+ Tmessage::Readdir(ref readdir) => self.readdir(readdir).map(Rmessage::Readdir),
+ Tmessage::Fsync(ref fsync) => self.fsync(fsync).and(Ok(Rmessage::Fsync)),
+ Tmessage::Lock(ref lock) => self.lock(lock).map(Rmessage::Lock),
+ Tmessage::GetLock(ref get_lock) => self.get_lock(get_lock).map(Rmessage::GetLock),
+ Tmessage::Link(link) => self.link(link).and(Ok(Rmessage::Link)),
+ Tmessage::Mkdir(mkdir) => self.mkdir(mkdir).map(Rmessage::Mkdir),
+ Tmessage::RenameAt(rename_at) => self.rename_at(rename_at).and(Ok(Rmessage::RenameAt)),
+ Tmessage::UnlinkAt(unlink_at) => self.unlink_at(unlink_at).and(Ok(Rmessage::UnlinkAt)),
+ };
+
+ // Errors while handling requests are never fatal.
+ let response = Rframe {
+ tag,
+ msg: rmsg.unwrap_or_else(error_to_rmessage),
+ };
+
+ response.encode(writer)?;
+ writer.flush()
+ }
+
+ fn auth(&mut self, _auth: &Tauth) -> io::Result<Rauth> {
+ // Returning an error for the auth message means that the server does not require
+ // authentication.
+ Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+ }
+
+ fn attach(&mut self, attach: &Tattach) -> io::Result<Rattach> {
+ // TODO: Check attach parameters
+ match self.fids.entry(attach.fid) {
+ btree_map::Entry::Vacant(entry) => {
+ let root = CString::new(self.cfg.root.as_os_str().as_bytes())
+ .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
+
+ // Safe because this doesn't modify any memory and we check the return value.
+ let fd = syscall!(unsafe {
+ libc::openat(
+ libc::AT_FDCWD,
+ root.as_ptr(),
+ libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
+ )
+ })?;
+
+ let root_path = unsafe { File::from_raw_fd(fd) };
+ let st = stat(&root_path)?;
+
+ let fid = Fid {
+ // Safe because we just opened this fd.
+ path: root_path,
+ file: None,
+ filetype: st.st_mode.into(),
+ };
+ let response = Rattach { qid: st.into() };
+ entry.insert(fid);
+ Ok(response)
+ }
+ btree_map::Entry::Occupied(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
+ }
+ }
+
+ fn version(&mut self, version: &Tversion) -> io::Result<Rversion> {
+ if version.msize < MIN_MESSAGE_SIZE {
+ return Err(io::Error::from_raw_os_error(libc::EINVAL));
+ }
+
+ // A Tversion request clunks all open fids and terminates any pending I/O.
+ self.fids.clear();
+ self.cfg.msize = min(self.cfg.msize, version.msize);
+
+ Ok(Rversion {
+ msize: self.cfg.msize,
+ version: if version.version == "9P2000.L" {
+ String::from("9P2000.L")
+ } else {
+ String::from("unknown")
+ },
+ })
+ }
+
+ #[allow(clippy::unnecessary_wraps)]
+ fn flush(&mut self, _flush: &Tflush) -> io::Result<()> {
+ // TODO: Since everything is synchronous we can't actually flush requests.
+ Ok(())
+ }
+
+ fn walk(&mut self, walk: Twalk) -> io::Result<Rwalk> {
+ // `newfid` must not currently be in use unless it is the same as `fid`.
+ if walk.fid != walk.newfid && self.fids.contains_key(&walk.newfid) {
+ return Err(io::Error::from_raw_os_error(libc::EBADF));
+ }
+
+ // We need to walk the tree. First get the starting path.
+ let start = self
+ .fids
+ .get(&walk.fid)
+ .ok_or_else(ebadf)
+ .and_then(|fid| fid.path.try_clone())?;
+
+ // Now walk the tree and break on the first error, if any.
+ let expected_len = walk.wnames.len();
+ let mut mds = Vec::with_capacity(expected_len);
+ match do_walk(
+ &self.proc,
+ walk.wnames,
+ start,
+ self.cfg.ascii_casefold,
+ &mut mds,
+ ) {
+ Ok(end) => {
+ // Store the new fid if the full walk succeeded.
+ if mds.len() == expected_len {
+ let st = mds.last().copied().map(Ok).unwrap_or_else(|| stat(&end))?;
+ self.fids.insert(
+ walk.newfid,
+ Fid {
+ path: end,
+ file: None,
+ filetype: st.st_mode.into(),
+ },
+ );
+ }
+ }
+ Err(e) => {
+ // Only return an error if it occurred on the first component.
+ if mds.is_empty() {
+ return Err(e);
+ }
+ }
+ }
+
+ Ok(Rwalk {
+ wqids: mds.into_iter().map(Qid::from).collect(),
+ })
+ }
+
+ fn read(&mut self, read: &Tread) -> io::Result<Rread> {
+ // Thankfully, `read` cannot be used to read directories in 9P2000.L.
+ let file = self
+ .fids
+ .get_mut(&read.fid)
+ .and_then(|fid| fid.file.as_mut())
+ .ok_or_else(ebadf)?;
+
+ // Use an empty Rread struct to figure out the overhead of the header.
+ let header_size = Rframe {
+ tag: 0,
+ msg: Rmessage::Read(Rread {
+ data: Data(Vec::new()),
+ }),
+ }
+ .byte_size();
+
+ let capacity = min(self.cfg.msize - header_size, read.count);
+ let mut buf = Data(vec![0u8; capacity as usize]);
+
+ let count = file.read_at(&mut buf, read.offset)?;
+ buf.truncate(count);
+
+ Ok(Rread { data: buf })
+ }
+
+ fn write(&mut self, write: &Twrite) -> io::Result<Rwrite> {
+ let file = self
+ .fids
+ .get_mut(&write.fid)
+ .and_then(|fid| fid.file.as_mut())
+ .ok_or_else(ebadf)?;
+
+ let count = file.write_at(&write.data, write.offset)?;
+ Ok(Rwrite {
+ count: count as u32,
+ })
+ }
+
+ fn clunk(&mut self, clunk: &Tclunk) -> io::Result<()> {
+ match self.fids.entry(clunk.fid) {
+ btree_map::Entry::Vacant(_) => Err(io::Error::from_raw_os_error(libc::EBADF)),
+ btree_map::Entry::Occupied(entry) => {
+ entry.remove();
+ Ok(())
+ }
+ }
+ }
+
+ fn remove(&mut self, _remove: &Tremove) -> io::Result<()> {
+ // Since a file could be linked into multiple locations, there is no way to know exactly
+ // which path we are supposed to unlink. Linux uses unlink_at anyway, so we can just return
+ // an error here.
+ Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+ }
+
+ fn statfs(&mut self, statfs: &Tstatfs) -> io::Result<Rstatfs> {
+ let fid = self.fids.get(&statfs.fid).ok_or_else(ebadf)?;
+ let mut buf = MaybeUninit::zeroed();
+
+ // Safe because this will only modify `out` and we check the return value.
+ syscall!(unsafe { libc::fstatfs64(fid.path.as_raw_fd(), buf.as_mut_ptr()) })?;
+
+ // Safe because this only has integer types and any value is valid.
+ let out = unsafe { buf.assume_init() };
+ Ok(Rstatfs {
+ ty: out.f_type as u32,
+ bsize: out.f_bsize as u32,
+ blocks: out.f_blocks,
+ bfree: out.f_bfree,
+ bavail: out.f_bavail,
+ files: out.f_files,
+ ffree: out.f_ffree,
+ // Safe because the fsid has only integer fields and the compiler will verify that is
+ // the same width as the `fsid` field in Rstatfs.
+ fsid: unsafe { mem::transmute(out.f_fsid) },
+ namelen: out.f_namelen as u32,
+ })
+ }
+
+ fn lopen(&mut self, lopen: &Tlopen) -> io::Result<Rlopen> {
+ let fid = self.fids.get_mut(&lopen.fid).ok_or_else(ebadf)?;
+
+ let file = open_fid(&self.proc, &fid.path, lopen.flags)?;
+ let st = stat(&file)?;
+
+ fid.file = Some(file);
+ let iounit = st.st_blksize as u32;
+ Ok(Rlopen {
+ qid: st.into(),
+ iounit,
+ })
+ }
+
+ fn lcreate(&mut self, lcreate: Tlcreate) -> io::Result<Rlcreate> {
+ let fid = self.fids.get_mut(&lcreate.fid).ok_or_else(ebadf)?;
+
+ if fid.filetype != FileType::Directory {
+ return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
+ }
+
+ let mut flags: i32 = libc::O_CLOEXEC | libc::O_CREAT | libc::O_EXCL;
+ for &(p9f, of) in &MAPPED_FLAGS {
+ if (lcreate.flags & p9f) != 0 {
+ flags |= of;
+ }
+ }
+ if lcreate.flags & P9_NOACCESS == P9_RDONLY {
+ flags |= libc::O_RDONLY;
+ }
+
+ let name = string_to_cstring(lcreate.name)?;
+
+ // Safe because this doesn't modify any memory and we check the return value.
+ let fd = syscall!(unsafe {
+ libc::openat(fid.path.as_raw_fd(), name.as_ptr(), flags, lcreate.mode)
+ })?;
+
+ // Safe because we just opened this fd and we know it is valid.
+ let file = unsafe { File::from_raw_fd(fd) };
+ let st = stat(&file)?;
+ let iounit = st.st_blksize as u32;
+
+ fid.file = Some(file);
+
+ // This fid now refers to the newly created file so we need to update the O_PATH fd for it
+ // as well.
+ fid.path = lookup(&fid.path, &name)?;
+
+ Ok(Rlcreate {
+ qid: st.into(),
+ iounit,
+ })
+ }
+
+ fn symlink(&mut self, _symlink: &Tsymlink) -> io::Result<Rsymlink> {
+ // symlinks are not allowed.
+ Err(io::Error::from_raw_os_error(libc::EACCES))
+ }
+
+ fn mknod(&mut self, _mknod: &Tmknod) -> io::Result<Rmknod> {
+ // No nodes either.
+ Err(io::Error::from_raw_os_error(libc::EACCES))
+ }
+
+ fn rename(&mut self, _rename: &Trename) -> io::Result<()> {
+ // We cannot support this as an inode may be linked into multiple directories but we don't
+ // know which one the client wants us to rename. Linux uses rename_at anyway, so we don't
+ // need to worry about this.
+ Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+ }
+
+ fn readlink(&mut self, _readlink: &Treadlink) -> io::Result<Rreadlink> {
+ // symlinks are not allowed
+ Err(io::Error::from_raw_os_error(libc::EACCES))
+ }
+
+ fn get_attr(&mut self, get_attr: &Tgetattr) -> io::Result<Rgetattr> {
+ let fid = self.fids.get_mut(&get_attr.fid).ok_or_else(ebadf)?;
+
+ let st = stat(&fid.path)?;
+
+ Ok(Rgetattr {
+ valid: P9_GETATTR_BASIC,
+ qid: st.into(),
+ mode: st.st_mode,
+ uid: map_id_from_host(&self.cfg.uid_map, st.st_uid),
+ gid: map_id_from_host(&self.cfg.gid_map, st.st_gid),
+ nlink: st.st_nlink as u64,
+ rdev: st.st_rdev,
+ size: st.st_size as u64,
+ blksize: st.st_blksize as u64,
+ blocks: st.st_blocks as u64,
+ atime_sec: st.st_atime as u64,
+ atime_nsec: st.st_atime_nsec as u64,
+ mtime_sec: st.st_mtime as u64,
+ mtime_nsec: st.st_mtime_nsec as u64,
+ ctime_sec: st.st_ctime as u64,
+ ctime_nsec: st.st_ctime_nsec as u64,
+ btime_sec: 0,
+ btime_nsec: 0,
+ gen: 0,
+ data_version: 0,
+ })
+ }
+
+ fn set_attr(&mut self, set_attr: &Tsetattr) -> io::Result<()> {
+ let fid = self.fids.get(&set_attr.fid).ok_or_else(ebadf)?;
+
+ let file = if let Some(ref file) = fid.file {
+ MaybeOwned::Borrowed(file)
+ } else {
+ let flags = match fid.filetype {
+ FileType::Regular => P9_RDWR,
+ FileType::Directory => P9_RDONLY | P9_DIRECTORY,
+ FileType::Other => P9_RDWR,
+ };
+ MaybeOwned::Owned(open_fid(&self.proc, &fid.path, P9_NONBLOCK | flags)?)
+ };
+
+ if set_attr.valid & P9_SETATTR_MODE != 0 {
+ // Safe because this doesn't modify any memory and we check the return value.
+ syscall!(unsafe { libc::fchmod(file.as_raw_fd(), set_attr.mode) })?;
+ }
+
+ if set_attr.valid & (P9_SETATTR_UID | P9_SETATTR_GID) != 0 {
+ let uid = if set_attr.valid & P9_SETATTR_UID != 0 {
+ set_attr.uid
+ } else {
+ -1i32 as u32
+ };
+ let gid = if set_attr.valid & P9_SETATTR_GID != 0 {
+ set_attr.gid
+ } else {
+ -1i32 as u32
+ };
+
+ // Safe because this doesn't modify any memory and we check the return value.
+ syscall!(unsafe { libc::fchown(file.as_raw_fd(), uid, gid) })?;
+ }
+
+ if set_attr.valid & P9_SETATTR_SIZE != 0 {
+ file.set_len(set_attr.size)?;
+ }
+
+ if set_attr.valid & (P9_SETATTR_ATIME | P9_SETATTR_MTIME) != 0 {
+ let times = [
+ libc::timespec {
+ tv_sec: set_attr.atime_sec as _,
+ tv_nsec: if set_attr.valid & P9_SETATTR_ATIME == 0 {
+ libc::UTIME_OMIT
+ } else if set_attr.valid & P9_SETATTR_ATIME_SET == 0 {
+ libc::UTIME_NOW
+ } else {
+ set_attr.atime_nsec as _
+ },
+ },
+ libc::timespec {
+ tv_sec: set_attr.mtime_sec as _,
+ tv_nsec: if set_attr.valid & P9_SETATTR_MTIME == 0 {
+ libc::UTIME_OMIT
+ } else if set_attr.valid & P9_SETATTR_MTIME_SET == 0 {
+ libc::UTIME_NOW
+ } else {
+ set_attr.mtime_nsec as _
+ },
+ },
+ ];
+
+ // Safe because file is valid and we have initialized times fully.
+ let ret = unsafe { libc::futimens(file.as_raw_fd(), &times as *const libc::timespec) };
+ if ret < 0 {
+ return Err(io::Error::last_os_error());
+ }
+ }
+
+ // The ctime would have been updated by any of the above operations so we only
+ // need to change it if it was the only option given.
+ if set_attr.valid & P9_SETATTR_CTIME != 0 && set_attr.valid & (!P9_SETATTR_CTIME) == 0 {
+ // Setting -1 as the uid and gid will not actually change anything but will
+ // still update the ctime.
+ let ret = unsafe {
+ libc::fchown(
+ file.as_raw_fd(),
+ libc::uid_t::max_value(),
+ libc::gid_t::max_value(),
+ )
+ };
+ if ret < 0 {
+ return Err(io::Error::last_os_error());
+ }
+ }
+
+ Ok(())
+ }
+
+ fn xattr_walk(&mut self, _xattr_walk: &Txattrwalk) -> io::Result<Rxattrwalk> {
+ Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+ }
+
+ fn xattr_create(&mut self, _xattr_create: &Txattrcreate) -> io::Result<()> {
+ Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+ }
+
+ fn readdir(&mut self, readdir: &Treaddir) -> io::Result<Rreaddir> {
+ let fid = self.fids.get_mut(&readdir.fid).ok_or_else(ebadf)?;
+
+ if fid.filetype != FileType::Directory {
+ return Err(io::Error::from_raw_os_error(libc::ENOTDIR));
+ }
+
+ // Use an empty Rreaddir struct to figure out the maximum number of bytes that
+ // can be returned.
+ let header_size = Rframe {
+ tag: 0,
+ msg: Rmessage::Readdir(Rreaddir {
+ data: Data(Vec::new()),
+ }),
+ }
+ .byte_size();
+ let count = min(self.cfg.msize - header_size, readdir.count);
+ let mut cursor = Cursor::new(Vec::with_capacity(count as usize));
+
+ let dir = fid.file.as_mut().ok_or_else(ebadf)?;
+ let mut dirents = read_dir(dir, readdir.offset as libc::off64_t)?;
+ while let Some(dirent) = dirents.next().transpose()? {
+ let st = statat(&fid.path, dirent.name, 0)?;
+
+ let name = dirent
+ .name
+ .to_str()
+ .map(String::from)
+ .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
+
+ let entry = Dirent {
+ qid: st.into(),
+ offset: dirent.offset,
+ ty: dirent.type_,
+ name,
+ };
+
+ let byte_size = entry.byte_size() as usize;
+
+ if cursor.get_ref().capacity() - cursor.get_ref().len() < byte_size {
+ // No more room in the buffer.
+ break;
+ }
+
+ entry.encode(&mut cursor)?;
+ }
+
+ Ok(Rreaddir {
+ data: Data(cursor.into_inner()),
+ })
+ }
+
+ fn fsync(&mut self, fsync: &Tfsync) -> io::Result<()> {
+ let file = self
+ .fids
+ .get(&fsync.fid)
+ .and_then(|fid| fid.file.as_ref())
+ .ok_or_else(ebadf)?;
+
+ if fsync.datasync == 0 {
+ file.sync_all()?;
+ } else {
+ file.sync_data()?;
+ }
+ Ok(())
+ }
+
+ fn lock(&mut self, _lock: &Tlock) -> io::Result<Rlock> {
+ // File locking is not supported.
+ Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+ }
+ fn get_lock(&mut self, _get_lock: &Tgetlock) -> io::Result<Rgetlock> {
+ // File locking is not supported.
+ Err(io::Error::from_raw_os_error(libc::EOPNOTSUPP))
+ }
+
+ fn link(&mut self, link: Tlink) -> io::Result<()> {
+ let target = self.fids.get(&link.fid).ok_or_else(ebadf)?;
+ let path = string_to_cstring(format!("self/fd/{}", target.path.as_raw_fd()))?;
+
+ let dir = self.fids.get(&link.dfid).ok_or_else(ebadf)?;
+ let name = string_to_cstring(link.name)?;
+
+ // Safe because this doesn't modify any memory and we check the return value.
+ syscall!(unsafe {
+ libc::linkat(
+ self.proc.as_raw_fd(),
+ path.as_ptr(),
+ dir.path.as_raw_fd(),
+ name.as_ptr(),
+ libc::AT_SYMLINK_FOLLOW,
+ )
+ })?;
+ Ok(())
+ }
+
+ fn mkdir(&mut self, mkdir: Tmkdir) -> io::Result<Rmkdir> {
+ let fid = self.fids.get(&mkdir.dfid).ok_or_else(ebadf)?;
+ let name = string_to_cstring(mkdir.name)?;
+
+ // Safe because this doesn't modify any memory and we check the return value.
+ syscall!(unsafe { libc::mkdirat(fid.path.as_raw_fd(), name.as_ptr(), mkdir.mode) })?;
+ Ok(Rmkdir {
+ qid: statat(&fid.path, &name, 0).map(Qid::from)?,
+ })
+ }
+
+ fn rename_at(&mut self, rename_at: Trenameat) -> io::Result<()> {
+ let olddir = self.fids.get(&rename_at.olddirfid).ok_or_else(ebadf)?;
+ let oldname = string_to_cstring(rename_at.oldname)?;
+
+ let newdir = self.fids.get(&rename_at.newdirfid).ok_or_else(ebadf)?;
+ let newname = string_to_cstring(rename_at.newname)?;
+
+ // Safe because this doesn't modify any memory and we check the return value.
+ syscall!(unsafe {
+ libc::renameat(
+ olddir.path.as_raw_fd(),
+ oldname.as_ptr(),
+ newdir.path.as_raw_fd(),
+ newname.as_ptr(),
+ )
+ })?;
+
+ Ok(())
+ }
+
+ fn unlink_at(&mut self, unlink_at: Tunlinkat) -> io::Result<()> {
+ let dir = self.fids.get(&unlink_at.dirfd).ok_or_else(ebadf)?;
+ let name = string_to_cstring(unlink_at.name)?;
+
+ syscall!(unsafe {
+ libc::unlinkat(
+ dir.path.as_raw_fd(),
+ name.as_ptr(),
+ unlink_at.flags as libc::c_int,
+ )
+ })?;
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests;
diff --git a/common/p9/src/server/tests.rs b/common/p9/src/server/tests.rs
new file mode 100644
index 000000000..c9107063b
--- /dev/null
+++ b/common/p9/src/server/tests.rs
@@ -0,0 +1,1101 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::*;
+
+use std::borrow::Cow;
+use std::collections::{HashSet, VecDeque};
+use std::env;
+use std::ffi::{CString, OsString};
+use std::fs::{self, File};
+use std::io::{self, Cursor};
+use std::mem;
+use std::ops::Deref;
+use std::os::unix::ffi::OsStringExt;
+use std::os::unix::fs::MetadataExt;
+use std::path::{Component, Path, PathBuf};
+use std::u32;
+
+// Used to indicate that there is no fid associated with this message.
+const P9_NOFID: u32 = u32::MAX;
+
+// The fid associated with the root directory of the server.
+const ROOT_FID: u32 = 1;
+
+// How big we want the default buffer to be when running tests.
+const DEFAULT_BUFFER_SIZE: u32 = 4096;
+
+// Joins `path` to `buf`. If `path` is '..', removes the last component from `buf`
+// only if `buf` != `root` but does nothing if `buf` == `root`. Pushes `path` onto
+// `buf` if it is a normal path component.
+//
+// Returns an error if `path` is absolute, has more than one component, or contains
+// a '.' component.
+fn join_path<P: AsRef<Path>, R: AsRef<Path>>(
+ mut buf: PathBuf,
+ path: P,
+ root: R,
+) -> io::Result<PathBuf> {
+ let path = path.as_ref();
+ let root = root.as_ref();
+ debug_assert!(buf.starts_with(root));
+
+ if path.components().count() > 1 {
+ return Err(io::Error::from_raw_os_error(libc::EINVAL));
+ }
+
+ for component in path.components() {
+ match component {
+ // Prefix should only appear on windows systems.
+ Component::Prefix(_) => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
+ // Absolute paths are not allowed.
+ Component::RootDir => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
+ // '.' elements are not allowed.
+ Component::CurDir => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
+ Component::ParentDir => {
+ // We only remove the parent path if we are not already at the root of the
+ // file system.
+ if buf != root {
+ buf.pop();
+ }
+ }
+ Component::Normal(element) => buf.push(element),
+ }
+ }
+
+ Ok(buf)
+}
+
+// Automatically deletes the path it contains when it goes out of scope.
+struct ScopedPath<P: AsRef<Path>>(P);
+
+impl<P: AsRef<Path>> AsRef<Path> for ScopedPath<P> {
+ fn as_ref(&self) -> &Path {
+ self.0.as_ref()
+ }
+}
+
+impl<P: AsRef<Path>> Deref for ScopedPath<P> {
+ type Target = Path;
+
+ fn deref(&self) -> &Self::Target {
+ self.0.as_ref()
+ }
+}
+
+impl<P: AsRef<Path>> Drop for ScopedPath<P> {
+ fn drop(&mut self) {
+ if let Err(e) = fs::remove_dir_all(&**self) {
+ println!("Failed to remove {}: {}", self.display(), e);
+ }
+ }
+}
+
+enum DirEntry<'a> {
+ File {
+ name: &'a str,
+ content: &'a [u8],
+ },
+ Directory {
+ name: &'a str,
+ entries: &'a [DirEntry<'a>],
+ },
+}
+
+impl<'a> DirEntry<'a> {
+ // Creates `self` in the path given by `dir`.
+ // TODO(b/228627457): clippy is warning about the `Cow` below, but it is necessary
+ #[allow(clippy::ptr_arg)]
+ fn create(&self, dir: &mut Cow<Path>) {
+ match *self {
+ DirEntry::File { name, content } => {
+ let mut f = File::create(dir.join(name)).expect("failed to create file");
+ f.write_all(content).expect("failed to write file content");
+ }
+ DirEntry::Directory { name, entries } => {
+ dir.to_mut().push(name);
+
+ fs::create_dir_all(&**dir).expect("failed to create directory");
+ for e in entries {
+ e.create(dir);
+ }
+
+ assert!(dir.to_mut().pop());
+ }
+ }
+ }
+}
+
+// Creates a file with `name` in `dir` and fills it with random
+// content.
+fn create_local_file<P: AsRef<Path>>(dir: P, name: &str) -> Vec<u8> {
+ let mut content = Vec::new();
+ File::open("/dev/urandom")
+ .and_then(|f| f.take(200).read_to_end(&mut content))
+ .expect("failed to read from /dev/urandom");
+
+ let f = DirEntry::File {
+ name,
+ content: &content,
+ };
+ f.create(&mut Cow::from(dir.as_ref()));
+
+ content
+}
+
+fn check_qid(qid: &Qid, md: &fs::Metadata) {
+ let ty = if md.is_dir() {
+ P9_QTDIR
+ } else if md.is_file() {
+ P9_QTFILE
+ } else if md.file_type().is_symlink() {
+ P9_QTSYMLINK
+ } else {
+ panic!("unknown file type: {:?}", md.file_type());
+ };
+ assert_eq!(qid.ty, ty);
+ assert_eq!(qid.version, md.mtime() as u32);
+ assert_eq!(qid.path, md.ino());
+}
+
+fn check_attr(server: &mut Server, fid: u32, md: &fs::Metadata) {
+ let tgetattr = Tgetattr {
+ fid,
+ request_mask: P9_GETATTR_BASIC,
+ };
+
+ let rgetattr = server.get_attr(&tgetattr).expect("failed to call get_attr");
+
+ let ty = if md.is_dir() {
+ P9_QTDIR
+ } else if md.is_file() {
+ P9_QTFILE
+ } else if md.file_type().is_symlink() {
+ P9_QTSYMLINK
+ } else {
+ panic!("unknown file type: {:?}", md.file_type());
+ };
+ assert_eq!(rgetattr.valid, P9_GETATTR_BASIC);
+ assert_eq!(rgetattr.qid.ty, ty);
+ assert_eq!(rgetattr.qid.version, md.mtime() as u32);
+ assert_eq!(rgetattr.qid.path, md.ino());
+ assert_eq!(rgetattr.mode, md.mode());
+ assert_eq!(rgetattr.uid, md.uid());
+ assert_eq!(rgetattr.gid, md.gid());
+ assert_eq!(rgetattr.nlink, md.nlink());
+ assert_eq!(rgetattr.rdev, md.rdev());
+ assert_eq!(rgetattr.size, md.size());
+ assert_eq!(rgetattr.atime_sec, md.atime() as u64);
+ assert_eq!(rgetattr.atime_nsec, md.atime_nsec() as u64);
+ assert_eq!(rgetattr.mtime_sec, md.mtime() as u64);
+ assert_eq!(rgetattr.mtime_nsec, md.mtime_nsec() as u64);
+ assert_eq!(rgetattr.ctime_sec, md.ctime() as u64);
+ assert_eq!(rgetattr.ctime_nsec, md.ctime_nsec() as u64);
+ assert_eq!(rgetattr.btime_sec, 0);
+ assert_eq!(rgetattr.btime_nsec, 0);
+ assert_eq!(rgetattr.gen, 0);
+ assert_eq!(rgetattr.data_version, 0);
+}
+
+fn check_content(server: &mut Server, content: &[u8], fid: u32) {
+ for offset in 0..content.len() {
+ let tread = Tread {
+ fid,
+ offset: offset as u64,
+ count: DEFAULT_BUFFER_SIZE,
+ };
+
+ let rread = server.read(&tread).expect("failed to read file");
+ assert_eq!(content[offset..], rread.data[..]);
+ }
+}
+
+fn walk<P: Into<PathBuf>>(
+ server: &mut Server,
+ start: P,
+ fid: u32,
+ newfid: u32,
+ names: Vec<String>,
+) {
+ let mut mds = Vec::with_capacity(names.len());
+ let mut buf = start.into();
+ for name in &names {
+ buf.push(name);
+ mds.push(
+ buf.symlink_metadata()
+ .expect("failed to get metadata for path"),
+ );
+ }
+
+ let twalk = Twalk {
+ fid,
+ newfid,
+ wnames: names,
+ };
+
+ let rwalk = server.walk(twalk).expect("failed to walk directoy");
+ assert_eq!(mds.len(), rwalk.wqids.len());
+ for (md, qid) in mds.iter().zip(rwalk.wqids.iter()) {
+ check_qid(qid, md);
+ }
+}
+
+fn open<P: Into<PathBuf>>(
+ server: &mut Server,
+ dir: P,
+ dir_fid: u32,
+ name: &str,
+ fid: u32,
+ flags: u32,
+) -> io::Result<Rlopen> {
+ let wnames = if name.is_empty() {
+ vec![]
+ } else {
+ vec![String::from(name)]
+ };
+ walk(server, dir, dir_fid, fid, wnames);
+
+ let tlopen = Tlopen { fid, flags };
+
+ server.lopen(&tlopen)
+}
+
+fn write<P: AsRef<Path>>(server: &mut Server, dir: P, name: &str, fid: u32, flags: u32) {
+ let file_path = dir.as_ref().join(name);
+ let file_len = if file_path.exists() {
+ fs::symlink_metadata(&file_path)
+ .expect("unable to get metadata for file")
+ .len() as usize
+ } else {
+ 0usize
+ };
+ let mut new_content = Vec::new();
+ File::open("/dev/urandom")
+ .and_then(|f| f.take(200).read_to_end(&mut new_content))
+ .expect("failed to read from /dev/urandom");
+
+ let twrite = Twrite {
+ fid,
+ offset: 0,
+ data: Data(new_content),
+ };
+
+ let rwrite = server.write(&twrite).expect("failed to write file");
+ assert_eq!(rwrite.count, twrite.data.len() as u32);
+
+ let tfsync = Tfsync { fid, datasync: 0 };
+ server.fsync(&tfsync).expect("failed to sync file contents");
+
+ let actual_content = fs::read(file_path).expect("failed to read back content from file");
+
+ // If the file was opened append-only, then the content should have been
+ // written to the end even though the offset was 0.
+ let idx = if flags & P9_APPEND == 0 { 0 } else { file_len };
+ assert_eq!(actual_content[idx..], twrite.data[..]);
+}
+
+fn create<P: Into<PathBuf>>(
+ server: &mut Server,
+ dir: P,
+ dir_fid: u32,
+ fid: u32,
+ name: &str,
+ flags: u32,
+ mode: u32,
+) -> io::Result<Rlcreate> {
+ // The `fid` in the lcreate call initially points to the directory
+ // but is supposed to point to the newly created file after the call
+ // completes. Duplicate the fid so that we don't end up consuming the
+ // directory fid.
+ walk(server, dir, dir_fid, fid, Vec::new());
+
+ let tlcreate = Tlcreate {
+ fid,
+ name: String::from(name),
+ flags,
+ mode,
+ gid: 0,
+ };
+
+ server.lcreate(tlcreate)
+}
+
+struct Readdir<'a> {
+ server: &'a mut Server,
+ fid: u32,
+ offset: u64,
+ cursor: Cursor<Vec<u8>>,
+}
+
+impl<'a> Iterator for Readdir<'a> {
+ type Item = Dirent;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.cursor.position() >= self.cursor.get_ref().len() as u64 {
+ let treaddir = Treaddir {
+ fid: self.fid,
+ offset: self.offset,
+ count: DEFAULT_BUFFER_SIZE,
+ };
+
+ let Rreaddir { data } = self
+ .server
+ .readdir(&treaddir)
+ .expect("failed to read directory");
+ if data.is_empty() {
+ // No more entries.
+ return None;
+ }
+
+ mem::drop(mem::replace(&mut self.cursor, Cursor::new(data.0)));
+ }
+
+ let dirent: Dirent = WireFormat::decode(&mut self.cursor).expect("failed to decode dirent");
+ self.offset = dirent.offset;
+
+ Some(dirent)
+ }
+}
+
+fn readdir(server: &mut Server, fid: u32) -> Readdir {
+ Readdir {
+ server,
+ fid,
+ offset: 0,
+ cursor: Cursor::new(Vec::new()),
+ }
+}
+
+// Sets up the server to start handling messages. Creates a new temporary
+// directory to act as the server root and sends an initial Tattach message.
+// At the end of setup, fid 1 points to the root of the server.
+fn setup<P: AsRef<Path>>(name: P) -> (ScopedPath<OsString>, Server) {
+ let mut test_dir = env::var_os("T")
+ .map(PathBuf::from)
+ .unwrap_or_else(env::temp_dir);
+ test_dir.push(name);
+
+ let mut os_str = OsString::from(test_dir);
+ os_str.push(".XXXXXX");
+
+ // Create a c string and release ownership. This seems like the only way
+ // to get a *mut c_char.
+ let buf = CString::new(os_str.into_vec())
+ .expect("failed to create CString")
+ .into_raw();
+
+ // Safe because this will only modify the contents of `buf`.
+ let ret = unsafe { libc::mkdtemp(buf) };
+
+ // Take ownership of the buffer back before checking the result. Safe because
+ // this was created by a call to into_raw() above and mkdtemp will not overwrite
+ // the trailing '\0'.
+ let buf = unsafe { CString::from_raw(buf) };
+
+ assert!(!ret.is_null());
+
+ let test_dir = ScopedPath(OsString::from_vec(buf.into_bytes()));
+
+ // Create a basic file system hierarchy.
+ let entries = [
+ DirEntry::Directory {
+ name: "subdir",
+ entries: &[
+ DirEntry::File {
+ name: "b",
+ content: b"hello, world!",
+ },
+ DirEntry::Directory {
+ name: "nested",
+ entries: &[DirEntry::File {
+ name: "Огонь по готовности!",
+ content: &[
+ 0xe9u8, 0xbeu8, 0x8du8, 0xe3u8, 0x81u8, 0x8cu8, 0xe6u8, 0x88u8, 0x91u8,
+ 0xe3u8, 0x81u8, 0x8cu8, 0xe6u8, 0x95u8, 0xb5u8, 0xe3u8, 0x82u8, 0x92u8,
+ 0xe5u8, 0x96u8, 0xb0u8, 0xe3u8, 0x82u8, 0x89u8, 0xe3u8, 0x81u8, 0x86u8,
+ 0x21u8,
+ ],
+ }],
+ },
+ ],
+ },
+ DirEntry::File {
+ name: "世界.txt",
+ content: &[
+ 0xe3u8, 0x81u8, 0x93u8, 0xe3u8, 0x82u8, 0x93u8, 0xe3u8, 0x81u8, 0xabu8, 0xe3u8,
+ 0x81u8, 0xa1u8, 0xe3u8, 0x81u8, 0xafu8,
+ ],
+ },
+ ];
+
+ for e in &entries {
+ e.create(&mut Cow::from(&*test_dir));
+ }
+
+ let md = test_dir
+ .symlink_metadata()
+ .expect("failed to get metadata for root dir");
+
+ let mut server = Server::new(&*test_dir, Default::default(), Default::default())
+ .expect("Failed to create server");
+
+ let tversion = Tversion {
+ msize: DEFAULT_BUFFER_SIZE,
+ version: String::from("9P2000.L"),
+ };
+
+ let rversion = server
+ .version(&tversion)
+ .expect("failed to get version from server");
+ assert_eq!(rversion.msize, DEFAULT_BUFFER_SIZE);
+ assert_eq!(rversion.version, "9P2000.L");
+
+ let tattach = Tattach {
+ fid: ROOT_FID,
+ afid: P9_NOFID,
+ uname: String::from("unittest"),
+ aname: String::from(""),
+ n_uname: 1000,
+ };
+
+ let rattach = server.attach(&tattach).expect("failed to attach to server");
+ check_qid(&rattach.qid, &md);
+
+ (test_dir, server)
+}
+
+#[test]
+fn path_joins() {
+ let root = PathBuf::from("/a/b/c");
+ let path = PathBuf::from("/a/b/c/d/e/f");
+
+ assert_eq!(
+ &join_path(path.clone(), "nested", &root).expect("normal"),
+ Path::new("/a/b/c/d/e/f/nested")
+ );
+
+ let p1 = join_path(path, "..", &root).expect("parent 1");
+ assert_eq!(&p1, Path::new("/a/b/c/d/e/"));
+
+ let p2 = join_path(p1, "..", &root).expect("parent 2");
+ assert_eq!(&p2, Path::new("/a/b/c/d/"));
+
+ let p3 = join_path(p2, "..", &root).expect("parent 3");
+ assert_eq!(&p3, Path::new("/a/b/c/"));
+
+ let p4 = join_path(p3, "..", &root).expect("parent of root");
+ assert_eq!(&p4, Path::new("/a/b/c/"));
+}
+
+#[test]
+fn invalid_joins() {
+ let root = PathBuf::from("/a");
+ let path = PathBuf::from("/a/b");
+
+ join_path(path.clone(), ".", &root).expect_err("current directory");
+ join_path(path.clone(), "c/d/e", &root).expect_err("too many components");
+ join_path(path, "/c/d/e", &root).expect_err("absolute path");
+}
+
+#[test]
+fn clunk() {
+ let (_test_dir, mut server) = setup("clunk");
+
+ let tclunk = Tclunk { fid: ROOT_FID };
+ server.clunk(&tclunk).expect("failed to clunk root fid");
+}
+
+#[test]
+fn get_attr() {
+ let (test_dir, mut server) = setup("get_attr");
+
+ let md = test_dir
+ .symlink_metadata()
+ .expect("failed to get metadata for test dir");
+
+ check_attr(&mut server, ROOT_FID, &md);
+}
+
+#[test]
+fn tree_walk() {
+ let (test_dir, mut server) = setup("readdir");
+
+ let mut next_fid = ROOT_FID + 1;
+
+ let mut dirs = VecDeque::new();
+ dirs.push_back(test_dir.to_path_buf());
+
+ while let Some(dir) = dirs.pop_front() {
+ let dfid = next_fid;
+ next_fid += 1;
+
+ let wnames: Vec<String> = dir
+ .strip_prefix(&test_dir)
+ .expect("test directory is not prefix of subdir")
+ .components()
+ .map(|c| Path::new(&c).to_string_lossy().to_string())
+ .collect();
+ walk(&mut server, &*test_dir, ROOT_FID, dfid, wnames);
+
+ let md = dir.symlink_metadata().expect("failed to get metadata");
+
+ check_attr(&mut server, dfid, &md);
+
+ let fid = next_fid;
+ next_fid += 1;
+ open(&mut server, &dir, dfid, "", fid, P9_DIRECTORY).expect("Failed to open directory");
+ for dirent in readdir(&mut server, fid) {
+ if dirent.name == "." || dirent.name == ".." {
+ continue;
+ }
+
+ let entry_path = dir.join(&dirent.name);
+ assert!(
+ entry_path.exists(),
+ "directory entry \"{}\" does not exist",
+ entry_path.display()
+ );
+ let md = fs::symlink_metadata(&entry_path).expect("failed to get metadata for entry");
+
+ let ty = if md.is_dir() {
+ dirs.push_back(dir.join(dirent.name));
+ libc::DT_DIR
+ } else if md.is_file() {
+ libc::DT_REG
+ } else if md.file_type().is_symlink() {
+ libc::DT_LNK
+ } else {
+ panic!("unknown file type: {:?}", md.file_type());
+ };
+
+ assert_eq!(dirent.ty, ty);
+ check_qid(&dirent.qid, &md);
+ }
+
+ let tclunk = Tclunk { fid };
+ server.clunk(&tclunk).expect("failed to clunk fid");
+ }
+}
+
+#[test]
+fn create_existing_file() {
+ let (test_dir, mut server) = setup("create_existing");
+
+ let name = "existing";
+ create_local_file(&test_dir, name);
+
+ let fid = ROOT_FID + 1;
+ create(
+ &mut server,
+ &*test_dir,
+ ROOT_FID,
+ fid,
+ name,
+ P9_APPEND,
+ 0o644,
+ )
+ .expect_err("successfully created existing file");
+}
+
+enum SetAttrKind {
+ File,
+ Directory,
+}
+
+fn set_attr_test<F>(kind: SetAttrKind, set_fields: F) -> io::Result<fs::Metadata>
+where
+ F: FnOnce(&mut Tsetattr),
+{
+ let (test_dir, mut server) = setup("set_attr");
+
+ let name = "existing";
+ match kind {
+ SetAttrKind::File => {
+ create_local_file(&test_dir, name);
+ }
+ SetAttrKind::Directory => {
+ let tmkdir = Tmkdir {
+ dfid: ROOT_FID,
+ name: String::from(name),
+ mode: 0o755,
+ gid: 0,
+ };
+
+ let rmkdir = server.mkdir(tmkdir).expect("failed to create directory");
+ let md = fs::symlink_metadata(test_dir.join(name))
+ .expect("failed to get metadata for directory");
+
+ assert!(md.is_dir());
+ check_qid(&rmkdir.qid, &md);
+ }
+ };
+
+ let fid = ROOT_FID + 1;
+ walk(
+ &mut server,
+ &*test_dir,
+ ROOT_FID,
+ fid,
+ vec![String::from(name)],
+ );
+
+ let mut tsetattr = Tsetattr {
+ fid,
+ valid: 0,
+ mode: 0,
+ uid: 0,
+ gid: 0,
+ size: 0,
+ atime_sec: 0,
+ atime_nsec: 0,
+ mtime_sec: 0,
+ mtime_nsec: 0,
+ };
+
+ set_fields(&mut tsetattr);
+ server.set_attr(&tsetattr)?;
+
+ fs::symlink_metadata(test_dir.join(name))
+}
+
+#[test]
+fn set_len() {
+ let len = 661;
+ let md = set_attr_test(SetAttrKind::File, |tsetattr| {
+ tsetattr.valid = P9_SETATTR_SIZE;
+ tsetattr.size = len;
+ })
+ .expect("failed to run set length of file");
+
+ assert_eq!(md.size(), len);
+}
+
+#[test]
+fn set_file_mode() {
+ let mode = 0o640;
+ let md = set_attr_test(SetAttrKind::File, |tsetattr| {
+ tsetattr.valid = P9_SETATTR_MODE;
+ tsetattr.mode = mode;
+ })
+ .expect("failed to set mode");
+
+ assert_eq!(md.mode() & 0o777, mode);
+}
+
+#[test]
+fn set_file_mtime() {
+ let (secs, nanos) = (1245247825, 524617);
+ let md = set_attr_test(SetAttrKind::File, |tsetattr| {
+ tsetattr.valid = P9_SETATTR_MTIME | P9_SETATTR_MTIME_SET;
+ tsetattr.mtime_sec = secs;
+ tsetattr.mtime_nsec = nanos;
+ })
+ .expect("failed to set mtime");
+
+ assert_eq!(md.mtime() as u64, secs);
+ assert_eq!(md.mtime_nsec() as u64, nanos);
+}
+
+#[test]
+fn set_file_atime() {
+ let (secs, nanos) = (9247605, 4016);
+ let md = set_attr_test(SetAttrKind::File, |tsetattr| {
+ tsetattr.valid = P9_SETATTR_ATIME | P9_SETATTR_ATIME_SET;
+ tsetattr.atime_sec = secs;
+ tsetattr.atime_nsec = nanos;
+ })
+ .expect("failed to set atime");
+
+ assert_eq!(md.atime() as u64, secs);
+ assert_eq!(md.atime_nsec() as u64, nanos);
+}
+
+#[test]
+fn set_dir_mode() {
+ let mode = 0o640;
+ let md = set_attr_test(SetAttrKind::Directory, |tsetattr| {
+ tsetattr.valid = P9_SETATTR_MODE;
+ tsetattr.mode = mode;
+ })
+ .expect("failed to set mode");
+
+ assert_eq!(md.mode() & 0o777, mode);
+}
+
+#[test]
+fn set_dir_mtime() {
+ let (secs, nanos) = (1245247825, 524617);
+ let md = set_attr_test(SetAttrKind::Directory, |tsetattr| {
+ tsetattr.valid = P9_SETATTR_MTIME | P9_SETATTR_MTIME_SET;
+ tsetattr.mtime_sec = secs;
+ tsetattr.mtime_nsec = nanos;
+ })
+ .expect("failed to set mtime");
+
+ assert_eq!(md.mtime() as u64, secs);
+ assert_eq!(md.mtime_nsec() as u64, nanos);
+}
+
+#[test]
+fn set_dir_atime() {
+ let (secs, nanos) = (9247605, 4016);
+ let md = set_attr_test(SetAttrKind::Directory, |tsetattr| {
+ tsetattr.valid = P9_SETATTR_ATIME | P9_SETATTR_ATIME_SET;
+ tsetattr.atime_sec = secs;
+ tsetattr.atime_nsec = nanos;
+ })
+ .expect("failed to set atime");
+
+ assert_eq!(md.atime() as u64, secs);
+ assert_eq!(md.atime_nsec() as u64, nanos);
+}
+
+#[test]
+fn huge_directory() {
+ let (test_dir, mut server) = setup("huge_directory");
+
+ let name = "newdir";
+ let newdir = test_dir.join(name);
+ fs::create_dir(&newdir).expect("failed to create directory");
+
+ let dfid = ROOT_FID + 1;
+ walk(
+ &mut server,
+ &*test_dir,
+ ROOT_FID,
+ dfid,
+ vec![String::from(name)],
+ );
+
+ // Create ~4K files in the directory and then attempt to read them all.
+ let mut filenames = HashSet::with_capacity(4096);
+ for i in 0..4096 {
+ let name = format!("file_{}", i);
+ create_local_file(&newdir, &name);
+ assert!(filenames.insert(name));
+ }
+
+ let fid = dfid + 1;
+ open(&mut server, &newdir, dfid, "", fid, P9_DIRECTORY).expect("Failed to open directory");
+ for f in readdir(&mut server, fid) {
+ let path = newdir.join(&f.name);
+
+ let md = fs::symlink_metadata(path).expect("failed to get metadata for path");
+ check_qid(&f.qid, &md);
+
+ if f.name == "." || f.name == ".." {
+ assert_eq!(f.ty, libc::DT_DIR);
+ } else {
+ assert_eq!(f.ty, libc::DT_REG);
+ assert!(filenames.remove(&f.name));
+ }
+ }
+
+ assert!(filenames.is_empty());
+}
+
+#[test]
+fn mkdir() {
+ let (test_dir, mut server) = setup("mkdir");
+
+ let name = "conan";
+ let tmkdir = Tmkdir {
+ dfid: ROOT_FID,
+ name: String::from(name),
+ mode: 0o755,
+ gid: 0,
+ };
+
+ let rmkdir = server.mkdir(tmkdir).expect("failed to create directory");
+ let md =
+ fs::symlink_metadata(test_dir.join(name)).expect("failed to get metadata for directory");
+
+ assert!(md.is_dir());
+ check_qid(&rmkdir.qid, &md);
+}
+
+#[test]
+fn unlink_all() {
+ let (test_dir, mut server) = setup("readdir");
+
+ let mut next_fid = ROOT_FID + 1;
+
+ let mut dirs = VecDeque::new();
+ dirs.push_back((ROOT_FID, test_dir.to_path_buf()));
+
+ // First iterate over the whole directory.
+ let mut unlinks = VecDeque::new();
+ while let Some((dfid, dir)) = dirs.pop_front() {
+ let mut names = VecDeque::new();
+ for entry in fs::read_dir(dir).expect("failed to read directory") {
+ let entry = entry.expect("unable to iterate over directory");
+ let ft = entry
+ .file_type()
+ .expect("failed to get file type for entry");
+ if ft.is_dir() {
+ let fid = next_fid;
+ next_fid += 1;
+
+ let wnames: Vec<String> = entry
+ .path()
+ .strip_prefix(&test_dir)
+ .expect("test directory is not prefix of subdir")
+ .components()
+ .map(|c| Path::new(&c).to_string_lossy().to_string())
+ .collect();
+ walk(&mut server, &*test_dir, ROOT_FID, fid, wnames);
+ dirs.push_back((fid, entry.path()));
+ }
+
+ names.push_back((
+ entry
+ .file_name()
+ .into_string()
+ .expect("failed to convert entry name to string"),
+ if ft.is_dir() {
+ libc::AT_REMOVEDIR as u32
+ } else {
+ 0
+ },
+ ));
+ }
+
+ unlinks.push_back((dfid, names));
+ }
+
+ // Now remove everything in reverse order.
+ while let Some((dfid, names)) = unlinks.pop_back() {
+ for (name, flags) in names {
+ let tunlinkat = Tunlinkat {
+ dirfd: dfid,
+ name,
+ flags,
+ };
+
+ server.unlink_at(tunlinkat).expect("failed to unlink path");
+ }
+ }
+}
+
+#[test]
+fn rename_at() {
+ let (test_dir, mut server) = setup("rename");
+
+ let name = "oldfile";
+ let content = create_local_file(&test_dir, name);
+
+ let newname = "newfile";
+ let trename = Trenameat {
+ olddirfid: ROOT_FID,
+ oldname: String::from(name),
+ newdirfid: ROOT_FID,
+ newname: String::from(newname),
+ };
+
+ server.rename_at(trename).expect("failed to rename file");
+
+ assert!(!test_dir.join(name).exists());
+
+ let mut newcontent = Vec::with_capacity(content.len());
+ let size = File::open(test_dir.join(newname))
+ .expect("failed to open file")
+ .read_to_end(&mut newcontent)
+ .expect("failed to read new file content");
+ assert_eq!(size, content.len());
+ assert_eq!(newcontent, content);
+}
+
+macro_rules! open_test {
+ ($name:ident, $flags:expr) => {
+ #[test]
+ fn $name() {
+ let (test_dir, mut server) = setup("open");
+
+ let fid = ROOT_FID + 1;
+ let name = "test.txt";
+ let content = create_local_file(&test_dir, name);
+
+ let rlopen = open(&mut server, &*test_dir, ROOT_FID, name, fid, $flags as u32)
+ .expect("failed to open file");
+
+ let md =
+ fs::symlink_metadata(test_dir.join(name)).expect("failed to get metadata for file");
+ check_qid(&rlopen.qid, &md);
+ assert_eq!(rlopen.iounit, md.blksize() as u32);
+
+ check_attr(&mut server, fid, &md);
+
+ // Check that the file has the proper contents as long as we didn't
+ // truncate it first.
+ if $flags & P9_TRUNC == 0 && $flags & P9_WRONLY == 0 {
+ check_content(&mut server, &content, fid);
+ }
+
+ // Check that we can write to the file.
+ if $flags & P9_RDWR != 0 || $flags & P9_WRONLY != 0 {
+ write(&mut server, &test_dir, name, fid, $flags);
+ }
+
+ let tclunk = Tclunk { fid };
+ server.clunk(&tclunk).expect("Unable to clunk file");
+ }
+ };
+ ($name:ident, $flags:expr, $expected_err:expr) => {
+ #[test]
+ fn $name() {
+ let (test_dir, mut server) = setup("open_fail");
+
+ let fid = ROOT_FID + 1;
+ let name = "test.txt";
+ create_local_file(&test_dir, name);
+
+ let err = open(&mut server, &*test_dir, ROOT_FID, name, fid, $flags as u32)
+ .expect_err("successfully opened file");
+ assert_eq!(err.kind(), $expected_err);
+
+ let tclunk = Tclunk { fid };
+ server.clunk(&tclunk).expect("Unable to clunk file");
+ }
+ };
+}
+
+open_test!(read_only_file_open, P9_RDONLY);
+open_test!(read_write_file_open, P9_RDWR);
+open_test!(write_only_file_open, P9_WRONLY);
+
+open_test!(create_read_only_file_open, P9_CREATE | P9_RDONLY);
+open_test!(create_read_write_file_open, P9_CREATE | P9_RDWR);
+open_test!(create_write_only_file_open, P9_CREATE | P9_WRONLY);
+
+open_test!(append_read_only_file_open, P9_APPEND | P9_RDONLY);
+open_test!(append_read_write_file_open, P9_APPEND | P9_RDWR);
+open_test!(append_write_only_file_open, P9_APPEND | P9_WRONLY);
+
+open_test!(trunc_read_only_file_open, P9_TRUNC | P9_RDONLY);
+open_test!(trunc_read_write_file_open, P9_TRUNC | P9_RDWR);
+open_test!(trunc_write_only_file_open, P9_TRUNC | P9_WRONLY);
+
+open_test!(
+ create_append_read_only_file_open,
+ P9_CREATE | P9_APPEND | P9_RDONLY
+);
+open_test!(
+ create_append_read_write_file_open,
+ P9_CREATE | P9_APPEND | P9_RDWR
+);
+open_test!(
+ create_append_wronly_file_open,
+ P9_CREATE | P9_APPEND | P9_WRONLY
+);
+
+open_test!(
+ create_trunc_read_only_file_open,
+ P9_CREATE | P9_TRUNC | P9_RDONLY
+);
+open_test!(
+ create_trunc_read_write_file_open,
+ P9_CREATE | P9_TRUNC | P9_RDWR
+);
+open_test!(
+ create_trunc_wronly_file_open,
+ P9_CREATE | P9_TRUNC | P9_WRONLY
+);
+
+open_test!(
+ append_trunc_read_only_file_open,
+ P9_APPEND | P9_TRUNC | P9_RDONLY
+);
+open_test!(
+ append_trunc_read_write_file_open,
+ P9_APPEND | P9_TRUNC | P9_RDWR
+);
+open_test!(
+ append_trunc_wronly_file_open,
+ P9_APPEND | P9_TRUNC | P9_WRONLY
+);
+
+open_test!(
+ create_append_trunc_read_only_file_open,
+ P9_CREATE | P9_APPEND | P9_TRUNC | P9_RDONLY
+);
+open_test!(
+ create_append_trunc_read_write_file_open,
+ P9_CREATE | P9_APPEND | P9_TRUNC | P9_RDWR
+);
+open_test!(
+ create_append_trunc_wronly_file_open,
+ P9_CREATE | P9_APPEND | P9_TRUNC | P9_WRONLY
+);
+
+open_test!(
+ create_excl_read_only_file_open,
+ P9_CREATE | P9_EXCL | P9_RDONLY,
+ io::ErrorKind::AlreadyExists
+);
+open_test!(
+ create_excl_read_write_file_open,
+ P9_CREATE | P9_EXCL | P9_RDWR,
+ io::ErrorKind::AlreadyExists
+);
+open_test!(
+ create_excl_wronly_file_open,
+ P9_CREATE | P9_EXCL | P9_WRONLY,
+ io::ErrorKind::AlreadyExists
+);
+
+macro_rules! create_test {
+ ($name:ident, $flags:expr, $mode:expr) => {
+ #[test]
+ fn $name() {
+ let (test_dir, mut server) = setup("create");
+
+ let name = "foo.txt";
+ let fid = ROOT_FID + 1;
+ let rlcreate = create(&mut server, &*test_dir, ROOT_FID, fid, name, $flags, $mode)
+ .expect("failed to create file");
+
+ let md =
+ fs::symlink_metadata(test_dir.join(name)).expect("failed to get metadata for file");
+ assert_eq!(rlcreate.iounit, md.blksize() as u32);
+ check_qid(&rlcreate.qid, &md);
+ check_attr(&mut server, fid, &md);
+
+ // Check that we can write to the file.
+ if $flags & P9_RDWR != 0 || $flags & P9_WRONLY != 0 {
+ write(&mut server, &test_dir, name, fid, $flags);
+ }
+
+ let tclunk = Tclunk { fid };
+ server.clunk(&tclunk).expect("Unable to clunk file");
+ }
+ };
+ ($name:ident, $flags:expr, $mode:expr, $expected_err:expr) => {
+ #[test]
+ fn $name() {
+ let (test_dir, mut server) = setup("create_fail");
+
+ let name = "foo.txt";
+ // The `fid` in the lcreate call initially points to the directory
+ // but is supposed to point to the newly created file after the call
+ // completes. Duplicate the fid so that we don't end up consuming the
+ // root fid.
+ let fid = ROOT_FID + 1;
+ let err = create(&mut server, &*test_dir, ROOT_FID, fid, name, $flags, $mode)
+ .expect_err("successfully created file");
+ assert_eq!(err.kind(), $expected_err);
+ }
+ };
+}
+
+create_test!(read_only_file_create, P9_RDONLY, 0o600u32);
+create_test!(read_write_file_create, P9_RDWR, 0o600u32);
+create_test!(write_only_file_create, P9_WRONLY, 0o600u32);
+
+create_test!(
+ append_read_only_file_create,
+ P9_APPEND | P9_RDONLY,
+ 0o600u32
+);
+create_test!(append_read_write_file_create, P9_APPEND | P9_RDWR, 0o600u32);
+create_test!(append_wronly_file_create, P9_APPEND | P9_WRONLY, 0o600u32);
diff --git a/common/p9/wire_format_derive/Android.bp b/common/p9/wire_format_derive/Android.bp
new file mode 100644
index 000000000..0c6767cf2
--- /dev/null
+++ b/common/p9/wire_format_derive/Android.bp
@@ -0,0 +1,26 @@
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_crosvm_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-BSD
+ default_applicable_licenses: ["external_crosvm_license"],
+}
+
+rust_proc_macro {
+ name: "libwire_format_derive",
+ defaults: ["crosvm_defaults"],
+ crate_name: "wire_format_derive",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
+ srcs: ["wire_format_derive.rs"],
+ edition: "2015",
+ rustlibs: [
+ "libproc_macro2",
+ "libquote",
+ "libsyn",
+ ],
+}
diff --git a/enumn/Cargo.toml b/common/p9/wire_format_derive/Cargo.toml
index 381c3d700..52ab6a4c6 100644
--- a/enumn/Cargo.toml
+++ b/common/p9/wire_format_derive/Cargo.toml
@@ -1,13 +1,13 @@
[package]
-name = "enumn"
+name = "wire_format_derive"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
-
-[lib]
-proc-macro = true
[dependencies]
-proc-macro2 = "^1"
-quote = "^1"
syn = "^1"
+quote = "^1"
+proc-macro2 = "^1"
+
+[lib]
+proc-macro = true
+path = "wire_format_derive.rs"
diff --git a/common/p9/wire_format_derive/cargo2android.json b/common/p9/wire_format_derive/cargo2android.json
new file mode 100644
index 000000000..9a6eee39c
--- /dev/null
+++ b/common/p9/wire_format_derive/cargo2android.json
@@ -0,0 +1,7 @@
+{
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "run": true,
+ "tests": false
+} \ No newline at end of file
diff --git a/common/p9/wire_format_derive/wire_format_derive.rs b/common/p9/wire_format_derive/wire_format_derive.rs
new file mode 100644
index 000000000..290ffc5f1
--- /dev/null
+++ b/common/p9/wire_format_derive/wire_format_derive.rs
@@ -0,0 +1,303 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Derives a 9P wire format encoding for a struct by recursively calling
+//! `WireFormat::encode` or `WireFormat::decode` on the fields of the struct.
+//! This is only intended to be used from within the `p9` crate.
+
+#![recursion_limit = "256"]
+
+extern crate proc_macro;
+extern crate proc_macro2;
+
+#[macro_use]
+extern crate quote;
+
+#[macro_use]
+extern crate syn;
+
+use proc_macro2::{Span, TokenStream};
+use syn::spanned::Spanned;
+use syn::{Data, DeriveInput, Fields, Ident};
+
+/// The function that derives the actual implementation.
+#[proc_macro_derive(P9WireFormat)]
+pub fn p9_wire_format(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ p9_wire_format_inner(input).into()
+}
+
+fn p9_wire_format_inner(input: DeriveInput) -> TokenStream {
+ if !input.generics.params.is_empty() {
+ return quote! {
+ compile_error!("derive(P9WireFormat) does not support generic parameters");
+ };
+ }
+
+ let container = input.ident;
+
+ let byte_size_impl = byte_size_sum(&input.data);
+ let encode_impl = encode_wire_format(&input.data);
+ let decode_impl = decode_wire_format(&input.data, &container);
+
+ let scope = format!("wire_format_{}", container).to_lowercase();
+ let scope = Ident::new(&scope, Span::call_site());
+ quote! {
+ mod #scope {
+ extern crate std;
+ use self::std::io;
+ use self::std::result::Result::Ok;
+
+ use super::#container;
+
+ use protocol::WireFormat;
+
+ impl WireFormat for #container {
+ fn byte_size(&self) -> u32 {
+ #byte_size_impl
+ }
+
+ fn encode<W: io::Write>(&self, _writer: &mut W) -> io::Result<()> {
+ #encode_impl
+ }
+
+ fn decode<R: io::Read>(_reader: &mut R) -> io::Result<Self> {
+ #decode_impl
+ }
+ }
+ }
+ }
+}
+
+// Generate code that recursively calls byte_size on every field in the struct.
+fn byte_size_sum(data: &Data) -> TokenStream {
+ if let Data::Struct(ref data) = *data {
+ if let Fields::Named(ref fields) = data.fields {
+ let fields = fields.named.iter().map(|f| {
+ let field = &f.ident;
+ let span = field.span();
+ quote_spanned! {span=>
+ WireFormat::byte_size(&self.#field)
+ }
+ });
+
+ quote! {
+ 0 #(+ #fields)*
+ }
+ } else {
+ unimplemented!();
+ }
+ } else {
+ unimplemented!();
+ }
+}
+
+// Generate code that recursively calls encode on every field in the struct.
+fn encode_wire_format(data: &Data) -> TokenStream {
+ if let Data::Struct(ref data) = *data {
+ if let Fields::Named(ref fields) = data.fields {
+ let fields = fields.named.iter().map(|f| {
+ let field = &f.ident;
+ let span = field.span();
+ quote_spanned! {span=>
+ WireFormat::encode(&self.#field, _writer)?;
+ }
+ });
+
+ quote! {
+ #(#fields)*
+
+ Ok(())
+ }
+ } else {
+ unimplemented!();
+ }
+ } else {
+ unimplemented!();
+ }
+}
+
+// Generate code that recursively calls decode on every field in the struct.
+fn decode_wire_format(data: &Data, container: &Ident) -> TokenStream {
+ if let Data::Struct(ref data) = *data {
+ if let Fields::Named(ref fields) = data.fields {
+ let values = fields.named.iter().map(|f| {
+ let field = &f.ident;
+ let span = field.span();
+ quote_spanned! {span=>
+ let #field = WireFormat::decode(_reader)?;
+ }
+ });
+
+ let members = fields.named.iter().map(|f| {
+ let field = &f.ident;
+ quote! {
+ #field: #field,
+ }
+ });
+
+ quote! {
+ #(#values)*
+
+ Ok(#container {
+ #(#members)*
+ })
+ }
+ } else {
+ unimplemented!();
+ }
+ } else {
+ unimplemented!();
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn byte_size() {
+ let input: DeriveInput = parse_quote! {
+ struct Item {
+ ident: u32,
+ with_underscores: String,
+ other: u8,
+ }
+ };
+
+ let expected = quote! {
+ 0
+ + WireFormat::byte_size(&self.ident)
+ + WireFormat::byte_size(&self.with_underscores)
+ + WireFormat::byte_size(&self.other)
+ };
+
+ assert_eq!(byte_size_sum(&input.data).to_string(), expected.to_string());
+ }
+
+ #[test]
+ fn encode() {
+ let input: DeriveInput = parse_quote! {
+ struct Item {
+ ident: u32,
+ with_underscores: String,
+ other: u8,
+ }
+ };
+
+ let expected = quote! {
+ WireFormat::encode(&self.ident, _writer)?;
+ WireFormat::encode(&self.with_underscores, _writer)?;
+ WireFormat::encode(&self.other, _writer)?;
+ Ok(())
+ };
+
+ assert_eq!(
+ encode_wire_format(&input.data).to_string(),
+ expected.to_string(),
+ );
+ }
+
+ #[test]
+ fn decode() {
+ let input: DeriveInput = parse_quote! {
+ struct Item {
+ ident: u32,
+ with_underscores: String,
+ other: u8,
+ }
+ };
+
+ let container = Ident::new("Item", Span::call_site());
+ let expected = quote! {
+ let ident = WireFormat::decode(_reader)?;
+ let with_underscores = WireFormat::decode(_reader)?;
+ let other = WireFormat::decode(_reader)?;
+ Ok(Item {
+ ident: ident,
+ with_underscores: with_underscores,
+ other: other,
+ })
+ };
+
+ assert_eq!(
+ decode_wire_format(&input.data, &container).to_string(),
+ expected.to_string(),
+ );
+ }
+
+ #[test]
+ fn end_to_end() {
+ let input: DeriveInput = parse_quote! {
+ struct Niijima_先輩 {
+ a: u8,
+ b: u16,
+ c: u32,
+ d: u64,
+ e: String,
+ f: Vec<String>,
+ g: Nested,
+ }
+ };
+
+ let expected = quote! {
+ mod wire_format_niijima_先輩 {
+ extern crate std;
+ use self::std::io;
+ use self::std::result::Result::Ok;
+
+ use super::Niijima_先輩;
+
+ use protocol::WireFormat;
+
+ impl WireFormat for Niijima_先輩 {
+ fn byte_size(&self) -> u32 {
+ 0
+ + WireFormat::byte_size(&self.a)
+ + WireFormat::byte_size(&self.b)
+ + WireFormat::byte_size(&self.c)
+ + WireFormat::byte_size(&self.d)
+ + WireFormat::byte_size(&self.e)
+ + WireFormat::byte_size(&self.f)
+ + WireFormat::byte_size(&self.g)
+ }
+
+ fn encode<W: io::Write>(&self, _writer: &mut W) -> io::Result<()> {
+ WireFormat::encode(&self.a, _writer)?;
+ WireFormat::encode(&self.b, _writer)?;
+ WireFormat::encode(&self.c, _writer)?;
+ WireFormat::encode(&self.d, _writer)?;
+ WireFormat::encode(&self.e, _writer)?;
+ WireFormat::encode(&self.f, _writer)?;
+ WireFormat::encode(&self.g, _writer)?;
+ Ok(())
+ }
+ fn decode<R: io::Read>(_reader: &mut R) -> io::Result<Self> {
+ let a = WireFormat::decode(_reader)?;
+ let b = WireFormat::decode(_reader)?;
+ let c = WireFormat::decode(_reader)?;
+ let d = WireFormat::decode(_reader)?;
+ let e = WireFormat::decode(_reader)?;
+ let f = WireFormat::decode(_reader)?;
+ let g = WireFormat::decode(_reader)?;
+ Ok(Niijima_先輩 {
+ a: a,
+ b: b,
+ c: c,
+ d: d,
+ e: e,
+ f: f,
+ g: g,
+ })
+ }
+ }
+ }
+ };
+
+ assert_eq!(
+ p9_wire_format_inner(input).to_string(),
+ expected.to_string(),
+ );
+ }
+}
diff --git a/sync/Android.bp b/common/sync/Android.bp
index 176f78e18..c6aff6655 100644
--- a/sync/Android.bp
+++ b/common/sync/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -16,29 +16,8 @@ rust_library {
stem: "libsync",
host_supported: true,
crate_name: "sync",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
-}
-
-rust_defaults {
- name: "sync_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "sync",
- srcs: ["src/lib.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
-}
-
-rust_test_host {
- name: "sync_host_test_src_lib",
- defaults: ["sync_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "sync_device_test_src_lib",
- defaults: ["sync_defaults"],
+ edition: "2021",
}
diff --git a/sync/Cargo.toml b/common/sync/Cargo.toml
index af38fd15b..953d41b47 100644
--- a/sync/Cargo.toml
+++ b/common/sync/Cargo.toml
@@ -2,7 +2,7 @@
name = "sync"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
include = ["src/**/*", "Cargo.toml"]
[workspace]
diff --git a/sync/src/condvar.rs b/common/sync/src/condvar.rs
index 6d5d2b224..508a04d2c 100644
--- a/sync/src/condvar.rs
+++ b/common/sync/src/condvar.rs
@@ -6,6 +6,8 @@ use std::fmt::{self, Debug};
use std::sync::{Condvar as StdCondvar, MutexGuard, WaitTimeoutResult};
use std::time::Duration;
+static CONDVAR_POISONED: &str = "condvar is poisoned";
+
/// A Condition Variable.
#[derive(Default)]
pub struct Condvar {
@@ -22,10 +24,18 @@ impl Condvar {
/// Waits on a condvar, blocking the current thread until it is notified.
pub fn wait<'a, T>(&self, guard: MutexGuard<'a, T>) -> MutexGuard<'a, T> {
- match self.std.wait(guard) {
- Ok(guard) => guard,
- Err(_) => panic!("condvar is poisoned"),
- }
+ self.std.wait(guard).expect(CONDVAR_POISONED)
+ }
+
+ /// Blocks the current thread until this condition variable receives a notification and the
+ /// provided condition is false.
+ pub fn wait_while<'a, T, F>(&self, guard: MutexGuard<'a, T>, condition: F) -> MutexGuard<'a, T>
+ where
+ F: FnMut(&mut T) -> bool,
+ {
+ self.std
+ .wait_while(guard, condition)
+ .expect(CONDVAR_POISONED)
}
/// Waits on a condvar, blocking the current thread until it is notified
@@ -35,10 +45,22 @@ impl Condvar {
guard: MutexGuard<'a, T>,
dur: Duration,
) -> (MutexGuard<'a, T>, WaitTimeoutResult) {
- match self.std.wait_timeout(guard, dur) {
- Ok(result) => result,
- Err(_) => panic!("condvar is poisoned"),
- }
+ self.std.wait_timeout(guard, dur).expect(CONDVAR_POISONED)
+ }
+
+ /// Waits on this condition variable for a notification, timing out after a specified duration.
+ pub fn wait_timeout_while<'a, T, F>(
+ &self,
+ guard: MutexGuard<'a, T>,
+ dur: Duration,
+ condition: F,
+ ) -> (MutexGuard<'a, T>, WaitTimeoutResult)
+ where
+ F: FnMut(&mut T) -> bool,
+ {
+ self.std
+ .wait_timeout_while(guard, dur, condition)
+ .expect(CONDVAR_POISONED)
}
/// Notifies one thread blocked by this condvar.
diff --git a/sync/src/lib.rs b/common/sync/src/lib.rs
index 5a1e88318..5a1e88318 100644
--- a/sync/src/lib.rs
+++ b/common/sync/src/lib.rs
diff --git a/sync/src/mutex.rs b/common/sync/src/mutex.rs
index c479a2e56..c479a2e56 100644
--- a/sync/src/mutex.rs
+++ b/common/sync/src/mutex.rs
diff --git a/common/sys_util/Android.bp b/common/sys_util/Android.bp
new file mode 100644
index 000000000..cb5355f92
--- /dev/null
+++ b/common/sys_util/Android.bp
@@ -0,0 +1,57 @@
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_crosvm_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-BSD
+ default_applicable_licenses: ["external_crosvm_license"],
+}
+
+rust_library {
+ name: "libsys_util",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "sys_util",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ rustlibs: [
+ "libdata_model",
+ "liblibc",
+ "libserde",
+ "libserde_json",
+ "libsync_rust",
+ "libsys_util_core",
+ "libtempfile",
+ "libthiserror",
+ ],
+ proc_macros: [
+ "libpoll_token_derive",
+ "libremain",
+ ],
+ shared_libs: ["libcap"], // specified in src/capabilities.rs
+ target: {
+ android: {
+ rustlibs: [
+ "libandroid_log_sys",
+ ],
+ },
+ linux_bionic_arm64: {
+ // For ARM architecture, we use aarch64-linux-android for BOTH
+ // device and host targets. As a result, host targets are also
+ // built with target_os = "android". Therefore, sys_util/src/android
+ // is used and thus this android module is required.
+ // This seems incorrect, but is inevitable because rustc doesn't
+ // yet support a Linux-based target using Bionic as libc. We can't
+ // use aarch64-unknown-linux-gnu because it's using glibc which
+ // we don't support for cross-host builds.
+ rustlibs: [
+ "libandroid_log_sys",
+ ],
+ },
+ },
+}
diff --git a/sys_util/Cargo.toml b/common/sys_util/Cargo.toml
index 7edffd9f5..933dbda39 100644
--- a/sys_util/Cargo.toml
+++ b/common/sys_util/Cargo.toml
@@ -2,17 +2,20 @@
name = "sys_util"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
include = ["src/**/*", "Cargo.toml"]
[dependencies]
+sys_util_core = { path = "../sys_util_core" } # provided by ebuild
data_model = { path = "../data_model" } # provided by ebuild
libc = "*"
-poll_token_derive = { version = "*", path = "poll_token_derive" }
+poll_token_derive = { path = "../sys_util_core/poll_token_derive" } # provided by ebuild
+remain = "0.2"
+thiserror = "*"
serde = { version = "1", features = [ "derive" ] }
serde_json = "1"
sync = { path = "../sync" } # provided by ebuild
-tempfile = { path = "../tempfile" } # provided by ebuild
+tempfile = "3"
[target.'cfg(target_os = "android")'.dependencies]
android_log-sys = "0.2"
diff --git a/cros_async/TEST_MAPPING b/common/sys_util/TEST_MAPPING
index 19bd9f578..6e12fc12b 100644
--- a/cros_async/TEST_MAPPING
+++ b/common/sys_util/TEST_MAPPING
@@ -4,10 +4,10 @@
// "presubmit": [
// {
// "host": true,
-// "name": "cros_async_host_test_src_lib"
+// "name": "sys_util_test_src_lib"
// },
// {
-// "name": "cros_async_device_test_src_lib"
+// "name": "sys_util_test_src_lib"
// }
// ]
}
diff --git a/common/sys_util/cargo2android.json b/common/sys_util/cargo2android.json
new file mode 100644
index 000000000..b3dd2dbe0
--- /dev/null
+++ b/common/sys_util/cargo2android.json
@@ -0,0 +1,9 @@
+{
+ "add-module-block": "cargo2android_target.bp",
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "no-subdir": true,
+ "run": true,
+ "tests": false
+}
diff --git a/common/sys_util/cargo2android_target.bp b/common/sys_util/cargo2android_target.bp
new file mode 100644
index 000000000..7e4db948d
--- /dev/null
+++ b/common/sys_util/cargo2android_target.bp
@@ -0,0 +1,21 @@
+shared_libs: ["libcap"], // specified in src/capabilities.rs
+target: {
+ android: {
+ rustlibs: [
+ "libandroid_log_sys",
+ ],
+ },
+ linux_bionic_arm64: {
+ // For ARM architecture, we use aarch64-linux-android for BOTH
+ // device and host targets. As a result, host targets are also
+ // built with target_os = "android". Therefore, sys_util/src/android
+ // is used and thus this android module is required.
+ // This seems incorrect, but is inevitable because rustc doesn't
+ // yet support a Linux-based target using Bionic as libc. We can't
+ // use aarch64-unknown-linux-gnu because it's using glibc which
+ // we don't support for cross-host builds.
+ rustlibs: [
+ "libandroid_log_sys",
+ ],
+ },
+} \ No newline at end of file
diff --git a/common/sys_util/src/acpi_event.rs b/common/sys_util/src/acpi_event.rs
new file mode 100644
index 000000000..39f772c80
--- /dev/null
+++ b/common/sys_util/src/acpi_event.rs
@@ -0,0 +1,109 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::str;
+
+use super::netlink::*;
+use thiserror::Error;
+
+use data_model::DataInit;
+
+const ACPI_EVENT_SIZE: usize = std::mem::size_of::<AcpiGenlEvent>();
+const GENL_HDRLEN: usize = std::mem::size_of::<GenlMsgHdr>();
+const NLA_HDRLEN: usize = std::mem::size_of::<NlAttr>();
+
+#[derive(Error, Debug)]
+pub enum AcpiEventError {
+ #[error("GenmsghdrCmd or NlAttrType inappropriate for acpi event")]
+ TypeAttrMissmatch,
+ #[error("Something goes wrong: msg_len {0} is not correct")]
+ InvalidMsgLen(usize),
+}
+type Result<T> = std::result::Result<T, AcpiEventError>;
+
+/// attributes of AcpiGenlFamily
+#[allow(dead_code)]
+enum NlAttrType {
+ AcpiGenlAttrUnspec,
+ AcpiGenlAttrEvent, // acpi_event (needed by user space)
+ AcpiGenlAttrMax,
+}
+
+/// commands supported by the AcpiGenlFamily
+#[allow(dead_code)]
+enum GenmsghdrCmd {
+ AcpiGenlCmdUnspec,
+ AcpiGenlCmdEvent, // kernel->user notifications for acpi_events
+ AcpiGenlCmdMax,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+struct AcpiGenlEvent {
+ device_class: [::std::os::raw::c_char; 20usize],
+ bus_id: [::std::os::raw::c_char; 15usize],
+ _type: u32,
+ data: u32,
+}
+unsafe impl DataInit for AcpiGenlEvent {}
+
+pub struct AcpiNotifyEvent {
+ pub device_class: String,
+ pub bus_id: String,
+ pub _type: u32,
+ pub data: u32,
+}
+
+impl AcpiNotifyEvent {
+ /// Create acpi event by decapsulating it from NetlinkMessage.
+ pub fn new(netlink_message: NetlinkMessage) -> Result<Self> {
+ let msg_len = netlink_message.data.len();
+ if msg_len != GENL_HDRLEN + NLA_HDRLEN + ACPI_EVENT_SIZE {
+ return Err(AcpiEventError::InvalidMsgLen(msg_len));
+ }
+
+ let genl_hdr = GenlMsgHdr::from_slice(&netlink_message.data[..GENL_HDRLEN])
+ .expect("unable to get GenlMsgHdr from slice");
+
+ let nlattr_end = GENL_HDRLEN + NLA_HDRLEN;
+ let nl_attr = NlAttr::from_slice(&netlink_message.data[GENL_HDRLEN..nlattr_end])
+ .expect("unable to get NlAttr from slice");
+
+ // Sanity check that the headers have correct for acpi event `cmd` and `_type`
+ if genl_hdr.cmd != GenmsghdrCmd::AcpiGenlCmdEvent as u8
+ || nl_attr._type != NlAttrType::AcpiGenlAttrEvent as u16
+ {
+ return Err(AcpiEventError::TypeAttrMissmatch);
+ }
+
+ let acpi_event = AcpiGenlEvent::from_slice(&netlink_message.data[nlattr_end..msg_len])
+ .expect("unable to get AcpiGenlEvent from slice");
+
+ // The raw::c_char is either i8 or u8 which is known portability issue:
+ // https://github.com/rust-lang/rust/issues/79089,
+ // before using device_class further cast it to u8.
+ let device_class: &[u8; 20usize] =
+ unsafe { ::std::mem::transmute(&acpi_event.device_class) };
+ let bus_id: &[u8; 15usize] = unsafe { ::std::mem::transmute(&acpi_event.bus_id) };
+
+ Ok(AcpiNotifyEvent {
+ device_class: strip_padding(device_class).to_owned(),
+ bus_id: strip_padding(bus_id).to_owned(),
+ _type: acpi_event._type,
+ data: acpi_event.data,
+ })
+ }
+}
+
+// Like `CStr::from_bytes_with_nul` but strips any bytes starting from first '\0'-byte and
+// returns &str. Panics if `b` doesn't contain any '\0' bytes.
+fn strip_padding(b: &[u8]) -> &str {
+ // It would be nice if we could use memchr here but that's locked behind an unstable gate.
+ let pos = b
+ .iter()
+ .position(|&c| c == 0)
+ .expect("`b` doesn't contain any nul bytes");
+
+ str::from_utf8(&b[..pos]).unwrap()
+}
diff --git a/common/sys_util/src/android/mod.rs b/common/sys_util/src/android/mod.rs
new file mode 100644
index 000000000..bf3fe8885
--- /dev/null
+++ b/common/sys_util/src/android/mod.rs
@@ -0,0 +1,7 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Android-specific utility modules.
+
+pub mod syslog;
diff --git a/common/sys_util/src/android/syslog.rs b/common/sys_util/src/android/syslog.rs
new file mode 100644
index 000000000..fae3f279e
--- /dev/null
+++ b/common/sys_util/src/android/syslog.rs
@@ -0,0 +1,107 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Implementation of the Syslog trait as a wrapper around Android's logging library, liblog.
+
+extern crate android_log_sys;
+
+use super::super::syslog::{Error, Facility, Priority, Syslog};
+use android_log_sys::{
+ __android_log_is_loggable, __android_log_message, __android_log_write_log_message, log_id_t,
+ LogPriority,
+};
+use std::{
+ ffi::{CString, NulError},
+ fmt,
+ mem::size_of,
+ os::unix::io::RawFd,
+};
+
+pub struct PlatformSyslog {
+ enabled: bool,
+}
+
+impl Syslog for PlatformSyslog {
+ fn new() -> Result<Self, Error> {
+ Ok(Self { enabled: true })
+ }
+
+ fn enable(&mut self, enable: bool) -> Result<(), Error> {
+ self.enabled = enable;
+ Ok(())
+ }
+
+ fn push_fds(&self, _fds: &mut Vec<RawFd>) {}
+
+ fn log(
+ &self,
+ proc_name: Option<&str>,
+ pri: Priority,
+ _fac: Facility,
+ file_line: Option<(&str, u32)>,
+ args: fmt::Arguments,
+ ) {
+ let priority = match pri {
+ Priority::Emergency => LogPriority::ERROR,
+ Priority::Alert => LogPriority::ERROR,
+ Priority::Critical => LogPriority::ERROR,
+ Priority::Error => LogPriority::ERROR,
+ Priority::Warning => LogPriority::WARN,
+ Priority::Notice => LogPriority::INFO,
+ Priority::Info => LogPriority::DEBUG,
+ Priority::Debug => LogPriority::VERBOSE,
+ };
+ let tag = proc_name.unwrap_or("crosvm");
+ let message = std::fmt::format(args);
+ let _ = android_log(
+ log_id_t::SYSTEM,
+ priority,
+ tag,
+ file_line.map(|(file, _)| file),
+ file_line.map(|(_, line)| line),
+ &message,
+ );
+ }
+}
+
+/// Send a log message to the Android logger (logd, by default) if it is currently configured to be
+/// loggable based on the priority and tag.
+///
+/// # Arguments
+/// * `priority` - The Android log priority. Used to determine whether the message is loggable.
+/// * `tag` - A tag to indicate where the log comes from.
+/// * `file` - The name of the file from where the message is being logged, if available.
+/// * `line` - The line number from where the message is being logged, if available.
+/// * `message` - The message to log.
+fn android_log(
+ buffer_id: log_id_t,
+ priority: LogPriority,
+ tag: &str,
+ file: Option<&str>,
+ line: Option<u32>,
+ message: &str,
+) -> Result<(), NulError> {
+ let tag = CString::new(tag)?;
+ let default_pri = LogPriority::VERBOSE;
+ if unsafe { __android_log_is_loggable(priority as i32, tag.as_ptr(), default_pri as i32) } != 0
+ {
+ let c_file_name = match file {
+ Some(file_name) => CString::new(file_name)?.as_ptr(),
+ None => std::ptr::null(),
+ };
+ let line = line.unwrap_or(0);
+ let message = CString::new(message)?;
+ let mut log_message = __android_log_message {
+ struct_size: size_of::<__android_log_message>(),
+ buffer_id: buffer_id as i32,
+ priority: priority as i32,
+ tag: tag.as_ptr(),
+ file: c_file_name,
+ line,
+ message: message.as_ptr(),
+ };
+ unsafe { __android_log_write_log_message(&mut log_message) };
+ }
+ Ok(())
+}
diff --git a/common/sys_util/src/capabilities.rs b/common/sys_util/src/capabilities.rs
new file mode 100644
index 000000000..5d3acd45a
--- /dev/null
+++ b/common/sys_util/src/capabilities.rs
@@ -0,0 +1,41 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use libc::{c_int, c_void};
+
+use super::{errno_result, Result};
+
+#[allow(non_camel_case_types)]
+type cap_t = *mut c_void;
+
+#[link(name = "cap")]
+extern "C" {
+ fn cap_init() -> cap_t;
+ fn cap_free(ptr: *mut c_void) -> c_int;
+ fn cap_set_proc(cap: cap_t) -> c_int;
+}
+
+/// Drops all capabilities (permitted, inheritable, and effective) from the current process.
+pub fn drop_capabilities() -> Result<()> {
+ unsafe {
+ // Safe because we do not actually manipulate any memory handled by libcap
+ // and we check errors.
+ let caps = cap_init();
+ if caps.is_null() {
+ return errno_result();
+ }
+
+ // Freshly initialized capabilities do not have any bits set, so applying them
+ // will drop all capabilities from the process.
+ // Safe because we will check the result and otherwise do not touch the memory.
+ let ret = cap_set_proc(caps);
+ // We need to free capabilities regardless of success of the operation above.
+ cap_free(caps);
+ // Now check if we managed to apply (drop) capabilities.
+ if ret < 0 {
+ return errno_result();
+ }
+ }
+ Ok(())
+}
diff --git a/common/sys_util/src/clock.rs b/common/sys_util/src/clock.rs
new file mode 100644
index 000000000..5bf0d3004
--- /dev/null
+++ b/common/sys_util/src/clock.rs
@@ -0,0 +1,120 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Utility file to provide a fake clock object representing current time, and a timerfd driven by
+// that time.
+
+use std::{
+ os::unix::io::AsRawFd,
+ time::{Duration, Instant},
+};
+
+use super::EventFd;
+
+#[derive(Debug, Copy, Clone)]
+pub struct Clock(Instant);
+impl Clock {
+ pub fn new() -> Self {
+ Clock(Instant::now())
+ }
+
+ pub fn now(&self) -> Self {
+ Clock(Instant::now())
+ }
+
+ pub fn duration_since(&self, earlier: &Self) -> Duration {
+ self.0.duration_since(earlier.0)
+ }
+
+ pub fn elapsed(&self) -> Duration {
+ self.0.elapsed()
+ }
+
+ pub fn checked_sub(&self, duration: Duration) -> Option<Self> {
+ Some(Clock(self.0.checked_sub(duration)?))
+ }
+}
+
+impl Default for Clock {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+const NS_PER_SEC: u64 = 1_000_000_000;
+/// A fake clock that can be used in tests to give exact control over the time.
+/// For a code example, see the tests in sys_util/src/timerfd.rs.
+#[derive(Debug)]
+pub struct FakeClock {
+ ns_since_epoch: u64,
+ deadlines: Vec<(u64, EventFd)>,
+}
+
+impl FakeClock {
+ pub fn new() -> Self {
+ FakeClock {
+ ns_since_epoch: 1_547_163_599 * NS_PER_SEC,
+ deadlines: Vec::new(),
+ }
+ }
+
+ /// Get the current time, according to this clock.
+ pub fn now(&self) -> Self {
+ FakeClock {
+ ns_since_epoch: self.ns_since_epoch,
+ deadlines: Vec::new(),
+ }
+ }
+
+ /// Get the current time in ns, according to this clock.
+ pub fn nanos(&self) -> u64 {
+ self.ns_since_epoch
+ }
+
+ /// Get the duration since |earlier|, assuming that earlier < self.
+ pub fn duration_since(&self, earlier: &Self) -> Duration {
+ let ns_diff = self.ns_since_epoch - earlier.ns_since_epoch;
+ Duration::new(ns_diff / NS_PER_SEC, (ns_diff % NS_PER_SEC) as u32)
+ }
+
+ /// Get the time that has elapsed since this clock was made. Always returns 0 on a FakeClock.
+ pub fn elapsed(&self) -> Duration {
+ self.now().duration_since(self)
+ }
+
+ pub fn checked_sub(&self, duration: Duration) -> Option<Self> {
+ Some(FakeClock {
+ ns_since_epoch: self
+ .ns_since_epoch
+ .checked_sub(duration.as_nanos() as u64)?,
+ deadlines: Vec::new(),
+ })
+ }
+
+ /// Register the event fd for a notification when self's time is |deadline_ns|.
+ /// Drop any existing events registered to the same raw fd.
+ pub fn add_event_fd(&mut self, deadline_ns: u64, fd: EventFd) {
+ self.deadlines
+ .retain(|(_, old_fd)| fd.as_raw_fd() != old_fd.as_raw_fd());
+ self.deadlines.push((deadline_ns, fd));
+ }
+
+ pub fn add_ns(&mut self, ns: u64) {
+ self.ns_since_epoch += ns;
+ let time = self.ns_since_epoch;
+ self.deadlines.retain(|(ns, fd)| {
+ let expired = *ns <= time;
+ if expired {
+ fd.write(1).unwrap();
+ }
+ !expired
+ });
+ }
+}
+
+impl Default for FakeClock {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/sys_util/src/descriptor.rs b/common/sys_util/src/descriptor.rs
index 325b2b450..ba20b465a 100644
--- a/sys_util/src/descriptor.rs
+++ b/common/sys_util/src/descriptor.rs
@@ -2,22 +2,32 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::convert::TryFrom;
-use std::fs::File;
-use std::io::{Stderr, Stdin, Stdout};
-use std::mem;
-use std::net::UdpSocket;
-use std::ops::Drop;
-use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
-use std::os::unix::net::{UnixDatagram, UnixStream};
+use std::{
+ convert::TryFrom,
+ fs::File,
+ io::{Stderr, Stdin, Stdout},
+ mem,
+ mem::ManuallyDrop,
+ net::UdpSocket,
+ ops::Drop,
+ os::unix::{
+ io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
+ net::{UnixDatagram, UnixListener, UnixStream},
+ },
+};
use serde::{Deserialize, Serialize};
-use crate::net::UnlinkUnixSeqpacketListener;
-use crate::{errno_result, PollToken, Result};
+use super::{
+ errno_result,
+ net::{UnixSeqpacket, UnlinkUnixSeqpacketListener},
+ PollToken, Result,
+};
pub type RawDescriptor = RawFd;
+pub const INVALID_DESCRIPTOR: RawDescriptor = -1;
+
/// Trait for forfeiting ownership of the current raw descriptor, and returning the raw descriptor
pub trait IntoRawDescriptor {
fn into_raw_descriptor(self) -> RawDescriptor;
@@ -36,10 +46,17 @@ pub trait FromRawDescriptor {
unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Self;
}
+/// Clones `descriptor`, returning a new `RawDescriptor` that refers to the same open file
+/// description as `descriptor`. The cloned descriptor will have the `FD_CLOEXEC` flag set but will
+/// not share any other file descriptor flags with `descriptor`.
+pub fn clone_descriptor(descriptor: &dyn AsRawDescriptor) -> Result<RawDescriptor> {
+ clone_fd(&descriptor.as_raw_descriptor())
+}
+
/// Clones `fd`, returning a new file descriptor that refers to the same open file description as
/// `fd`. The cloned fd will have the `FD_CLOEXEC` flag set but will not share any other file
/// descriptor flags with `fd`.
-pub fn clone_fd(fd: &dyn AsRawFd) -> Result<RawFd> {
+fn clone_fd(fd: &dyn AsRawFd) -> Result<RawFd> {
// Safe because this doesn't modify any memory and we check the return value.
let ret = unsafe { libc::fcntl(fd.as_raw_fd(), libc::F_DUPFD_CLOEXEC, 0) };
if ret < 0 {
@@ -53,7 +70,7 @@ pub fn clone_fd(fd: &dyn AsRawFd) -> Result<RawFd> {
#[derive(Serialize, Deserialize, Debug, Eq)]
#[serde(transparent)]
pub struct SafeDescriptor {
- #[serde(with = "crate::with_raw_descriptor")]
+ #[serde(with = "super::with_raw_descriptor")]
descriptor: RawDescriptor,
}
@@ -126,6 +143,27 @@ impl TryFrom<&dyn AsRawFd> for SafeDescriptor {
}
}
+impl TryFrom<&dyn AsRawDescriptor> for SafeDescriptor {
+ type Error = std::io::Error;
+
+ /// Clones the underlying descriptor (handle), internally creating a new descriptor.
+ fn try_from(rd: &dyn AsRawDescriptor) -> std::result::Result<Self, Self::Error> {
+ // Safe because the underlying raw descriptor is guaranteed valid by rd's existence.
+ //
+ // Note that we are cloning the underlying raw descriptor since we have no guarantee of
+ // its existence after this function returns.
+ let rd_as_safe_desc = ManuallyDrop::new(unsafe {
+ SafeDescriptor::from_raw_descriptor(rd.as_raw_descriptor())
+ });
+
+ // We have to clone rd because we have no guarantee ownership was transferred (rd is
+ // borrowed).
+ rd_as_safe_desc
+ .try_clone()
+ .map_err(|e| Self::Error::from_raw_os_error(e.errno()))
+ }
+}
+
impl SafeDescriptor {
/// Clones this descriptor, internally creating a new descriptor. The new SafeDescriptor will
/// share the same underlying count within the kernel.
@@ -154,6 +192,20 @@ impl From<File> for SafeDescriptor {
}
}
+impl From<SafeDescriptor> for UnixStream {
+ fn from(s: SafeDescriptor) -> Self {
+ // Safe because we own the SafeDescriptor at this point.
+ unsafe { Self::from_raw_fd(s.into_raw_descriptor()) }
+ }
+}
+
+impl From<UnixSeqpacket> for SafeDescriptor {
+ fn from(s: UnixSeqpacket) -> Self {
+ // Safe because we own the UnixSeqpacket at this point.
+ unsafe { SafeDescriptor::from_raw_descriptor(s.into_raw_descriptor()) }
+ }
+}
+
/// For use cases where a simple wrapper around a RawDescriptor is needed.
/// This is a simply a wrapper and does not manage the lifetime of the descriptor.
/// Most usages should prefer SafeDescriptor or using a RawDescriptor directly
@@ -221,43 +273,21 @@ macro_rules! IntoRawDescriptor {
// relevant container type.
AsRawDescriptor!(File);
AsRawDescriptor!(UnlinkUnixSeqpacketListener);
+AsRawDescriptor!(UdpSocket);
+AsRawDescriptor!(UnixDatagram);
+AsRawDescriptor!(UnixListener);
+AsRawDescriptor!(UnixStream);
FromRawDescriptor!(File);
+FromRawDescriptor!(UnixStream);
+FromRawDescriptor!(UnixDatagram);
IntoRawDescriptor!(File);
+IntoRawDescriptor!(UnixDatagram);
AsRawDescriptor!(Stdin);
AsRawDescriptor!(Stdout);
AsRawDescriptor!(Stderr);
-impl AsRawDescriptor for UdpSocket {
- fn as_raw_descriptor(&self) -> RawDescriptor {
- self.as_raw_fd()
- }
-}
-
-impl AsRawDescriptor for UnixStream {
- fn as_raw_descriptor(&self) -> RawDescriptor {
- self.as_raw_fd()
- }
-}
-
-impl AsRawDescriptor for UnixDatagram {
- fn as_raw_descriptor(&self) -> RawDescriptor {
- self.as_raw_fd()
- }
-}
-
-impl FromRawDescriptor for UnixDatagram {
- unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Self {
- Self::from_raw_fd(descriptor)
- }
-}
-
-impl IntoRawDescriptor for UnixDatagram {
- fn into_raw_descriptor(self) -> RawDescriptor {
- self.into_raw_fd()
- }
-}
-
#[test]
+#[allow(clippy::eq_op)]
fn clone_equality() {
let ret = unsafe { libc::eventfd(0, 0) };
if ret < 0 {
diff --git a/sys_util/src/descriptor_reflection.rs b/common/sys_util/src/descriptor_reflection.rs
index 9b18ffc37..1f2933c1d 100644
--- a/sys_util/src/descriptor_reflection.rs
+++ b/common/sys_util/src/descriptor_reflection.rs
@@ -48,18 +48,23 @@
//! .expect("failed to deserialize");
//! ```
-use std::cell::{Cell, RefCell};
-use std::convert::TryInto;
-use std::fmt;
-use std::fs::File;
-use std::ops::{Deref, DerefMut};
-use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
-
-use serde::de::{self, Error, Visitor};
-use serde::ser;
-use serde::{Deserialize, Deserializer, Serialize, Serializer};
-
-use crate::{RawDescriptor, SafeDescriptor};
+use std::{
+ cell::{Cell, RefCell},
+ convert::TryInto,
+ fmt,
+ fs::File,
+ ops::{Deref, DerefMut},
+ panic::{catch_unwind, resume_unwind, AssertUnwindSafe},
+};
+
+use serde::{
+ de::{
+ Error, Visitor, {self},
+ },
+ ser, Deserialize, Deserializer, Serialize, Serializer,
+};
+
+use super::{RawDescriptor, SafeDescriptor};
thread_local! {
static DESCRIPTOR_DST: RefCell<Option<Vec<RawDescriptor>>> = Default::default();
@@ -334,7 +339,7 @@ where
/// }
/// ```
pub mod with_raw_descriptor {
- use crate::{IntoRawDescriptor, RawDescriptor};
+ use super::super::{IntoRawDescriptor, RawDescriptor};
use serde::Deserializer;
pub use super::serialize_descriptor as serialize;
@@ -364,7 +369,7 @@ pub mod with_raw_descriptor {
/// }
/// ```
pub mod with_as_descriptor {
- use crate::{AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor};
+ use super::super::{AsRawDescriptor, FromRawDescriptor, IntoRawDescriptor};
use serde::{Deserializer, Serializer};
pub fn serialize<S: Serializer>(
@@ -425,15 +430,12 @@ impl DerefMut for FileSerdeWrapper {
#[cfg(test)]
mod tests {
- use crate::{
+ use super::super::{
deserialize_with_descriptors, with_as_descriptor, with_raw_descriptor, FileSerdeWrapper,
FromRawDescriptor, RawDescriptor, SafeDescriptor, SerializeDescriptors,
};
- use std::collections::HashMap;
- use std::fs::File;
- use std::mem::ManuallyDrop;
- use std::os::unix::io::AsRawFd;
+ use std::{collections::HashMap, fs::File, mem::ManuallyDrop, os::unix::io::AsRawFd};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use tempfile::tempfile;
@@ -462,7 +464,7 @@ mod tests {
}
// Specifically chosen to not overlap a real descriptor to avoid having to allocate any
// descriptors for this test.
- let fake_rd = 5_123_457 as _;
+ let fake_rd = 5_123_457_i32;
let v = RawContainer { rd: fake_rd };
let v_serialize = SerializeDescriptors::new(&v);
let json = serde_json::to_string(&v_serialize).unwrap();
diff --git a/common/sys_util/src/eventfd.rs b/common/sys_util/src/eventfd.rs
new file mode 100644
index 000000000..f4f6cac0e
--- /dev/null
+++ b/common/sys_util/src/eventfd.rs
@@ -0,0 +1,227 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ mem,
+ ops::Deref,
+ os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
+ ptr,
+ time::Duration,
+};
+
+use libc::{c_void, eventfd, read, write, POLLIN};
+use serde::{Deserialize, Serialize};
+
+use super::{
+ duration_to_timespec, errno_result, generate_scoped_event, AsRawDescriptor, FromRawDescriptor,
+ IntoRawDescriptor, RawDescriptor, Result, SafeDescriptor,
+};
+
+/// A safe wrapper around a Linux eventfd (man 2 eventfd).
+///
+/// An eventfd is useful because it is sendable across processes and can be used for signaling in
+/// and out of the KVM API. They can also be polled like any other file descriptor.
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(transparent)]
+pub struct EventFd {
+ event_handle: SafeDescriptor,
+}
+
+/// Wrapper around the return value of doing a read on an EventFd which distinguishes between
+/// getting a valid count of the number of times the eventfd has been written to and timing out
+/// waiting for the count to be non-zero.
+#[derive(Debug, PartialEq, Eq)]
+pub enum EventReadResult {
+ Count(u64),
+ Timeout,
+}
+
+impl EventFd {
+ /// Creates a new blocking EventFd with an initial value of 0.
+ pub fn new() -> Result<EventFd> {
+ // This is safe because eventfd merely allocated an eventfd for our process and we handle
+ // the error case.
+ let ret = unsafe { eventfd(0, 0) };
+ if ret < 0 {
+ return errno_result();
+ }
+ // This is safe because we checked ret for success and know the kernel gave us an fd that we
+ // own.
+ Ok(EventFd {
+ event_handle: unsafe { SafeDescriptor::from_raw_descriptor(ret) },
+ })
+ }
+
+ /// Adds `v` to the eventfd's count, blocking until this won't overflow the count.
+ pub fn write(&self, v: u64) -> Result<()> {
+ // This is safe because we made this fd and the pointer we pass can not overflow because we
+ // give the syscall's size parameter properly.
+ let ret = unsafe {
+ write(
+ self.as_raw_fd(),
+ &v as *const u64 as *const c_void,
+ mem::size_of::<u64>(),
+ )
+ };
+ if ret <= 0 {
+ return errno_result();
+ }
+ Ok(())
+ }
+
+ /// Blocks until the the eventfd's count is non-zero, then resets the count to zero.
+ pub fn read(&self) -> Result<u64> {
+ let mut buf: u64 = 0;
+ let ret = unsafe {
+ // This is safe because we made this fd and the pointer we pass can not overflow because
+ // we give the syscall's size parameter properly.
+ read(
+ self.as_raw_fd(),
+ &mut buf as *mut u64 as *mut c_void,
+ mem::size_of::<u64>(),
+ )
+ };
+ if ret <= 0 {
+ return errno_result();
+ }
+ Ok(buf)
+ }
+
+ /// Blocks for a maximum of `timeout` duration until the the eventfd's count is non-zero. If
+ /// a timeout does not occur then the count is returned as a EventReadResult::Count(count),
+ /// and the count is reset to 0. If a timeout does occur then this function will return
+ /// EventReadResult::Timeout.
+ pub fn read_timeout(&self, timeout: Duration) -> Result<EventReadResult> {
+ let mut pfd = libc::pollfd {
+ fd: self.as_raw_descriptor(),
+ events: POLLIN,
+ revents: 0,
+ };
+ let timeoutspec: libc::timespec = duration_to_timespec(timeout);
+ // Safe because this only modifies |pfd| and we check the return value
+ let ret = unsafe {
+ libc::ppoll(
+ &mut pfd as *mut libc::pollfd,
+ 1,
+ &timeoutspec,
+ ptr::null_mut(),
+ )
+ };
+ if ret < 0 {
+ return errno_result();
+ }
+
+ // no return events (revents) means we got a timeout
+ if pfd.revents == 0 {
+ return Ok(EventReadResult::Timeout);
+ }
+
+ let mut buf = 0u64;
+ // This is safe because we made this fd and the pointer we pass can not overflow because
+ // we give the syscall's size parameter properly.
+ let ret = unsafe {
+ libc::read(
+ self.as_raw_descriptor(),
+ &mut buf as *mut _ as *mut c_void,
+ mem::size_of::<u64>(),
+ )
+ };
+ if ret < 0 {
+ return errno_result();
+ }
+ Ok(EventReadResult::Count(buf))
+ }
+
+ /// Clones this EventFd, internally creating a new file descriptor. The new EventFd will share
+ /// the same underlying count within the kernel.
+ pub fn try_clone(&self) -> Result<EventFd> {
+ self.event_handle
+ .try_clone()
+ .map(|event_handle| EventFd { event_handle })
+ }
+}
+
+impl AsRawFd for EventFd {
+ fn as_raw_fd(&self) -> RawFd {
+ self.event_handle.as_raw_fd()
+ }
+}
+
+impl AsRawDescriptor for EventFd {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.event_handle.as_raw_descriptor()
+ }
+}
+
+impl FromRawFd for EventFd {
+ unsafe fn from_raw_fd(fd: RawFd) -> Self {
+ EventFd {
+ event_handle: SafeDescriptor::from_raw_descriptor(fd),
+ }
+ }
+}
+
+impl IntoRawFd for EventFd {
+ fn into_raw_fd(self) -> RawFd {
+ self.event_handle.into_raw_descriptor()
+ }
+}
+
+impl From<EventFd> for SafeDescriptor {
+ fn from(evt: EventFd) -> Self {
+ evt.event_handle
+ }
+}
+
+generate_scoped_event!(EventFd);
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn new() {
+ EventFd::new().unwrap();
+ }
+
+ #[test]
+ fn read_write() {
+ let evt = EventFd::new().unwrap();
+ evt.write(55).unwrap();
+ assert_eq!(evt.read(), Ok(55));
+ }
+
+ #[test]
+ fn clone() {
+ let evt = EventFd::new().unwrap();
+ let evt_clone = evt.try_clone().unwrap();
+ evt.write(923).unwrap();
+ assert_eq!(evt_clone.read(), Ok(923));
+ }
+
+ #[test]
+ fn scoped_event() {
+ let scoped_evt = ScopedEvent::new().unwrap();
+ let evt_clone: EventFd = scoped_evt.try_clone().unwrap();
+ drop(scoped_evt);
+ assert_eq!(evt_clone.read(), Ok(1));
+ }
+
+ #[test]
+ fn eventfd_from_scoped_event() {
+ let scoped_evt = ScopedEvent::new().unwrap();
+ let evt: EventFd = scoped_evt.into();
+ evt.write(1).unwrap();
+ }
+
+ #[test]
+ fn timeout() {
+ let evt = EventFd::new().expect("failed to create eventfd");
+ assert_eq!(
+ evt.read_timeout(Duration::from_millis(1))
+ .expect("failed to read from eventfd with timeout"),
+ EventReadResult::Timeout
+ );
+ }
+}
diff --git a/common/sys_util/src/file_flags.rs b/common/sys_util/src/file_flags.rs
new file mode 100644
index 000000000..210f891df
--- /dev/null
+++ b/common/sys_util/src/file_flags.rs
@@ -0,0 +1,55 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::os::unix::io::AsRawFd;
+
+use libc::{fcntl, EINVAL, F_GETFL, O_ACCMODE, O_RDONLY, O_RDWR, O_WRONLY};
+
+use super::{errno_result, Error, Result};
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum FileFlags {
+ Read,
+ Write,
+ ReadWrite,
+}
+
+impl FileFlags {
+ pub fn from_file(file: &dyn AsRawFd) -> Result<FileFlags> {
+ // Trivially safe because fcntl with the F_GETFL command is totally safe and we check for
+ // error.
+ let flags = unsafe { fcntl(file.as_raw_fd(), F_GETFL) };
+ if flags == -1 {
+ errno_result()
+ } else {
+ match flags & O_ACCMODE {
+ O_RDONLY => Ok(FileFlags::Read),
+ O_WRONLY => Ok(FileFlags::Write),
+ O_RDWR => Ok(FileFlags::ReadWrite),
+ _ => Err(Error::new(EINVAL)),
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{
+ super::{pipe, EventFd},
+ *,
+ };
+
+ #[test]
+ fn pipe_pair() {
+ let (read_pipe, write_pipe) = pipe(true).unwrap();
+ assert_eq!(FileFlags::from_file(&read_pipe).unwrap(), FileFlags::Read);
+ assert_eq!(FileFlags::from_file(&write_pipe).unwrap(), FileFlags::Write);
+ }
+
+ #[test]
+ fn eventfd() {
+ let evt = EventFd::new().unwrap();
+ assert_eq!(FileFlags::from_file(&evt).unwrap(), FileFlags::ReadWrite);
+ }
+}
diff --git a/sys_util/src/file_traits.rs b/common/sys_util/src/file_traits.rs
index b6586952e..88c23eec7 100644
--- a/sys_util/src/file_traits.rs
+++ b/common/sys_util/src/file_traits.rs
@@ -2,13 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fs::File;
-use std::io::{Error, ErrorKind, Result};
-use std::os::unix::io::{AsRawFd, RawFd};
+use std::{
+ fs::File,
+ io::{Error, ErrorKind, Result},
+ os::unix::{
+ io::{AsRawFd, RawFd},
+ net::UnixStream,
+ },
+};
use data_model::VolatileSlice;
-use crate::{fallocate, FallocateMode};
+use super::{fallocate, FallocateMode};
/// A trait for flushing the contents of a file to disk.
/// This is equivalent to File's `sync_all` method, but
@@ -266,7 +271,7 @@ pub mod lib {
write, writev,
};
- pub use data_model::VolatileSlice;
+ pub use data_model::{IoBufMut, VolatileSlice};
}
#[macro_export]
@@ -297,7 +302,8 @@ macro_rules! volatile_impl {
&mut self,
bufs: &[$crate::file_traits::lib::VolatileSlice],
) -> std::io::Result<usize> {
- let iovecs = $crate::file_traits::lib::VolatileSlice::as_iobufs(bufs);
+ let iobufs = $crate::file_traits::lib::VolatileSlice::as_iobufs(bufs);
+ let iovecs = $crate::file_traits::lib::IoBufMut::as_iobufs(iobufs);
if iovecs.is_empty() {
return Ok(0);
@@ -343,7 +349,8 @@ macro_rules! volatile_impl {
&mut self,
bufs: &[$crate::file_traits::lib::VolatileSlice],
) -> std::io::Result<usize> {
- let iovecs = $crate::file_traits::lib::VolatileSlice::as_iobufs(bufs);
+ let iobufs = $crate::file_traits::lib::VolatileSlice::as_iobufs(bufs);
+ let iovecs = $crate::file_traits::lib::IoBufMut::as_iobufs(iobufs);
if iovecs.is_empty() {
return Ok(0);
@@ -400,7 +407,8 @@ macro_rules! volatile_at_impl {
bufs: &[$crate::file_traits::lib::VolatileSlice],
offset: u64,
) -> std::io::Result<usize> {
- let iovecs = $crate::file_traits::lib::VolatileSlice::as_iobufs(bufs);
+ let iobufs = $crate::file_traits::lib::VolatileSlice::as_iobufs(bufs);
+ let iovecs = $crate::file_traits::lib::IoBufMut::as_iobufs(iobufs);
if iovecs.is_empty() {
return Ok(0);
@@ -451,7 +459,8 @@ macro_rules! volatile_at_impl {
bufs: &[$crate::file_traits::lib::VolatileSlice],
offset: u64,
) -> std::io::Result<usize> {
- let iovecs = $crate::file_traits::lib::VolatileSlice::as_iobufs(bufs);
+ let iobufs = $crate::file_traits::lib::VolatileSlice::as_iobufs(bufs);
+ let iovecs = $crate::file_traits::lib::IoBufMut::as_iobufs(iobufs);
if iovecs.is_empty() {
return Ok(0);
@@ -479,6 +488,7 @@ macro_rules! volatile_at_impl {
volatile_impl!(File);
volatile_at_impl!(File);
+volatile_impl!(UnixStream);
/// A trait similar to `AsRawFd` but supports an arbitrary number of file descriptors.
pub trait AsRawFds {
diff --git a/common/sys_util/src/get_filesystem_type.rs b/common/sys_util/src/get_filesystem_type.rs
new file mode 100644
index 000000000..83aae829a
--- /dev/null
+++ b/common/sys_util/src/get_filesystem_type.rs
@@ -0,0 +1,28 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::{syscall, Result};
+use libc::fstatfs;
+use std::{fs::File, mem::MaybeUninit, os::unix::io::AsRawFd};
+
+/// Obtain file system type of the file system that the file is served from.
+pub fn get_filesystem_type(file: &File) -> Result<i64> {
+ let mut statfs_buf = MaybeUninit::<libc::statfs>::uninit();
+ // Safe because we just got the memory space with exact required amount and
+ // passing that on.
+ syscall!(unsafe { fstatfs(file.as_raw_fd(), statfs_buf.as_mut_ptr()) })?;
+ // Safe because the kernel guarantees the struct is initialized.
+ let statfs_buf = unsafe { statfs_buf.assume_init() };
+ Ok(statfs_buf.f_type as i64)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn simple_test() {
+ let file = File::open("/dev/null").unwrap();
+ let _fstype = get_filesystem_type(&file).unwrap();
+ }
+}
diff --git a/sys_util/src/handle_eintr.rs b/common/sys_util/src/handle_eintr.rs
index ea7d673a3..27120a1f1 100644
--- a/sys_util/src/handle_eintr.rs
+++ b/common/sys_util/src/handle_eintr.rs
@@ -15,7 +15,7 @@ pub trait InterruptibleResult {
fn is_interrupted(&self) -> bool;
}
-impl<T> InterruptibleResult for crate::Result<T> {
+impl<T> InterruptibleResult for super::Result<T> {
fn is_interrupted(&self) -> bool {
matches!(self, Err(e) if e.errno() == EINTR)
}
@@ -171,8 +171,7 @@ macro_rules! handle_eintr_errno {
#[cfg(test)]
mod tests {
- use super::*;
- use crate::Error as SysError;
+ use super::{super::Error as SysError, *};
// Sets errno to the given error code.
fn set_errno(e: i32) {
diff --git a/sys_util/src/ioctl.rs b/common/sys_util/src/ioctl.rs
index 690b7789b..dd8cc6691 100644
--- a/sys_util/src/ioctl.rs
+++ b/common/sys_util/src/ioctl.rs
@@ -8,8 +8,7 @@
// `libc::ioctl`. Their safety follows `libc::ioctl`'s safety.
#![allow(clippy::missing_safety_doc)]
-use std::os::raw::*;
-use std::os::unix::io::AsRawFd;
+use std::os::{raw::*, unix::io::AsRawFd};
/// Raw macro to declare the expression that calculates an ioctl number
#[macro_export]
diff --git a/sys_util/src/lib.rs b/common/sys_util/src/lib.rs
index 43bdcc4d1..3f2841e9c 100644
--- a/sys_util/src/lib.rs
+++ b/common/sys_util/src/lib.rs
@@ -4,7 +4,11 @@
//! Small system utility modules for usage by other modules.
-mod alloc;
+// Fail sys_util compilation on windows.
+// This will make any unintentional windows code submitted to the crate unusable.
+#[cfg(windows)]
+compile_error!("sys_util is not windows friendly crate. See/use win_sys_util.");
+
#[cfg(target_os = "android")]
mod android;
#[cfg(target_os = "android")]
@@ -19,100 +23,114 @@ pub mod handle_eintr;
pub mod ioctl;
#[macro_use]
pub mod syslog;
+mod acpi_event;
mod capabilities;
mod clock;
mod descriptor;
mod descriptor_reflection;
-mod errno;
mod eventfd;
-mod external_mapping;
mod file_flags;
pub mod file_traits;
-mod fork;
+mod get_filesystem_type;
mod mmap;
pub mod net;
-mod passwd;
+mod netlink;
mod poll;
mod priority;
pub mod rand;
mod raw_fd;
-pub mod sched;
+pub mod read_dir;
+mod sched;
pub mod scoped_path;
pub mod scoped_signal_handler;
-mod seek_hole;
mod shm;
pub mod signal;
mod signalfd;
mod sock_ctrl_msg;
-mod struct_util;
mod terminal;
mod timerfd;
pub mod vsock;
mod write_zeroes;
-pub use crate::alloc::LayoutAllocation;
-pub use crate::capabilities::drop_capabilities;
-pub use crate::clock::{Clock, FakeClock};
-pub use crate::descriptor::*;
-pub use crate::errno::{errno_result, Error, Result};
-pub use crate::eventfd::*;
-pub use crate::external_mapping::*;
-pub use crate::file_flags::*;
-pub use crate::fork::*;
-pub use crate::ioctl::*;
-pub use crate::mmap::*;
-pub use crate::passwd::*;
-pub use crate::poll::*;
-pub use crate::priority::*;
-pub use crate::raw_fd::*;
-pub use crate::sched::*;
-pub use crate::scoped_signal_handler::*;
-pub use crate::shm::*;
-pub use crate::signal::*;
-pub use crate::signalfd::*;
-pub use crate::sock_ctrl_msg::*;
-pub use crate::struct_util::*;
-pub use crate::terminal::*;
-pub use crate::timerfd::*;
+pub use acpi_event::*;
+pub use capabilities::drop_capabilities;
+pub use clock::{Clock, FakeClock};
+pub use descriptor::*;
pub use descriptor_reflection::{
deserialize_with_descriptors, with_as_descriptor, with_raw_descriptor, FileSerdeWrapper,
SerializeDescriptors,
};
+pub use eventfd::*;
+pub use file_flags::*;
+pub use get_filesystem_type::*;
+pub use ioctl::*;
+pub use mmap::*;
+pub use netlink::*;
+pub use poll::*;
pub use poll_token_derive::*;
-
-pub use crate::external_mapping::Error as ExternalMappingError;
-pub use crate::external_mapping::Result as ExternalMappingResult;
-pub use crate::file_traits::{
+pub use priority::*;
+pub use raw_fd::*;
+pub use sched::*;
+pub use scoped_signal_handler::*;
+pub use shm::*;
+pub use signal::*;
+pub use signalfd::*;
+pub use sock_ctrl_msg::*;
+pub use sys_util_core::{generate_scoped_event, Error, Result, *};
+pub use terminal::*;
+pub use timerfd::*;
+
+pub use file_traits::{
AsRawFds, FileAllocate, FileGetLen, FileReadWriteAtVolatile, FileReadWriteVolatile, FileSetLen,
FileSync,
};
-pub use crate::mmap::Error as MmapError;
-pub use crate::seek_hole::SeekHole;
-pub use crate::signalfd::Error as SignalFdError;
-pub use crate::write_zeroes::{PunchHole, WriteZeroes, WriteZeroesAt};
-
-use std::cell::Cell;
-use std::ffi::CStr;
-use std::fs::{remove_file, File};
-use std::mem;
-use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
-use std::os::unix::net::UnixDatagram;
-use std::ptr;
-use std::time::Duration;
+pub use mmap::Error as MmapError;
+pub use signalfd::Error as SignalFdError;
+pub use write_zeroes::{PunchHole, WriteZeroes, WriteZeroesAt};
+
+use std::{
+ cell::Cell,
+ convert::TryFrom,
+ ffi::CStr,
+ fs::{remove_file, File, OpenOptions},
+ mem,
+ ops::Deref,
+ os::unix::{
+ fs::OpenOptionsExt,
+ io::{AsRawFd, FromRawFd, RawFd},
+ net::{UnixDatagram, UnixListener},
+ },
+ path::Path,
+ ptr,
+ time::Duration,
+};
use libc::{
- c_int, c_long, fcntl, pipe2, syscall, sysconf, waitpid, SYS_getpid, SYS_gettid, F_GETFL,
- F_SETFL, O_CLOEXEC, SIGKILL, WNOHANG, _SC_IOV_MAX, _SC_PAGESIZE,
+ c_int, c_long, fcntl, pipe2, syscall, sysconf, waitpid, SYS_getpid, SYS_gettid, EINVAL,
+ F_GETFL, F_SETFL, O_CLOEXEC, O_DIRECT, SIGKILL, WNOHANG, _SC_IOV_MAX, _SC_PAGESIZE,
};
/// Re-export libc types that are part of the API.
pub type Pid = libc::pid_t;
pub type Uid = libc::uid_t;
pub type Gid = libc::gid_t;
+pub type Mode = libc::mode_t;
/// Used to mark types as !Sync.
pub type UnsyncMarker = std::marker::PhantomData<Cell<usize>>;
+#[macro_export]
+macro_rules! syscall {
+ ($e:expr) => {{
+ let res = $e;
+ if res < 0 {
+ $crate::errno_result()
+ } else {
+ Ok(res)
+ }
+ }};
+}
+
/// Safe wrapper for `sysconf(_SC_PAGESIZE)`.
#[inline(always)]
pub fn pagesize() -> usize {
@@ -150,25 +168,13 @@ pub fn gettid() -> Pid {
/// Safe wrapper for `getsid(2)`.
pub fn getsid(pid: Option<Pid>) -> Result<Pid> {
// Calling the getsid() sycall is always safe.
- let ret = unsafe { libc::getsid(pid.unwrap_or(0)) } as Pid;
-
- if ret < 0 {
- errno_result()
- } else {
- Ok(ret)
- }
+ syscall!(unsafe { libc::getsid(pid.unwrap_or(0)) } as Pid)
}
/// Wrapper for `setsid(2)`.
pub fn setsid() -> Result<Pid> {
// Safe because the return code is checked.
- let ret = unsafe { libc::setsid() as Pid };
-
- if ret < 0 {
- errno_result()
- } else {
- Ok(ret)
- }
+ syscall!(unsafe { libc::setsid() as Pid })
}
/// Safe wrapper for `geteuid(2)`.
@@ -189,13 +195,21 @@ pub fn getegid() -> Gid {
#[inline(always)]
pub fn chown(path: &CStr, uid: Uid, gid: Gid) -> Result<()> {
// Safe since we pass in a valid string pointer and check the return value.
- let ret = unsafe { libc::chown(path.as_ptr(), uid, gid) };
+ syscall!(unsafe { libc::chown(path.as_ptr(), uid, gid) }).map(|_| ())
+}
- if ret < 0 {
- errno_result()
- } else {
- Ok(())
- }
+/// Safe wrapper for fchmod(2).
+#[inline(always)]
+pub fn fchmod<A: AsRawFd>(fd: &A, mode: Mode) -> Result<()> {
+ // Safe since the function does not operate on pointers and check the return value.
+ syscall!(unsafe { libc::fchmod(fd.as_raw_fd(), mode) }).map(|_| ())
+}
+
+/// Safe wrapper for fchown(2).
+#[inline(always)]
+pub fn fchown<A: AsRawFd>(fd: &A, uid: Uid, gid: Gid) -> Result<()> {
+ // Safe since the function does not operate on pointers and check the return value.
+ syscall!(unsafe { libc::fchown(fd.as_raw_fd(), uid, gid) }).map(|_| ())
}
/// The operation to perform with `flock`.
@@ -220,13 +234,7 @@ pub fn flock(file: &dyn AsRawFd, op: FlockOperation, nonblocking: bool) -> Resul
}
// Safe since we pass in a valid fd and flock operation, and check the return value.
- let ret = unsafe { libc::flock(file.as_raw_fd(), operation) };
-
- if ret < 0 {
- errno_result()
- } else {
- Ok(())
- }
+ syscall!(unsafe { libc::flock(file.as_raw_fd(), operation) }).map(|_| ())
}
/// The operation to perform with `fallocate`.
@@ -268,12 +276,70 @@ pub fn fallocate(
// Safe since we pass in a valid fd and fallocate mode, validate offset and len,
// and check the return value.
- let ret = unsafe { libc::fallocate64(file.as_raw_fd(), mode, offset, len) };
+ syscall!(unsafe { libc::fallocate64(file.as_raw_fd(), mode, offset, len) }).map(|_| ())
+}
+
+/// A trait used to abstract types that provide a process id that can be operated on.
+pub trait AsRawPid {
+ fn as_raw_pid(&self) -> Pid;
+}
+
+impl AsRawPid for Pid {
+ fn as_raw_pid(&self) -> Pid {
+ *self
+ }
+}
+
+impl AsRawPid for std::process::Child {
+ fn as_raw_pid(&self) -> Pid {
+ self.id() as Pid
+ }
+}
+
+/// A logical set of the values *status can take from libc::wait and libc::waitpid.
+pub enum WaitStatus {
+ Continued,
+ Exited(u8),
+ Running,
+ Signaled(Signal),
+ Stopped(Signal),
+}
+
+impl From<c_int> for WaitStatus {
+ fn from(status: c_int) -> WaitStatus {
+ use WaitStatus::*;
+ if libc::WIFEXITED(status) {
+ Exited(libc::WEXITSTATUS(status) as u8)
+ } else if libc::WIFSIGNALED(status) {
+ Signaled(Signal::try_from(libc::WTERMSIG(status)).unwrap())
+ } else if libc::WIFSTOPPED(status) {
+ Stopped(Signal::try_from(libc::WSTOPSIG(status)).unwrap())
+ } else if libc::WIFCONTINUED(status) {
+ Continued
+ } else {
+ Running
+ }
+ }
+}
+
+/// A safe wrapper around waitpid.
+///
+/// On success if a process was reaped, it will be returned as the first value.
+/// The second returned value is the WaitStatus from the libc::waitpid() call.
+///
+/// Note: this can block if libc::WNOHANG is not set and EINTR is not handled internally.
+pub fn wait_for_pid<A: AsRawPid>(pid: A, options: c_int) -> Result<(Option<Pid>, WaitStatus)> {
+ let pid = pid.as_raw_pid();
+ let mut status: c_int = 1;
+ // Safe because status is owned and the error is checked.
+ let ret = unsafe { libc::waitpid(pid, &mut status, options) };
if ret < 0 {
- errno_result()
- } else {
- Ok(())
+ return errno_result();
}
+ Ok((
+ if ret == 0 { None } else { Some(ret) },
+ WaitStatus::from(status),
+ ))
}
/// Reaps a child process that has terminated.
@@ -349,11 +415,7 @@ pub fn pipe(close_on_exec: bool) -> Result<(File, File)> {
/// Returns the new size of the pipe or an error if the OS fails to set the pipe size.
pub fn set_pipe_size(fd: RawFd, size: usize) -> Result<usize> {
// Safe because fcntl with the `F_SETPIPE_SZ` arg doesn't touch memory.
- let ret = unsafe { fcntl(fd, libc::F_SETPIPE_SZ, size as c_int) };
- if ret < 0 {
- return errno_result();
- }
- Ok(ret as usize)
+ syscall!(unsafe { fcntl(fd, libc::F_SETPIPE_SZ, size as c_int) }).map(|ret| ret as usize)
}
/// Test-only function used to create a pipe that is full. The pipe is created, has its size set to
@@ -392,6 +454,35 @@ impl Drop for UnlinkUnixDatagram {
}
}
+/// Used to attempt to clean up a named pipe after it is no longer used.
+pub struct UnlinkUnixListener(pub UnixListener);
+
+impl AsRef<UnixListener> for UnlinkUnixListener {
+ fn as_ref(&self) -> &UnixListener {
+ &self.0
+ }
+}
+
+impl Deref for UnlinkUnixListener {
+ type Target = UnixListener;
+
+ fn deref(&self) -> &UnixListener {
+ &self.0
+ }
+}
+
+impl Drop for UnlinkUnixListener {
+ fn drop(&mut self) {
+ if let Ok(addr) = self.0.local_addr() {
+ if let Some(path) = addr.as_pathname() {
+ if let Err(e) = remove_file(path) {
+ warn!("failed to remove control socket file: {}", e);
+ }
+ }
+ }
+ }
+}
+
/// Verifies that |raw_fd| is actually owned by this process and duplicates it to ensure that
/// we have a unique handle to it.
pub fn validate_raw_fd(raw_fd: RawFd) -> Result<RawFd> {
@@ -439,11 +530,7 @@ pub fn poll_in(fd: &dyn AsRawFd) -> bool {
/// Returns an error if the OS indicates the flags can't be retrieved.
fn get_fd_flags(fd: RawFd) -> Result<c_int> {
// Safe because no third parameter is expected and we check the return result.
- let ret = unsafe { fcntl(fd, F_GETFL) };
- if ret < 0 {
- return errno_result();
- }
- Ok(ret)
+ syscall!(unsafe { fcntl(fd, F_GETFL) })
}
/// Sets the file flags set for the given `RawFD`.
@@ -452,11 +539,7 @@ fn get_fd_flags(fd: RawFd) -> Result<c_int> {
fn set_fd_flags(fd: RawFd, flags: c_int) -> Result<()> {
// Safe because we supply the third parameter and we check the return result.
// fcntlt is trusted not to modify the memory of the calling process.
- let ret = unsafe { fcntl(fd, F_SETFL, flags) };
- if ret < 0 {
- return errno_result();
- }
- Ok(())
+ syscall!(unsafe { fcntl(fd, F_SETFL, flags) }).map(|_| ())
}
/// Performs a logical OR of the given flags with the FD's flags, setting the given bits for the
@@ -493,8 +576,64 @@ pub fn max_timeout() -> Duration {
Duration::new(libc::time_t::max_value() as u64, 999999999)
}
+/// If the given path is of the form /proc/self/fd/N for some N, returns `Ok(Some(N))`. Otherwise
+/// returns `Ok(None)`.
+pub fn safe_descriptor_from_path<P: AsRef<Path>>(path: P) -> Result<Option<SafeDescriptor>> {
+ let path = path.as_ref();
+ if path.parent() == Some(Path::new("/proc/self/fd")) {
+ let raw_descriptor = path
+ .file_name()
+ .and_then(|fd_osstr| fd_osstr.to_str())
+ .and_then(|fd_str| fd_str.parse::<RawFd>().ok())
+ .ok_or_else(|| Error::new(EINVAL))?;
+ let validated_fd = validate_raw_fd(raw_descriptor)?;
+ Ok(Some(
+ // Safe because nothing else has access to validated_fd after this call.
+ unsafe { SafeDescriptor::from_raw_descriptor(validated_fd) },
+ ))
+ } else {
+ Ok(None)
+ }
+}
+
+/// Open the file with the given path, or if it is of the form `/proc/self/fd/N` then just use the
+/// file descriptor.
+///
+/// Note that this will not work properly if the same `/proc/self/fd/N` path is used twice in
+/// different places, as the metadata (including the offset) will be shared between both file
+/// descriptors.
+pub fn open_file<P: AsRef<Path>>(path: P, read_only: bool, o_direct: bool) -> Result<File> {
+ let path = path.as_ref();
+ // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
+ Ok(if let Some(fd) = safe_descriptor_from_path(path)? {
+ fd.into()
+ } else {
+ OpenOptions::new()
+ .custom_flags(if o_direct { O_DIRECT } else { 0 })
+ .write(!read_only)
+ .read(true)
+ .open(path)?
+ })
+}
+
+/// Get the max number of open files allowed by the environment.
+pub fn get_max_open_files() -> Result<u64> {
+ let mut buf = mem::MaybeUninit::<libc::rlimit64>::zeroed();
+
+ // Safe because this will only modify `buf` and we check the return value.
+ let res = unsafe { libc::prlimit64(0, libc::RLIMIT_NOFILE, ptr::null(), buf.as_mut_ptr()) };
+ if res == 0 {
+ // Safe because the kernel guarantees that the struct is fully initialized.
+ let limit = unsafe { buf.assume_init() };
+ Ok(limit.rlim_max)
+ } else {
+ errno_result()
+ }
+}
+
#[cfg(test)]
mod tests {
+ use libc::EBADF;
use std::io::Write;
use super::*;
@@ -509,4 +648,35 @@ mod tests {
tx.write(&[0u8; 8])
.expect_err("Write after fill didn't fail");
}
+
+ #[test]
+ fn safe_descriptor_from_path_valid() {
+ assert!(safe_descriptor_from_path(Path::new("/proc/self/fd/2"))
+ .unwrap()
+ .is_some());
+ }
+
+ #[test]
+ fn safe_descriptor_from_path_invalid_integer() {
+ assert_eq!(
+ safe_descriptor_from_path(Path::new("/proc/self/fd/blah")),
+ Err(Error::new(EINVAL))
+ );
+ }
+
+ #[test]
+ fn safe_descriptor_from_path_invalid_fd() {
+ assert_eq!(
+ safe_descriptor_from_path(Path::new("/proc/self/fd/42")),
+ Err(Error::new(EBADF))
+ );
+ }
+
+ #[test]
+ fn safe_descriptor_from_path_none() {
+ assert_eq!(
+ safe_descriptor_from_path(Path::new("/something/else")).unwrap(),
+ None
+ );
+ }
}
diff --git a/common/sys_util/src/linux/mod.rs b/common/sys_util/src/linux/mod.rs
new file mode 100644
index 000000000..8503daa89
--- /dev/null
+++ b/common/sys_util/src/linux/mod.rs
@@ -0,0 +1,7 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Linux-specific utility modules.
+
+pub mod syslog;
diff --git a/common/sys_util/src/linux/syslog.rs b/common/sys_util/src/linux/syslog.rs
new file mode 100644
index 000000000..05972a3a3
--- /dev/null
+++ b/common/sys_util/src/linux/syslog.rs
@@ -0,0 +1,186 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Implementation of the Syslog trait for Linux.
+
+use std::{
+ fmt,
+ fs::File,
+ io::{Cursor, ErrorKind, Write},
+ mem,
+ os::unix::{
+ io::{AsRawFd, FromRawFd, RawFd},
+ net::UnixDatagram,
+ },
+ ptr::null,
+};
+
+use libc::{
+ closelog, fcntl, localtime_r, openlog, time, time_t, tm, F_GETFD, LOG_NDELAY, LOG_PERROR,
+ LOG_PID, LOG_USER,
+};
+
+use super::super::{
+ getpid,
+ syslog::{Error, Facility, Priority, Syslog},
+};
+
+const SYSLOG_PATH: &str = "/dev/log";
+
+pub struct PlatformSyslog {
+ socket: Option<UnixDatagram>,
+}
+
+impl Syslog for PlatformSyslog {
+ fn new() -> Result<Self, Error> {
+ Ok(Self {
+ socket: Some(openlog_and_get_socket()?),
+ })
+ }
+
+ fn enable(&mut self, enable: bool) -> Result<(), Error> {
+ match self.socket.take() {
+ Some(_) if enable => {}
+ Some(s) => {
+ // Because `openlog_and_get_socket` actually just "borrows" the syslog FD, this module
+ // does not own the syslog connection and therefore should not destroy it.
+ mem::forget(s);
+ }
+ None if enable => {
+ let s = openlog_and_get_socket()?;
+ self.socket = Some(s);
+ }
+ _ => {}
+ }
+ Ok(())
+ }
+
+ fn push_fds(&self, fds: &mut Vec<RawFd>) {
+ fds.extend(self.socket.iter().map(|s| s.as_raw_fd()));
+ }
+
+ fn log(
+ &self,
+ proc_name: Option<&str>,
+ pri: Priority,
+ fac: Facility,
+ file_line: Option<(&str, u32)>,
+ args: fmt::Arguments,
+ ) {
+ const MONTHS: [&str; 12] = [
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+ ];
+
+ let mut buf = [0u8; 1024];
+ if let Some(socket) = &self.socket {
+ let tm = get_localtime();
+ let prifac = (pri as u8) | (fac as u8);
+ let res = {
+ let mut buf_cursor = Cursor::new(&mut buf[..]);
+ write!(
+ &mut buf_cursor,
+ "<{}>{} {:02} {:02}:{:02}:{:02} {}[{}]: ",
+ prifac,
+ MONTHS[tm.tm_mon as usize],
+ tm.tm_mday,
+ tm.tm_hour,
+ tm.tm_min,
+ tm.tm_sec,
+ proc_name.unwrap_or("-"),
+ getpid()
+ )
+ .and_then(|()| {
+ if let Some((file_name, line)) = &file_line {
+ write!(&mut buf_cursor, " [{}:{}] ", file_name, line)
+ } else {
+ Ok(())
+ }
+ })
+ .and_then(|()| write!(&mut buf_cursor, "{}", args))
+ .map(|()| buf_cursor.position() as usize)
+ };
+
+ if let Ok(len) = &res {
+ send_buf(socket, &buf[..*len])
+ }
+ }
+ }
+}
+
+// Uses libc's openlog function to get a socket to the syslogger. By getting the socket this way, as
+// opposed to connecting to the syslogger directly, libc's internal state gets initialized for other
+// libraries (e.g. minijail) that make use of libc's syslog function. Note that this function
+// depends on no other threads or signal handlers being active in this process because they might
+// create FDs.
+//
+// TODO(zachr): Once https://android-review.googlesource.com/470998 lands, there won't be any
+// libraries in use that hard depend on libc's syslogger. Remove this and go back to making the
+// connection directly once minjail is ready.
+fn openlog_and_get_socket() -> Result<UnixDatagram, Error> {
+ // closelog first in case there was already a file descriptor open. Safe because it takes no
+ // arguments and just closes an open file descriptor. Does nothing if the file descriptor
+ // was not already open.
+ unsafe {
+ closelog();
+ }
+
+ // Ordinarily libc's FD for the syslog connection can't be accessed, but we can guess that the
+ // FD that openlog will be getting is the lowest unused FD. To guarantee that an FD is opened in
+ // this function we use the LOG_NDELAY to tell openlog to connect to the syslog now. To get the
+ // lowest unused FD, we open a dummy file (which the manual says will always return the lowest
+ // fd), and then close that fd. Voilà, we now know the lowest numbered FD. The call to openlog
+ // will make use of that FD, and then we just wrap a `UnixDatagram` around it for ease of use.
+ let fd = File::open("/dev/null")
+ .map_err(Error::GetLowestFd)?
+ .as_raw_fd();
+
+ unsafe {
+ // Safe because openlog accesses no pointers because `ident` is null, only valid flags are
+ // used, and it returns no error.
+ openlog(null(), LOG_NDELAY | LOG_PERROR | LOG_PID, LOG_USER);
+ // For safety, ensure the fd we guessed is valid. The `fcntl` call itself only reads the
+ // file descriptor table of the current process, which is trivially safe.
+ if fcntl(fd, F_GETFD) >= 0 {
+ Ok(UnixDatagram::from_raw_fd(fd))
+ } else {
+ Err(Error::InvalidFd)
+ }
+ }
+}
+
+/// Should only be called after `init()` was called.
+fn send_buf(socket: &UnixDatagram, buf: &[u8]) {
+ const SEND_RETRY: usize = 2;
+
+ for _ in 0..SEND_RETRY {
+ match socket.send(buf) {
+ Ok(_) => break,
+ Err(e) => match e.kind() {
+ ErrorKind::ConnectionRefused
+ | ErrorKind::ConnectionReset
+ | ErrorKind::ConnectionAborted
+ | ErrorKind::NotConnected => {
+ let res = socket.connect(SYSLOG_PATH);
+ if res.is_err() {
+ break;
+ }
+ }
+ _ => {}
+ },
+ }
+ }
+}
+
+fn get_localtime() -> tm {
+ unsafe {
+ // Safe because tm is just a struct of plain data.
+ let mut tm: tm = mem::zeroed();
+ let mut now: time_t = 0;
+ // Safe because we give time a valid pointer and can never fail.
+ time(&mut now as *mut _);
+ // Safe because we give localtime_r valid pointers and can never fail.
+ localtime_r(&now, &mut tm as *mut _);
+ tm
+ }
+}
diff --git a/sys_util/src/mmap.rs b/common/sys_util/src/mmap.rs
index 28ad07f57..fe667fe15 100644
--- a/sys_util/src/mmap.rs
+++ b/common/sys_util/src/mmap.rs
@@ -5,68 +5,50 @@
//! The mmap module provides a safe interface to mmap memory and ensures unmap is called when the
//! mmap object leaves scope.
-use std::cmp::min;
-use std::fmt::{self, Display};
-use std::io;
-use std::mem::size_of;
-use std::os::unix::io::AsRawFd;
-use std::ptr::{copy_nonoverlapping, null_mut, read_unaligned, write_unaligned};
-
-use libc::{self, c_int, c_void, read, write};
-
-use data_model::volatile_memory::*;
-use data_model::DataInit;
-
-use crate::{errno, pagesize};
-
-#[derive(Debug)]
+use std::{
+ cmp::min,
+ io,
+ mem::size_of,
+ os::unix::io::AsRawFd,
+ ptr::{copy_nonoverlapping, null_mut, read_unaligned, write_unaligned},
+};
+
+use libc::{
+ c_int, c_void, read, write, {self},
+};
+use remain::sorted;
+use sys_util_core::ExternalMapping;
+
+use data_model::{volatile_memory::*, DataInit};
+
+use super::{pagesize, Error as ErrnoError};
+
+#[sorted]
+#[derive(Debug, thiserror::Error)]
pub enum Error {
- /// `add_fd_mapping` is not supported.
+ #[error("`add_fd_mapping` is unsupported")]
AddFdMappingIsUnsupported,
- /// Requested memory out of range.
+ #[error("requested memory out of range")]
InvalidAddress,
- /// Invalid argument provided when building mmap.
+ #[error("invalid argument provided when creating mapping")]
InvalidArgument,
- /// Requested offset is out of range of `libc::off_t`.
+ #[error("requested offset is out of range of off_t")]
InvalidOffset,
- /// Requested mapping is not page aligned
- NotPageAligned,
- /// Requested memory range spans past the end of the region.
+ #[error("requested memory range spans past the end of the region: offset={0} count={1} region_size={2}")]
InvalidRange(usize, usize, usize),
- /// `mmap` returned the given error.
- SystemCallFailed(errno::Error),
- /// Writing to memory failed
- ReadToMemory(io::Error),
- /// `remove_mapping` is not supported
+ #[error("requested memory is not page aligned")]
+ NotPageAligned,
+ #[error("failed to read from file to memory: {0}")]
+ ReadToMemory(#[source] io::Error),
+ #[error("`remove_mapping` is unsupported")]
RemoveMappingIsUnsupported,
- /// Reading from memory failed
- WriteFromMemory(io::Error),
+ #[error("mmap related system call failed: {0}")]
+ SystemCallFailed(#[source] ErrnoError),
+ #[error("failed to write from memory to file: {0}")]
+ WriteFromMemory(#[source] io::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- AddFdMappingIsUnsupported => write!(f, "`add_fd_mapping` is unsupported"),
- InvalidAddress => write!(f, "requested memory out of range"),
- InvalidArgument => write!(f, "invalid argument provided when creating mapping"),
- InvalidOffset => write!(f, "requested offset is out of range of off_t"),
- NotPageAligned => write!(f, "requested memory is not page aligned"),
- InvalidRange(offset, count, region_size) => write!(
- f,
- "requested memory range spans past the end of the region: offset={} count={} region_size={}",
- offset, count, region_size,
- ),
- SystemCallFailed(e) => write!(f, "mmap related system call failed: {}", e),
- ReadToMemory(e) => write!(f, "failed to read from file to memory: {}", e),
- RemoveMappingIsUnsupported => write!(f, "`remove_mapping` is unsupported"),
- WriteFromMemory(e) => write!(f, "failed to write from memory to file: {}", e),
- }
- }
-}
-
/// Memory access type for anonymous shared memory mapping.
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Protection(c_int);
@@ -187,12 +169,24 @@ impl dyn MappedRegion {
if ret != -1 {
Ok(())
} else {
- Err(Error::SystemCallFailed(errno::Error::last()))
+ Err(Error::SystemCallFailed(ErrnoError::last()))
}
}
}
-/// Wraps an anonymous shared memory mapping in the current process.
+unsafe impl MappedRegion for ExternalMapping {
+ fn as_ptr(&self) -> *mut u8 {
+ self.as_ptr()
+ }
+
+ /// Returns the size of the memory region in bytes.
+ fn size(&self) -> usize {
+ self.size()
+ }
+}
+
+/// Wraps an anonymous shared memory mapping in the current process. Provides
+/// RAII semantics including munmap when no longer needed.
#[derive(Debug)]
pub struct MemoryMapping {
addr: *mut u8,
@@ -395,16 +389,11 @@ impl MemoryMapping {
};
let addr = libc::mmap(addr, size, prot, flags, fd, offset);
if addr == libc::MAP_FAILED {
- return Err(Error::SystemCallFailed(errno::Error::last()));
- }
- // This is safe because we call madvise with a valid address and size, and we check the
- // return value. We only warn about an error because failure here is not fatal to the mmap.
- if libc::madvise(addr, size, libc::MADV_DONTDUMP) == -1 {
- warn!(
- "failed madvise(MADV_DONTDUMP) on mmap: {}",
- errno::Error::last()
- );
+ return Err(Error::SystemCallFailed(ErrnoError::last()));
}
+ // This is safe because we call madvise with a valid address and size.
+ let _ = libc::madvise(addr, size, libc::MADV_DONTDUMP);
+
Ok(MemoryMapping {
addr: addr as *mut u8,
size,
@@ -431,7 +420,7 @@ impl MemoryMapping {
)
};
if ret == -1 {
- Err(Error::SystemCallFailed(errno::Error::last()))
+ Err(Error::SystemCallFailed(ErrnoError::last()))
} else {
Ok(())
}
@@ -449,7 +438,7 @@ impl MemoryMapping {
)
};
if ret == -1 {
- return Err(Error::SystemCallFailed(errno::Error::last()));
+ return Err(Error::SystemCallFailed(ErrnoError::last()));
}
Ok(())
}
@@ -786,15 +775,30 @@ impl MemoryMappingArena {
MemoryMapping::new_protection(size, Protection::none().set_read()).map(From::from)
}
+ /// Anonymously maps `size` bytes at `offset` bytes from the start of the arena
+ /// with `prot` protections. `offset` must be page aligned.
+ ///
+ /// # Arguments
+ /// * `offset` - Page aligned offset into the arena in bytes.
+ /// * `size` - Size of memory region in bytes.
+ /// * `prot` - Protection (e.g. readable/writable) of the memory region.
+ pub fn add_anon_protection(
+ &mut self,
+ offset: usize,
+ size: usize,
+ prot: Protection,
+ ) -> Result<()> {
+ self.try_add(offset, size, prot, None)
+ }
+
/// Anonymously maps `size` bytes at `offset` bytes from the start of the arena.
/// `offset` must be page aligned.
///
/// # Arguments
/// * `offset` - Page aligned offset into the arena in bytes.
/// * `size` - Size of memory region in bytes.
- /// * `fd` - File descriptor to mmap from.
pub fn add_anon(&mut self, offset: usize, size: usize) -> Result<()> {
- self.try_add(offset, size, Protection::read_write(), None)
+ self.add_anon_protection(offset, size, Protection::read_write())
}
/// Maps `size` bytes from the start of the given `fd` at `offset` bytes from
@@ -943,8 +947,7 @@ impl Drop for MemoryMappingArena {
#[cfg(test)]
mod tests {
- use super::*;
- use crate::Descriptor;
+ use super::{super::Descriptor, *};
use data_model::{VolatileMemory, VolatileMemoryError};
use tempfile::tempfile;
@@ -1139,10 +1142,10 @@ mod tests {
let size = 0x40000;
let m = MemoryMappingArena::new(size).unwrap();
let ps = pagesize();
- MappedRegion::msync(&m, 0, ps).unwrap();
- MappedRegion::msync(&m, 0, size).unwrap();
- MappedRegion::msync(&m, ps, size - ps).unwrap();
- let res = MappedRegion::msync(&m, ps, size).unwrap_err();
+ <dyn MappedRegion>::msync(&m, 0, ps).unwrap();
+ <dyn MappedRegion>::msync(&m, 0, size).unwrap();
+ <dyn MappedRegion>::msync(&m, ps, size - ps).unwrap();
+ let res = <dyn MappedRegion>::msync(&m, ps, size).unwrap_err();
match res {
Error::InvalidAddress => {}
e => panic!("unexpected error: {}", e),
diff --git a/sys_util/src/net.rs b/common/sys_util/src/net.rs
index b59fb0f21..8705ccd29 100644
--- a/sys_util/src/net.rs
+++ b/common/sys_util/src/net.rs
@@ -2,20 +2,25 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::ffi::OsString;
-use std::fs::remove_file;
-use std::io;
-use std::mem::{self, size_of};
-use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6, TcpListener, TcpStream, ToSocketAddrs};
-use std::ops::Deref;
-use std::os::unix::{
- ffi::{OsStrExt, OsStringExt},
- io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
+use std::{
+ cmp::Ordering,
+ convert::TryFrom,
+ ffi::OsString,
+ fs::remove_file,
+ io,
+ mem::{
+ size_of, {self},
+ },
+ net::{SocketAddr, SocketAddrV4, SocketAddrV6, TcpListener, TcpStream, ToSocketAddrs},
+ ops::Deref,
+ os::unix::{
+ ffi::{OsStrExt, OsStringExt},
+ io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
+ },
+ path::{Path, PathBuf},
+ ptr::null_mut,
+ time::{Duration, Instant},
};
-use std::path::Path;
-use std::path::PathBuf;
-use std::ptr::null_mut;
-use std::time::Duration;
use libc::{
c_int, in6_addr, in_addr, recvfrom, sa_family_t, sockaddr, sockaddr_in, sockaddr_in6,
@@ -23,8 +28,10 @@ use libc::{
};
use serde::{Deserialize, Serialize};
-use crate::sock_ctrl_msg::{ScmSocket, SCM_SOCKET_MAX_FD_COUNT};
-use crate::{AsRawDescriptor, RawDescriptor};
+use super::{
+ sock_ctrl_msg::{ScmSocket, SCM_SOCKET_MAX_FD_COUNT},
+ AsRawDescriptor, Error, FromRawDescriptor, IntoRawDescriptor, RawDescriptor,
+};
/// Assist in handling both IP version 4 and IP version 6.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@@ -332,9 +339,9 @@ fn sockaddr_un<P: AsRef<Path>>(path: P) -> io::Result<(libc::sockaddr_un, libc::
}
/// A Unix `SOCK_SEQPACKET` socket point to given `path`
-#[derive(Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize)]
pub struct UnixSeqpacket {
- #[serde(with = "crate::with_raw_descriptor")]
+ #[serde(with = "super::with_raw_descriptor")]
fd: RawFd,
}
@@ -531,7 +538,8 @@ impl UnixSeqpacket {
let packet_size = self.next_packet_size()?;
let mut buf = vec![0; packet_size];
let mut fd_buf = vec![-1; SCM_SOCKET_MAX_FD_COUNT];
- let (read_bytes, read_fds) = self.recv_with_fds(&mut buf, &mut fd_buf)?;
+ let (read_bytes, read_fds) =
+ self.recv_with_fds(io::IoSliceMut::new(&mut buf), &mut fd_buf)?;
buf.resize(read_bytes, 0);
fd_buf.resize(read_fds, -1);
Ok((buf, fd_buf))
@@ -604,6 +612,20 @@ impl FromRawFd for UnixSeqpacket {
}
}
+impl FromRawDescriptor for UnixSeqpacket {
+ unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Self {
+ Self { fd: descriptor }
+ }
+}
+
+impl IntoRawDescriptor for UnixSeqpacket {
+ fn into_raw_descriptor(self) -> RawDescriptor {
+ let fd = self.fd;
+ mem::forget(self);
+ fd
+ }
+}
+
impl AsRawFd for UnixSeqpacket {
fn as_raw_fd(&self) -> RawFd {
self.fd
@@ -622,6 +644,30 @@ impl AsRawDescriptor for UnixSeqpacket {
}
}
+impl io::Read for UnixSeqpacket {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.recv(buf)
+ }
+}
+
+impl io::Write for UnixSeqpacket {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.send(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+impl IntoRawFd for UnixSeqpacket {
+ fn into_raw_fd(self) -> RawFd {
+ let fd = self.fd;
+ mem::forget(self);
+ fd
+ }
+}
+
/// Like a `UnixListener` but for accepting `UnixSeqpacket` type sockets.
pub struct UnixSeqpacketListener {
fd: RawFd,
@@ -669,6 +715,32 @@ impl UnixSeqpacketListener {
Ok(unsafe { UnixSeqpacket::from_raw_fd(ret) })
}
+ pub fn accept_with_timeout(&self, timeout: Duration) -> io::Result<UnixSeqpacket> {
+ let start = Instant::now();
+
+ loop {
+ let mut fds = libc::pollfd {
+ fd: self.fd,
+ events: libc::POLLIN,
+ revents: 0,
+ };
+ let elapsed = Instant::now().saturating_duration_since(start);
+ let remaining = timeout.checked_sub(elapsed).unwrap_or(Duration::ZERO);
+ let cur_timeout_ms = i32::try_from(remaining.as_millis()).unwrap_or(i32::MAX);
+ // Safe because we give a valid pointer to a list (of 1) FD and we check
+ // the return value.
+ match unsafe { libc::poll(&mut fds, 1, cur_timeout_ms) }.cmp(&0) {
+ Ordering::Greater => return self.accept(),
+ Ordering::Equal => return Err(io::Error::from_raw_os_error(libc::ETIMEDOUT)),
+ Ordering::Less => {
+ if Error::last() != Error::new(libc::EINTR) {
+ return Err(io::Error::last_os_error());
+ }
+ }
+ }
+ }
+ }
+
/// Gets the path that this listener is bound to.
pub fn path(&self) -> io::Result<PathBuf> {
let mut addr = libc::sockaddr_un {
@@ -767,9 +839,7 @@ impl Drop for UnlinkUnixSeqpacketListener {
#[cfg(test)]
mod tests {
use super::*;
- use std::env;
- use std::io::ErrorKind;
- use std::path::PathBuf;
+ use std::{env, io::ErrorKind, path::PathBuf};
fn tmpdir() -> PathBuf {
env::temp_dir()
@@ -798,6 +868,7 @@ mod tests {
}
#[test]
+ #[allow(clippy::unnecessary_cast)]
fn sockaddr_un_pass() {
let path_size = 50;
let (addr, len) =
@@ -847,6 +918,40 @@ mod tests {
}
#[test]
+ fn unix_seqpacket_path_listener_accept_with_timeout() {
+ let mut socket_path = tmpdir();
+ socket_path.push("path_listerner_accept_with_timeout");
+ let listener = UnlinkUnixSeqpacketListener(
+ UnixSeqpacketListener::bind(&socket_path)
+ .expect("failed to create UnixSeqpacketListener"),
+ );
+
+ for d in [Duration::from_millis(10), Duration::ZERO] {
+ let _ = listener.accept_with_timeout(d).expect_err(&format!(
+ "UnixSeqpacket::accept_with_timeout {:?} connected",
+ d
+ ));
+
+ let s1 = UnixSeqpacket::connect(socket_path.as_path())
+ .unwrap_or_else(|_| panic!("UnixSeqpacket::connect {:?} failed", d));
+
+ let s2 = listener
+ .accept_with_timeout(d)
+ .unwrap_or_else(|_| panic!("UnixSeqpacket::accept {:?} failed", d));
+
+ let data1 = &[0, 1, 2, 3, 4];
+ let data2 = &[10, 11, 12, 13, 14];
+ s2.send(data2).expect("failed to send data2");
+ s1.send(data1).expect("failed to send data1");
+ let recv_data = &mut [0; 5];
+ s2.recv(recv_data).expect("failed to recv data");
+ assert_eq!(data1, recv_data);
+ s1.recv(recv_data).expect("failed to recv data");
+ assert_eq!(data2, recv_data);
+ }
+ }
+
+ #[test]
fn unix_seqpacket_path_listener_accept() {
let mut socket_path = tmpdir();
socket_path.push("path_listerner_accept");
diff --git a/common/sys_util/src/netlink.rs b/common/sys_util/src/netlink.rs
new file mode 100644
index 000000000..897e8d570
--- /dev/null
+++ b/common/sys_util/src/netlink.rs
@@ -0,0 +1,501 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{alloc::Layout, mem::MaybeUninit, os::unix::io::AsRawFd, str};
+
+use data_model::DataInit;
+use libc::EINVAL;
+
+use sys_util_core::LayoutAllocation;
+
+use super::{
+ errno_result, getpid, AsRawDescriptor, Error, FromRawDescriptor, RawDescriptor, Result,
+ SafeDescriptor,
+};
+
+macro_rules! debug_pr {
+ // By default debugs are suppressed, to enabled them replace macro body with:
+ // $($args:tt)+) => (println!($($args)*))
+ ($($args:tt)+) => {};
+}
+
+const NLMSGHDR_SIZE: usize = std::mem::size_of::<NlMsgHdr>();
+const GENL_HDRLEN: usize = std::mem::size_of::<GenlMsgHdr>();
+const NLA_HDRLEN: usize = std::mem::size_of::<NlAttr>();
+const NLATTR_ALIGN_TO: usize = 4;
+
+// Custom nlmsghdr struct that can be declared DataInit.
+#[repr(C)]
+#[derive(Copy, Clone)]
+struct NlMsgHdr {
+ pub nlmsg_len: u32,
+ pub nlmsg_type: u16,
+ pub nlmsg_flags: u16,
+ pub nlmsg_seq: u32,
+ pub nlmsg_pid: u32,
+}
+unsafe impl DataInit for NlMsgHdr {}
+
+/// Netlink attribute struct, can be used by netlink consumer
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct NlAttr {
+ pub len: u16,
+ pub _type: u16,
+}
+unsafe impl DataInit for NlAttr {}
+
+/// Generic netlink header struct, can be used by netlink consumer
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct GenlMsgHdr {
+ pub cmd: u8,
+ pub version: u8,
+ pub reserved: u16,
+}
+unsafe impl DataInit for GenlMsgHdr {}
+
+/// A single netlink message, including its header and data.
+pub struct NetlinkMessage<'a> {
+ pub _type: u16,
+ pub flags: u16,
+ pub seq: u32,
+ pub pid: u32,
+ pub data: &'a [u8],
+}
+
+pub struct NlAttrWithData<'a> {
+ pub len: u16,
+ pub _type: u16,
+ pub data: &'a [u8],
+}
+
+fn nlattr_align(offset: usize) -> usize {
+ return (offset + NLATTR_ALIGN_TO - 1) & !(NLATTR_ALIGN_TO - 1);
+}
+
+/// Iterator over `struct NlAttr` as received from a netlink socket.
+pub struct NetlinkGenericDataIter<'a> {
+ // `data` must be properly aligned for NlAttr.
+ data: &'a [u8],
+}
+
+impl<'a> Iterator for NetlinkGenericDataIter<'a> {
+ type Item = NlAttrWithData<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.data.len() < NLA_HDRLEN {
+ return None;
+ }
+ let nl_hdr = NlAttr::from_slice(&self.data[..NLA_HDRLEN])?;
+
+ // Make sure NlAtrr fits
+ let nl_data_len = nl_hdr.len as usize;
+ if nl_data_len < NLA_HDRLEN || nl_data_len > self.data.len() {
+ return None;
+ }
+
+ // Get data related to processed NlAttr
+ let data_start = NLA_HDRLEN;
+ let data = &self.data[data_start..nl_data_len];
+
+ // Get next NlAttr
+ let next_hdr = nlattr_align(nl_data_len);
+ if next_hdr >= self.data.len() {
+ self.data = &[];
+ } else {
+ self.data = &self.data[next_hdr..];
+ }
+
+ Some(NlAttrWithData {
+ _type: nl_hdr._type,
+ len: nl_hdr.len,
+ data,
+ })
+ }
+}
+
+/// Iterator over `struct nlmsghdr` as received from a netlink socket.
+pub struct NetlinkMessageIter<'a> {
+ // `data` must be properly aligned for nlmsghdr.
+ data: &'a [u8],
+}
+
+impl<'a> Iterator for NetlinkMessageIter<'a> {
+ type Item = NetlinkMessage<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.data.len() < NLMSGHDR_SIZE {
+ return None;
+ }
+ let hdr = NlMsgHdr::from_slice(&self.data[..NLMSGHDR_SIZE])?;
+
+ // NLMSG_OK
+ let msg_len = hdr.nlmsg_len as usize;
+ if msg_len < NLMSGHDR_SIZE || msg_len > self.data.len() {
+ return None;
+ }
+
+ // NLMSG_DATA
+ let data_start = NLMSGHDR_SIZE;
+ let data = &self.data[data_start..msg_len];
+
+ // NLMSG_NEXT
+ let align_to = std::mem::align_of::<NlMsgHdr>();
+ let next_hdr = (msg_len + align_to - 1) & !(align_to - 1);
+ if next_hdr >= self.data.len() {
+ self.data = &[];
+ } else {
+ self.data = &self.data[next_hdr..];
+ }
+
+ Some(NetlinkMessage {
+ _type: hdr.nlmsg_type,
+ flags: hdr.nlmsg_flags,
+ seq: hdr.nlmsg_seq,
+ pid: hdr.nlmsg_pid,
+ data,
+ })
+ }
+}
+
+/// Safe wrapper for `NETLINK_GENERIC` netlink sockets.
+pub struct NetlinkGenericSocket {
+ sock: SafeDescriptor,
+}
+
+impl AsRawDescriptor for NetlinkGenericSocket {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.sock.as_raw_descriptor()
+ }
+}
+
+impl NetlinkGenericSocket {
+ /// Create and bind a new `NETLINK_GENERIC` socket.
+ pub fn new(nl_groups: u32) -> Result<Self> {
+ // Safe because we check the return value and convert the raw fd into a SafeDescriptor.
+ let sock = unsafe {
+ let fd = libc::socket(
+ libc::AF_NETLINK,
+ libc::SOCK_RAW | libc::SOCK_CLOEXEC,
+ libc::NETLINK_GENERIC,
+ );
+ if fd < 0 {
+ return errno_result();
+ }
+
+ SafeDescriptor::from_raw_descriptor(fd)
+ };
+
+ // This MaybeUninit dance is needed because sockaddr_nl has a private padding field and
+ // doesn't implement Default. Safe because all 0s is valid data for sockaddr_nl.
+ let mut sa = unsafe { MaybeUninit::<libc::sockaddr_nl>::zeroed().assume_init() };
+ sa.nl_family = libc::AF_NETLINK as libc::sa_family_t;
+ sa.nl_groups = nl_groups;
+
+ // Safe because we pass a descriptor that we own and valid pointer/size for sockaddr.
+ unsafe {
+ let res = libc::bind(
+ sock.as_raw_fd(),
+ &sa as *const libc::sockaddr_nl as *const libc::sockaddr,
+ std::mem::size_of_val(&sa) as libc::socklen_t,
+ );
+ if res < 0 {
+ return errno_result();
+ }
+ }
+
+ Ok(NetlinkGenericSocket { sock })
+ }
+
+ /// Receive messages from the netlink socket.
+ pub fn recv(&self) -> Result<NetlinkGenericRead> {
+ let buf_size = 8192; // TODO(dverkamp): make this configurable?
+
+ // Create a buffer with sufficient alignment for nlmsghdr.
+ let layout = Layout::from_size_align(buf_size, std::mem::align_of::<NlMsgHdr>())
+ .map_err(|_| Error::new(EINVAL))?;
+ let allocation = LayoutAllocation::uninitialized(layout);
+
+ // Safe because we pass a valid, owned socket fd and a valid pointer/size for the buffer.
+ let bytes_read = unsafe {
+ let res = libc::recv(
+ self.sock.as_raw_fd(),
+ allocation.as_ptr() as *mut libc::c_void,
+ buf_size,
+ 0,
+ );
+ if res < 0 {
+ return errno_result();
+ }
+ res as usize
+ };
+
+ Ok(NetlinkGenericRead {
+ allocation,
+ len: bytes_read,
+ })
+ }
+
+ pub fn family_name_query(&self, family_name: String) -> Result<NetlinkGenericRead> {
+ let buf_size = 1024;
+ debug_pr!(
+ "preparing query for family name {}, len {}",
+ family_name,
+ family_name.len()
+ );
+
+ // Create a buffer with sufficient alignment for nlmsghdr.
+ let layout = Layout::from_size_align(buf_size, std::mem::align_of::<NlMsgHdr>())
+ .map_err(|_| Error::new(EINVAL))
+ .unwrap();
+ let mut allocation = LayoutAllocation::zeroed(layout);
+
+ // Safe because the data in allocation was initialized up to `buf_size` and is
+ // sufficiently aligned.
+ let data = unsafe { allocation.as_mut_slice(buf_size) };
+
+ // Prepare the netlink message header
+ let mut hdr =
+ &mut NlMsgHdr::from_mut_slice(&mut data[..NLMSGHDR_SIZE]).expect("failed to unwrap");
+ hdr.nlmsg_len = NLMSGHDR_SIZE as u32 + GENL_HDRLEN as u32;
+ hdr.nlmsg_len += NLA_HDRLEN as u32 + family_name.len() as u32 + 1;
+ hdr.nlmsg_flags = libc::NLM_F_REQUEST as u16;
+ hdr.nlmsg_type = libc::GENL_ID_CTRL as u16;
+ hdr.nlmsg_pid = getpid() as u32;
+
+ // Prepare generic netlink message header
+ let genl_hdr_end = NLMSGHDR_SIZE + GENL_HDRLEN;
+ let genl_hdr = GenlMsgHdr::from_mut_slice(&mut data[NLMSGHDR_SIZE..genl_hdr_end])
+ .expect("unable to get GenlMsgHdr from slice");
+ genl_hdr.cmd = libc::CTRL_CMD_GETFAMILY as u8;
+ genl_hdr.version = 0x1;
+
+ // Netlink attributes
+ let nlattr_start = genl_hdr_end;
+ let nlattr_end = nlattr_start + NLA_HDRLEN;
+ let nl_attr = NlAttr::from_mut_slice(&mut data[nlattr_start..nlattr_end])
+ .expect("unable to get NlAttr from slice");
+ nl_attr._type = libc::CTRL_ATTR_FAMILY_NAME as u16;
+ nl_attr.len = family_name.len() as u16 + 1 + NLA_HDRLEN as u16;
+
+ // Fill the message payload with the family name
+ let payload_start = nlattr_end;
+ let payload_end = payload_start + family_name.len();
+ data[payload_start..payload_end].copy_from_slice(family_name.as_bytes());
+
+ // Safe because we pass a valid, owned socket fd and a valid pointer/size for the buffer.
+ let _bytes_read = unsafe {
+ let res = libc::send(
+ self.sock.as_raw_fd(),
+ allocation.as_ptr() as *mut libc::c_void,
+ payload_end + 1,
+ 0,
+ );
+ if res < 0 {
+ error!("failed to send get_family_cmd");
+ return errno_result();
+ }
+ };
+
+ // Return the answer
+ match self.recv() {
+ Ok(msg) => return Ok(msg),
+ Err(e) => {
+ error!("recv get_family returned with error {}", e);
+ return Err(e);
+ }
+ };
+ }
+}
+
+fn parse_ctrl_group_name_and_id(
+ nested_nl_attr_data: NetlinkGenericDataIter,
+ group_name: &str,
+) -> Option<u32> {
+ let mut mcast_group_id: Option<u32> = None;
+
+ for nested_nl_attr in nested_nl_attr_data {
+ debug_pr!(
+ "\t\tmcast_grp: nlattr type {}, len {}",
+ nested_nl_attr._type,
+ nested_nl_attr.len
+ );
+
+ if nested_nl_attr._type == libc::CTRL_ATTR_MCAST_GRP_ID as u16 {
+ mcast_group_id = Some(u32::from_ne_bytes(nested_nl_attr.data.try_into().unwrap()));
+ debug_pr!("\t\t mcast group_id {}", mcast_group_id?);
+ }
+
+ if nested_nl_attr._type == libc::CTRL_ATTR_MCAST_GRP_NAME as u16 {
+ debug_pr!(
+ "\t\t mcast group name {}",
+ strip_padding(&nested_nl_attr.data)
+ );
+
+ // If the group name match and the group_id was set in previous iteration, return,
+ // valid for group_name, group_id
+ if group_name.eq(strip_padding(nested_nl_attr.data)) && mcast_group_id.is_some() {
+ debug_pr!(
+ "\t\t Got what we were looking for group_id = {} for {}",
+ mcast_group_id?,
+ group_name
+ );
+
+ return mcast_group_id;
+ }
+ }
+ }
+
+ None
+}
+
+/// Parse CTRL_ATTR_MCAST_GROUPS data in order to get multicast group id
+///
+/// On success, returns group_id for a given `group_name`
+///
+/// # Arguments
+///
+/// * `nl_attr_area` - Nested attributes area (CTRL_ATTR_MCAST_GROUPS data), where nl_attr's
+/// corresponding to specific groups are embed
+/// * `group_name` - String with group_name for which we are looking group_id
+///
+/// the CTRL_ATTR_MCAST_GROUPS data has nested attributes. Each of nested attribute is per
+/// multicast group attributes, which have another nested attributes: CTRL_ATTR_MCAST_GRP_NAME and
+/// CTRL_ATTR_MCAST_GRP_ID. Need to parse all of them to get mcast group id for a given group_name..
+///
+/// Illustrated layout:
+/// CTRL_ATTR_MCAST_GROUPS:
+/// GR1 (nl_attr._type = 1):
+/// CTRL_ATTR_MCAST_GRP_ID,
+/// CTRL_ATTR_MCAST_GRP_NAME,
+/// GR2 (nl_attr._type = 2):
+/// CTRL_ATTR_MCAST_GRP_ID,
+/// CTRL_ATTR_MCAST_GRP_NAME,
+/// ..
+///
+/// Unfortunately kernel implementation uses `nla_nest_start_noflag` for that
+/// purpose, which means that it never marked their nest attributes with NLA_F_NESTED flag.
+/// Therefore all this nesting stages need to be deduced based on specific nl_attr type.
+fn parse_ctrl_mcast_group_id(
+ nl_attr_area: NetlinkGenericDataIter,
+ group_name: &str,
+) -> Option<u32> {
+ // There may be multiple nested multicast groups, go through all of them.
+ // Each of nested group, has other nested nlattr:
+ // CTRL_ATTR_MCAST_GRP_ID
+ // CTRL_ATTR_MCAST_GRP_NAME
+ //
+ // which are further proceed by parse_ctrl_group_name_and_id
+ for nested_gr_nl_attr in nl_attr_area {
+ debug_pr!(
+ "\tmcast_groups: nlattr type(gr_nr) {}, len {}",
+ nested_gr_nl_attr._type,
+ nested_gr_nl_attr.len
+ );
+
+ let netlink_nested_attr = NetlinkGenericDataIter {
+ data: nested_gr_nl_attr.data,
+ };
+
+ if let Some(mcast_group_id) = parse_ctrl_group_name_and_id(netlink_nested_attr, group_name)
+ {
+ return Some(mcast_group_id);
+ }
+ }
+
+ None
+}
+
+// Like `CStr::from_bytes_with_nul` but strips any bytes starting from first '\0'-byte and
+// returns &str. Panics if `b` doesn't contain any '\0' bytes.
+fn strip_padding(b: &[u8]) -> &str {
+ // It would be nice if we could use memchr here but that's locked behind an unstable gate.
+ let pos = b
+ .iter()
+ .position(|&c| c == 0)
+ .expect("`b` doesn't contain any nul bytes");
+
+ str::from_utf8(&b[..pos]).unwrap()
+}
+
+pub struct NetlinkGenericRead {
+ allocation: LayoutAllocation,
+ len: usize,
+}
+
+impl NetlinkGenericRead {
+ pub fn iter(&self) -> NetlinkMessageIter {
+ // Safe because the data in allocation was initialized up to `self.len` by `recv()` and is
+ // sufficiently aligned.
+ let data = unsafe { &self.allocation.as_slice(self.len) };
+ NetlinkMessageIter { data }
+ }
+
+ /// Parse NetlinkGeneric response in order to get multicast group id
+ ///
+ /// On success, returns group_id for a given `group_name`
+ ///
+ /// # Arguments
+ ///
+ /// * `group_name` - String with group_name for which we are looking group_id
+ ///
+ /// Response from family_name_query (CTRL_CMD_GETFAMILY) is a netlink message with multiple
+ /// attributes encapsulated (some of them are nested). An example response layout is
+ /// illustrated below:
+ ///
+ /// {
+ /// CTRL_ATTR_FAMILY_NAME
+ /// CTRL_ATTR_FAMILY_ID
+ /// CTRL_ATTR_VERSION
+ /// ...
+ /// CTRL_ATTR_MCAST_GROUPS {
+ /// GR1 (nl_attr._type = 1) {
+ /// CTRL_ATTR_MCAST_GRP_ID *we need parse this attr to obtain group id used for
+ /// the group mask
+ /// CTRL_ATTR_MCAST_GRP_NAME *group_name that we need to match with
+ /// }
+ /// GR2 (nl_attr._type = 2) {
+ /// CTRL_ATTR_MCAST_GRP_ID
+ /// CTRL_ATTR_MCAST_GRP_NAME
+ /// }
+ /// ...
+ /// }
+ /// }
+ ///
+ pub fn get_multicast_group_id(&self, group_name: String) -> Option<u32> {
+ for netlink_msg in self.iter() {
+ debug_pr!(
+ "received type: {}, flags {}, pid {}, data {:?}",
+ netlink_msg._type,
+ netlink_msg.flags,
+ netlink_msg.pid,
+ netlink_msg.data
+ );
+
+ if netlink_msg._type != libc::GENL_ID_CTRL as u16 {
+ error!("Received not a generic netlink controller msg");
+ return None;
+ }
+
+ let netlink_data = NetlinkGenericDataIter {
+ data: &netlink_msg.data[GENL_HDRLEN..],
+ };
+ for nl_attr in netlink_data {
+ debug_pr!("nl_attr type {}, len {}", nl_attr._type, nl_attr.len);
+
+ if nl_attr._type == libc::CTRL_ATTR_MCAST_GROUPS as u16 {
+ let netlink_nested_attr = NetlinkGenericDataIter { data: nl_attr.data };
+
+ if let Some(mcast_group_id) =
+ parse_ctrl_mcast_group_id(netlink_nested_attr, &group_name)
+ {
+ return Some(mcast_group_id);
+ }
+ }
+ }
+ }
+ None
+ }
+}
diff --git a/sys_util/src/poll.rs b/common/sys_util/src/poll.rs
index 50af02984..f720da7dd 100644
--- a/sys_util/src/poll.rs
+++ b/common/sys_util/src/poll.rs
@@ -2,24 +2,24 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::cell::{Cell, Ref, RefCell};
-use std::cmp::min;
-use std::fs::File;
-use std::i32;
-use std::i64;
-use std::marker::PhantomData;
-use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
-use std::ptr::null_mut;
-use std::slice;
-use std::thread;
-use std::time::Duration;
+use std::{
+ cell::{Cell, Ref, RefCell},
+ cmp::min,
+ fs::File,
+ i32, i64,
+ marker::PhantomData,
+ os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
+ ptr::null_mut,
+ slice, thread,
+ time::Duration,
+};
use libc::{
c_int, epoll_create1, epoll_ctl, epoll_event, epoll_wait, EPOLLHUP, EPOLLIN, EPOLLOUT,
- EPOLL_CLOEXEC, EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD,
+ EPOLLRDHUP, EPOLL_CLOEXEC, EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD,
};
-use crate::{errno_result, Result};
+use super::{errno_result, Result};
const POLL_CONTEXT_MAX_EVENTS: usize = 16;
@@ -139,7 +139,7 @@ impl<'a, T: PollToken> PollEvent<'a, T> {
/// True if the `fd` associated with this token in `PollContext::add` has been hungup on.
pub fn hungup(&self) -> bool {
- self.event.events & (EPOLLHUP as u32) != 0
+ self.event.events & ((EPOLLHUP | EPOLLRDHUP) as u32) != 0
}
}
@@ -218,7 +218,7 @@ impl<'a, T: PollToken> PollEvents<'a, T> {
/// Iterates over each hungup event.
pub fn iter_hungup(&self) -> PollEventIter<slice::Iter<epoll_event>, T> {
PollEventIter {
- mask: EPOLLHUP as u32,
+ mask: (EPOLLHUP | EPOLLRDHUP) as u32,
iter: self.events[..self.count].iter(),
tokens: PhantomData,
}
@@ -691,11 +691,9 @@ impl<T: PollToken> IntoRawFd for PollContext<T> {
#[cfg(test)]
mod tests {
- use super::*;
- use crate::EventFd;
+ use super::{super::EventFd, *};
use poll_token_derive::PollToken;
- use std::os::unix::net::UnixStream;
- use std::time::Instant;
+ use std::{os::unix::net::UnixStream, time::Instant};
#[test]
fn poll_context() {
diff --git a/common/sys_util/src/priority.rs b/common/sys_util/src/priority.rs
new file mode 100644
index 000000000..e02ab753c
--- /dev/null
+++ b/common/sys_util/src/priority.rs
@@ -0,0 +1,38 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::{errno_result, Result};
+
+/// Enables real time thread priorities in the current thread up to `limit`.
+pub fn set_rt_prio_limit(limit: u64) -> Result<()> {
+ let rt_limit_arg = libc::rlimit {
+ rlim_cur: limit as libc::rlim_t,
+ rlim_max: limit as libc::rlim_t,
+ };
+ // Safe because the kernel doesn't modify memory that is accessible to the process here.
+ let res = unsafe { libc::setrlimit(libc::RLIMIT_RTPRIO, &rt_limit_arg) };
+
+ if res != 0 {
+ errno_result()
+ } else {
+ Ok(())
+ }
+}
+
+/// Sets the current thread to be scheduled using the round robin real time class with `priority`.
+pub fn set_rt_round_robin(priority: i32) -> Result<()> {
+ let sched_param = libc::sched_param {
+ sched_priority: priority,
+ };
+
+ // Safe because the kernel doesn't modify memory that is accessible to the process here.
+ let res =
+ unsafe { libc::pthread_setschedparam(libc::pthread_self(), libc::SCHED_RR, &sched_param) };
+
+ if res != 0 {
+ errno_result()
+ } else {
+ Ok(())
+ }
+}
diff --git a/sys_util/src/rand.rs b/common/sys_util/src/rand.rs
index 7c7799397..3a264c063 100644
--- a/sys_util/src/rand.rs
+++ b/common/sys_util/src/rand.rs
@@ -4,15 +4,11 @@
//! Rust implementation of functionality parallel to libchrome's base/rand_util.h.
-use std::thread::sleep;
-use std::time::Duration;
+use std::{thread::sleep, time::Duration};
use libc::{c_uint, c_void};
-use crate::{
- errno::{errno_result, Result},
- handle_eintr_errno,
-};
+use super::{errno_result, handle_eintr_errno, Result};
/// How long to wait before calling getrandom again if it does not return
/// enough bytes.
diff --git a/common/sys_util/src/raw_fd.rs b/common/sys_util/src/raw_fd.rs
new file mode 100644
index 000000000..05a762f28
--- /dev/null
+++ b/common/sys_util/src/raw_fd.rs
@@ -0,0 +1,16 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Utility file to provide a slightly safer Fd type that cannot be confused with c_int.
+// Also useful for situations that require something that is `AsRawFd` but
+// where we don't want to store more than the fd.
+
+use std::os::unix::io::{AsRawFd, RawFd};
+
+pub struct Fd(pub RawFd);
+impl AsRawFd for Fd {
+ fn as_raw_fd(&self) -> RawFd {
+ self.0
+ }
+}
diff --git a/common/sys_util/src/read_dir.rs b/common/sys_util/src/read_dir.rs
new file mode 100644
index 000000000..7ee24b4fe
--- /dev/null
+++ b/common/sys_util/src/read_dir.rs
@@ -0,0 +1,148 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{ffi::CStr, io::Result, mem::size_of, os::unix::io::AsRawFd};
+
+use data_model::DataInit;
+
+use super::syscall;
+
+#[repr(C, packed)]
+#[derive(Clone, Copy)]
+struct LinuxDirent64 {
+ d_ino: libc::ino64_t,
+ d_off: libc::off64_t,
+ d_reclen: libc::c_ushort,
+ d_ty: libc::c_uchar,
+}
+unsafe impl DataInit for LinuxDirent64 {}
+
+pub struct DirEntry<'r> {
+ pub ino: libc::ino64_t,
+ pub offset: u64,
+ pub type_: u8,
+ pub name: &'r CStr,
+}
+
+pub struct ReadDir<'d, D> {
+ buf: [u8; 256],
+ dir: &'d mut D,
+ current: usize,
+ end: usize,
+}
+
+impl<'d, D: AsRawFd> ReadDir<'d, D> {
+ /// Return the next directory entry. This is implemented as a separate method rather than via
+ /// the `Iterator` trait because rust doesn't currently support generic associated types.
+ #[allow(clippy::should_implement_trait)]
+ pub fn next(&mut self) -> Option<Result<DirEntry>> {
+ if self.current >= self.end {
+ let res: Result<libc::c_long> = syscall!(unsafe {
+ libc::syscall(
+ libc::SYS_getdents64,
+ self.dir.as_raw_fd(),
+ self.buf.as_mut_ptr() as *mut LinuxDirent64,
+ self.buf.len() as libc::c_int,
+ )
+ })
+ .map_err(|e| e.into());
+ match res {
+ Ok(end) => {
+ self.current = 0;
+ self.end = end as usize;
+ }
+ Err(e) => return Some(Err(e)),
+ }
+ }
+
+ let rem = &self.buf[self.current..self.end];
+ if rem.is_empty() {
+ return None;
+ }
+
+ // We only use debug asserts here because these values are coming from the kernel and we
+ // trust them implicitly.
+ debug_assert!(
+ rem.len() >= size_of::<LinuxDirent64>(),
+ "not enough space left in `rem`"
+ );
+
+ let (front, back) = rem.split_at(size_of::<LinuxDirent64>());
+
+ let dirent64 =
+ LinuxDirent64::from_slice(front).expect("unable to get LinuxDirent64 from slice");
+
+ let namelen = dirent64.d_reclen as usize - size_of::<LinuxDirent64>();
+ debug_assert!(namelen <= back.len(), "back is smaller than `namelen`");
+
+ // The kernel will pad the name with additional nul bytes until it is 8-byte aligned so
+ // we need to strip those off here.
+ let name = strip_padding(&back[..namelen]);
+ let entry = DirEntry {
+ ino: dirent64.d_ino,
+ offset: dirent64.d_off as u64,
+ type_: dirent64.d_ty,
+ name,
+ };
+
+ debug_assert!(
+ rem.len() >= dirent64.d_reclen as usize,
+ "rem is smaller than `d_reclen`"
+ );
+ self.current += dirent64.d_reclen as usize;
+ Some(Ok(entry))
+ }
+}
+
+pub fn read_dir<D: AsRawFd>(dir: &mut D, offset: libc::off64_t) -> Result<ReadDir<D>> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ syscall!(unsafe { libc::lseek64(dir.as_raw_fd(), offset, libc::SEEK_SET) })?;
+
+ Ok(ReadDir {
+ buf: [0u8; 256],
+ dir,
+ current: 0,
+ end: 0,
+ })
+}
+
+// Like `CStr::from_bytes_with_nul` but strips any bytes after the first '\0'-byte. Panics if `b`
+// doesn't contain any '\0' bytes.
+fn strip_padding(b: &[u8]) -> &CStr {
+ // It would be nice if we could use memchr here but that's locked behind an unstable gate.
+ let pos = b
+ .iter()
+ .position(|&c| c == 0)
+ .expect("`b` doesn't contain any nul bytes");
+
+ // Safe because we are creating this string with the first nul-byte we found so we can
+ // guarantee that it is nul-terminated and doesn't contain any interior nuls.
+ unsafe { CStr::from_bytes_with_nul_unchecked(&b[..pos + 1]) }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn padded_cstrings() {
+ assert_eq!(strip_padding(b".\0\0\0\0\0\0\0").to_bytes(), b".");
+ assert_eq!(strip_padding(b"..\0\0\0\0\0\0").to_bytes(), b"..");
+ assert_eq!(
+ strip_padding(b"normal cstring\0").to_bytes(),
+ b"normal cstring"
+ );
+ assert_eq!(strip_padding(b"\0\0\0\0").to_bytes(), b"");
+ assert_eq!(
+ strip_padding(b"interior\0nul bytes\0\0\0").to_bytes(),
+ b"interior"
+ );
+ }
+
+ #[test]
+ #[should_panic(expected = "`b` doesn't contain any nul bytes")]
+ fn no_nul_byte() {
+ strip_padding(b"no nul bytes in string");
+ }
+}
diff --git a/common/sys_util/src/sched.rs b/common/sys_util/src/sched.rs
new file mode 100644
index 000000000..9210fa78f
--- /dev/null
+++ b/common/sys_util/src/sched.rs
@@ -0,0 +1,143 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Wrappers for CPU affinity functions.
+
+use std::{iter::FromIterator, mem};
+
+use libc::{
+ cpu_set_t, prctl, sched_getaffinity, sched_setaffinity, CPU_ISSET, CPU_SET, CPU_SETSIZE,
+ CPU_ZERO, EINVAL,
+};
+
+use super::{errno_result, Error, Result};
+
+// This is needed because otherwise the compiler will complain that the
+// impl doesn't reference any types from inside this crate.
+struct CpuSet(cpu_set_t);
+
+impl CpuSet {
+ pub fn new() -> CpuSet {
+ // cpu_set_t is a C struct and can be safely initialized with zeroed memory.
+ let mut cpuset: cpu_set_t = unsafe { mem::MaybeUninit::zeroed().assume_init() };
+ // Safe because we pass a valid cpuset pointer.
+ unsafe { CPU_ZERO(&mut cpuset) };
+ CpuSet(cpuset)
+ }
+
+ pub fn to_cpus(&self) -> Vec<usize> {
+ let mut cpus = Vec::new();
+ for i in 0..(CPU_SETSIZE as usize) {
+ if unsafe { CPU_ISSET(i, &self.0) } {
+ cpus.push(i);
+ }
+ }
+ cpus
+ }
+}
+
+impl FromIterator<usize> for CpuSet {
+ fn from_iter<I: IntoIterator<Item = usize>>(cpus: I) -> Self {
+ // cpu_set_t is a C struct and can be safely initialized with zeroed memory.
+ let mut cpuset: cpu_set_t = unsafe { mem::zeroed() };
+ // Safe because we pass a valid cpuset pointer.
+ unsafe { CPU_ZERO(&mut cpuset) };
+ for cpu in cpus {
+ // Safe because we pass a valid cpuset pointer and cpu index.
+ unsafe { CPU_SET(cpu, &mut cpuset) };
+ }
+ CpuSet(cpuset)
+ }
+}
+
+/// Set the CPU affinity of the current thread to a given set of CPUs.
+///
+/// # Examples
+///
+/// Set the calling thread's CPU affinity so it will run on only CPUs
+/// 0, 1, 5, and 6.
+///
+/// ```
+/// # use sys_util::set_cpu_affinity;
+/// set_cpu_affinity(vec![0, 1, 5, 6]).unwrap();
+/// ```
+pub fn set_cpu_affinity<I: IntoIterator<Item = usize>>(cpus: I) -> Result<()> {
+ let CpuSet(cpuset) = cpus
+ .into_iter()
+ .map(|cpu| {
+ if cpu < CPU_SETSIZE as usize {
+ Ok(cpu)
+ } else {
+ Err(Error::new(EINVAL))
+ }
+ })
+ .collect::<Result<CpuSet>>()?;
+
+ // Safe because we pass 0 for the current thread, and cpuset is a valid pointer and only
+ // used for the duration of this call.
+ let res = unsafe { sched_setaffinity(0, mem::size_of_val(&cpuset), &cpuset) };
+
+ if res != 0 {
+ errno_result()
+ } else {
+ Ok(())
+ }
+}
+
+pub fn get_cpu_affinity() -> Result<Vec<usize>> {
+ let mut cpu_set = CpuSet::new();
+
+ // Safe because we pass 0 for the current thread, and cpu_set.0 is a valid pointer and only
+ // used for the duration of this call.
+ super::syscall!(unsafe { sched_getaffinity(0, mem::size_of_val(&cpu_set.0), &mut cpu_set.0) })?;
+
+ Ok(cpu_set.to_cpus())
+}
+
+/// Enable experimental core scheduling for the current thread.
+///
+/// If successful, the kernel should not schedule this thread with any other thread within the same
+/// SMT core. Because this is experimental, this will return success on kernels which do not support
+/// this function.
+pub fn enable_core_scheduling() -> Result<()> {
+ const PR_SCHED_CORE: i32 = 62;
+ const PR_SCHED_CORE_CREATE: i32 = 1;
+
+ #[allow(clippy::upper_case_acronyms, non_camel_case_types, dead_code)]
+ /// Specifies the scope of the pid parameter of `PR_SCHED_CORE`.
+ enum pid_type {
+ /// `PID` refers to threads.
+ PIDTYPE_PID,
+ /// `TGPID` refers to a process.
+ PIDTYPE_TGID,
+ /// `TGPID` refers to a process group.
+ PIDTYPE_PGID,
+ }
+
+ let ret = match unsafe {
+ prctl(
+ PR_SCHED_CORE,
+ PR_SCHED_CORE_CREATE,
+ 0, // id of target task, 0 indicates current task
+ pid_type::PIDTYPE_PID as i32, // PID scopes to this thread only
+ 0, // ignored by PR_SCHED_CORE_CREATE command
+ )
+ } {
+ #[cfg(feature = "chromeos")]
+ -1 => {
+ // Chrome OS has an pre-upstream version of this functionality which might work.
+ const PR_SET_CORE_SCHED: i32 = 0x200;
+ unsafe { prctl(PR_SET_CORE_SCHED, 1) }
+ }
+ ret => ret,
+ };
+ if ret == -1 {
+ let error = Error::last();
+ // prctl returns EINVAL for unknown functions, which we will ignore for now.
+ if error.errno() != libc::EINVAL {
+ return Err(error);
+ }
+ }
+ Ok(())
+}
diff --git a/common/sys_util/src/scoped_path.rs b/common/sys_util/src/scoped_path.rs
new file mode 100644
index 000000000..745137eae
--- /dev/null
+++ b/common/sys_util/src/scoped_path.rs
@@ -0,0 +1,138 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::env::{current_exe, temp_dir};
+use std::fs::{create_dir_all, remove_dir_all};
+use std::io::Result;
+use std::ops::Deref;
+use std::path::{Path, PathBuf};
+use std::thread::panicking;
+
+use crate::{getpid, gettid};
+
+/// Returns a stable path based on the label, pid, and tid. If the label isn't provided the
+/// current_exe is used instead.
+pub fn get_temp_path(label: Option<&str>) -> PathBuf {
+ if let Some(label) = label {
+ temp_dir().join(format!("{}-{}-{}", label, getpid(), gettid()))
+ } else {
+ get_temp_path(Some(
+ current_exe()
+ .unwrap()
+ .file_name()
+ .unwrap()
+ .to_str()
+ .unwrap(),
+ ))
+ }
+}
+
+/// Automatically deletes the path it contains when it goes out of scope unless it is a test and
+/// drop is called after a panic!.
+///
+/// This is particularly useful for creating temporary directories for use with tests.
+pub struct ScopedPath<P: AsRef<Path>>(P);
+
+impl<P: AsRef<Path>> ScopedPath<P> {
+ pub fn create(p: P) -> Result<Self> {
+ create_dir_all(p.as_ref())?;
+ Ok(ScopedPath(p))
+ }
+}
+
+impl<P: AsRef<Path>> AsRef<Path> for ScopedPath<P> {
+ fn as_ref(&self) -> &Path {
+ self.0.as_ref()
+ }
+}
+
+impl<P: AsRef<Path>> Deref for ScopedPath<P> {
+ type Target = Path;
+
+ fn deref(&self) -> &Self::Target {
+ self.0.as_ref()
+ }
+}
+
+impl<P: AsRef<Path>> Drop for ScopedPath<P> {
+ fn drop(&mut self) {
+ // Leave the files on a failed test run for debugging.
+ if panicking() && cfg!(test) {
+ eprintln!("NOTE: Not removing {}", self.display());
+ return;
+ }
+ if let Err(e) = remove_dir_all(&**self) {
+ eprintln!("Failed to remove {}: {}", self.display(), e);
+ }
+ }
+}
+
+#[cfg(test)]
+pub(crate) mod tests {
+ use super::*;
+
+ use std::panic::catch_unwind;
+
+ #[test]
+ fn gettemppath() {
+ assert_ne!("", get_temp_path(None).to_string_lossy());
+ assert!(get_temp_path(None).starts_with(temp_dir()));
+ assert_eq!(
+ get_temp_path(None),
+ get_temp_path(Some(
+ current_exe()
+ .unwrap()
+ .file_name()
+ .unwrap()
+ .to_str()
+ .unwrap()
+ ))
+ );
+ assert_ne!(
+ get_temp_path(Some("label")),
+ get_temp_path(Some(
+ current_exe()
+ .unwrap()
+ .file_name()
+ .unwrap()
+ .to_str()
+ .unwrap()
+ ))
+ );
+ }
+
+ #[test]
+ fn scopedpath_exists() {
+ let tmp_path = get_temp_path(None);
+ {
+ let scoped_path = ScopedPath::create(&tmp_path).unwrap();
+ assert!(scoped_path.exists());
+ }
+ assert!(!tmp_path.exists());
+ }
+
+ #[test]
+ fn scopedpath_notexists() {
+ let tmp_path = get_temp_path(None);
+ {
+ let _scoped_path = ScopedPath(&tmp_path);
+ }
+ assert!(!tmp_path.exists());
+ }
+
+ #[test]
+ fn scopedpath_panic() {
+ let tmp_path = get_temp_path(None);
+ assert!(catch_unwind(|| {
+ {
+ let scoped_path = ScopedPath::create(&tmp_path).unwrap();
+ assert!(scoped_path.exists());
+ panic!()
+ }
+ })
+ .is_err());
+ assert!(tmp_path.exists());
+ remove_dir_all(&tmp_path).unwrap();
+ }
+}
diff --git a/common/sys_util/src/scoped_signal_handler.rs b/common/sys_util/src/scoped_signal_handler.rs
new file mode 100644
index 000000000..b4d387df9
--- /dev/null
+++ b/common/sys_util/src/scoped_signal_handler.rs
@@ -0,0 +1,417 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Provides a struct for registering signal handlers that get cleared on drop.
+
+use std::{
+ convert::TryFrom,
+ fmt,
+ io::{Cursor, Write},
+ panic::catch_unwind,
+ result,
+};
+
+use libc::{c_int, c_void, STDERR_FILENO};
+use remain::sorted;
+use thiserror::Error;
+
+use super::{
+ signal::{
+ clear_signal_handler, has_default_signal_handler, register_signal_handler, wait_for_signal,
+ Signal,
+ },
+ Error as ErrnoError,
+};
+
+#[sorted]
+#[derive(Error, Debug)]
+pub enum Error {
+ /// Already waiting for interrupt.
+ #[error("already waiting for interrupt.")]
+ AlreadyWaiting,
+ /// Signal already has a handler.
+ #[error("signal handler already set for {0:?}")]
+ HandlerAlreadySet(Signal),
+ /// Failed to check if signal has the default signal handler.
+ #[error("failed to check the signal handler for {0:?}: {1}")]
+ HasDefaultSignalHandler(Signal, ErrnoError),
+ /// Failed to register a signal handler.
+ #[error("failed to register a signal handler for {0:?}: {1}")]
+ RegisterSignalHandler(Signal, ErrnoError),
+ /// Sigaction failed.
+ #[error("sigaction failed for {0:?}: {1}")]
+ Sigaction(Signal, ErrnoError),
+ /// Failed to wait for signal.
+ #[error("wait_for_signal failed: {0}")]
+ WaitForSignal(ErrnoError),
+}
+
+pub type Result<T> = result::Result<T, Error>;
+
+/// The interface used by Scoped Signal handler.
+///
+/// # Safety
+/// The implementation of handle_signal needs to be async signal-safe.
+///
+/// NOTE: panics are caught when possible because a panic inside ffi is undefined behavior.
+pub unsafe trait SignalHandler {
+ /// A function that is called to handle the passed signal.
+ fn handle_signal(signal: Signal);
+}
+
+/// Wrap the handler with an extern "C" function.
+extern "C" fn call_handler<H: SignalHandler>(signum: c_int) {
+ // Make an effort to surface an error.
+ if catch_unwind(|| H::handle_signal(Signal::try_from(signum).unwrap())).is_err() {
+ // Note the following cannot be used:
+ // eprintln! - uses std::io which has locks that may be held.
+ // format! - uses the allocator which enforces mutual exclusion.
+
+ // Get the debug representation of signum.
+ let signal: Signal;
+ let signal_debug: &dyn fmt::Debug = match Signal::try_from(signum) {
+ Ok(s) => {
+ signal = s;
+ &signal as &dyn fmt::Debug
+ }
+ Err(_) => &signum as &dyn fmt::Debug,
+ };
+
+ // Buffer the output, so a single call to write can be used.
+ // The message accounts for 29 chars, that leaves 35 for the string representation of the
+ // signal which is more than enough.
+ let mut buffer = [0u8; 64];
+ let mut cursor = Cursor::new(buffer.as_mut());
+ if writeln!(cursor, "signal handler got error for: {:?}", signal_debug).is_ok() {
+ let len = cursor.position() as usize;
+ // Safe in the sense that buffer is owned and the length is checked. This may print in
+ // the middle of an existing write, but that is considered better than dropping the
+ // error.
+ unsafe {
+ libc::write(
+ STDERR_FILENO,
+ cursor.get_ref().as_ptr() as *const c_void,
+ len,
+ )
+ };
+ } else {
+ // This should never happen, but write an error message just in case.
+ const ERROR_DROPPED: &str = "Error dropped by signal handler.";
+ let bytes = ERROR_DROPPED.as_bytes();
+ unsafe { libc::write(STDERR_FILENO, bytes.as_ptr() as *const c_void, bytes.len()) };
+ }
+ }
+}
+
+/// Represents a signal handler that is registered with a set of signals that unregistered when the
+/// struct goes out of scope. Prefer a signalfd based solution before using this.
+pub struct ScopedSignalHandler {
+ signals: Vec<Signal>,
+}
+
+impl ScopedSignalHandler {
+ /// Attempts to register `handler` with the provided `signals`. It will fail if there is already
+ /// an existing handler on any of `signals`.
+ ///
+ /// # Safety
+ /// This is safe if H::handle_signal is async-signal safe.
+ pub fn new<H: SignalHandler>(signals: &[Signal]) -> Result<Self> {
+ let mut scoped_handler = ScopedSignalHandler {
+ signals: Vec::with_capacity(signals.len()),
+ };
+ for &signal in signals {
+ if !has_default_signal_handler((signal).into())
+ .map_err(|err| Error::HasDefaultSignalHandler(signal, err))?
+ {
+ return Err(Error::HandlerAlreadySet(signal));
+ }
+ // Requires an async-safe callback.
+ unsafe {
+ register_signal_handler((signal).into(), call_handler::<H>)
+ .map_err(|err| Error::RegisterSignalHandler(signal, err))?
+ };
+ scoped_handler.signals.push(signal);
+ }
+ Ok(scoped_handler)
+ }
+}
+
+/// Clears the signal handler for any of the associated signals.
+impl Drop for ScopedSignalHandler {
+ fn drop(&mut self) {
+ for signal in &self.signals {
+ if let Err(err) = clear_signal_handler((*signal).into()) {
+ eprintln!("Error: failed to clear signal handler: {:?}", err);
+ }
+ }
+ }
+}
+
+/// A signal handler that does nothing.
+///
+/// This is useful in cases where wait_for_signal is used since it will never trigger if the signal
+/// is blocked and the default handler may have undesired effects like terminating the process.
+pub struct EmptySignalHandler;
+/// # Safety
+/// Safe because handle_signal is async-signal safe.
+unsafe impl SignalHandler for EmptySignalHandler {
+ fn handle_signal(_: Signal) {}
+}
+
+/// Blocks until SIGINT is received, which often happens because Ctrl-C was pressed in an
+/// interactive terminal.
+///
+/// Note: if you are using a multi-threaded application you need to block SIGINT on all other
+/// threads or they may receive the signal instead of the desired thread.
+pub fn wait_for_interrupt() -> Result<()> {
+ // Register a signal handler if there is not one already so the thread is not killed.
+ let ret = ScopedSignalHandler::new::<EmptySignalHandler>(&[Signal::Interrupt]);
+ if !matches!(&ret, Ok(_) | Err(Error::HandlerAlreadySet(_))) {
+ ret?;
+ }
+
+ match wait_for_signal(&[Signal::Interrupt.into()], None) {
+ Ok(_) => Ok(()),
+ Err(err) => Err(Error::WaitForSignal(err)),
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use std::{
+ fs::File,
+ io::{BufRead, BufReader},
+ mem::zeroed,
+ ptr::{null, null_mut},
+ sync::{
+ atomic::{AtomicI32, AtomicUsize, Ordering},
+ Arc, Mutex, MutexGuard, Once,
+ },
+ thread::{sleep, spawn},
+ time::{Duration, Instant},
+ };
+
+ use libc::sigaction;
+
+ use super::super::{gettid, kill, Pid};
+
+ const TEST_SIGNAL: Signal = Signal::User1;
+ const TEST_SIGNALS: &[Signal] = &[Signal::User1, Signal::User2];
+
+ static TEST_SIGNAL_COUNTER: AtomicUsize = AtomicUsize::new(0);
+
+ /// Only allows one test case to execute at a time.
+ fn get_mutex() -> MutexGuard<'static, ()> {
+ static INIT: Once = Once::new();
+ static mut VAL: Option<Arc<Mutex<()>>> = None;
+
+ INIT.call_once(|| {
+ let val = Some(Arc::new(Mutex::new(())));
+ // Safe because the mutation is protected by the Once.
+ unsafe { VAL = val }
+ });
+
+ // Safe mutation only happens in the Once.
+ unsafe { VAL.as_ref() }.unwrap().lock().unwrap()
+ }
+
+ fn reset_counter() {
+ TEST_SIGNAL_COUNTER.swap(0, Ordering::SeqCst);
+ }
+
+ fn get_sigaction(signal: Signal) -> Result<sigaction> {
+ // Safe because sigaction is owned and expected to be initialized ot zeros.
+ let mut sigact: sigaction = unsafe { zeroed() };
+
+ if unsafe { sigaction(signal.into(), null(), &mut sigact) } < 0 {
+ Err(Error::Sigaction(signal, ErrnoError::last()))
+ } else {
+ Ok(sigact)
+ }
+ }
+
+ /// Safety:
+ /// This is only safe if the signal handler set in sigaction is safe.
+ unsafe fn restore_sigaction(signal: Signal, sigact: sigaction) -> Result<sigaction> {
+ if sigaction(signal.into(), &sigact, null_mut()) < 0 {
+ Err(Error::Sigaction(signal, ErrnoError::last()))
+ } else {
+ Ok(sigact)
+ }
+ }
+
+ /// Safety:
+ /// Safe if the signal handler for Signal::User1 is safe.
+ unsafe fn send_test_signal() {
+ kill(gettid(), Signal::User1.into()).unwrap()
+ }
+
+ macro_rules! assert_counter_eq {
+ ($compare_to:expr) => {{
+ let expected: usize = $compare_to;
+ let got: usize = TEST_SIGNAL_COUNTER.load(Ordering::SeqCst);
+ if got != expected {
+ panic!(
+ "wrong signal counter value: got {}; expected {}",
+ got, expected
+ );
+ }
+ }};
+ }
+
+ struct TestHandler;
+
+ /// # Safety
+ /// Safe because handle_signal is async-signal safe.
+ unsafe impl SignalHandler for TestHandler {
+ fn handle_signal(signal: Signal) {
+ if TEST_SIGNAL == signal {
+ TEST_SIGNAL_COUNTER.fetch_add(1, Ordering::SeqCst);
+ }
+ }
+ }
+
+ #[test]
+ fn scopedsignalhandler_success() {
+ // Prevent other test cases from running concurrently since the signal
+ // handlers are shared for the process.
+ let _guard = get_mutex();
+
+ reset_counter();
+ assert_counter_eq!(0);
+
+ assert!(has_default_signal_handler(TEST_SIGNAL.into()).unwrap());
+ let handler = ScopedSignalHandler::new::<TestHandler>(&[TEST_SIGNAL]).unwrap();
+ assert!(!has_default_signal_handler(TEST_SIGNAL.into()).unwrap());
+
+ // Safe because test_handler is safe.
+ unsafe { send_test_signal() };
+
+ // Give the handler time to run in case it is on a different thread.
+ for _ in 1..40 {
+ if TEST_SIGNAL_COUNTER.load(Ordering::SeqCst) > 0 {
+ break;
+ }
+ sleep(Duration::from_millis(250));
+ }
+
+ assert_counter_eq!(1);
+
+ drop(handler);
+ assert!(has_default_signal_handler(TEST_SIGNAL.into()).unwrap());
+ }
+
+ #[test]
+ fn scopedsignalhandler_handleralreadyset() {
+ // Prevent other test cases from running concurrently since the signal
+ // handlers are shared for the process.
+ let _guard = get_mutex();
+
+ reset_counter();
+ assert_counter_eq!(0);
+
+ assert!(has_default_signal_handler(TEST_SIGNAL.into()).unwrap());
+ // Safe because TestHandler is async-signal safe.
+ let handler = ScopedSignalHandler::new::<TestHandler>(&[TEST_SIGNAL]).unwrap();
+ assert!(!has_default_signal_handler(TEST_SIGNAL.into()).unwrap());
+
+ // Safe because TestHandler is async-signal safe.
+ assert!(matches!(
+ ScopedSignalHandler::new::<TestHandler>(TEST_SIGNALS),
+ Err(Error::HandlerAlreadySet(Signal::User1))
+ ));
+
+ assert_counter_eq!(0);
+ drop(handler);
+ assert!(has_default_signal_handler(TEST_SIGNAL.into()).unwrap());
+ }
+
+ /// Stores the thread used by WaitForInterruptHandler.
+ static WAIT_FOR_INTERRUPT_THREAD_ID: AtomicI32 = AtomicI32::new(0);
+ /// Forwards SIGINT to the appropriate thread.
+ struct WaitForInterruptHandler;
+
+ /// # Safety
+ /// Safe because handle_signal is async-signal safe.
+ unsafe impl SignalHandler for WaitForInterruptHandler {
+ fn handle_signal(_: Signal) {
+ let tid = WAIT_FOR_INTERRUPT_THREAD_ID.load(Ordering::SeqCst);
+ // If the thread ID is set and executed on the wrong thread, forward the signal.
+ if tid != 0 && gettid() != tid {
+ // Safe because the handler is safe and the target thread id is expecting the signal.
+ unsafe { kill(tid, Signal::Interrupt.into()) }.unwrap();
+ }
+ }
+ }
+
+ /// Query /proc/${tid}/status for its State and check if it is either S (sleeping) or in
+ /// D (disk sleep).
+ fn thread_is_sleeping(tid: Pid) -> result::Result<bool, ErrnoError> {
+ const PREFIX: &str = "State:";
+ let mut status_reader = BufReader::new(File::open(format!("/proc/{}/status", tid))?);
+ let mut line = String::new();
+ loop {
+ let count = status_reader.read_line(&mut line)?;
+ if count == 0 {
+ return Err(ErrnoError::new(libc::EIO));
+ }
+ if let Some(stripped) = line.strip_prefix(PREFIX) {
+ return Ok(matches!(
+ stripped.trim_start().chars().next(),
+ Some('S') | Some('D')
+ ));
+ }
+ line.clear();
+ }
+ }
+
+ /// Wait for a process to block either in a sleeping or disk sleep state.
+ fn wait_for_thread_to_sleep(tid: Pid, timeout: Duration) -> result::Result<(), ErrnoError> {
+ let start = Instant::now();
+ loop {
+ if thread_is_sleeping(tid)? {
+ return Ok(());
+ }
+ if start.elapsed() > timeout {
+ return Err(ErrnoError::new(libc::EAGAIN));
+ }
+ sleep(Duration::from_millis(50));
+ }
+ }
+
+ #[test]
+ fn waitforinterrupt_success() {
+ // Prevent other test cases from running concurrently since the signal
+ // handlers are shared for the process.
+ let _guard = get_mutex();
+
+ let to_restore = get_sigaction(Signal::Interrupt).unwrap();
+ clear_signal_handler(Signal::Interrupt.into()).unwrap();
+ // Safe because TestHandler is async-signal safe.
+ let handler =
+ ScopedSignalHandler::new::<WaitForInterruptHandler>(&[Signal::Interrupt]).unwrap();
+
+ let tid = gettid();
+ WAIT_FOR_INTERRUPT_THREAD_ID.store(tid, Ordering::SeqCst);
+
+ let join_handle = spawn(move || -> result::Result<(), ErrnoError> {
+ // Wait unitl the thread is ready to receive the signal.
+ wait_for_thread_to_sleep(tid, Duration::from_secs(10)).unwrap();
+
+ // Safe because the SIGINT handler is safe.
+ unsafe { kill(tid, Signal::Interrupt.into()) }
+ });
+ let wait_ret = wait_for_interrupt();
+ let join_ret = join_handle.join();
+
+ drop(handler);
+ // Safe because we are restoring the previous SIGINT handler.
+ unsafe { restore_sigaction(Signal::Interrupt, to_restore) }.unwrap();
+
+ wait_ret.unwrap();
+ join_ret.unwrap().unwrap();
+ }
+}
diff --git a/sys_util/src/shm.rs b/common/sys_util/src/shm.rs
index 927734aef..1b96018a0 100644
--- a/sys_util/src/shm.rs
+++ b/common/sys_util/src/shm.rs
@@ -2,24 +2,28 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::ffi::{CStr, CString};
-use std::fs::{read_link, File};
-use std::io::{self, Read, Seek, SeekFrom, Write};
-use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+use std::{
+ ffi::{CStr, CString},
+ fs::{read_link, File},
+ io::{
+ Read, Seek, SeekFrom, Write, {self},
+ },
+ os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
+};
use libc::{
- self, c_char, c_int, c_long, c_uint, close, fcntl, ftruncate64, off64_t, syscall,
- SYS_memfd_create, EINVAL, F_ADD_SEALS, F_GET_SEALS, F_SEAL_GROW, F_SEAL_SEAL, F_SEAL_SHRINK,
- F_SEAL_WRITE, MFD_ALLOW_SEALING,
+ c_char, c_int, c_long, c_uint, close, fcntl, ftruncate64, off64_t, syscall, SYS_memfd_create,
+ EINVAL, F_ADD_SEALS, F_GET_SEALS, F_SEAL_FUTURE_WRITE, F_SEAL_GROW, F_SEAL_SEAL, F_SEAL_SHRINK,
+ F_SEAL_WRITE, MFD_ALLOW_SEALING, {self},
};
use serde::{Deserialize, Serialize};
-use crate::{errno, errno_result, Result};
+use super::{errno_result, Error, Result};
/// A shared memory file descriptor and its size.
#[derive(Serialize, Deserialize)]
pub struct SharedMemory {
- #[serde(with = "crate::with_as_descriptor")]
+ #[serde(with = "super::with_as_descriptor")]
fd: File,
size: u64,
}
@@ -50,7 +54,7 @@ impl MemfdSeals {
self.0
}
- /// True of the grow seal bit is present.
+ /// True if the grow seal bit is present.
#[inline]
pub fn grow_seal(self) -> bool {
self.0 & F_SEAL_GROW != 0
@@ -62,7 +66,7 @@ impl MemfdSeals {
self.0 |= F_SEAL_GROW;
}
- /// True of the shrink seal bit is present.
+ /// True if the shrink seal bit is present.
#[inline]
pub fn shrink_seal(self) -> bool {
self.0 & F_SEAL_SHRINK != 0
@@ -74,7 +78,7 @@ impl MemfdSeals {
self.0 |= F_SEAL_SHRINK;
}
- /// True of the write seal bit is present.
+ /// True if the write seal bit is present.
#[inline]
pub fn write_seal(self) -> bool {
self.0 & F_SEAL_WRITE != 0
@@ -86,6 +90,18 @@ impl MemfdSeals {
self.0 |= F_SEAL_WRITE;
}
+ /// True if the future write seal bit is present.
+ #[inline]
+ pub fn future_write_seal(self) -> bool {
+ self.0 & F_SEAL_FUTURE_WRITE != 0
+ }
+
+ /// Sets the future write seal bit.
+ #[inline]
+ pub fn set_future_write_seal(&mut self) {
+ self.0 |= F_SEAL_FUTURE_WRITE;
+ }
+
/// True of the seal seal bit is present.
#[inline]
pub fn seal_seal(self) -> bool {
@@ -106,9 +122,7 @@ impl SharedMemory {
/// Note that the given name may not have NUL characters anywhere in it, or this will return an
/// error.
pub fn named<T: Into<Vec<u8>>>(name: T) -> Result<SharedMemory> {
- Self::new(Some(
- &CString::new(name).map_err(|_| errno::Error::new(EINVAL))?,
- ))
+ Self::new(Some(&CString::new(name).map_err(|_| Error::new(EINVAL))?))
}
/// Convenience function for `SharedMemory::new` that has an arbitrary and unspecified name.
@@ -209,7 +223,7 @@ impl SharedMemory {
.trim_end_matches(" (deleted)")
.to_owned()
})
- .ok_or_else(|| errno::Error::new(EINVAL))
+ .ok_or_else(|| Error::new(EINVAL))
}
}
@@ -288,7 +302,7 @@ pub fn kernel_has_memfd() -> bool {
unsafe {
let fd = memfd_create(b"/test_memfd_create\0".as_ptr() as *const c_char, 0);
if fd < 0 {
- if errno::Error::last().errno() == libc::ENOSYS {
+ if Error::last().errno() == libc::ENOSYS {
return false;
}
return true;
@@ -306,7 +320,7 @@ mod tests {
use data_model::VolatileMemory;
- use crate::MemoryMapping;
+ use super::super::MemoryMapping;
#[test]
fn named() {
diff --git a/common/sys_util/src/signal.rs b/common/sys_util/src/signal.rs
new file mode 100644
index 000000000..164b18918
--- /dev/null
+++ b/common/sys_util/src/signal.rs
@@ -0,0 +1,695 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use libc::{
+ c_int, pthread_kill, pthread_sigmask, pthread_t, sigaction, sigaddset, sigemptyset, siginfo_t,
+ sigismember, sigpending, sigset_t, sigtimedwait, sigwait, timespec, waitpid, EAGAIN, EINTR,
+ EINVAL, SA_RESTART, SIG_BLOCK, SIG_DFL, SIG_UNBLOCK, WNOHANG,
+};
+use remain::sorted;
+use thiserror::Error;
+
+use std::{
+ cmp::Ordering,
+ convert::TryFrom,
+ io, mem,
+ os::unix::thread::JoinHandleExt,
+ process::Child,
+ ptr::{null, null_mut},
+ result,
+ thread::JoinHandle,
+ time::{Duration, Instant},
+};
+
+use super::{duration_to_timespec, errno_result, getsid, Error as ErrnoError, Pid, Result};
+use std::ops::{Deref, DerefMut};
+
+const POLL_RATE: Duration = Duration::from_millis(50);
+const DEFAULT_KILL_TIMEOUT: Duration = Duration::from_secs(5);
+
+#[sorted]
+#[derive(Error, Debug)]
+pub enum Error {
+ /// The signal could not be blocked.
+ #[error("signal could not be blocked: {0}")]
+ BlockSignal(ErrnoError),
+ /// Failed to check if given signal is in the set of pending signals.
+ #[error("failed to check whether given signal is in the pending set: {0}")]
+ ClearCheckPending(ErrnoError),
+ /// Failed to get pending signals.
+ #[error("failed to get pending signals: {0}")]
+ ClearGetPending(ErrnoError),
+ /// Failed to wait for given signal.
+ #[error("failed to wait for given signal: {0}")]
+ ClearWaitPending(ErrnoError),
+ /// Failed to check if the requested signal is in the blocked set already.
+ #[error("failed to check whether requested signal is in the blocked set: {0}")]
+ CompareBlockedSignals(ErrnoError),
+ /// Couldn't create a sigset.
+ #[error("couldn't create a sigset: {0}")]
+ CreateSigset(ErrnoError),
+ /// Failed to get session id.
+ #[error("failed to get session id: {0}")]
+ GetSid(ErrnoError),
+ /// Failed to send signal to pid.
+ #[error("failed to send signal: {0}")]
+ Kill(ErrnoError),
+ /// The signal mask could not be retrieved.
+ #[error("failed to retrieve signal mask: {}", io::Error::from_raw_os_error(*.0))]
+ RetrieveSignalMask(i32),
+ /// Converted signum greater than SIGRTMAX.
+ #[error("got RT signal greater than max: {0:?}")]
+ RtSignumGreaterThanMax(Signal),
+ /// The wrapped signal has already been blocked.
+ #[error("signal {0} already blocked")]
+ SignalAlreadyBlocked(c_int),
+ /// Timeout reached.
+ #[error("timeout reached.")]
+ TimedOut,
+ /// The signal could not be unblocked.
+ #[error("signal could not be unblocked: {0}")]
+ UnblockSignal(ErrnoError),
+ /// Failed to convert signum to Signal.
+ #[error("unrecoginized signal number: {0}")]
+ UnrecognizedSignum(i32),
+ /// Failed to wait for signal.
+ #[error("failed to wait for signal: {0}")]
+ WaitForSignal(ErrnoError),
+ /// Failed to wait for pid.
+ #[error("failed to wait for process: {0}")]
+ WaitPid(ErrnoError),
+}
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[repr(i32)]
+pub enum Signal {
+ Abort = libc::SIGABRT,
+ Alarm = libc::SIGALRM,
+ Bus = libc::SIGBUS,
+ Child = libc::SIGCHLD,
+ Continue = libc::SIGCONT,
+ ExceededFileSize = libc::SIGXFSZ,
+ FloatingPointException = libc::SIGFPE,
+ HangUp = libc::SIGHUP,
+ IllegalInstruction = libc::SIGILL,
+ Interrupt = libc::SIGINT,
+ Io = libc::SIGIO,
+ Kill = libc::SIGKILL,
+ Pipe = libc::SIGPIPE,
+ Power = libc::SIGPWR,
+ Profile = libc::SIGPROF,
+ Quit = libc::SIGQUIT,
+ SegmentationViolation = libc::SIGSEGV,
+ StackFault = libc::SIGSTKFLT,
+ Stop = libc::SIGSTOP,
+ Sys = libc::SIGSYS,
+ Trap = libc::SIGTRAP,
+ Terminate = libc::SIGTERM,
+ TtyIn = libc::SIGTTIN,
+ TtyOut = libc::SIGTTOU,
+ TtyStop = libc::SIGTSTP,
+ Urgent = libc::SIGURG,
+ User1 = libc::SIGUSR1,
+ User2 = libc::SIGUSR2,
+ VtAlarm = libc::SIGVTALRM,
+ Winch = libc::SIGWINCH,
+ Xcpu = libc::SIGXCPU,
+ // Rt signal numbers are be adjusted in the conversion to integer.
+ Rt0 = libc::SIGSYS + 1,
+ Rt1,
+ Rt2,
+ Rt3,
+ Rt4,
+ Rt5,
+ Rt6,
+ Rt7,
+ // Only 8 are guaranteed by POSIX, Linux has 32, but only 29 or 30 are usable.
+ Rt8,
+ Rt9,
+ Rt10,
+ Rt11,
+ Rt12,
+ Rt13,
+ Rt14,
+ Rt15,
+ Rt16,
+ Rt17,
+ Rt18,
+ Rt19,
+ Rt20,
+ Rt21,
+ Rt22,
+ Rt23,
+ Rt24,
+ Rt25,
+ Rt26,
+ Rt27,
+ Rt28,
+ Rt29,
+ Rt30,
+ Rt31,
+}
+
+impl From<Signal> for c_int {
+ fn from(signal: Signal) -> c_int {
+ let num = signal as libc::c_int;
+ if num >= Signal::Rt0 as libc::c_int {
+ return num - (Signal::Rt0 as libc::c_int) + SIGRTMIN();
+ }
+ num
+ }
+}
+
+impl TryFrom<c_int> for Signal {
+ type Error = Error;
+
+ fn try_from(value: c_int) -> result::Result<Self, Self::Error> {
+ use Signal::*;
+
+ Ok(match value {
+ libc::SIGABRT => Abort,
+ libc::SIGALRM => Alarm,
+ libc::SIGBUS => Bus,
+ libc::SIGCHLD => Child,
+ libc::SIGCONT => Continue,
+ libc::SIGXFSZ => ExceededFileSize,
+ libc::SIGFPE => FloatingPointException,
+ libc::SIGHUP => HangUp,
+ libc::SIGILL => IllegalInstruction,
+ libc::SIGINT => Interrupt,
+ libc::SIGIO => Io,
+ libc::SIGKILL => Kill,
+ libc::SIGPIPE => Pipe,
+ libc::SIGPWR => Power,
+ libc::SIGPROF => Profile,
+ libc::SIGQUIT => Quit,
+ libc::SIGSEGV => SegmentationViolation,
+ libc::SIGSTKFLT => StackFault,
+ libc::SIGSTOP => Stop,
+ libc::SIGSYS => Sys,
+ libc::SIGTRAP => Trap,
+ libc::SIGTERM => Terminate,
+ libc::SIGTTIN => TtyIn,
+ libc::SIGTTOU => TtyOut,
+ libc::SIGTSTP => TtyStop,
+ libc::SIGURG => Urgent,
+ libc::SIGUSR1 => User1,
+ libc::SIGUSR2 => User2,
+ libc::SIGVTALRM => VtAlarm,
+ libc::SIGWINCH => Winch,
+ libc::SIGXCPU => Xcpu,
+ _ => {
+ if value < SIGRTMIN() {
+ return Err(Error::UnrecognizedSignum(value));
+ }
+ let signal = match value - SIGRTMIN() {
+ 0 => Rt0,
+ 1 => Rt1,
+ 2 => Rt2,
+ 3 => Rt3,
+ 4 => Rt4,
+ 5 => Rt5,
+ 6 => Rt6,
+ 7 => Rt7,
+ 8 => Rt8,
+ 9 => Rt9,
+ 10 => Rt10,
+ 11 => Rt11,
+ 12 => Rt12,
+ 13 => Rt13,
+ 14 => Rt14,
+ 15 => Rt15,
+ 16 => Rt16,
+ 17 => Rt17,
+ 18 => Rt18,
+ 19 => Rt19,
+ 20 => Rt20,
+ 21 => Rt21,
+ 22 => Rt22,
+ 23 => Rt23,
+ 24 => Rt24,
+ 25 => Rt25,
+ 26 => Rt26,
+ 27 => Rt27,
+ 28 => Rt28,
+ 29 => Rt29,
+ 30 => Rt30,
+ 31 => Rt31,
+ _ => {
+ return Err(Error::UnrecognizedSignum(value));
+ }
+ };
+ if value > SIGRTMAX() {
+ return Err(Error::RtSignumGreaterThanMax(signal));
+ }
+ signal
+ }
+ })
+ }
+}
+
+pub type SignalResult<T> = result::Result<T, Error>;
+
+#[link(name = "c")]
+extern "C" {
+ fn __libc_current_sigrtmin() -> c_int;
+ fn __libc_current_sigrtmax() -> c_int;
+}
+
+/// Returns the minimum (inclusive) real-time signal number.
+#[allow(non_snake_case)]
+pub fn SIGRTMIN() -> c_int {
+ unsafe { __libc_current_sigrtmin() }
+}
+
+/// Returns the maximum (inclusive) real-time signal number.
+#[allow(non_snake_case)]
+pub fn SIGRTMAX() -> c_int {
+ unsafe { __libc_current_sigrtmax() }
+}
+
+fn valid_rt_signal_num(num: c_int) -> bool {
+ num >= SIGRTMIN() && num <= SIGRTMAX()
+}
+
+/// Registers `handler` as the signal handler of signum `num`.
+///
+/// # Safety
+///
+/// This is considered unsafe because the given handler will be called asynchronously, interrupting
+/// whatever the thread was doing and therefore must only do async-signal-safe operations.
+pub unsafe fn register_signal_handler(num: c_int, handler: extern "C" fn(c_int)) -> Result<()> {
+ let mut sigact: sigaction = mem::zeroed();
+ sigact.sa_flags = SA_RESTART;
+ sigact.sa_sigaction = handler as *const () as usize;
+
+ let ret = sigaction(num, &sigact, null_mut());
+ if ret < 0 {
+ return errno_result();
+ }
+
+ Ok(())
+}
+
+/// Resets the signal handler of signum `num` back to the default.
+pub fn clear_signal_handler(num: c_int) -> Result<()> {
+ // Safe because sigaction is owned and expected to be initialized ot zeros.
+ let mut sigact: sigaction = unsafe { mem::zeroed() };
+ sigact.sa_flags = SA_RESTART;
+ sigact.sa_sigaction = SIG_DFL;
+
+ // Safe because sigact is owned, and this is restoring the default signal handler.
+ let ret = unsafe { sigaction(num, &sigact, null_mut()) };
+ if ret < 0 {
+ return errno_result();
+ }
+
+ Ok(())
+}
+
+/// Returns true if the signal handler for signum `num` is the default.
+pub fn has_default_signal_handler(num: c_int) -> Result<bool> {
+ // Safe because sigaction is owned and expected to be initialized ot zeros.
+ let mut sigact: sigaction = unsafe { mem::zeroed() };
+
+ // Safe because sigact is owned, and this is just querying the existing state.
+ let ret = unsafe { sigaction(num, null(), &mut sigact) };
+ if ret < 0 {
+ return errno_result();
+ }
+
+ Ok(sigact.sa_sigaction == SIG_DFL)
+}
+
+/// Registers `handler` as the signal handler for the real-time signal with signum `num`.
+///
+/// The value of `num` must be within [`SIGRTMIN`, `SIGRTMAX`] range.
+///
+/// # Safety
+///
+/// This is considered unsafe because the given handler will be called asynchronously, interrupting
+/// whatever the thread was doing and therefore must only do async-signal-safe operations.
+pub unsafe fn register_rt_signal_handler(num: c_int, handler: extern "C" fn(c_int)) -> Result<()> {
+ if !valid_rt_signal_num(num) {
+ return Err(ErrnoError::new(EINVAL));
+ }
+
+ register_signal_handler(num, handler)
+}
+
+/// Creates `sigset` from an array of signal numbers.
+///
+/// This is a helper function used when we want to manipulate signals.
+pub fn create_sigset(signals: &[c_int]) -> Result<sigset_t> {
+ // sigset will actually be initialized by sigemptyset below.
+ let mut sigset: sigset_t = unsafe { mem::zeroed() };
+
+ // Safe - return value is checked.
+ let ret = unsafe { sigemptyset(&mut sigset) };
+ if ret < 0 {
+ return errno_result();
+ }
+
+ for signal in signals {
+ // Safe - return value is checked.
+ let ret = unsafe { sigaddset(&mut sigset, *signal) };
+ if ret < 0 {
+ return errno_result();
+ }
+ }
+
+ Ok(sigset)
+}
+
+/// Wait for signal before continuing. The signal number of the consumed signal is returned on
+/// success. EAGAIN means the timeout was reached.
+pub fn wait_for_signal(signals: &[c_int], timeout: Option<Duration>) -> Result<c_int> {
+ let sigset = create_sigset(signals)?;
+
+ match timeout {
+ Some(timeout) => {
+ let ts = duration_to_timespec(timeout);
+ // Safe - return value is checked.
+ let ret = handle_eintr_errno!(unsafe { sigtimedwait(&sigset, null_mut(), &ts) });
+ if ret < 0 {
+ errno_result()
+ } else {
+ Ok(ret)
+ }
+ }
+ None => {
+ let mut ret: c_int = 0;
+ let err = handle_eintr_rc!(unsafe { sigwait(&sigset, &mut ret as *mut c_int) });
+ if err != 0 {
+ Err(ErrnoError::new(err))
+ } else {
+ Ok(ret)
+ }
+ }
+ }
+}
+
+/// Retrieves the signal mask of the current thread as a vector of c_ints.
+pub fn get_blocked_signals() -> SignalResult<Vec<c_int>> {
+ let mut mask = Vec::new();
+
+ // Safe - return values are checked.
+ unsafe {
+ let mut old_sigset: sigset_t = mem::zeroed();
+ let ret = pthread_sigmask(SIG_BLOCK, null(), &mut old_sigset as *mut sigset_t);
+ if ret < 0 {
+ return Err(Error::RetrieveSignalMask(ret));
+ }
+
+ for num in 0..=SIGRTMAX() {
+ if sigismember(&old_sigset, num) > 0 {
+ mask.push(num);
+ }
+ }
+ }
+
+ Ok(mask)
+}
+
+/// Masks given signal.
+///
+/// If signal is already blocked the call will fail with Error::SignalAlreadyBlocked
+/// result.
+pub fn block_signal(num: c_int) -> SignalResult<()> {
+ let sigset = create_sigset(&[num]).map_err(Error::CreateSigset)?;
+
+ // Safe - return values are checked.
+ unsafe {
+ let mut old_sigset: sigset_t = mem::zeroed();
+ let ret = pthread_sigmask(SIG_BLOCK, &sigset, &mut old_sigset as *mut sigset_t);
+ if ret < 0 {
+ return Err(Error::BlockSignal(ErrnoError::last()));
+ }
+ let ret = sigismember(&old_sigset, num);
+ match ret.cmp(&0) {
+ Ordering::Less => {
+ return Err(Error::CompareBlockedSignals(ErrnoError::last()));
+ }
+ Ordering::Greater => {
+ return Err(Error::SignalAlreadyBlocked(num));
+ }
+ _ => (),
+ };
+ }
+ Ok(())
+}
+
+/// Unmasks given signal.
+pub fn unblock_signal(num: c_int) -> SignalResult<()> {
+ let sigset = create_sigset(&[num]).map_err(Error::CreateSigset)?;
+
+ // Safe - return value is checked.
+ let ret = unsafe { pthread_sigmask(SIG_UNBLOCK, &sigset, null_mut()) };
+ if ret < 0 {
+ return Err(Error::UnblockSignal(ErrnoError::last()));
+ }
+ Ok(())
+}
+
+/// Clears pending signal.
+pub fn clear_signal(num: c_int) -> SignalResult<()> {
+ let sigset = create_sigset(&[num]).map_err(Error::CreateSigset)?;
+
+ while {
+ // This is safe as we are rigorously checking return values
+ // of libc calls.
+ unsafe {
+ let mut siginfo: siginfo_t = mem::zeroed();
+ let ts = timespec {
+ tv_sec: 0,
+ tv_nsec: 0,
+ };
+ // Attempt to consume one instance of pending signal. If signal
+ // is not pending, the call will fail with EAGAIN or EINTR.
+ let ret = sigtimedwait(&sigset, &mut siginfo, &ts);
+ if ret < 0 {
+ let e = ErrnoError::last();
+ match e.errno() {
+ EAGAIN | EINTR => {}
+ _ => {
+ return Err(Error::ClearWaitPending(ErrnoError::last()));
+ }
+ }
+ }
+
+ // This sigset will be actually filled with `sigpending` call.
+ let mut chkset: sigset_t = mem::zeroed();
+ // See if more instances of the signal are pending.
+ let ret = sigpending(&mut chkset);
+ if ret < 0 {
+ return Err(Error::ClearGetPending(ErrnoError::last()));
+ }
+
+ let ret = sigismember(&chkset, num);
+ if ret < 0 {
+ return Err(Error::ClearCheckPending(ErrnoError::last()));
+ }
+
+ // This is do-while loop condition.
+ ret != 0
+ }
+ } {}
+
+ Ok(())
+}
+
+/// # Safety
+/// This is marked unsafe because it allows signals to be sent to arbitrary PIDs. Sending some
+/// signals may lead to undefined behavior. Also, the return codes of the child processes need to be
+/// reaped to avoid leaking zombie processes.
+pub unsafe fn kill(pid: Pid, signum: c_int) -> Result<()> {
+ let ret = libc::kill(pid, signum);
+
+ if ret != 0 {
+ errno_result()
+ } else {
+ Ok(())
+ }
+}
+
+/// Trait for threads that can be signalled via `pthread_kill`.
+///
+/// Note that this is only useful for signals between SIGRTMIN and SIGRTMAX because these are
+/// guaranteed to not be used by the C runtime.
+///
+/// This is marked unsafe because the implementation of this trait must guarantee that the returned
+/// pthread_t is valid and has a lifetime at least that of the trait object.
+pub unsafe trait Killable {
+ fn pthread_handle(&self) -> pthread_t;
+
+ /// Sends the signal `num` to this killable thread.
+ ///
+ /// The value of `num` must be within [`SIGRTMIN`, `SIGRTMAX`] range.
+ fn kill(&self, num: c_int) -> Result<()> {
+ if !valid_rt_signal_num(num) {
+ return Err(ErrnoError::new(EINVAL));
+ }
+
+ // Safe because we ensure we are using a valid pthread handle, a valid signal number, and
+ // check the return result.
+ let ret = unsafe { pthread_kill(self.pthread_handle(), num) };
+ if ret < 0 {
+ return errno_result();
+ }
+ Ok(())
+ }
+}
+
+// Safe because we fulfill our contract of returning a genuine pthread handle.
+unsafe impl<T> Killable for JoinHandle<T> {
+ fn pthread_handle(&self) -> pthread_t {
+ self.as_pthread_t()
+ }
+}
+
+/// Treat some errno's as Ok(()).
+macro_rules! ok_if {
+ ($result:expr, $errno:pat) => {{
+ match $result {
+ Err(err) if !matches!(err.errno(), $errno) => Err(err),
+ _ => Ok(()),
+ }
+ }};
+}
+
+/// Terminates and reaps a child process. If the child process is a group leader, its children will
+/// be terminated and reaped as well. After the given timeout, the child process and any relevant
+/// children are killed (i.e. sent SIGKILL).
+pub fn kill_tree(child: &mut Child, terminate_timeout: Duration) -> SignalResult<()> {
+ let target = {
+ let pid = child.id() as Pid;
+ if getsid(Some(pid)).map_err(Error::GetSid)? == pid {
+ -pid
+ } else {
+ pid
+ }
+ };
+
+ // Safe because target is a child process (or group) and behavior of SIGTERM is defined.
+ ok_if!(unsafe { kill(target, libc::SIGTERM) }, libc::ESRCH).map_err(Error::Kill)?;
+
+ // Reap the direct child first in case it waits for its descendants, afterward reap any
+ // remaining group members.
+ let start = Instant::now();
+ let mut child_running = true;
+ loop {
+ // Wait for the direct child to exit before reaping any process group members.
+ if child_running {
+ if child
+ .try_wait()
+ .map_err(|e| Error::WaitPid(ErrnoError::from(e)))?
+ .is_some()
+ {
+ child_running = false;
+ // Skip the timeout check because waitpid(..., WNOHANG) will not block.
+ continue;
+ }
+ } else {
+ // Safe because target is a child process (or group), WNOHANG is used, and the return
+ // value is checked.
+ let ret = unsafe { waitpid(target, null_mut(), WNOHANG) };
+ match ret {
+ -1 => {
+ let err = ErrnoError::last();
+ if err.errno() == libc::ECHILD {
+ // No group members to wait on.
+ break;
+ }
+ return Err(Error::WaitPid(err));
+ }
+ 0 => {}
+ // If a process was reaped, skip the timeout check in case there are more.
+ _ => continue,
+ };
+ }
+
+ // Check for a timeout.
+ let elapsed = start.elapsed();
+ if elapsed > terminate_timeout {
+ // Safe because target is a child process (or group) and behavior of SIGKILL is defined.
+ ok_if!(unsafe { kill(target, libc::SIGKILL) }, libc::ESRCH).map_err(Error::Kill)?;
+ return Err(Error::TimedOut);
+ }
+
+ // Wait a SIGCHLD or until either the remaining time or a poll interval elapses.
+ ok_if!(
+ wait_for_signal(
+ &[libc::SIGCHLD],
+ Some(POLL_RATE.min(terminate_timeout - elapsed))
+ ),
+ libc::EAGAIN | libc::EINTR
+ )
+ .map_err(Error::WaitForSignal)?
+ }
+
+ Ok(())
+}
+
+/// Wraps a Child process, and calls kill_tree for its process group to clean
+/// it up when dropped.
+pub struct KillOnDrop {
+ process: Child,
+ timeout: Duration,
+}
+
+impl KillOnDrop {
+ /// Get the timeout. See timeout_mut() for more details.
+ pub fn timeout(&self) -> Duration {
+ self.timeout
+ }
+
+ /// Change the timeout for how long child processes are waited for before
+ /// the process group is forcibly killed.
+ pub fn timeout_mut(&mut self) -> &mut Duration {
+ &mut self.timeout
+ }
+}
+
+impl From<Child> for KillOnDrop {
+ fn from(process: Child) -> Self {
+ KillOnDrop {
+ process,
+ timeout: DEFAULT_KILL_TIMEOUT,
+ }
+ }
+}
+
+impl AsRef<Child> for KillOnDrop {
+ fn as_ref(&self) -> &Child {
+ &self.process
+ }
+}
+
+impl AsMut<Child> for KillOnDrop {
+ fn as_mut(&mut self) -> &mut Child {
+ &mut self.process
+ }
+}
+
+impl Deref for KillOnDrop {
+ type Target = Child;
+
+ fn deref(&self) -> &Self::Target {
+ &self.process
+ }
+}
+
+impl DerefMut for KillOnDrop {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.process
+ }
+}
+
+impl Drop for KillOnDrop {
+ fn drop(&mut self) {
+ if let Err(err) = kill_tree(&mut self.process, self.timeout) {
+ eprintln!("failed to kill child process group: {}", err);
+ }
+ }
+}
diff --git a/common/sys_util/src/signalfd.rs b/common/sys_util/src/signalfd.rs
new file mode 100644
index 000000000..c14c7b462
--- /dev/null
+++ b/common/sys_util/src/signalfd.rs
@@ -0,0 +1,183 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ fs::File,
+ mem,
+ os::{
+ raw::c_int,
+ unix::io::{AsRawFd, FromRawFd, RawFd},
+ },
+ result,
+};
+
+use libc::{c_void, read, signalfd, signalfd_siginfo, EAGAIN, SFD_CLOEXEC, SFD_NONBLOCK};
+use remain::sorted;
+use thiserror::Error;
+
+use super::{signal, AsRawDescriptor, Error as ErrnoError, RawDescriptor};
+
+#[sorted]
+#[derive(Error, Debug)]
+pub enum Error {
+ /// Failed to block the signal when creating signalfd.
+ #[error("failed to block the signal when creating signalfd: {0}")]
+ CreateBlockSignal(signal::Error),
+ /// Failed to create a new signalfd.
+ #[error("failed to create a new signalfd: {0}")]
+ CreateSignalFd(ErrnoError),
+ /// Failed to construct sigset when creating signalfd.
+ #[error("failed to construct sigset when creating signalfd: {0}")]
+ CreateSigset(ErrnoError),
+ /// Signalfd could be read, but didn't return a full siginfo struct.
+ /// This wraps the number of bytes that were actually read.
+ #[error("signalfd failed to return a full siginfo struct, read only {0} bytes")]
+ SignalFdPartialRead(usize),
+ /// Unable to read from signalfd.
+ #[error("unable to read from signalfd: {0}")]
+ SignalFdRead(ErrnoError),
+}
+
+pub type Result<T> = result::Result<T, Error>;
+
+/// A safe wrapper around a Linux signalfd (man 2 signalfd).
+///
+/// A signalfd can be used for non-synchronous signals (such as SIGCHLD) so that
+/// signals can be processed without the use of a signal handler.
+pub struct SignalFd {
+ signalfd: File,
+ signal: c_int,
+}
+
+impl SignalFd {
+ /// Creates a new SignalFd for the given signal, blocking the normal handler
+ /// for the signal as well. Since we mask out the normal handler, this is
+ /// a risky operation - signal masking will persist across fork and even
+ /// **exec** so the user of SignalFd should think long and hard about
+ /// when to mask signals.
+ pub fn new(signal: c_int) -> Result<SignalFd> {
+ let sigset = signal::create_sigset(&[signal]).map_err(Error::CreateSigset)?;
+
+ // This is safe as we check the return value and know that fd is valid.
+ let fd = unsafe { signalfd(-1, &sigset, SFD_CLOEXEC | SFD_NONBLOCK) };
+ if fd < 0 {
+ return Err(Error::CreateSignalFd(ErrnoError::last()));
+ }
+
+ // Mask out the normal handler for the signal.
+ signal::block_signal(signal).map_err(Error::CreateBlockSignal)?;
+
+ // This is safe because we checked fd for success and know the
+ // kernel gave us an fd that we own.
+ unsafe {
+ Ok(SignalFd {
+ signalfd: File::from_raw_fd(fd),
+ signal,
+ })
+ }
+ }
+
+ /// Read a siginfo struct from the signalfd, if available.
+ pub fn read(&self) -> Result<Option<signalfd_siginfo>> {
+ // signalfd_siginfo doesn't have a default, so just zero it.
+ let mut siginfo: signalfd_siginfo = unsafe { mem::zeroed() };
+ let siginfo_size = mem::size_of::<signalfd_siginfo>();
+
+ // This read is safe since we've got the space allocated for a
+ // single signalfd_siginfo, and that's exactly how much we're
+ // reading. Handling of EINTR is not required since SFD_NONBLOCK
+ // was specified. signalfds will always read in increments of
+ // sizeof(signalfd_siginfo); see man 2 signalfd.
+ let ret = unsafe {
+ read(
+ self.signalfd.as_raw_fd(),
+ &mut siginfo as *mut signalfd_siginfo as *mut c_void,
+ siginfo_size,
+ )
+ };
+
+ if ret < 0 {
+ let err = ErrnoError::last();
+ if err.errno() == EAGAIN {
+ Ok(None)
+ } else {
+ Err(Error::SignalFdRead(err))
+ }
+ } else if ret == (siginfo_size as isize) {
+ Ok(Some(siginfo))
+ } else {
+ Err(Error::SignalFdPartialRead(ret as usize))
+ }
+ }
+}
+
+impl AsRawFd for SignalFd {
+ fn as_raw_fd(&self) -> RawFd {
+ self.signalfd.as_raw_fd()
+ }
+}
+
+impl AsRawDescriptor for SignalFd {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.signalfd.as_raw_descriptor()
+ }
+}
+
+impl Drop for SignalFd {
+ fn drop(&mut self) {
+ // This is thread-safe and safe in the sense that we're doing what
+ // was promised - unmasking the signal when we go out of scope.
+ let res = signal::unblock_signal(self.signal);
+ if let Err(e) = res {
+ error!("signalfd failed to unblock signal {}: {}", self.signal, e);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use super::super::signal::SIGRTMIN;
+ use libc::{pthread_sigmask, raise, sigismember, sigset_t};
+ use std::{mem, ptr::null};
+
+ #[test]
+ fn new() {
+ SignalFd::new(SIGRTMIN()).unwrap();
+ }
+
+ #[test]
+ fn read() {
+ let sigid = SIGRTMIN() + 1;
+ let sigrt_fd = SignalFd::new(sigid).unwrap();
+
+ let ret = unsafe { raise(sigid) };
+ assert_eq!(ret, 0);
+
+ let siginfo = sigrt_fd.read().unwrap().unwrap();
+ assert_eq!(siginfo.ssi_signo, sigid as u32);
+ }
+
+ #[test]
+ fn drop() {
+ let sigid = SIGRTMIN() + 2;
+
+ let sigrt_fd = SignalFd::new(sigid).unwrap();
+ unsafe {
+ let mut sigset: sigset_t = mem::zeroed();
+ pthread_sigmask(0, null(), &mut sigset as *mut sigset_t);
+ assert_eq!(sigismember(&sigset, sigid), 1);
+ }
+
+ mem::drop(sigrt_fd);
+
+ // The signal should no longer be masked.
+ unsafe {
+ let mut sigset: sigset_t = mem::zeroed();
+ pthread_sigmask(0, null(), &mut sigset as *mut sigset_t);
+ assert_eq!(sigismember(&sigset, sigid), 0);
+ }
+ }
+}
diff --git a/common/sys_util/src/sock_ctrl_msg.rs b/common/sys_util/src/sock_ctrl_msg.rs
new file mode 100644
index 000000000..5bc0784e2
--- /dev/null
+++ b/common/sys_util/src/sock_ctrl_msg.rs
@@ -0,0 +1,546 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Used to send and receive messages with file descriptors on sockets that accept control messages
+//! (e.g. Unix domain sockets).
+
+use std::{
+ fs::File,
+ io::{IoSlice, IoSliceMut},
+ mem::size_of,
+ os::unix::{
+ io::{AsRawFd, FromRawFd, RawFd},
+ net::{UnixDatagram, UnixStream},
+ },
+ ptr::{copy_nonoverlapping, null_mut, write_unaligned},
+ slice,
+};
+
+use libc::{
+ c_long, c_void, cmsghdr, iovec, msghdr, recvmsg, sendmsg, MSG_NOSIGNAL, SCM_RIGHTS, SOL_SOCKET,
+};
+
+use data_model::{IoBufMut, VolatileSlice};
+
+use super::{net::UnixSeqpacket, Error, Result};
+
+// Each of the following macros performs the same function as their C counterparts. They are each
+// macros because they are used to size statically allocated arrays.
+
+macro_rules! CMSG_ALIGN {
+ ($len:expr) => {
+ (($len) + size_of::<c_long>() - 1) & !(size_of::<c_long>() - 1)
+ };
+}
+
+macro_rules! CMSG_SPACE {
+ ($len:expr) => {
+ size_of::<cmsghdr>() + CMSG_ALIGN!($len)
+ };
+}
+
+macro_rules! CMSG_LEN {
+ ($len:expr) => {
+ size_of::<cmsghdr>() + ($len)
+ };
+}
+
+// This function (macro in the C version) is not used in any compile time constant slots, so is just
+// an ordinary function. The returned pointer is hard coded to be RawFd because that's all that this
+// module supports.
+#[allow(non_snake_case)]
+#[inline(always)]
+fn CMSG_DATA(cmsg_buffer: *mut cmsghdr) -> *mut RawFd {
+ // Essentially returns a pointer to just past the header.
+ cmsg_buffer.wrapping_offset(1) as *mut RawFd
+}
+
+// This function is like CMSG_NEXT, but safer because it reads only from references, although it
+// does some pointer arithmetic on cmsg_ptr.
+#[allow(clippy::cast_ptr_alignment)]
+fn get_next_cmsg(msghdr: &msghdr, cmsg: &cmsghdr, cmsg_ptr: *mut cmsghdr) -> *mut cmsghdr {
+ let next_cmsg = (cmsg_ptr as *mut u8).wrapping_add(CMSG_ALIGN!(cmsg.cmsg_len)) as *mut cmsghdr;
+ if next_cmsg
+ .wrapping_offset(1)
+ .wrapping_sub(msghdr.msg_control as usize) as usize
+ > msghdr.msg_controllen
+ {
+ null_mut()
+ } else {
+ next_cmsg
+ }
+}
+
+const CMSG_BUFFER_INLINE_CAPACITY: usize = CMSG_SPACE!(size_of::<RawFd>() * 32);
+
+enum CmsgBuffer {
+ Inline([u64; (CMSG_BUFFER_INLINE_CAPACITY + 7) / 8]),
+ Heap(Box<[cmsghdr]>),
+}
+
+impl CmsgBuffer {
+ fn with_capacity(capacity: usize) -> CmsgBuffer {
+ let cap_in_cmsghdr_units =
+ (capacity.checked_add(size_of::<cmsghdr>()).unwrap() - 1) / size_of::<cmsghdr>();
+ if capacity <= CMSG_BUFFER_INLINE_CAPACITY {
+ CmsgBuffer::Inline([0u64; (CMSG_BUFFER_INLINE_CAPACITY + 7) / 8])
+ } else {
+ CmsgBuffer::Heap(
+ vec![
+ cmsghdr {
+ cmsg_len: 0,
+ cmsg_level: 0,
+ cmsg_type: 0,
+ };
+ cap_in_cmsghdr_units
+ ]
+ .into_boxed_slice(),
+ )
+ }
+ }
+
+ fn as_mut_ptr(&mut self) -> *mut cmsghdr {
+ match self {
+ CmsgBuffer::Inline(a) => a.as_mut_ptr() as *mut cmsghdr,
+ CmsgBuffer::Heap(a) => a.as_mut_ptr(),
+ }
+ }
+}
+
+fn raw_sendmsg<D: AsIobuf>(fd: RawFd, out_data: &[D], out_fds: &[RawFd]) -> Result<usize> {
+ let cmsg_capacity = CMSG_SPACE!(size_of::<RawFd>() * out_fds.len());
+ let mut cmsg_buffer = CmsgBuffer::with_capacity(cmsg_capacity);
+
+ let iovec = AsIobuf::as_iobuf_slice(out_data);
+
+ let mut msg = msghdr {
+ msg_name: null_mut(),
+ msg_namelen: 0,
+ msg_iov: iovec.as_ptr() as *mut iovec,
+ msg_iovlen: iovec.len(),
+ msg_control: null_mut(),
+ msg_controllen: 0,
+ msg_flags: 0,
+ };
+
+ if !out_fds.is_empty() {
+ let cmsg = cmsghdr {
+ cmsg_len: CMSG_LEN!(size_of::<RawFd>() * out_fds.len()),
+ cmsg_level: SOL_SOCKET,
+ cmsg_type: SCM_RIGHTS,
+ };
+ unsafe {
+ // Safe because cmsg_buffer was allocated to be large enough to contain cmsghdr.
+ write_unaligned(cmsg_buffer.as_mut_ptr() as *mut cmsghdr, cmsg);
+ // Safe because the cmsg_buffer was allocated to be large enough to hold out_fds.len()
+ // file descriptors.
+ copy_nonoverlapping(
+ out_fds.as_ptr(),
+ CMSG_DATA(cmsg_buffer.as_mut_ptr()),
+ out_fds.len(),
+ );
+ }
+
+ msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void;
+ msg.msg_controllen = cmsg_capacity;
+ }
+
+ // Safe because the msghdr was properly constructed from valid (or null) pointers of the
+ // indicated length and we check the return value.
+ let write_count = unsafe { sendmsg(fd, &msg, MSG_NOSIGNAL) };
+
+ if write_count == -1 {
+ Err(Error::last())
+ } else {
+ Ok(write_count as usize)
+ }
+}
+
+fn raw_recvmsg(fd: RawFd, iovs: &mut [IoSliceMut], in_fds: &mut [RawFd]) -> Result<(usize, usize)> {
+ let cmsg_capacity = CMSG_SPACE!(size_of::<RawFd>() * in_fds.len());
+ let mut cmsg_buffer = CmsgBuffer::with_capacity(cmsg_capacity);
+
+ let mut msg = msghdr {
+ msg_name: null_mut(),
+ msg_namelen: 0,
+ msg_iov: iovs.as_mut_ptr() as *mut iovec,
+ msg_iovlen: iovs.len(),
+ msg_control: null_mut(),
+ msg_controllen: 0,
+ msg_flags: 0,
+ };
+
+ if !in_fds.is_empty() {
+ msg.msg_control = cmsg_buffer.as_mut_ptr() as *mut c_void;
+ msg.msg_controllen = cmsg_capacity;
+ }
+
+ // Safe because the msghdr was properly constructed from valid (or null) pointers of the
+ // indicated length and we check the return value.
+ let total_read = unsafe { recvmsg(fd, &mut msg, 0) };
+
+ if total_read == -1 {
+ return Err(Error::last());
+ }
+
+ if total_read == 0 && msg.msg_controllen < size_of::<cmsghdr>() {
+ return Ok((0, 0));
+ }
+
+ let mut cmsg_ptr = msg.msg_control as *mut cmsghdr;
+ let mut in_fds_count = 0;
+ while !cmsg_ptr.is_null() {
+ // Safe because we checked that cmsg_ptr was non-null, and the loop is constructed such that
+ // that only happens when there is at least sizeof(cmsghdr) space after the pointer to read.
+ let cmsg = unsafe { (cmsg_ptr as *mut cmsghdr).read_unaligned() };
+
+ if cmsg.cmsg_level == SOL_SOCKET && cmsg.cmsg_type == SCM_RIGHTS {
+ let fd_count = (cmsg.cmsg_len - CMSG_LEN!(0)) / size_of::<RawFd>();
+ unsafe {
+ copy_nonoverlapping(
+ CMSG_DATA(cmsg_ptr),
+ in_fds[in_fds_count..(in_fds_count + fd_count)].as_mut_ptr(),
+ fd_count,
+ );
+ }
+ in_fds_count += fd_count;
+ }
+
+ cmsg_ptr = get_next_cmsg(&msg, &cmsg, cmsg_ptr);
+ }
+
+ Ok((total_read as usize, in_fds_count))
+}
+
+/// The maximum number of FDs that can be sent in a single send.
+pub const SCM_SOCKET_MAX_FD_COUNT: usize = 253;
+
+/// Trait for file descriptors can send and receive socket control messages via `sendmsg` and
+/// `recvmsg`.
+pub trait ScmSocket {
+ /// Gets the file descriptor of this socket.
+ fn socket_fd(&self) -> RawFd;
+
+ /// Sends the given data and file descriptor over the socket.
+ ///
+ /// On success, returns the number of bytes sent.
+ ///
+ /// # Arguments
+ ///
+ /// * `buf` - A buffer of data to send on the `socket`.
+ /// * `fd` - A file descriptors to be sent.
+ fn send_with_fd<D: AsIobuf>(&self, buf: &[D], fd: RawFd) -> Result<usize> {
+ self.send_with_fds(buf, &[fd])
+ }
+
+ /// Sends the given data and file descriptors over the socket.
+ ///
+ /// On success, returns the number of bytes sent.
+ ///
+ /// # Arguments
+ ///
+ /// * `buf` - A buffer of data to send on the `socket`.
+ /// * `fds` - A list of file descriptors to be sent.
+ fn send_with_fds<D: AsIobuf>(&self, buf: &[D], fd: &[RawFd]) -> Result<usize> {
+ raw_sendmsg(self.socket_fd(), buf, fd)
+ }
+
+ /// Sends the given data and file descriptor over the socket.
+ ///
+ /// On success, returns the number of bytes sent.
+ ///
+ /// # Arguments
+ ///
+ /// * `bufs` - A slice of slices of data to send on the `socket`.
+ /// * `fd` - A file descriptors to be sent.
+ fn send_bufs_with_fd(&self, bufs: &[IoSlice], fd: RawFd) -> Result<usize> {
+ self.send_bufs_with_fds(bufs, &[fd])
+ }
+
+ /// Sends the given data and file descriptors over the socket.
+ ///
+ /// On success, returns the number of bytes sent.
+ ///
+ /// # Arguments
+ ///
+ /// * `bufs` - A slice of slices of data to send on the `socket`.
+ /// * `fds` - A list of file descriptors to be sent.
+ fn send_bufs_with_fds(&self, bufs: &[IoSlice], fd: &[RawFd]) -> Result<usize> {
+ raw_sendmsg(self.socket_fd(), bufs, fd)
+ }
+
+ /// Receives data and potentially a file descriptor from the socket.
+ ///
+ /// On success, returns the number of bytes and an optional file descriptor.
+ ///
+ /// # Arguments
+ ///
+ /// * `buf` - A buffer to receive data from the socket.vm
+ fn recv_with_fd(&self, buf: IoSliceMut) -> Result<(usize, Option<File>)> {
+ let mut fd = [0];
+ let (read_count, fd_count) = self.recv_with_fds(buf, &mut fd)?;
+ let file = if fd_count == 0 {
+ None
+ } else {
+ // Safe because the first fd from recv_with_fds is owned by us and valid because this
+ // branch was taken.
+ Some(unsafe { File::from_raw_fd(fd[0]) })
+ };
+ Ok((read_count, file))
+ }
+
+ /// Receives data and file descriptors from the socket.
+ ///
+ /// On success, returns the number of bytes and file descriptors received as a tuple
+ /// `(bytes count, files count)`.
+ ///
+ /// # Arguments
+ ///
+ /// * `buf` - A buffer to receive data from the socket.
+ /// * `fds` - A slice of `RawFd`s to put the received file descriptors into. On success, the
+ /// number of valid file descriptors is indicated by the second element of the
+ /// returned tuple. The caller owns these file descriptors, but they will not be
+ /// closed on drop like a `File`-like type would be. It is recommended that each valid
+ /// file descriptor gets wrapped in a drop type that closes it after this returns.
+ fn recv_with_fds(&self, buf: IoSliceMut, fds: &mut [RawFd]) -> Result<(usize, usize)> {
+ raw_recvmsg(self.socket_fd(), &mut [buf], fds)
+ }
+
+ /// Receives data and file descriptors from the socket.
+ ///
+ /// On success, returns the number of bytes and file descriptors received as a tuple
+ /// `(bytes count, files count)`.
+ ///
+ /// # Arguments
+ ///
+ /// * `iovecs` - A slice of buffers to store received data.
+ /// * `offset` - An offset for `bufs`. The first `offset` bytes in `bufs` won't be touched.
+ /// Returns an error if `offset` is larger than or equal to the total size of
+ /// `bufs`.
+ /// * `fds` - A slice of `RawFd`s to put the received file descriptors into. On success, the
+ /// number of valid file descriptors is indicated by the second element of the
+ /// returned tuple. The caller owns these file descriptors, but they will not be
+ /// closed on drop like a `File`-like type would be. It is recommended that each valid
+ /// file descriptor gets wrapped in a drop type that closes it after this returns.
+ fn recv_iovecs_with_fds(
+ &self,
+ iovecs: &mut [IoSliceMut],
+ fds: &mut [RawFd],
+ ) -> Result<(usize, usize)> {
+ raw_recvmsg(self.socket_fd(), iovecs, fds)
+ }
+}
+
+impl ScmSocket for UnixDatagram {
+ fn socket_fd(&self) -> RawFd {
+ self.as_raw_fd()
+ }
+}
+
+impl ScmSocket for UnixStream {
+ fn socket_fd(&self) -> RawFd {
+ self.as_raw_fd()
+ }
+}
+
+impl ScmSocket for UnixSeqpacket {
+ fn socket_fd(&self) -> RawFd {
+ self.as_raw_fd()
+ }
+}
+
+/// Trait for types that can be converted into an `iovec` that can be referenced by a syscall for
+/// the lifetime of this object.
+///
+/// This trait is unsafe because interfaces that use this trait depend on the base pointer and size
+/// being accurate.
+pub unsafe trait AsIobuf: Sized {
+ /// Returns a `iovec` that describes a contiguous region of memory.
+ fn as_iobuf(&self) -> iovec;
+
+ /// Returns a slice of `iovec`s that each describe a contiguous region of memory.
+ #[allow(clippy::wrong_self_convention)]
+ fn as_iobuf_slice(bufs: &[Self]) -> &[iovec];
+}
+
+// Safe because there are no other mutable references to the memory described by `IoSlice` and it is
+// guaranteed to be ABI-compatible with `iovec`.
+unsafe impl<'a> AsIobuf for IoSlice<'a> {
+ fn as_iobuf(&self) -> iovec {
+ iovec {
+ iov_base: self.as_ptr() as *mut c_void,
+ iov_len: self.len(),
+ }
+ }
+
+ fn as_iobuf_slice(bufs: &[Self]) -> &[iovec] {
+ // Safe because `IoSlice` is guaranteed to be ABI-compatible with `iovec`.
+ unsafe { slice::from_raw_parts(bufs.as_ptr() as *const iovec, bufs.len()) }
+ }
+}
+
+// Safe because there are no other references to the memory described by `IoSliceMut` and it is
+// guaranteed to be ABI-compatible with `iovec`.
+unsafe impl<'a> AsIobuf for IoSliceMut<'a> {
+ fn as_iobuf(&self) -> iovec {
+ iovec {
+ iov_base: self.as_ptr() as *mut c_void,
+ iov_len: self.len(),
+ }
+ }
+
+ fn as_iobuf_slice(bufs: &[Self]) -> &[iovec] {
+ // Safe because `IoSliceMut` is guaranteed to be ABI-compatible with `iovec`.
+ unsafe { slice::from_raw_parts(bufs.as_ptr() as *const iovec, bufs.len()) }
+ }
+}
+
+// Safe because volatile slices are only ever accessed with other volatile interfaces and the
+// pointer and size are guaranteed to be accurate.
+unsafe impl<'a> AsIobuf for VolatileSlice<'a> {
+ fn as_iobuf(&self) -> iovec {
+ *self.as_iobuf().as_ref()
+ }
+
+ fn as_iobuf_slice(bufs: &[Self]) -> &[iovec] {
+ IoBufMut::as_iobufs(VolatileSlice::as_iobufs(bufs))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use std::{
+ io::Write,
+ mem::size_of,
+ os::{raw::c_long, unix::net::UnixDatagram},
+ slice::from_raw_parts,
+ };
+
+ use libc::cmsghdr;
+
+ use super::super::EventFd;
+
+ // Doing this as a macro makes it easier to see the line if it fails
+ macro_rules! CMSG_SPACE_TEST {
+ ($len:literal) => {
+ assert_eq!(
+ CMSG_SPACE!(size_of::<[RawFd; $len]>()) as libc::c_uint,
+ unsafe { libc::CMSG_SPACE(size_of::<[RawFd; $len]>() as libc::c_uint) }
+ );
+ };
+ }
+
+ #[test]
+ #[allow(clippy::erasing_op, clippy::identity_op)]
+ fn buffer_len() {
+ CMSG_SPACE_TEST!(0);
+ CMSG_SPACE_TEST!(1);
+ CMSG_SPACE_TEST!(2);
+ CMSG_SPACE_TEST!(3);
+ CMSG_SPACE_TEST!(4);
+ }
+
+ #[test]
+ fn send_recv_no_fd() {
+ let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
+
+ let send_buf = [1u8, 1, 2, 21, 34, 55];
+ let ioslice = IoSlice::new(&send_buf);
+ let write_count = s1
+ .send_with_fds(&[ioslice], &[])
+ .expect("failed to send data");
+
+ assert_eq!(write_count, 6);
+
+ let mut buf = [0; 6];
+ let mut files = [0; 1];
+ let (read_count, file_count) = s2
+ .recv_with_fds(IoSliceMut::new(&mut buf), &mut files)
+ .expect("failed to recv data");
+
+ assert_eq!(read_count, 6);
+ assert_eq!(file_count, 0);
+ assert_eq!(buf, [1, 1, 2, 21, 34, 55]);
+
+ let write_count = s1
+ .send_bufs_with_fds(&[IoSlice::new(&send_buf[..])], &[])
+ .expect("failed to send data");
+
+ assert_eq!(write_count, 6);
+ let (read_count, file_count) = s2
+ .recv_with_fds(IoSliceMut::new(&mut buf), &mut files)
+ .expect("failed to recv data");
+
+ assert_eq!(read_count, 6);
+ assert_eq!(file_count, 0);
+ assert_eq!(buf, [1, 1, 2, 21, 34, 55]);
+ }
+
+ #[test]
+ fn send_recv_only_fd() {
+ let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
+
+ let evt = EventFd::new().expect("failed to create eventfd");
+ let ioslice = IoSlice::new([].as_ref());
+ let write_count = s1
+ .send_with_fd(&[ioslice], evt.as_raw_fd())
+ .expect("failed to send fd");
+
+ assert_eq!(write_count, 0);
+
+ let mut buf = [];
+ let (read_count, file_opt) = s2
+ .recv_with_fd(IoSliceMut::new(&mut buf))
+ .expect("failed to recv fd");
+
+ let mut file = file_opt.unwrap();
+
+ assert_eq!(read_count, 0);
+ assert!(file.as_raw_fd() >= 0);
+ assert_ne!(file.as_raw_fd(), s1.as_raw_fd());
+ assert_ne!(file.as_raw_fd(), s2.as_raw_fd());
+ assert_ne!(file.as_raw_fd(), evt.as_raw_fd());
+
+ file.write_all(unsafe { from_raw_parts(&1203u64 as *const u64 as *const u8, 8) })
+ .expect("failed to write to sent fd");
+
+ assert_eq!(evt.read().expect("failed to read from eventfd"), 1203);
+ }
+
+ #[test]
+ fn send_recv_with_fd() {
+ let (s1, s2) = UnixDatagram::pair().expect("failed to create socket pair");
+
+ let evt = EventFd::new().expect("failed to create eventfd");
+ let ioslice = IoSlice::new([237].as_ref());
+ let write_count = s1
+ .send_with_fds(&[ioslice], &[evt.as_raw_fd()])
+ .expect("failed to send fd");
+
+ assert_eq!(write_count, 1);
+
+ let mut files = [0; 2];
+ let mut buf = [0u8];
+ let (read_count, file_count) = s2
+ .recv_with_fds(IoSliceMut::new(&mut buf), &mut files)
+ .expect("failed to recv fd");
+
+ assert_eq!(read_count, 1);
+ assert_eq!(buf[0], 237);
+ assert_eq!(file_count, 1);
+ assert!(files[0] >= 0);
+ assert_ne!(files[0], s1.as_raw_fd());
+ assert_ne!(files[0], s2.as_raw_fd());
+ assert_ne!(files[0], evt.as_raw_fd());
+
+ let mut file = unsafe { File::from_raw_fd(files[0]) };
+
+ file.write_all(unsafe { from_raw_parts(&1203u64 as *const u64 as *const u8, 8) })
+ .expect("failed to write to sent fd");
+
+ assert_eq!(evt.read().expect("failed to read from eventfd"), 1203);
+ }
+}
diff --git a/sys_util/src/syslog.rs b/common/sys_util/src/syslog.rs
index a20db926f..dfc4c5292 100644
--- a/sys_util/src/syslog.rs
+++ b/common/sys_util/src/syslog.rs
@@ -20,19 +20,24 @@
//! error!("something went horribly wrong: {}", "out of RAMs");
//! ```
-use crate::target_os::syslog::PlatformSyslog;
-use crate::RawDescriptor;
-use std::env;
-use std::ffi::{OsStr, OsString};
-use std::fmt::{self, Display};
-use std::fs::File;
-use std::io;
-use std::io::{stderr, Cursor, Write};
-use std::os::unix::io::{AsRawFd, RawFd};
-use std::path::PathBuf;
-use std::sync::{MutexGuard, Once};
-
+use super::{target_os::syslog::PlatformSyslog, RawDescriptor};
+use std::{
+ env,
+ ffi::{OsStr, OsString},
+ fmt::{
+ Display, {self},
+ },
+ fs::File,
+ io,
+ io::{stderr, Cursor, Write},
+ os::unix::io::{AsRawFd, RawFd},
+ path::PathBuf,
+ sync::{MutexGuard, Once},
+};
+
+use remain::sorted;
use sync::Mutex;
+use thiserror::Error as ThisError;
/// The priority (i.e. severity) of a syslog message.
///
@@ -93,35 +98,27 @@ pub enum Facility {
}
/// Errors returned by `syslog::init()`.
-#[derive(Debug)]
+#[sorted]
+#[derive(ThisError, Debug)]
pub enum Error {
+ /// Error while attempting to connect socket.
+ #[error("failed to connect socket: {0}")]
+ Connect(io::Error),
+ /// There was an error using `open` to get the lowest file descriptor.
+ #[error("failed to get lowest file descriptor: {0}")]
+ GetLowestFd(io::Error),
+ /// The guess of libc's file descriptor for the syslog connection was invalid.
+ #[error("guess of fd for syslog connection was invalid")]
+ InvalidFd,
/// Initialization was never attempted.
+ #[error("initialization was never attempted")]
NeverInitialized,
/// Initialization has previously failed and can not be retried.
+ #[error("initialization previously failed and cannot be retried")]
Poisoned,
/// Error while creating socket.
+ #[error("failed to create socket: {0}")]
Socket(io::Error),
- /// Error while attempting to connect socket.
- Connect(io::Error),
- // There was an error using `open` to get the lowest file descriptor.
- GetLowestFd(io::Error),
- // The guess of libc's file descriptor for the syslog connection was invalid.
- InvalidFd,
-}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- NeverInitialized => write!(f, "initialization was never attempted"),
- Poisoned => write!(f, "initialization previously failed and cannot be retried"),
- Socket(e) => write!(f, "failed to create socket: {}", e),
- Connect(e) => write!(f, "failed to connect socket: {}", e),
- GetLowestFd(e) => write!(f, "failed to get lowest file descriptor: {}", e),
- InvalidFd => write!(f, "guess of fd for syslog connection was invalid"),
- }
- }
}
fn get_proc_name() -> Option<String> {
@@ -196,7 +193,7 @@ macro_rules! lock {
match lock() {
Ok(s) => s,
_ => return,
- };
+ }
};
}
@@ -456,9 +453,11 @@ mod tests {
use libc::{shm_open, shm_unlink, O_CREAT, O_EXCL, O_RDWR};
- use std::ffi::CStr;
- use std::io::{Read, Seek, SeekFrom};
- use std::os::unix::io::FromRawFd;
+ use std::{
+ ffi::CStr,
+ io::{Read, Seek, SeekFrom},
+ os::unix::io::FromRawFd,
+ };
#[test]
fn init_syslog() {
@@ -567,7 +566,7 @@ mod tests {
let s = "Writing string to syslog\n";
syslogger
- .write_all(&s.as_bytes())
+ .write_all(s.as_bytes())
.expect("error writing string");
}
@@ -579,7 +578,7 @@ mod tests {
let s = "Writing partial string";
// Should not log because there is no newline character
syslogger
- .write_all(&s.as_bytes())
+ .write_all(s.as_bytes())
.expect("error writing string");
}
}
diff --git a/common/sys_util/src/terminal.rs b/common/sys_util/src/terminal.rs
new file mode 100644
index 000000000..caafee3de
--- /dev/null
+++ b/common/sys_util/src/terminal.rs
@@ -0,0 +1,94 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{io::Stdin, mem::zeroed, os::unix::io::RawFd};
+
+use libc::{
+ isatty, read, tcgetattr, tcsetattr, termios, ECHO, ICANON, ISIG, O_NONBLOCK, STDIN_FILENO,
+ TCSANOW,
+};
+
+use super::{add_fd_flags, clear_fd_flags, errno_result, Result};
+
+fn modify_mode<F: FnOnce(&mut termios)>(fd: RawFd, f: F) -> Result<()> {
+ // Safe because we check the return value of isatty.
+ if unsafe { isatty(fd) } != 1 {
+ return Ok(());
+ }
+
+ // The following pair are safe because termios gets totally overwritten by tcgetattr and we
+ // check the return result.
+ let mut termios: termios = unsafe { zeroed() };
+ let ret = unsafe { tcgetattr(fd, &mut termios as *mut _) };
+ if ret < 0 {
+ return errno_result();
+ }
+ let mut new_termios = termios;
+ f(&mut new_termios);
+ // Safe because the syscall will only read the extent of termios and we check the return result.
+ let ret = unsafe { tcsetattr(fd, TCSANOW, &new_termios as *const _) };
+ if ret < 0 {
+ return errno_result();
+ }
+
+ Ok(())
+}
+
+/// Safe only when the FD given is valid and reading the fd will have no Rust safety implications.
+unsafe fn read_raw(fd: RawFd, out: &mut [u8]) -> Result<usize> {
+ let ret = read(fd, out.as_mut_ptr() as *mut _, out.len());
+ if ret < 0 {
+ return errno_result();
+ }
+
+ Ok(ret as usize)
+}
+
+/// Read raw bytes from stdin.
+///
+/// This will block depending on the underlying mode of stdin. This will ignore the usual lock
+/// around stdin that the stdlib usually uses. If other code is using stdin, it is undefined who
+/// will get the underlying bytes.
+pub fn read_raw_stdin(out: &mut [u8]) -> Result<usize> {
+ // Safe because reading from stdin shouldn't have any safety implications.
+ unsafe { read_raw(STDIN_FILENO, out) }
+}
+
+/// Trait for file descriptors that are TTYs, according to `isatty(3)`.
+///
+/// This is marked unsafe because the implementation must promise that the returned RawFd is a valid
+/// fd and that the lifetime of the returned fd is at least that of the trait object.
+pub unsafe trait Terminal {
+ /// Gets the file descriptor of the TTY.
+ fn tty_fd(&self) -> RawFd;
+
+ /// Set this terminal's mode to canonical mode (`ICANON | ECHO | ISIG`).
+ fn set_canon_mode(&self) -> Result<()> {
+ modify_mode(self.tty_fd(), |t| t.c_lflag |= ICANON | ECHO | ISIG)
+ }
+
+ /// Set this terminal's mode to raw mode (`!(ICANON | ECHO | ISIG)`).
+ fn set_raw_mode(&self) -> Result<()> {
+ modify_mode(self.tty_fd(), |t| t.c_lflag &= !(ICANON | ECHO | ISIG))
+ }
+
+ /// Sets the non-blocking mode of this terminal's file descriptor.
+ ///
+ /// If `non_block` is `true`, then `read_raw` will not block. If `non_block` is `false`, then
+ /// `read_raw` may block if there is nothing to read.
+ fn set_non_block(&self, non_block: bool) -> Result<()> {
+ if non_block {
+ add_fd_flags(self.tty_fd(), O_NONBLOCK)
+ } else {
+ clear_fd_flags(self.tty_fd(), O_NONBLOCK)
+ }
+ }
+}
+
+// Safe because we return a genuine terminal fd that never changes and shares our lifetime.
+unsafe impl Terminal for Stdin {
+ fn tty_fd(&self) -> RawFd {
+ STDIN_FILENO
+ }
+}
diff --git a/common/sys_util/src/timerfd.rs b/common/sys_util/src/timerfd.rs
new file mode 100644
index 000000000..b6cb93541
--- /dev/null
+++ b/common/sys_util/src/timerfd.rs
@@ -0,0 +1,296 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ fs::File,
+ mem,
+ os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd},
+ ptr,
+ sync::Arc,
+ time::Duration,
+};
+use sync::Mutex;
+
+use libc::{self, clock_getres, timerfd_create, timerfd_settime, CLOCK_MONOTONIC, TFD_CLOEXEC};
+
+use super::{errno_result, EventFd, FakeClock, Result};
+
+/// A safe wrapper around a Linux timerfd (man 2 timerfd_create).
+pub struct TimerFd(File);
+
+impl TimerFd {
+ /// Creates a new timerfd. The timer is initally disarmed and must be armed by calling
+ /// `reset`.
+ pub fn new() -> Result<TimerFd> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe { timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC) };
+ if ret < 0 {
+ return errno_result();
+ }
+
+ // Safe because we uniquely own the file descriptor.
+ Ok(TimerFd(unsafe { File::from_raw_fd(ret) }))
+ }
+
+ /// Creates a new `TimerFd` instance that shares the same underlying `File` as the existing
+ /// `TimerFd` instance.
+ pub fn try_clone(&self) -> std::result::Result<TimerFd, std::io::Error> {
+ self.0.try_clone().map(TimerFd)
+ }
+
+ /// Sets the timer to expire after `dur`. If `interval` is not `None` it represents
+ /// the period for repeated expirations after the initial expiration. Otherwise
+ /// the timer will expire just once. Cancels any existing duration and repeating interval.
+ pub fn reset(&self, dur: Duration, interval: Option<Duration>) -> Result<()> {
+ // Safe because we are zero-initializing a struct with only primitive member fields.
+ let mut spec: libc::itimerspec = unsafe { mem::zeroed() };
+ spec.it_value.tv_sec = dur.as_secs() as libc::time_t;
+ // nsec always fits in i32 because subsec_nanos is defined to be less than one billion.
+ let nsec = dur.subsec_nanos() as i32;
+ spec.it_value.tv_nsec = libc::c_long::from(nsec);
+
+ if let Some(int) = interval {
+ spec.it_interval.tv_sec = int.as_secs() as libc::time_t;
+ // nsec always fits in i32 because subsec_nanos is defined to be less than one billion.
+ let nsec = int.subsec_nanos() as i32;
+ spec.it_interval.tv_nsec = libc::c_long::from(nsec);
+ }
+
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe { timerfd_settime(self.as_raw_fd(), 0, &spec, ptr::null_mut()) };
+ if ret < 0 {
+ return errno_result();
+ }
+
+ Ok(())
+ }
+
+ /// Waits until the timer expires. The return value represents the number of times the timer
+ /// has expired since the last time `wait` was called. If the timer has not yet expired once
+ /// this call will block until it does.
+ pub fn wait(&self) -> Result<u64> {
+ let mut count = 0u64;
+
+ // Safe because this will only modify |buf| and we check the return value.
+ let ret = unsafe {
+ libc::read(
+ self.as_raw_fd(),
+ &mut count as *mut _ as *mut libc::c_void,
+ mem::size_of_val(&count),
+ )
+ };
+ if ret < 0 {
+ return errno_result();
+ }
+
+ // The bytes in the buffer are guaranteed to be in native byte-order so we don't need to
+ // use from_le or from_be.
+ Ok(count)
+ }
+
+ /// Disarms the timer.
+ pub fn clear(&self) -> Result<()> {
+ // Safe because we are zero-initializing a struct with only primitive member fields.
+ let spec: libc::itimerspec = unsafe { mem::zeroed() };
+
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe { timerfd_settime(self.as_raw_fd(), 0, &spec, ptr::null_mut()) };
+ if ret < 0 {
+ return errno_result();
+ }
+
+ Ok(())
+ }
+
+ /// Returns the resolution of timers on the host.
+ pub fn resolution() -> Result<Duration> {
+ // Safe because we are zero-initializing a struct with only primitive member fields.
+ let mut res: libc::timespec = unsafe { mem::zeroed() };
+
+ // Safe because it only modifies a local struct and we check the return value.
+ let ret = unsafe { clock_getres(CLOCK_MONOTONIC, &mut res) };
+
+ if ret != 0 {
+ return errno_result();
+ }
+
+ Ok(Duration::new(res.tv_sec as u64, res.tv_nsec as u32))
+ }
+}
+
+impl AsRawFd for TimerFd {
+ fn as_raw_fd(&self) -> RawFd {
+ self.0.as_raw_fd()
+ }
+}
+
+impl FromRawFd for TimerFd {
+ unsafe fn from_raw_fd(fd: RawFd) -> Self {
+ TimerFd(File::from_raw_fd(fd))
+ }
+}
+
+impl IntoRawFd for TimerFd {
+ fn into_raw_fd(self) -> RawFd {
+ self.0.into_raw_fd()
+ }
+}
+
+/// FakeTimerFd: For use in tests.
+pub struct FakeTimerFd {
+ clock: Arc<Mutex<FakeClock>>,
+ deadline_ns: Option<u64>,
+ interval: Option<Duration>,
+ fd: EventFd,
+}
+
+impl FakeTimerFd {
+ /// Creates a new fake timerfd. The timer is initally disarmed and must be armed by calling
+ /// `reset`.
+ pub fn new(clock: Arc<Mutex<FakeClock>>) -> Self {
+ FakeTimerFd {
+ clock,
+ deadline_ns: None,
+ interval: None,
+ fd: EventFd::new().unwrap(),
+ }
+ }
+
+ fn duration_to_nanos(d: Duration) -> u64 {
+ d.as_secs() * 1_000_000_000 + u64::from(d.subsec_nanos())
+ }
+
+ /// Sets the timer to expire after `dur`. If `interval` is not `None` it represents
+ /// the period for repeated expirations after the initial expiration. Otherwise
+ /// the timer will expire just once. Cancels any existing duration and repeating interval.
+ pub fn reset(&mut self, dur: Duration, interval: Option<Duration>) -> Result<()> {
+ let mut guard = self.clock.lock();
+ let deadline = guard.nanos() + FakeTimerFd::duration_to_nanos(dur);
+ self.deadline_ns = Some(deadline);
+ self.interval = interval;
+ guard.add_event_fd(deadline, self.fd.try_clone()?);
+ Ok(())
+ }
+
+ /// Waits until the timer expires. The return value represents the number of times the timer
+ /// has expired since the last time `wait` was called. If the timer has not yet expired once
+ /// this call will block until it does.
+ pub fn wait(&mut self) -> Result<u64> {
+ loop {
+ self.fd.read()?;
+ if let Some(deadline_ns) = &mut self.deadline_ns {
+ let mut guard = self.clock.lock();
+ let now = guard.nanos();
+ if now >= *deadline_ns {
+ let mut expirys = 0;
+ if let Some(interval) = self.interval {
+ let interval_ns = FakeTimerFd::duration_to_nanos(interval);
+ if interval_ns > 0 {
+ expirys += (now - *deadline_ns) / interval_ns;
+ *deadline_ns += (expirys + 1) * interval_ns;
+ guard.add_event_fd(*deadline_ns, self.fd.try_clone()?);
+ }
+ }
+ return Ok(expirys + 1);
+ }
+ }
+ }
+ }
+
+ /// Disarms the timer.
+ pub fn clear(&mut self) -> Result<()> {
+ self.deadline_ns = None;
+ self.interval = None;
+ Ok(())
+ }
+
+ /// Returns the resolution of timers on the host.
+ pub fn resolution() -> Result<Duration> {
+ Ok(Duration::from_nanos(1))
+ }
+}
+
+impl AsRawFd for FakeTimerFd {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd.as_raw_fd()
+ }
+}
+
+impl IntoRawFd for FakeTimerFd {
+ fn into_raw_fd(self) -> RawFd {
+ self.fd.into_raw_fd()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::{
+ thread::sleep,
+ time::{Duration, Instant},
+ };
+
+ #[test]
+ fn one_shot() {
+ let tfd = TimerFd::new().expect("failed to create timerfd");
+
+ let dur = Duration::from_millis(200);
+ let now = Instant::now();
+ tfd.reset(dur, None).expect("failed to arm timer");
+
+ let count = tfd.wait().expect("unable to wait for timer");
+
+ assert_eq!(count, 1);
+ assert!(now.elapsed() >= dur);
+ }
+
+ #[test]
+ fn repeating() {
+ let tfd = TimerFd::new().expect("failed to create timerfd");
+
+ let dur = Duration::from_millis(200);
+ let interval = Duration::from_millis(100);
+ tfd.reset(dur, Some(interval)).expect("failed to arm timer");
+
+ sleep(dur * 3);
+
+ let count = tfd.wait().expect("unable to wait for timer");
+ assert!(count >= 5, "count = {}", count);
+ }
+
+ #[test]
+ fn fake_one_shot() {
+ let clock = Arc::new(Mutex::new(FakeClock::new()));
+ let mut tfd = FakeTimerFd::new(clock.clone());
+
+ let dur = Duration::from_nanos(200);
+ tfd.reset(dur, None).expect("failed to arm timer");
+
+ clock.lock().add_ns(200);
+
+ let count = tfd.wait().expect("unable to wait for timer");
+
+ assert_eq!(count, 1);
+ }
+
+ #[test]
+ fn fake_repeating() {
+ let clock = Arc::new(Mutex::new(FakeClock::new()));
+ let mut tfd = FakeTimerFd::new(clock.clone());
+
+ let dur = Duration::from_nanos(200);
+ let interval = Duration::from_nanos(100);
+ tfd.reset(dur, Some(interval)).expect("failed to arm timer");
+
+ clock.lock().add_ns(300);
+
+ let mut count = tfd.wait().expect("unable to wait for timer");
+ // An expiration from the initial expiry and from 1 repeat.
+ assert_eq!(count, 2);
+
+ clock.lock().add_ns(300);
+ count = tfd.wait().expect("unable to wait for timer");
+ assert_eq!(count, 3);
+ }
+}
diff --git a/common/sys_util/src/vsock.rs b/common/sys_util/src/vsock.rs
new file mode 100644
index 000000000..692652258
--- /dev/null
+++ b/common/sys_util/src/vsock.rs
@@ -0,0 +1,497 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// Support for virtual sockets.
+use std::fmt;
+use std::{
+ io,
+ mem::{
+ size_of, {self},
+ },
+ num::ParseIntError,
+ os::{
+ raw::{c_uchar, c_uint, c_ushort},
+ unix::io::{AsRawFd, IntoRawFd, RawFd},
+ },
+ result,
+ str::FromStr,
+};
+
+use libc::{
+ c_void, sa_family_t, size_t, sockaddr, socklen_t, F_GETFL, F_SETFL, O_NONBLOCK, VMADDR_CID_ANY,
+ VMADDR_CID_HOST, VMADDR_CID_HYPERVISOR, {self},
+};
+use thiserror::Error;
+
+// The domain for vsock sockets.
+const AF_VSOCK: sa_family_t = 40;
+
+// Vsock loopback address.
+const VMADDR_CID_LOCAL: c_uint = 1;
+
+/// Vsock equivalent of binding on port 0. Binds to a random port.
+pub const VMADDR_PORT_ANY: c_uint = c_uint::max_value();
+
+// The number of bytes of padding to be added to the sockaddr_vm struct. Taken directly
+// from linux/vm_sockets.h.
+const PADDING: usize = size_of::<sockaddr>()
+ - size_of::<sa_family_t>()
+ - size_of::<c_ushort>()
+ - (2 * size_of::<c_uint>());
+
+#[repr(C)]
+#[derive(Default)]
+struct sockaddr_vm {
+ svm_family: sa_family_t,
+ svm_reserved1: c_ushort,
+ svm_port: c_uint,
+ svm_cid: c_uint,
+ svm_zero: [c_uchar; PADDING],
+}
+
+#[derive(Error, Debug)]
+#[error("failed to parse vsock address")]
+pub struct AddrParseError;
+
+/// The vsock equivalent of an IP address.
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
+pub enum VsockCid {
+ /// Vsock equivalent of INADDR_ANY. Indicates the context id of the current endpoint.
+ Any,
+ /// An address that refers to the bare-metal machine that serves as the hypervisor.
+ Hypervisor,
+ /// The loopback address.
+ Local,
+ /// The parent machine. It may not be the hypervisor for nested VMs.
+ Host,
+ /// An assigned CID that serves as the address for VSOCK.
+ Cid(c_uint),
+}
+
+impl fmt::Display for VsockCid {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ match &self {
+ VsockCid::Any => write!(fmt, "Any"),
+ VsockCid::Hypervisor => write!(fmt, "Hypervisor"),
+ VsockCid::Local => write!(fmt, "Local"),
+ VsockCid::Host => write!(fmt, "Host"),
+ VsockCid::Cid(c) => write!(fmt, "'{}'", c),
+ }
+ }
+}
+
+impl From<c_uint> for VsockCid {
+ fn from(c: c_uint) -> Self {
+ match c {
+ VMADDR_CID_ANY => VsockCid::Any,
+ VMADDR_CID_HYPERVISOR => VsockCid::Hypervisor,
+ VMADDR_CID_LOCAL => VsockCid::Local,
+ VMADDR_CID_HOST => VsockCid::Host,
+ _ => VsockCid::Cid(c),
+ }
+ }
+}
+
+impl FromStr for VsockCid {
+ type Err = ParseIntError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ let c: c_uint = s.parse()?;
+ Ok(c.into())
+ }
+}
+
+impl From<VsockCid> for c_uint {
+ fn from(cid: VsockCid) -> c_uint {
+ match cid {
+ VsockCid::Any => VMADDR_CID_ANY,
+ VsockCid::Hypervisor => VMADDR_CID_HYPERVISOR,
+ VsockCid::Local => VMADDR_CID_LOCAL,
+ VsockCid::Host => VMADDR_CID_HOST,
+ VsockCid::Cid(c) => c,
+ }
+ }
+}
+
+/// An address associated with a virtual socket.
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
+pub struct SocketAddr {
+ pub cid: VsockCid,
+ pub port: c_uint,
+}
+
+pub trait ToSocketAddr {
+ fn to_socket_addr(&self) -> result::Result<SocketAddr, AddrParseError>;
+}
+
+impl ToSocketAddr for SocketAddr {
+ fn to_socket_addr(&self) -> result::Result<SocketAddr, AddrParseError> {
+ Ok(*self)
+ }
+}
+
+impl ToSocketAddr for str {
+ fn to_socket_addr(&self) -> result::Result<SocketAddr, AddrParseError> {
+ self.parse()
+ }
+}
+
+impl ToSocketAddr for (VsockCid, c_uint) {
+ fn to_socket_addr(&self) -> result::Result<SocketAddr, AddrParseError> {
+ let (cid, port) = *self;
+ Ok(SocketAddr { cid, port })
+ }
+}
+
+impl<'a, T: ToSocketAddr + ?Sized> ToSocketAddr for &'a T {
+ fn to_socket_addr(&self) -> result::Result<SocketAddr, AddrParseError> {
+ (**self).to_socket_addr()
+ }
+}
+
+impl FromStr for SocketAddr {
+ type Err = AddrParseError;
+
+ /// Parse a vsock SocketAddr from a string. vsock socket addresses are of the form
+ /// "vsock:cid:port".
+ fn from_str(s: &str) -> Result<SocketAddr, AddrParseError> {
+ let components: Vec<&str> = s.split(':').collect();
+ if components.len() != 3 || components[0] != "vsock" {
+ return Err(AddrParseError);
+ }
+
+ Ok(SocketAddr {
+ cid: components[1].parse().map_err(|_| AddrParseError)?,
+ port: components[2].parse().map_err(|_| AddrParseError)?,
+ })
+ }
+}
+
+impl fmt::Display for SocketAddr {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ write!(fmt, "{}:{}", self.cid, self.port)
+ }
+}
+
+/// Sets `fd` to be blocking or nonblocking. `fd` must be a valid fd of a type that accepts the
+/// `O_NONBLOCK` flag. This includes regular files, pipes, and sockets.
+unsafe fn set_nonblocking(fd: RawFd, nonblocking: bool) -> io::Result<()> {
+ let flags = libc::fcntl(fd, F_GETFL, 0);
+ if flags < 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ let flags = if nonblocking {
+ flags | O_NONBLOCK
+ } else {
+ flags & !O_NONBLOCK
+ };
+
+ let ret = libc::fcntl(fd, F_SETFL, flags);
+ if ret < 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ Ok(())
+}
+
+/// A virtual socket.
+///
+/// Do not use this class unless you need to change socket options or query the
+/// state of the socket prior to calling listen or connect. Instead use either VsockStream or
+/// VsockListener.
+#[derive(Debug)]
+pub struct VsockSocket {
+ fd: RawFd,
+}
+
+impl VsockSocket {
+ pub fn new() -> io::Result<Self> {
+ let fd = unsafe { libc::socket(libc::AF_VSOCK, libc::SOCK_STREAM | libc::SOCK_CLOEXEC, 0) };
+ if fd < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(VsockSocket { fd })
+ }
+ }
+
+ pub fn bind<A: ToSocketAddr>(&mut self, addr: A) -> io::Result<()> {
+ let sockaddr = addr
+ .to_socket_addr()
+ .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
+
+ // The compiler should optimize this out since these are both compile-time constants.
+ assert_eq!(size_of::<sockaddr_vm>(), size_of::<sockaddr>());
+
+ let svm = sockaddr_vm {
+ svm_family: AF_VSOCK,
+ svm_cid: sockaddr.cid.into(),
+ svm_port: sockaddr.port,
+ ..Default::default()
+ };
+
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe {
+ libc::bind(
+ self.fd,
+ &svm as *const sockaddr_vm as *const sockaddr,
+ size_of::<sockaddr_vm>() as socklen_t,
+ )
+ };
+ if ret < 0 {
+ let bind_err = io::Error::last_os_error();
+ Err(bind_err)
+ } else {
+ Ok(())
+ }
+ }
+
+ pub fn connect<A: ToSocketAddr>(self, addr: A) -> io::Result<VsockStream> {
+ let sockaddr = addr
+ .to_socket_addr()
+ .map_err(|_| io::Error::from_raw_os_error(libc::EINVAL))?;
+
+ let svm = sockaddr_vm {
+ svm_family: AF_VSOCK,
+ svm_cid: sockaddr.cid.into(),
+ svm_port: sockaddr.port,
+ ..Default::default()
+ };
+
+ // Safe because this just connects a vsock socket, and the return value is checked.
+ let ret = unsafe {
+ libc::connect(
+ self.fd,
+ &svm as *const sockaddr_vm as *const sockaddr,
+ size_of::<sockaddr_vm>() as socklen_t,
+ )
+ };
+ if ret < 0 {
+ let connect_err = io::Error::last_os_error();
+ Err(connect_err)
+ } else {
+ Ok(VsockStream { sock: self })
+ }
+ }
+
+ pub fn listen(self) -> io::Result<VsockListener> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe { libc::listen(self.fd, 1) };
+ if ret < 0 {
+ let listen_err = io::Error::last_os_error();
+ return Err(listen_err);
+ }
+ Ok(VsockListener { sock: self })
+ }
+
+ /// Returns the port that this socket is bound to. This can only succeed after bind is called.
+ pub fn local_port(&self) -> io::Result<u32> {
+ let mut svm: sockaddr_vm = Default::default();
+
+ // Safe because we give a valid pointer for addrlen and check the length.
+ let mut addrlen = size_of::<sockaddr_vm>() as socklen_t;
+ let ret = unsafe {
+ // Get the socket address that was actually bound.
+ libc::getsockname(
+ self.fd,
+ &mut svm as *mut sockaddr_vm as *mut sockaddr,
+ &mut addrlen as *mut socklen_t,
+ )
+ };
+ if ret < 0 {
+ let getsockname_err = io::Error::last_os_error();
+ Err(getsockname_err)
+ } else {
+ // If this doesn't match, it's not safe to get the port out of the sockaddr.
+ assert_eq!(addrlen as usize, size_of::<sockaddr_vm>());
+
+ Ok(svm.svm_port)
+ }
+ }
+
+ pub fn try_clone(&self) -> io::Result<Self> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let dup_fd = unsafe { libc::fcntl(self.fd, libc::F_DUPFD_CLOEXEC, 0) };
+ if dup_fd < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(Self { fd: dup_fd })
+ }
+ }
+
+ pub fn set_nonblocking(&mut self, nonblocking: bool) -> io::Result<()> {
+ // Safe because the fd is valid and owned by this stream.
+ unsafe { set_nonblocking(self.fd, nonblocking) }
+ }
+}
+
+impl IntoRawFd for VsockSocket {
+ fn into_raw_fd(self) -> RawFd {
+ let fd = self.fd;
+ mem::forget(self);
+ fd
+ }
+}
+
+impl AsRawFd for VsockSocket {
+ fn as_raw_fd(&self) -> RawFd {
+ self.fd
+ }
+}
+
+impl Drop for VsockSocket {
+ fn drop(&mut self) {
+ // Safe because this doesn't modify any memory and we are the only
+ // owner of the file descriptor.
+ unsafe { libc::close(self.fd) };
+ }
+}
+
+/// A virtual stream socket.
+#[derive(Debug)]
+pub struct VsockStream {
+ sock: VsockSocket,
+}
+
+impl VsockStream {
+ pub fn connect<A: ToSocketAddr>(addr: A) -> io::Result<VsockStream> {
+ let sock = VsockSocket::new()?;
+ sock.connect(addr)
+ }
+
+ /// Returns the port that this stream is bound to.
+ pub fn local_port(&self) -> io::Result<u32> {
+ self.sock.local_port()
+ }
+
+ pub fn try_clone(&self) -> io::Result<VsockStream> {
+ self.sock.try_clone().map(|f| VsockStream { sock: f })
+ }
+
+ pub fn set_nonblocking(&mut self, nonblocking: bool) -> io::Result<()> {
+ self.sock.set_nonblocking(nonblocking)
+ }
+}
+
+impl io::Read for VsockStream {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ // Safe because this will only modify the contents of |buf| and we check the return value.
+ let ret = unsafe {
+ libc::read(
+ self.sock.as_raw_fd(),
+ buf as *mut [u8] as *mut c_void,
+ buf.len() as size_t,
+ )
+ };
+ if ret < 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ Ok(ret as usize)
+ }
+}
+
+impl io::Write for VsockStream {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe {
+ libc::write(
+ self.sock.as_raw_fd(),
+ buf as *const [u8] as *const c_void,
+ buf.len() as size_t,
+ )
+ };
+ if ret < 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ Ok(ret as usize)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ // No buffered data so nothing to do.
+ Ok(())
+ }
+}
+
+impl AsRawFd for VsockStream {
+ fn as_raw_fd(&self) -> RawFd {
+ self.sock.as_raw_fd()
+ }
+}
+
+impl IntoRawFd for VsockStream {
+ fn into_raw_fd(self) -> RawFd {
+ self.sock.into_raw_fd()
+ }
+}
+
+/// Represents a virtual socket server.
+#[derive(Debug)]
+pub struct VsockListener {
+ sock: VsockSocket,
+}
+
+impl VsockListener {
+ /// Creates a new `VsockListener` bound to the specified port on the current virtual socket
+ /// endpoint.
+ pub fn bind<A: ToSocketAddr>(addr: A) -> io::Result<VsockListener> {
+ let mut sock = VsockSocket::new()?;
+ sock.bind(addr)?;
+ sock.listen()
+ }
+
+ /// Returns the port that this listener is bound to.
+ pub fn local_port(&self) -> io::Result<u32> {
+ self.sock.local_port()
+ }
+
+ /// Accepts a new incoming connection on this listener. Blocks the calling thread until a
+ /// new connection is established. When established, returns the corresponding `VsockStream`
+ /// and the remote peer's address.
+ pub fn accept(&self) -> io::Result<(VsockStream, SocketAddr)> {
+ let mut svm: sockaddr_vm = Default::default();
+
+ // Safe because this will only modify |svm| and we check the return value.
+ let mut socklen: socklen_t = size_of::<sockaddr_vm>() as socklen_t;
+ let fd = unsafe {
+ libc::accept4(
+ self.sock.as_raw_fd(),
+ &mut svm as *mut sockaddr_vm as *mut sockaddr,
+ &mut socklen as *mut socklen_t,
+ libc::SOCK_CLOEXEC,
+ )
+ };
+ if fd < 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ if svm.svm_family != AF_VSOCK {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ format!("unexpected address family: {}", svm.svm_family),
+ ));
+ }
+
+ Ok((
+ VsockStream {
+ sock: VsockSocket { fd },
+ },
+ SocketAddr {
+ cid: svm.svm_cid.into(),
+ port: svm.svm_port,
+ },
+ ))
+ }
+
+ pub fn set_nonblocking(&mut self, nonblocking: bool) -> io::Result<()> {
+ self.sock.set_nonblocking(nonblocking)
+ }
+}
+
+impl AsRawFd for VsockListener {
+ fn as_raw_fd(&self) -> RawFd {
+ self.sock.as_raw_fd()
+ }
+}
diff --git a/common/sys_util/src/write_zeroes.rs b/common/sys_util/src/write_zeroes.rs
new file mode 100644
index 000000000..15c92a23f
--- /dev/null
+++ b/common/sys_util/src/write_zeroes.rs
@@ -0,0 +1,216 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ cmp::min,
+ fs::File,
+ io::{
+ Error, ErrorKind, Seek, SeekFrom, {self},
+ },
+ os::unix::fs::FileExt,
+};
+
+use super::{fallocate, FallocateMode};
+
+/// A trait for deallocating space in a file.
+pub trait PunchHole {
+ /// Replace a range of bytes with a hole.
+ fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()>;
+}
+
+impl PunchHole for File {
+ fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()> {
+ fallocate(self, FallocateMode::PunchHole, true, offset, length as u64)
+ .map_err(|e| io::Error::from_raw_os_error(e.errno()))
+ }
+}
+
+/// A trait for writing zeroes to a stream.
+pub trait WriteZeroes {
+ /// Write up to `length` bytes of zeroes to the stream, returning how many bytes were written.
+ fn write_zeroes(&mut self, length: usize) -> io::Result<usize>;
+
+ /// Write zeroes to the stream until `length` bytes have been written.
+ ///
+ /// This method will continuously call `write_zeroes` until the requested
+ /// `length` is satisfied or an error is encountered.
+ fn write_zeroes_all(&mut self, mut length: usize) -> io::Result<()> {
+ while length > 0 {
+ match self.write_zeroes(length) {
+ Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
+ Ok(bytes_written) => {
+ length = length
+ .checked_sub(bytes_written)
+ .ok_or_else(|| Error::from(ErrorKind::Other))?
+ }
+ Err(e) => {
+ if e.kind() != ErrorKind::Interrupted {
+ return Err(e);
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+/// A trait for writing zeroes to an arbitrary position in a file.
+pub trait WriteZeroesAt {
+ /// Write up to `length` bytes of zeroes starting at `offset`, returning how many bytes were
+ /// written.
+ fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize>;
+
+ /// Write zeroes starting at `offset` until `length` bytes have been written.
+ ///
+ /// This method will continuously call `write_zeroes_at` until the requested
+ /// `length` is satisfied or an error is encountered.
+ fn write_zeroes_all_at(&mut self, mut offset: u64, mut length: usize) -> io::Result<()> {
+ while length > 0 {
+ match self.write_zeroes_at(offset, length) {
+ Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
+ Ok(bytes_written) => {
+ length = length
+ .checked_sub(bytes_written)
+ .ok_or_else(|| Error::from(ErrorKind::Other))?;
+ offset = offset
+ .checked_add(bytes_written as u64)
+ .ok_or_else(|| Error::from(ErrorKind::Other))?;
+ }
+ Err(e) => {
+ if e.kind() != ErrorKind::Interrupted {
+ return Err(e);
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+impl WriteZeroesAt for File {
+ fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize> {
+ // Try to use fallocate() first.
+ if fallocate(self, FallocateMode::ZeroRange, true, offset, length as u64).is_ok() {
+ return Ok(length);
+ }
+
+ // fall back to write()
+ // fallocate() failed; fall back to writing a buffer of zeroes
+ // until we have written up to length.
+ let buf_size = min(length, 0x10000);
+ let buf = vec![0u8; buf_size];
+ let mut nwritten: usize = 0;
+ while nwritten < length {
+ let remaining = length - nwritten;
+ let write_size = min(remaining, buf_size);
+ nwritten += self.write_at(&buf[0..write_size], offset + nwritten as u64)?;
+ }
+ Ok(length)
+ }
+}
+
+impl<T: WriteZeroesAt + Seek> WriteZeroes for T {
+ fn write_zeroes(&mut self, length: usize) -> io::Result<usize> {
+ let offset = self.seek(SeekFrom::Current(0))?;
+ let nwritten = self.write_zeroes_at(offset, length)?;
+ // Advance the seek cursor as if we had done a real write().
+ self.seek(SeekFrom::Current(nwritten as i64))?;
+ Ok(length)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::{Read, Seek, SeekFrom, Write};
+ use tempfile::tempfile;
+
+ #[test]
+ fn simple_test() {
+ let mut f = tempfile().unwrap();
+ f.set_len(16384).unwrap();
+
+ // Write buffer of non-zero bytes to offset 1234
+ let orig_data = [0x55u8; 5678];
+ f.seek(SeekFrom::Start(1234)).unwrap();
+ f.write_all(&orig_data).unwrap();
+
+ // Read back the data plus some overlap on each side
+ let mut readback = [0u8; 16384];
+ f.seek(SeekFrom::Start(0)).unwrap();
+ f.read_exact(&mut readback).unwrap();
+ // Bytes before the write should still be 0
+ for read in &readback[0..1234] {
+ assert_eq!(*read, 0);
+ }
+ // Bytes that were just written should be 0x55
+ for read in &readback[1234..(1234 + 5678)] {
+ assert_eq!(*read, 0x55);
+ }
+ // Bytes after the written area should still be 0
+ for read in &readback[(1234 + 5678)..] {
+ assert_eq!(*read, 0);
+ }
+
+ // Overwrite some of the data with zeroes
+ f.seek(SeekFrom::Start(2345)).unwrap();
+ f.write_zeroes_all(4321).expect("write_zeroes failed");
+ // Verify seek position after write_zeroes_all()
+ assert_eq!(f.seek(SeekFrom::Current(0)).unwrap(), 2345 + 4321);
+
+ // Read back the data and verify that it is now zero
+ f.seek(SeekFrom::Start(0)).unwrap();
+ f.read_exact(&mut readback).unwrap();
+ // Bytes before the write should still be 0
+ for read in &readback[0..1234] {
+ assert_eq!(*read, 0);
+ }
+ // Original data should still exist before the write_zeroes region
+ for read in &readback[1234..2345] {
+ assert_eq!(*read, 0x55);
+ }
+ // The write_zeroes region should now be zero
+ for read in &readback[2345..(2345 + 4321)] {
+ assert_eq!(*read, 0);
+ }
+ // Original data should still exist after the write_zeroes region
+ for read in &readback[(2345 + 4321)..(1234 + 5678)] {
+ assert_eq!(*read, 0x55);
+ }
+ // The rest of the file should still be 0
+ for read in &readback[(1234 + 5678)..] {
+ assert_eq!(*read, 0);
+ }
+ }
+
+ #[test]
+ fn large_write_zeroes() {
+ let mut f = tempfile().unwrap();
+ f.set_len(16384).unwrap();
+
+ // Write buffer of non-zero bytes
+ let orig_data = [0x55u8; 0x20000];
+ f.seek(SeekFrom::Start(0)).unwrap();
+ f.write_all(&orig_data).unwrap();
+
+ // Overwrite some of the data with zeroes
+ f.seek(SeekFrom::Start(0)).unwrap();
+ f.write_zeroes_all(0x10001).expect("write_zeroes failed");
+ // Verify seek position after write_zeroes_all()
+ assert_eq!(f.seek(SeekFrom::Current(0)).unwrap(), 0x10001);
+
+ // Read back the data and verify that it is now zero
+ let mut readback = [0u8; 0x20000];
+ f.seek(SeekFrom::Start(0)).unwrap();
+ f.read_exact(&mut readback).unwrap();
+ // The write_zeroes region should now be zero
+ for read in &readback[0..0x10001] {
+ assert_eq!(*read, 0);
+ }
+ // Original data should still exist after the write_zeroes region
+ for read in &readback[0x10001..0x20000] {
+ assert_eq!(*read, 0x55);
+ }
+ }
+}
diff --git a/syscall_defines/Android.bp b/common/sys_util_core/Android.bp
index f66fe441e..8ea55846e 100644
--- a/syscall_defines/Android.bp
+++ b/common/sys_util_core/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace --no-subdir.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -11,33 +11,40 @@ package {
}
rust_library {
- name: "libsyscall_defines",
+ name: "libsys_util_core",
defaults: ["crosvm_defaults"],
host_supported: true,
- crate_name: "syscall_defines",
+ crate_name: "sys_util_core",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
+ rustlibs: [
+ "liblibc",
+ "libserde",
+ "libthiserror",
+ ],
+ proc_macros: ["libremain"],
}
-rust_defaults {
- name: "syscall_defines_defaults",
+rust_test {
+ name: "sys_util_core_test_src_lib",
defaults: ["crosvm_defaults"],
- crate_name: "syscall_defines",
+ host_supported: true,
+ crate_name: "sys_util_core",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
-}
-
-rust_test_host {
- name: "syscall_defines_host_test_src_lib",
- defaults: ["syscall_defines_defaults"],
test_options: {
unit_test: true,
},
-}
-
-rust_test {
- name: "syscall_defines_device_test_src_lib",
- defaults: ["syscall_defines_defaults"],
+ edition: "2021",
+ rustlibs: [
+ "liblibc",
+ "libserde",
+ "libthiserror",
+ ],
+ proc_macros: ["libremain"],
}
diff --git a/common/sys_util_core/Cargo.toml b/common/sys_util_core/Cargo.toml
new file mode 100644
index 000000000..2546a51fc
--- /dev/null
+++ b/common/sys_util_core/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "sys_util_core"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2021"
+include = ["src/**/*", "Cargo.toml"]
+
+[dependencies]
+libc = "*"
+remain = "0.2"
+serde = { version = "1", features = [ "derive" ] }
+thiserror = "*"
+
+[workspace]
+members = [
+ "poll_token_derive",
+]
diff --git a/common/sys_util_core/poll_token_derive/Android.bp b/common/sys_util_core/poll_token_derive/Android.bp
new file mode 100644
index 000000000..68376d550
--- /dev/null
+++ b/common/sys_util_core/poll_token_derive/Android.bp
@@ -0,0 +1,26 @@
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_crosvm_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-BSD
+ default_applicable_licenses: ["external_crosvm_license"],
+}
+
+rust_proc_macro {
+ name: "libpoll_token_derive",
+ defaults: ["crosvm_defaults"],
+ crate_name: "poll_token_derive",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
+ srcs: ["poll_token_derive.rs"],
+ edition: "2021",
+ rustlibs: [
+ "libproc_macro2",
+ "libquote",
+ "libsyn",
+ ],
+}
diff --git a/sys_util/poll_token_derive/Cargo.toml b/common/sys_util_core/poll_token_derive/Cargo.toml
index 45290bed0..e24705e21 100644
--- a/sys_util/poll_token_derive/Cargo.toml
+++ b/common/sys_util_core/poll_token_derive/Cargo.toml
@@ -2,7 +2,7 @@
name = "poll_token_derive"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
include = ["*.rs", "Cargo.toml"]
[lib]
diff --git a/common/sys_util_core/poll_token_derive/cargo2android.json b/common/sys_util_core/poll_token_derive/cargo2android.json
new file mode 100644
index 000000000..9a6eee39c
--- /dev/null
+++ b/common/sys_util_core/poll_token_derive/cargo2android.json
@@ -0,0 +1,7 @@
+{
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "run": true,
+ "tests": false
+} \ No newline at end of file
diff --git a/common/sys_util_core/poll_token_derive/poll_token_derive.rs b/common/sys_util_core/poll_token_derive/poll_token_derive.rs
new file mode 100644
index 000000000..5b6eb3b17
--- /dev/null
+++ b/common/sys_util_core/poll_token_derive/poll_token_derive.rs
@@ -0,0 +1,172 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#![recursion_limit = "128"]
+
+extern crate proc_macro;
+
+use proc_macro2::{Ident, TokenStream};
+use quote::quote;
+use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, Index, Member, Variant};
+
+#[cfg(test)]
+mod tests;
+
+// The method for packing an enum into a u64 is as follows:
+// 1) Reserve the lowest "ceil(log_2(x))" bits where x is the number of enum variants.
+// 2) Store the enum variant's index (0-based index based on order in the enum definition) in
+// reserved bits.
+// 3) If there is data in the enum variant, store the data in remaining bits.
+// The method for unpacking is as follows
+// 1) Mask the raw token to just the reserved bits
+// 2) Match the reserved bits to the enum variant token.
+// 3) If the indicated enum variant had data, extract it from the unreserved bits.
+
+// Calculates the number of bits needed to store the variant index. Essentially the log base 2
+// of the number of variants, rounded up.
+fn variant_bits(variants: &[Variant]) -> u32 {
+ if variants.is_empty() {
+ // The degenerate case of no variants.
+ 0
+ } else {
+ variants.len().next_power_of_two().trailing_zeros()
+ }
+}
+
+// Name of the field if it has one, otherwise 0 assuming this is the zeroth
+// field of a tuple variant.
+fn field_member(field: &Field) -> Member {
+ match &field.ident {
+ Some(name) => Member::Named(name.clone()),
+ None => Member::Unnamed(Index::from(0)),
+ }
+}
+
+// Generates the function body for `as_raw_token`.
+fn generate_as_raw_token(enum_name: &Ident, variants: &[Variant]) -> TokenStream {
+ let variant_bits = variant_bits(variants);
+
+ // Each iteration corresponds to one variant's match arm.
+ let cases = variants.iter().enumerate().map(|(index, variant)| {
+ let variant_name = &variant.ident;
+ let index = index as u64;
+
+ // The capture string is for everything between the variant identifier and the `=>` in
+ // the match arm: the variant's data capture.
+ let capture = variant.fields.iter().next().map(|field| {
+ let member = field_member(field);
+ quote!({ #member: data })
+ });
+
+ // The modifier string ORs the variant index with extra bits from the variant data
+ // field.
+ let modifier = match variant.fields {
+ Fields::Named(_) | Fields::Unnamed(_) => Some(quote! {
+ | ((data as u64) << #variant_bits)
+ }),
+ Fields::Unit => None,
+ };
+
+ // Assembly of the match arm.
+ quote! {
+ #enum_name::#variant_name #capture => #index #modifier
+ }
+ });
+
+ quote! {
+ match *self {
+ #(
+ #cases,
+ )*
+ }
+ }
+}
+
+// Generates the function body for `from_raw_token`.
+fn generate_from_raw_token(enum_name: &Ident, variants: &[Variant]) -> TokenStream {
+ let variant_bits = variant_bits(variants);
+ let variant_mask = ((1 << variant_bits) - 1) as u64;
+
+ // Each iteration corresponds to one variant's match arm.
+ let cases = variants.iter().enumerate().map(|(index, variant)| {
+ let variant_name = &variant.ident;
+ let index = index as u64;
+
+ // The data string is for extracting the enum variant's data bits out of the raw token
+ // data, which includes both variant index and data bits.
+ let data = variant.fields.iter().next().map(|field| {
+ let member = field_member(field);
+ let ty = &field.ty;
+ quote!({ #member: (data >> #variant_bits) as #ty })
+ });
+
+ // Assembly of the match arm.
+ quote! {
+ #index => #enum_name::#variant_name #data
+ }
+ });
+
+ quote! {
+ // The match expression only matches the bits for the variant index.
+ match data & #variant_mask {
+ #(
+ #cases,
+ )*
+ _ => unreachable!(),
+ }
+ }
+}
+
+// The proc_macro::TokenStream type can only be constructed from within a
+// procedural macro, meaning that unit tests are not able to invoke `fn
+// poll_token` below as an ordinary Rust function. We factor out the logic into
+// a signature that deals with Syn and proc-macro2 types only which are not
+// restricted to a procedural macro invocation.
+fn poll_token_inner(input: DeriveInput) -> TokenStream {
+ let variants: Vec<Variant> = match input.data {
+ Data::Enum(data) => data.variants.into_iter().collect(),
+ Data::Struct(_) | Data::Union(_) => panic!("input must be an enum"),
+ };
+
+ for variant in &variants {
+ assert!(variant.fields.iter().count() <= 1);
+ }
+
+ // Given our basic model of a user given enum that is suitable as a token, we generate the
+ // implementation. The implementation is NOT always well formed, such as when a variant's data
+ // type is not bit shiftable or castable to u64, but we let Rust generate such errors as it
+ // would be difficult to detect every kind of error. Importantly, every implementation that we
+ // generate here and goes on to compile succesfully is sound.
+
+ let enum_name = input.ident;
+ let as_raw_token = generate_as_raw_token(&enum_name, &variants);
+ let from_raw_token = generate_from_raw_token(&enum_name, &variants);
+
+ quote! {
+ impl PollToken for #enum_name {
+ fn as_raw_token(&self) -> u64 {
+ #as_raw_token
+ }
+
+ fn from_raw_token(data: u64) -> Self {
+ #from_raw_token
+ }
+ }
+ }
+}
+
+/// Implements the PollToken trait for a given `enum`.
+///
+/// There are limitations on what `enum`s this custom derive will work on:
+///
+/// * Each variant must be a unit variant (no data), or have a single (un)named data field.
+/// * If a variant has data, it must be a primitive type castable to and from a `u64`.
+/// * If a variant data has size greater than or equal to a `u64`, its most significant bits must be
+/// zero. The number of bits truncated is equal to the number of bits used to store the variant
+/// index plus the number of bits above 64.
+#[proc_macro_derive(PollToken)]
+pub fn poll_token(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ poll_token_inner(input).into()
+}
diff --git a/common/sys_util_core/poll_token_derive/tests.rs b/common/sys_util_core/poll_token_derive/tests.rs
new file mode 100644
index 000000000..935f91c9b
--- /dev/null
+++ b/common/sys_util_core/poll_token_derive/tests.rs
@@ -0,0 +1,65 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use quote::quote;
+use syn::{parse_quote, DeriveInput};
+
+#[test]
+fn test_variant_bits() {
+ let mut variants = vec![parse_quote!(A)];
+ assert_eq!(crate::variant_bits(&variants), 0);
+
+ variants.push(parse_quote!(B));
+ variants.push(parse_quote!(C));
+ assert_eq!(crate::variant_bits(&variants), 2);
+
+ for _ in 0..1021 {
+ variants.push(parse_quote!(Dynamic));
+ }
+ assert_eq!(crate::variant_bits(&variants), 10);
+
+ variants.push(parse_quote!(OneMore));
+ assert_eq!(crate::variant_bits(&variants), 11);
+}
+
+#[test]
+fn poll_token_e2e() {
+ let input: DeriveInput = parse_quote! {
+ enum Token {
+ A,
+ B,
+ C,
+ D(usize),
+ E { foobaz: u32 },
+ }
+ };
+
+ let actual = crate::poll_token_inner(input);
+ let expected = quote! {
+ impl PollToken for Token {
+ fn as_raw_token(&self) -> u64 {
+ match *self {
+ Token::A => 0u64,
+ Token::B => 1u64,
+ Token::C => 2u64,
+ Token::D { 0: data } => 3u64 | ((data as u64) << 3u32),
+ Token::E { foobaz: data } => 4u64 | ((data as u64) << 3u32),
+ }
+ }
+
+ fn from_raw_token(data: u64) -> Self {
+ match data & 7u64 {
+ 0u64 => Token::A,
+ 1u64 => Token::B,
+ 2u64 => Token::C,
+ 3u64 => Token::D { 0: (data >> 3u32) as usize },
+ 4u64 => Token::E { foobaz: (data >> 3u32) as u32 },
+ _ => unreachable!(),
+ }
+ }
+ }
+ };
+
+ assert_eq!(actual.to_string(), expected.to_string());
+}
diff --git a/common/sys_util_core/src/alloc.rs b/common/sys_util_core/src/alloc.rs
new file mode 100644
index 000000000..a21b23b37
--- /dev/null
+++ b/common/sys_util_core/src/alloc.rs
@@ -0,0 +1,215 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ alloc::{alloc, alloc_zeroed, dealloc, Layout},
+ cmp::min,
+};
+
+/// A contiguous memory allocation with a specified size and alignment, with a
+/// Drop impl to perform the deallocation.
+///
+/// Conceptually this is like a Box<[u8]> but for which we can select a minimum
+/// required alignment at the time of allocation.
+///
+/// # Example
+///
+/// ```
+/// use std::alloc::Layout;
+/// use std::mem;
+/// use sys_util_core::LayoutAllocation;
+///
+/// #[repr(C)]
+/// struct Header {
+/// q: usize,
+/// entries: [Entry; 0], // flexible array member
+/// }
+///
+/// #[repr(C)]
+/// struct Entry {
+/// e: usize,
+/// }
+///
+/// fn demo(num_entries: usize) {
+/// let size = mem::size_of::<Header>() + num_entries * mem::size_of::<Entry>();
+/// let layout = Layout::from_size_align(size, mem::align_of::<Header>()).unwrap();
+/// let mut allocation = LayoutAllocation::zeroed(layout);
+///
+/// // Safe to obtain an exclusive reference because there are no other
+/// // references to the allocation yet and all-zero is a valid bit pattern for
+/// // our header.
+/// let header = unsafe { allocation.as_mut::<Header>() };
+/// }
+/// ```
+pub struct LayoutAllocation {
+ ptr: *mut u8,
+ layout: Layout,
+}
+
+impl LayoutAllocation {
+ /// Allocates memory with the specified size and alignment. The content is
+ /// not initialized.
+ ///
+ /// Uninitialized data is not safe to read. Further, it is not safe to
+ /// obtain a reference to data potentially holding a bit pattern
+ /// incompatible with its type, for example an uninitialized bool or enum.
+ pub fn uninitialized(layout: Layout) -> Self {
+ let ptr = if layout.size() > 0 {
+ unsafe {
+ // Safe as long as we guarantee layout.size() > 0.
+ alloc(layout)
+ }
+ } else {
+ layout.align() as *mut u8
+ };
+ LayoutAllocation { ptr, layout }
+ }
+
+ /// Allocates memory with the specified size and alignment and initializes
+ /// the content to all zero-bytes.
+ ///
+ /// Note that zeroing the memory does not necessarily make it safe to obtain
+ /// a reference to the allocation. Depending on the intended type T,
+ /// all-zero may or may not be a legal bit pattern for that type. For
+ /// example obtaining a reference would immediately be undefined behavior if
+ /// one of the fields has type NonZeroUsize.
+ pub fn zeroed(layout: Layout) -> Self {
+ let ptr = if layout.size() > 0 {
+ unsafe {
+ // Safe as long as we guarantee layout.size() > 0.
+ alloc_zeroed(layout)
+ }
+ } else {
+ layout.align() as *mut u8
+ };
+ LayoutAllocation { ptr, layout }
+ }
+
+ /// Returns a raw pointer to the allocated data.
+ pub fn as_ptr<T>(&self) -> *mut T {
+ self.ptr as *mut T
+ }
+
+ /// Returns a reference to the `Layout` used to create this allocation.
+ pub fn layout(&self) -> &Layout {
+ &self.layout
+ }
+
+ /// Returns a shared reference to the allocated data.
+ ///
+ /// # Safety
+ ///
+ /// Caller is responsible for ensuring that the data behind this pointer has
+ /// been initialized as much as necessary and that there are no already
+ /// existing mutable references to any part of the data.
+ pub unsafe fn as_ref<T>(&self) -> &T {
+ &*self.as_ptr()
+ }
+
+ /// Returns an exclusive reference to the allocated data.
+ ///
+ /// # Safety
+ ///
+ /// Caller is responsible for ensuring that the data behind this pointer has
+ /// been initialized as much as necessary and that there are no already
+ /// existing references to any part of the data.
+ pub unsafe fn as_mut<T>(&mut self) -> &mut T {
+ &mut *self.as_ptr()
+ }
+
+ /// Returns a shared slice reference to the allocated data.
+ ///
+ /// # Arguments
+ ///
+ /// `num_elements` - Number of `T` elements to include in the slice.
+ /// The length of the slice will be capped to the allocation's size.
+ /// Caller must ensure that any sliced elements are initialized.
+ ///
+ /// # Safety
+ ///
+ /// Caller is responsible for ensuring that the data behind this pointer has
+ /// been initialized as much as necessary and that there are no already
+ /// existing mutable references to any part of the data.
+ pub unsafe fn as_slice<T>(&self, num_elements: usize) -> &[T] {
+ let len = min(num_elements, self.layout.size() / std::mem::size_of::<T>());
+ std::slice::from_raw_parts(self.as_ptr(), len)
+ }
+
+ /// Returns an exclusive slice reference to the allocated data.
+ ///
+ /// # Arguments
+ ///
+ /// `num_elements` - Number of `T` elements to include in the slice.
+ /// The length of the slice will be capped to the allocation's size.
+ /// Caller must ensure that any sliced elements are initialized.
+ ///
+ /// # Safety
+ ///
+ /// Caller is responsible for ensuring that the data behind this pointer has
+ /// been initialized as much as necessary and that there are no already
+ /// existing references to any part of the data.
+ pub unsafe fn as_mut_slice<T>(&mut self, num_elements: usize) -> &mut [T] {
+ let len = min(num_elements, self.layout.size() / std::mem::size_of::<T>());
+ std::slice::from_raw_parts_mut(self.as_ptr(), len)
+ }
+}
+
+impl Drop for LayoutAllocation {
+ fn drop(&mut self) {
+ if self.layout.size() > 0 {
+ unsafe {
+ // Safe as long as we guarantee layout.size() > 0.
+ dealloc(self.ptr, self.layout);
+ }
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::mem::{align_of, size_of};
+
+ #[test]
+ fn test_as_slice_u32() {
+ let layout = Layout::from_size_align(size_of::<u32>() * 15, align_of::<u32>()).unwrap();
+ let allocation = LayoutAllocation::zeroed(layout);
+ let slice: &[u32] = unsafe { allocation.as_slice(15) };
+ assert_eq!(slice.len(), 15);
+ assert_eq!(slice[0], 0);
+ assert_eq!(slice[14], 0);
+ }
+
+ #[test]
+ fn test_as_slice_u32_smaller_len() {
+ let layout = Layout::from_size_align(size_of::<u32>() * 15, align_of::<u32>()).unwrap();
+ let allocation = LayoutAllocation::zeroed(layout);
+
+ // Slice less than the allocation size, which will return a slice of only the requested length.
+ let slice: &[u32] = unsafe { allocation.as_slice(5) };
+ assert_eq!(slice.len(), 5);
+ }
+
+ #[test]
+ fn test_as_slice_u32_larger_len() {
+ let layout = Layout::from_size_align(size_of::<u32>() * 15, align_of::<u32>()).unwrap();
+ let allocation = LayoutAllocation::zeroed(layout);
+
+ // Slice more than the allocation size, which will clamp the returned slice len to the limit.
+ let slice: &[u32] = unsafe { allocation.as_slice(100) };
+ assert_eq!(slice.len(), 15);
+ }
+
+ #[test]
+ fn test_as_slice_u32_remainder() {
+ // Allocate a buffer that is not a multiple of u32 in size.
+ let layout = Layout::from_size_align(size_of::<u32>() * 15 + 2, align_of::<u32>()).unwrap();
+ let allocation = LayoutAllocation::zeroed(layout);
+
+ // Slice as many u32s as possible, which should return a slice that only includes the full
+ // u32s, not the trailing 2 bytes.
+ let slice: &[u32] = unsafe { allocation.as_slice(100) };
+ assert_eq!(slice.len(), 15);
+ }
+}
diff --git a/common/sys_util_core/src/errno.rs b/common/sys_util_core/src/errno.rs
new file mode 100644
index 000000000..231f787ca
--- /dev/null
+++ b/common/sys_util_core/src/errno.rs
@@ -0,0 +1,75 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ convert::{From, TryInto},
+ fmt::{
+ Display, {self},
+ },
+ io, result,
+};
+
+use serde::{Deserialize, Serialize};
+use thiserror::Error;
+
+/// A system error
+/// In Unix systems, retrieved from errno (man 3 errno), set by a libc
+/// function that returned an error.
+/// On Windows, retrieved from GetLastError, set by a Windows function
+/// that returned an error
+#[derive(Error, Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
+#[serde(transparent)]
+pub struct Error(i32);
+pub type Result<T> = result::Result<T, Error>;
+
+impl Error {
+ /// Constructs a new error with the given error number.
+ pub fn new<T: TryInto<i32>>(e: T) -> Error {
+ // A value outside the bounds of an i32 will never be a valid
+ // errno/GetLastError
+ Error(e.try_into().unwrap_or_default())
+ }
+
+ /// Constructs an Error from the most recent system error.
+ ///
+ /// The result of this only has any meaning just after a libc/Windows call that returned
+ /// a value indicating errno was set.
+ pub fn last() -> Error {
+ Error(io::Error::last_os_error().raw_os_error().unwrap())
+ }
+
+ /// Gets the errno for this error
+ pub fn errno(self) -> i32 {
+ self.0
+ }
+}
+
+impl From<io::Error> for Error {
+ fn from(e: io::Error) -> Self {
+ Error(e.raw_os_error().unwrap_or_default())
+ }
+}
+
+impl From<Error> for io::Error {
+ fn from(e: Error) -> io::Error {
+ io::Error::from_raw_os_error(e.0)
+ }
+}
+
+impl From<Error> for Box<dyn std::error::Error + Send> {
+ fn from(e: Error) -> Self {
+ Box::new(e)
+ }
+}
+
+impl Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ Into::<io::Error>::into(*self).fmt(f)
+ }
+}
+
+/// Returns the last errno as a Result that is always an error.
+pub fn errno_result<T>() -> Result<T> {
+ Err(Error::last())
+}
diff --git a/common/sys_util_core/src/external_mapping.rs b/common/sys_util_core/src/external_mapping.rs
new file mode 100644
index 000000000..22c98c753
--- /dev/null
+++ b/common/sys_util_core/src/external_mapping.rs
@@ -0,0 +1,133 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use remain::sorted;
+use thiserror::Error;
+
+#[sorted]
+#[derive(Error, Debug, Eq, PartialEq)]
+pub enum Error {
+ // For external mappings that have weird sizes
+ #[error("invalid size returned")]
+ InvalidSize,
+ // External library failed to map
+ #[error("library failed to map with {0}")]
+ LibraryError(i32),
+ // A null address is typically bad. mmap allows it, but not external libraries
+ #[error("null address returned")]
+ NullAddress,
+ // If external mapping is unsupported.
+ #[error("external mapping unsupported")]
+ Unsupported,
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+// Maps a external library resource given an id, returning address and size upon success
+pub type Map = fn(u32) -> Result<(u64, usize)>;
+// Unmaps the resource given a resource id.
+pub type Unmap = fn(u32);
+
+/// ExternalMapping wraps an external library mapping. This is useful in cases where where the
+/// device memory is not compatible with the mmap interface, such as Vulkan VkDeviceMemory in the
+/// non-exportable case or when exported as an opaque fd.
+#[derive(Debug, PartialEq)]
+pub struct ExternalMapping {
+ resource_id: u32,
+ ptr: u64,
+ size: usize,
+ unmap: Unmap,
+}
+
+unsafe impl Send for ExternalMapping {}
+unsafe impl Sync for ExternalMapping {}
+impl ExternalMapping {
+ /// Creates an ExternalMapping given a library-specific resource id and map/unmap functions.
+ ///
+ /// # Safety
+ ///
+ /// The map function must return a valid host memory region. In addition, callers of the
+ /// function must guarantee that the map and unmap functions are thread-safe, never return a
+ /// region overlapping already Rust referenced-data, and the backing store of the resource
+ /// doesn't disappear before the unmap function is called.
+ pub unsafe fn new(resource_id: u32, map: Map, unmap: Unmap) -> Result<ExternalMapping> {
+ let (ptr, size) = map(resource_id)?;
+
+ if (ptr as *mut u8).is_null() {
+ return Err(Error::NullAddress);
+ }
+ if size == 0 {
+ return Err(Error::InvalidSize);
+ }
+
+ Ok(ExternalMapping {
+ resource_id,
+ ptr,
+ size,
+ unmap,
+ })
+ }
+
+ /// used for passing this region to ioctls for setting guest memory.
+ pub fn as_ptr(&self) -> *mut u8 {
+ self.ptr as *mut u8
+ }
+
+ /// Returns the size of the memory region in bytes.
+ pub fn size(&self) -> usize {
+ self.size
+ }
+}
+
+impl Drop for ExternalMapping {
+ fn drop(&mut self) {
+ // This is safe because we own this memory range, and nobody else is holding a reference to
+ // it.
+ (self.unmap)(self.resource_id)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn check_valid_external_map() {
+ let map1: Map = |_resource_id| Ok((0xAAAABBBB, 500));
+ let map2: Map = |_resource_id| Ok((0xBBBBAAAA, 1000));
+ let unmap: Unmap = |_resource_id| {};
+ let external_map1 = unsafe { ExternalMapping::new(0, map1, unmap).unwrap() };
+ let external_map2 = unsafe { ExternalMapping::new(0, map2, unmap).unwrap() };
+
+ assert_eq!(external_map1.as_ptr(), 0xAAAABBBB as *mut u8);
+ assert_eq!(external_map1.size(), 500);
+ assert_eq!(external_map2.as_ptr(), 0xBBBBAAAA as *mut u8);
+ assert_eq!(external_map2.size(), 1000);
+ }
+
+ #[test]
+ fn check_invalid_external_map() {
+ let map1: Map = |_resource_id| Ok((0xAAAABBBB, 0));
+ let map2: Map = |_resource_id| Ok((0, 500));
+ let unmap: Unmap = |_resource_id| {};
+
+ assert_eq!(
+ unsafe { ExternalMapping::new(0, map1, unmap) },
+ Err(Error::InvalidSize)
+ );
+
+ assert_eq!(
+ unsafe { ExternalMapping::new(0, map2, unmap) },
+ Err(Error::NullAddress)
+ );
+ }
+
+ #[test]
+ #[should_panic]
+ fn check_external_map_drop() {
+ let map = |_resource_id| Ok((0xAAAABBBB, 500));
+ let unmap = |_resource_id| panic!();
+ let _external_map = unsafe { ExternalMapping::new(0, map, unmap) };
+ }
+}
diff --git a/common/sys_util_core/src/lib.rs b/common/sys_util_core/src/lib.rs
new file mode 100644
index 000000000..461e04ec2
--- /dev/null
+++ b/common/sys_util_core/src/lib.rs
@@ -0,0 +1,24 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Small system utility modules for usage by other higher level system
+//! utility modules like sys_util(on unix/linux) and win_sys_util(on windows).
+//!
+//! Crates other than sys_util and win_sys_util should not depend directly on
+//! sys_util_core.
+//!
+//! sys_util_core contains system utilities that are strictly platform/os
+//! independent. Platform dependent, conditionally compiled, code should
+//! not be added to sys_util_core.
+//!
+
+mod alloc;
+mod errno;
+mod external_mapping;
+mod scoped_event_macro;
+
+pub use alloc::LayoutAllocation;
+pub use errno::{errno_result, Error, Result};
+pub use external_mapping::{Error as ExternalMappingError, Result as ExternalMappingResult, *};
+pub use scoped_event_macro::*;
diff --git a/common/sys_util_core/src/scoped_event_macro.rs b/common/sys_util_core/src/scoped_event_macro.rs
new file mode 100644
index 000000000..f0f950ed1
--- /dev/null
+++ b/common/sys_util_core/src/scoped_event_macro.rs
@@ -0,0 +1,52 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#[macro_export]
+macro_rules! generate_scoped_event {
+ ($event:ident) => {
+ /// An `Event` wrapper which triggers when it goes out of scope.
+ ///
+ /// If the underlying `Event` fails to trigger during drop, a panic is triggered instead.
+ pub struct ScopedEvent($event);
+
+ impl ScopedEvent {
+ /// Creates a new `ScopedEvent` which triggers when it goes out of scope.
+ pub fn new() -> Result<ScopedEvent> {
+ Ok($event::new()?.into())
+ }
+ }
+
+ impl From<$event> for ScopedEvent {
+ fn from(e: $event) -> Self {
+ Self(e)
+ }
+ }
+
+ impl From<ScopedEvent> for $event {
+ fn from(scoped_event: ScopedEvent) -> Self {
+ // Rust doesn't allow moving out of types with a Drop implementation, so we have to
+ // use something that copies instead of moves. This is safe because we prevent the
+ // drop of `scoped_event` using `mem::forget`, so the underlying `Event` will not
+ // experience a double-drop.
+ let evt = unsafe { ptr::read(&scoped_event.0) };
+ mem::forget(scoped_event);
+ evt
+ }
+ }
+
+ impl Deref for ScopedEvent {
+ type Target = $event;
+
+ fn deref(&self) -> &$event {
+ &self.0
+ }
+ }
+
+ impl Drop for ScopedEvent {
+ fn drop(&mut self) {
+ self.write(1).expect("failed to trigger scoped event");
+ }
+ }
+ };
+}
diff --git a/cros_async/Android.bp b/cros_async/Android.bp
index d9ed6dd7d..3ea235161 100644
--- a/cros_async/Android.bp
+++ b/cros_async/Android.bp
@@ -1,4 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
package {
// See: http://go/android-license-faq
@@ -9,115 +10,76 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "cros_async_defaults",
+rust_test {
+ name: "cros_async_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "cros_async",
- // has rustc warnings
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.1",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: false,
+ },
+ edition: "2021",
rustlibs: [
+ "libanyhow",
"libasync_task",
+ "libaudio_streams",
+ "libbase_rust",
"libdata_model",
"libfutures",
- "libfutures_executor",
- "libfutures_util",
- "libintrusive_collections",
+ "libfutures_executor",
+ "libfutures_util",
+ "libintrusive_collections",
"libio_uring",
"liblibc",
+ "libonce_cell",
"libpin_utils",
+ "libserde",
"libslab",
"libsync_rust",
- "libsys_util",
- "libsyscall_defines",
"libtempfile",
"libthiserror",
- "libvm_memory",
],
proc_macros: [
"libasync_trait",
"libpaste",
+ "libremain",
],
}
-rust_test_host {
- name: "cros_async_host_test_src_lib",
- defaults: ["cros_async_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "cros_async_device_test_src_lib",
- defaults: ["cros_async_defaults"],
-}
-
rust_library {
name: "libcros_async",
defaults: ["crosvm_defaults"],
- // has rustc warnings
host_supported: true,
crate_name: "cros_async",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.1",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
+ "libanyhow",
"libasync_task",
+ "libaudio_streams",
+ "libbase_rust",
"libdata_model",
"libfutures",
- "libintrusive_collections", // added manually
+ "libintrusive_collections",
"libio_uring",
"liblibc",
+ "libonce_cell",
"libpin_utils",
+ "libserde",
"libslab",
"libsync_rust",
- "libsys_util",
- "libsyscall_defines",
"libthiserror",
],
proc_macros: [
"libasync_trait",
"libpaste",
+ "libremain",
],
}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../syscall_defines/src/lib.rs
-// ../tempfile/src/lib.rs
-// ../vm_memory/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.45
-// futures-0.3.13 "alloc,async-await,default,executor,futures-executor,std"
-// futures-channel-0.3.13 "alloc,futures-sink,sink,std"
-// futures-core-0.3.13 "alloc,std"
-// futures-executor-0.3.13 "std"
-// futures-io-0.3.13 "std"
-// futures-macro-0.3.13
-// futures-sink-0.3.13 "alloc,std"
-// futures-task-0.3.13 "alloc,std"
-// futures-util-0.3.13 "alloc,async-await,async-await-macro,channel,futures-channel,futures-io,futures-macro,futures-sink,io,memchr,proc-macro-hack,proc-macro-nested,sink,slab,std"
-// libc-0.2.87 "default,std"
-// memchr-2.3.4 "default,std"
-// paste-1.0.4
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro-hack-0.5.19
-// proc-macro-nested-0.1.7
-// proc-macro2-1.0.24 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// serde-1.0.123 "default,derive,serde_derive,std"
-// serde_derive-1.0.123 "default"
-// slab-0.4.2
-// syn-1.0.61 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/cros_async/Cargo.toml b/cros_async/Cargo.toml
index fcaf95bc7..b081b7489 100644
--- a/cros_async/Cargo.toml
+++ b/cros_async/Cargo.toml
@@ -1,22 +1,27 @@
[package]
name = "cros_async"
-version = "0.1.0"
+version = "0.1.1"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
async-trait = "0.1.36"
async-task = "4"
+data_model = { path = "../common/data_model" } # provided by ebuild
intrusive-collections = "0.9"
io_uring = { path = "../io_uring" } # provided by ebuild
libc = "*"
+once_cell = "1.7.2"
paste = "1.0"
pin-utils = "0.1.0-alpha.4"
+remain = "0.2"
slab = "0.4"
-sync = { path = "../sync" } # provided by ebuild
-sys_util = { path = "../sys_util" } # provided by ebuild
-data_model = { path = "../data_model" } # provided by ebuild
+sync = { path = "../common/sync" } # provided by ebuild
+base = { path = "../base" } # provided by ebuild
thiserror = "1.0.20"
+audio_streams = { path = "../common/audio_streams" } # provided by ebuild
+anyhow = "1.0"
+serde = "*"
[dependencies.futures]
version = "*"
@@ -27,6 +32,6 @@ features = ["alloc"]
futures = { version = "*", features = ["executor"] }
futures-executor = { version = "0.3", features = ["thread-pool"] }
futures-util = "0.3"
-tempfile = { path = "../tempfile" } # provided by ebuild
+tempfile = "3"
+
-[workspace] \ No newline at end of file
diff --git a/cros_async/DEPRECATED.md b/cros_async/DEPRECATED.md
new file mode 100644
index 000000000..77e18642d
--- /dev/null
+++ b/cros_async/DEPRECATED.md
@@ -0,0 +1,4 @@
+Use crosvm/cros_async instead.
+
+Code in this directory is not used by crosvm, it is only used in ChromeOS and will move to a
+separate ChromeOS repository soon.
diff --git a/cros_async/cargo2android.json b/cros_async/cargo2android.json
new file mode 100644
index 000000000..9a64b6c75
--- /dev/null
+++ b/cros_async/cargo2android.json
@@ -0,0 +1,8 @@
+{
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "no_presubmit": true,
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/cros_async/src/async_types.rs b/cros_async/src/async_types.rs
new file mode 100644
index 000000000..8054de121
--- /dev/null
+++ b/cros_async/src/async_types.rs
@@ -0,0 +1,71 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::{Executor, IntoAsync};
+use base::{AsRawDescriptor, RecvTube, SendTube, Tube, TubeResult};
+use serde::{de::DeserializeOwned, Serialize};
+use std::io;
+use std::os::unix::io::{AsRawFd, RawFd};
+
+#[cfg_attr(windows, path = "win/async_types.rs")]
+#[cfg_attr(not(windows), path = "unix/async_types.rs")]
+mod async_types;
+pub use async_types::*;
+
+/// Like `cros_async::IntoAsync`, except for use with crosvm's AsRawDescriptor
+/// trait object family.
+pub trait DescriptorIntoAsync: AsRawDescriptor {}
+
+/// To use an IO struct with cros_async, the type must be marked with
+/// DescriptorIntoAsync (to signify it is suitable for use with async
+/// operations), and then wrapped with this type.
+pub struct DescriptorAdapter<T: DescriptorIntoAsync>(pub T);
+impl<T> AsRawFd for DescriptorAdapter<T>
+where
+ T: DescriptorIntoAsync,
+{
+ fn as_raw_fd(&self) -> RawFd {
+ self.0.as_raw_descriptor()
+ }
+}
+impl<T> IntoAsync for DescriptorAdapter<T> where T: DescriptorIntoAsync {}
+
+impl IntoAsync for Tube {}
+impl IntoAsync for SendTube {}
+impl IntoAsync for RecvTube {}
+
+pub struct RecvTubeAsync(AsyncTube);
+#[allow(dead_code)]
+impl RecvTubeAsync {
+ pub fn new(tube: RecvTube, ex: &Executor) -> io::Result<Self> {
+ Ok(Self(AsyncTube::new(
+ ex,
+ #[allow(deprecated)]
+ tube.into_tube(),
+ )?))
+ }
+
+ /// TODO(b/145998747, b/184398671): this async approach needs to be refactored
+ /// upstream, but for now is implemented to work using simple blocking futures
+ /// (avoiding the unimplemented wait_readable).
+ pub async fn next<T: 'static + DeserializeOwned + Send>(&self) -> TubeResult<T> {
+ self.0.next().await
+ }
+}
+
+pub struct SendTubeAsync(AsyncTube);
+#[allow(dead_code)]
+impl SendTubeAsync {
+ pub fn new(tube: SendTube, ex: &Executor) -> io::Result<Self> {
+ Ok(Self(AsyncTube::new(
+ ex,
+ #[allow(deprecated)]
+ tube.into_tube(),
+ )?))
+ }
+
+ pub async fn send<T: 'static + Serialize + Send + Sync>(&self, msg: T) -> TubeResult<()> {
+ self.0.send(msg).await
+ }
+}
diff --git a/cros_async/src/audio_streams_async.rs b/cros_async/src/audio_streams_async.rs
new file mode 100644
index 000000000..735006658
--- /dev/null
+++ b/cros_async/src/audio_streams_async.rs
@@ -0,0 +1,68 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Implements the interface required by `audio_streams` using the cros_async Executor.
+//!
+//! It implements the `AudioStreamsExecutor` trait for `Executor`, so it can be passed into
+//! the audio_streams API.
+#[cfg(unix)]
+use std::os::unix::net::UnixStream;
+
+use std::{io::Result, time::Duration};
+
+use super::{AsyncWrapper, IntoAsync, IoSourceExt, TimerAsync};
+use async_trait::async_trait;
+use audio_streams::async_api::{
+ AsyncStream, AudioStreamsExecutor, ReadAsync, ReadWriteAsync, WriteAsync,
+};
+
+/// A wrapper around IoSourceExt that is compatible with the audio_streams traits.
+pub struct IoSourceWrapper<T: IntoAsync + Send> {
+ source: Box<dyn IoSourceExt<T> + Send>,
+}
+
+#[async_trait(?Send)]
+impl<T: IntoAsync + Send> ReadAsync for IoSourceWrapper<T> {
+ async fn read_to_vec<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ vec: Vec<u8>,
+ ) -> Result<(usize, Vec<u8>)> {
+ self.source
+ .read_to_vec(file_offset, vec)
+ .await
+ .map_err(Into::into)
+ }
+}
+
+#[async_trait(?Send)]
+impl<T: IntoAsync + Send> WriteAsync for IoSourceWrapper<T> {
+ async fn write_from_vec<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ vec: Vec<u8>,
+ ) -> Result<(usize, Vec<u8>)> {
+ self.source
+ .write_from_vec(file_offset, vec)
+ .await
+ .map_err(Into::into)
+ }
+}
+
+#[async_trait(?Send)]
+impl<T: IntoAsync + Send> ReadWriteAsync for IoSourceWrapper<T> {}
+
+#[async_trait(?Send)]
+impl AudioStreamsExecutor for super::Executor {
+ #[cfg(unix)]
+ fn async_unix_stream(&self, stream: UnixStream) -> Result<AsyncStream> {
+ return Ok(Box::new(IoSourceWrapper {
+ source: self.async_from(AsyncWrapper::new(stream))?,
+ }));
+ }
+
+ async fn delay(&self, dur: Duration) -> Result<()> {
+ TimerAsync::sleep(self, dur).await.map_err(Into::into)
+ }
+}
diff --git a/cros_async/src/blocking.rs b/cros_async/src/blocking.rs
new file mode 100644
index 000000000..f6430a78b
--- /dev/null
+++ b/cros_async/src/blocking.rs
@@ -0,0 +1,9 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod block_on;
+mod pool;
+
+pub use block_on::*;
+pub use pool::*;
diff --git a/cros_async/src/blocking/block_on.rs b/cros_async/src/blocking/block_on.rs
new file mode 100644
index 000000000..2cb0b1e24
--- /dev/null
+++ b/cros_async/src/blocking/block_on.rs
@@ -0,0 +1,202 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ future::Future,
+ ptr,
+ sync::{
+ atomic::{AtomicI32, Ordering},
+ Arc,
+ },
+ task::{Context, Poll},
+};
+
+use futures::{
+ pin_mut,
+ task::{waker_ref, ArcWake},
+};
+
+// Randomly generated values to indicate the state of the current thread.
+const WAITING: i32 = 0x25de_74d1;
+const WOKEN: i32 = 0x72d3_2c9f;
+
+const FUTEX_WAIT_PRIVATE: libc::c_int = libc::FUTEX_WAIT | libc::FUTEX_PRIVATE_FLAG;
+const FUTEX_WAKE_PRIVATE: libc::c_int = libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG;
+
+thread_local!(static PER_THREAD_WAKER: Arc<Waker> = Arc::new(Waker(AtomicI32::new(WAITING))));
+
+#[repr(transparent)]
+struct Waker(AtomicI32);
+
+extern "C" {
+ #[cfg_attr(target_os = "android", link_name = "__errno")]
+ #[cfg_attr(target_os = "linux", link_name = "__errno_location")]
+ fn errno_location() -> *mut libc::c_int;
+}
+
+impl ArcWake for Waker {
+ fn wake_by_ref(arc_self: &Arc<Self>) {
+ let state = arc_self.0.swap(WOKEN, Ordering::Release);
+ if state == WAITING {
+ // The thread hasn't already been woken up so wake it up now. Safe because this doesn't
+ // modify any memory and we check the return value.
+ let res = unsafe {
+ libc::syscall(
+ libc::SYS_futex,
+ &arc_self.0,
+ FUTEX_WAKE_PRIVATE,
+ libc::INT_MAX, // val
+ ptr::null() as *const libc::timespec, // timeout
+ ptr::null() as *const libc::c_int, // uaddr2
+ 0_i32, // val3
+ )
+ };
+ if res < 0 {
+ panic!("unexpected error from FUTEX_WAKE_PRIVATE: {}", unsafe {
+ *errno_location()
+ });
+ }
+ }
+ }
+}
+
+/// Run a future to completion on the current thread.
+///
+/// This method will block the current thread until `f` completes. Useful when you need to call an
+/// async fn from a non-async context.
+pub fn block_on<F: Future>(f: F) -> F::Output {
+ pin_mut!(f);
+
+ PER_THREAD_WAKER.with(|thread_waker| {
+ let waker = waker_ref(thread_waker);
+ let mut cx = Context::from_waker(&waker);
+
+ loop {
+ if let Poll::Ready(t) = f.as_mut().poll(&mut cx) {
+ return t;
+ }
+
+ let state = thread_waker.0.swap(WAITING, Ordering::Acquire);
+ if state == WAITING {
+ // If we weren't already woken up then wait until we are. Safe because this doesn't
+ // modify any memory and we check the return value.
+ let res = unsafe {
+ libc::syscall(
+ libc::SYS_futex,
+ &thread_waker.0,
+ FUTEX_WAIT_PRIVATE,
+ state,
+ ptr::null() as *const libc::timespec, // timeout
+ ptr::null() as *const libc::c_int, // uaddr2
+ 0_i32, // val3
+ )
+ };
+
+ if res < 0 {
+ // Safe because libc guarantees that this is a valid pointer.
+ match unsafe { *errno_location() } {
+ libc::EAGAIN | libc::EINTR => {}
+ e => panic!("unexpected error from FUTEX_WAIT_PRIVATE: {}", e),
+ }
+ }
+
+ // Clear the state to prevent unnecessary extra loop iterations and also to allow
+ // nested usage of `block_on`.
+ thread_waker.0.store(WAITING, Ordering::Release);
+ }
+ }
+ })
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use std::{
+ future::Future,
+ pin::Pin,
+ sync::{
+ mpsc::{channel, Sender},
+ Arc,
+ },
+ task::{Context, Poll, Waker},
+ thread,
+ time::Duration,
+ };
+
+ use super::super::super::sync::SpinLock;
+
+ struct TimerState {
+ fired: bool,
+ waker: Option<Waker>,
+ }
+ struct Timer {
+ state: Arc<SpinLock<TimerState>>,
+ }
+
+ impl Future for Timer {
+ type Output = ();
+
+ fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ let mut state = self.state.lock();
+ if state.fired {
+ return Poll::Ready(());
+ }
+
+ state.waker = Some(cx.waker().clone());
+ Poll::Pending
+ }
+ }
+
+ fn start_timer(dur: Duration, notify: Option<Sender<()>>) -> Timer {
+ let state = Arc::new(SpinLock::new(TimerState {
+ fired: false,
+ waker: None,
+ }));
+
+ let thread_state = Arc::clone(&state);
+ thread::spawn(move || {
+ thread::sleep(dur);
+ let mut ts = thread_state.lock();
+ ts.fired = true;
+ if let Some(waker) = ts.waker.take() {
+ waker.wake();
+ }
+ drop(ts);
+
+ if let Some(tx) = notify {
+ tx.send(()).expect("Failed to send completion notification");
+ }
+ });
+
+ Timer { state }
+ }
+
+ #[test]
+ fn it_works() {
+ block_on(start_timer(Duration::from_millis(100), None));
+ }
+
+ #[test]
+ fn nested() {
+ async fn inner() {
+ block_on(start_timer(Duration::from_millis(100), None));
+ }
+
+ block_on(inner());
+ }
+
+ #[test]
+ fn ready_before_poll() {
+ let (tx, rx) = channel();
+
+ let timer = start_timer(Duration::from_millis(50), Some(tx));
+
+ rx.recv()
+ .expect("Failed to receive completion notification");
+
+ // We know the timer has already fired so the poll should complete immediately.
+ block_on(timer);
+ }
+}
diff --git a/cros_async/src/blocking/pool.rs b/cros_async/src/blocking/pool.rs
new file mode 100644
index 000000000..26df32e97
--- /dev/null
+++ b/cros_async/src/blocking/pool.rs
@@ -0,0 +1,524 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{
+ collections::VecDeque,
+ mem,
+ sync::{
+ mpsc::{channel, Receiver, Sender},
+ Arc,
+ },
+ thread::{
+ JoinHandle, {self},
+ },
+ time::{Duration, Instant},
+};
+
+use async_task::{Runnable, Task};
+use base::{error, warn};
+use slab::Slab;
+use sync::{Condvar, Mutex};
+
+const DEFAULT_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(10);
+
+struct State {
+ tasks: VecDeque<Runnable>,
+ num_threads: usize,
+ num_idle: usize,
+ num_notified: usize,
+ worker_threads: Slab<JoinHandle<()>>,
+ exited_threads: Option<Receiver<usize>>,
+ exit: Sender<usize>,
+ shutting_down: bool,
+}
+
+fn run_blocking_thread(idx: usize, inner: Arc<Inner>, exit: Sender<usize>) {
+ let mut state = inner.state.lock();
+ while !state.shutting_down {
+ if let Some(runnable) = state.tasks.pop_front() {
+ drop(state);
+ runnable.run();
+ state = inner.state.lock();
+ continue;
+ }
+
+ // No more tasks so wait for more work.
+ state.num_idle += 1;
+
+ let (guard, result) = inner
+ .condvar
+ .wait_timeout_while(state, inner.keepalive, |s| {
+ !s.shutting_down && s.num_notified == 0
+ });
+ state = guard;
+
+ // If `state.num_notified > 0` then this was a real wakeup.
+ if state.num_notified > 0 {
+ state.num_notified -= 1;
+ continue;
+ }
+
+ // Only decrement the idle count if we timed out. Otherwise, it was decremented when new
+ // work was added to `state.tasks`.
+ if result.timed_out() {
+ state.num_idle = state
+ .num_idle
+ .checked_sub(1)
+ .expect("`num_idle` underflow on timeout");
+ break;
+ }
+ }
+
+ state.num_threads -= 1;
+
+ // If we're shutting down then the BlockingPool will take care of joining all the threads.
+ // Otherwise, we need to join the last worker thread that exited here.
+ let last_exited_thread = if let Some(exited_threads) = state.exited_threads.as_mut() {
+ exited_threads
+ .try_recv()
+ .map(|idx| state.worker_threads.remove(idx))
+ .ok()
+ } else {
+ None
+ };
+
+ // Drop the lock before trying to join the last exited thread.
+ drop(state);
+
+ if let Some(handle) = last_exited_thread {
+ let _ = handle.join();
+ }
+
+ if let Err(e) = exit.send(idx) {
+ error!("Failed to send thread exit event on channel: {}", e);
+ }
+}
+
+struct Inner {
+ state: Mutex<State>,
+ condvar: Condvar,
+ max_threads: usize,
+ keepalive: Duration,
+}
+
+impl Inner {
+ fn schedule(self: &Arc<Inner>, runnable: Runnable) {
+ let mut state = self.state.lock();
+
+ // If we're shutting down then nothing is going to run this task.
+ if state.shutting_down {
+ return;
+ }
+
+ state.tasks.push_back(runnable);
+
+ if state.num_idle == 0 {
+ // There are no idle threads. Spawn a new one if possible.
+ if state.num_threads < self.max_threads {
+ state.num_threads += 1;
+ let exit = state.exit.clone();
+ let entry = state.worker_threads.vacant_entry();
+ let idx = entry.key();
+ let inner = self.clone();
+ entry.insert(
+ thread::Builder::new()
+ .name(format!("blockingPool{}", idx))
+ .spawn(move || run_blocking_thread(idx, inner, exit))
+ .unwrap(),
+ );
+ }
+ } else {
+ // We have idle threads, wake one up.
+ state.num_idle -= 1;
+ state.num_notified += 1;
+ self.condvar.notify_one();
+ }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error("{0} BlockingPool threads did not exit in time and will be detached")]
+pub struct ShutdownTimedOut(usize);
+
+/// A thread pool for running work that may block.
+///
+/// It is generally discouraged to do any blocking work inside an async function. However, this is
+/// sometimes unavoidable when dealing with interfaces that don't provide async variants. In this
+/// case callers may use the `BlockingPool` to run the blocking work on a different thread and
+/// `await` for its result to finish, which will prevent blocking the main thread of the
+/// application.
+///
+/// Since the blocking work is sent to another thread, users should be careful when using the
+/// `BlockingPool` for latency-sensitive operations. Additionally, the `BlockingPool` is intended to
+/// be used for work that will eventually complete on its own. Users who want to spawn a thread
+/// should just use `thread::spawn` directly.
+///
+/// There is no way to cancel work once it has been picked up by one of the worker threads in the
+/// `BlockingPool`. Dropping or shutting down the pool will block up to a timeout (default 10
+/// seconds) to wait for any active blocking work to finish. Any threads running tasks that have not
+/// completed by that time will be detached.
+///
+/// # Examples
+///
+/// Spawn a task to run in the `BlockingPool` and await on its result.
+///
+/// ```edition2018
+/// use cros_async::BlockingPool;
+///
+/// # async fn do_it() {
+/// let pool = BlockingPool::default();
+///
+/// let res = pool.spawn(move || {
+/// // Do some CPU-intensive or blocking work here.
+///
+/// 42
+/// }).await;
+///
+/// assert_eq!(res, 42);
+/// # }
+/// # cros_async::block_on(do_it());
+/// ```
+pub struct BlockingPool {
+ inner: Arc<Inner>,
+}
+
+impl BlockingPool {
+ /// Create a new `BlockingPool`.
+ ///
+ /// The `BlockingPool` will never spawn more than `max_threads` threads to do work, regardless
+ /// of the number of tasks that are added to it. This value should be set relatively low (for
+ /// example, the number of CPUs on the machine) if the pool is intended to run CPU intensive
+ /// work or it should be set relatively high (128 or more) if the pool is intended to be used
+ /// for various IO operations that cannot be completed asynchronously. The default value is 256.
+ ///
+ /// Worker threads are spawned on demand when new work is added to the pool and will
+ /// automatically exit after being idle for some time so there is no overhead for setting
+ /// `max_threads` to a large value when there is little to no work assigned to the
+ /// `BlockingPool`. `keepalive` determines the idle duration after which the worker thread will
+ /// exit. The default value is 10 seconds.
+ pub fn new(max_threads: usize, keepalive: Duration) -> BlockingPool {
+ let (exit, exited_threads) = channel();
+ BlockingPool {
+ inner: Arc::new(Inner {
+ state: Mutex::new(State {
+ tasks: VecDeque::new(),
+ num_threads: 0,
+ num_idle: 0,
+ num_notified: 0,
+ worker_threads: Slab::new(),
+ exited_threads: Some(exited_threads),
+ exit,
+ shutting_down: false,
+ }),
+ condvar: Condvar::new(),
+ max_threads,
+ keepalive,
+ }),
+ }
+ }
+
+ /// Like new but with pre-allocating capacity for up to `max_threads`.
+ pub fn with_capacity(max_threads: usize, keepalive: Duration) -> BlockingPool {
+ let (exit, exited_threads) = channel();
+ BlockingPool {
+ inner: Arc::new(Inner {
+ state: Mutex::new(State {
+ tasks: VecDeque::new(),
+ num_threads: 0,
+ num_idle: 0,
+ num_notified: 0,
+ worker_threads: Slab::with_capacity(max_threads),
+ exited_threads: Some(exited_threads),
+ exit,
+ shutting_down: false,
+ }),
+ condvar: Condvar::new(),
+ max_threads,
+ keepalive,
+ }),
+ }
+ }
+
+ /// Spawn a task to run in the `BlockingPool`.
+ ///
+ /// Callers may `await` the returned `Task` to be notified when the work is completed.
+ ///
+ /// # Panics
+ ///
+ /// `await`ing a `Task` after dropping the `BlockingPool` or calling `BlockingPool::shutdown`
+ /// will panic if the work was not completed before the pool was shut down.
+ pub fn spawn<F, R>(&self, f: F) -> Task<R>
+ where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+ {
+ let raw = Arc::downgrade(&self.inner);
+ let schedule = move |runnable| {
+ if let Some(i) = raw.upgrade() {
+ i.schedule(runnable);
+ }
+ };
+
+ let (runnable, task) = async_task::spawn(async move { f() }, schedule);
+ runnable.schedule();
+
+ task
+ }
+
+ /// Shut down the `BlockingPool`.
+ ///
+ /// If `deadline` is provided then this will block until either all worker threads exit or the
+ /// deadline is exceeded. If `deadline` is not given then this will block indefinitely until all
+ /// worker threads exit. Any work that was added to the `BlockingPool` but not yet picked up by
+ /// a worker thread will not complete and `await`ing on the `Task` for that work will panic.
+ pub fn shutdown(&self, deadline: Option<Instant>) -> Result<(), ShutdownTimedOut> {
+ let mut state = self.inner.state.lock();
+
+ if state.shutting_down {
+ // We've already shut down this BlockingPool.
+ return Ok(());
+ }
+
+ state.shutting_down = true;
+ let exited_threads = state.exited_threads.take().expect("exited_threads missing");
+ let unfinished_tasks = std::mem::take(&mut state.tasks);
+ let mut worker_threads = mem::replace(&mut state.worker_threads, Slab::new());
+ drop(state);
+
+ self.inner.condvar.notify_all();
+
+ // Cancel any unfinished work after releasing the lock.
+ drop(unfinished_tasks);
+
+ // Now wait for all worker threads to exit.
+ if let Some(deadline) = deadline {
+ let mut now = Instant::now();
+ while now < deadline && !worker_threads.is_empty() {
+ if let Ok(idx) = exited_threads.recv_timeout(deadline - now) {
+ let _ = worker_threads.remove(idx).join();
+ }
+ now = Instant::now();
+ }
+
+ // Any threads that have not yet joined will just be detached.
+ if !worker_threads.is_empty() {
+ return Err(ShutdownTimedOut(worker_threads.len()));
+ }
+
+ Ok(())
+ } else {
+ // Block indefinitely until all worker threads exit.
+ for handle in worker_threads.drain() {
+ let _ = handle.join();
+ }
+
+ Ok(())
+ }
+ }
+}
+
+impl Default for BlockingPool {
+ fn default() -> BlockingPool {
+ BlockingPool::new(256, Duration::from_secs(10))
+ }
+}
+
+impl Drop for BlockingPool {
+ fn drop(&mut self) {
+ if let Err(e) = self.shutdown(Some(Instant::now() + DEFAULT_SHUTDOWN_TIMEOUT)) {
+ warn!("{}", e);
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use std::{
+ sync::{Arc, Barrier},
+ thread,
+ time::{Duration, Instant},
+ };
+
+ use futures::{stream::FuturesUnordered, StreamExt};
+ use sync::{Condvar, Mutex};
+
+ use super::super::super::{block_on, BlockingPool};
+
+ #[test]
+ fn blocking_sleep() {
+ let pool = BlockingPool::default();
+
+ let res = block_on(pool.spawn(|| 42));
+ assert_eq!(res, 42);
+ }
+
+ #[test]
+ fn fast_tasks_with_short_keepalive() {
+ let pool = BlockingPool::new(256, Duration::from_millis(1));
+
+ let streams = FuturesUnordered::new();
+ for _ in 0..2 {
+ for _ in 0..256 {
+ let task = pool.spawn(|| ());
+ streams.push(task);
+ }
+
+ thread::sleep(Duration::from_millis(1));
+ }
+
+ block_on(streams.collect::<Vec<_>>());
+
+ // The test passes if there are no panics, which would happen if one of the worker threads
+ // triggered an underflow on `pool.inner.state.num_idle`.
+ }
+
+ #[test]
+ fn more_tasks_than_threads() {
+ let pool = BlockingPool::new(4, Duration::from_secs(10));
+
+ let stream = (0..19)
+ .map(|_| pool.spawn(|| thread::sleep(Duration::from_millis(5))))
+ .collect::<FuturesUnordered<_>>();
+
+ let results = block_on(stream.collect::<Vec<_>>());
+ assert_eq!(results.len(), 19);
+ }
+
+ #[test]
+ fn shutdown() {
+ let pool = BlockingPool::default();
+
+ let stream = (0..19)
+ .map(|_| pool.spawn(|| thread::sleep(Duration::from_millis(5))))
+ .collect::<FuturesUnordered<_>>();
+
+ let results = block_on(stream.collect::<Vec<_>>());
+ assert_eq!(results.len(), 19);
+
+ pool.shutdown(Some(Instant::now() + Duration::from_secs(10)))
+ .unwrap();
+ let state = pool.inner.state.lock();
+ assert_eq!(state.num_threads, 0);
+ }
+
+ #[test]
+ fn keepalive_timeout() {
+ // Set the keepalive to a very low value so that threads will exit soon after they run out
+ // of work.
+ let pool = BlockingPool::new(7, Duration::from_millis(1));
+
+ let stream = (0..19)
+ .map(|_| pool.spawn(|| thread::sleep(Duration::from_millis(5))))
+ .collect::<FuturesUnordered<_>>();
+
+ let results = block_on(stream.collect::<Vec<_>>());
+ assert_eq!(results.len(), 19);
+
+ // Wait for all threads to exit.
+ let deadline = Instant::now() + Duration::from_secs(10);
+ while Instant::now() < deadline {
+ thread::sleep(Duration::from_millis(100));
+ let state = pool.inner.state.lock();
+ if state.num_threads == 0 {
+ break;
+ }
+ }
+
+ {
+ let state = pool.inner.state.lock();
+ assert_eq!(state.num_threads, 0);
+ assert_eq!(state.num_idle, 0);
+ }
+ }
+
+ #[test]
+ #[should_panic]
+ fn shutdown_with_pending_work() {
+ let pool = BlockingPool::new(1, Duration::from_secs(10));
+
+ let mu = Arc::new(Mutex::new(false));
+ let cv = Arc::new(Condvar::new());
+
+ // First spawn a thread that blocks the pool.
+ let task_mu = mu.clone();
+ let task_cv = cv.clone();
+ pool.spawn(move || {
+ let mut ready = task_mu.lock();
+ while !*ready {
+ ready = task_cv.wait(ready);
+ }
+ })
+ .detach();
+
+ // This task will never finish because we will shut down the pool first.
+ let unfinished = pool.spawn(|| 5);
+
+ // Spawn a thread to unblock the work we started earlier once it sees that the pool is
+ // shutting down.
+ let inner = pool.inner.clone();
+ thread::spawn(move || {
+ let mut state = inner.state.lock();
+ while !state.shutting_down {
+ state = inner.condvar.wait(state);
+ }
+
+ *mu.lock() = true;
+ cv.notify_all();
+ });
+ pool.shutdown(None).unwrap();
+
+ // This should panic.
+ assert_eq!(block_on(unfinished), 5);
+ }
+
+ #[test]
+ fn unfinished_worker_thread() {
+ let pool = BlockingPool::default();
+
+ let ready = Arc::new(Mutex::new(false));
+ let cv = Arc::new(Condvar::new());
+ let barrier = Arc::new(Barrier::new(2));
+
+ let thread_ready = ready.clone();
+ let thread_barrier = barrier.clone();
+ let thread_cv = cv.clone();
+
+ let task = pool.spawn(move || {
+ thread_barrier.wait();
+ let mut ready = thread_ready.lock();
+ while !*ready {
+ ready = thread_cv.wait(ready);
+ }
+ });
+
+ // Wait to shut down the pool until after the worker thread has started.
+ barrier.wait();
+ pool.shutdown(Some(Instant::now() + Duration::from_millis(5)))
+ .unwrap_err();
+
+ let num_threads = pool.inner.state.lock().num_threads;
+ assert_eq!(num_threads, 1);
+
+ // Now wake up the blocked task so we don't leak the thread.
+ *ready.lock() = true;
+ cv.notify_all();
+
+ block_on(task);
+
+ let deadline = Instant::now() + Duration::from_secs(10);
+ while Instant::now() < deadline {
+ thread::sleep(Duration::from_millis(100));
+ let state = pool.inner.state.lock();
+ if state.num_threads == 0 {
+ break;
+ }
+ }
+
+ {
+ let state = pool.inner.state.lock();
+ assert_eq!(state.num_threads, 0);
+ assert_eq!(state.num_idle, 0);
+ }
+ }
+}
diff --git a/cros_async/src/complete.rs b/cros_async/src/complete.rs
index dada336d0..9fb9273f4 100644
--- a/cros_async/src/complete.rs
+++ b/cros_async/src/complete.rs
@@ -5,9 +5,11 @@
// Need non-snake case so the macro can re-use type names for variables.
#![allow(non_snake_case)]
-use std::future::Future;
-use std::pin::Pin;
-use std::task::{Context, Poll};
+use std::{
+ future::Future,
+ pin::Pin,
+ task::{Context, Poll},
+};
use futures::future::{maybe_done, MaybeDone};
use pin_utils::unsafe_pinned;
diff --git a/cros_async/src/event.rs b/cros_async/src/event.rs
index 20fd55812..6f65d0cbc 100644
--- a/cros_async/src/event.rs
+++ b/cros_async/src/event.rs
@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use sys_util::EventFd;
+use base::EventFd;
-use crate::{AsyncResult, Executor, IntoAsync, IoSourceExt};
+use super::{AsyncResult, Executor, IntoAsync, IoSourceExt};
-/// An async version of `sys_util::EventFd`.
+/// An async version of `base::EventFd`.
pub struct EventAsync {
io_source: Box<dyn IoSourceExt<EventFd>>,
}
@@ -18,13 +18,13 @@ impl EventAsync {
}
#[cfg(test)]
- pub(crate) fn new_poll(event: EventFd, ex: &crate::FdExecutor) -> AsyncResult<EventAsync> {
- crate::executor::async_poll_from(event, ex).map(|io_source| EventAsync { io_source })
+ pub(crate) fn new_poll(event: EventFd, ex: &super::FdExecutor) -> AsyncResult<EventAsync> {
+ super::executor::async_poll_from(event, ex).map(|io_source| EventAsync { io_source })
}
#[cfg(test)]
- pub(crate) fn new_uring(event: EventFd, ex: &crate::URingExecutor) -> AsyncResult<EventAsync> {
- crate::executor::async_uring_from(event, ex).map(|io_source| EventAsync { io_source })
+ pub(crate) fn new_uring(event: EventFd, ex: &super::URingExecutor) -> AsyncResult<EventAsync> {
+ super::executor::async_uring_from(event, ex).map(|io_source| EventAsync { io_source })
}
/// Gets the next value from the eventfd.
@@ -40,7 +40,7 @@ impl IntoAsync for EventFd {}
mod tests {
use super::*;
- use crate::{Executor, FdExecutor, URingExecutor};
+ use super::super::{uring_executor::use_uring, Executor, FdExecutor, URingExecutor};
#[test]
fn next_val_reads_value() {
@@ -58,6 +58,10 @@ mod tests {
#[test]
fn next_val_reads_value_poll_and_ring() {
+ if !use_uring() {
+ return;
+ }
+
async fn go(event_async: EventAsync) -> u64 {
event_async.next_val().await.unwrap()
}
diff --git a/cros_async/src/executor.rs b/cros_async/src/executor.rs
index f2472395c..e79790901 100644
--- a/cros_async/src/executor.rs
+++ b/cros_async/src/executor.rs
@@ -6,25 +6,24 @@ use std::future::Future;
use async_task::Task;
-use crate::poll_source::Error as PollError;
-use crate::uring_executor::use_uring;
-use crate::{
- AsyncResult, FdExecutor, IntoAsync, IoSourceExt, PollSource, URingExecutor, UringSource,
+use super::{
+ poll_source::Error as PollError, uring_executor::use_uring, AsyncResult, FdExecutor, IntoAsync,
+ IoSourceExt, PollSource, URingExecutor, UringSource,
};
-pub(crate) fn async_uring_from<'a, F: IntoAsync + 'a>(
+pub(crate) fn async_uring_from<'a, F: IntoAsync + Send + 'a>(
f: F,
ex: &URingExecutor,
-) -> AsyncResult<Box<dyn IoSourceExt<F> + 'a>> {
- Ok(UringSource::new(f, ex).map(|u| Box::new(u) as Box<dyn IoSourceExt<F>>)?)
+) -> AsyncResult<Box<dyn IoSourceExt<F> + Send + 'a>> {
+ Ok(UringSource::new(f, ex).map(|u| Box::new(u) as Box<dyn IoSourceExt<F> + Send>)?)
}
/// Creates a concrete `IoSourceExt` using the fd_executor.
-pub(crate) fn async_poll_from<'a, F: IntoAsync + 'a>(
+pub(crate) fn async_poll_from<'a, F: IntoAsync + Send + 'a>(
f: F,
ex: &FdExecutor,
-) -> AsyncResult<Box<dyn IoSourceExt<F> + 'a>> {
- Ok(PollSource::new(f, ex).map(|u| Box::new(u) as Box<dyn IoSourceExt<F>>)?)
+) -> AsyncResult<Box<dyn IoSourceExt<F> + Send + 'a>> {
+ Ok(PollSource::new(f, ex).map(|u| Box::new(u) as Box<dyn IoSourceExt<F> + Send>)?)
}
/// An executor for scheduling tasks that poll futures to completion.
@@ -50,7 +49,7 @@ pub(crate) fn async_poll_from<'a, F: IntoAsync + 'a>(
/// // Write all bytes from `data` to `f`.
/// async fn write_file(f: &dyn IoSourceExt<File>, mut data: Vec<u8>) -> AsyncResult<()> {
/// while data.len() > 0 {
-/// let (count, mut buf) = f.write_from_vec(0, data).await?;
+/// let (count, mut buf) = f.write_from_vec(None, data).await?;
///
/// data = buf.split_off(count);
/// }
@@ -68,7 +67,7 @@ pub(crate) fn async_poll_from<'a, F: IntoAsync + 'a>(
///
/// while rem > 0 {
/// let buf = vec![0u8; min(rem, CHUNK_SIZE)];
-/// let (count, mut data) = from.read_to_vec(0, buf).await?;
+/// let (count, mut data) = from.read_to_vec(None, buf).await?;
///
/// if count == 0 {
/// // End of file. Return the number of bytes transferred.
@@ -87,7 +86,7 @@ pub(crate) fn async_poll_from<'a, F: IntoAsync + 'a>(
/// # fn do_it() -> Result<(), Box<dyn Error>> {
/// let ex = Executor::new()?;
///
-/// let (rx, tx) = sys_util::pipe(true)?;
+/// let (rx, tx) = base::pipe(true)?;
/// let zero = File::open("/dev/zero")?;
/// let zero_bytes = CHUNK_SIZE * 7;
/// let zero_to_pipe = transfer_data(
@@ -137,10 +136,10 @@ impl Executor {
/// Create a new `Box<dyn IoSourceExt<F>>` associated with `self`. Callers may then use the
/// returned `IoSourceExt` to directly start async operations without needing a separate
/// reference to the executor.
- pub fn async_from<'a, F: IntoAsync + 'a>(
+ pub fn async_from<'a, F: IntoAsync + Send + 'a>(
&self,
f: F,
- ) -> AsyncResult<Box<dyn IoSourceExt<F> + 'a>> {
+ ) -> AsyncResult<Box<dyn IoSourceExt<F> + Send + 'a>> {
match self {
Executor::Uring(ex) => async_uring_from(f, ex),
Executor::Fd(ex) => async_poll_from(f, ex),
@@ -227,6 +226,46 @@ impl Executor {
}
}
+ /// Run the provided closure on a dedicated thread where blocking is allowed.
+ ///
+ /// Callers may `await` on the returned `Task` to wait for the result of `f`. Dropping or
+ /// canceling the returned `Task` may not cancel the operation if it was already started on a
+ /// worker thread.
+ ///
+ /// # Panics
+ ///
+ /// `await`ing the `Task` after the `Executor` is dropped will panic if the work was not already
+ /// completed.
+ ///
+ /// # Examples
+ ///
+ /// ```edition2018
+ /// # use cros_async::Executor;
+ ///
+ /// # async fn do_it(ex: &Executor) {
+ /// let res = ex.spawn_blocking(move || {
+ /// // Do some CPU-intensive or blocking work here.
+ ///
+ /// 42
+ /// }).await;
+ ///
+ /// assert_eq!(res, 42);
+ /// # }
+ ///
+ /// # let ex = Executor::new().unwrap();
+ /// # ex.run_until(do_it(&ex)).unwrap();
+ /// ```
+ pub fn spawn_blocking<F, R>(&self, f: F) -> Task<R>
+ where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+ {
+ match self {
+ Executor::Uring(ex) => ex.spawn_blocking(f),
+ Executor::Fd(ex) => ex.spawn_blocking(f),
+ }
+ }
+
/// Run the executor indefinitely, driving all spawned futures to completion. This method will
/// block the current thread and only return in the case of an error.
///
diff --git a/cros_async/src/fd_executor.rs b/cros_async/src/fd_executor.rs
index ec6a72061..85ee79637 100644
--- a/cros_async/src/fd_executor.rs
+++ b/cros_async/src/fd_executor.rs
@@ -9,58 +9,84 @@
//! `FdExecutor` is meant to be used with the `futures-rs` crate that provides combinators and
//! utility functions to combine futures.
-use std::fs::File;
-use std::future::Future;
-use std::mem;
-use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
-use std::pin::Pin;
-use std::sync::atomic::{AtomicI32, Ordering};
-use std::sync::{Arc, Weak};
-use std::task::{Context, Poll, Waker};
+use std::{
+ fs::File,
+ future::Future,
+ io, mem,
+ os::unix::io::{AsRawFd, FromRawFd, RawFd},
+ pin::Pin,
+ sync::{
+ atomic::{AtomicI32, Ordering},
+ Arc, Weak,
+ },
+ task::{Context, Poll, Waker},
+};
use async_task::Task;
+use base::{add_fd_flags, warn, EpollContext, EpollEvents, EventFd, WatchingEvents};
use futures::task::noop_waker;
use pin_utils::pin_mut;
+use remain::sorted;
use slab::Slab;
use sync::Mutex;
-use sys_util::{add_fd_flags, warn, EpollContext, EpollEvents, EventFd, WatchingEvents};
use thiserror::Error as ThisError;
-use crate::queue::RunnableQueue;
-use crate::waker::{new_waker, WakerToken, WeakWake};
+use super::{
+ queue::RunnableQueue,
+ waker::{new_waker, WakerToken, WeakWake},
+ BlockingPool,
+};
+#[sorted]
#[derive(Debug, ThisError)]
pub enum Error {
/// Failed to clone the EventFd for waking the executor.
#[error("Failed to clone the EventFd for waking the executor: {0}")]
- CloneEventFd(sys_util::Error),
+ CloneEventFd(base::Error),
/// Failed to create the EventFd for waking the executor.
#[error("Failed to create the EventFd for waking the executor: {0}")]
- CreateEventFd(sys_util::Error),
+ CreateEventFd(base::Error),
+ /// Creating a context to wait on FDs failed.
+ #[error("An error creating the fd waiting context: {0}")]
+ CreatingContext(base::Error),
/// Failed to copy the FD for the polling context.
#[error("Failed to copy the FD for the polling context: {0}")]
- DuplicatingFd(sys_util::Error),
+ DuplicatingFd(base::Error),
/// The Executor is gone.
#[error("The FDExecutor is gone")]
ExecutorGone,
- /// Creating a context to wait on FDs failed.
- #[error("An error creating the fd waiting context: {0}")]
- CreatingContext(sys_util::Error),
/// PollContext failure.
#[error("PollContext failure: {0}")]
- PollContextError(sys_util::Error),
+ PollContextError(base::Error),
/// An error occurred when setting the FD non-blocking.
#[error("An error occurred setting the FD non-blocking: {0}.")]
- SettingNonBlocking(sys_util::Error),
+ SettingNonBlocking(base::Error),
/// Failed to submit the waker to the polling context.
#[error("An error adding to the Aio context: {0}")]
- SubmittingWaker(sys_util::Error),
+ SubmittingWaker(base::Error),
/// A Waker was canceled, but the operation isn't running.
#[error("Unknown waker")]
UnknownWaker,
}
pub type Result<T> = std::result::Result<T, Error>;
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ use Error::*;
+ match e {
+ CloneEventFd(e) => e.into(),
+ CreateEventFd(e) => e.into(),
+ DuplicatingFd(e) => e.into(),
+ ExecutorGone => io::Error::new(io::ErrorKind::Other, e),
+ CreatingContext(e) => e.into(),
+ PollContextError(e) => e.into(),
+ SettingNonBlocking(e) => e.into(),
+ SubmittingWaker(e) => e.into(),
+ UnknownWaker => io::Error::new(io::ErrorKind::Other, e),
+ }
+ }
+}
+
// A poll operation that has been submitted and is potentially being waited on.
struct OpData {
file: File,
@@ -235,6 +261,7 @@ struct RawExecutor {
queue: RunnableQueue,
poll_ctx: EpollContext<usize>,
ops: Mutex<Slab<OpStatus>>,
+ blocking_pool: BlockingPool,
state: AtomicI32,
notify: EventFd,
}
@@ -245,6 +272,7 @@ impl RawExecutor {
queue: RunnableQueue::new(),
poll_ctx: EpollContext::new().map_err(Error::CreatingContext)?,
ops: Mutex::new(Slab::with_capacity(64)),
+ blocking_pool: Default::default(),
state: AtomicI32::new(PROCESSING),
notify,
})
@@ -312,6 +340,14 @@ impl RawExecutor {
task
}
+ fn spawn_blocking<F, R>(self: &Arc<Self>, f: F) -> Task<R>
+ where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+ {
+ self.blocking_pool.spawn(f)
+ }
+
fn run<F: Future>(&self, cx: &mut Context, done: F) -> Result<F::Output> {
let events = EpollEvents::new();
pin_mut!(done);
@@ -479,11 +515,19 @@ impl FdExecutor {
self.raw.spawn_local(f)
}
+ pub fn spawn_blocking<F, R>(&self, f: F) -> Task<R>
+ where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+ {
+ self.raw.spawn_blocking(f)
+ }
+
pub fn run(&self) -> Result<()> {
let waker = new_waker(Arc::downgrade(&self.raw));
let mut cx = Context::from_waker(&waker);
- self.raw.run(&mut cx, crate::empty::<()>())
+ self.raw.run(&mut cx, super::empty::<()>())
}
pub fn run_until<F: Future>(&self, f: F) -> Result<F::Output> {
@@ -507,7 +551,7 @@ impl FdExecutor {
unsafe fn dup_fd(fd: RawFd) -> Result<RawFd> {
let ret = libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0);
if ret < 0 {
- Err(Error::DuplicatingFd(sys_util::Error::last()))
+ Err(Error::DuplicatingFd(base::Error::last()))
} else {
Ok(ret)
}
@@ -515,9 +559,11 @@ unsafe fn dup_fd(fd: RawFd) -> Result<RawFd> {
#[cfg(test)]
mod test {
- use std::cell::RefCell;
- use std::io::{Read, Write};
- use std::rc::Rc;
+ use std::{
+ cell::RefCell,
+ io::{Read, Write},
+ rc::Rc,
+ };
use futures::future::Either;
@@ -526,7 +572,7 @@ mod test {
#[test]
fn test_it() {
async fn do_test(ex: &FdExecutor) {
- let (r, _w) = sys_util::pipe(true).unwrap();
+ let (r, _w) = base::pipe(true).unwrap();
let done = Box::pin(async { 5usize });
let source = ex.register_source(r).unwrap();
let pending = source.wait_readable().unwrap();
@@ -545,7 +591,7 @@ mod test {
}
let x = Rc::new(RefCell::new(0));
- crate::run_one_poll(my_async(x.clone())).unwrap();
+ super::super::run_one_poll(my_async(x.clone())).unwrap();
assert_eq!(*x.borrow(), 4);
}
@@ -566,7 +612,7 @@ mod test {
}
}
- let (mut rx, tx) = sys_util::pipe(true).expect("Pipe failed");
+ let (mut rx, tx) = base::pipe(true).expect("Pipe failed");
let ex = FdExecutor::new().unwrap();
diff --git a/cros_async/src/io_ext.rs b/cros_async/src/io_ext.rs
index 8c41a6e33..6b3e6e827 100644
--- a/cros_async/src/io_ext.rs
+++ b/cros_async/src/io_ext.rs
@@ -15,36 +15,40 @@
//! Operations can only access memory in a `Vec` or an implementor of `BackingMemory`. See the
//! `URingExecutor` documentation for an explaination of why.
-use std::fs::File;
-use std::os::unix::io::AsRawFd;
-use std::sync::Arc;
+use std::{
+ fs::File,
+ io,
+ ops::{Deref, DerefMut},
+ os::unix::io::{AsRawFd, RawFd},
+ sync::Arc,
+};
use async_trait::async_trait;
-use sys_util::net::UnixSeqpacket;
+use base::UnixSeqpacket;
+use remain::sorted;
use thiserror::Error as ThisError;
-use crate::{BackingMemory, MemRegion};
+use super::{BackingMemory, MemRegion};
+#[sorted]
#[derive(ThisError, Debug)]
pub enum Error {
/// An error with a polled(FD) source.
#[error("An error with a poll source: {0}")]
- Poll(crate::poll_source::Error),
+ Poll(#[from] super::poll_source::Error),
/// An error with a uring source.
#[error("An error with a uring source: {0}")]
- Uring(crate::uring_executor::Error),
+ Uring(#[from] super::uring_executor::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
-impl From<crate::uring_executor::Error> for Error {
- fn from(err: crate::uring_executor::Error) -> Self {
- Error::Uring(err)
- }
-}
-
-impl From<crate::poll_source::Error> for Error {
- fn from(err: crate::poll_source::Error) -> Self {
- Error::Poll(err)
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ use Error::*;
+ match e {
+ Poll(e) => e.into(),
+ Uring(e) => e.into(),
+ }
}
}
@@ -52,12 +56,16 @@ impl From<crate::poll_source::Error> for Error {
#[async_trait(?Send)]
pub trait ReadAsync {
/// Reads from the iosource at `file_offset` and fill the given `vec`.
- async fn read_to_vec<'a>(&'a self, file_offset: u64, vec: Vec<u8>) -> Result<(usize, Vec<u8>)>;
+ async fn read_to_vec<'a>(
+ &'a self,
+ file_offset: Option<u64>,
+ vec: Vec<u8>,
+ ) -> Result<(usize, Vec<u8>)>;
/// Reads to the given `mem` at the given offsets from the file starting at `file_offset`.
async fn read_to_mem<'a>(
&'a self,
- file_offset: u64,
+ file_offset: Option<u64>,
mem: Arc<dyn BackingMemory + Send + Sync>,
mem_offsets: &'a [MemRegion],
) -> Result<usize>;
@@ -75,14 +83,14 @@ pub trait WriteAsync {
/// Writes from the given `vec` to the file starting at `file_offset`.
async fn write_from_vec<'a>(
&'a self,
- file_offset: u64,
+ file_offset: Option<u64>,
vec: Vec<u8>,
) -> Result<(usize, Vec<u8>)>;
/// Writes from the given `mem` from the given offsets to the file starting at `file_offset`.
async fn write_from_mem<'a>(
&'a self,
- file_offset: u64,
+ file_offset: Option<u64>,
mem: Arc<dyn BackingMemory + Send + Sync>,
mem_offsets: &'a [MemRegion],
) -> Result<usize>;
@@ -108,33 +116,77 @@ pub trait IoSourceExt<F>: ReadAsync + WriteAsync {
}
/// Marker trait signifying that the implementor is suitable for use with
-/// cros_async. Examples of this include File, and sys_util::net::UnixSeqpacket.
+/// cros_async. Examples of this include File, and base::net::UnixSeqpacket.
///
/// (Note: it'd be really nice to implement a TryFrom for any implementors, and
/// remove our factory functions. Unfortunately
-/// https://github.com/rust-lang/rust/issues/50133 makes that too painful.)
+/// <https://github.com/rust-lang/rust/issues/50133> makes that too painful.)
pub trait IntoAsync: AsRawFd {}
impl IntoAsync for File {}
impl IntoAsync for UnixSeqpacket {}
impl IntoAsync for &UnixSeqpacket {}
+/// Simple wrapper struct to implement IntoAsync on foreign types.
+pub struct AsyncWrapper<T>(T);
+
+impl<T> AsyncWrapper<T> {
+ /// Create a new `AsyncWrapper` that wraps `val`.
+ pub fn new(val: T) -> Self {
+ AsyncWrapper(val)
+ }
+
+ /// Consumes the `AsyncWrapper`, returning the inner struct.
+ pub fn into_inner(self) -> T {
+ self.0
+ }
+}
+
+impl<T> Deref for AsyncWrapper<T> {
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ &self.0
+ }
+}
+
+impl<T> DerefMut for AsyncWrapper<T> {
+ fn deref_mut(&mut self) -> &mut T {
+ &mut self.0
+ }
+}
+
+impl<T: AsRawFd> AsRawFd for AsyncWrapper<T> {
+ fn as_raw_fd(&self) -> RawFd {
+ self.0.as_raw_fd()
+ }
+}
+
+impl<T: AsRawFd> IntoAsync for AsyncWrapper<T> {}
+
#[cfg(test)]
mod tests {
- use std::fs::{File, OpenOptions};
- use std::future::Future;
- use std::os::unix::io::AsRawFd;
- use std::pin::Pin;
- use std::sync::Arc;
- use std::task::{Context, Poll, Waker};
- use std::thread;
+ use std::{
+ fs::{File, OpenOptions},
+ future::Future,
+ os::unix::io::AsRawFd,
+ pin::Pin,
+ sync::Arc,
+ task::{Context, Poll, Waker},
+ thread,
+ };
use sync::Mutex;
- use super::*;
- use crate::executor::{async_poll_from, async_uring_from};
- use crate::mem::VecIoWrapper;
- use crate::{Executor, FdExecutor, MemRegion, PollSource, URingExecutor, UringSource};
+ use super::{
+ super::{
+ executor::{async_poll_from, async_uring_from},
+ mem::VecIoWrapper,
+ uring_executor::use_uring,
+ Executor, FdExecutor, MemRegion, PollSource, URingExecutor, UringSource,
+ },
+ *,
+ };
struct State {
should_quit: bool,
@@ -172,10 +224,13 @@ mod tests {
#[test]
fn await_uring_from_poll() {
+ if !use_uring() {
+ return;
+ }
// Start a uring operation and then await the result from an FdExecutor.
async fn go(source: UringSource<File>) {
let v = vec![0xa4u8; 16];
- let (len, vec) = source.read_to_vec(0, v).await.unwrap();
+ let (len, vec) = source.read_to_vec(None, v).await.unwrap();
assert_eq!(len, 16);
assert!(vec.iter().all(|&b| b == 0));
}
@@ -203,10 +258,13 @@ mod tests {
#[test]
fn await_poll_from_uring() {
+ if !use_uring() {
+ return;
+ }
// Start a poll operation and then await the result from a URingExecutor.
async fn go(source: PollSource<File>) {
let v = vec![0x2cu8; 16];
- let (len, vec) = source.read_to_vec(0, v).await.unwrap();
+ let (len, vec) = source.read_to_vec(None, v).await.unwrap();
assert_eq!(len, 16);
assert!(vec.iter().all(|&b| b == 0));
}
@@ -234,10 +292,13 @@ mod tests {
#[test]
fn readvec() {
+ if !use_uring() {
+ return;
+ }
async fn go<F: AsRawFd>(async_source: Box<dyn IoSourceExt<F>>) {
let v = vec![0x55u8; 32];
let v_ptr = v.as_ptr();
- let ret = async_source.read_to_vec(0, v).await.unwrap();
+ let ret = async_source.read_to_vec(None, v).await.unwrap();
assert_eq!(ret.0, 32);
let ret_v = ret.1;
assert_eq!(v_ptr, ret_v.as_ptr());
@@ -257,10 +318,13 @@ mod tests {
#[test]
fn writevec() {
+ if !use_uring() {
+ return;
+ }
async fn go<F: AsRawFd>(async_source: Box<dyn IoSourceExt<F>>) {
let v = vec![0x55u8; 32];
let v_ptr = v.as_ptr();
- let ret = async_source.write_from_vec(0, v).await.unwrap();
+ let ret = async_source.write_from_vec(None, v).await.unwrap();
assert_eq!(ret.0, 32);
let ret_v = ret.1;
assert_eq!(v_ptr, ret_v.as_ptr());
@@ -279,11 +343,14 @@ mod tests {
#[test]
fn readmem() {
+ if !use_uring() {
+ return;
+ }
async fn go<F: AsRawFd>(async_source: Box<dyn IoSourceExt<F>>) {
let mem = Arc::new(VecIoWrapper::from(vec![0x55u8; 8192]));
let ret = async_source
.read_to_mem(
- 0,
+ None,
Arc::<VecIoWrapper>::clone(&mem),
&[
MemRegion { offset: 0, len: 32 },
@@ -319,11 +386,14 @@ mod tests {
#[test]
fn writemem() {
+ if !use_uring() {
+ return;
+ }
async fn go<F: AsRawFd>(async_source: Box<dyn IoSourceExt<F>>) {
let mem = Arc::new(VecIoWrapper::from(vec![0x55u8; 8192]));
let ret = async_source
.write_from_mem(
- 0,
+ None,
Arc::<VecIoWrapper>::clone(&mem),
&[MemRegion { offset: 0, len: 32 }],
)
@@ -345,6 +415,9 @@ mod tests {
#[test]
fn read_u64s() {
+ if !use_uring() {
+ return;
+ }
async fn go(async_source: File, ex: URingExecutor) -> u64 {
let source = async_uring_from(async_source, &ex).unwrap();
source.read_u64().await.unwrap()
@@ -358,7 +431,10 @@ mod tests {
#[test]
fn read_eventfds() {
- use sys_util::EventFd;
+ if !use_uring() {
+ return;
+ }
+ use base::EventFd;
async fn go<F: AsRawFd>(source: Box<dyn IoSourceExt<F>>) -> u64 {
source.read_u64().await.unwrap()
@@ -381,10 +457,13 @@ mod tests {
#[test]
fn fsync() {
+ if !use_uring() {
+ return;
+ }
async fn go<F: AsRawFd>(source: Box<dyn IoSourceExt<F>>) {
let v = vec![0x55u8; 32];
let v_ptr = v.as_ptr();
- let ret = source.write_from_vec(0, v).await.unwrap();
+ let ret = source.write_from_vec(None, v).await.unwrap();
assert_eq!(ret.0, 32);
let ret_v = ret.1;
assert_eq!(v_ptr, ret_v.as_ptr());
diff --git a/cros_async/src/lib.rs b/cros_async/src/lib.rs
index 271115bd4..e266f232b 100644
--- a/cros_async/src/lib.rs
+++ b/cros_async/src/lib.rs
@@ -58,6 +58,9 @@
//! See the docs for `IoSourceExt` if support for kernels <5.4 is required. Focus on `UringSource` if
//! all systems have support for io_uring.
+mod async_types;
+pub mod audio_streams_async;
+mod blocking;
mod complete;
mod event;
mod executor;
@@ -73,11 +76,15 @@ mod uring_executor;
mod uring_source;
mod waker;
+pub use async_types::*;
+pub use base;
+pub use blocking::{block_on, BlockingPool};
pub use event::EventAsync;
pub use executor::Executor;
pub use fd_executor::FdExecutor;
pub use io_ext::{
- Error as AsyncError, IntoAsync, IoSourceExt, ReadAsync, Result as AsyncResult, WriteAsync,
+ AsyncWrapper, Error as AsyncError, IntoAsync, IoSourceExt, ReadAsync, Result as AsyncResult,
+ WriteAsync,
};
pub use mem::{BackingMemory, MemRegion};
pub use poll_source::PollSource;
@@ -86,24 +93,47 @@ pub use timer::TimerAsync;
pub use uring_executor::URingExecutor;
pub use uring_source::UringSource;
-use std::future::Future;
-use std::marker::PhantomData;
-use std::pin::Pin;
-use std::task::{Context, Poll};
+use std::{
+ future::Future,
+ io,
+ marker::PhantomData,
+ pin::Pin,
+ task::{Context, Poll},
+};
+use remain::sorted;
use thiserror::Error as ThisError;
+#[sorted]
#[derive(ThisError, Debug)]
pub enum Error {
/// Error from the FD executor.
#[error("Failure in the FD executor: {0}")]
FdExecutor(fd_executor::Error),
+ /// Error from TimerFd.
+ #[error("Failure in TimerAsync: {0}")]
+ TimerAsync(AsyncError),
+ /// Error from TimerFd.
+ #[error("Failure in TimerFd: {0}")]
+ TimerFd(base::Error),
/// Error from the uring executor.
#[error("Failure in the uring executor: {0}")]
URingExecutor(uring_executor::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ use Error::*;
+ match e {
+ FdExecutor(e) => e.into(),
+ URingExecutor(e) => e.into(),
+ TimerFd(e) => e.into(),
+ TimerAsync(e) => e.into(),
+ }
+ }
+}
+
// A Future that never completes.
pub struct Empty<T> {
phantom: PhantomData<T>,
@@ -378,6 +408,33 @@ pub async fn select6<
select::Select6::new(f1, f2, f3, f4, f5, f6).await
}
+pub async fn select7<
+ F1: Future + Unpin,
+ F2: Future + Unpin,
+ F3: Future + Unpin,
+ F4: Future + Unpin,
+ F5: Future + Unpin,
+ F6: Future + Unpin,
+ F7: Future + Unpin,
+>(
+ f1: F1,
+ f2: F2,
+ f3: F3,
+ f4: F4,
+ f5: F5,
+ f6: F6,
+ f7: F7,
+) -> (
+ SelectResult<F1>,
+ SelectResult<F2>,
+ SelectResult<F3>,
+ SelectResult<F4>,
+ SelectResult<F5>,
+ SelectResult<F6>,
+ SelectResult<F7>,
+) {
+ select::Select7::new(f1, f2, f3, f4, f5, f6, f7).await
+}
// Combination helpers to run until all futures are complete.
/// Creates a combinator that runs the two given futures to completion, returning a tuple of the
diff --git a/cros_async/src/mem.rs b/cros_async/src/mem.rs
index f90df470d..691e629fe 100644
--- a/cros_async/src/mem.rs
+++ b/cros_async/src/mem.rs
@@ -2,32 +2,19 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fmt::{self, Display};
-
use data_model::VolatileSlice;
+use remain::sorted;
+use thiserror::Error as ThisError;
-#[derive(Debug)]
+#[sorted]
+#[derive(ThisError, Debug)]
pub enum Error {
/// Invalid offset or length given for an iovec in backing memory.
+ #[error("Invalid offset/len for getting a slice from {0} with len {1}.")]
InvalidOffset(u64, usize),
}
pub type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- InvalidOffset(base, len) => write!(
- f,
- "Invalid offset/len for getting a slice from {} with len {}.",
- base, len
- ),
- }
- }
-}
-impl std::error::Error for Error {}
-
/// Used to index subslices of backing memory. Like an iovec, but relative to the start of the
/// memory region instead of an absolute pointer.
/// The backing memory referenced by the region can be an array, an mmapped file, or guest memory.
@@ -68,9 +55,9 @@ impl From<Vec<u8>> for VecIoWrapper {
}
}
-impl Into<Vec<u8>> for VecIoWrapper {
- fn into(self) -> Vec<u8> {
- self.inner.into()
+impl From<VecIoWrapper> for Vec<u8> {
+ fn from(v: VecIoWrapper) -> Vec<u8> {
+ v.inner.into()
}
}
diff --git a/cros_async/src/poll_source.rs b/cros_async/src/poll_source.rs
index 7fc674c62..26866b0bf 100644
--- a/cros_async/src/poll_source.rs
+++ b/cros_async/src/poll_source.rs
@@ -5,19 +5,27 @@
//! A wrapped IO source that uses FdExecutor to drive asynchronous completion. Used from
//! `IoSourceExt::new` when uring isn't available in the kernel.
-use async_trait::async_trait;
-use std::ops::{Deref, DerefMut};
-use std::os::unix::io::AsRawFd;
-use std::sync::Arc;
+use std::{
+ io,
+ ops::{Deref, DerefMut},
+ os::unix::io::AsRawFd,
+ sync::Arc,
+};
+use async_trait::async_trait;
+use data_model::VolatileSlice;
+use remain::sorted;
use thiserror::Error as ThisError;
-use crate::fd_executor::{self, FdExecutor, RegisteredSource};
-use crate::mem::{BackingMemory, MemRegion};
-use crate::{AsyncError, AsyncResult};
-use crate::{IoSourceExt, ReadAsync, WriteAsync};
-use data_model::VolatileSlice;
+use super::{
+ fd_executor::{
+ FdExecutor, RegisteredSource, {self},
+ },
+ mem::{BackingMemory, MemRegion},
+ AsyncError, AsyncResult, IoSourceExt, ReadAsync, WriteAsync,
+};
+#[sorted]
#[derive(ThisError, Debug)]
pub enum Error {
/// An error occurred attempting to register a waker with the executor.
@@ -28,22 +36,37 @@ pub enum Error {
Executor(fd_executor::Error),
/// An error occurred when executing fallocate synchronously.
#[error("An error occurred when executing fallocate synchronously: {0}")]
- Fallocate(sys_util::Error),
+ Fallocate(base::Error),
/// An error occurred when executing fsync synchronously.
#[error("An error occurred when executing fsync synchronously: {0}")]
- Fsync(sys_util::Error),
+ Fsync(base::Error),
/// An error occurred when reading the FD.
#[error("An error occurred when reading the FD: {0}.")]
- Read(sys_util::Error),
+ Read(base::Error),
/// Can't seek file.
#[error("An error occurred when seeking the FD: {0}.")]
- Seeking(sys_util::Error),
+ Seeking(base::Error),
/// An error occurred when writing the FD.
#[error("An error occurred when writing the FD: {0}.")]
- Write(sys_util::Error),
+ Write(base::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ use Error::*;
+ match e {
+ AddingWaker(e) => e.into(),
+ Executor(e) => e.into(),
+ Fallocate(e) => e.into(),
+ Fsync(e) => e.into(),
+ Read(e) => e.into(),
+ Seeking(e) => e.into(),
+ Write(e) => e.into(),
+ }
+ }
+}
+
/// Async wrapper for an IO source that uses the FD executor to drive async operations.
/// Used by `IoSourceExt::new` when uring isn't available.
pub struct PollSource<F>(RegisteredSource<F>);
@@ -81,25 +104,35 @@ impl<F: AsRawFd> ReadAsync for PollSource<F> {
/// Reads from the iosource at `file_offset` and fill the given `vec`.
async fn read_to_vec<'a>(
&'a self,
- file_offset: u64,
+ file_offset: Option<u64>,
mut vec: Vec<u8>,
) -> AsyncResult<(usize, Vec<u8>)> {
loop {
// Safe because this will only modify `vec` and we check the return value.
- let res = unsafe {
- libc::pread64(
- self.as_raw_fd(),
- vec.as_mut_ptr() as *mut libc::c_void,
- vec.len(),
- file_offset as libc::off64_t,
- )
+ let res = if let Some(offset) = file_offset {
+ unsafe {
+ libc::pread64(
+ self.as_raw_fd(),
+ vec.as_mut_ptr() as *mut libc::c_void,
+ vec.len(),
+ offset as libc::off64_t,
+ )
+ }
+ } else {
+ unsafe {
+ libc::read(
+ self.as_raw_fd(),
+ vec.as_mut_ptr() as *mut libc::c_void,
+ vec.len(),
+ )
+ }
};
if res >= 0 {
return Ok((res as usize, vec));
}
- match sys_util::Error::last() {
+ match base::Error::last() {
e if e.errno() == libc::EWOULDBLOCK => {
let op = self.0.wait_readable().map_err(Error::AddingWaker)?;
op.await.map_err(Error::Executor)?;
@@ -112,7 +145,7 @@ impl<F: AsRawFd> ReadAsync for PollSource<F> {
/// Reads to the given `mem` at the given offsets from the file starting at `file_offset`.
async fn read_to_mem<'a>(
&'a self,
- file_offset: u64,
+ file_offset: Option<u64>,
mem: Arc<dyn BackingMemory + Send + Sync>,
mem_offsets: &'a [MemRegion],
) -> AsyncResult<usize> {
@@ -124,20 +157,30 @@ impl<F: AsRawFd> ReadAsync for PollSource<F> {
loop {
// Safe because we trust the kernel not to write path the length given and the length is
// guaranteed to be valid from the pointer by io_slice_mut.
- let res = unsafe {
- libc::preadv64(
- self.as_raw_fd(),
- iovecs.as_mut_ptr() as *mut _,
- iovecs.len() as i32,
- file_offset as libc::off64_t,
- )
+ let res = if let Some(offset) = file_offset {
+ unsafe {
+ libc::preadv64(
+ self.as_raw_fd(),
+ iovecs.as_mut_ptr() as *mut _,
+ iovecs.len() as i32,
+ offset as libc::off64_t,
+ )
+ }
+ } else {
+ unsafe {
+ libc::readv(
+ self.as_raw_fd(),
+ iovecs.as_mut_ptr() as *mut _,
+ iovecs.len() as i32,
+ )
+ }
};
if res >= 0 {
return Ok(res as usize);
}
- match sys_util::Error::last() {
+ match base::Error::last() {
e if e.errno() == libc::EWOULDBLOCK => {
let op = self.0.wait_readable().map_err(Error::AddingWaker)?;
op.await.map_err(Error::Executor)?;
@@ -170,7 +213,7 @@ impl<F: AsRawFd> ReadAsync for PollSource<F> {
return Ok(u64::from_ne_bytes(buf));
}
- match sys_util::Error::last() {
+ match base::Error::last() {
e if e.errno() == libc::EWOULDBLOCK => {
let op = self.0.wait_readable().map_err(Error::AddingWaker)?;
op.await.map_err(Error::Executor)?;
@@ -186,25 +229,35 @@ impl<F: AsRawFd> WriteAsync for PollSource<F> {
/// Writes from the given `vec` to the file starting at `file_offset`.
async fn write_from_vec<'a>(
&'a self,
- file_offset: u64,
+ file_offset: Option<u64>,
vec: Vec<u8>,
) -> AsyncResult<(usize, Vec<u8>)> {
loop {
// Safe because this will not modify any memory and we check the return value.
- let res = unsafe {
- libc::pwrite64(
- self.as_raw_fd(),
- vec.as_ptr() as *const libc::c_void,
- vec.len(),
- file_offset as libc::off64_t,
- )
+ let res = if let Some(offset) = file_offset {
+ unsafe {
+ libc::pwrite64(
+ self.as_raw_fd(),
+ vec.as_ptr() as *const libc::c_void,
+ vec.len(),
+ offset as libc::off64_t,
+ )
+ }
+ } else {
+ unsafe {
+ libc::write(
+ self.as_raw_fd(),
+ vec.as_ptr() as *const libc::c_void,
+ vec.len(),
+ )
+ }
};
if res >= 0 {
return Ok((res as usize, vec));
}
- match sys_util::Error::last() {
+ match base::Error::last() {
e if e.errno() == libc::EWOULDBLOCK => {
let op = self.0.wait_writable().map_err(Error::AddingWaker)?;
op.await.map_err(Error::Executor)?;
@@ -217,7 +270,7 @@ impl<F: AsRawFd> WriteAsync for PollSource<F> {
/// Writes from the given `mem` from the given offsets to the file starting at `file_offset`.
async fn write_from_mem<'a>(
&'a self,
- file_offset: u64,
+ file_offset: Option<u64>,
mem: Arc<dyn BackingMemory + Send + Sync>,
mem_offsets: &'a [MemRegion],
) -> AsyncResult<usize> {
@@ -230,20 +283,30 @@ impl<F: AsRawFd> WriteAsync for PollSource<F> {
loop {
// Safe because we trust the kernel not to write path the length given and the length is
// guaranteed to be valid from the pointer by io_slice_mut.
- let res = unsafe {
- libc::pwritev64(
- self.as_raw_fd(),
- iovecs.as_ptr() as *mut _,
- iovecs.len() as i32,
- file_offset as libc::off64_t,
- )
+ let res = if let Some(offset) = file_offset {
+ unsafe {
+ libc::pwritev64(
+ self.as_raw_fd(),
+ iovecs.as_ptr() as *mut _,
+ iovecs.len() as i32,
+ offset as libc::off64_t,
+ )
+ }
+ } else {
+ unsafe {
+ libc::writev(
+ self.as_raw_fd(),
+ iovecs.as_ptr() as *mut _,
+ iovecs.len() as i32,
+ )
+ }
};
if res >= 0 {
return Ok(res as usize);
}
- match sys_util::Error::last() {
+ match base::Error::last() {
e if e.errno() == libc::EWOULDBLOCK => {
let op = self.0.wait_writable().map_err(Error::AddingWaker)?;
op.await.map_err(Error::Executor)?;
@@ -266,7 +329,7 @@ impl<F: AsRawFd> WriteAsync for PollSource<F> {
if ret == 0 {
Ok(())
} else {
- Err(AsyncError::Poll(Error::Fallocate(sys_util::Error::last())))
+ Err(AsyncError::Poll(Error::Fallocate(base::Error::last())))
}
}
@@ -276,7 +339,7 @@ impl<F: AsRawFd> WriteAsync for PollSource<F> {
if ret == 0 {
Ok(())
} else {
- Err(AsyncError::Poll(Error::Fsync(sys_util::Error::last())))
+ Err(AsyncError::Poll(Error::Fsync(base::Error::last())))
}
}
}
@@ -301,8 +364,10 @@ impl<F: AsRawFd> IoSourceExt<F> for PollSource<F> {
#[cfg(test)]
mod tests {
- use std::fs::{File, OpenOptions};
- use std::path::PathBuf;
+ use std::{
+ fs::{File, OpenOptions},
+ path::PathBuf,
+ };
use super::*;
@@ -313,7 +378,7 @@ mod tests {
let async_source = PollSource::new(f, ex).unwrap();
let v = vec![0x55u8; 32];
let v_ptr = v.as_ptr();
- let ret = async_source.read_to_vec(0, v).await.unwrap();
+ let ret = async_source.read_to_vec(None, v).await.unwrap();
assert_eq!(ret.0, 32);
let ret_v = ret.1;
assert_eq!(v_ptr, ret_v.as_ptr());
@@ -331,7 +396,7 @@ mod tests {
let async_source = PollSource::new(f, ex).unwrap();
let v = vec![0x55u8; 32];
let v_ptr = v.as_ptr();
- let ret = async_source.write_from_vec(0, v).await.unwrap();
+ let ret = async_source.write_from_vec(None, v).await.unwrap();
assert_eq!(ret.0, 32);
let ret_v = ret.1;
assert_eq!(v_ptr, ret_v.as_ptr());
@@ -372,7 +437,7 @@ mod tests {
let _ = source.wait_readable().await;
}
- let (rx, _tx) = sys_util::pipe(true).unwrap();
+ let (rx, _tx) = base::pipe(true).unwrap();
let ex = FdExecutor::new().unwrap();
let source = PollSource::new(rx, &ex).unwrap();
ex.spawn_local(owns_poll_source(source)).detach();
diff --git a/cros_async/src/queue.rs b/cros_async/src/queue.rs
index 3192703a5..f95de0629 100644
--- a/cros_async/src/queue.rs
+++ b/cros_async/src/queue.rs
@@ -38,6 +38,12 @@ impl RunnableQueue {
}
}
+impl Default for RunnableQueue {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
impl<'q> IntoIterator for &'q RunnableQueue {
type Item = Runnable;
type IntoIter = RunnableQueueIter<'q>;
diff --git a/cros_async/src/select.rs b/cros_async/src/select.rs
index 903ae2064..acf83e768 100644
--- a/cros_async/src/select.rs
+++ b/cros_async/src/select.rs
@@ -5,9 +5,11 @@
// Need non-snake case so the macro can re-use type names for variables.
#![allow(non_snake_case)]
-use std::future::Future;
-use std::pin::Pin;
-use std::task::{Context, Poll};
+use std::{
+ future::Future,
+ pin::Pin,
+ task::{Context, Poll},
+};
use futures::future::{maybe_done, FutureExt, MaybeDone};
@@ -24,7 +26,7 @@ macro_rules! generate {
$(#[$doc:meta])*
($Select:ident, <$($Fut:ident),*>),
)*) => ($(
- #[must_use = "Combinations of futures don't do anything unless run in an executor."]
+
paste::item! {
pub(crate) struct $Select<$($Fut: Future + Unpin),*> {
$($Fut: MaybeDone<$Fut>,)*
@@ -84,4 +86,7 @@ generate! {
/// _Future for the [`select6`] function.
(Select6, <_Fut1, _Fut2, _Fut3, _Fut4, _Fut5, _Fut6>),
+
+ /// _Future for the [`select7`] function.
+ (Select7, <_Fut1, _Fut2, _Fut3, _Fut4, _Fut5, _Fut6, _Fut7>),
}
diff --git a/cros_async/src/sync.rs b/cros_async/src/sync.rs
index 80dec3858..e2c1a1452 100644
--- a/cros_async/src/sync.rs
+++ b/cros_async/src/sync.rs
@@ -2,13 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-mod blocking;
mod cv;
mod mu;
mod spin;
mod waiter;
-pub use blocking::block_on;
pub use cv::Condvar;
pub use mu::Mutex;
pub use spin::SpinLock;
diff --git a/cros_async/src/sync/cv.rs b/cros_async/src/sync/cv.rs
index 4da0a12ef..46b96dd18 100644
--- a/cros_async/src/sync/cv.rs
+++ b/cros_async/src/sync/cv.rs
@@ -2,13 +2,19 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::cell::UnsafeCell;
-use std::mem;
-use std::sync::atomic::{spin_loop_hint, AtomicUsize, Ordering};
-use std::sync::Arc;
-
-use crate::sync::mu::{MutexGuard, MutexReadGuard, RawMutex};
-use crate::sync::waiter::{Kind as WaiterKind, Waiter, WaiterAdapter, WaiterList, WaitingFor};
+use std::{
+ cell::UnsafeCell,
+ hint, mem,
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc,
+ },
+};
+
+use super::super::sync::{
+ mu::{MutexGuard, MutexReadGuard, RawMutex},
+ waiter::{Kind as WaiterKind, Waiter, WaiterAdapter, WaiterList, WaitingFor},
+};
const SPINLOCK: usize = 1 << 0;
const HAS_WAITERS: usize = 1 << 1;
@@ -27,7 +33,10 @@ const HAS_WAITERS: usize = 1 << 1;
/// use std::thread;
/// use std::sync::mpsc::channel;
///
-/// use cros_async::sync::{block_on, Condvar, Mutex};
+/// use cros_async::{
+/// block_on,
+/// sync::{Condvar, Mutex},
+/// };
///
/// const N: usize = 13;
///
@@ -92,7 +101,10 @@ impl Condvar {
/// # use std::sync::Arc;
/// # use std::thread;
///
- /// # use cros_async::sync::{block_on, Condvar, Mutex};
+ /// # use cros_async::{
+ /// # block_on,
+ /// # sync::{Condvar, Mutex},
+ /// # };
///
/// # let mu = Arc::new(Mutex::new(false));
/// # let cv = Arc::new(Condvar::new());
@@ -171,7 +183,7 @@ impl Condvar {
)
.is_err()
{
- spin_loop_hint();
+ hint::spin_loop();
oldstate = self.state.load(Ordering::Relaxed);
}
@@ -223,7 +235,7 @@ impl Condvar {
)
.is_err()
{
- spin_loop_hint();
+ hint::spin_loop();
oldstate = self.state.load(Ordering::Relaxed);
}
@@ -281,7 +293,7 @@ impl Condvar {
)
.is_err()
{
- spin_loop_hint();
+ hint::spin_loop();
oldstate = self.state.load(Ordering::Relaxed);
}
@@ -319,7 +331,7 @@ impl Condvar {
)
.is_err()
{
- spin_loop_hint();
+ hint::spin_loop();
oldstate = self.state.load(Ordering::Relaxed);
}
@@ -441,23 +453,31 @@ fn cancel_waiter(cv: usize, waiter: &Waiter, wake_next: bool) {
mod test {
use super::*;
- use std::future::Future;
- use std::mem;
- use std::ptr;
- use std::rc::Rc;
- use std::sync::mpsc::{channel, Sender};
- use std::sync::Arc;
- use std::task::{Context, Poll};
- use std::thread::{self, JoinHandle};
- use std::time::Duration;
-
- use futures::channel::oneshot;
- use futures::task::{waker_ref, ArcWake};
- use futures::{select, FutureExt};
+ use std::{
+ future::Future,
+ mem, ptr,
+ rc::Rc,
+ sync::{
+ mpsc::{channel, Sender},
+ Arc,
+ },
+ task::{Context, Poll},
+ thread::{
+ JoinHandle, {self},
+ },
+ time::Duration,
+ };
+
+ use futures::{
+ channel::oneshot,
+ select,
+ task::{waker_ref, ArcWake},
+ FutureExt,
+ };
use futures_executor::{LocalPool, LocalSpawner, ThreadPool};
use futures_util::task::LocalSpawnExt;
- use crate::sync::{block_on, Mutex};
+ use super::super::super::{block_on, sync::Mutex};
// Dummy waker used when we want to manually drive futures.
struct TestWaker;
diff --git a/cros_async/src/sync/mu.rs b/cros_async/src/sync/mu.rs
index 4f3443fb5..e1f408dfa 100644
--- a/cros_async/src/sync/mu.rs
+++ b/cros_async/src/sync/mu.rs
@@ -2,14 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::cell::UnsafeCell;
-use std::mem;
-use std::ops::{Deref, DerefMut};
-use std::sync::atomic::{spin_loop_hint, AtomicUsize, Ordering};
-use std::sync::Arc;
-use std::thread::yield_now;
-
-use crate::sync::waiter::{Kind as WaiterKind, Waiter, WaiterAdapter, WaiterList, WaitingFor};
+use std::{
+ cell::UnsafeCell,
+ hint, mem,
+ ops::{Deref, DerefMut},
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc,
+ },
+ thread::yield_now,
+};
+
+use super::super::sync::waiter::{
+ Kind as WaiterKind, Waiter, WaiterAdapter, WaiterList, WaitingFor,
+};
// Set when the mutex is exclusively locked.
const LOCKED: usize = 1 << 0;
@@ -182,7 +188,7 @@ fn get_wake_list(waiters: &mut WaiterList) -> (WaiterList, usize) {
#[inline]
fn cpu_relax(iterations: usize) {
for _ in 0..iterations {
- spin_loop_hint();
+ hint::spin_loop();
}
}
@@ -518,7 +524,7 @@ impl RawMutex {
)
.is_err()
{
- spin_loop_hint();
+ hint::spin_loop();
oldstate = self.state.load(Ordering::Relaxed);
}
@@ -593,7 +599,7 @@ impl RawMutex {
)
.is_err()
{
- spin_loop_hint();
+ hint::spin_loop();
oldstate = self.state.load(Ordering::Relaxed);
}
@@ -650,7 +656,7 @@ fn cancel_waiter(raw: usize, waiter: &Waiter, wake_next: bool) {
/// use std::thread;
/// use std::sync::mpsc::channel;
///
-/// use cros_async::sync::{block_on, Mutex};
+/// use cros_async::{block_on, sync::Mutex};
///
/// const N: usize = 10;
///
@@ -881,24 +887,34 @@ impl<'a, T: ?Sized> Drop for MutexReadGuard<'a, T> {
mod test {
use super::*;
- use std::future::Future;
- use std::mem;
- use std::pin::Pin;
- use std::rc::Rc;
- use std::sync::atomic::{AtomicUsize, Ordering};
- use std::sync::mpsc::{channel, Sender};
- use std::sync::Arc;
- use std::task::{Context, Poll, Waker};
- use std::thread;
- use std::time::Duration;
-
- use futures::channel::oneshot;
- use futures::task::{waker_ref, ArcWake};
- use futures::{pending, select, FutureExt};
+ use std::{
+ future::Future,
+ mem,
+ pin::Pin,
+ rc::Rc,
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ mpsc::{channel, Sender},
+ Arc,
+ },
+ task::{Context, Poll, Waker},
+ thread,
+ time::Duration,
+ };
+
+ use futures::{
+ channel::oneshot,
+ pending, select,
+ task::{waker_ref, ArcWake},
+ FutureExt,
+ };
use futures_executor::{LocalPool, ThreadPool};
use futures_util::task::LocalSpawnExt;
- use crate::sync::{block_on, Condvar, SpinLock};
+ use super::super::super::{
+ block_on,
+ sync::{Condvar, SpinLock},
+ };
#[derive(Debug, Eq, PartialEq)]
struct NonCopy(u32);
diff --git a/cros_async/src/sync/spin.rs b/cros_async/src/sync/spin.rs
index 8b7b81193..7faec6dc2 100644
--- a/cros_async/src/sync/spin.rs
+++ b/cros_async/src/sync/spin.rs
@@ -2,9 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::cell::UnsafeCell;
-use std::ops::{Deref, DerefMut};
-use std::sync::atomic::{spin_loop_hint, AtomicBool, Ordering};
+use std::{
+ cell::UnsafeCell,
+ hint,
+ ops::{Deref, DerefMut},
+ sync::atomic::{AtomicBool, Ordering},
+};
const UNLOCKED: bool = false;
const LOCKED: bool = true;
@@ -64,7 +67,7 @@ impl<T: ?Sized> SpinLock<T> {
{
break;
}
- spin_loop_hint();
+ hint::spin_loop();
}
SpinLockGuard {
@@ -133,10 +136,14 @@ impl<'a, T: ?Sized> Drop for SpinLockGuard<'a, T> {
mod test {
use super::*;
- use std::mem;
- use std::sync::atomic::{AtomicUsize, Ordering};
- use std::sync::Arc;
- use std::thread;
+ use std::{
+ mem,
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc,
+ },
+ thread,
+ };
#[derive(PartialEq, Eq, Debug)]
struct NonCopy(u32);
diff --git a/cros_async/src/sync/waiter.rs b/cros_async/src/sync/waiter.rs
index 072a0f506..e161c623d 100644
--- a/cros_async/src/sync/waiter.rs
+++ b/cros_async/src/sync/waiter.rs
@@ -2,19 +2,26 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::cell::UnsafeCell;
-use std::future::Future;
-use std::mem;
-use std::pin::Pin;
-use std::ptr::NonNull;
-use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
-use std::sync::Arc;
-use std::task::{Context, Poll, Waker};
-
-use intrusive_collections::linked_list::{LinkedList, LinkedListOps};
-use intrusive_collections::{intrusive_adapter, DefaultLinkOps, LinkOps};
-
-use crate::sync::SpinLock;
+use std::{
+ cell::UnsafeCell,
+ future::Future,
+ mem,
+ pin::Pin,
+ ptr::NonNull,
+ sync::{
+ atomic::{AtomicBool, AtomicU8, Ordering},
+ Arc,
+ },
+ task::{Context, Poll, Waker},
+};
+
+use intrusive_collections::{
+ intrusive_adapter,
+ linked_list::{LinkedList, LinkedListOps},
+ DefaultLinkOps, LinkOps,
+};
+
+use super::super::sync::SpinLock;
// An atomic version of a LinkedListLink. See https://github.com/Amanieu/intrusive-rs/issues/47 for
// more details.
diff --git a/cros_async/src/timer.rs b/cros_async/src/timer.rs
index 7069c7b47..6ce3ff756 100644
--- a/cros_async/src/timer.rs
+++ b/cros_async/src/timer.rs
@@ -4,13 +4,14 @@
use std::time::Duration;
-use sys_util::{Result as SysResult, TimerFd};
+use base::{Result as SysResult, TimerFd};
+
+use super::{AsyncResult, Error, Executor, IntoAsync, IoSourceExt};
-use crate::{AsyncResult, Executor, IntoAsync, IoSourceExt};
#[cfg(test)]
-use crate::{FdExecutor, URingExecutor};
+use super::{FdExecutor, URingExecutor};
-/// An async version of sys_util::TimerFd.
+/// An async version of base::TimerFd.
pub struct TimerAsync {
io_source: Box<dyn IoSourceExt<TimerFd>>,
}
@@ -23,12 +24,12 @@ impl TimerAsync {
#[cfg(test)]
pub(crate) fn new_poll(timer: TimerFd, ex: &FdExecutor) -> AsyncResult<TimerAsync> {
- crate::executor::async_poll_from(timer, ex).map(|io_source| TimerAsync { io_source })
+ super::executor::async_poll_from(timer, ex).map(|io_source| TimerAsync { io_source })
}
#[cfg(test)]
pub(crate) fn new_uring(timer: TimerFd, ex: &URingExecutor) -> AsyncResult<TimerAsync> {
- crate::executor::async_uring_from(timer, ex).map(|io_source| TimerAsync { io_source })
+ super::executor::async_uring_from(timer, ex).map(|io_source| TimerAsync { io_source })
}
/// Gets the next value from the timer.
@@ -36,6 +37,15 @@ impl TimerAsync {
self.io_source.read_u64().await
}
+ /// Async sleep for the given duration
+ pub async fn sleep(ex: &Executor, dur: Duration) -> std::result::Result<(), Error> {
+ let tfd = TimerFd::new().map_err(Error::TimerFd)?;
+ tfd.reset(dur, None).map_err(Error::TimerFd)?;
+ let t = TimerAsync::new(tfd, ex).map_err(Error::TimerAsync)?;
+ t.next_val().await.map_err(Error::TimerAsync)?;
+ Ok(())
+ }
+
/// Sets the timer to expire after `dur`. If `interval` is not `None` it represents
/// the period for repeated expirations after the initial expiration. Otherwise
/// the timer will expire just once. Cancels any existing duration and repeating interval.
@@ -48,21 +58,22 @@ impl IntoAsync for TimerFd {}
#[cfg(test)]
mod tests {
- use super::*;
+ use super::{super::uring_executor::use_uring, *};
use std::time::{Duration, Instant};
#[test]
fn one_shot() {
- async fn this_test(ex: &URingExecutor) -> () {
+ if !use_uring() {
+ return;
+ }
+
+ async fn this_test(ex: &URingExecutor) {
let tfd = TimerFd::new().expect("failed to create timerfd");
- assert_eq!(tfd.is_armed().unwrap(), false);
let dur = Duration::from_millis(200);
let now = Instant::now();
tfd.reset(dur, None).expect("failed to arm timer");
- assert_eq!(tfd.is_armed().unwrap(), true);
-
let t = TimerAsync::new_uring(tfd, ex).unwrap();
let count = t.next_val().await.expect("unable to wait for timer");
@@ -76,16 +87,13 @@ mod tests {
#[test]
fn one_shot_fd() {
- async fn this_test(ex: &FdExecutor) -> () {
+ async fn this_test(ex: &FdExecutor) {
let tfd = TimerFd::new().expect("failed to create timerfd");
- assert_eq!(tfd.is_armed().unwrap(), false);
let dur = Duration::from_millis(200);
let now = Instant::now();
tfd.reset(dur, None).expect("failed to arm timer");
- assert_eq!(tfd.is_armed().unwrap(), true);
-
let t = TimerAsync::new_poll(tfd, ex).unwrap();
let count = t.next_val().await.expect("unable to wait for timer");
@@ -96,4 +104,17 @@ mod tests {
let ex = FdExecutor::new().unwrap();
ex.run_until(this_test(&ex)).unwrap();
}
+
+ #[test]
+ fn timer() {
+ async fn this_test(ex: &Executor) {
+ let dur = Duration::from_millis(200);
+ let now = Instant::now();
+ TimerAsync::sleep(ex, dur).await.expect("unable to sleep");
+ assert!(now.elapsed() >= dur);
+ }
+
+ let ex = Executor::new().expect("creating an executor failed");
+ ex.run_until(this_test(&ex)).unwrap();
+ }
}
diff --git a/cros_async/src/unix/async_types.rs b/cros_async/src/unix/async_types.rs
new file mode 100644
index 000000000..c1653c244
--- /dev/null
+++ b/cros_async/src/unix/async_types.rs
@@ -0,0 +1,42 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use crate::{Executor, IoSourceExt};
+use base::{Tube, TubeResult};
+use serde::{de::DeserializeOwned, Serialize};
+use std::io;
+use std::ops::Deref;
+
+pub struct AsyncTube {
+ inner: Box<dyn IoSourceExt<Tube>>,
+}
+
+impl AsyncTube {
+ pub fn new(ex: &Executor, tube: Tube) -> io::Result<AsyncTube> {
+ return Ok(AsyncTube {
+ inner: ex.async_from(tube)?,
+ });
+ }
+ pub async fn next<T: DeserializeOwned>(&self) -> TubeResult<T> {
+ self.inner.wait_readable().await.unwrap();
+ self.inner.as_source().recv()
+ }
+
+ pub async fn send<T: 'static + Serialize + Send + Sync>(&self, msg: T) -> TubeResult<()> {
+ self.inner.as_source().send(&msg)
+ }
+}
+
+impl Deref for AsyncTube {
+ type Target = Tube;
+
+ fn deref(&self) -> &Self::Target {
+ self.inner.as_source()
+ }
+}
+
+impl From<AsyncTube> for Tube {
+ fn from(at: AsyncTube) -> Tube {
+ at.inner.into_source()
+ }
+}
diff --git a/cros_async/src/uring_executor.rs b/cros_async/src/uring_executor.rs
index 39bce9581..743939b75 100644
--- a/cros_async/src/uring_executor.rs
+++ b/cros_async/src/uring_executor.rs
@@ -51,37 +51,54 @@
//! ensures that only the kernel is allowed to access the `Vec` and wraps the the `Vec` in an Arc to
//! ensure it lives long enough.
-use std::convert::TryInto;
-use std::fs::File;
-use std::future::Future;
-use std::io;
-use std::mem;
-use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
-use std::pin::Pin;
-use std::sync::atomic::{AtomicI32, AtomicU32, Ordering};
-use std::sync::{Arc, Weak};
-use std::task::Waker;
-use std::task::{Context, Poll};
-use std::thread::{self, ThreadId};
+use std::{
+ convert::TryInto,
+ ffi::CStr,
+ fs::File,
+ future::Future,
+ io,
+ mem::{
+ MaybeUninit, {self},
+ },
+ os::unix::io::{AsRawFd, FromRawFd, RawFd},
+ pin::Pin,
+ sync::{
+ atomic::{AtomicI32, Ordering},
+ Arc, Weak,
+ },
+ task::{Context, Poll, Waker},
+ thread::{
+ ThreadId, {self},
+ },
+};
use async_task::Task;
+use base::{warn, WatchingEvents};
use futures::task::noop_waker;
use io_uring::URingContext;
+use once_cell::sync::Lazy;
use pin_utils::pin_mut;
+use remain::sorted;
use slab::Slab;
use sync::Mutex;
-use sys_util::{warn, WatchingEvents};
use thiserror::Error as ThisError;
-use crate::mem::{BackingMemory, MemRegion};
-use crate::queue::RunnableQueue;
-use crate::waker::{new_waker, WakerToken, WeakWake};
+use super::{
+ mem::{BackingMemory, MemRegion},
+ queue::RunnableQueue,
+ waker::{new_waker, WakerToken, WeakWake},
+ BlockingPool,
+};
+#[sorted]
#[derive(Debug, ThisError)]
pub enum Error {
+ /// Creating a context to wait on FDs failed.
+ #[error("Error creating the fd waiting context: {0}")]
+ CreatingContext(io_uring::Error),
/// Failed to copy the FD for the polling context.
#[error("Failed to copy the FD for the polling context: {0}")]
- DuplicatingFd(sys_util::Error),
+ DuplicatingFd(base::Error),
/// The Executor is gone.
#[error("The URingExecutor is gone")]
ExecutorGone,
@@ -94,9 +111,6 @@ pub enum Error {
/// Error doing the IO.
#[error("Error during IO: {0}")]
Io(io::Error),
- /// Creating a context to wait on FDs failed.
- #[error("Error creating the fd waiting context: {0}")]
- CreatingContext(io_uring::Error),
/// Failed to remove the waker remove the polling context.
#[error("Error removing from the URing context: {0}")]
RemovingWaker(io_uring::Error),
@@ -112,29 +126,59 @@ pub enum Error {
}
pub type Result<T> = std::result::Result<T, Error>;
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ use Error::*;
+ match e {
+ DuplicatingFd(e) => e.into(),
+ ExecutorGone => io::Error::new(io::ErrorKind::Other, ExecutorGone),
+ InvalidOffset => io::Error::new(io::ErrorKind::InvalidInput, InvalidOffset),
+ InvalidSource => io::Error::new(io::ErrorKind::InvalidData, InvalidSource),
+ Io(e) => e,
+ CreatingContext(e) => e.into(),
+ RemovingWaker(e) => e.into(),
+ SubmittingOp(e) => e.into(),
+ URingContextError(e) => e.into(),
+ URingEnter(e) => e.into(),
+ }
+ }
+}
+
+static USE_URING: Lazy<bool> = Lazy::new(|| {
+ let mut utsname = MaybeUninit::zeroed();
+
+ // Safe because this will only modify `utsname` and we check the return value.
+ let res = unsafe { libc::uname(utsname.as_mut_ptr()) };
+ if res < 0 {
+ return false;
+ }
+
+ // Safe because the kernel has initialized `utsname`.
+ let utsname = unsafe { utsname.assume_init() };
+
+ // Safe because the pointer is valid and the kernel guarantees that this is a valid C string.
+ let release = unsafe { CStr::from_ptr(utsname.release.as_ptr()) };
+
+ let mut components = match release.to_str().map(|r| r.split('.').map(str::parse)) {
+ Ok(c) => c,
+ Err(_) => return false,
+ };
+
+ // Kernels older than 5.10 either didn't support io_uring or had bugs in the implementation.
+ match (components.next(), components.next()) {
+ (Some(Ok(major)), Some(Ok(minor))) if (major, minor) >= (5, 10) => {
+ // The kernel version is new enough so check if we can actually make a uring context.
+ URingContext::new(8).is_ok()
+ }
+ _ => false,
+ }
+});
+
// Checks if the uring executor is available.
// Caches the result so that the check is only run once.
// Useful for falling back to the FD executor on pre-uring kernels.
pub(crate) fn use_uring() -> bool {
- const UNKNOWN: u32 = 0;
- const URING: u32 = 1;
- const FD: u32 = 2;
- static USE_URING: AtomicU32 = AtomicU32::new(UNKNOWN);
- match USE_URING.load(Ordering::Relaxed) {
- UNKNOWN => {
- // Create a dummy uring context to check that the kernel understands the syscalls.
- if URingContext::new(8).is_ok() {
- USE_URING.store(URING, Ordering::Relaxed);
- true
- } else {
- USE_URING.store(FD, Ordering::Relaxed);
- false
- }
- }
- URING => true,
- FD => false,
- _ => unreachable!("invalid use uring state"),
- }
+ *USE_URING
}
pub struct RegisteredSource {
@@ -259,6 +303,7 @@ struct RawExecutor {
ctx: URingContext,
queue: RunnableQueue,
ring: Mutex<Ring>,
+ blocking_pool: BlockingPool,
thread_id: Mutex<Option<ThreadId>>,
state: AtomicI32,
}
@@ -272,6 +317,7 @@ impl RawExecutor {
ops: Slab::with_capacity(NUM_ENTRIES),
registered_sources: Slab::with_capacity(NUM_ENTRIES),
}),
+ blocking_pool: Default::default(),
thread_id: Mutex::new(None),
state: AtomicI32::new(PROCESSING),
})
@@ -333,6 +379,14 @@ impl RawExecutor {
task
}
+ fn spawn_blocking<F, R>(self: &Arc<Self>, f: F) -> Task<R>
+ where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+ {
+ self.blocking_pool.spawn(f)
+ }
+
fn runs_tasks_on_current_thread(&self) -> bool {
let executor_thread = self.thread_id.lock();
executor_thread
@@ -475,7 +529,7 @@ impl RawExecutor {
fn submit_poll(
&self,
source: &RegisteredSource,
- events: &sys_util::WatchingEvents,
+ events: &base::WatchingEvents,
) -> Result<WakerToken> {
let mut ring = self.ring.lock();
let src = ring
@@ -584,9 +638,12 @@ impl RawExecutor {
// The addresses have already been validated, so unwrapping them will succeed.
// validate their addresses before submitting.
- let iovecs = addrs
- .iter()
- .map(|&mem_range| *mem.get_volatile_slice(mem_range).unwrap().as_iobuf());
+ let iovecs = addrs.iter().map(|&mem_range| {
+ *mem.get_volatile_slice(mem_range)
+ .unwrap()
+ .as_iobuf()
+ .as_ref()
+ });
unsafe {
// Safe because all the addresses are within the Memory that an Arc is kept for the
@@ -634,9 +691,12 @@ impl RawExecutor {
// The addresses have already been validated, so unwrapping them will succeed.
// validate their addresses before submitting.
- let iovecs = addrs
- .iter()
- .map(|&mem_range| *mem.get_volatile_slice(mem_range).unwrap().as_iobuf());
+ let iovecs = addrs.iter().map(|&mem_range| {
+ *mem.get_volatile_slice(mem_range)
+ .unwrap()
+ .as_iobuf()
+ .as_ref()
+ });
unsafe {
// Safe because all the addresses are within the Memory that an Arc is kept for the
@@ -735,11 +795,19 @@ impl URingExecutor {
self.raw.spawn_local(f)
}
+ pub fn spawn_blocking<F, R>(&self, f: F) -> Task<R>
+ where
+ F: FnOnce() -> R + Send + 'static,
+ R: Send + 'static,
+ {
+ self.raw.spawn_blocking(f)
+ }
+
pub fn run(&self) -> Result<()> {
let waker = new_waker(Arc::downgrade(&self.raw));
let mut cx = Context::from_waker(&waker);
- self.raw.run(&mut cx, crate::empty::<()>())
+ self.raw.run(&mut cx, super::empty::<()>())
}
pub fn run_until<F: Future>(&self, f: F) -> Result<F::Output> {
@@ -768,7 +836,7 @@ impl URingExecutor {
unsafe fn dup_fd(fd: RawFd) -> Result<RawFd> {
let ret = libc::fcntl(fd, libc::F_DUPFD_CLOEXEC, 0);
if ret < 0 {
- Err(Error::DuplicatingFd(sys_util::Error::last()))
+ Err(Error::DuplicatingFd(base::Error::last()))
} else {
Ok(ret)
}
@@ -832,16 +900,20 @@ impl Drop for PendingOperation {
#[cfg(test)]
mod tests {
- use std::future::Future;
- use std::io::{Read, Write};
- use std::mem;
- use std::pin::Pin;
- use std::task::{Context, Poll};
+ use std::{
+ future::Future,
+ io::{Read, Write},
+ mem,
+ pin::Pin,
+ task::{Context, Poll},
+ };
use futures::executor::block_on;
- use super::*;
- use crate::mem::{BackingMemory, MemRegion, VecIoWrapper};
+ use super::{
+ super::mem::{BackingMemory, MemRegion, VecIoWrapper},
+ *,
+ };
// A future that returns ready when the uring queue is empty.
struct UringQueueEmpty<'a> {
@@ -862,13 +934,17 @@ mod tests {
#[test]
fn dont_drop_backing_mem_read() {
+ if !use_uring() {
+ return;
+ }
+
// Create a backing memory wrapped in an Arc and check that the drop isn't called while the
// op is pending.
let bm =
Arc::new(VecIoWrapper::from(vec![0u8; 4096])) as Arc<dyn BackingMemory + Send + Sync>;
// Use pipes to create a future that will block forever.
- let (rx, mut tx) = sys_util::pipe(true).unwrap();
+ let (rx, mut tx) = base::pipe(true).unwrap();
// Set up the TLS for the uring_executor by creating one.
let ex = URingExecutor::new().unwrap();
@@ -902,13 +978,17 @@ mod tests {
#[test]
fn dont_drop_backing_mem_write() {
+ if !use_uring() {
+ return;
+ }
+
// Create a backing memory wrapped in an Arc and check that the drop isn't called while the
// op is pending.
let bm =
Arc::new(VecIoWrapper::from(vec![0u8; 4096])) as Arc<dyn BackingMemory + Send + Sync>;
// Use pipes to create a future that will block forever.
- let (mut rx, tx) = sys_util::new_pipe_full().expect("Pipe failed");
+ let (mut rx, tx) = base::new_pipe_full().expect("Pipe failed");
// Set up the TLS for the uring_executor by creating one.
let ex = URingExecutor::new().unwrap();
@@ -934,7 +1014,7 @@ mod tests {
// Finishing the operation should put the Arc count back to 1.
// write to the pipe to wake the read pipe and then wait for the uring result in the
// executor.
- let mut buf = vec![0u8; sys_util::round_up_to_page_size(1)];
+ let mut buf = vec![0u8; base::round_up_to_page_size(1)];
rx.read_exact(&mut buf).expect("read to empty failed");
ex.run_until(UringQueueEmpty { ex: &ex })
.expect("Failed to wait for write pipe ready");
@@ -943,6 +1023,10 @@ mod tests {
#[test]
fn canceled_before_completion() {
+ if !use_uring() {
+ return;
+ }
+
async fn cancel_io(op: PendingOperation) {
mem::drop(op);
}
@@ -955,7 +1039,7 @@ mod tests {
let bm =
Arc::new(VecIoWrapper::from(vec![0u8; 16])) as Arc<dyn BackingMemory + Send + Sync>;
- let (rx, tx) = sys_util::pipe(true).expect("Pipe failed");
+ let (rx, tx) = base::pipe(true).expect("Pipe failed");
let ex = URingExecutor::new().unwrap();
@@ -981,6 +1065,10 @@ mod tests {
#[test]
fn drop_before_completion() {
+ if !use_uring() {
+ return;
+ }
+
const VALUE: u64 = 0xef6c_a8df_b842_eb9c;
async fn check_op(op: PendingOperation) {
@@ -991,7 +1079,7 @@ mod tests {
}
}
- let (mut rx, mut tx) = sys_util::pipe(true).expect("Pipe failed");
+ let (mut rx, mut tx) = base::pipe(true).expect("Pipe failed");
let ex = URingExecutor::new().unwrap();
@@ -1026,6 +1114,10 @@ mod tests {
#[test]
fn drop_on_different_thread() {
+ if !use_uring() {
+ return;
+ }
+
let ex = URingExecutor::new().unwrap();
let ex2 = ex.clone();
@@ -1035,7 +1127,7 @@ mod tests {
// Leave an uncompleted operation in the queue so that the drop impl will try to drive it to
// completion.
- let (_rx, tx) = sys_util::pipe(true).expect("Pipe failed");
+ let (_rx, tx) = base::pipe(true).expect("Pipe failed");
let tx = ex.register_source(&tx).expect("Failed to register source");
let bm = Arc::new(VecIoWrapper::from(0xf2e96u64.to_ne_bytes().to_vec()));
let op = tx
diff --git a/cros_async/src/uring_source.rs b/cros_async/src/uring_source.rs
index fa1b3c2c6..9d8525eae 100644
--- a/cros_async/src/uring_source.rs
+++ b/cros_async/src/uring_source.rs
@@ -2,42 +2,25 @@
// 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::io;
-use std::ops::{Deref, DerefMut};
-use std::os::unix::io::AsRawFd;
-use std::sync::Arc;
+use std::{
+ convert::TryInto,
+ io,
+ ops::{Deref, DerefMut},
+ os::unix::io::AsRawFd,
+ sync::Arc,
+};
use async_trait::async_trait;
-use crate::mem::{BackingMemory, MemRegion, VecIoWrapper};
-use crate::uring_executor::{Error, RegisteredSource, Result, URingExecutor};
-use crate::AsyncError;
-use crate::AsyncResult;
+use super::{
+ mem::{BackingMemory, MemRegion, VecIoWrapper},
+ uring_executor::{Error, RegisteredSource, Result, URingExecutor},
+ AsyncError, AsyncResult,
+};
/// `UringSource` wraps FD backed IO sources for use with io_uring. It is a thin wrapper around
/// registering an IO source with the uring that provides an `IoSource` implementation.
/// Most useful functions are provided by 'IoSourceExt'.
-///
-/// # Example
-/// ```rust
-/// use std::fs::File;
-/// use cros_async::{UringSource, ReadAsync, URingExecutor};
-///
-/// async fn read_four_bytes(source: &UringSource<File>) -> (usize, Vec<u8>) {
-/// let mem = vec![0u8; 4];
-/// source.read_to_vec(0, mem).await.unwrap()
-/// }
-///
-/// fn read_file(f: File) -> Result<(), Box<dyn std::error::Error>> {
-/// let ex = URingExecutor::new()?;
-/// let async_source = UringSource::new(f, &ex)?;
-/// let (nread, vec) = ex.run_until(read_four_bytes(&async_source))?;
-/// assert_eq!(nread, 4);
-/// assert_eq!(vec.len(), 4);
-/// Ok(())
-/// }
-/// ```
pub struct UringSource<F: AsRawFd> {
registered_source: RegisteredSource,
source: F,
@@ -60,16 +43,16 @@ impl<F: AsRawFd> UringSource<F> {
}
#[async_trait(?Send)]
-impl<F: AsRawFd> crate::ReadAsync for UringSource<F> {
+impl<F: AsRawFd> super::ReadAsync for UringSource<F> {
/// Reads from the iosource at `file_offset` and fill the given `vec`.
async fn read_to_vec<'a>(
&'a self,
- file_offset: u64,
+ file_offset: Option<u64>,
vec: Vec<u8>,
) -> AsyncResult<(usize, Vec<u8>)> {
let buf = Arc::new(VecIoWrapper::from(vec));
let op = self.registered_source.start_read_to_mem(
- file_offset,
+ file_offset.unwrap_or(0),
buf.clone(),
&[MemRegion {
offset: 0,
@@ -127,29 +110,29 @@ impl<F: AsRawFd> crate::ReadAsync for UringSource<F> {
/// Reads to the given `mem` at the given offsets from the file starting at `file_offset`.
async fn read_to_mem<'a>(
&'a self,
- file_offset: u64,
+ file_offset: Option<u64>,
mem: Arc<dyn BackingMemory + Send + Sync>,
mem_offsets: &'a [MemRegion],
) -> AsyncResult<usize> {
- let op = self
- .registered_source
- .start_read_to_mem(file_offset, mem, mem_offsets)?;
+ let op =
+ self.registered_source
+ .start_read_to_mem(file_offset.unwrap_or(0), mem, mem_offsets)?;
let len = op.await?;
Ok(len as usize)
}
}
#[async_trait(?Send)]
-impl<F: AsRawFd> crate::WriteAsync for UringSource<F> {
+impl<F: AsRawFd> super::WriteAsync for UringSource<F> {
/// Writes from the given `vec` to the file starting at `file_offset`.
async fn write_from_vec<'a>(
&'a self,
- file_offset: u64,
+ file_offset: Option<u64>,
vec: Vec<u8>,
) -> AsyncResult<(usize, Vec<u8>)> {
let buf = Arc::new(VecIoWrapper::from(vec));
let op = self.registered_source.start_write_from_mem(
- file_offset,
+ file_offset.unwrap_or(0),
buf.clone(),
&[MemRegion {
offset: 0,
@@ -169,13 +152,15 @@ impl<F: AsRawFd> crate::WriteAsync for UringSource<F> {
/// Writes from the given `mem` from the given offsets to the file starting at `file_offset`.
async fn write_from_mem<'a>(
&'a self,
- file_offset: u64,
+ file_offset: Option<u64>,
mem: Arc<dyn BackingMemory + Send + Sync>,
mem_offsets: &'a [MemRegion],
) -> AsyncResult<usize> {
- let op = self
- .registered_source
- .start_write_from_mem(file_offset, mem, mem_offsets)?;
+ let op = self.registered_source.start_write_from_mem(
+ file_offset.unwrap_or(0),
+ mem,
+ mem_offsets,
+ )?;
let len = op.await?;
Ok(len as usize)
}
@@ -198,7 +183,7 @@ impl<F: AsRawFd> crate::WriteAsync for UringSource<F> {
}
#[async_trait(?Send)]
-impl<F: AsRawFd> crate::IoSourceExt<F> for UringSource<F> {
+impl<F: AsRawFd> super::IoSourceExt<F> for UringSource<F> {
/// Yields the underlying IO source.
fn into_source(self: Box<Self>) -> F {
self.source
@@ -231,18 +216,27 @@ impl<F: AsRawFd> DerefMut for UringSource<F> {
#[cfg(test)]
mod tests {
- use std::fs::{File, OpenOptions};
- use std::os::unix::io::AsRawFd;
- use std::path::PathBuf;
-
- use crate::io_ext::{ReadAsync, WriteAsync};
- use crate::UringSource;
+ use std::{
+ fs::{File, OpenOptions},
+ os::unix::io::AsRawFd,
+ path::PathBuf,
+ };
+
+ use super::super::{
+ io_ext::{ReadAsync, WriteAsync},
+ uring_executor::use_uring,
+ UringSource,
+ };
use super::*;
#[test]
fn read_to_mem() {
- use crate::mem::VecIoWrapper;
+ if !use_uring() {
+ return;
+ }
+
+ use super::super::mem::VecIoWrapper;
use std::io::Write;
use tempfile::tempfile;
@@ -250,7 +244,7 @@ mod tests {
// Use guest memory as a test file, it implements AsRawFd.
let mut source = tempfile().unwrap();
let data = vec![0x55; 8192];
- source.write(&data).unwrap();
+ source.write_all(&data).unwrap();
let io_obj = UringSource::new(source, &ex).unwrap();
@@ -258,7 +252,7 @@ mod tests {
let buf: Arc<VecIoWrapper> = Arc::new(VecIoWrapper::from(vec![0x44; 8192]));
let fut = io_obj.read_to_mem(
- 0,
+ None,
Arc::<VecIoWrapper>::clone(&buf),
&[MemRegion {
offset: 0,
@@ -275,12 +269,16 @@ mod tests {
#[test]
fn readvec() {
+ if !use_uring() {
+ return;
+ }
+
async fn go(ex: &URingExecutor) {
let f = File::open("/dev/zero").unwrap();
let source = UringSource::new(f, ex).unwrap();
let v = vec![0x55u8; 32];
let v_ptr = v.as_ptr();
- let ret = source.read_to_vec(0, v).await.unwrap();
+ let ret = source.read_to_vec(None, v).await.unwrap();
assert_eq!(ret.0, 32);
let ret_v = ret.1;
assert_eq!(v_ptr, ret_v.as_ptr());
@@ -293,13 +291,20 @@ mod tests {
#[test]
fn readmulti() {
+ if !use_uring() {
+ return;
+ }
+
async fn go(ex: &URingExecutor) {
let f = File::open("/dev/zero").unwrap();
let source = UringSource::new(f, ex).unwrap();
let v = vec![0x55u8; 32];
let v2 = vec![0x55u8; 32];
- let (ret, ret2) =
- futures::future::join(source.read_to_vec(0, v), source.read_to_vec(32, v2)).await;
+ let (ret, ret2) = futures::future::join(
+ source.read_to_vec(None, v),
+ source.read_to_vec(Some(32), v2),
+ )
+ .await;
assert!(ret.unwrap().1.iter().all(|&b| b == 0));
assert!(ret2.unwrap().1.iter().all(|&b| b == 0));
@@ -312,7 +317,7 @@ mod tests {
async fn read_u64<T: AsRawFd>(source: &UringSource<T>) -> u64 {
// Init a vec that translates to u64::max;
let u64_mem = vec![0xffu8; std::mem::size_of::<u64>()];
- let (ret, u64_mem) = source.read_to_vec(0, u64_mem).await.unwrap();
+ let (ret, u64_mem) = source.read_to_vec(None, u64_mem).await.unwrap();
assert_eq!(ret as usize, std::mem::size_of::<u64>());
let mut val = 0u64.to_ne_bytes();
val.copy_from_slice(&u64_mem);
@@ -321,6 +326,10 @@ mod tests {
#[test]
fn u64_from_file() {
+ if !use_uring() {
+ return;
+ }
+
let f = File::open("/dev/zero").unwrap();
let ex = URingExecutor::new().unwrap();
let source = UringSource::new(f, &ex).unwrap();
@@ -330,7 +339,11 @@ mod tests {
#[test]
fn event() {
- use sys_util::EventFd;
+ if !use_uring() {
+ return;
+ }
+
+ use base::EventFd;
async fn write_event(ev: EventFd, wait: EventFd, ex: &URingExecutor) {
let wait = UringSource::new(wait, ex).unwrap();
@@ -367,12 +380,16 @@ mod tests {
#[test]
fn pend_on_pipe() {
+ if !use_uring() {
+ return;
+ }
+
use std::io::Write;
use futures::future::Either;
async fn do_test(ex: &URingExecutor) {
- let (read_source, mut w) = sys_util::pipe(true).unwrap();
+ let (read_source, mut w) = base::pipe(true).unwrap();
let source = UringSource::new(read_source, ex).unwrap();
let done = Box::pin(async { 5usize });
let pending = Box::pin(read_u64(&source));
@@ -380,7 +397,7 @@ mod tests {
Either::Right((5, pending)) => {
// Write to the pipe so that the kernel will release the memory associated with
// the uring read operation.
- w.write(&[0]).expect("failed to write to pipe");
+ w.write_all(&[0]).expect("failed to write to pipe");
::std::mem::drop(pending);
}
_ => panic!("unexpected select result"),
@@ -393,6 +410,10 @@ mod tests {
#[test]
fn readmem() {
+ if !use_uring() {
+ return;
+ }
+
async fn go(ex: &URingExecutor) {
let f = File::open("/dev/zero").unwrap();
let source = UringSource::new(f, ex).unwrap();
@@ -400,7 +421,7 @@ mod tests {
let vw = Arc::new(VecIoWrapper::from(v));
let ret = source
.read_to_mem(
- 0,
+ None,
Arc::<VecIoWrapper>::clone(&vw),
&[MemRegion { offset: 0, len: 32 }],
)
@@ -419,7 +440,7 @@ mod tests {
let vw = Arc::new(VecIoWrapper::from(v));
let ret = source
.read_to_mem(
- 0,
+ None,
Arc::<VecIoWrapper>::clone(&vw),
&[MemRegion {
offset: 32,
@@ -443,6 +464,10 @@ mod tests {
#[test]
fn range_error() {
+ if !use_uring() {
+ return;
+ }
+
async fn go(ex: &URingExecutor) {
let f = File::open("/dev/zero").unwrap();
let source = UringSource::new(f, ex).unwrap();
@@ -450,7 +475,7 @@ mod tests {
let vw = Arc::new(VecIoWrapper::from(v));
let ret = source
.read_to_mem(
- 0,
+ None,
Arc::<VecIoWrapper>::clone(&vw),
&[MemRegion {
offset: 32,
@@ -467,6 +492,10 @@ mod tests {
#[test]
fn fallocate() {
+ if !use_uring() {
+ return;
+ }
+
async fn go(ex: &URingExecutor) {
let dir = tempfile::TempDir::new().unwrap();
let mut file_path = PathBuf::from(dir.path());
@@ -477,10 +506,12 @@ mod tests {
.write(true)
.open(&file_path)
.unwrap();
- let source = UringSource::new(f, &ex).unwrap();
+ let source = UringSource::new(f, ex).unwrap();
if let Err(e) = source.fallocate(0, 4096, 0).await {
match e {
- crate::io_ext::Error::Uring(crate::uring_executor::Error::Io(io_err)) => {
+ super::super::io_ext::Error::Uring(
+ super::super::uring_executor::Error::Io(io_err),
+ ) => {
if io_err.kind() == std::io::ErrorKind::InvalidInput {
// Skip the test on kernels before fallocate support.
return;
@@ -500,6 +531,10 @@ mod tests {
#[test]
fn fsync() {
+ if !use_uring() {
+ return;
+ }
+
async fn go(ex: &URingExecutor) {
let f = tempfile::tempfile().unwrap();
let source = UringSource::new(f, ex).unwrap();
@@ -512,6 +547,10 @@ mod tests {
#[test]
fn wait_read() {
+ if !use_uring() {
+ return;
+ }
+
async fn go(ex: &URingExecutor) {
let f = File::open("/dev/zero").unwrap();
let source = UringSource::new(f, ex).unwrap();
@@ -524,6 +563,10 @@ mod tests {
#[test]
fn writemem() {
+ if !use_uring() {
+ return;
+ }
+
async fn go(ex: &URingExecutor) {
let f = OpenOptions::new()
.create(true)
@@ -532,9 +575,9 @@ mod tests {
.unwrap();
let source = UringSource::new(f, ex).unwrap();
let v = vec![0x55u8; 64];
- let vw = Arc::new(crate::mem::VecIoWrapper::from(v));
+ let vw = Arc::new(super::super::mem::VecIoWrapper::from(v));
let ret = source
- .write_from_mem(0, vw, &[MemRegion { offset: 0, len: 32 }])
+ .write_from_mem(None, vw, &[MemRegion { offset: 0, len: 32 }])
.await
.unwrap();
assert_eq!(32, ret);
@@ -546,6 +589,10 @@ mod tests {
#[test]
fn writevec() {
+ if !use_uring() {
+ return;
+ }
+
async fn go(ex: &URingExecutor) {
let f = OpenOptions::new()
.create(true)
@@ -556,7 +603,7 @@ mod tests {
let source = UringSource::new(f, ex).unwrap();
let v = vec![0x55u8; 32];
let v_ptr = v.as_ptr();
- let (ret, ret_v) = source.write_from_vec(0, v).await.unwrap();
+ let (ret, ret_v) = source.write_from_vec(None, v).await.unwrap();
assert_eq!(32, ret);
assert_eq!(v_ptr, ret_v.as_ptr());
}
@@ -567,6 +614,10 @@ mod tests {
#[test]
fn writemulti() {
+ if !use_uring() {
+ return;
+ }
+
async fn go(ex: &URingExecutor) {
let f = OpenOptions::new()
.create(true)
@@ -577,9 +628,11 @@ mod tests {
let source = UringSource::new(f, ex).unwrap();
let v = vec![0x55u8; 32];
let v2 = vec![0x55u8; 32];
- let (r, r2) =
- futures::future::join(source.write_from_vec(0, v), source.write_from_vec(32, v2))
- .await;
+ let (r, r2) = futures::future::join(
+ source.write_from_vec(None, v),
+ source.write_from_vec(Some(32), v2),
+ )
+ .await;
assert_eq!(32, r.unwrap().0);
assert_eq!(32, r2.unwrap().0);
}
diff --git a/cros_async/src/waker.rs b/cros_async/src/waker.rs
index ce4971070..5daeb5771 100644
--- a/cros_async/src/waker.rs
+++ b/cros_async/src/waker.rs
@@ -2,9 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::mem::{drop, ManuallyDrop};
-use std::sync::Weak;
-use std::task::{RawWaker, RawWakerVTable, Waker};
+use std::{
+ mem::{drop, ManuallyDrop},
+ sync::Weak,
+ task::{RawWaker, RawWakerVTable, Waker},
+};
/// Wrapper around a usize used as a token to uniquely identify a pending waker.
#[derive(Debug)]
diff --git a/cros_async/src/win/async_types.rs b/cros_async/src/win/async_types.rs
new file mode 100644
index 000000000..bf9c54b4f
--- /dev/null
+++ b/cros_async/src/win/async_types.rs
@@ -0,0 +1,58 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::{Executor, HandleWrapper};
+use base::{AsRawDescriptor, Descriptor, RecvTube, SendTube, Tube, TubeResult};
+use serde::de::DeserializeOwned;
+use std::io;
+use std::os::windows::io::AsRawHandle;
+use std::sync::{Arc, Mutex};
+
+pub struct AsyncTube {
+ inner: Arc<Mutex<Tube>>,
+}
+
+impl AsyncTube {
+ pub fn new(ex: &Executor, tube: Tube) -> io::Result<AsyncTube> {
+ Ok(AsyncTube {
+ inner: Arc::new(Mutex::new(tube)),
+ })
+ }
+
+ /// TODO(b/145998747, b/184398671): this async approach needs to be refactored
+ /// upstream, but for now is implemented to work using simple blocking futures
+ /// (avoiding the unimplemented wait_readable).
+ pub async fn next<T: 'static + DeserializeOwned + Send>(&self) -> TubeResult<T> {
+ let tube = Arc::clone(&self.inner);
+ let handles = HandleWrapper::new(vec![Descriptor(tube.lock().unwrap().as_raw_handle())]);
+ unblock(
+ move || tube.lock().unwrap().recv(),
+ move || Err(handles.lock().cancel_sync_io(Error::OperationCancelled)),
+ )
+ .await
+ }
+
+ pub async fn send<T: 'static + Serialize + Send + Sync>(&self, msg: T) -> TubeResult<()> {
+ let tube = Arc::clone(&self.inner);
+ let handles = HandleWrapper::new(vec![Descriptor(tube.lock().unwrap().as_raw_handle())]);
+ unblock(
+ move || tube.lock().unwrap().send(&msg),
+ move || Err(handles.lock().cancel_sync_io(Error::OperationCancelled)),
+ )
+ .await
+ }
+}
+
+impl From<AsyncTube> for Tube {
+ fn from(at: AsyncTube) -> Tube {
+ // We ensure this is safe by waiting to acquire the mutex on
+ // the tube before unwrapping. This only works because the
+ // worker thread in "next" holds the mutex for its entire life.
+ //
+ // This does however mean that into will block until all async
+ // operations are complete.
+ let _ = at.inner.lock().unwrap();
+ Arc::try_unwrap(at.inner).unwrap().into_inner().unwrap()
+ }
+}
diff --git a/fuzz/.gitignore b/crosvm-fuzz/.gitignore
index a0925114d..a0925114d 100644
--- a/fuzz/.gitignore
+++ b/crosvm-fuzz/.gitignore
diff --git a/fuzz/Cargo.toml b/crosvm-fuzz/Cargo.toml
index d4c7a1e26..5cb0a29cf 100644
--- a/fuzz/Cargo.toml
+++ b/crosvm-fuzz/Cargo.toml
@@ -2,19 +2,20 @@
name = "crosvm-fuzz"
version = "0.0.1"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
cros_fuzz = "*"
-data_model = { path = "../data_model" }
+data_model = { path = "../common/data_model" }
devices = { path = "../devices" }
disk = { path = "../disk" }
fuse = { path = "../fuse" }
+hypervisor = { path = "../hypervisor" }
kernel_loader = { path = "../kernel_loader" }
libc = "*"
rand = "0.6"
base = { path = "../base" }
-tempfile = { path = "../tempfile" }
+tempfile = "3"
usb_util = { path = "../usb_util" }
vm_memory = { path = "../vm_memory" }
diff --git a/crosvm-fuzz/OWNERS b/crosvm-fuzz/OWNERS
new file mode 100644
index 000000000..dcd119abb
--- /dev/null
+++ b/crosvm-fuzz/OWNERS
@@ -0,0 +1 @@
+denniskempin@chromium.org
diff --git a/fuzz/block_fuzzer.rs b/crosvm-fuzz/block_fuzzer.rs
index 124dcacce..de0f79892 100644
--- a/fuzz/block_fuzzer.rs
+++ b/crosvm-fuzz/block_fuzzer.rs
@@ -12,8 +12,8 @@ use std::sync::Arc;
use base::Event;
use cros_fuzz::fuzz_target;
use devices::virtio::{base_features, Block, Interrupt, Queue, VirtioDevice};
-use devices::ProtectionType;
-use tempfile;
+use devices::IrqLevelEvent;
+use hypervisor::ProtectionType;
use vm_memory::{GuestAddress, GuestMemory};
const MEM_SIZE: u64 = 256 * 1024 * 1024;
@@ -88,8 +88,7 @@ fuzz_target!(|bytes| {
mem,
Interrupt::new(
Arc::new(AtomicUsize::new(0)),
- Event::new().unwrap(),
- Event::new().unwrap(),
+ IrqLevelEvent::new().unwrap(),
None, // msix_config
0xFFFF, // VIRTIO_MSI_NO_VECTOR
),
diff --git a/crosvm-fuzz/cargo2android.json b/crosvm-fuzz/cargo2android.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/crosvm-fuzz/cargo2android.json
@@ -0,0 +1 @@
+{}
diff --git a/crosvm-fuzz/fs_server_fuzzer.rs b/crosvm-fuzz/fs_server_fuzzer.rs
new file mode 100644
index 000000000..5421dee3c
--- /dev/null
+++ b/crosvm-fuzz/fs_server_fuzzer.rs
@@ -0,0 +1,51 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#![no_main]
+
+#[cfg(fuzzing)]
+mod fs_server_fuzzer {
+ use std::convert::TryInto;
+
+ use cros_fuzz::fuzz_target;
+ use devices::virtio::{create_descriptor_chain, DescriptorType, Reader, Writer};
+ use fuse::fuzzing::fuzz_server;
+ use vm_memory::{GuestAddress, GuestMemory};
+
+ const MEM_SIZE: u64 = 256 * 1024 * 1024;
+ const BUFFER_ADDR: GuestAddress = GuestAddress(0x100);
+
+ thread_local! {
+ static GUEST_MEM: GuestMemory = GuestMemory::new(&[(GuestAddress(0), MEM_SIZE)]).unwrap();
+ }
+
+ fuzz_target!(|data| {
+ use DescriptorType::*;
+
+ GUEST_MEM.with(|mem| {
+ mem.write_all_at_addr(data, BUFFER_ADDR).unwrap();
+
+ let chain = create_descriptor_chain(
+ mem,
+ GuestAddress(0),
+ BUFFER_ADDR,
+ vec![
+ (Readable, data.len().try_into().unwrap()),
+ (
+ Writable,
+ (MEM_SIZE as u32)
+ .saturating_sub(data.len().try_into().unwrap())
+ .saturating_sub(0x100),
+ ),
+ ],
+ 0,
+ )
+ .unwrap();
+
+ let r = Reader::new(mem.clone(), chain.clone()).unwrap();
+ let w = Writer::new(mem.clone(), chain).unwrap();
+ fuzz_server(r, w);
+ });
+ });
+}
diff --git a/fuzz/qcow_fuzzer.rs b/crosvm-fuzz/qcow_fuzzer.rs
index 729f6044d..23cba4e48 100644
--- a/fuzz/qcow_fuzzer.rs
+++ b/crosvm-fuzz/qcow_fuzzer.rs
@@ -4,9 +4,10 @@
#![no_main]
+use base::FileReadWriteAtVolatile;
use cros_fuzz::fuzz_target;
+use data_model::VolatileSlice;
use disk::QcowFile;
-use tempfile;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::mem::size_of;
@@ -21,13 +22,14 @@ fuzz_target!(|bytes| {
let mut disk_image = Cursor::new(bytes);
let addr = read_u64(&mut disk_image);
let value = read_u64(&mut disk_image);
+ let max_nesting_depth = 10;
let mut disk_file = tempfile::tempfile().unwrap();
disk_file.write_all(&bytes[16..]).unwrap();
disk_file.seek(SeekFrom::Start(0)).unwrap();
- if let Ok(mut qcow) = QcowFile::from(disk_file) {
- if qcow.seek(SeekFrom::Start(addr)).is_ok() {
- let _ = qcow.write_all(&value.to_le_bytes());
- }
+ if let Ok(mut qcow) = QcowFile::from(disk_file, max_nesting_depth) {
+ let mut mem = value.to_le_bytes().to_owned();
+ let vslice = VolatileSlice::new(&mut mem);
+ let _ = qcow.write_all_at_volatile(vslice, addr);
}
});
diff --git a/fuzz/usb_descriptor_fuzzer.rs b/crosvm-fuzz/usb_descriptor_fuzzer.rs
index a33e7c752..a33e7c752 100644
--- a/fuzz/usb_descriptor_fuzzer.rs
+++ b/crosvm-fuzz/usb_descriptor_fuzzer.rs
diff --git a/fuzz/virtqueue_fuzzer.rs b/crosvm-fuzz/virtqueue_fuzzer.rs
index 4f2c7acf9..4f2c7acf9 100644
--- a/fuzz/virtqueue_fuzzer.rs
+++ b/crosvm-fuzz/virtqueue_fuzzer.rs
diff --git a/fuzz/zimage_fuzzer.rs b/crosvm-fuzz/zimage_fuzzer.rs
index 953c5449e..fff46e11b 100644
--- a/fuzz/zimage_fuzzer.rs
+++ b/crosvm-fuzz/zimage_fuzzer.rs
@@ -5,7 +5,6 @@
#![no_main]
use cros_fuzz::fuzz_target;
-use tempfile;
use vm_memory::{GuestAddress, GuestMemory};
use std::fs::File;
diff --git a/crosvm_control/Android.bp b/crosvm_control/Android.bp
new file mode 100644
index 000000000..09ff5c551
--- /dev/null
+++ b/crosvm_control/Android.bp
@@ -0,0 +1,28 @@
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
+// Do not modify this file as changes will be overridden on upgrade.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_crosvm_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-BSD
+ default_applicable_licenses: ["external_crosvm_license"],
+}
+
+rust_ffi_shared {
+ name: "libcrosvm_control_shared",
+ defaults: ["crosvm_defaults"],
+ stem: "libcrosvm_control",
+ host_supported: true,
+ crate_name: "crosvm_control",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ rustlibs: [
+ "libbase_rust",
+ "liblibc",
+ "libvm_control",
+ ],
+}
diff --git a/libcrosvm_control/Cargo.toml b/crosvm_control/Cargo.toml
index 2ed68b8a9..13ff6b3e8 100644
--- a/libcrosvm_control/Cargo.toml
+++ b/crosvm_control/Cargo.toml
@@ -1,8 +1,8 @@
[package]
-name = "libcrosvm_control"
+name = "crosvm_control"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[lib]
crate-type = ["cdylib"]
diff --git a/libcrosvm_control/src/lib.rs b/crosvm_control/src/lib.rs
index 973d64e0a..372159687 100644
--- a/libcrosvm_control/src/lib.rs
+++ b/crosvm_control/src/lib.rs
@@ -237,9 +237,9 @@ pub extern "C" fn crosvm_client_modify_battery(
do_modify_battery(
&socket_path,
- &battery_type.to_str().unwrap(),
- &property.to_str().unwrap(),
- &target.to_str().unwrap(),
+ battery_type.to_str().unwrap(),
+ property.to_str().unwrap(),
+ target.to_str().unwrap(),
)
.is_ok()
} else {
@@ -294,8 +294,7 @@ pub struct BalloonStatsFfi {
impl From<&BalloonStats> for BalloonStatsFfi {
fn from(other: &BalloonStats) -> Self {
- let convert =
- |x: Option<u64>| -> i64 { x.map(|y| y.try_into().ok()).flatten().unwrap_or(-1) };
+ let convert = |x: Option<u64>| -> i64 { x.and_then(|y| y.try_into().ok()).unwrap_or(-1) };
Self {
swap_in: convert(other.swap_in),
swap_out: convert(other.swap_out),
diff --git a/crosvm_plugin/Android.bp b/crosvm_plugin/Android.bp
index bf0e21591..25e7b3e3d 100644
--- a/crosvm_plugin/Android.bp
+++ b/crosvm_plugin/Android.bp
@@ -1,62 +1 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Do not modify this file as changes will be overridden on upgrade.
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../kvm/src/lib.rs
-// ../kvm_sys/src/lib.rs
-// ../protos/src/lib.rs "kvm_sys,plugin"
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// ../vm_memory/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// cfg-if-1.0.0
-// either-1.6.1 "default,use_std"
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// getrandom-0.2.2 "std"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// log-0.4.14
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro2-1.0.26 "default,proc-macro"
-// protobuf-2.22.1
-// protobuf-codegen-2.22.1
-// protoc-2.22.1
-// protoc-rust-2.22.1
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.3 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.0 "std"
-// rand_core-0.6.2 "alloc,getrandom,std"
-// remove_dir_all-0.5.3
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
-// which-4.1.0
+// Feature is disabled for Android
diff --git a/crosvm_plugin/Cargo.toml b/crosvm_plugin/Cargo.toml
index c42564523..75b538dfa 100644
--- a/crosvm_plugin/Cargo.toml
+++ b/crosvm_plugin/Cargo.toml
@@ -2,7 +2,7 @@
name = "crosvm_plugin"
version = "0.17.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[features]
stats = []
diff --git a/crosvm_plugin/cargo2android.json b/crosvm_plugin/cargo2android.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/crosvm_plugin/cargo2android.json
@@ -0,0 +1 @@
+{} \ No newline at end of file
diff --git a/crosvm_plugin/crosvm.h b/crosvm_plugin/crosvm.h
index d1bea2a49..e18e9f8b9 100644
--- a/crosvm_plugin/crosvm.h
+++ b/crosvm_plugin/crosvm.h
@@ -175,6 +175,11 @@ static_assert(sizeof(struct crosvm_net_config) == 20,
#endif
/*
+ * Gets fd for the gpu server.
+ */
+int crosvm_get_render_server_fd(void);
+
+/*
* Gets the network configuration.
*/
int crosvm_net_get_config(struct crosvm*, struct crosvm_net_config*);
diff --git a/crosvm_plugin/src/lib.rs b/crosvm_plugin/src/lib.rs
index 86827a681..4a074506d 100644
--- a/crosvm_plugin/src/lib.rs
+++ b/crosvm_plugin/src/lib.rs
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#![cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#![allow(non_camel_case_types)]
//! This module implements the dynamically loaded client library API used by a crosvm plugin,
@@ -17,7 +18,7 @@
use std::env;
use std::fs::File;
-use std::io::{IoSlice, Read, Write};
+use std::io::{IoSlice, IoSliceMut, Read, Write};
use std::mem::{size_of, swap};
use std::os::raw::{c_int, c_void};
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
@@ -31,7 +32,7 @@ use std::sync::Arc;
use libc::{E2BIG, EINVAL, ENOENT, ENOTCONN, EPROTO};
-use protobuf::{parse_from_bytes, Message, ProtobufEnum, RepeatedField};
+use protobuf::{Message, ProtobufEnum, RepeatedField};
use base::ScmSocket;
@@ -62,6 +63,11 @@ const CROSVM_VCPU_EVENT_KIND_PAUSED: u32 = 2;
const CROSVM_VCPU_EVENT_KIND_HYPERV_HCALL: u32 = 3;
const CROSVM_VCPU_EVENT_KIND_HYPERV_SYNIC: u32 = 4;
+pub const CROSVM_GPU_SERVER_FD_ENV: &str = "CROSVM_GPU_SERVER_FD";
+pub const CROSVM_SOCKET_ENV: &str = "CROSVM_SOCKET";
+#[cfg(feature = "stats")]
+pub const CROSVM_STATS_ENV: &str = "CROSVM_STATS";
+
#[repr(C)]
#[derive(Copy, Clone)]
pub struct crosvm_net_config {
@@ -235,7 +241,7 @@ fn record(_a: Stat) -> u32 {
#[cfg(feature = "stats")]
fn printstats() {
// Unsafe due to racy access - OK for stats
- if std::env::var("CROSVM_STATS").is_ok() {
+ if std::env::var(CROSVM_STATS_ENV).is_ok() {
unsafe {
stats::STATS.print();
}
@@ -300,7 +306,10 @@ impl crosvm {
let mut datagram_fds = [0; MAX_DATAGRAM_FD];
let (msg_size, fd_count) = self
.socket
- .recv_with_fds(&mut self.response_buffer, &mut datagram_fds)
+ .recv_with_fds(
+ IoSliceMut::new(&mut self.response_buffer),
+ &mut datagram_fds,
+ )
.map_err(|e| -e.errno())?;
// Safe because the first fd_count fds from recv_with_fds are owned by us and valid.
let datagram_files = datagram_fds[..fd_count]
@@ -308,8 +317,8 @@ impl crosvm {
.map(|&fd| unsafe { File::from_raw_fd(fd) })
.collect();
- let response: MainResponse =
- parse_from_bytes(&self.response_buffer[..msg_size]).map_err(proto_error_to_int)?;
+ let response: MainResponse = Message::parse_from_bytes(&self.response_buffer[..msg_size])
+ .map_err(proto_error_to_int)?;
if response.errno != 0 {
return Err(response.errno);
}
@@ -510,8 +519,7 @@ impl crosvm {
entry.irq_id = route.irq_id;
match route.kind {
CROSVM_IRQ_ROUTE_IRQCHIP => {
- let irqchip: &mut MainRequest_SetIrqRouting_Route_Irqchip;
- irqchip = entry.mut_irqchip();
+ let irqchip: &mut MainRequest_SetIrqRouting_Route_Irqchip = entry.mut_irqchip();
// Safe because route.kind indicates which union field is valid.
irqchip.irqchip = unsafe { route.route.irqchip }.irqchip;
irqchip.pin = unsafe { route.route.irqchip }.pin;
@@ -1070,7 +1078,7 @@ impl crosvm_vcpu {
if bytes == 0 || total_size > self.response_length {
return Err(EINVAL);
}
- let response: VcpuResponse = parse_from_bytes(
+ let response: VcpuResponse = Message::parse_from_bytes(
&self.response_buffer[self.response_base + bytes..self.response_base + total_size],
)
.map_err(proto_error_to_int)?;
@@ -1360,9 +1368,22 @@ fn to_crosvm_rc<T>(r: result::Result<T, c_int>) -> c_int {
}
#[no_mangle]
+pub unsafe extern "C" fn crosvm_get_render_server_fd() -> c_int {
+ let fd = match env::var(CROSVM_GPU_SERVER_FD_ENV) {
+ Ok(v) => v,
+ _ => return -EINVAL,
+ };
+
+ match fd.parse() {
+ Ok(v) if v >= 0 => v,
+ _ => -EINVAL,
+ }
+}
+
+#[no_mangle]
pub unsafe extern "C" fn crosvm_connect(out: *mut *mut crosvm) -> c_int {
let _u = record(Stat::Connect);
- let socket_name = match env::var("CROSVM_SOCKET") {
+ let socket_name = match env::var(CROSVM_SOCKET_ENV) {
Ok(v) => v,
_ => return -ENOTCONN,
};
diff --git a/cuttlefish/Android.bp b/cuttlefish/Android.bp
index a685d4ad4..3f2a2f09e 100644
--- a/cuttlefish/Android.bp
+++ b/cuttlefish/Android.bp
@@ -12,14 +12,14 @@ sh_binary_host {
name: "common_crosvm",
filename: "crosvm",
target: {
- linux_glibc: {
+ host_linux: {
src: "crosvm",
},
darwin: {
- src: "crosvm",
+ src: "crosvm",
},
linux_bionic: {
- src: "crosvm_bionic",
- },
+ src: "crosvm_bionic",
+ },
},
}
diff --git a/devices/.build_test_serial b/devices/.build_test_serial
deleted file mode 100644
index e69de29bb..000000000
--- a/devices/.build_test_serial
+++ /dev/null
diff --git a/devices/Android.bp b/devices/Android.bp
index 6b3cebace..783b302f6 100644
--- a/devices/Android.bp
+++ b/devices/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Manually added features: audio, gfxstream, gpu, gpu_buffer, gpu_display, gpu_renderer,
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
package {
// See: http://go/android-license-faq
@@ -10,20 +10,34 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "devices_defaults",
+rust_test {
+ name: "devices_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "devices",
- // has rustc warnings
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: false,
+ },
+ edition: "2021",
+ features: [
+ "audio",
+ "usb",
+ ],
rustlibs: [
"libacpi_tables",
+ "libanyhow",
+ "libargh",
+ "libasync_task",
"libaudio_streams",
+ "libballoon_control",
"libbase_rust",
"libbit_field",
+ "libcfg_if",
"libcros_async",
"libdata_model",
"libdisk",
@@ -32,60 +46,49 @@ rust_defaults {
"libhypervisor",
"libkvm_sys",
"liblibc",
- "liblibchromeos",
- "liblibcras",
"liblinux_input_sys",
+ "libmemoffset",
"libminijail_rust",
"libnet_sys",
"libnet_util",
+ "libonce_cell",
"libp9",
"libpower_monitor",
- "librand_ish",
"libresources",
- "libserde",
- "libsmallvec",
+ "libserde",
+ "libserde_json",
+ "libserde_keyvalue",
+ "libsmallvec",
"libsync_rust",
- "libsys_util",
- "libsyscall_defines",
"libtempfile",
"libthiserror",
"libusb_util",
+ "libuuid",
"libvfio_sys",
"libvhost",
"libvirtio_sys",
"libvm_control",
"libvm_memory",
- "libvmm_vhost",
+ "libvmm_vhost",
],
proc_macros: [
"libenumn",
"libremain",
],
-}
-
-rust_test_host {
- name: "devices_host_test_src_lib",
- defaults: ["devices_defaults"],
- shared_libs: ["libgfxstream_backend",],
- features: [ // added manually
- "audio",
- "gpu",
- "gpu_buffer",
- "gpu_display",
- "gpu_renderer",
- "gfxstream",
- ],
- rustlibs: [
- "libaudio_streams", // moved manually
- "libgpu_display", // added manually
- "liblibcras", // moved manually
- "librutabaga_gfx", // added manually
- ],
-
- // added manually
target: {
host: {
- shared_libs: ["libvirglrenderer"],
+ features: [
+ "gfxstream",
+ "gpu",
+ ],
+ rustlibs: [
+ "libgpu_display",
+ "librutabaga_gfx",
+ ],
+ shared_libs: [
+ "libvirglrenderer",
+ "libgfxstream_backend",
+ ],
},
android: {
shared_libs: ["libdrm"],
@@ -98,157 +101,76 @@ rust_test_host {
},
}
-rust_test {
- name: "devices_device_test_src_lib",
- defaults: ["devices_defaults"],
-}
-
rust_library {
name: "libdevices",
defaults: ["crosvm_defaults"],
- // has rustc warnings
host_supported: true,
crate_name: "devices",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
features: [
"audio",
- "gpu",
- "gpu_buffer",
- "gpu_display",
- "gpu_renderer",
- "gfxstream",
+ "usb",
],
- shared_libs: ["libgfxstream_backend",],
rustlibs: [
"libacpi_tables",
+ "libanyhow",
+ "libargh",
+ "libasync_task",
"libaudio_streams",
+ "libballoon_control",
"libbase_rust",
"libbit_field",
+ "libcfg_if",
"libcros_async",
"libdata_model",
"libdisk",
"libfuse_rust",
"libfutures",
- "libgpu_display", // added manually
"libhypervisor",
"libkvm_sys",
"liblibc",
- "liblibchromeos",
- "liblibcras",
"liblinux_input_sys",
+ "libmemoffset",
"libminijail_rust",
"libnet_sys",
"libnet_util",
+ "libonce_cell",
"libp9",
"libpower_monitor",
- "librand_ish",
"libresources",
- "librutabaga_gfx", // added manually
- "libserde", // added manually
- "libsmallvec",
+ "libserde",
+ "libserde_json",
+ "libserde_keyvalue",
+ "libsmallvec",
"libsync_rust",
- "libsys_util",
- "libsyscall_defines",
"libthiserror",
"libusb_util",
+ "libuuid",
"libvfio_sys",
"libvhost",
"libvirtio_sys",
"libvm_control",
"libvm_memory",
- "libvmm_vhost", // added manually
+ "libvmm_vhost",
],
proc_macros: [
"libenumn",
"libremain",
],
+ target: {
+ host: {
+ features: [
+ "gfxstream",
+ "gpu",
+ ],
+ rustlibs: [
+ "libgpu_display",
+ "librutabaga_gfx",
+ ],
+ shared_libs: ["libgfxstream_backend"],
+ },
+ },
}
-
-// dependent_library ["feature_list"]
-// ../../adhd/audio_streams/src/audio_streams.rs
-// ../../adhd/cras/client/cras-sys/src/lib.rs
-// ../../adhd/cras/client/libcras/src/libcras.rs
-// ../../libchromeos-rs/src/lib.rs
-// ../../minijail/rust/minijail-sys/lib.rs
-// ../../minijail/rust/minijail/src/lib.rs
-// ../../vm_tools/p9/src/lib.rs
-// ../../vm_tools/p9/wire_format_derive/wire_format_derive.rs
-// ../acpi_tables/src/lib.rs
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../bit_field/bit_field_derive/bit_field_derive.rs
-// ../bit_field/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../disk/src/disk.rs
-// ../enumn/src/lib.rs
-// ../fuse/src/lib.rs
-// ../hypervisor/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../kvm/src/lib.rs
-// ../kvm_sys/src/lib.rs
-// ../linux_input_sys/src/lib.rs
-// ../net_sys/src/lib.rs
-// ../net_util/src/lib.rs
-// ../power_monitor/src/lib.rs
-// ../rand_ish/src/lib.rs
-// ../resources/src/lib.rs
-// ../rutabaga_gfx/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../syscall_defines/src/lib.rs
-// ../tempfile/src/lib.rs
-// ../usb_sys/src/lib.rs
-// ../usb_util/src/lib.rs
-// ../vfio_sys/src/lib.rs
-// ../vhost/src/lib.rs
-// ../virtio_sys/src/lib.rs
-// ../vm_control/src/lib.rs
-// ../vm_memory/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.45
-// autocfg-1.0.1
-// base-0.1.0
-// bitflags-1.2.1 "default"
-// cfg-if-1.0.0
-// downcast-rs-1.2.0 "default,std"
-// futures-0.3.13 "alloc,async-await,default,executor,futures-executor,std"
-// futures-channel-0.3.13 "alloc,futures-sink,sink,std"
-// futures-core-0.3.13 "alloc,std"
-// futures-executor-0.3.13 "std"
-// futures-io-0.3.13 "std"
-// futures-macro-0.3.13
-// futures-sink-0.3.13 "alloc,std"
-// futures-task-0.3.13 "alloc,std"
-// futures-util-0.3.13 "alloc,async-await,async-await-macro,channel,futures-channel,futures-io,futures-macro,futures-sink,io,memchr,proc-macro-hack,proc-macro-nested,sink,slab,std"
-// getrandom-0.2.2 "std"
-// intrusive-collections-0.9.0 "alloc,default"
-// libc-0.2.87 "default,std"
-// log-0.4.14
-// memchr-2.3.4 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.4
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// pkg-config-0.3.19
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro-hack-0.5.19
-// proc-macro-nested-0.1.7
-// proc-macro2-1.0.24 "default,proc-macro"
-// protobuf-2.22.0
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.3 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.0 "std"
-// rand_core-0.6.2 "alloc,getrandom,std"
-// remain-0.2.2
-// remove_dir_all-0.5.3
-// serde-1.0.123 "default,derive,serde_derive,std"
-// serde_derive-1.0.123 "default"
-// slab-0.4.2
-// syn-1.0.61 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/devices/Cargo.toml b/devices/Cargo.toml
index f77035c57..35a56a47f 100644
--- a/devices/Cargo.toml
+++ b/devices/Cargo.toml
@@ -2,65 +2,80 @@
name = "devices"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[features]
audio = []
+audio_cras = ["libcras"]
+chromeos = ["dbus", "protobuf", "system_api"]
direct = []
gpu = ["gpu_display","rutabaga_gfx"]
-tpm = ["protos/trunks", "tpm2"]
+tpm = ["tpm2"]
+usb = []
video-decoder = []
video-encoder = []
minigbm = ["rutabaga_gfx/minigbm"]
-x = ["gpu_display/x"]
+x = ["gpu_display/x", "rutabaga_gfx/x"]
virgl_renderer = ["gpu", "rutabaga_gfx/virgl_renderer"]
gfxstream = ["gpu", "rutabaga_gfx/gfxstream"]
+slirp = []
[dependencies]
+argh = "0.1.7"
+async-task = "4"
acpi_tables = {path = "../acpi_tables" }
-audio_streams = { path = "../../adhd/audio_streams" } # ignored by ebuild
+anyhow = "*"
+audio_streams = { path = "../common/audio_streams" }
+balloon_control = { path = "../common/balloon_control" }
base = { path = "../base" }
bit_field = { path = "../bit_field" }
+cfg-if = "1.0.0"
cros_async = { path = "../cros_async" }
-data_model = { path = "../data_model" }
+data_model = { path = "../common/data_model" }
+dbus = { version = "0.9", optional = true }
disk = { path = "../disk" }
-enumn = { path = "../enumn" }
+enumn = "0.1.0"
fuse = {path = "../fuse" }
gpu_display = { path = "../gpu_display", optional = true }
rutabaga_gfx = { path = "../rutabaga_gfx", optional = true }
hypervisor = { path = "../hypervisor" }
kvm_sys = { path = "../kvm_sys" }
libc = "*"
-libchromeos = { path = "../../libchromeos-rs" } # ignored by ebuild
-libcras = { path = "../../adhd/cras/client/libcras" } # ignored by ebuild
+libcras = { path = "../libcras_stub", optional = true }
+libvda = { path = "../media/libvda", optional = true }
linux_input_sys = { path = "../linux_input_sys" }
+memoffset = { version = "0.6" }
minijail = { path = "../../minijail/rust/minijail" } # ignored by ebuild
net_sys = { path = "../net_sys" }
net_util = { path = "../net_util" }
-p9 = { path = "../../vm_tools/p9" }
+once_cell = "1.7.2"
+p9 = { path = "../common/p9" }
power_monitor = { path = "../power_monitor" }
+protobuf = { version = "2.3", optional = true }
protos = { path = "../protos", optional = true }
-rand_ish = { path = "../rand_ish" }
remain = "*"
resources = { path = "../resources" }
serde = { version = "1", features = [ "derive" ] }
+serde_json = "1"
+serde_keyvalue = { path = "../serde_keyvalue" }
smallvec = "1.6.1"
-sync = { path = "../sync" }
-sys_util = { path = "../sys_util" }
+sync = { path = "../common/sync" }
+system_api = { path = "../system_api_stub", optional = true }
thiserror = "1.0.20"
tpm2 = { path = "../tpm2", optional = true }
usb_util = { path = "../usb_util" }
+uuid = { version = "0.8.2" }
vfio_sys = { path = "../vfio_sys" }
vhost = { path = "../vhost" }
-vmm_vhost = { version = "*", features = ["vhost-user-master"] }
+vmm_vhost = { path = "../third_party/vmm_vhost", features = ["vmm", "device", "vfio-device"] }
virtio_sys = { path = "../virtio_sys" }
vm_control = { path = "../vm_control" }
vm_memory = { path = "../vm_memory" }
[dependencies.futures]
version = "*"
-features = ["std"]
+features = ["async-await", "std"]
default-features = false
[dev-dependencies]
-tempfile = { path = "../tempfile" }
+tempfile = "3"
diff --git a/devices/TEST_MAPPING b/devices/TEST_MAPPING
index 5dbf09243..6e8c9f0b8 100644
--- a/devices/TEST_MAPPING
+++ b/devices/TEST_MAPPING
@@ -4,10 +4,10 @@
// "presubmit": [
// {
// "host": true,
-// "name": "devices_host_test_src_lib"
+// "name": "devices_test_src_lib"
// },
// {
-// "name": "devices_device_test_src_lib"
+// "name": "devices_test_src_lib"
// }
// ]
}
diff --git a/devices/cargo2android.json b/devices/cargo2android.json
new file mode 100644
index 000000000..26f8211a1
--- /dev/null
+++ b/devices/cargo2android.json
@@ -0,0 +1,11 @@
+{
+ "add-module-block": "cargo2android_libs.bp",
+ "add_workspace": true,
+ "device": true,
+ "features": "audio,usb",
+ "global_defaults": "crosvm_defaults",
+ "no_presubmit": true,
+ "patch": "patches/Android.bp.patch",
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/devices/cargo2android_libs.bp b/devices/cargo2android_libs.bp
new file mode 100644
index 000000000..ff7c6e6f5
--- /dev/null
+++ b/devices/cargo2android_libs.bp
@@ -0,0 +1,13 @@
+target: {
+ host: {
+ features: [
+ "gfxstream",
+ "gpu",
+ ],
+ rustlibs: [
+ "libgpu_display",
+ "librutabaga_gfx",
+ ],
+ shared_libs: ["libgfxstream_backend"],
+ },
+} \ No newline at end of file
diff --git a/devices/patches/Android.bp.patch b/devices/patches/Android.bp.patch
new file mode 100644
index 000000000..df0740f2c
--- /dev/null
+++ b/devices/patches/Android.bp.patch
@@ -0,0 +1,21 @@
+diff --git a/devices/Android.bp b/devices/Android.bp
+index a1ac9363..ae2fdde5 100644
+--- a/devices/Android.bp
++++ b/devices/Android.bp
+@@ -83,7 +83,15 @@ rust_test {
+ "libgpu_display",
+ "librutabaga_gfx",
+ ],
+- shared_libs: ["libgfxstream_backend"],
++ shared_libs: ["libvirglrenderer", "libgfxstream_backend"],
++ },
++ android: {
++ shared_libs: ["libdrm"],
++ static_libs: [
++ "libepoxy",
++ "libgbm",
++ "libvirglrenderer",
++ ],
+ },
+ },
+ }
diff --git a/devices/src/acpi.rs b/devices/src/acpi.rs
index d5674f28a..be2b07854 100644
--- a/devices/src/acpi.rs
+++ b/devices/src/acpi.rs
@@ -2,54 +2,620 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use crate::{BusAccessInfo, BusDevice, BusResumeDevice};
+use crate::{BusAccessInfo, BusDevice, BusResumeDevice, IrqLevelEvent};
use acpi_tables::{aml, aml::Aml};
-use base::{error, warn, Event};
+use base::{error, info, warn, Error as SysError, Event, PollToken, WaitContext};
+use base::{AcpiNotifyEvent, NetlinkGenericSocket};
+use std::collections::BTreeMap;
+use std::sync::Arc;
+use std::thread;
+use sync::Mutex;
+use thiserror::Error;
+use vm_control::{GpeNotify, PmResource};
+
+#[cfg(feature = "direct")]
+use {std::fs, std::io::Error as IoError, std::path::PathBuf};
+
+#[derive(Error, Debug)]
+pub enum ACPIPMError {
+ /// Creating WaitContext failed.
+ #[error("failed to create wait context: {0}")]
+ CreateWaitContext(SysError),
+ /// Error while waiting for events.
+ #[error("failed to wait for events: {0}")]
+ WaitError(SysError),
+ #[error("Did not find group_id corresponding to acpi_mc_group")]
+ AcpiMcGroupError,
+ #[error("Failed to create and bind NETLINK_GENERIC socket for acpi_mc_group: {0}")]
+ AcpiEventSockError(base::Error),
+}
+
+struct Pm1Resource {
+ status: u16,
+ enable: u16,
+ control: u16,
+}
+
+struct GpeResource {
+ status: [u8; ACPIPM_RESOURCE_GPE0_BLK_LEN as usize / 2],
+ enable: [u8; ACPIPM_RESOURCE_GPE0_BLK_LEN as usize / 2],
+ gpe_notify: BTreeMap<u32, Vec<Arc<Mutex<dyn GpeNotify>>>>,
+}
+
+#[cfg(feature = "direct")]
+struct DirectGpe {
+ num: u32,
+ path: PathBuf,
+ ready: bool,
+ enabled: bool,
+}
/// ACPI PM resource for handling OS suspend/resume request
+#[allow(dead_code)]
pub struct ACPIPMResource {
+ // This is SCI interrupt that will be raised in the VM.
+ sci_evt: IrqLevelEvent,
+ // This is the host SCI that is being handled by crosvm.
+ #[cfg(feature = "direct")]
+ sci_direct_evt: Option<IrqLevelEvent>,
+ #[cfg(feature = "direct")]
+ direct_gpe: Vec<DirectGpe>,
+ kill_evt: Option<Event>,
+ worker_thread: Option<thread::JoinHandle<()>>,
suspend_evt: Event,
exit_evt: Event,
- pm1_status: u16,
- pm1_enable: u16,
- pm1_control: u16,
- sleep_control: u8,
- sleep_status: u8,
+ pm1: Arc<Mutex<Pm1Resource>>,
+ gpe0: Arc<Mutex<GpeResource>>,
}
impl ACPIPMResource {
/// Constructs ACPI Power Management Resouce.
- pub fn new(suspend_evt: Event, exit_evt: Event) -> ACPIPMResource {
+ ///
+ /// `direct_gpe_info` - tuple of host SCI trigger and resample events, and list of direct GPEs
+ #[allow(dead_code)]
+ pub fn new(
+ sci_evt: IrqLevelEvent,
+ #[cfg(feature = "direct")] direct_gpe_info: Option<(IrqLevelEvent, &[u32])>,
+ suspend_evt: Event,
+ exit_evt: Event,
+ ) -> ACPIPMResource {
+ let pm1 = Pm1Resource {
+ status: 0,
+ enable: 0,
+ control: 0,
+ };
+ let gpe0 = GpeResource {
+ status: Default::default(),
+ enable: Default::default(),
+ gpe_notify: BTreeMap::new(),
+ };
+
+ #[cfg(feature = "direct")]
+ let (sci_direct_evt, direct_gpe) = if let Some(info) = direct_gpe_info {
+ let (evt, gpes) = info;
+ let gpe_vec = gpes.iter().map(|gpe| DirectGpe::new(*gpe)).collect();
+ (Some(evt), gpe_vec)
+ } else {
+ (None, Vec::new())
+ };
+
ACPIPMResource {
+ sci_evt,
+ #[cfg(feature = "direct")]
+ sci_direct_evt,
+ #[cfg(feature = "direct")]
+ direct_gpe,
+ kill_evt: None,
+ worker_thread: None,
suspend_evt,
exit_evt,
- pm1_status: 0,
- pm1_enable: 0,
- pm1_control: 0,
- sleep_control: 0,
- sleep_status: 0,
+ pm1: Arc::new(Mutex::new(pm1)),
+ gpe0: Arc::new(Mutex::new(gpe0)),
+ }
+ }
+
+ pub fn start(&mut self) {
+ let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
+ Ok(v) => v,
+ Err(e) => {
+ error!("failed to create kill Event pair: {}", e);
+ return;
+ }
+ };
+ self.kill_evt = Some(self_kill_evt);
+
+ let sci_evt = self.sci_evt.try_clone().expect("failed to clone event");
+ let pm1 = self.pm1.clone();
+ let gpe0 = self.gpe0.clone();
+
+ #[cfg(feature = "direct")]
+ let sci_direct_evt = self.sci_direct_evt.take();
+
+ #[cfg(feature = "direct")]
+ // Direct GPEs are forwarded via direct SCI forwarding,
+ // not via ACPI netlink events.
+ let acpi_event_ignored_gpe = self.direct_gpe.iter().map(|gpe| gpe.num).collect();
+
+ #[cfg(not(feature = "direct"))]
+ let acpi_event_ignored_gpe = Vec::new();
+
+ let worker_result = thread::Builder::new()
+ .name("ACPI PM worker".to_string())
+ .spawn(move || {
+ if let Err(e) = run_worker(
+ sci_evt,
+ kill_evt,
+ pm1,
+ gpe0,
+ acpi_event_ignored_gpe,
+ #[cfg(feature = "direct")]
+ sci_direct_evt,
+ ) {
+ error!("{}", e);
+ }
+ });
+
+ match worker_result {
+ Err(e) => error!("failed to spawn ACPI PM worker thread: {}", e),
+ Ok(join_handle) => self.worker_thread = Some(join_handle),
+ }
+ }
+}
+
+fn run_worker(
+ sci_evt: IrqLevelEvent,
+ kill_evt: Event,
+ pm1: Arc<Mutex<Pm1Resource>>,
+ gpe0: Arc<Mutex<GpeResource>>,
+ acpi_event_ignored_gpe: Vec<u32>,
+ #[cfg(feature = "direct")] sci_direct_evt: Option<IrqLevelEvent>,
+) -> Result<(), ACPIPMError> {
+ // Get group id corresponding to acpi_mc_group of acpi_event family
+ let nl_groups: u32;
+ match get_acpi_event_group() {
+ Some(group) if group > 0 => {
+ nl_groups = 1 << (group - 1);
+ info!("Listening on acpi_mc_group of acpi_event family");
+ }
+ _ => {
+ return Err(ACPIPMError::AcpiMcGroupError);
+ }
+ }
+
+ let acpi_event_sock = match NetlinkGenericSocket::new(nl_groups) {
+ Ok(acpi_sock) => acpi_sock,
+ Err(e) => {
+ return Err(ACPIPMError::AcpiEventSockError(e));
+ }
+ };
+
+ #[derive(PollToken)]
+ enum Token {
+ AcpiEvent,
+ InterruptResample,
+ #[cfg(feature = "direct")]
+ InterruptTriggerDirect,
+ Kill,
+ }
+
+ let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
+ (&acpi_event_sock, Token::AcpiEvent),
+ (sci_evt.get_resample(), Token::InterruptResample),
+ (&kill_evt, Token::Kill),
+ ])
+ .map_err(ACPIPMError::CreateWaitContext)?;
+
+ #[cfg(feature = "direct")]
+ if let Some(ref evt) = sci_direct_evt {
+ wait_ctx
+ .add(evt.get_trigger(), Token::InterruptTriggerDirect)
+ .map_err(ACPIPMError::CreateWaitContext)?;
+ }
+
+ #[cfg(feature = "direct")]
+ let mut pending_sci_direct: Option<&IrqLevelEvent> = None;
+
+ loop {
+ let events = wait_ctx.wait().map_err(ACPIPMError::WaitError)?;
+ for event in events.iter().filter(|e| e.is_readable) {
+ match event.token {
+ Token::AcpiEvent => {
+ acpi_event_run(
+ &acpi_event_sock,
+ &gpe0,
+ &pm1,
+ &sci_evt,
+ &acpi_event_ignored_gpe,
+ );
+ }
+ Token::InterruptResample => {
+ sci_evt.clear_resample();
+
+ #[cfg(feature = "direct")]
+ if let Some(evt) = pending_sci_direct.take() {
+ if let Err(e) = evt.trigger_resample() {
+ error!("ACPIPM: failed to resample sci event: {}", e);
+ }
+ }
+
+ // Re-trigger SCI if PM1 or GPE status is still not cleared.
+ pm1.lock().trigger_sci(&sci_evt);
+ gpe0.lock().trigger_sci(&sci_evt);
+ }
+ #[cfg(feature = "direct")]
+ Token::InterruptTriggerDirect => {
+ if let Some(ref evt) = sci_direct_evt {
+ evt.clear_trigger();
+
+ for (gpe, devs) in &gpe0.lock().gpe_notify {
+ if DirectGpe::is_gpe_trigger(*gpe).unwrap_or(false) {
+ for dev in devs {
+ dev.lock().notify();
+ }
+ }
+ }
+
+ if let Err(e) = sci_evt.trigger() {
+ error!("ACPIPM: failed to trigger sci event: {}", e);
+ }
+ pending_sci_direct = Some(evt);
+ }
+ }
+ Token::Kill => return Ok(()),
+ }
}
}
}
+fn acpi_event_handle_gpe(
+ gpe_number: u32,
+ _type: u32,
+ gpe0: &Arc<Mutex<GpeResource>>,
+ sci_evt: &IrqLevelEvent,
+ ignored_gpe: &[u32],
+) {
+ // If gpe event, emulate GPE and trigger SCI
+ if _type == 0 && gpe_number < 256 && !ignored_gpe.contains(&gpe_number) {
+ let mut gpe0 = gpe0.lock();
+ let byte = gpe_number as usize / 8;
+
+ if byte >= gpe0.status.len() {
+ error!("gpe_evt: GPE register {} does not exist", byte);
+ return;
+ }
+ gpe0.status[byte] |= 1 << (gpe_number % 8);
+ gpe0.trigger_sci(sci_evt);
+ }
+}
+
+const ACPI_BUTTON_NOTIFY_STATUS: u32 = 0x80;
+
+fn acpi_event_handle_power_button(
+ acpi_event: AcpiNotifyEvent,
+ pm1: &Arc<Mutex<Pm1Resource>>,
+ sci_evt: &IrqLevelEvent,
+) {
+ // If received power button event, emulate PM/PWRBTN_STS and trigger SCI
+ if acpi_event._type == ACPI_BUTTON_NOTIFY_STATUS && acpi_event.bus_id.contains("LNXPWRBN") {
+ let mut pm1 = pm1.lock();
+
+ pm1.status |= BITMASK_PM1STS_PWRBTN_STS;
+ pm1.trigger_sci(sci_evt);
+ }
+}
+
+fn get_acpi_event_group() -> Option<u32> {
+ // Create netlink generic socket which will be used to query about given family name
+ let netlink_ctrl_sock = match NetlinkGenericSocket::new(0) {
+ Ok(sock) => sock,
+ Err(e) => {
+ error!("netlink generic socket creation error: {}", e);
+ return None;
+ }
+ };
+
+ let nlmsg_family_response = netlink_ctrl_sock
+ .family_name_query("acpi_event".to_string())
+ .unwrap();
+ return nlmsg_family_response.get_multicast_group_id("acpi_mc_group".to_string());
+}
+
+fn acpi_event_run(
+ acpi_event_sock: &NetlinkGenericSocket,
+ gpe0: &Arc<Mutex<GpeResource>>,
+ pm1: &Arc<Mutex<Pm1Resource>>,
+ sci_evt: &IrqLevelEvent,
+ ignored_gpe: &[u32],
+) {
+ let nl_msg = match acpi_event_sock.recv() {
+ Ok(msg) => msg,
+ Err(e) => {
+ error!("recv returned with error {}", e);
+ return;
+ }
+ };
+
+ for netlink_message in nl_msg.iter() {
+ let acpi_event = match AcpiNotifyEvent::new(netlink_message) {
+ Ok(evt) => evt,
+ Err(e) => {
+ error!("Received netlink message is not an acpi_event, error {}", e);
+ continue;
+ }
+ };
+ match acpi_event.device_class.as_str() {
+ "gpe" => {
+ acpi_event_handle_gpe(
+ acpi_event.data,
+ acpi_event._type,
+ gpe0,
+ sci_evt,
+ ignored_gpe,
+ );
+ }
+ "button/power" => acpi_event_handle_power_button(acpi_event, pm1, sci_evt),
+ c => warn!("unknown acpi event {}", c),
+ };
+ }
+}
+
+impl Drop for ACPIPMResource {
+ fn drop(&mut self) {
+ if let Some(kill_evt) = self.kill_evt.take() {
+ let _ = kill_evt.write(1);
+ }
+
+ if let Some(worker_thread) = self.worker_thread.take() {
+ let _ = worker_thread.join();
+ }
+ }
+}
+
+impl Pm1Resource {
+ fn trigger_sci(&self, sci_evt: &IrqLevelEvent) {
+ if self.status
+ & self.enable
+ & (BITMASK_PM1EN_GBL_EN
+ | BITMASK_PM1EN_PWRBTN_EN
+ | BITMASK_PM1EN_SLPBTN_EN
+ | BITMASK_PM1EN_RTC_EN)
+ != 0
+ {
+ if let Err(e) = sci_evt.trigger() {
+ error!("ACPIPM: failed to trigger sci event for pm1: {}", e);
+ }
+ }
+ }
+}
+
+impl GpeResource {
+ fn trigger_sci(&self, sci_evt: &IrqLevelEvent) {
+ let mut trigger = false;
+ for i in 0..self.status.len() {
+ let gpes = self.status[i] & self.enable[i];
+ if gpes == 0 {
+ continue;
+ }
+
+ for j in 0..8 {
+ if gpes & (1 << j) == 0 {
+ continue;
+ }
+
+ let gpe_num: u32 = i as u32 * 8 + j;
+ if let Some(notify_devs) = self.gpe_notify.get(&gpe_num) {
+ for notify_dev in notify_devs.iter() {
+ notify_dev.lock().notify();
+ }
+ }
+ }
+ trigger = true;
+ }
+
+ if trigger {
+ if let Err(e) = sci_evt.trigger() {
+ error!("ACPIPM: failed to trigger sci event for gpe: {}", e);
+ }
+ }
+ }
+}
+
+#[cfg(feature = "direct")]
+impl DirectGpe {
+ fn new(gpe: u32) -> DirectGpe {
+ DirectGpe {
+ num: gpe,
+ path: PathBuf::from("/sys/firmware/acpi/interrupts").join(format!("gpe{:02X}", gpe)),
+ ready: false,
+ enabled: false,
+ }
+ }
+
+ fn is_status_set(&self) -> Result<bool, IoError> {
+ match fs::read_to_string(&self.path) {
+ Err(e) => {
+ error!("ACPIPM: failed to read gpe {} STS: {}", self.num, e);
+ Err(e)
+ }
+ Ok(s) => Ok(s.split_whitespace().any(|s| s == "STS")),
+ }
+ }
+
+ fn is_enabled(&self) -> Result<bool, IoError> {
+ match fs::read_to_string(&self.path) {
+ Err(e) => {
+ error!("ACPIPM: failed to read gpe {} EN: {}", self.num, e);
+ Err(e)
+ }
+ Ok(s) => Ok(s.split_whitespace().any(|s| s == "EN")),
+ }
+ }
+
+ fn clear(&self) {
+ if !self.is_status_set().unwrap_or(false) {
+ // Just to avoid harmless error messages due to clearing an already cleared GPE.
+ return;
+ }
+
+ if let Err(e) = fs::write(&self.path, "clear\n") {
+ error!("ACPIPM: failed to clear gpe {}: {}", self.num, e);
+ }
+ }
+
+ fn enable(&mut self) {
+ if self.enabled {
+ // Just to avoid harmless error messages due to enabling an already enabled GPE.
+ return;
+ }
+
+ if !self.ready {
+ // The GPE is being enabled for the first time.
+ // Use "enable" to ensure the ACPICA's reference count for this GPE is > 0.
+ match fs::write(&self.path, "enable\n") {
+ Err(e) => error!("ACPIPM: failed to enable gpe {}: {}", self.num, e),
+ Ok(()) => {
+ self.ready = true;
+ self.enabled = true;
+ }
+ }
+ } else {
+ // Use "unmask" instead of "enable", to bypass ACPICA's reference counting.
+ match fs::write(&self.path, "unmask\n") {
+ Err(e) => error!("ACPIPM: failed to unmask gpe {}: {}", self.num, e),
+ Ok(()) => {
+ self.enabled = true;
+ }
+ }
+ }
+ }
+
+ fn disable(&mut self) {
+ if !self.enabled {
+ // Just to avoid harmless error messages due to disabling an already disabled GPE.
+ return;
+ }
+
+ // Use "mask" instead of "disable", to bypass ACPICA's reference counting.
+ match fs::write(&self.path, "mask\n") {
+ Err(e) => error!("ACPIPM: failed to mask gpe {}: {}", self.num, e),
+ Ok(()) => {
+ self.enabled = false;
+ }
+ }
+ }
+
+ fn is_gpe_trigger(gpe: u32) -> Result<bool, IoError> {
+ let path = PathBuf::from("/sys/firmware/acpi/interrupts").join(format!("gpe{:02X}", gpe));
+ let s = fs::read_to_string(&path)?;
+ let mut enable = false;
+ let mut status = false;
+ for itr in s.split_whitespace() {
+ match itr {
+ "EN" => enable = true,
+ "STS" => status = true,
+ _ => (),
+ }
+ }
+
+ Ok(enable && status)
+ }
+}
+
/// the ACPI PM register length.
-pub const ACPIPM_RESOURCE_LEN: u8 = 8;
pub const ACPIPM_RESOURCE_EVENTBLK_LEN: u8 = 4;
pub const ACPIPM_RESOURCE_CONTROLBLK_LEN: u8 = 2;
+pub const ACPIPM_RESOURCE_GPE0_BLK_LEN: u8 = 64;
+pub const ACPIPM_RESOURCE_LEN: u8 = ACPIPM_RESOURCE_EVENTBLK_LEN + 4 + ACPIPM_RESOURCE_GPE0_BLK_LEN;
-/// ACPI PM register value definations
+/// ACPI PM register value definitions
+
+/// 4.8.4.1.1 PM1 Status Registers, ACPI Spec Version 6.4
+/// Register Location: <PM1a_EVT_BLK / PM1b_EVT_BLK> System I/O or Memory Space (defined in FADT)
+/// Size: PM1_EVT_LEN / 2 (defined in FADT)
const PM1_STATUS: u16 = 0;
-const PM1_ENABLE: u16 = 2;
-const PM1_CONTROL: u16 = 4;
-const SLEEP_CONTROL: u16 = 6;
-const SLEEP_STATUS: u16 = 7;
+
+/// 4.8.4.1.2 PM1Enable Registers, ACPI Spec Version 6.4
+/// Register Location: <<PM1a_EVT_BLK / PM1b_EVT_BLK> + PM1_EVT_LEN / 2 System I/O or Memory Space
+/// (defined in FADT)
+/// Size: PM1_EVT_LEN / 2 (defined in FADT)
+const PM1_ENABLE: u16 = PM1_STATUS + (ACPIPM_RESOURCE_EVENTBLK_LEN as u16 / 2);
+
+/// 4.8.4.2.1 PM1 Control Registers, ACPI Spec Version 6.4
+/// Register Location: <PM1a_CNT_BLK / PM1b_CNT_BLK> System I/O or Memory Space (defined in FADT)
+/// Size: PM1_CNT_LEN (defined in FADT)
+const PM1_CONTROL: u16 = PM1_STATUS + ACPIPM_RESOURCE_EVENTBLK_LEN as u16;
+
+/// 4.8.5.1 General-Purpose Event Register Blocks, ACPI Spec Version 6.4
+/// - Each register block contains two registers: an enable and a status register.
+/// - Each register block is 32-bit aligned.
+/// - Each register in the block is accessed as a byte.
+
+/// 4.8.5.1.1 General-Purpose Event 0 Register Block, ACPI Spec Version 6.4
+/// This register block consists of two registers: The GPE0_STS and the GPE0_EN registers. Each
+/// register’s length is defined to be half the length of the GPE0 register block, and is described
+/// in the ACPI FADT’s GPE0_BLK and GPE0_BLK_LEN operators.
+
+/// 4.8.5.1.1.1 General-Purpose Event 0 Status Register, ACPI Spec Version 6.4
+/// Register Location: <GPE0_STS> System I/O or System Memory Space (defined in FADT)
+/// Size: GPE0_BLK_LEN/2 (defined in FADT)
+const GPE0_STATUS: u16 = PM1_STATUS + ACPIPM_RESOURCE_EVENTBLK_LEN as u16 + 4; // ensure alignment
+
+/// 4.8.5.1.1.2 General-Purpose Event 0 Enable Register, ACPI Spec Version 6.4
+/// Register Location: <GPE0_EN> System I/O or System Memory Space (defined in FADT)
+/// Size: GPE0_BLK_LEN/2 (defined in FADT)
+const GPE0_ENABLE: u16 = GPE0_STATUS + (ACPIPM_RESOURCE_GPE0_BLK_LEN as u16 / 2);
+
+const BITMASK_PM1STS_PWRBTN_STS: u16 = 1 << 8;
+const BITMASK_PM1EN_GBL_EN: u16 = 1 << 5;
+const BITMASK_PM1EN_PWRBTN_EN: u16 = 1 << 8;
+const BITMASK_PM1EN_SLPBTN_EN: u16 = 1 << 9;
+const BITMASK_PM1EN_RTC_EN: u16 = 1 << 10;
const BITMASK_PM1CNT_SLEEP_ENABLE: u16 = 0x2000;
-const BITMASK_SLEEPCNT_SLEEP_ENABLE: u8 = 0x20;
const BITMASK_PM1CNT_WAKE_STATUS: u16 = 0x8000;
-const BITMASK_SLEEPCNT_WAKE_STATUS: u8 = 0x80;
+#[cfg(not(feature = "direct"))]
const BITMASK_PM1CNT_SLEEP_TYPE: u16 = 0x1C00;
-const SLEEP_TYPE_S5: u16 = 0;
+#[cfg(not(feature = "direct"))]
+const SLEEP_TYPE_S1: u16 = 1 << 10;
+#[cfg(not(feature = "direct"))]
+const SLEEP_TYPE_S5: u16 = 0 << 10;
+
+impl PmResource for ACPIPMResource {
+ fn pwrbtn_evt(&mut self) {
+ let mut pm1 = self.pm1.lock();
+
+ pm1.status |= BITMASK_PM1STS_PWRBTN_STS;
+ pm1.trigger_sci(&self.sci_evt);
+ }
+
+ fn gpe_evt(&mut self, gpe: u32) {
+ let mut gpe0 = self.gpe0.lock();
+
+ let byte = gpe as usize / 8;
+ if byte >= gpe0.status.len() {
+ error!("gpe_evt: GPE register {} does not exist", byte);
+ return;
+ }
+ gpe0.status[byte] |= 1 << (gpe % 8);
+ gpe0.trigger_sci(&self.sci_evt);
+ }
+
+ fn register_gpe_notify_dev(&mut self, gpe: u32, notify_dev: Arc<Mutex<dyn GpeNotify>>) {
+ let mut gpe0 = self.gpe0.lock();
+ match gpe0.gpe_notify.get_mut(&gpe) {
+ Some(v) => v.push(notify_dev),
+ None => {
+ gpe0.gpe_notify.insert(gpe, vec![notify_dev]);
+ }
+ }
+ }
+}
+
+const PM1_STATUS_LAST: u16 = PM1_STATUS + (ACPIPM_RESOURCE_EVENTBLK_LEN as u16 / 2) - 1;
+const PM1_ENABLE_LAST: u16 = PM1_ENABLE + (ACPIPM_RESOURCE_EVENTBLK_LEN as u16 / 2) - 1;
+const PM1_CONTROL_LAST: u16 = PM1_CONTROL + ACPIPM_RESOURCE_CONTROLBLK_LEN as u16 - 1;
+const GPE0_STATUS_LAST: u16 = GPE0_STATUS + (ACPIPM_RESOURCE_GPE0_BLK_LEN as u16 / 2) - 1;
+const GPE0_ENABLE_LAST: u16 = GPE0_ENABLE + (ACPIPM_RESOURCE_GPE0_BLK_LEN as u16 / 2) - 1;
impl BusDevice for ACPIPMResource {
fn debug_label(&self) -> String {
@@ -57,71 +623,224 @@ impl BusDevice for ACPIPMResource {
}
fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
- let val = match info.offset as u16 {
- PM1_STATUS => self.pm1_status,
- PM1_ENABLE => self.pm1_enable,
- PM1_CONTROL => self.pm1_control,
- SLEEP_CONTROL => self.sleep_control as u16,
- SLEEP_STATUS => self.sleep_status as u16,
- _ => {
- warn!("ACPIPM: Bad read from {}", info);
- return;
+ match info.offset as u16 {
+ // Accesses to the PM1 registers are done through byte or word accesses
+ PM1_STATUS..=PM1_STATUS_LAST => {
+ if data.len() > std::mem::size_of::<u16>()
+ || info.offset + data.len() as u64 > (PM1_STATUS_LAST + 1).into()
+ {
+ warn!("ACPIPM: bad read size: {}", data.len());
+ return;
+ }
+ let offset = (info.offset - PM1_STATUS as u64) as usize;
+ data.copy_from_slice(
+ &self.pm1.lock().status.to_ne_bytes()[offset..offset + data.len()],
+ );
}
- };
+ PM1_ENABLE..=PM1_ENABLE_LAST => {
+ if data.len() > std::mem::size_of::<u16>()
+ || info.offset + data.len() as u64 > (PM1_ENABLE_LAST + 1).into()
+ {
+ warn!("ACPIPM: bad read size: {}", data.len());
+ return;
+ }
+ let offset = (info.offset - PM1_ENABLE as u64) as usize;
+ data.copy_from_slice(
+ &self.pm1.lock().enable.to_ne_bytes()[offset..offset + data.len()],
+ );
+ }
+ PM1_CONTROL..=PM1_CONTROL_LAST => {
+ if data.len() > std::mem::size_of::<u16>()
+ || info.offset + data.len() as u64 > (PM1_CONTROL_LAST + 1).into()
+ {
+ warn!("ACPIPM: bad read size: {}", data.len());
+ return;
+ }
+ let offset = (info.offset - PM1_CONTROL as u64) as usize;
+ data.copy_from_slice(
+ &self.pm1.lock().control.to_ne_bytes()[offset..offset + data.len()],
+ );
+ }
+ // OSPM accesses GPE registers through byte accesses (regardless of their length)
+ GPE0_STATUS..=GPE0_STATUS_LAST => {
+ if data.len() > std::mem::size_of::<u8>()
+ || info.offset + data.len() as u64 > (GPE0_STATUS_LAST + 1).into()
+ {
+ warn!("ACPIPM: bad read size: {}", data.len());
+ return;
+ }
+ let offset = (info.offset - GPE0_STATUS as u64) as usize;
+ data[0] = self.gpe0.lock().status[offset];
- let val_arr = val.to_ne_bytes();
- for i in 0..std::mem::size_of::<u16>() {
- if i < data.len() {
- data[i] = val_arr[i];
+ #[cfg(feature = "direct")]
+ for gpe in self
+ .direct_gpe
+ .iter()
+ .filter(|gpe| gpe.num / 8 == offset as u32)
+ {
+ data[0] &= !(1 << (gpe.num % 8));
+ if gpe.is_status_set().unwrap_or(false) {
+ data[0] |= 1 << (gpe.num % 8);
+ }
+ }
+ }
+ GPE0_ENABLE..=GPE0_ENABLE_LAST => {
+ if data.len() > std::mem::size_of::<u8>()
+ || info.offset + data.len() as u64 > (GPE0_ENABLE_LAST + 1).into()
+ {
+ warn!("ACPIPM: bad read size: {}", data.len());
+ return;
+ }
+ let offset = (info.offset - GPE0_ENABLE as u64) as usize;
+ data[0] = self.gpe0.lock().enable[offset];
+
+ #[cfg(feature = "direct")]
+ for gpe in self
+ .direct_gpe
+ .iter()
+ .filter(|gpe| gpe.num / 8 == offset as u32)
+ {
+ data[0] &= !(1 << (gpe.num % 8));
+ if gpe.is_enabled().unwrap_or(false) {
+ data[0] |= 1 << (gpe.num % 8);
+ }
+ }
+ }
+ _ => {
+ warn!("ACPIPM: Bad read from {}", info);
}
}
}
fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
- let max_bytes = std::mem::size_of::<u16>();
+ match info.offset as u16 {
+ // Accesses to the PM1 registers are done through byte or word accesses
+ PM1_STATUS..=PM1_STATUS_LAST => {
+ if data.len() > std::mem::size_of::<u16>()
+ || info.offset + data.len() as u64 > (PM1_STATUS_LAST + 1).into()
+ {
+ warn!("ACPIPM: bad write size: {}", data.len());
+ return;
+ }
+ let offset = (info.offset - PM1_STATUS as u64) as usize;
- // only allow maximum max_bytes to write
- if data.len() > max_bytes {
- warn!("ACPIPM: bad write size: {}", data.len());
- return;
- }
+ let mut pm1 = self.pm1.lock();
+ let mut v = pm1.status.to_ne_bytes();
+ for (i, j) in (offset..offset + data.len()).enumerate() {
+ v[j] &= !data[i];
+ }
+ pm1.status = u16::from_ne_bytes(v);
+ }
+ PM1_ENABLE..=PM1_ENABLE_LAST => {
+ if data.len() > std::mem::size_of::<u16>()
+ || info.offset + data.len() as u64 > (PM1_ENABLE_LAST + 1).into()
+ {
+ warn!("ACPIPM: bad write size: {}", data.len());
+ return;
+ }
+ let offset = (info.offset - PM1_ENABLE as u64) as usize;
- let mut val_arr = u16::to_ne_bytes(0u16);
- for i in 0..std::mem::size_of::<u16>() {
- if i < data.len() {
- val_arr[i] = data[i];
+ let mut pm1 = self.pm1.lock();
+ let mut v = pm1.enable.to_ne_bytes();
+ for (i, j) in (offset..offset + data.len()).enumerate() {
+ v[j] = data[i];
+ }
+ pm1.enable = u16::from_ne_bytes(v);
+ pm1.trigger_sci(&self.sci_evt);
}
- }
- let val = u16::from_ne_bytes(val_arr);
+ PM1_CONTROL..=PM1_CONTROL_LAST => {
+ if data.len() > std::mem::size_of::<u16>()
+ || info.offset + data.len() as u64 > (PM1_CONTROL_LAST + 1).into()
+ {
+ warn!("ACPIPM: bad write size: {}", data.len());
+ return;
+ }
+ let offset = (info.offset - PM1_CONTROL as u64) as usize;
- match info.offset as u16 {
- PM1_STATUS => self.pm1_status &= !val,
- PM1_ENABLE => self.pm1_enable = val,
- PM1_CONTROL => {
- if (val & BITMASK_PM1CNT_SLEEP_ENABLE) == BITMASK_PM1CNT_SLEEP_ENABLE {
- if val & BITMASK_PM1CNT_SLEEP_TYPE == SLEEP_TYPE_S5 {
- if let Err(e) = self.exit_evt.write(1) {
- error!("ACPIPM: failed to trigger exit event: {}", e);
+ let mut pm1 = self.pm1.lock();
+
+ let mut v = pm1.control.to_ne_bytes();
+ for (i, j) in (offset..offset + data.len()).enumerate() {
+ v[j] = data[i];
+ }
+ let val = u16::from_ne_bytes(v);
+
+ // SLP_EN is a write-only bit and reads to it always return a zero
+ if (val & BITMASK_PM1CNT_SLEEP_ENABLE) != 0 {
+ // only support S5 in direct mode
+ #[cfg(feature = "direct")]
+ if let Err(e) = self.exit_evt.write(1) {
+ error!("ACPIPM: failed to trigger exit event: {}", e);
+ }
+ #[cfg(not(feature = "direct"))]
+ match val & BITMASK_PM1CNT_SLEEP_TYPE {
+ SLEEP_TYPE_S1 => {
+ if let Err(e) = self.suspend_evt.write(1) {
+ error!("ACPIPM: failed to trigger suspend event: {}", e);
+ }
}
- } else {
- if let Err(e) = self.suspend_evt.write(1) {
- error!("ACPIPM: failed to trigger suspend event: {}", e);
+ SLEEP_TYPE_S5 => {
+ if let Err(e) = self.exit_evt.write(1) {
+ error!("ACPIPM: failed to trigger exit event: {}", e);
+ }
}
+ _ => error!(
+ "ACPIPM: unknown SLP_TYP written: {}",
+ (val & BITMASK_PM1CNT_SLEEP_TYPE) >> 10
+ ),
}
}
- self.pm1_control = val & !BITMASK_PM1CNT_SLEEP_ENABLE;
+ pm1.control = val & !BITMASK_PM1CNT_SLEEP_ENABLE;
}
- SLEEP_CONTROL => {
- let sleep_control = val as u8;
- if (sleep_control & BITMASK_SLEEPCNT_SLEEP_ENABLE) == BITMASK_SLEEPCNT_SLEEP_ENABLE
+ // OSPM accesses GPE registers through byte accesses (regardless of their length)
+ GPE0_STATUS..=GPE0_STATUS_LAST => {
+ if data.len() > std::mem::size_of::<u8>()
+ || info.offset + data.len() as u64 > (GPE0_STATUS_LAST + 1).into()
{
- if let Err(e) = self.suspend_evt.write(1) {
- error!("ACPIPM: failed to trigger suspend event: {}", e);
+ warn!("ACPIPM: bad write size: {}", data.len());
+ return;
+ }
+ let offset = (info.offset - GPE0_STATUS as u64) as usize;
+
+ #[cfg(feature = "direct")]
+ for gpe in self
+ .direct_gpe
+ .iter()
+ .filter(|gpe| gpe.num / 8 == offset as u32)
+ {
+ if data[0] & (1 << (gpe.num % 8)) != 0 {
+ gpe.clear();
+ }
+ }
+
+ self.gpe0.lock().status[offset] &= !data[0];
+ }
+ GPE0_ENABLE..=GPE0_ENABLE_LAST => {
+ if data.len() > std::mem::size_of::<u8>()
+ || info.offset + data.len() as u64 > (GPE0_ENABLE_LAST + 1).into()
+ {
+ warn!("ACPIPM: bad write size: {}", data.len());
+ return;
+ }
+ let offset = (info.offset - GPE0_ENABLE as u64) as usize;
+
+ #[cfg(feature = "direct")]
+ for gpe in self
+ .direct_gpe
+ .iter_mut()
+ .filter(|gpe| gpe.num / 8 == offset as u32)
+ {
+ if data[0] & (1 << (gpe.num % 8)) != 0 {
+ gpe.enable();
+ } else {
+ gpe.disable();
}
}
- self.sleep_control = sleep_control as u8 & !BITMASK_SLEEPCNT_SLEEP_ENABLE;
+
+ let mut gpe = self.gpe0.lock();
+ gpe.enable[offset] = data[0];
+ gpe.trigger_sci(&self.sci_evt);
}
- SLEEP_STATUS => self.sleep_status &= !val as u8,
_ => {
warn!("ACPIPM: Bad write to {}", info);
}
@@ -131,11 +850,7 @@ impl BusDevice for ACPIPMResource {
impl BusResumeDevice for ACPIPMResource {
fn resume_imminent(&mut self) {
- let val = self.pm1_status;
- self.pm1_status = val | BITMASK_PM1CNT_WAKE_STATUS;
-
- let val = self.sleep_status;
- self.sleep_status = val | BITMASK_SLEEPCNT_WAKE_STATUS;
+ self.pm1.lock().status |= BITMASK_PM1CNT_WAKE_STATUS;
}
}
diff --git a/devices/src/bat.rs b/devices/src/bat.rs
index 95f3cd70a..63add059a 100644
--- a/devices/src/bat.rs
+++ b/devices/src/bat.rs
@@ -2,34 +2,27 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use crate::{BusAccessInfo, BusDevice};
+use crate::{BusAccessInfo, BusDevice, IrqLevelEvent};
use acpi_tables::{aml, aml::Aml};
use base::{
error, warn, AsRawDescriptor, Descriptor, Event, PollToken, RawDescriptor, Tube, WaitContext,
};
use power_monitor::{BatteryStatus, CreatePowerMonitorFn};
-use std::fmt::{self, Display};
+use remain::sorted;
use std::sync::Arc;
use std::thread;
use sync::Mutex;
+use thiserror::Error;
use vm_control::{BatControlCommand, BatControlResult};
/// Errors for battery devices.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum BatteryError {
+ #[error("Non 32-bit mmio address space")]
Non32BitMmioAddress,
}
-impl Display for BatteryError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::BatteryError::*;
-
- match self {
- Non32BitMmioAddress => write!(f, "Non 32-bit mmio address space"),
- }
- }
-}
-
type Result<T> = std::result::Result<T, BatteryError>;
/// the GoldFish Battery MMIO length.
@@ -100,8 +93,7 @@ pub struct GoldfishBattery {
state: Arc<Mutex<GoldfishBatteryState>>,
mmio_base: u32,
irq_num: u32,
- irq_evt: Event,
- irq_resample_evt: Event,
+ irq_evt: IrqLevelEvent,
activated: bool,
monitor_thread: Option<thread::JoinHandle<()>>,
kill_evt: Option<Event>,
@@ -143,8 +135,7 @@ const BATTERY_HEALTH_VAL_UNKNOWN: u32 = 0;
fn command_monitor(
tube: Tube,
- irq_evt: Event,
- irq_resample_evt: Event,
+ irq_evt: IrqLevelEvent,
kill_evt: Event,
state: Arc<Mutex<GoldfishBatteryState>>,
create_power_monitor: Option<Box<dyn CreatePowerMonitorFn>>,
@@ -152,7 +143,7 @@ fn command_monitor(
let wait_ctx: WaitContext<Token> = match WaitContext::build_with(&[
(&Descriptor(tube.as_raw_descriptor()), Token::Commands),
(
- &Descriptor(irq_resample_evt.as_raw_descriptor()),
+ &Descriptor(irq_evt.get_resample().as_raw_descriptor()),
Token::Resample,
),
(&Descriptor(kill_evt.as_raw_descriptor()), Token::Kill),
@@ -228,7 +219,7 @@ fn command_monitor(
};
if inject_irq {
- let _ = irq_evt.write(1);
+ let _ = irq_evt.trigger();
}
if let Err(e) = tube.send(&BatControlResult::Ok) {
@@ -276,14 +267,14 @@ fn command_monitor(
}
if inject_irq {
- let _ = irq_evt.write(1);
+ let _ = irq_evt.trigger();
}
}
Token::Resample => {
- let _ = irq_resample_evt.read();
+ irq_evt.clear_resample();
if state.lock().int_status() != 0 {
- let _ = irq_evt.write(1);
+ let _ = irq_evt.trigger();
}
}
@@ -301,13 +292,11 @@ impl GoldfishBattery {
/// which will be put into the ACPI DSDT.
/// * `irq_evt` - The interrupt event used to notify driver about
/// the battery properties changing.
- /// * `irq_resample_evt` - Resample interrupt event notified at EOI.
/// * `socket` - Battery control socket
pub fn new(
mmio_base: u64,
irq_num: u32,
- irq_evt: Event,
- irq_resample_evt: Event,
+ irq_evt: IrqLevelEvent,
tube: Tube,
create_power_monitor: Option<Box<dyn CreatePowerMonitorFn>>,
) -> Result<Self> {
@@ -333,7 +322,6 @@ impl GoldfishBattery {
mmio_base: mmio_base as u32,
irq_num,
irq_evt,
- irq_resample_evt,
activated: false,
monitor_thread: None,
kill_evt: None,
@@ -345,8 +333,8 @@ impl GoldfishBattery {
/// return the fds used by this device
pub fn keep_rds(&self) -> Vec<RawDescriptor> {
let mut rds = vec![
- self.irq_evt.as_raw_descriptor(),
- self.irq_resample_evt.as_raw_descriptor(),
+ self.irq_evt.get_trigger().as_raw_descriptor(),
+ self.irq_evt.get_resample().as_raw_descriptor(),
];
if let Some(tube) = &self.tube {
@@ -376,21 +364,13 @@ impl GoldfishBattery {
if let Some(tube) = self.tube.take() {
let irq_evt = self.irq_evt.try_clone().unwrap();
- let irq_resample_evt = self.irq_resample_evt.try_clone().unwrap();
let bat_state = self.state.clone();
let create_monitor_fn = self.create_power_monitor.take();
let monitor_result = thread::Builder::new()
.name(self.debug_label())
.spawn(move || {
- command_monitor(
- tube,
- irq_evt,
- irq_resample_evt,
- kill_evt,
- bat_state,
- create_monitor_fn,
- );
+ command_monitor(tube, irq_evt, kill_evt, bat_state, create_monitor_fn);
});
self.monitor_thread = match monitor_result {
diff --git a/devices/src/bus.rs b/devices/src/bus.rs
index 90360cd9d..f93190f87 100644
--- a/devices/src/bus.rs
+++ b/devices/src/bus.rs
@@ -6,12 +6,16 @@
use std::cmp::{Ord, Ordering, PartialEq, PartialOrd};
use std::collections::btree_map::BTreeMap;
-use std::fmt::{self, Display};
+use std::fmt;
use std::result;
use std::sync::Arc;
+use remain::sorted;
use serde::{Deserialize, Serialize};
use sync::Mutex;
+use thiserror::Error;
+
+use crate::{PciAddress, PciDevice, VfioPlatformDevice};
/// Information about how a device was accessed.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)]
@@ -33,21 +37,29 @@ impl std::fmt::Display for BusAccessInfo {
/// Result of a write to a device's PCI configuration space.
/// This value represents the state change(s) that occurred due to the write.
-/// Each member of this structure may be `None` if no change occurred, or `Some(new_value)` to
-/// indicate a state change.
-#[derive(Copy, Clone, Debug, Default, PartialEq)]
+#[derive(Clone, Debug, Default, PartialEq)]
pub struct ConfigWriteResult {
- /// New state of the memory bus for this PCI device:
- /// - `None`: no change in state.
- /// - `Some(true)`: memory decode enabled; device should respond to memory accesses.
- /// - `Some(false)`: memory decode disabled; device should not respond to memory accesses.
- pub mem_bus_new_state: Option<bool>,
-
- /// New state of the I/O bus for this PCI device:
- /// - `None`: no change in state.
- /// - `Some(true)`: I/O decode enabled; device should respond to I/O accesses.
- /// - `Some(false)`: I/O decode disabled; device should not respond to I/O accesses.
- pub io_bus_new_state: Option<bool>,
+ /// The BusRange in the vector will be removed from mmio_bus
+ pub mmio_remove: Vec<BusRange>,
+
+ /// The BusRange in the vector will be added into mmio_bus
+ pub mmio_add: Vec<BusRange>,
+
+ /// The BusRange in the vector will be removed from io_bus
+ pub io_remove: Vec<BusRange>,
+
+ /// The BusRange in the vector will be added into io_bus
+ pub io_add: Vec<BusRange>,
+
+ /// Device specified at PciAddress will be removed after this config write
+ /// - 'Vec<PciAddress>>': specified device will be removed after this config write
+ pub removed_pci_devices: Vec<PciAddress>,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub enum BusType {
+ Mmio,
+ Io,
}
/// Trait for devices that respond to reads or writes in an arbitrary address space.
@@ -58,6 +70,12 @@ pub struct ConfigWriteResult {
pub trait BusDevice: Send {
/// Returns a label suitable for debug output.
fn debug_label(&self) -> String;
+
+ /// Returns a unique id per device type suitable for metrics gathering.
+ // TODO(225991065): Remove this default implementation when all of the crate is upstreamed.
+ fn device_id(&self) -> u32 {
+ 0
+ }
/// Reads at `offset` from this device
fn read(&mut self, offset: BusAccessInfo, data: &mut [u8]) {}
/// Writes at `offset` into this device
@@ -80,8 +98,25 @@ pub trait BusDevice: Send {
fn config_register_read(&self, reg_idx: usize) -> u32 {
0
}
+ /// Sets a register in the virtual config space. Only used by PCI.
+ /// * `reg_idx` - The index of the config register to modify.
+ /// * `value` - The value to be written.
+ fn virtual_config_register_write(&mut self, reg_idx: usize, value: u32) {}
+ /// Gets a register from the virtual config space. Only used by PCI.
+ /// * `reg_idx` - The index of the config register to read.
+ fn virtual_config_register_read(&self, reg_idx: usize) -> u32 {
+ 0
+ }
/// Invoked when the device is sandboxed.
fn on_sandboxed(&mut self) {}
+
+ /// Gets a list of all ranges registered by this BusDevice.
+ fn get_ranges(&self) -> Vec<(BusRange, BusType)> {
+ Vec::new()
+ }
+
+ /// Invoked when the device is destroyed
+ fn destroy_device(&mut self) {}
}
pub trait BusDeviceSync: BusDevice + Sync {
@@ -95,20 +130,68 @@ pub trait BusResumeDevice: Send {
fn resume_imminent(&mut self) {}
}
-#[derive(Debug)]
-pub enum Error {
- /// The insertion failed because the new device overlapped with an old device.
- Overlap,
+/// The key to identify hotplug device from host view.
+/// like host sysfs path for vfio pci device, host disk file
+/// path for virtio block device
+#[derive(Copy, Clone)]
+pub enum HostHotPlugKey {
+ Vfio { host_addr: PciAddress },
}
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
+/// Trait for devices that notify hotplug event into guest
+pub trait HotPlugBus {
+ /// Notify hotplug in event into guest
+ /// * 'addr' - the guest pci address for hotplug in device
+ fn hot_plug(&mut self, addr: PciAddress);
+ /// Notify hotplug out event into guest
+ /// * 'addr' - the guest pci address for hotplug out device
+ fn hot_unplug(&mut self, addr: PciAddress);
+ /// Check whether the hotplug bus is available to add the new device
+ ///
+ /// - 'None': hotplug bus isn't match with host pci device
+ /// - 'Some(bus_num)': hotplug bus is match and put the device at bus_num
+ fn is_match(&self, host_addr: PciAddress) -> Option<u8>;
+ /// Add hotplug device into this bus
+ /// * 'host_key' - the key to identify hotplug device from host view
+ /// * 'guest_addr' - the guest pci address for hotplug device
+ fn add_hotplug_device(&mut self, host_key: HostHotPlugKey, guest_addr: PciAddress);
+ /// get guest pci address from the specified host_key
+ fn get_hotplug_device(&self, host_key: HostHotPlugKey) -> Option<PciAddress>;
+}
- match self {
- Overlap => write!(f, "new device overlaps with an old device"),
- }
+/// Trait for generic device abstraction, that is, all devices that reside on BusDevice and want
+/// to be converted back to its original type. Each new foo device must provide
+/// as_foo_device() + as_foo_device_mut() + into_foo_device(), default impl methods return None.
+pub trait BusDeviceObj {
+ fn as_pci_device(&self) -> Option<&dyn PciDevice> {
+ None
+ }
+ fn as_pci_device_mut(&mut self) -> Option<&mut dyn PciDevice> {
+ None
+ }
+ fn into_pci_device(self: Box<Self>) -> Option<Box<dyn PciDevice>> {
+ None
+ }
+
+ fn as_platform_device(&self) -> Option<&VfioPlatformDevice> {
+ None
}
+ fn as_platform_device_mut(&mut self) -> Option<&mut VfioPlatformDevice> {
+ None
+ }
+ fn into_platform_device(self: Box<Self>) -> Option<Box<VfioPlatformDevice>> {
+ None
+ }
+}
+
+#[sorted]
+#[derive(Error, Debug)]
+pub enum Error {
+ #[error("Bus Range not found")]
+ Empty,
+ /// The insertion failed because the new device overlapped with an old device.
+ #[error("new device overlaps with an old device")]
+ Overlap,
}
pub type Result<T> = result::Result<T, Error>;
@@ -117,7 +200,7 @@ pub type Result<T> = result::Result<T, Error>;
///
/// * base - The address at which the range start.
/// * len - The length of the range in bytes.
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct BusRange {
pub base: u64,
pub len: u64,
@@ -165,13 +248,9 @@ enum BusDeviceEntry {
///
/// This doesn't have any restrictions on what kind of device or address space this applies to. The
/// only restriction is that no two devices can overlap in this address space.
-///
-/// the 'resume_notify_devices' contains the devices which requires to be notified before the system
-/// resume back from S3 suspended state.
#[derive(Clone)]
pub struct Bus {
- devices: BTreeMap<BusRange, BusDeviceEntry>,
- resume_notify_devices: Vec<Arc<Mutex<dyn BusResumeDevice>>>,
+ devices: Arc<Mutex<BTreeMap<BusRange, BusDeviceEntry>>>,
access_id: usize,
}
@@ -179,8 +258,7 @@ impl Bus {
/// Constructs an a bus with an empty address space.
pub fn new() -> Bus {
Bus {
- devices: BTreeMap::new(),
- resume_notify_devices: Vec::new(),
+ devices: Arc::new(Mutex::new(BTreeMap::new())),
access_id: 0,
}
}
@@ -190,16 +268,16 @@ impl Bus {
self.access_id = id;
}
- fn first_before(&self, addr: u64) -> Option<(BusRange, &BusDeviceEntry)> {
- let (range, dev) = self
- .devices
+ fn first_before(&self, addr: u64) -> Option<(BusRange, BusDeviceEntry)> {
+ let devices = self.devices.lock();
+ let (range, dev) = devices
.range(..=BusRange { base: addr, len: 1 })
.rev()
.next()?;
- Some((*range, dev))
+ Some((*range, dev.clone()))
}
- fn get_device(&self, addr: u64) -> Option<(u64, u64, &BusDeviceEntry)> {
+ fn get_device(&self, addr: u64) -> Option<(u64, u64, BusDeviceEntry)> {
if let Some((range, dev)) = self.first_before(addr) {
let offset = addr - range.base;
if offset < range.len {
@@ -210,22 +288,21 @@ impl Bus {
}
/// Puts the given device at the given address space.
- pub fn insert(&mut self, device: Arc<Mutex<dyn BusDevice>>, base: u64, len: u64) -> Result<()> {
+ pub fn insert(&self, device: Arc<Mutex<dyn BusDevice>>, base: u64, len: u64) -> Result<()> {
if len == 0 {
return Err(Error::Overlap);
}
// Reject all cases where the new device's range overlaps with an existing device.
- if self
- .devices
+ let mut devices = self.devices.lock();
+ if devices
.iter()
.any(|(range, _dev)| range.overlaps(base, len))
{
return Err(Error::Overlap);
}
- if self
- .devices
+ if devices
.insert(BusRange { base, len }, BusDeviceEntry::OuterSync(device))
.is_some()
{
@@ -238,27 +315,21 @@ impl Bus {
/// Puts the given device that implements BusDeviceSync at the given address space. Devices
/// that implement BusDeviceSync manage thread safety internally, and thus can be written to
/// by multiple threads simultaneously.
- pub fn insert_sync(
- &mut self,
- device: Arc<dyn BusDeviceSync>,
- base: u64,
- len: u64,
- ) -> Result<()> {
+ pub fn insert_sync(&self, device: Arc<dyn BusDeviceSync>, base: u64, len: u64) -> Result<()> {
if len == 0 {
return Err(Error::Overlap);
}
// Reject all cases where the new device's range overlaps with an existing device.
- if self
- .devices
+ let mut devices = self.devices.lock();
+ if devices
.iter()
.any(|(range, _dev)| range.overlaps(base, len))
{
return Err(Error::Overlap);
}
- if self
- .devices
+ if devices
.insert(BusRange { base, len }, BusDeviceEntry::InnerSync(device))
.is_some()
{
@@ -268,6 +339,28 @@ impl Bus {
Ok(())
}
+ /// Remove the given device at the given address space.
+ pub fn remove(&self, base: u64, len: u64) -> Result<()> {
+ if len == 0 {
+ return Err(Error::Overlap);
+ }
+
+ let mut devices = self.devices.lock();
+ if devices
+ .iter()
+ .any(|(range, _dev)| range.base == base && range.len == len)
+ {
+ let ret = devices.remove(&BusRange { base, len });
+ if ret.is_some() {
+ Ok(())
+ } else {
+ Err(Error::Empty)
+ }
+ } else {
+ Err(Error::Empty)
+ }
+ }
+
/// Reads data from the device that owns the range containing `addr` and puts it into `data`.
///
/// Returns true on success, otherwise `data` is untouched.
@@ -307,19 +400,6 @@ impl Bus {
false
}
}
-
- /// Register `device` for notifications of VM resume from suspend.
- pub fn notify_on_resume(&mut self, device: Arc<Mutex<dyn BusResumeDevice>>) {
- self.resume_notify_devices.push(device);
- }
-
- /// Call `notify_resume` to notify the device that suspend resume is imminent.
- pub fn notify_resume(&mut self) {
- let devices = self.resume_notify_devices.clone();
- for dev in devices {
- dev.lock().resume_imminent();
- }
- }
}
#[cfg(test)]
@@ -367,7 +447,7 @@ mod tests {
#[test]
fn bus_insert() {
- let mut bus = Bus::new();
+ let bus = Bus::new();
let dummy = Arc::new(Mutex::new(DummyDevice));
assert!(bus.insert(dummy.clone(), 0x10, 0).is_err());
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
@@ -379,12 +459,12 @@ mod tests {
assert!(bus.insert(dummy.clone(), 0x0, 0x20).is_err());
assert!(bus.insert(dummy.clone(), 0x20, 0x05).is_ok());
assert!(bus.insert(dummy.clone(), 0x25, 0x05).is_ok());
- assert!(bus.insert(dummy.clone(), 0x0, 0x10).is_ok());
+ assert!(bus.insert(dummy, 0x0, 0x10).is_ok());
}
#[test]
fn bus_insert_full_addr() {
- let mut bus = Bus::new();
+ let bus = Bus::new();
let dummy = Arc::new(Mutex::new(DummyDevice));
assert!(bus.insert(dummy.clone(), 0x10, 0).is_err());
assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
@@ -396,14 +476,14 @@ mod tests {
assert!(bus.insert(dummy.clone(), 0x0, 0x20).is_err());
assert!(bus.insert(dummy.clone(), 0x20, 0x05).is_ok());
assert!(bus.insert(dummy.clone(), 0x25, 0x05).is_ok());
- assert!(bus.insert(dummy.clone(), 0x0, 0x10).is_ok());
+ assert!(bus.insert(dummy, 0x0, 0x10).is_ok());
}
#[test]
fn bus_read_write() {
- let mut bus = Bus::new();
+ let bus = Bus::new();
let dummy = Arc::new(Mutex::new(DummyDevice));
- assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
+ assert!(bus.insert(dummy, 0x10, 0x10).is_ok());
assert!(bus.read(0x10, &mut [0, 0, 0, 0]));
assert!(bus.write(0x10, &[0, 0, 0, 0]));
assert!(bus.read(0x11, &mut [0, 0, 0, 0]));
@@ -411,18 +491,18 @@ mod tests {
assert!(bus.read(0x16, &mut [0, 0, 0, 0]));
assert!(bus.write(0x16, &[0, 0, 0, 0]));
assert!(!bus.read(0x20, &mut [0, 0, 0, 0]));
- assert!(!bus.write(0x20, &mut [0, 0, 0, 0]));
+ assert!(!bus.write(0x20, &[0, 0, 0, 0]));
assert!(!bus.read(0x06, &mut [0, 0, 0, 0]));
- assert!(!bus.write(0x06, &mut [0, 0, 0, 0]));
+ assert!(!bus.write(0x06, &[0, 0, 0, 0]));
}
#[test]
fn bus_read_write_values() {
- let mut bus = Bus::new();
+ let bus = Bus::new();
let dummy = Arc::new(Mutex::new(ConstantDevice {
uses_full_addr: false,
}));
- assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
+ assert!(bus.insert(dummy, 0x10, 0x10).is_ok());
let mut values = [0, 1, 2, 3];
assert!(bus.read(0x10, &mut values));
@@ -435,11 +515,11 @@ mod tests {
#[test]
fn bus_read_write_full_addr_values() {
- let mut bus = Bus::new();
+ let bus = Bus::new();
let dummy = Arc::new(Mutex::new(ConstantDevice {
uses_full_addr: true,
}));
- assert!(bus.insert(dummy.clone(), 0x10, 0x10).is_ok());
+ assert!(bus.insert(dummy, 0x10, 0x10).is_ok());
let mut values = [0u8; 4];
assert!(bus.read(0x10, &mut values));
diff --git a/devices/src/direct_io.rs b/devices/src/direct_io.rs
index 2f80d7eee..8d5353d52 100644
--- a/devices/src/direct_io.rs
+++ b/devices/src/direct_io.rs
@@ -2,9 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use crate::{BusAccessInfo, BusDevice, BusDeviceSync};
+use crate::{BusAccessInfo, BusDevice, BusDeviceSync, BusRange};
+use base::{
+ error, pagesize, round_up_to_page_size, MemoryMapping, MemoryMappingBuilder, Protection,
+};
use std::fs::{File, OpenOptions};
use std::io;
+use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::prelude::FileExt;
use std::path::Path;
use std::sync::Mutex;
@@ -66,3 +70,130 @@ impl BusDeviceSync for DirectIo {
self.iowr(ai.address, data);
}
}
+
+pub struct DirectMmio {
+ dev: Mutex<Vec<(BusRange, MemoryMapping)>>,
+ read_only: bool,
+}
+
+impl DirectMmio {
+ /// Create simple direct mmio access device.
+ pub fn new(path: &Path, read_only: bool, ranges: &[BusRange]) -> Result<Self, io::Error> {
+ let dev = OpenOptions::new()
+ .read(true)
+ .write(!read_only)
+ .custom_flags(libc::O_SYNC)
+ .open(path)?;
+ let mut mmap_info = Vec::new();
+
+ let protection = if read_only {
+ Protection::read()
+ } else {
+ Protection::read_write()
+ };
+
+ for range in ranges {
+ // set to the page start
+ let start = range.base & (!((pagesize() - 1) as u64));
+ // set to the next page of the end address
+ let end = round_up_to_page_size((range.base + range.len) as usize);
+ let len = end - start as usize;
+ let mmap = match MemoryMappingBuilder::new(len)
+ .from_file(&dev)
+ .offset(start)
+ .protection(protection)
+ .build()
+ {
+ Ok(m) => m,
+ Err(e) => {
+ error!(
+ "failed to create mmap for mmio: {:x} ~ {:x}, error: {}",
+ range.base,
+ range.base + range.len,
+ e
+ );
+ continue;
+ }
+ };
+
+ mmap_info.push((*range, mmap));
+ }
+
+ Ok(DirectMmio {
+ dev: Mutex::new(mmap_info),
+ read_only,
+ })
+ }
+
+ fn iowr(&self, ai: BusAccessInfo, data: &[u8]) {
+ if self.read_only {
+ return;
+ }
+
+ let dev = match self.dev.lock() {
+ Ok(d) => d,
+ Err(_) => return,
+ };
+
+ for (range, mmap) in dev.iter() {
+ if !range.contains(ai.address) || !range.contains(ai.address + data.len() as u64) {
+ continue;
+ }
+
+ let page_mask = (pagesize() - 1) as u64;
+ let offset = (range.base & page_mask) + ai.offset;
+ if let Err(e) = mmap.write_slice(data, offset as usize) {
+ error!("write mmio {:x} error, {}", ai.address, e);
+ }
+ return;
+ }
+ }
+
+ fn iord(&self, ai: BusAccessInfo, data: &mut [u8]) {
+ let dev = match self.dev.lock() {
+ Ok(d) => d,
+ Err(_) => return,
+ };
+
+ for (range, mmap) in dev.iter() {
+ if !range.contains(ai.address) || !range.contains(ai.address + data.len() as u64) {
+ continue;
+ }
+
+ let page_mask = (pagesize() - 1) as u64;
+ let offset = (range.base & page_mask) + ai.offset;
+ if let Err(e) = mmap.read_slice(data, offset as usize) {
+ error!("read mmio {:x} error {}", ai.address, e);
+ }
+ return;
+ }
+ }
+}
+
+impl BusDevice for DirectMmio {
+ fn debug_label(&self) -> String {
+ "direct-mmio".to_string()
+ }
+
+ /// Reads at `offset` from this device
+ fn read(&mut self, ai: BusAccessInfo, data: &mut [u8]) {
+ self.iord(ai, data);
+ }
+
+ /// Writes at `offset` into this device
+ fn write(&mut self, ai: BusAccessInfo, data: &[u8]) {
+ self.iowr(ai, data);
+ }
+}
+
+impl BusDeviceSync for DirectMmio {
+ /// Reads at `offset` from this device
+ fn read(&self, ai: BusAccessInfo, data: &mut [u8]) {
+ self.iord(ai, data);
+ }
+
+ /// Writes at `offset` into this device
+ fn write(&self, ai: BusAccessInfo, data: &[u8]) {
+ self.iowr(ai, data);
+ }
+}
diff --git a/devices/src/direct_irq.rs b/devices/src/direct_irq.rs
index fe44c4c4d..c120c7f14 100644
--- a/devices/src/direct_irq.rs
+++ b/devices/src/direct_irq.rs
@@ -4,37 +4,56 @@
use base::{ioctl_with_ref, AsRawDescriptor, Event, RawDescriptor};
use data_model::vec_with_array_field;
-use std::fmt;
use std::fs::{File, OpenOptions};
use std::io;
use std::mem::size_of;
+use remain::sorted;
+use thiserror::Error;
use vfio_sys::*;
-#[derive(Debug)]
+use crate::{IrqEdgeEvent, IrqLevelEvent};
+
+#[sorted]
+#[derive(Error, Debug)]
pub enum DirectIrqError {
- Open(io::Error),
+ #[error("failed to clone trigger event: {0}")]
+ CloneEvent(base::Error),
+ #[error("failed to clone resample event: {0}")]
+ CloneResampleEvent(base::Error),
+ #[error("failed to enable direct irq")]
Enable,
-}
-
-impl fmt::Display for DirectIrqError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- DirectIrqError::Open(e) => write!(f, "failed to open /dev/plat-irq-forward: {}", e),
- DirectIrqError::Enable => write!(f, "failed to enable direct irq"),
- }
- }
+ #[error("failed to enable gpe irq")]
+ EnableGpe,
+ #[error("failed to enable direct sci irq")]
+ EnableSci,
+ #[error("failed to enable wake irq")]
+ EnableWake,
+ #[error("failed to open /dev/plat-irq-forward: {0}")]
+ Open(io::Error),
}
pub struct DirectIrq {
dev: File,
trigger: Event,
resample: Option<Event>,
+ sci_irq_prepared: bool,
}
impl DirectIrq {
- /// Create DirectIrq object to access hardware triggered interrupts.
- pub fn new(trigger: Event, resample: Option<Event>) -> Result<Self, DirectIrqError> {
+ fn new(trigger_evt: &Event, resample_evt: Option<&Event>) -> Result<Self, DirectIrqError> {
+ let trigger = trigger_evt
+ .try_clone()
+ .map_err(DirectIrqError::CloneEvent)?;
+ let resample = if let Some(event) = resample_evt {
+ Some(
+ event
+ .try_clone()
+ .map_err(DirectIrqError::CloneResampleEvent)?,
+ )
+ } else {
+ None
+ };
let dev = OpenOptions::new()
.read(true)
.write(true)
@@ -44,9 +63,20 @@ impl DirectIrq {
dev,
trigger,
resample,
+ sci_irq_prepared: false,
})
}
+ /// Create DirectIrq object to access hardware edge triggered interrupts.
+ pub fn new_edge(irq_evt: &IrqEdgeEvent) -> Result<Self, DirectIrqError> {
+ DirectIrq::new(irq_evt.get_trigger(), None)
+ }
+
+ /// Create DirectIrq object to access hardware level triggered interrupts.
+ pub fn new_level(irq_evt: &IrqLevelEvent) -> Result<Self, DirectIrqError> {
+ DirectIrq::new(irq_evt.get_trigger(), Some(irq_evt.get_resample()))
+ }
+
/// Enable hardware triggered interrupt handling.
///
/// Note: this feature is not part of VFIO, but provides
@@ -79,6 +109,35 @@ impl DirectIrq {
Ok(())
}
+ pub fn irq_wake_enable(&self, irq_num: u32) -> Result<(), DirectIrqError> {
+ self.plat_irq_wake_ioctl(irq_num, PLAT_IRQ_WAKE_ENABLE)
+ }
+
+ /// Enable hardware triggered SCI interrupt handling for GPE.
+ ///
+ /// Note: sci_irq_prepare() itself does not enable SCI forwarding yet
+ /// but configures it so it can be enabled for selected GPEs using gpe_enable_forwarding().
+ pub fn sci_irq_prepare(&mut self) -> Result<(), DirectIrqError> {
+ if let Some(resample) = &self.resample {
+ self.plat_irq_ioctl(
+ 0,
+ PLAT_IRQ_FORWARD_SET_LEVEL_SCI_FOR_GPE_TRIGGER_EVENTFD,
+ self.trigger.as_raw_descriptor(),
+ )?;
+ self.plat_irq_ioctl(
+ 0,
+ PLAT_IRQ_FORWARD_SET_LEVEL_SCI_FOR_GPE_UNMASK_EVENTFD,
+ resample.as_raw_descriptor(),
+ )?;
+ } else {
+ return Err(DirectIrqError::EnableSci);
+ }
+
+ self.sci_irq_prepared = true;
+
+ Ok(())
+ }
+
fn plat_irq_ioctl(
&self,
irq_num: u32,
@@ -106,6 +165,53 @@ impl DirectIrq {
Ok(())
}
}
+
+ fn plat_irq_wake_ioctl(&self, irq_num: u32, action: u32) -> Result<(), DirectIrqError> {
+ let mut irq_wake_set = vec_with_array_field::<plat_irq_wake_set, u32>(0);
+ irq_wake_set[0].argsz = (size_of::<plat_irq_wake_set>()) as u32;
+ irq_wake_set[0].action_flags = action;
+ irq_wake_set[0].irq_number_host = irq_num;
+
+ // Safe as we are the owner of plat_irq_wake_set and irq_wake_set which are valid value
+ let ret = unsafe { ioctl_with_ref(self, PLAT_IRQ_WAKE_SET(), &irq_wake_set[0]) };
+ if ret < 0 {
+ Err(DirectIrqError::EnableWake)
+ } else {
+ Ok(())
+ }
+ }
+
+ /// Enable hardware triggered GPE handling via SCI interrupt forwarding.
+ /// Note: requires sci_irq_prepare() to be called beforehand.
+ ///
+ /// # Arguments
+ ///
+ /// * `gpe_num` - host GPE number.
+ ///
+ pub fn gpe_enable_forwarding(&mut self, gpe_num: u32) -> Result<(), DirectIrqError> {
+ if self.resample.is_none() || !self.sci_irq_prepared {
+ return Err(DirectIrqError::EnableGpe);
+ }
+
+ self.gpe_forward_ioctl(gpe_num, ACPI_GPE_FORWARD_SET_TRIGGER)?;
+
+ Ok(())
+ }
+
+ fn gpe_forward_ioctl(&self, gpe_num: u32, action: u32) -> Result<(), DirectIrqError> {
+ let mut gpe_set = vec_with_array_field::<gpe_forward_set, u32>(0);
+ gpe_set[0].argsz = (size_of::<gpe_forward_set>()) as u32;
+ gpe_set[0].action_flags = action;
+ gpe_set[0].gpe_host_nr = gpe_num;
+
+ // Safe as we are the owner of plat_irq_forward and gpe_set which are valid value
+ let ret = unsafe { ioctl_with_ref(self, ACPI_GPE_FORWARD_SET(), &gpe_set[0]) };
+ if ret < 0 {
+ Err(DirectIrqError::EnableGpe)
+ } else {
+ Ok(())
+ }
+ }
}
impl AsRawDescriptor for DirectIrq {
diff --git a/devices/src/irq_event.rs b/devices/src/irq_event.rs
new file mode 100644
index 000000000..2eb650063
--- /dev/null
+++ b/devices/src/irq_event.rs
@@ -0,0 +1,120 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use base::{AsRawDescriptor, AsRawDescriptors, Event, RawDescriptor, Result};
+
+/// A structure suitable for implementing edge triggered interrupts in device backends.
+pub struct IrqEdgeEvent(Event);
+
+impl IrqEdgeEvent {
+ pub fn new() -> Result<IrqEdgeEvent> {
+ Event::new().map(IrqEdgeEvent)
+ }
+
+ pub fn try_clone(&self) -> Result<IrqEdgeEvent> {
+ self.0.try_clone().map(IrqEdgeEvent)
+ }
+
+ /// Creates an instance of IrqLevelEvent from an existing event.
+ pub fn from_event(trigger_evt: Event) -> IrqEdgeEvent {
+ IrqEdgeEvent(trigger_evt)
+ }
+
+ pub fn get_trigger(&self) -> &Event {
+ &self.0
+ }
+
+ pub fn trigger(&self) -> Result<()> {
+ self.0.write(1)
+ }
+
+ pub fn clear_trigger(&self) {
+ let _ = self.0.read();
+ }
+}
+
+/// A structure suitable for implementing level triggered interrupts in device backends.
+pub struct IrqLevelEvent {
+ /// An event used by the device backend to signal hypervisor/VM about data or new unit
+ /// of work being available.
+ trigger_evt: Event,
+ /// An event used by the hypervisor to signal device backend that it completed processing
+ /// a unit of work and that device should re-raise `trigger_evt` if there is additional
+ /// work needs to be done.
+ resample_evt: Event,
+}
+
+impl IrqLevelEvent {
+ pub fn new() -> Result<IrqLevelEvent> {
+ let trigger_evt = Event::new()?;
+ let resample_evt = Event::new()?;
+ Ok(IrqLevelEvent {
+ trigger_evt,
+ resample_evt,
+ })
+ }
+
+ pub fn try_clone(&self) -> Result<IrqLevelEvent> {
+ let trigger_evt = self.trigger_evt.try_clone()?;
+ let resample_evt = self.resample_evt.try_clone()?;
+ Ok(IrqLevelEvent {
+ trigger_evt,
+ resample_evt,
+ })
+ }
+
+ /// Creates an instance of IrqLevelEvent from an existing pair of events.
+ pub fn from_event_pair(trigger_evt: Event, resample_evt: Event) -> IrqLevelEvent {
+ IrqLevelEvent {
+ trigger_evt,
+ resample_evt,
+ }
+ }
+
+ pub fn get_trigger(&self) -> &Event {
+ &self.trigger_evt
+ }
+
+ pub fn get_resample(&self) -> &Event {
+ &self.resample_evt
+ }
+
+ /// Allows backend to inject interrupt (typically into guest).
+ pub fn trigger(&self) -> Result<()> {
+ self.trigger_evt.write(1)
+ }
+
+ /// Allows code servicing interrupt to consume or clear the event.
+ pub fn clear_trigger(&self) {
+ let _ = self.trigger_evt.read();
+ }
+
+ /// Allows code servicing interrupt to signal that processing is done and that the backend
+ /// should go ahead and re-trigger it if there is more work needs to be done.
+ /// Note that typically resampling is signalled not by individual backends, but rather
+ /// by the code implementing interrupt controller.
+ pub fn trigger_resample(&self) -> Result<()> {
+ self.resample_evt.write(1)
+ }
+
+ /// Allows backend to consume or clear the resample event.
+ pub fn clear_resample(&self) {
+ let _ = self.resample_evt.read();
+ }
+}
+
+impl AsRawDescriptors for IrqEdgeEvent {
+ fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
+ vec![self.0.as_raw_descriptor()]
+ }
+}
+
+impl AsRawDescriptors for IrqLevelEvent {
+ fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
+ vec![
+ self.trigger_evt.as_raw_descriptor(),
+ self.resample_evt.as_raw_descriptor(),
+ ]
+ }
+}
diff --git a/devices/src/irqchip/aarch64.rs b/devices/src/irqchip/aarch64.rs
index 838cff903..fa1eac05e 100644
--- a/devices/src/irqchip/aarch64.rs
+++ b/devices/src/irqchip/aarch64.rs
@@ -2,11 +2,25 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use crate::IrqChip;
+use base::Result;
use hypervisor::DeviceKind;
+use crate::IrqChip;
+
pub trait IrqChipAArch64: IrqChip {
+ // Clones this trait as a `Box` version of itself.
+ fn try_box_clone(&self) -> Result<Box<dyn IrqChipAArch64>>;
+
+ // Get this as the super-trait IrqChip.
+ fn as_irq_chip(&self) -> &dyn IrqChip;
+
+ // Get this as the mutable super-trait IrqChip.
+ fn as_irq_chip_mut(&mut self) -> &mut dyn IrqChip;
+
/// Get the version of VGIC that this chip is emulating. Currently KVM may either implement
/// VGIC version 2 or 3.
fn get_vgic_version(&self) -> DeviceKind;
+
+ /// Once all the VCPUs have been enabled, finalize the irq chip.
+ fn finalize(&self) -> Result<()>;
}
diff --git a/devices/src/irqchip/ioapic.rs b/devices/src/irqchip/ioapic.rs
index a2117489f..aaf7d1cf3 100644
--- a/devices/src/irqchip/ioapic.rs
+++ b/devices/src/irqchip/ioapic.rs
@@ -6,16 +6,16 @@
// See https://www.intel.com/content/dam/doc/datasheet/io-controller-hub-10-family-datasheet.pdf
// for a specification.
-use std::fmt::{self, Display};
-
use super::IrqEvent;
use crate::bus::BusAccessInfo;
use crate::BusDevice;
use base::{error, warn, Error, Event, Result, Tube, TubeError};
use hypervisor::{
IoapicRedirectionTableEntry, IoapicState, MsiAddressMessage, MsiDataMessage, TriggerMode,
- NUM_IOAPIC_PINS,
+ MAX_IOAPIC_PINS, NUM_IOAPIC_PINS,
};
+use remain::sorted;
+use thiserror::Error;
use vm_control::{VmIrqRequest, VmIrqResponse};
// ICH10 I/O APIC version: 0x20
@@ -146,7 +146,7 @@ impl BusDevice for Ioapic {
impl Ioapic {
pub fn new(irq_tube: Tube, num_pins: usize) -> Result<Ioapic> {
- let num_pins = num_pins.max(NUM_IOAPIC_PINS as usize);
+ let num_pins = num_pins.max(NUM_IOAPIC_PINS).min(MAX_IOAPIC_PINS);
let mut entry = IoapicRedirectionTableEntry::new();
entry.set_interrupt_mask(true);
Ok(Ioapic {
@@ -156,12 +156,28 @@ impl Ioapic {
rtc_remote_irr: false,
out_events: (0..num_pins).map(|_| None).collect(),
resample_events: Vec::new(),
- redirect_table: (0..num_pins).map(|_| entry.clone()).collect(),
+ redirect_table: (0..num_pins).map(|_| entry).collect(),
interrupt_level: (0..num_pins).map(|_| false).collect(),
irq_tube,
})
}
+ pub fn init_direct_gsi<F>(&mut self, register_irqfd: F) -> Result<()>
+ where
+ F: Fn(u32, &Event) -> Result<()>,
+ {
+ for (gsi, out_event) in self.out_events.iter_mut().enumerate() {
+ let event = Event::new()?;
+ register_irqfd(gsi as u32, &event)?;
+ *out_event = Some(IrqEvent {
+ gsi: gsi as u32,
+ event,
+ resample_event: None,
+ });
+ }
+ Ok(())
+ }
+
pub fn get_ioapic_state(&self) -> IoapicState {
// Convert vector of first NUM_IOAPIC_PINS active interrupts into an u32 value.
let level_bitmap = self
@@ -381,7 +397,12 @@ impl Ioapic {
evt.gsi
} else {
let event = Event::new().map_err(IoapicError::CreateEvent)?;
- let request = VmIrqRequest::AllocateOneMsi { irqfd: event };
+ let request = VmIrqRequest::AllocateOneMsi {
+ irqfd: event,
+ device_id: self.device_id(),
+ queue_id: index,
+ device_name: self.debug_label(),
+ };
self.irq_tube
.send(&request)
.map_err(IoapicError::AllocateOneMsiSend)?;
@@ -394,7 +415,7 @@ impl Ioapic {
self.out_events[index] = Some(IrqEvent {
gsi,
event: match request {
- VmIrqRequest::AllocateOneMsi { irqfd } => irqfd,
+ VmIrqRequest::AllocateOneMsi { irqfd, .. } => irqfd,
_ => unreachable!(),
},
resample_event: None,
@@ -445,35 +466,25 @@ impl Ioapic {
}
}
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
enum IoapicError {
+ #[error("AddMsiRoute failed: {0}")]
AddMsiRoute(Error),
+ #[error("failed to receive AddMsiRoute response: {0}")]
AddMsiRouteRecv(TubeError),
+ #[error("failed to send AddMsiRoute request: {0}")]
AddMsiRouteSend(TubeError),
+ #[error("AllocateOneMsi failed: {0}")]
AllocateOneMsi(Error),
+ #[error("failed to receive AllocateOneMsi response: {0}")]
AllocateOneMsiRecv(TubeError),
+ #[error("failed to send AllocateOneMsi request: {0}")]
AllocateOneMsiSend(TubeError),
+ #[error("failed to create event object: {0}")]
CreateEvent(Error),
}
-impl Display for IoapicError {
- #[remain::check]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::IoapicError::*;
-
- #[sorted]
- match self {
- AddMsiRoute(e) => write!(f, "AddMsiRoute failed: {}", e),
- AddMsiRouteRecv(e) => write!(f, "failed to receive AddMsiRoute response: {}", e),
- AddMsiRouteSend(e) => write!(f, "failed to send AddMsiRoute request: {}", e),
- AllocateOneMsi(e) => write!(f, "AllocateOneMsi failed: {}", e),
- AllocateOneMsiRecv(e) => write!(f, "failed to receive AllocateOneMsi response: {}", e),
- AllocateOneMsiSend(e) => write!(f, "failed to send AllocateOneMsi request: {}", e),
- CreateEvent(e) => write!(f, "failed to create event object: {}", e),
- }
- }
-}
-
#[cfg(test)]
mod tests {
use super::*;
diff --git a/devices/src/irqchip/kvm/aarch64.rs b/devices/src/irqchip/kvm/aarch64.rs
index 376eef28a..25c51a935 100644
--- a/devices/src/irqchip/kvm/aarch64.rs
+++ b/devices/src/irqchip/kvm/aarch64.rs
@@ -10,7 +10,7 @@ use hypervisor::kvm::{KvmVcpu, KvmVm};
use hypervisor::{DeviceKind, IrqRoute, Vm};
use kvm_sys::*;
-use crate::IrqChipAArch64;
+use crate::{IrqChip, IrqChipAArch64};
/// Default ARM routing table. AARCH64_GIC_NR_SPIS pins go to VGIC.
fn kvm_default_irq_routing_table() -> Vec<IrqRoute> {
@@ -48,7 +48,7 @@ const AARCH64_GIC_REDIST_SIZE: u64 = 0x20000;
// PPI (16) and GSI (16).
pub const AARCH64_GIC_NR_IRQS: u32 = 64;
// Number of SPIs (32), which is the NR_IRQS (64) minus the number of PPIs (16) and GSIs (16)
-const AARCH64_GIC_NR_SPIS: u32 = 32;
+pub const AARCH64_GIC_NR_SPIS: u32 = 32;
const AARCH64_AXI_BASE: u64 = 0x40000000;
@@ -125,20 +125,6 @@ impl KvmKernelIrqChip {
return errno_result();
}
- // Finalize the GIC
- let init_gic_attr = kvm_device_attr {
- group: KVM_DEV_ARM_VGIC_GRP_CTRL,
- attr: KVM_DEV_ARM_VGIC_CTRL_INIT as u64,
- addr: 0,
- flags: 0,
- };
-
- // Safe because we allocated the struct that's being passed in
- let ret = unsafe { ioctl_with_ref(&vgic, KVM_SET_DEVICE_ATTR(), &init_gic_attr) };
- if ret != 0 {
- return errno_result();
- }
-
Ok(KvmKernelIrqChip {
vm,
vcpus: Arc::new(Mutex::new((0..num_vcpus).map(|_| None).collect())),
@@ -161,7 +147,35 @@ impl KvmKernelIrqChip {
}
impl IrqChipAArch64 for KvmKernelIrqChip {
+ fn try_box_clone(&self) -> Result<Box<dyn IrqChipAArch64>> {
+ Ok(Box::new(self.try_clone()?))
+ }
+
+ fn as_irq_chip(&self) -> &dyn IrqChip {
+ self
+ }
+
+ fn as_irq_chip_mut(&mut self) -> &mut dyn IrqChip {
+ self
+ }
+
fn get_vgic_version(&self) -> DeviceKind {
self.device_kind
}
+
+ fn finalize(&self) -> Result<()> {
+ let init_gic_attr = kvm_device_attr {
+ group: KVM_DEV_ARM_VGIC_GRP_CTRL,
+ attr: KVM_DEV_ARM_VGIC_CTRL_INIT as u64,
+ addr: 0,
+ flags: 0,
+ };
+
+ // Safe because we allocated the struct that's being passed in
+ let ret = unsafe { ioctl_with_ref(&self.vgic, KVM_SET_DEVICE_ATTR(), &init_gic_attr) };
+ if ret != 0 {
+ return errno_result();
+ }
+ Ok(())
+ }
}
diff --git a/devices/src/irqchip/kvm/mod.rs b/devices/src/irqchip/kvm/mod.rs
index 21b0a9904..9cf67bb4f 100644
--- a/devices/src/irqchip/kvm/mod.rs
+++ b/devices/src/irqchip/kvm/mod.rs
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use crate::Bus;
+use crate::{Bus, IrqEdgeEvent, IrqLevelEvent};
use base::{error, Error, Event, Result};
use hypervisor::kvm::KvmVcpu;
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
@@ -36,20 +36,37 @@ impl IrqChip for KvmKernelIrqChip {
Ok(())
}
- /// Register an event that can trigger an interrupt for a particular GSI.
- fn register_irq_event(
+ /// Register an event with edge-trigger semantic that can trigger an interrupt
+ /// for a particular GSI.
+ fn register_edge_irq_event(
&mut self,
irq: u32,
- irq_event: &Event,
- resample_event: Option<&Event>,
+ irq_event: &IrqEdgeEvent,
) -> Result<Option<IrqEventIndex>> {
- self.vm.register_irqfd(irq, irq_event, resample_event)?;
+ self.vm.register_irqfd(irq, irq_event.get_trigger(), None)?;
Ok(None)
}
- /// Unregister an event for a particular GSI.
- fn unregister_irq_event(&mut self, irq: u32, irq_event: &Event) -> Result<()> {
- self.vm.unregister_irqfd(irq, irq_event)
+ /// Unregister an event with edge-trigger semantic for a particular GSI.
+ fn unregister_edge_irq_event(&mut self, irq: u32, irq_event: &IrqEdgeEvent) -> Result<()> {
+ self.vm.unregister_irqfd(irq, irq_event.get_trigger())
+ }
+
+ /// Register an event with level-trigger semantic that can trigger an interrupt
+ /// for a particular GSI.
+ fn register_level_irq_event(
+ &mut self,
+ irq: u32,
+ irq_event: &IrqLevelEvent,
+ ) -> Result<Option<IrqEventIndex>> {
+ self.vm
+ .register_irqfd(irq, irq_event.get_trigger(), Some(irq_event.get_resample()))?;
+ Ok(None)
+ }
+
+ /// Unregister an event with level-trigger semantic for a particular GSI.
+ fn unregister_level_irq_event(&mut self, irq: u32, irq_event: &IrqLevelEvent) -> Result<()> {
+ self.vm.unregister_irqfd(irq, irq_event.get_trigger())
}
/// Route an IRQ line to an interrupt controller, or to a particular MSI vector.
@@ -156,8 +173,8 @@ impl IrqChip for KvmKernelIrqChip {
fn finalize_devices(
&mut self,
_resources: &mut SystemAllocator,
- _io_bus: &mut Bus,
- _mmio_bus: &mut Bus,
+ _io_bus: &Bus,
+ _mmio_bus: &Bus,
) -> Result<()> {
Ok(())
}
@@ -167,12 +184,16 @@ impl IrqChip for KvmKernelIrqChip {
Ok(())
}
+ fn irq_delayed_event_token(&self) -> Result<Option<Event>> {
+ Ok(None)
+ }
+
fn check_capability(&self, c: IrqChipCap) -> bool {
match c {
IrqChipCap::TscDeadlineTimer => self
.vm
.get_hypervisor()
- .check_capability(&HypervisorCap::TscDeadlineTimer),
+ .check_capability(HypervisorCap::TscDeadlineTimer),
IrqChipCap::X2Apic => true,
}
}
@@ -180,9 +201,8 @@ impl IrqChip for KvmKernelIrqChip {
#[cfg(test)]
mod tests {
-
use hypervisor::kvm::{Kvm, KvmVm};
- use hypervisor::{MPState, Vm};
+ use hypervisor::{MPState, ProtectionType, Vm};
use vm_memory::GuestMemory;
use crate::irqchip::{IrqChip, KvmKernelIrqChip};
@@ -196,7 +216,8 @@ mod tests {
fn create_kvm_kernel_irqchip() {
let kvm = Kvm::new().expect("failed to instantiate Kvm");
let mem = GuestMemory::new(&[]).unwrap();
- let vm = KvmVm::new(&kvm, mem).expect("failed to instantiate vm");
+ let vm =
+ KvmVm::new(&kvm, mem, ProtectionType::Unprotected).expect("failed to instantiate vm");
let mut chip = KvmKernelIrqChip::new(vm.try_clone().expect("failed to clone vm"), 1)
.expect("failed to instantiate KvmKernelIrqChip");
@@ -210,7 +231,8 @@ mod tests {
fn mp_state() {
let kvm = Kvm::new().expect("failed to instantiate Kvm");
let mem = GuestMemory::new(&[]).unwrap();
- let vm = KvmVm::new(&kvm, mem).expect("failed to instantiate vm");
+ let vm =
+ KvmVm::new(&kvm, mem, ProtectionType::Unprotected).expect("failed to instantiate vm");
let mut chip = KvmKernelIrqChip::new(vm.try_clone().expect("failed to clone vm"), 1)
.expect("failed to instantiate KvmKernelIrqChip");
diff --git a/devices/src/irqchip/kvm/x86_64.rs b/devices/src/irqchip/kvm/x86_64.rs
index 613e1c4fd..e9bd14581 100644
--- a/devices/src/irqchip/kvm/x86_64.rs
+++ b/devices/src/irqchip/kvm/x86_64.rs
@@ -13,7 +13,7 @@ use base::FakeClock as Clock;
use hypervisor::kvm::{KvmVcpu, KvmVm};
use hypervisor::{
HypervisorCap, IoapicState, IrqRoute, IrqSource, IrqSourceChip, LapicState, MPState, PicSelect,
- PicState, PitState, Vcpu, VcpuX86_64, Vm, VmX86_64, NUM_IOAPIC_PINS,
+ PicState, PitState, Vcpu, VcpuX86_64, Vm, VmX86_64,
};
use kvm_sys::*;
use resources::SystemAllocator;
@@ -24,7 +24,7 @@ use crate::irqchip::{
Ioapic, IrqEvent, IrqEventIndex, Pic, VcpuRunState, IOAPIC_BASE_ADDRESS,
IOAPIC_MEM_LENGTH_BYTES,
};
-use crate::{Bus, IrqChip, IrqChipCap, IrqChipX86_64, Pit, PitError};
+use crate::{Bus, IrqChip, IrqChipCap, IrqChipX86_64, IrqEdgeEvent, IrqLevelEvent, Pit, PitError};
/// PIT tube 0 timer is connected to IRQ 0
const PIT_CHANNEL0_IRQ: u32 = 0;
@@ -63,11 +63,12 @@ impl KvmKernelIrqChip {
pub fn new(vm: KvmVm, num_vcpus: usize) -> Result<KvmKernelIrqChip> {
vm.create_irq_chip()?;
vm.create_pit()?;
+ let ioapic_pins = vm.get_ioapic_num_pins()?;
Ok(KvmKernelIrqChip {
vm,
vcpus: Arc::new(Mutex::new((0..num_vcpus).map(|_| None).collect())),
- routes: Arc::new(Mutex::new(kvm_default_irq_routing_table(NUM_IOAPIC_PINS))),
+ routes: Arc::new(Mutex::new(kvm_default_irq_routing_table(ioapic_pins))),
})
}
/// Attempt to create a shallow clone of this x86_64 KvmKernelIrqChip instance.
@@ -81,6 +82,18 @@ impl KvmKernelIrqChip {
}
impl IrqChipX86_64 for KvmKernelIrqChip {
+ fn try_box_clone(&self) -> Result<Box<dyn IrqChipX86_64>> {
+ Ok(Box::new(self.try_clone()?))
+ }
+
+ fn as_irq_chip(&self) -> &dyn IrqChip {
+ self
+ }
+
+ fn as_irq_chip_mut(&mut self) -> &mut dyn IrqChip {
+ self
+ }
+
/// Get the current state of the PIC
fn get_pic_state(&self, select: PicSelect) -> Result<PicState> {
Ok(PicState::from(&self.vm.get_pic_state(select)?))
@@ -151,6 +164,8 @@ pub struct KvmSplitIrqChip {
/// locked the ioapic and the ioapic sends a AddMsiRoute signal to the main thread (which
/// itself may be busy trying to call service_irq).
delayed_ioapic_irq_events: Arc<Mutex<Vec<usize>>>,
+ /// Event which is meant to trigger process of any irqs events that were delayed.
+ delayed_ioapic_irq_trigger: Event,
/// Array of Events that devices will use to assert ioapic pins.
irq_events: Arc<Mutex<Vec<Option<IrqEvent>>>>,
}
@@ -180,20 +195,22 @@ impl KvmSplitIrqChip {
irq_tube: Tube,
ioapic_pins: Option<usize>,
) -> Result<Self> {
- let ioapic_pins = ioapic_pins.unwrap_or(hypervisor::NUM_IOAPIC_PINS);
+ let ioapic_pins = ioapic_pins.unwrap_or(vm.get_ioapic_num_pins()?);
vm.enable_split_irqchip(ioapic_pins)?;
- let pit_evt = Event::new()?;
+ let pit_evt = IrqEdgeEvent::new()?;
let pit = Arc::new(Mutex::new(
- Pit::new(pit_evt.try_clone()?, Arc::new(Mutex::new(Clock::new()))).map_err(
- |e| match e {
- PitError::CloneEvent(err) => err,
- PitError::CreateEvent(err) => err,
- PitError::CreateWaitContext(err) => err,
- PitError::WaitError(err) => err,
- PitError::TimerCreateError(err) => err,
- PitError::SpawnThread(_) => Error::new(libc::EIO),
- },
- )?,
+ Pit::new(
+ pit_evt.get_trigger().try_clone()?,
+ Arc::new(Mutex::new(Clock::new())),
+ )
+ .map_err(|e| match e {
+ PitError::CloneEvent(err) => err,
+ PitError::CreateEvent(err) => err,
+ PitError::CreateWaitContext(err) => err,
+ PitError::WaitError(err) => err,
+ PitError::TimerCreateError(err) => err,
+ PitError::SpawnThread(_) => Error::new(libc::EIO),
+ })?,
));
let mut chip = KvmSplitIrqChip {
@@ -205,9 +222,19 @@ impl KvmSplitIrqChip {
ioapic: Arc::new(Mutex::new(Ioapic::new(irq_tube, ioapic_pins)?)),
ioapic_pins,
delayed_ioapic_irq_events: Arc::new(Mutex::new(Vec::new())),
+ delayed_ioapic_irq_trigger: Event::new()?,
irq_events: Arc::new(Mutex::new(Default::default())),
};
+ // crosvm-direct requires 1:1 GSI mapping between host and guest. The predefined IRQ
+ // numbering will be exposed to the guest with no option to allocate it dynamically.
+ // Tell the IOAPIC to fill in IRQ output events with 1:1 GSI mapping upfront so that
+ // IOAPIC wont assign a new GSI but use the same as for host instead.
+ #[cfg(feature = "direct")]
+ chip.ioapic
+ .lock()
+ .init_direct_gsi(|gsi, event| chip.vm.register_irqfd(gsi as u32, &event, None))?;
+
// Setup standard x86 irq routes
let mut routes = kvm_default_irq_routing_table(ioapic_pins);
// Add dummy MSI routes for the first ioapic_pins GSIs
@@ -216,7 +243,7 @@ impl KvmSplitIrqChip {
// Set the routes so they get sent to KVM
chip.set_irq_routes(&routes)?;
- chip.register_irq_event(PIT_CHANNEL0_IRQ, &pit_evt, None)?;
+ chip.register_edge_irq_event(PIT_CHANNEL0_IRQ, &pit_evt)?;
Ok(chip)
}
}
@@ -268,6 +295,52 @@ impl KvmSplitIrqChip {
.get_external_interrupt()
.map(|vector| vector as u32)
}
+
+ /// Register an event that can trigger an interrupt for a particular GSI.
+ fn register_irq_event(
+ &mut self,
+ irq: u32,
+ irq_event: &Event,
+ resample_event: Option<&Event>,
+ ) -> Result<Option<IrqEventIndex>> {
+ if irq < self.ioapic_pins as u32 {
+ let mut evt = IrqEvent {
+ gsi: irq,
+ event: irq_event.try_clone()?,
+ resample_event: None,
+ };
+
+ if let Some(resample_event) = resample_event {
+ evt.resample_event = Some(resample_event.try_clone()?);
+ }
+
+ let mut irq_events = self.irq_events.lock();
+ let index = irq_events.len();
+ irq_events.push(Some(evt));
+ Ok(Some(index))
+ } else {
+ self.vm.register_irqfd(irq, irq_event, resample_event)?;
+ Ok(None)
+ }
+ }
+
+ /// Unregister an event for a particular GSI.
+ fn unregister_irq_event(&mut self, irq: u32, irq_event: &Event) -> Result<()> {
+ if irq < self.ioapic_pins as u32 {
+ let mut irq_events = self.irq_events.lock();
+ for (index, evt) in irq_events.iter().enumerate() {
+ if let Some(evt) = evt {
+ if evt.gsi == irq && irq_event.eq(&evt.event) {
+ irq_events[index] = None;
+ break;
+ }
+ }
+ }
+ Ok(())
+ } else {
+ self.vm.unregister_irqfd(irq, irq_event)
+ }
+ }
}
/// Convenience function for determining whether or not two irq routes conflict.
@@ -312,49 +385,28 @@ impl IrqChip for KvmSplitIrqChip {
}
/// Register an event that can trigger an interrupt for a particular GSI.
- fn register_irq_event(
+ fn register_edge_irq_event(
&mut self,
irq: u32,
- irq_event: &Event,
- resample_event: Option<&Event>,
+ irq_event: &IrqEdgeEvent,
) -> Result<Option<IrqEventIndex>> {
- if irq < self.ioapic_pins as u32 {
- let mut evt = IrqEvent {
- gsi: irq,
- event: irq_event.try_clone()?,
- resample_event: None,
- };
+ self.register_irq_event(irq, irq_event.get_trigger(), None)
+ }
- if let Some(resample_event) = resample_event {
- evt.resample_event = Some(resample_event.try_clone()?);
- }
+ fn unregister_edge_irq_event(&mut self, irq: u32, irq_event: &IrqEdgeEvent) -> Result<()> {
+ self.unregister_irq_event(irq, irq_event.get_trigger())
+ }
- let mut irq_events = self.irq_events.lock();
- let index = irq_events.len();
- irq_events.push(Some(evt));
- Ok(Some(index))
- } else {
- self.vm.register_irqfd(irq, irq_event, resample_event)?;
- Ok(None)
- }
+ fn register_level_irq_event(
+ &mut self,
+ irq: u32,
+ irq_event: &IrqLevelEvent,
+ ) -> Result<Option<IrqEventIndex>> {
+ self.register_irq_event(irq, irq_event.get_trigger(), Some(irq_event.get_resample()))
}
- /// Unregister an event for a particular GSI.
- fn unregister_irq_event(&mut self, irq: u32, irq_event: &Event) -> Result<()> {
- if irq < self.ioapic_pins as u32 {
- let mut irq_events = self.irq_events.lock();
- for (index, evt) in irq_events.iter().enumerate() {
- if let Some(evt) = evt {
- if evt.gsi == irq && irq_event.eq(&evt.event) {
- irq_events[index] = None;
- break;
- }
- }
- }
- Ok(())
- } else {
- self.vm.unregister_irqfd(irq, irq_event)
- }
+ fn unregister_level_irq_event(&mut self, irq: u32, irq_event: &IrqLevelEvent) -> Result<()> {
+ self.unregister_irq_event(irq, irq_event.get_trigger())
}
/// Route an IRQ line to an interrupt controller, or to a particular MSI vector.
@@ -430,23 +482,20 @@ impl IrqChip for KvmSplitIrqChip {
match chip {
IrqSourceChip::PicPrimary | IrqSourceChip::PicSecondary => {
let mut pic = self.pic.lock();
- if evt.resample_event.is_some() {
- pic.service_irq(pin as u8, true);
- } else {
- pic.service_irq(pin as u8, true);
+ pic.service_irq(pin as u8, true);
+ if evt.resample_event.is_none() {
pic.service_irq(pin as u8, false);
}
}
IrqSourceChip::Ioapic => {
if let Ok(mut ioapic) = self.ioapic.try_lock() {
- if evt.resample_event.is_some() {
- ioapic.service_irq(pin as usize, true);
- } else {
- ioapic.service_irq(pin as usize, true);
+ ioapic.service_irq(pin as usize, true);
+ if evt.resample_event.is_none() {
ioapic.service_irq(pin as usize, false);
}
} else {
self.delayed_ioapic_irq_events.lock().push(event_index);
+ self.delayed_ioapic_irq_trigger.write(1).unwrap();
}
}
_ => {}
@@ -531,6 +580,7 @@ impl IrqChip for KvmSplitIrqChip {
ioapic: self.ioapic.clone(),
ioapic_pins: self.ioapic_pins,
delayed_ioapic_irq_events: self.delayed_ioapic_irq_events.clone(),
+ delayed_ioapic_irq_trigger: Event::new()?,
irq_events: self.irq_events.clone(),
})
}
@@ -540,8 +590,8 @@ impl IrqChip for KvmSplitIrqChip {
fn finalize_devices(
&mut self,
resources: &mut SystemAllocator,
- io_bus: &mut Bus,
- mmio_bus: &mut Bus,
+ io_bus: &Bus,
+ mmio_bus: &Bus,
) -> Result<()> {
// Insert pit into io_bus
io_bus.insert(self.pit.clone(), 0x040, 0x8).unwrap();
@@ -568,15 +618,13 @@ impl IrqChip for KvmSplitIrqChip {
let mut pic_resample_events: Vec<Vec<Event>> =
(0..self.ioapic_pins).map(|_| Vec::new()).collect();
- for evt in self.irq_events.lock().iter() {
- if let Some(evt) = evt {
- if (evt.gsi as usize) >= self.ioapic_pins {
- continue;
- }
- if let Some(resample_evt) = &evt.resample_event {
- ioapic_resample_events[evt.gsi as usize].push(resample_evt.try_clone()?);
- pic_resample_events[evt.gsi as usize].push(resample_evt.try_clone()?);
- }
+ for evt in self.irq_events.lock().iter().flatten() {
+ if (evt.gsi as usize) >= self.ioapic_pins {
+ continue;
+ }
+ if let Some(resample_evt) = &evt.resample_event {
+ ioapic_resample_events[evt.gsi as usize].push(resample_evt.try_clone()?);
+ pic_resample_events[evt.gsi as usize].push(resample_evt.try_clone()?);
}
}
@@ -611,10 +659,8 @@ impl IrqChip for KvmSplitIrqChip {
.retain(|&event_index| {
if let Some(evt) = &self.irq_events.lock()[event_index] {
if let Ok(mut ioapic) = self.ioapic.try_lock() {
- if evt.resample_event.is_some() {
- ioapic.service_irq(evt.gsi as usize, true);
- } else {
- ioapic.service_irq(evt.gsi as usize, true);
+ ioapic.service_irq(evt.gsi as usize, true);
+ if evt.resample_event.is_none() {
ioapic.service_irq(evt.gsi as usize, false);
}
@@ -627,21 +673,41 @@ impl IrqChip for KvmSplitIrqChip {
}
});
+ if self.delayed_ioapic_irq_events.lock().is_empty() {
+ self.delayed_ioapic_irq_trigger.read()?;
+ }
+
Ok(())
}
+ fn irq_delayed_event_token(&self) -> Result<Option<Event>> {
+ Ok(Some(self.delayed_ioapic_irq_trigger.try_clone()?))
+ }
+
fn check_capability(&self, c: IrqChipCap) -> bool {
match c {
IrqChipCap::TscDeadlineTimer => self
.vm
.get_hypervisor()
- .check_capability(&HypervisorCap::TscDeadlineTimer),
+ .check_capability(HypervisorCap::TscDeadlineTimer),
IrqChipCap::X2Apic => true,
}
}
}
impl IrqChipX86_64 for KvmSplitIrqChip {
+ fn try_box_clone(&self) -> Result<Box<dyn IrqChipX86_64>> {
+ Ok(Box::new(self.try_clone()?))
+ }
+
+ fn as_irq_chip(&self) -> &dyn IrqChip {
+ self
+ }
+
+ fn as_irq_chip_mut(&mut self) -> &mut dyn IrqChip {
+ self
+ }
+
/// Get the current state of the PIC
fn get_pic_state(&self, select: PicSelect) -> Result<PicState> {
Ok(self.pic.lock().get_pic_state(select))
@@ -703,19 +769,21 @@ mod tests {
use super::*;
use base::EventReadResult;
- use hypervisor::kvm::Kvm;
+ use hypervisor::{
+ kvm::Kvm, IoapicRedirectionTableEntry, PitRWMode, ProtectionType, TriggerMode, Vm, VmX86_64,
+ };
+ use resources::{MemRegion, SystemAllocator, SystemAllocatorConfig};
use vm_memory::GuestMemory;
- use hypervisor::{IoapicRedirectionTableEntry, PitRWMode, TriggerMode, Vm, VmX86_64};
-
- use super::super::super::tests::*;
+ use crate::irqchip::tests::*;
use crate::IrqChip;
/// Helper function for setting up a KvmKernelIrqChip
fn get_kernel_chip() -> KvmKernelIrqChip {
let kvm = Kvm::new().expect("failed to instantiate Kvm");
let mem = GuestMemory::new(&[]).unwrap();
- let vm = KvmVm::new(&kvm, mem).expect("failed tso instantiate vm");
+ let vm =
+ KvmVm::new(&kvm, mem, ProtectionType::Unprotected).expect("failed tso instantiate vm");
let mut chip = KvmKernelIrqChip::new(vm.try_clone().expect("failed to clone vm"), 1)
.expect("failed to instantiate KvmKernelIrqChip");
@@ -731,7 +799,8 @@ mod tests {
fn get_split_chip() -> KvmSplitIrqChip {
let kvm = Kvm::new().expect("failed to instantiate Kvm");
let mem = GuestMemory::new(&[]).unwrap();
- let vm = KvmVm::new(&kvm, mem).expect("failed tso instantiate vm");
+ let vm =
+ KvmVm::new(&kvm, mem, ProtectionType::Unprotected).expect("failed tso instantiate vm");
let (_, device_tube) = Tube::pair().expect("failed to create irq tube");
@@ -863,8 +932,8 @@ mod tests {
assert_eq!(tokens[0].1, 0);
// register another irq event
- let evt = Event::new().expect("failed to create event");
- chip.register_irq_event(6, &evt, None)
+ let evt = IrqEdgeEvent::new().expect("failed to create event");
+ chip.register_edge_irq_event(6, &evt)
.expect("failed to register irq event");
let tokens = chip
@@ -875,33 +944,46 @@ mod tests {
assert_eq!(tokens.len(), 2);
assert_eq!(tokens[0].1, 0);
assert_eq!(tokens[1].1, 6);
- assert_eq!(tokens[1].2, evt);
+ assert_eq!(tokens[1].2, *evt.get_trigger());
}
#[test]
fn finalize_devices() {
let mut chip = get_split_chip();
- let mut mmio_bus = Bus::new();
- let mut io_bus = Bus::new();
- let mut resources = SystemAllocator::builder()
- .add_io_addresses(0xc000, 0x10000)
- .add_low_mmio_addresses(0, 2048)
- .add_high_mmio_addresses(2048, 4096)
- .create_allocator(5)
- .expect("failed to create SystemAllocator");
-
- // setup an event and a resample event for irq line 1
- let evt = Event::new().expect("failed to create event");
- let mut resample_evt = Event::new().expect("failed to create event");
+ let mmio_bus = Bus::new();
+ let io_bus = Bus::new();
+ let mut resources = SystemAllocator::new(
+ SystemAllocatorConfig {
+ io: Some(MemRegion {
+ base: 0xc000,
+ size: 0x4000,
+ }),
+ low_mmio: MemRegion {
+ base: 0,
+ size: 2048,
+ },
+ high_mmio: MemRegion {
+ base: 0x1_0000_0000,
+ size: 0x2_0000_0000,
+ },
+ platform_mmio: None,
+ first_irq: 5,
+ },
+ None,
+ &[],
+ )
+ .expect("failed to create SystemAllocator");
+ // Set up a level-triggered interrupt line 1
+ let evt = IrqLevelEvent::new().expect("failed to create event");
let evt_index = chip
- .register_irq_event(1, &evt, Some(&resample_evt))
- .expect("failed to register_irq_event")
+ .register_level_irq_event(1, &evt)
+ .expect("failed to register irq event")
.expect("register_irq_event should not return None");
// Once we finalize devices, the pic/pit/ioapic should be attached to io and mmio busses
- chip.finalize_devices(&mut resources, &mut io_bus, &mut mmio_bus)
+ chip.finalize_devices(&mut resources, &io_bus, &mmio_bus)
.expect("failed to finalize devices");
// Should not be able to allocate an irq < 24 now
@@ -932,7 +1014,7 @@ mod tests {
assert!(state.special_fully_nested_mode);
// Need to write to the irq event before servicing it
- evt.write(1).expect("failed to write to event");
+ evt.trigger().expect("failed to write to event");
// if we assert irq line one, and then get the resulting interrupt, an auto-eoi should
// occur and cause the resample_event to be written to
@@ -948,6 +1030,8 @@ mod tests {
0x9
);
+ // Clone resample event because read_timeout() needs a mutable reference.
+ let resample_evt = evt.get_resample().try_clone().unwrap();
assert_eq!(
resample_evt
.read_timeout(std::time::Duration::from_secs(1))
@@ -1000,24 +1084,39 @@ mod tests {
fn broadcast_eoi() {
let mut chip = get_split_chip();
- let mut mmio_bus = Bus::new();
- let mut io_bus = Bus::new();
- let mut resources = SystemAllocator::builder()
- .add_io_addresses(0xc000, 0x10000)
- .add_low_mmio_addresses(0, 2048)
- .add_high_mmio_addresses(2048, 4096)
- .create_allocator(5)
- .expect("failed to create SystemAllocator");
+ let mmio_bus = Bus::new();
+ let io_bus = Bus::new();
+ let mut resources = SystemAllocator::new(
+ SystemAllocatorConfig {
+ io: Some(MemRegion {
+ base: 0xc000,
+ size: 0x4000,
+ }),
+ low_mmio: MemRegion {
+ base: 0,
+ size: 2048,
+ },
+ high_mmio: MemRegion {
+ base: 0x1_0000_0000,
+ size: 0x2_0000_0000,
+ },
+ platform_mmio: None,
+ first_irq: 5,
+ },
+ None,
+ &[],
+ )
+ .expect("failed to create SystemAllocator");
// setup an event and a resample event for irq line 1
let evt = Event::new().expect("failed to create event");
- let mut resample_evt = Event::new().expect("failed to create event");
+ let resample_evt = Event::new().expect("failed to create event");
chip.register_irq_event(1, &evt, Some(&resample_evt))
.expect("failed to register_irq_event");
// Once we finalize devices, the pic/pit/ioapic should be attached to io and mmio busses
- chip.finalize_devices(&mut resources, &mut io_bus, &mut mmio_bus)
+ chip.finalize_devices(&mut resources, &io_bus, &mmio_bus)
.expect("failed to finalize devices");
// setup a ioapic redirection table entry 1 with a vector of 123
diff --git a/devices/src/irqchip/mod.rs b/devices/src/irqchip/mod.rs
index d4313601d..90351eb6f 100644
--- a/devices/src/irqchip/mod.rs
+++ b/devices/src/irqchip/mod.rs
@@ -4,7 +4,7 @@
use std::marker::{Send, Sized};
-use crate::Bus;
+use crate::{Bus, IrqEdgeEvent, IrqLevelEvent};
use base::{Event, Result};
use hypervisor::{IrqRoute, MPState, Vcpu};
use resources::SystemAllocator;
@@ -13,7 +13,7 @@ mod kvm;
pub use self::kvm::KvmKernelIrqChip;
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
-pub use self::kvm::AARCH64_GIC_NR_IRQS;
+pub use self::kvm::{AARCH64_GIC_NR_IRQS, AARCH64_GIC_NR_SPIS};
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub use self::kvm::KvmSplitIrqChip;
@@ -42,6 +42,7 @@ pub use ioapic::*;
pub type IrqEventIndex = usize;
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
struct IrqEvent {
event: Event,
gsi: u32,
@@ -60,16 +61,25 @@ pub trait IrqChip: Send {
/// Add a vcpu to the irq chip.
fn add_vcpu(&mut self, vcpu_id: usize, vcpu: &dyn Vcpu) -> Result<()>;
- /// Register an event that can trigger an interrupt for a particular GSI.
- fn register_irq_event(
+ /// Register an event with edge-trigger semantic that can trigger an interrupt for a particular GSI.
+ fn register_edge_irq_event(
&mut self,
irq: u32,
- irq_event: &Event,
- resample_event: Option<&Event>,
+ irq_event: &IrqEdgeEvent,
) -> Result<Option<IrqEventIndex>>;
- /// Unregister an event for a particular GSI.
- fn unregister_irq_event(&mut self, irq: u32, irq_event: &Event) -> Result<()>;
+ /// Unregister an event with edge-trigger semantic for a particular GSI.
+ fn unregister_edge_irq_event(&mut self, irq: u32, irq_event: &IrqEdgeEvent) -> Result<()>;
+
+ /// Register an event with level-trigger semantic that can trigger an interrupt for a particular GSI.
+ fn register_level_irq_event(
+ &mut self,
+ irq: u32,
+ irq_event: &IrqLevelEvent,
+ ) -> Result<Option<IrqEventIndex>>;
+
+ /// Unregister an event with level-trigger semantic for a particular GSI.
+ fn unregister_level_irq_event(&mut self, irq: u32, irq_event: &IrqLevelEvent) -> Result<()>;
/// Route an IRQ line to an interrupt controller, or to a particular MSI vector.
fn route_irq(&mut self, route: IrqRoute) -> Result<()>;
@@ -127,13 +137,19 @@ pub trait IrqChip: Send {
fn finalize_devices(
&mut self,
resources: &mut SystemAllocator,
- io_bus: &mut Bus,
- mmio_bus: &mut Bus,
+ io_bus: &Bus,
+ mmio_bus: &Bus,
) -> Result<()>;
/// Process any irqs events that were delayed because of any locking issues.
fn process_delayed_irq_events(&mut self) -> Result<()>;
+ /// Return an event which is meant to trigger process of any irqs events that were delayed
+ /// by calling process_delayed_irq_events(). This should be used by the main thread to wait
+ /// for delayed irq event kick. It is process_delayed_irq_events() responsibility to read
+ /// the event as long as there is no more irqs to be serviced.
+ fn irq_delayed_event_token(&self) -> Result<Option<Event>>;
+
/// Checks if a particular `IrqChipCap` is available.
fn check_capability(&self, c: IrqChipCap) -> bool;
}
diff --git a/devices/src/irqchip/pic.rs b/devices/src/irqchip/pic.rs
index f26e073f7..a0f0fc363 100644
--- a/devices/src/irqchip/pic.rs
+++ b/devices/src/irqchip/pic.rs
@@ -191,17 +191,14 @@ impl Pic {
/// Determines the external interrupt number that the PIC is prepared to inject, if any.
pub fn get_external_interrupt(&mut self) -> Option<u8> {
self.interrupt_request = false;
- let irq_primary = if let Some(irq) = self.get_irq(PicSelect::Primary) {
- irq
- } else {
- // The architecturally correct behavior in this case is to inject a spurious interrupt.
- // Although this case only occurs as a result of a race condition where the interrupt
- // might also be avoided entirely. Here we return `None` to avoid the interrupt
- // entirely. The KVM unit test OS, which several unit tests rely upon, doesn't
- // properly handle spurious interrupts. Also spurious interrupts are much more common
- // in this code than real hardware because the hardware race is much much much smaller.
- return None;
- };
+ // If there is no interrupt request, return `None` to avoid the interrupt entirely.
+ // The architecturally correct behavior in this case is to inject a spurious interrupt.
+ // Although this case only occurs as a result of a race condition where the interrupt
+ // might also be avoided entirely. The KVM unit test OS, which several unit tests rely
+ // upon, doesn't properly handle spurious interrupts. Also spurious interrupts are much
+ // more common in this code than real hardware because the hardware race is much much much
+ // smaller.
+ let irq_primary = self.get_irq(PicSelect::Primary)?;
self.interrupt_ack(PicSelect::Primary, irq_primary);
let int_num = if irq_primary == PRIMARY_PIC_CASCADE_PIN {
@@ -544,15 +541,17 @@ mod tests {
// The PIC is added to the io_bus in three locations, so the offset depends on which
// address range the address is in. The PIC implementation currently does not use the
// offset, but we're setting it accurately here in case it does in the future.
- let offset = match address {
- x if x >= PIC_PRIMARY && x < PIC_PRIMARY + 0x2 => address - PIC_PRIMARY,
- x if x >= PIC_SECONDARY && x < PIC_SECONDARY + 0x2 => address - PIC_SECONDARY,
- x if x >= PIC_PRIMARY_ELCR && x < PIC_PRIMARY_ELCR + 0x2 => address - PIC_PRIMARY_ELCR,
- _ => panic!("invalid PIC address: {:#x}", address),
+ let base_address = if (PIC_PRIMARY..PIC_PRIMARY + 0x2).contains(&address) {
+ PIC_PRIMARY
+ } else if (PIC_SECONDARY..PIC_SECONDARY + 0x2).contains(&address) {
+ PIC_SECONDARY
+ } else if (PIC_PRIMARY_ELCR..PIC_PRIMARY_ELCR + 0x2).contains(&address) {
+ PIC_PRIMARY_ELCR
+ } else {
+ panic!("invalid PIC address: {:#x}", address);
};
-
BusAccessInfo {
- offset,
+ offset: address - base_address,
address,
id: 0,
}
diff --git a/devices/src/irqchip/x86_64.rs b/devices/src/irqchip/x86_64.rs
index f0e57a48e..5f2f599db 100644
--- a/devices/src/irqchip/x86_64.rs
+++ b/devices/src/irqchip/x86_64.rs
@@ -8,6 +8,15 @@ use hypervisor::{IoapicState, LapicState, PicSelect, PicState, PitState};
use crate::IrqChip;
pub trait IrqChipX86_64: IrqChip {
+ // Clones this trait as a `Box` version of itself.
+ fn try_box_clone(&self) -> Result<Box<dyn IrqChipX86_64>>;
+
+ // Get this as the super-trait IrqChip.
+ fn as_irq_chip(&self) -> &dyn IrqChip;
+
+ // Get this as the mutable super-trait IrqChip.
+ fn as_irq_chip_mut(&mut self) -> &mut dyn IrqChip;
+
/// Get the current state of the PIC
fn get_pic_state(&self, select: PicSelect) -> Result<PicState>;
diff --git a/devices/src/lib.rs b/devices/src/lib.rs
index 60b46934f..57fe6f99e 100644
--- a/devices/src/lib.rs
+++ b/devices/src/lib.rs
@@ -11,19 +11,27 @@ pub mod direct_io;
#[cfg(feature = "direct")]
pub mod direct_irq;
mod i8042;
+mod irq_event;
pub mod irqchip;
mod pci;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
mod pit;
pub mod pl030;
+mod platform;
mod proxy;
+#[cfg(feature = "usb")]
#[macro_use]
mod register_space;
pub mod acpi;
pub mod bat;
mod serial;
-mod serial_device;
+pub mod serial_device;
+#[cfg(feature = "tpm")]
+mod software_tpm;
+mod sys;
+#[cfg(feature = "usb")]
pub mod usb;
+#[cfg(feature = "usb")]
mod utils;
pub mod vfio;
pub mod virtio;
@@ -31,37 +39,81 @@ pub mod virtio;
pub use self::acpi::ACPIPMResource;
pub use self::bat::{BatteryError, GoldfishBattery};
pub use self::bus::Error as BusError;
-pub use self::bus::{Bus, BusAccessInfo, BusDevice, BusDeviceSync, BusRange, BusResumeDevice};
+pub use self::bus::{
+ Bus, BusAccessInfo, BusDevice, BusDeviceObj, BusDeviceSync, BusRange, BusResumeDevice, BusType,
+ HostHotPlugKey, HotPlugBus,
+};
pub use self::cmos::Cmos;
#[cfg(feature = "direct")]
-pub use self::direct_io::DirectIo;
+pub use self::direct_io::{DirectIo, DirectMmio};
#[cfg(feature = "direct")]
pub use self::direct_irq::{DirectIrq, DirectIrqError};
pub use self::i8042::I8042Device;
+pub use self::irq_event::{IrqEdgeEvent, IrqLevelEvent};
pub use self::irqchip::*;
#[cfg(feature = "audio")]
pub use self::pci::{Ac97Backend, Ac97Dev, Ac97Parameters};
pub use self::pci::{
- PciAddress, PciConfigIo, PciConfigMmio, PciDevice, PciDeviceError, PciInterruptPin, PciRoot,
- VfioPciDevice,
+ BarRange, CoIommuDev, CoIommuParameters, CoIommuUnpinPolicy, PciAddress, PciBridge,
+ PciClassCode, PciConfigIo, PciConfigMmio, PciDevice, PciDeviceError, PciInterruptPin, PciRoot,
+ PciVirtualConfigMmio, PcieHostRootPort, PcieRootPort, PvPanicCode, PvPanicPciDevice,
+ StubPciDevice, StubPciParameters, VfioPciDevice,
};
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub use self::pit::{Pit, PitError};
pub use self::pl030::Pl030;
+pub use self::platform::VfioPlatformDevice;
pub use self::proxy::Error as ProxyError;
pub use self::proxy::ProxyDevice;
pub use self::serial::Serial;
-pub use self::serial_device::SerialDevice;
+pub use self::serial_device::{
+ Error as SerialError, SerialDevice, SerialHardware, SerialParameters, SerialType,
+};
+#[cfg(feature = "tpm")]
+pub use self::software_tpm::SoftwareTpm;
+#[cfg(feature = "usb")]
pub use self::usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider;
+#[cfg(feature = "usb")]
pub use self::usb::xhci::xhci_controller::XhciController;
pub use self::vfio::{VfioContainer, VfioDevice};
-pub use self::virtio::VirtioPciDevice;
+pub use self::virtio::{vfio_wrapper, VirtioPciDevice};
+
+/// Request CoIOMMU to unpin a specific range.
+use serde::{Deserialize, Serialize};
+#[derive(Serialize, Deserialize, Debug)]
+pub struct UnpinRequest {
+ /// The ranges presents (start gfn, count).
+ ranges: Vec<(u64, u64)>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub enum UnpinResponse {
+ Success,
+ Failed,
+}
+
+#[derive(Debug)]
+pub enum ParseIommuDevTypeResult {
+ NoSuchType,
+}
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+pub enum IommuDevType {
+ NoIommu,
+ VirtioIommu,
+ CoIommu,
+}
+
+use std::str::FromStr;
+impl FromStr for IommuDevType {
+ type Err = ParseIommuDevTypeResult;
-/// Whether the VM should be run in protected mode or not.
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-pub enum ProtectionType {
- /// The VM should be run in the unprotected mode, where the host has access to its memory.
- Unprotected,
- /// The VM should be run in protected mode, so the host cannot access its memory directly.
- Protected,
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "off" => Ok(IommuDevType::NoIommu),
+ "viommu" => Ok(IommuDevType::VirtioIommu),
+ "coiommu" => Ok(IommuDevType::CoIommu),
+ _ => Err(ParseIommuDevTypeResult::NoSuchType),
+ }
+ }
}
diff --git a/devices/src/pci/ac97.rs b/devices/src/pci/ac97.rs
index c618b14d6..8a4fd0ffa 100644
--- a/devices/src/pci/ac97.rs
+++ b/devices/src/pci/ac97.rs
@@ -3,29 +3,32 @@
// found in the LICENSE file.
use std::default::Default;
-use std::error;
-use std::fmt::{self, Display};
use std::path::PathBuf;
use std::str::FromStr;
use audio_streams::shm_streams::{NullShmStreamSource, ShmStreamSource};
-use base::{error, Event, RawDescriptor};
+use base::{error, AsRawDescriptor, RawDescriptor};
+#[cfg(feature = "audio_cras")]
use libcras::{CrasClient, CrasClientType, CrasSocketType, CrasSysError};
+use remain::sorted;
use resources::{Alloc, MmioType, SystemAllocator};
+use thiserror::Error;
use vm_memory::GuestMemory;
use crate::pci::ac97_bus_master::Ac97BusMaster;
use crate::pci::ac97_mixer::Ac97Mixer;
use crate::pci::ac97_regs::*;
use crate::pci::pci_configuration::{
- PciBarConfiguration, PciClassCode, PciConfiguration, PciHeaderType, PciMultimediaSubclass,
+ PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciClassCode, PciConfiguration,
+ PciHeaderType, PciMultimediaSubclass,
};
-use crate::pci::pci_device::{self, PciDevice, Result};
+use crate::pci::pci_device::{self, BarRange, PciDevice, Result};
use crate::pci::{PciAddress, PciDeviceError, PciInterruptPin};
#[cfg(not(any(target_os = "linux", target_os = "android")))]
use crate::virtio::snd::vios_backend::Error as VioSError;
#[cfg(any(target_os = "linux", target_os = "android"))]
use crate::virtio::snd::vios_backend::VioSShmStreamSource;
+use crate::IrqLevelEvent;
// Use 82801AA because it's what qemu does.
const PCI_DEVICE_ID_INTEL_82801AA_5: u16 = 0x2415;
@@ -38,6 +41,7 @@ const PCI_DEVICE_ID_INTEL_82801AA_5: u16 = 0x2415;
#[derive(Debug, Clone)]
pub enum Ac97Backend {
NULL,
+ #[cfg(feature = "audio_cras")]
CRAS,
VIOS,
}
@@ -49,27 +53,20 @@ impl Default for Ac97Backend {
}
/// Errors that are possible from a `Ac97`.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Ac97Error {
+ #[error("Must be cras, vios or null")]
InvalidBackend,
+ #[error("server must be provided for vios backend")]
MissingServerPath,
}
-impl error::Error for Ac97Error {}
-
-impl Display for Ac97Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Ac97Error::InvalidBackend => write!(f, "Must be cras, vios or null"),
- Ac97Error::MissingServerPath => write!(f, "server must be provided for vios backend"),
- }
- }
-}
-
impl FromStr for Ac97Backend {
type Err = Ac97Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
+ #[cfg(feature = "audio_cras")]
"cras" => Ok(Ac97Backend::CRAS),
"vios" => Ok(Ac97Backend::VIOS),
"null" => Ok(Ac97Backend::NULL),
@@ -84,17 +81,33 @@ pub struct Ac97Parameters {
pub backend: Ac97Backend,
pub capture: bool,
pub vios_server_path: Option<PathBuf>,
+ #[cfg(feature = "audio_cras")]
client_type: Option<CrasClientType>,
+ #[cfg(feature = "audio_cras")]
+ socket_type: Option<CrasSocketType>,
}
impl Ac97Parameters {
/// Set CRAS client type by given client type string.
///
/// `client_type` - The client type string.
+ #[cfg(feature = "audio_cras")]
pub fn set_client_type(&mut self, client_type: &str) -> std::result::Result<(), CrasSysError> {
self.client_type = Some(client_type.parse()?);
Ok(())
}
+
+ /// Set CRAS socket type by given socket type string.
+ ///
+ /// `socket_type` - The socket type string.
+ #[cfg(feature = "audio_cras")]
+ pub fn set_socket_type(
+ &mut self,
+ socket_type: &str,
+ ) -> std::result::Result<(), libcras::Error> {
+ self.socket_type = Some(socket_type.parse()?);
+ Ok(())
+ }
}
pub struct Ac97Dev {
@@ -102,8 +115,7 @@ pub struct Ac97Dev {
pci_address: Option<PciAddress>,
// The irq events are temporarily saved here. They need to be passed to the device after the
// jail forks. This happens when the bus is first written.
- irq_evt: Option<Event>,
- irq_resample_evt: Option<Event>,
+ irq_evt: Option<IrqLevelEvent>,
bus_master: Ac97BusMaster,
mixer: Ac97Mixer,
backend: Ac97Backend,
@@ -115,7 +127,7 @@ impl Ac97Dev {
pub fn new(
mem: GuestMemory,
backend: Ac97Backend,
- audio_server: Box<dyn ShmStreamSource>,
+ audio_server: Box<dyn ShmStreamSource<base::Error>>,
) -> Self {
let config_regs = PciConfiguration::new(
0x8086,
@@ -133,7 +145,6 @@ impl Ac97Dev {
config_regs,
pci_address: None,
irq_evt: None,
- irq_resample_evt: None,
bus_master: Ac97BusMaster::new(mem, audio_server),
mixer: Ac97Mixer::new(),
backend,
@@ -144,6 +155,7 @@ impl Ac97Dev {
/// to create `Ac97Dev` with the given back-end, it'll fallback to the null audio device.
pub fn try_new(mem: GuestMemory, param: Ac97Parameters) -> Result<Self> {
match param.backend {
+ #[cfg(feature = "audio_cras")]
Ac97Backend::CRAS => Self::create_cras_audio_device(param, mem.clone()).or_else(|e| {
error!(
"Ac97Dev: create_cras_audio_device: {}. Fallback to null audio device",
@@ -159,15 +171,17 @@ impl Ac97Dev {
/// Return the minijail policy file path for the current Ac97Dev.
pub fn minijail_policy(&self) -> &'static str {
match self.backend {
+ #[cfg(feature = "audio_cras")]
Ac97Backend::CRAS => "cras_audio_device",
Ac97Backend::VIOS => "vios_audio_device",
Ac97Backend::NULL => "null_audio_device",
}
}
+ #[cfg(feature = "audio_cras")]
fn create_cras_audio_device(params: Ac97Parameters, mem: GuestMemory) -> Result<Self> {
let mut server = Box::new(
- CrasClient::with_type(CrasSocketType::Unified)
+ CrasClient::with_type(params.socket_type.unwrap_or(CrasSocketType::Unified))
.map_err(pci_device::Error::CreateCrasClientFailed)?,
);
server.set_client_type(
@@ -189,10 +203,10 @@ impl Ac97Dev {
let server = Box::new(
// The presence of vios_server_path is checked during argument parsing
VioSShmStreamSource::new(param.vios_server_path.expect("Missing server path"))
- .map_err(|e| pci_device::Error::CreateViosClientFailed(e))?,
+ .map_err(pci_device::Error::CreateViosClientFailed)?,
);
let vios_audio = Self::new(mem, Ac97Backend::VIOS, server);
- return Ok(vios_audio);
+ Ok(vios_audio)
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
Err(pci_device::Error::CreateViosClientFailed(
@@ -274,7 +288,7 @@ impl PciDevice for Ac97Dev {
fn allocate_address(&mut self, resources: &mut SystemAllocator) -> Result<PciAddress> {
if self.pci_address.is_none() {
- self.pci_address = match resources.allocate_pci(self.debug_label()) {
+ self.pci_address = match resources.allocate_pci(0, self.debug_label()) {
Some(Alloc::PciBar {
bus,
dev,
@@ -289,21 +303,24 @@ impl PciDevice for Ac97Dev {
fn assign_irq(
&mut self,
- irq_evt: Event,
- irq_resample_evt: Event,
- irq_num: u32,
- irq_pin: PciInterruptPin,
- ) {
- self.config_regs.set_irq(irq_num as u8, irq_pin);
- self.irq_evt = Some(irq_evt);
- self.irq_resample_evt = Some(irq_resample_evt);
+ irq_evt: &IrqLevelEvent,
+ irq_num: Option<u32>,
+ ) -> Option<(u32, PciInterruptPin)> {
+ self.irq_evt = Some(irq_evt.try_clone().ok()?);
+ let gsi = irq_num?;
+ let pin = self.pci_address.map_or(
+ PciInterruptPin::IntA,
+ PciConfiguration::suggested_interrupt_pin,
+ );
+ self.config_regs.set_irq(gsi as u8, pin);
+ Some((gsi, pin))
}
- fn allocate_io_bars(&mut self, resources: &mut SystemAllocator) -> Result<Vec<(u64, u64)>> {
+ fn allocate_io_bars(&mut self, resources: &mut SystemAllocator) -> Result<Vec<BarRange>> {
let address = self
.pci_address
.expect("allocate_address must be called prior to allocate_io_bars");
- let mut ranges = Vec::new();
+ let mut ranges: Vec<BarRange> = Vec::new();
let mixer_regs_addr = resources
.mmio_allocator(MmioType::Low)
.allocate_with_align(
@@ -318,14 +335,21 @@ impl PciDevice for Ac97Dev {
MIXER_REGS_SIZE,
)
.map_err(|e| pci_device::Error::IoAllocationFailed(MIXER_REGS_SIZE, e))?;
- let mixer_config = PciBarConfiguration::default()
- .set_register_index(0)
- .set_address(mixer_regs_addr)
- .set_size(MIXER_REGS_SIZE);
+ let mixer_config = PciBarConfiguration::new(
+ 0,
+ MIXER_REGS_SIZE,
+ PciBarRegionType::Memory32BitRegion,
+ PciBarPrefetchable::NotPrefetchable,
+ )
+ .set_address(mixer_regs_addr);
self.config_regs
.add_pci_bar(mixer_config)
.map_err(|e| pci_device::Error::IoRegistrationFailed(mixer_regs_addr, e))?;
- ranges.push((mixer_regs_addr, MIXER_REGS_SIZE));
+ ranges.push(BarRange {
+ addr: mixer_regs_addr,
+ size: MIXER_REGS_SIZE,
+ prefetchable: false,
+ });
let master_regs_addr = resources
.mmio_allocator(MmioType::Low)
@@ -341,17 +365,28 @@ impl PciDevice for Ac97Dev {
MASTER_REGS_SIZE,
)
.map_err(|e| pci_device::Error::IoAllocationFailed(MASTER_REGS_SIZE, e))?;
- let master_config = PciBarConfiguration::default()
- .set_register_index(1)
- .set_address(master_regs_addr)
- .set_size(MASTER_REGS_SIZE);
+ let master_config = PciBarConfiguration::new(
+ 1,
+ MASTER_REGS_SIZE,
+ PciBarRegionType::Memory32BitRegion,
+ PciBarPrefetchable::NotPrefetchable,
+ )
+ .set_address(master_regs_addr);
self.config_regs
.add_pci_bar(master_config)
.map_err(|e| pci_device::Error::IoRegistrationFailed(master_regs_addr, e))?;
- ranges.push((master_regs_addr, MASTER_REGS_SIZE));
+ ranges.push(BarRange {
+ addr: master_regs_addr,
+ size: MASTER_REGS_SIZE,
+ prefetchable: false,
+ });
Ok(ranges)
}
+ fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
+ self.config_regs.get_bar_configuration(bar_num)
+ }
+
fn read_config_register(&self, reg_idx: usize) -> u32 {
self.config_regs.read_reg(reg_idx)
}
@@ -361,11 +396,15 @@ impl PciDevice for Ac97Dev {
}
fn keep_rds(&self) -> Vec<RawDescriptor> {
- if let Some(server_fds) = self.bus_master.keep_rds() {
- server_fds
- } else {
- Vec::new()
+ let mut rds = Vec::new();
+ if let Some(mut server_fds) = self.bus_master.keep_rds() {
+ rds.append(&mut server_fds);
+ }
+ if let Some(irq_evt) = &self.irq_evt {
+ rds.push(irq_evt.get_trigger().as_raw_descriptor());
+ rds.push(irq_evt.get_resample().as_raw_descriptor());
}
+ rds
}
fn read_bar(&mut self, addr: u64, data: &mut [u8]) {
@@ -387,10 +426,8 @@ impl PciDevice for Ac97Dev {
a if a >= bar0 && a < bar0 + MIXER_REGS_SIZE => self.write_mixer(addr - bar0, data),
a if a >= bar1 && a < bar1 + MASTER_REGS_SIZE => {
// Check if the irq needs to be passed to the device.
- if let (Some(irq_evt), Some(irq_resample_evt)) =
- (self.irq_evt.take(), self.irq_resample_evt.take())
- {
- self.bus_master.set_irq_event(irq_evt, irq_resample_evt);
+ if let Some(irq_evt) = self.irq_evt.take() {
+ self.bus_master.set_irq_event(irq_evt);
}
self.write_bus_master(addr - bar1, data)
}
@@ -403,6 +440,7 @@ impl PciDevice for Ac97Dev {
mod tests {
use super::*;
use audio_streams::shm_streams::MockShmStreamSource;
+ use resources::{MemRegion, SystemAllocatorConfig};
use vm_memory::GuestAddress;
#[test]
@@ -410,12 +448,27 @@ mod tests {
let mem = GuestMemory::new(&[(GuestAddress(0u64), 4 * 1024 * 1024)]).unwrap();
let mut ac97_dev =
Ac97Dev::new(mem, Ac97Backend::NULL, Box::new(MockShmStreamSource::new()));
- let mut allocator = SystemAllocator::builder()
- .add_io_addresses(0x1000_0000, 0x1000_0000)
- .add_low_mmio_addresses(0x2000_0000, 0x1000_0000)
- .add_high_mmio_addresses(0x3000_0000, 0x1000_0000)
- .create_allocator(5)
- .unwrap();
+ let mut allocator = SystemAllocator::new(
+ SystemAllocatorConfig {
+ io: Some(MemRegion {
+ base: 0xc000,
+ size: 0x4000,
+ }),
+ low_mmio: MemRegion {
+ base: 0x2000_0000,
+ size: 0x1000_0000,
+ },
+ high_mmio: MemRegion {
+ base: 0x3000_0000,
+ size: 0x1000_0000,
+ },
+ platform_mmio: None,
+ first_irq: 5,
+ },
+ None,
+ &[],
+ )
+ .unwrap();
assert!(ac97_dev.allocate_address(&mut allocator).is_ok());
assert!(ac97_dev.allocate_io_bars(&mut allocator).is_ok());
}
diff --git a/devices/src/pci/ac97_bus_master.rs b/devices/src/pci/ac97_bus_master.rs
index e121a6f7b..f0350f246 100644
--- a/devices/src/pci/ac97_bus_master.rs
+++ b/devices/src/pci/ac97_bus_master.rs
@@ -4,7 +4,6 @@
use std::collections::VecDeque;
use std::convert::TryInto;
-use std::fmt::{self, Display};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
@@ -15,14 +14,17 @@ use audio_streams::{
BoxError, NoopStreamControl, SampleFormat, StreamControl, StreamDirection, StreamEffect,
};
use base::{
- self, error, set_rt_prio_limit, set_rt_round_robin, warn, AsRawDescriptors, Event,
- RawDescriptor,
+ self, error, set_rt_prio_limit, set_rt_round_robin, warn, AsRawDescriptor, AsRawDescriptors,
+ FromRawDescriptor, RawDescriptor, SharedMemoryUnix,
};
+use remain::sorted;
use sync::{Condvar, Mutex};
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory};
use crate::pci::ac97_mixer::Ac97Mixer;
use crate::pci::ac97_regs::*;
+use crate::IrqLevelEvent;
const INPUT_SAMPLE_RATE: u32 = 48000;
const DEVICE_INPUT_CHANNEL_COUNT: usize = 2;
@@ -38,7 +40,7 @@ struct Ac97BusMasterRegs {
glob_sta: u32,
// IRQ event - driven by the glob_sta register.
- irq_evt: Option<Event>,
+ irq_evt: Option<IrqLevelEvent>,
}
impl Ac97BusMasterRegs {
@@ -97,26 +99,14 @@ impl Ac97BusMasterRegs {
}
// Internal error type used for reporting errors from guest memory reading.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
enum GuestMemoryError {
// Failure getting the address of the audio buffer.
+ #[error("Failed to get the address of the audio buffer: {0}.")]
ReadingGuestBufferAddress(vm_memory::GuestMemoryError),
}
-impl std::error::Error for GuestMemoryError {}
-
-impl Display for GuestMemoryError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::GuestMemoryError::*;
-
- match self {
- ReadingGuestBufferAddress(e) => {
- write!(f, "Failed to get the address of the audio buffer: {}.", e)
- }
- }
- }
-}
-
impl From<GuestMemoryError> for AudioError {
fn from(err: GuestMemoryError) -> Self {
AudioError::ReadingGuestError(err)
@@ -126,42 +116,38 @@ impl From<GuestMemoryError> for AudioError {
type GuestMemoryResult<T> = std::result::Result<T, GuestMemoryError>;
// Internal error type used for reporting errors from the audio thread.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
enum AudioError {
+ // Failed to clone a descriptor.
+ #[error("Failed to clone a descriptor: {0}")]
+ CloneDescriptor(base::Error),
+ // Failed to create a shared memory.
+ #[error("Failed to create a shared memory: {0}.")]
+ CreateSharedMemory(base::Error),
// Failed to create a new stream.
+ #[error("Failed to create audio stream: {0}.")]
CreateStream(BoxError),
// Failure to get regions from guest memory.
+ #[error("Failed to get guest memory region: {0}.")]
GuestRegion(GuestMemoryError),
// Invalid buffer offset received from the audio server.
+ #[error("Offset > max usize")]
InvalidBufferOffset,
// Guest did not provide a buffer when needed.
+ #[error("No buffer was available from the Guest")]
NoBufferAvailable,
// Failure to read guest memory.
+ #[error("Failed to read guest memory: {0}.")]
ReadingGuestError(GuestMemoryError),
// Failure to respond to the ServerRequest.
+ #[error("Failed to respond to the ServerRequest: {0}")]
RespondRequest(BoxError),
// Failure to wait for a request from the stream.
+ #[error("Failed to wait for a message from the stream: {0}")]
WaitForAction(BoxError),
}
-impl std::error::Error for AudioError {}
-
-impl Display for AudioError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::AudioError::*;
-
- match self {
- CreateStream(e) => write!(f, "Failed to create audio stream: {}.", e),
- GuestRegion(e) => write!(f, "Failed to get guest memory region: {}.", e),
- InvalidBufferOffset => write!(f, "Offset > max usize"),
- NoBufferAvailable => write!(f, "No buffer was available from the Guest"),
- ReadingGuestError(e) => write!(f, "Failed to read guest memory: {}.", e),
- RespondRequest(e) => write!(f, "Failed to respond to the ServerRequest: {}", e),
- WaitForAction(e) => write!(f, "Failed to wait for a message from the stream: {}", e),
- }
- }
-}
-
type AudioResult<T> = std::result::Result<T, AudioError>;
// Audio thread book-keeping data
@@ -231,7 +217,7 @@ pub struct Ac97BusMaster {
pmic_info: AudioThreadInfo,
// Audio server used to create playback or capture streams.
- audio_server: Box<dyn ShmStreamSource>,
+ audio_server: Box<dyn ShmStreamSource<base::Error>>,
// Thread for hadlind IRQ resample events from the guest.
irq_resample_thread: Option<thread::JoinHandle<()>>,
@@ -240,7 +226,7 @@ pub struct Ac97BusMaster {
impl Ac97BusMaster {
/// Creates an Ac97BusMaster` object that plays audio from `mem` to streams provided by
/// `audio_server`.
- pub fn new(mem: GuestMemory, audio_server: Box<dyn ShmStreamSource>) -> Self {
+ pub fn new(mem: GuestMemory, audio_server: Box<dyn ShmStreamSource<base::Error>>) -> Self {
Ac97BusMaster {
mem,
regs: Arc::new(Mutex::new(Ac97BusMasterRegs::new())),
@@ -263,12 +249,12 @@ impl Ac97BusMaster {
}
/// Provides the events needed to raise interrupts in the guest.
- pub fn set_irq_event(&mut self, irq_evt: Event, irq_resample_evt: Event) {
+ pub fn set_irq_event(&mut self, irq_evt: IrqLevelEvent) {
let thread_regs = self.regs.clone();
- self.regs.lock().irq_evt = Some(irq_evt);
+ self.regs.lock().irq_evt = Some(irq_evt.try_clone().expect("cloning irq_evt failed"));
self.irq_resample_thread = Some(thread::spawn(move || {
loop {
- if let Err(e) = irq_resample_evt.read() {
+ if let Err(e) = irq_evt.get_resample().read() {
error!(
"Failed to read the irq event from the resample thread: {}.",
e,
@@ -279,11 +265,9 @@ impl Ac97BusMaster {
// Scope for the lock on thread_regs.
let regs = thread_regs.lock();
if regs.has_irq() {
- if let Some(irq_evt) = regs.irq_evt.as_ref() {
- if let Err(e) = irq_evt.write(1) {
- error!("Failed to set the irq from the resample thread: {}.", e);
- break;
- }
+ if let Err(e) = irq_evt.trigger() {
+ error!("Failed to set the irq from the resample thread: {}.", e);
+ break;
}
}
}
@@ -582,6 +566,21 @@ impl Ac97BusMaster {
}
StreamDirection::Playback => [0, 0],
};
+
+ // Create a `base::SharedMemory` object from a descriptor backing `self.mem`.
+ // This creation is expected to succeed because we can assume that `self.mem` was created
+ // from a `SharedMemory` object and its type was generalized to `dyn AsRawDescriptor`.
+ let desc: &dyn AsRawDescriptor = self
+ .mem
+ .offset_region(starting_offsets[0])
+ .map_err(|e| AudioError::GuestRegion(GuestMemoryError::ReadingGuestBufferAddress(e)))?;
+ let shm = {
+ let rd = base::clone_descriptor(desc).map_err(AudioError::CloneDescriptor)?;
+ // Safe because the fd is owned.
+ let sd = unsafe { base::SafeDescriptor::from_raw_descriptor(rd) };
+ base::SharedMemory::from_safe_descriptor(sd).map_err(AudioError::CreateSharedMemory)?
+ };
+
let stream = self
.audio_server
.new_stream(
@@ -591,12 +590,7 @@ impl Ac97BusMaster {
sample_rate,
buffer_frames,
&Self::stream_effects(func),
- self.mem
- .offset_region(starting_offsets[0])
- .map_err(|e| {
- AudioError::GuestRegion(GuestMemoryError::ReadingGuestBufferAddress(e))
- })?
- .inner(),
+ &shm,
starting_offsets,
)
.map_err(AudioError::CreateStream)?;
@@ -607,7 +601,7 @@ impl Ac97BusMaster {
pending_buffers,
message_interval: Duration::from_secs_f64(buffer_frames as f64 / sample_rate as f64),
};
- Ok(AudioWorker::new(&self, params))
+ Ok(AudioWorker::new(self, params))
}
fn thread_info(&self, func: Ac97Function) -> &AudioThreadInfo {
@@ -662,7 +656,6 @@ impl Ac97BusMaster {
#[derive(Debug)]
struct GuestBuffer {
- index: u8,
offset: usize,
frames: usize,
}
@@ -739,11 +732,7 @@ fn next_guest_buffer(
.map_err(|_| AudioError::InvalidBufferOffset)?;
let frames = get_buffer_samples(func_regs, mem, index)? / regs.tube_count(func);
- Ok(Some(GuestBuffer {
- index,
- offset,
- frames,
- }))
+ Ok(Some(GuestBuffer { offset, frames }))
}
// Marks the current buffer completed and moves to the next buffer for the given
@@ -776,7 +765,7 @@ fn buffer_completed(
update_sr(regs, func, new_sr);
- regs.func_regs_mut(func).picb = current_buffer_size(regs.func_regs(func), &mem)? as u16;
+ regs.func_regs_mut(func).picb = current_buffer_size(regs.func_regs(func), mem)? as u16;
if func == Ac97Function::Output {
regs.po_pointer_update_time = Instant::now();
}
@@ -949,9 +938,9 @@ fn update_sr(regs: &mut Ac97BusMasterRegs, func: Ac97Function, val: u16) {
if interrupt_high {
regs.glob_sta |= int_mask;
- if let Some(irq_evt) = regs.irq_evt.as_ref() {
+ if let Some(ref irq_evt) = regs.irq_evt {
// Ignore write failure, nothing can be done about it from here.
- let _ = irq_evt.write(1);
+ let _ = irq_evt.trigger();
}
} else {
regs.glob_sta &= !int_mask;
@@ -1088,7 +1077,7 @@ mod test {
mem.write_obj_at_addr(GUEST_ADDR_BASE + FRAGMENT_SIZE as u32, pointer_addr)
.expect("Writing guest memory failed.");
};
- mem.write_obj_at_addr(IOC_MASK | (FRAGMENT_SIZE as u32) / 2, control_addr)
+ mem.write_obj_at_addr(IOC_MASK | ((FRAGMENT_SIZE as u32) / 2), control_addr)
.expect("Writing guest memory failed.");
}
@@ -1226,8 +1215,7 @@ mod test {
GS_MINT,
),
_ => {
- assert!(false, "Invalid Ac97Function.");
- (0, 0, 0, 0, 0, 0, 0)
+ panic!("Invalid Ac97Function.");
}
};
@@ -1241,7 +1229,7 @@ mod test {
let control_addr = GuestAddress(GUEST_ADDR_BASE as u64 + i as u64 * 8 + 4);
mem.write_obj_at_addr(GUEST_ADDR_BASE + FRAGMENT_SIZE as u32, pointer_addr)
.expect("Writing guest memory failed.");
- mem.write_obj_at_addr(IOC_MASK | (FRAGMENT_SIZE as u32) / 2, control_addr)
+ mem.write_obj_at_addr(IOC_MASK | ((FRAGMENT_SIZE as u32) / 2), control_addr)
.expect("Writing guest memory failed.");
}
diff --git a/devices/src/pci/coiommu.rs b/devices/src/pci/coiommu.rs
new file mode 100644
index 000000000..421eea5dc
--- /dev/null
+++ b/devices/src/pci/coiommu.rs
@@ -0,0 +1,1615 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! This is the CoIOMMU backend implementation. CoIOMMU is a virtual device
+//! which provide fine-grained pinning for the VFIO pci-passthrough device
+//! so that hypervisor doesn't need to pin the enter VM's memory to improve
+//! the memory utilization. CoIOMMU doesn't provide the intra-guest protection
+//! so it can only be used for the TRUSTED passthrough devices.
+//!
+//! CoIOMMU is presented at KVM forum 2020:
+//! https://kvmforum2020.sched.com/event/eE2z/a-virtual-iommu-with-cooperative
+//! -dma-buffer-tracking-yu-zhang-intel
+//!
+//! Also presented at usenix ATC20:
+//! https://www.usenix.org/conference/atc20/presentation/tian
+
+use std::collections::VecDeque;
+use std::convert::TryInto;
+use std::default::Default;
+use std::panic;
+use std::str::FromStr;
+use std::sync::atomic::{fence, AtomicU32, Ordering};
+use std::sync::Arc;
+use std::time::Duration;
+use std::{fmt, mem, thread};
+
+use anyhow::{anyhow, bail, ensure, Context, Result};
+use base::{
+ error, info, AsRawDescriptor, Event, MemoryMapping, MemoryMappingBuilder, PollToken,
+ RawDescriptor, SafeDescriptor, SharedMemory, Timer, Tube, TubeError, WaitContext,
+};
+use data_model::DataInit;
+use hypervisor::Datamatch;
+use resources::{Alloc, MmioType, SystemAllocator};
+use serde::{Deserialize, Serialize};
+use sync::Mutex;
+use thiserror::Error as ThisError;
+
+use vm_control::{VmMemoryDestination, VmMemoryRequest, VmMemoryResponse, VmMemorySource};
+use vm_memory::{GuestAddress, GuestMemory};
+
+use crate::pci::pci_configuration::{
+ PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciClassCode, PciConfiguration,
+ PciHeaderType, PciOtherSubclass, COMMAND_REG, COMMAND_REG_MEMORY_SPACE_MASK,
+};
+use crate::pci::pci_device::{BarRange, PciDevice, Result as PciResult};
+use crate::pci::{PciAddress, PciDeviceError};
+use crate::vfio::VfioContainer;
+use crate::{UnpinRequest, UnpinResponse};
+
+const PCI_VENDOR_ID_COIOMMU: u16 = 0x1234;
+const PCI_DEVICE_ID_COIOMMU: u16 = 0xabcd;
+const COIOMMU_CMD_DEACTIVATE: u64 = 0;
+const COIOMMU_CMD_ACTIVATE: u64 = 1;
+const COIOMMU_CMD_PARK_UNPIN: u64 = 2;
+const COIOMMU_CMD_UNPARK_UNPIN: u64 = 3;
+const COIOMMU_REVISION_ID: u8 = 0x10;
+const COIOMMU_MMIO_BAR: u8 = 0;
+const COIOMMU_MMIO_BAR_SIZE: u64 = 0x2000;
+const COIOMMU_NOTIFYMAP_BAR: u8 = 2;
+const COIOMMU_NOTIFYMAP_SIZE: usize = 0x2000;
+const COIOMMU_TOPOLOGYMAP_BAR: u8 = 4;
+const COIOMMU_TOPOLOGYMAP_SIZE: usize = 0x2000;
+const PAGE_SIZE_4K: u64 = 4096;
+const PAGE_SHIFT_4K: u64 = 12;
+const PIN_PAGES_IN_BATCH: u64 = 1 << 63;
+
+const DTTE_PINNED_FLAG: u32 = 1 << 31;
+const DTTE_ACCESSED_FLAG: u32 = 1 << 30;
+const DTT_ENTRY_PRESENT: u64 = 1;
+const DTT_ENTRY_PFN_SHIFT: u64 = 12;
+
+#[derive(ThisError, Debug)]
+enum Error {
+ #[error("CoIommu failed to create shared memory")]
+ CreateSharedMemory,
+ #[error("Failed to get DTT entry")]
+ GetDTTEntry,
+ #[error("Tube error")]
+ TubeError,
+}
+
+//default interval is 60s
+const UNPIN_DEFAULT_INTERVAL: Duration = Duration::from_secs(60);
+const UNPIN_GEN_DEFAULT_THRES: u64 = 10;
+/// Holds the coiommu unpin policy
+#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
+pub enum CoIommuUnpinPolicy {
+ Off,
+ Lru,
+}
+
+impl FromStr for CoIommuUnpinPolicy {
+ type Err = anyhow::Error;
+
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ match s {
+ "off" => Ok(CoIommuUnpinPolicy::Off),
+ "lru" => Ok(CoIommuUnpinPolicy::Lru),
+ _ => Err(anyhow!(
+ "CoIommu doesn't have such unpin policy: {}",
+ s.to_string()
+ )),
+ }
+ }
+}
+
+impl fmt::Display for CoIommuUnpinPolicy {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::CoIommuUnpinPolicy::*;
+
+ match self {
+ Off => write!(f, "off"),
+ Lru => write!(f, "lru"),
+ }
+ }
+}
+
+/// Holds the parameters for a coiommu device
+#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
+pub struct CoIommuParameters {
+ pub unpin_policy: CoIommuUnpinPolicy,
+ pub unpin_interval: Duration,
+ pub unpin_limit: Option<u64>,
+ // Number of unpin intervals a pinned page must be busy for to be aged into the
+ // older, less frequently checked generation.
+ pub unpin_gen_threshold: u64,
+}
+
+impl Default for CoIommuParameters {
+ fn default() -> Self {
+ Self {
+ unpin_policy: CoIommuUnpinPolicy::Off,
+ unpin_interval: UNPIN_DEFAULT_INTERVAL,
+ unpin_limit: None,
+ unpin_gen_threshold: UNPIN_GEN_DEFAULT_THRES,
+ }
+ }
+}
+
+#[derive(Default, Debug, Copy, Clone)]
+struct CoIommuReg {
+ dtt_root: u64,
+ cmd: u64,
+ dtt_level: u64,
+}
+
+#[derive(Default, Debug, Copy, Clone, PartialEq)]
+struct PinnedPageInfo {
+ gfn: u64,
+ unpin_busy_cnt: u64,
+}
+
+impl PinnedPageInfo {
+ fn new(gfn: u64, unpin_busy_cnt: u64) -> Self {
+ PinnedPageInfo {
+ gfn,
+ unpin_busy_cnt,
+ }
+ }
+}
+
+#[derive(PartialEq, Debug)]
+enum UnpinThreadState {
+ Unparked,
+ Parked,
+}
+
+struct CoIommuPinState {
+ new_gen_pinned_pages: VecDeque<PinnedPageInfo>,
+ old_gen_pinned_pages: VecDeque<u64>,
+ unpin_thread_state: UnpinThreadState,
+ unpin_park_count: u64,
+}
+
+unsafe fn vfio_map(
+ vfio_container: &Arc<Mutex<VfioContainer>>,
+ iova: u64,
+ size: u64,
+ user_addr: u64,
+) -> bool {
+ match vfio_container
+ .lock()
+ .vfio_dma_map(iova, size, user_addr, true)
+ {
+ Ok(_) => true,
+ Err(e) => {
+ if let Some(errno) = std::io::Error::last_os_error().raw_os_error() {
+ if errno == libc::EEXIST {
+ // Already pinned. set PINNED flag
+ error!("CoIommu: iova 0x{:x} already pinned", iova);
+ return true;
+ }
+ }
+ error!("CoIommu: failed to map iova 0x{:x}: {}", iova, e);
+ false
+ }
+ }
+}
+
+fn vfio_unmap(vfio_container: &Arc<Mutex<VfioContainer>>, iova: u64, size: u64) -> bool {
+ match vfio_container.lock().vfio_dma_unmap(iova, size) {
+ Ok(_) => true,
+ Err(e) => {
+ error!("CoIommu: failed to unmap iova 0x{:x}: {}", iova, e);
+ false
+ }
+ }
+}
+
+#[derive(Default, Debug, Copy, Clone)]
+#[repr(C)]
+struct PinPageInfo {
+ bdf: u16,
+ pad: [u16; 3],
+ nr_pages: u64,
+}
+// Safe because the PinPageInfo structure is raw data
+unsafe impl DataInit for PinPageInfo {}
+
+const COIOMMU_UPPER_LEVEL_STRIDE: u64 = 9;
+const COIOMMU_UPPER_LEVEL_MASK: u64 = (1 << COIOMMU_UPPER_LEVEL_STRIDE) - 1;
+const COIOMMU_PT_LEVEL_STRIDE: u64 = 10;
+const COIOMMU_PT_LEVEL_MASK: u64 = (1 << COIOMMU_PT_LEVEL_STRIDE) - 1;
+
+fn level_to_offset(gfn: u64, level: u64) -> Result<u64> {
+ if level == 1 {
+ return Ok(gfn & COIOMMU_PT_LEVEL_MASK);
+ }
+
+ if level == 0 {
+ bail!("Invalid level for gfn 0x{:x}", gfn);
+ }
+
+ let offset = COIOMMU_PT_LEVEL_STRIDE + (level - 2) * COIOMMU_UPPER_LEVEL_STRIDE;
+
+ Ok((gfn >> offset) & COIOMMU_UPPER_LEVEL_MASK)
+}
+
+struct DTTIter {
+ ptr: *const u8,
+ gfn: u64,
+}
+
+impl Default for DTTIter {
+ fn default() -> Self {
+ DTTIter {
+ ptr: std::ptr::null(),
+ gfn: 0,
+ }
+ }
+}
+
+// Get a DMA Tracking Table(DTT) entry associated with the gfn.
+//
+// There are two ways to get the entry:
+// #1. Walking the DMA Tracking Table(DTT) by the GFN to get the
+// corresponding entry. The DTT is shared between frontend and
+// backend. It is page-table-like strctures and the entry is indexed
+// by GFN. The argument dtt_root represents the root page
+// pga and dtt_level represents the maximum page table level.
+//
+// #2. Calculate the entry address via the argument dtt_iter. dtt_iter
+// stores an entry address and the associated gfn. If the target gfn is
+// in the same page table page with the gfn in dtt_iter, then can
+// calculate the target entry address based on the entry address in
+// dtt_iter.
+//
+// As the DTT entry is shared between frontend and backend, the accessing
+// should be atomic. So the returned value is converted to an AtomicU32
+// pointer.
+fn gfn_to_dtt_pte(
+ mem: &GuestMemory,
+ dtt_level: u64,
+ dtt_root: u64,
+ dtt_iter: &mut DTTIter,
+ gfn: u64,
+) -> Result<*const AtomicU32> {
+ let ptr = if dtt_iter.ptr.is_null()
+ || dtt_iter.gfn >> COIOMMU_PT_LEVEL_STRIDE != gfn >> COIOMMU_PT_LEVEL_STRIDE
+ {
+ // Slow path to walk the DTT to get the pte entry
+ let mut level = dtt_level;
+ let mut pt_gpa = dtt_root;
+ let dtt_nonleaf_entry_size = mem::size_of::<u64>() as u64;
+
+ while level != 1 {
+ let index = level_to_offset(gfn, level)? * dtt_nonleaf_entry_size;
+ let parent_pt = mem
+ .read_obj_from_addr::<u64>(GuestAddress(pt_gpa + index))
+ .context(Error::GetDTTEntry)?;
+
+ if (parent_pt & DTT_ENTRY_PRESENT) == 0 {
+ bail!("DTT absent at level {} for gfn 0x{:x}", level, gfn);
+ }
+
+ pt_gpa = (parent_pt >> DTT_ENTRY_PFN_SHIFT) << PAGE_SHIFT_4K;
+ level -= 1;
+ }
+
+ let index = level_to_offset(gfn, level)? * mem::size_of::<u32>() as u64;
+
+ mem.get_host_address(GuestAddress(pt_gpa + index))
+ .context(Error::GetDTTEntry)?
+ } else {
+ // Safe because we checked that dtt_iter.ptr is valid and that the dtt_pte
+ // for gfn lies on the same dtt page as the dtt_pte for dtt_iter.gfn, which
+ // means the calculated ptr will point to the same page as dtt_iter.ptr
+ if gfn > dtt_iter.gfn {
+ unsafe {
+ dtt_iter
+ .ptr
+ .add(mem::size_of::<AtomicU32>() * (gfn - dtt_iter.gfn) as usize)
+ }
+ } else {
+ unsafe {
+ dtt_iter
+ .ptr
+ .sub(mem::size_of::<AtomicU32>() * (dtt_iter.gfn - gfn) as usize)
+ }
+ }
+ };
+
+ dtt_iter.ptr = ptr;
+ dtt_iter.gfn = gfn;
+
+ Ok(ptr as *const AtomicU32)
+}
+
+fn pin_page(
+ pinstate: &mut CoIommuPinState,
+ policy: CoIommuUnpinPolicy,
+ vfio_container: &Arc<Mutex<VfioContainer>>,
+ mem: &GuestMemory,
+ dtt_level: u64,
+ dtt_root: u64,
+ dtt_iter: &mut DTTIter,
+ gfn: u64,
+) -> Result<()> {
+ let leaf_entry = gfn_to_dtt_pte(mem, dtt_level, dtt_root, dtt_iter, gfn)?;
+
+ let gpa = (gfn << PAGE_SHIFT_4K) as u64;
+ let host_addr = mem
+ .get_host_address_range(GuestAddress(gpa), PAGE_SIZE_4K as usize)
+ .context("failed to get host address")? as u64;
+
+ // Safe because ptr is valid and guaranteed by the gfn_to_dtt_pte.
+ // Test PINNED flag
+ if (unsafe { (*leaf_entry).load(Ordering::Relaxed) } & DTTE_PINNED_FLAG) != 0 {
+ info!("CoIommu: gfn 0x{:x} already pinned", gfn);
+ return Ok(());
+ }
+
+ // Safe because the gpa is valid from the gfn_to_dtt_pte and the host_addr
+ // is guaranteed by MemoryMapping interface.
+ if unsafe { vfio_map(vfio_container, gpa, PAGE_SIZE_4K, host_addr) } {
+ // Safe because ptr is valid and guaranteed by the gfn_to_dtt_pte.
+ // set PINNED flag
+ unsafe { (*leaf_entry).fetch_or(DTTE_PINNED_FLAG, Ordering::SeqCst) };
+ if policy == CoIommuUnpinPolicy::Lru {
+ pinstate
+ .new_gen_pinned_pages
+ .push_back(PinnedPageInfo::new(gfn, 0));
+ }
+ }
+
+ Ok(())
+}
+
+#[derive(PartialEq, Debug)]
+enum UnpinResult {
+ UnpinlistEmpty,
+ Unpinned,
+ NotPinned,
+ NotUnpinned,
+ FailedUnpin,
+ UnpinParked,
+}
+
+fn unpin_page(
+ pinstate: &mut CoIommuPinState,
+ vfio_container: &Arc<Mutex<VfioContainer>>,
+ mem: &GuestMemory,
+ dtt_level: u64,
+ dtt_root: u64,
+ dtt_iter: &mut DTTIter,
+ gfn: u64,
+ force: bool,
+) -> UnpinResult {
+ if pinstate.unpin_thread_state == UnpinThreadState::Parked {
+ return UnpinResult::UnpinParked;
+ }
+
+ let leaf_entry = match gfn_to_dtt_pte(mem, dtt_level, dtt_root, dtt_iter, gfn) {
+ Ok(v) => v,
+ Err(_) => {
+ // The case force == true may try to unpin a page which is not
+ // mapped in the dtt. For such page, the pte doesn't exist yet
+ // thus don't need to report any error log.
+ // The case force == false is used by coiommu to periodically
+ // unpin the pages which have been mapped in dtt, thus the pte
+ // for such page does exist. However with the unpin request from
+ // virtio balloon, such pages can be unpinned already and the DTT
+ // pages might be reclaimed by the Guest OS kernel as well, thus
+ // it is also possible to be here. Not to report an error log.
+ return UnpinResult::NotPinned;
+ }
+ };
+
+ if force {
+ // Safe because leaf_entry is valid and guaranteed by the gfn_to_dtt_pte.
+ // This case is for balloon to evict pages so these pages should
+ // already been locked by balloon and no device driver in VM is
+ // able to access these pages, so just clear ACCESSED flag first
+ // to make sure the following unpin can be success.
+ unsafe { (*leaf_entry).fetch_and(!DTTE_ACCESSED_FLAG, Ordering::SeqCst) };
+ }
+
+ // Safe because leaf_entry is valid and guaranteed by the gfn_to_dtt_pte.
+ if let Err(entry) = unsafe {
+ (*leaf_entry).compare_exchange(DTTE_PINNED_FLAG, 0, Ordering::SeqCst, Ordering::SeqCst)
+ } {
+ // The compare_exchange failed as the original leaf entry is
+ // not DTTE_PINNED_FLAG so cannot do the unpin.
+ if entry == 0 {
+ // The GFN is already unpinned. This is very similar to the
+ // gfn_to_dtt_pte error case, with the only difference being
+ // that the dtt_pte happens to be on a present page table.
+ UnpinResult::NotPinned
+ } else {
+ if !force {
+ // Safe because leaf_entry is valid and guaranteed by the gfn_to_dtt_pte.
+ // The ACCESSED_FLAG is set by the guest if guest requires DMA map for
+ // this page. It represents whether or not this page is touched by the
+ // guest. By clearing this flag after an unpin work, we can detect if
+ // this page has been touched by the guest in the next round of unpin
+ // work. If the ACCESSED_FLAG is set at the next round, unpin this page
+ // will be failed and we will be here again to clear this flag. If this
+ // flag is not set at the next round, unpin this page will be probably
+ // success.
+ unsafe { (*leaf_entry).fetch_and(!DTTE_ACCESSED_FLAG, Ordering::SeqCst) };
+ } else {
+ // If we're here, then the guest is trying to release a page via the
+ // balloon that it still has pinned. This most likely that something is
+ // wrong in the guest kernel. Just leave the page pinned and log
+ // an error.
+ // This failure blocks the balloon from removing the page, which ensures
+ // that the guest's view of memory will remain consistent with device
+ // DMA's view of memory. Also note that the host kernel maintains an
+ // elevated refcount for pinned pages, which is a second guarantee the
+ // pages accessible by device DMA won't be freed until after they are
+ // unpinned.
+ error!(
+ "CoIommu: force case cannot pin gfn 0x{:x} entry 0x{:x}",
+ gfn, entry
+ );
+ }
+ // GFN cannot be unpinned either because the unmap count
+ // is non-zero or the it has accessed flag set.
+ UnpinResult::NotUnpinned
+ }
+ } else {
+ // The compare_exchange success as the original leaf entry is
+ // DTTE_PINNED_FLAG and the new leaf entry is 0 now. Unpin the
+ // page.
+ let gpa = (gfn << PAGE_SHIFT_4K) as u64;
+ if vfio_unmap(vfio_container, gpa, PAGE_SIZE_4K) {
+ UnpinResult::Unpinned
+ } else {
+ // Safe because leaf_entry is valid and guaranteed by the gfn_to_dtt_pte.
+ // make sure the pinned flag is set
+ unsafe { (*leaf_entry).fetch_or(DTTE_PINNED_FLAG, Ordering::SeqCst) };
+ // need to put this gfn back to pinned vector
+ UnpinResult::FailedUnpin
+ }
+ }
+}
+
+struct PinWorker {
+ mem: GuestMemory,
+ endpoints: Vec<u16>,
+ notifymap_mmap: Arc<MemoryMapping>,
+ dtt_level: u64,
+ dtt_root: u64,
+ ioevents: Vec<Event>,
+ vfio_container: Arc<Mutex<VfioContainer>>,
+ pinstate: Arc<Mutex<CoIommuPinState>>,
+ params: CoIommuParameters,
+}
+
+impl PinWorker {
+ fn debug_label(&self) -> &'static str {
+ "CoIommuPinWorker"
+ }
+
+ fn run(&mut self, kill_evt: Event) {
+ #[derive(PollToken)]
+ enum Token {
+ Kill,
+ Pin { index: usize },
+ }
+
+ let wait_ctx: WaitContext<Token> =
+ match WaitContext::build_with(&[(&kill_evt, Token::Kill)]) {
+ Ok(pc) => pc,
+ Err(e) => {
+ error!("{}: failed creating WaitContext: {}", self.debug_label(), e);
+ return;
+ }
+ };
+
+ for (index, event) in self.ioevents.iter().enumerate() {
+ match wait_ctx.add(event, Token::Pin { index }) {
+ Ok(_) => {}
+ Err(e) => {
+ error!(
+ "{}: failed to add ioevent for index {}: {}",
+ self.debug_label(),
+ index,
+ e
+ );
+ return;
+ }
+ }
+ }
+
+ 'wait: loop {
+ let events = match wait_ctx.wait() {
+ Ok(v) => v,
+ Err(e) => {
+ error!("{}: failed polling for events: {}", self.debug_label(), e);
+ break;
+ }
+ };
+
+ for event in events.iter().filter(|e| e.is_readable) {
+ match event.token {
+ Token::Kill => break 'wait,
+ Token::Pin { index } => {
+ let offset = index * mem::size_of::<u64>() as usize;
+ if let Some(event) = self.ioevents.get(index) {
+ if let Err(e) = event.read() {
+ error!(
+ "{}: failed reading event {}: {}",
+ self.debug_label(),
+ index,
+ e
+ );
+ self.notifymap_mmap.write_obj::<u64>(0, offset).unwrap();
+ break 'wait;
+ }
+ }
+ if let Ok(data) = self.notifymap_mmap.read_obj::<u64>(offset) {
+ if let Err(e) = self.pin_pages(data) {
+ error!("{}: {}", self.debug_label(), e);
+ }
+ }
+ fence(Ordering::SeqCst);
+ self.notifymap_mmap.write_obj::<u64>(0, offset).unwrap();
+ }
+ }
+ }
+ }
+ }
+
+ fn pin_pages_in_batch(&mut self, gpa: u64) -> Result<()> {
+ let pin_page_info = self
+ .mem
+ .read_obj_from_addr::<PinPageInfo>(GuestAddress(gpa))
+ .context("failed to get pin page info")?;
+
+ let bdf = pin_page_info.bdf;
+ ensure!(
+ self.endpoints.iter().any(|&x| x == bdf),
+ "pin page for unexpected bdf 0x{:x}",
+ bdf
+ );
+
+ let mut nr_pages = pin_page_info.nr_pages;
+ let mut offset = mem::size_of::<PinPageInfo>() as u64;
+ let mut dtt_iter: DTTIter = Default::default();
+ let mut pinstate = self.pinstate.lock();
+ while nr_pages > 0 {
+ let gfn = self
+ .mem
+ .read_obj_from_addr::<u64>(GuestAddress(gpa + offset))
+ .context("failed to get pin page gfn")?;
+
+ pin_page(
+ &mut pinstate,
+ self.params.unpin_policy,
+ &self.vfio_container,
+ &self.mem,
+ self.dtt_level,
+ self.dtt_root,
+ &mut dtt_iter,
+ gfn,
+ )?;
+
+ offset += mem::size_of::<u64>() as u64;
+ nr_pages -= 1;
+ }
+
+ Ok(())
+ }
+
+ fn pin_pages(&mut self, gfn_bdf: u64) -> Result<()> {
+ if gfn_bdf & PIN_PAGES_IN_BATCH != 0 {
+ let gpa = gfn_bdf & !PIN_PAGES_IN_BATCH;
+ self.pin_pages_in_batch(gpa)
+ } else {
+ let bdf = (gfn_bdf & 0xffff) as u16;
+ let gfn = gfn_bdf >> 16;
+ let mut dtt_iter: DTTIter = Default::default();
+ ensure!(
+ self.endpoints.iter().any(|&x| x == bdf),
+ "pin page for unexpected bdf 0x{:x}",
+ bdf
+ );
+
+ let mut pinstate = self.pinstate.lock();
+ pin_page(
+ &mut pinstate,
+ self.params.unpin_policy,
+ &self.vfio_container,
+ &self.mem,
+ self.dtt_level,
+ self.dtt_root,
+ &mut dtt_iter,
+ gfn,
+ )
+ }
+ }
+}
+
+struct UnpinWorker {
+ mem: GuestMemory,
+ dtt_level: u64,
+ dtt_root: u64,
+ vfio_container: Arc<Mutex<VfioContainer>>,
+ unpin_tube: Option<Tube>,
+ pinstate: Arc<Mutex<CoIommuPinState>>,
+ params: CoIommuParameters,
+ unpin_gen_threshold: u64,
+}
+
+impl UnpinWorker {
+ fn debug_label(&self) -> &'static str {
+ "CoIommuUnpinWorker"
+ }
+
+ fn run(&mut self, kill_evt: Event) {
+ #[derive(PollToken)]
+ enum Token {
+ UnpinTimer,
+ UnpinReq,
+ Kill,
+ }
+
+ let wait_ctx: WaitContext<Token> =
+ match WaitContext::build_with(&[(&kill_evt, Token::Kill)]) {
+ Ok(pc) => pc,
+ Err(e) => {
+ error!("{}: failed creating WaitContext: {}", self.debug_label(), e);
+ return;
+ }
+ };
+
+ if let Some(tube) = &self.unpin_tube {
+ if let Err(e) = wait_ctx.add(tube, Token::UnpinReq) {
+ error!("{}: failed creating WaitContext: {}", self.debug_label(), e);
+ return;
+ }
+ }
+
+ let mut unpin_timer = if self.params.unpin_policy != CoIommuUnpinPolicy::Off
+ && !self.params.unpin_interval.is_zero()
+ {
+ let duration = self.params.unpin_interval;
+ let interval = Some(self.params.unpin_interval);
+ let mut timer = match Timer::new() {
+ Ok(t) => t,
+ Err(e) => {
+ error!(
+ "{}: failed to create the unpin timer: {}",
+ self.debug_label(),
+ e
+ );
+ return;
+ }
+ };
+ if let Err(e) = timer.reset(duration, interval) {
+ error!(
+ "{}: failed to start the unpin timer: {}",
+ self.debug_label(),
+ e
+ );
+ return;
+ }
+ if let Err(e) = wait_ctx.add(&timer, Token::UnpinTimer) {
+ error!("{}: failed creating WaitContext: {}", self.debug_label(), e);
+ return;
+ }
+ Some(timer)
+ } else {
+ None
+ };
+
+ let unpin_tube = self.unpin_tube.take();
+ 'wait: loop {
+ let events = match wait_ctx.wait() {
+ Ok(v) => v,
+ Err(e) => {
+ error!("{}: failed polling for events: {}", self.debug_label(), e);
+ break;
+ }
+ };
+
+ for event in events.iter().filter(|e| e.is_readable) {
+ match event.token {
+ Token::UnpinTimer => {
+ self.unpin_pages();
+ if let Some(timer) = &mut unpin_timer {
+ if let Err(e) = timer.wait() {
+ error!(
+ "{}: failed to clear unpin timer: {}",
+ self.debug_label(),
+ e
+ );
+ break 'wait;
+ }
+ }
+ }
+ Token::UnpinReq => {
+ if let Some(tube) = &unpin_tube {
+ match tube.recv::<UnpinRequest>() {
+ Ok(req) => {
+ let mut unpin_done = true;
+ for range in req.ranges {
+ // Locking with respect to pin_pages isn't necessary
+ // for this case because the unpinned pages in the range
+ // should all be in the balloon and so nothing will attempt
+ // to pin them.
+ if !self.unpin_pages_in_range(range.0, range.1) {
+ unpin_done = false;
+ break;
+ }
+ }
+ let resp = if unpin_done {
+ UnpinResponse::Success
+ } else {
+ UnpinResponse::Failed
+ };
+ if let Err(e) = tube.send(&resp) {
+ error!(
+ "{}: failed to send unpin response {}",
+ self.debug_label(),
+ e
+ );
+ }
+ }
+ Err(e) => {
+ if let TubeError::Disconnected = e {
+ if let Err(e) = wait_ctx.delete(tube) {
+ error!(
+ "{}: failed to remove unpin_tube: {}",
+ self.debug_label(),
+ e
+ );
+ }
+ } else {
+ error!(
+ "{}: failed to recv Unpin Request: {}",
+ self.debug_label(),
+ e
+ );
+ }
+ }
+ }
+ }
+ }
+ Token::Kill => break 'wait,
+ }
+ }
+ }
+ self.unpin_tube = unpin_tube;
+ }
+
+ fn unpin_pages(&mut self) {
+ if self.params.unpin_policy == CoIommuUnpinPolicy::Lru {
+ self.lru_unpin_pages();
+ }
+ }
+
+ fn lru_unpin_page(
+ &mut self,
+ dtt_iter: &mut DTTIter,
+ new_gen: bool,
+ ) -> (UnpinResult, Option<PinnedPageInfo>) {
+ let mut pinstate = self.pinstate.lock();
+ let pageinfo = if new_gen {
+ pinstate.new_gen_pinned_pages.pop_front()
+ } else {
+ pinstate
+ .old_gen_pinned_pages
+ .pop_front()
+ .map(|gfn| PinnedPageInfo::new(gfn, 0))
+ };
+
+ pageinfo.map_or((UnpinResult::UnpinlistEmpty, None), |pageinfo| {
+ (
+ unpin_page(
+ &mut pinstate,
+ &self.vfio_container,
+ &self.mem,
+ self.dtt_level,
+ self.dtt_root,
+ dtt_iter,
+ pageinfo.gfn,
+ false,
+ ),
+ Some(pageinfo),
+ )
+ })
+ }
+
+ fn lru_unpin_pages_in_loop(&mut self, unpin_limit: Option<u64>, new_gen: bool) -> u64 {
+ let mut not_unpinned_new_gen_pages = VecDeque::new();
+ let mut not_unpinned_old_gen_pages = VecDeque::new();
+ let mut unpinned_count = 0;
+ let has_limit = unpin_limit.is_some();
+ let limit_count = unpin_limit.unwrap_or(0);
+ let mut dtt_iter: DTTIter = Default::default();
+
+ // If has_limit is true but limit_count is 0, will not do the unpin
+ while !has_limit || unpinned_count != limit_count {
+ let (result, pinned_page) = self.lru_unpin_page(&mut dtt_iter, new_gen);
+ match result {
+ UnpinResult::UnpinlistEmpty => break,
+ UnpinResult::Unpinned => unpinned_count += 1,
+ UnpinResult::NotPinned => {}
+ UnpinResult::NotUnpinned => {
+ if let Some(mut page) = pinned_page {
+ if self.params.unpin_gen_threshold != 0 {
+ page.unpin_busy_cnt += 1;
+ // Unpin from new_gen queue but not
+ // successfully unpinned. Need to check
+ // the unpin_gen threshold. If reach, put
+ // it to old_gen queue.
+ // And if it is not from new_gen, directly
+ // put into old_gen queue.
+ if !new_gen || page.unpin_busy_cnt >= self.params.unpin_gen_threshold {
+ not_unpinned_old_gen_pages.push_back(page.gfn);
+ } else {
+ not_unpinned_new_gen_pages.push_back(page);
+ }
+ }
+ }
+ }
+ UnpinResult::FailedUnpin | UnpinResult::UnpinParked => {
+ // Although UnpinParked means we didn't actually try to unpin
+ // gfn, it's not worth specifically handing since parking is
+ // expected to be relatively rare.
+ if let Some(page) = pinned_page {
+ if new_gen {
+ not_unpinned_new_gen_pages.push_back(page);
+ } else {
+ not_unpinned_old_gen_pages.push_back(page.gfn);
+ }
+ }
+ if result == UnpinResult::UnpinParked {
+ thread::park();
+ }
+ }
+ }
+ }
+
+ if !not_unpinned_new_gen_pages.is_empty() {
+ let mut pinstate = self.pinstate.lock();
+ pinstate
+ .new_gen_pinned_pages
+ .append(&mut not_unpinned_new_gen_pages);
+ }
+
+ if !not_unpinned_old_gen_pages.is_empty() {
+ let mut pinstate = self.pinstate.lock();
+ pinstate
+ .old_gen_pinned_pages
+ .append(&mut not_unpinned_old_gen_pages);
+ }
+
+ unpinned_count
+ }
+
+ fn lru_unpin_pages(&mut self) {
+ let mut unpin_count = 0;
+ if self.params.unpin_gen_threshold != 0 {
+ self.unpin_gen_threshold += 1;
+ if self.unpin_gen_threshold == self.params.unpin_gen_threshold {
+ self.unpin_gen_threshold = 0;
+ // Try to unpin inactive queue first if reaches the thres hold
+ unpin_count = self.lru_unpin_pages_in_loop(self.params.unpin_limit, false);
+ }
+ }
+ // Unpin the new_gen queue with the updated unpin_limit after unpin old_gen queue
+ self.lru_unpin_pages_in_loop(
+ self.params
+ .unpin_limit
+ .map(|limit| limit.saturating_sub(unpin_count)),
+ true,
+ );
+ }
+
+ fn unpin_pages_in_range(&self, gfn: u64, count: u64) -> bool {
+ let mut dtt_iter: DTTIter = Default::default();
+ let mut index = 0;
+ while index != count {
+ let mut pinstate = self.pinstate.lock();
+ let result = unpin_page(
+ &mut pinstate,
+ &self.vfio_container,
+ &self.mem,
+ self.dtt_level,
+ self.dtt_root,
+ &mut dtt_iter,
+ gfn + index,
+ true,
+ );
+ drop(pinstate);
+
+ match result {
+ UnpinResult::Unpinned | UnpinResult::NotPinned => {}
+ UnpinResult::UnpinParked => {
+ thread::park();
+ continue;
+ }
+ _ => {
+ error!("coiommu: force unpin failed by {:?}", result);
+ return false;
+ }
+ }
+ index += 1;
+ }
+ true
+ }
+}
+
+pub struct CoIommuDev {
+ config_regs: PciConfiguration,
+ pci_address: Option<PciAddress>,
+ mem: GuestMemory,
+ coiommu_reg: CoIommuReg,
+ endpoints: Vec<u16>,
+ notifymap_mem: SafeDescriptor,
+ notifymap_mmap: Arc<MemoryMapping>,
+ notifymap_addr: Option<u64>,
+ topologymap_mem: SafeDescriptor,
+ topologymap_addr: Option<u64>,
+ mmapped: bool,
+ device_tube: Tube,
+ pin_thread: Option<thread::JoinHandle<PinWorker>>,
+ pin_kill_evt: Option<Event>,
+ unpin_thread: Option<thread::JoinHandle<UnpinWorker>>,
+ unpin_kill_evt: Option<Event>,
+ unpin_tube: Option<Tube>,
+ ioevents: Vec<Event>,
+ vfio_container: Arc<Mutex<VfioContainer>>,
+ pinstate: Arc<Mutex<CoIommuPinState>>,
+ params: CoIommuParameters,
+}
+
+impl CoIommuDev {
+ pub fn new(
+ mem: GuestMemory,
+ vfio_container: Arc<Mutex<VfioContainer>>,
+ device_tube: Tube,
+ unpin_tube: Tube,
+ endpoints: Vec<u16>,
+ vcpu_count: u64,
+ params: CoIommuParameters,
+ ) -> Result<Self> {
+ let config_regs = PciConfiguration::new(
+ PCI_VENDOR_ID_COIOMMU,
+ PCI_DEVICE_ID_COIOMMU,
+ PciClassCode::Other,
+ &PciOtherSubclass::Other,
+ None, // No Programming interface.
+ PciHeaderType::Device,
+ PCI_VENDOR_ID_COIOMMU,
+ PCI_DEVICE_ID_COIOMMU,
+ COIOMMU_REVISION_ID,
+ );
+
+ // notifymap_mem is used as Bar2 for Guest to check if request is completed by coIOMMU.
+ let notifymap_mem = SharedMemory::named("coiommu_notifymap", COIOMMU_NOTIFYMAP_SIZE as u64)
+ .context(Error::CreateSharedMemory)?;
+ let notifymap_mmap = Arc::new(
+ MemoryMappingBuilder::new(COIOMMU_NOTIFYMAP_SIZE)
+ .from_shared_memory(&notifymap_mem)
+ .offset(0)
+ .build()?,
+ );
+
+ // topologymap_mem is used as Bar4 for Guest to check which device is on top of coIOMMU.
+ let topologymap_mem =
+ SharedMemory::named("coiommu_topologymap", COIOMMU_TOPOLOGYMAP_SIZE as u64)
+ .context(Error::CreateSharedMemory)?;
+ let topologymap_mmap = Arc::new(
+ MemoryMappingBuilder::new(COIOMMU_TOPOLOGYMAP_SIZE)
+ .from_shared_memory(&topologymap_mem)
+ .offset(0)
+ .build()?,
+ );
+
+ ensure!(
+ (endpoints.len() + 1) * mem::size_of::<u16>() <= COIOMMU_TOPOLOGYMAP_SIZE,
+ "Coiommu: too many endpoints"
+ );
+ topologymap_mmap.write_obj::<u16>(endpoints.len() as u16, 0)?;
+ for (index, endpoint) in endpoints.iter().enumerate() {
+ topologymap_mmap.write_obj::<u16>(*endpoint, (index + 1) * mem::size_of::<u16>())?;
+ }
+
+ let mut ioevents = Vec::new();
+ for _ in 0..vcpu_count {
+ ioevents.push(Event::new().context("CoIommu failed to create event fd")?);
+ }
+
+ Ok(Self {
+ config_regs,
+ pci_address: None,
+ mem,
+ coiommu_reg: Default::default(),
+ endpoints,
+ notifymap_mem: notifymap_mem.into(),
+ notifymap_mmap,
+ notifymap_addr: None,
+ topologymap_mem: topologymap_mem.into(),
+ topologymap_addr: None,
+ mmapped: false,
+ device_tube,
+ pin_thread: None,
+ pin_kill_evt: None,
+ unpin_thread: None,
+ unpin_kill_evt: None,
+ unpin_tube: Some(unpin_tube),
+ ioevents,
+ vfio_container,
+ pinstate: Arc::new(Mutex::new(CoIommuPinState {
+ new_gen_pinned_pages: VecDeque::new(),
+ old_gen_pinned_pages: VecDeque::new(),
+ unpin_thread_state: UnpinThreadState::Unparked,
+ unpin_park_count: 0,
+ })),
+ params,
+ })
+ }
+
+ fn send_msg(&self, msg: &VmMemoryRequest) -> Result<()> {
+ self.device_tube.send(msg).context(Error::TubeError)?;
+ let res = self.device_tube.recv().context(Error::TubeError)?;
+ match res {
+ VmMemoryResponse::RegisterMemory { .. } => Ok(()),
+ VmMemoryResponse::Err(e) => Err(anyhow!("Receive msg err {}", e)),
+ _ => Err(anyhow!("Msg cannot be handled")),
+ }
+ }
+
+ fn register_mmap(
+ &self,
+ descriptor: SafeDescriptor,
+ size: usize,
+ offset: u64,
+ gpa: u64,
+ read_only: bool,
+ ) -> Result<()> {
+ let request = VmMemoryRequest::RegisterMemory {
+ source: VmMemorySource::Descriptor {
+ descriptor,
+ offset,
+ size: size as u64,
+ },
+ dest: VmMemoryDestination::GuestPhysicalAddress(gpa),
+ read_only,
+ };
+ self.send_msg(&request)
+ }
+
+ fn mmap(&mut self) {
+ if self.mmapped {
+ return;
+ }
+
+ if let Some(gpa) = self.notifymap_addr {
+ match self.register_mmap(
+ self.notifymap_mem.try_clone().unwrap(),
+ COIOMMU_NOTIFYMAP_SIZE,
+ 0,
+ gpa,
+ false,
+ ) {
+ Ok(_) => {}
+ Err(e) => {
+ panic!("{}: map notifymap failed: {}", self.debug_label(), e);
+ }
+ }
+ }
+
+ if let Some(gpa) = self.topologymap_addr {
+ match self.register_mmap(
+ self.topologymap_mem.try_clone().unwrap(),
+ COIOMMU_TOPOLOGYMAP_SIZE,
+ 0,
+ gpa,
+ true,
+ ) {
+ Ok(_) => {}
+ Err(e) => {
+ panic!("{}: map topologymap failed: {}", self.debug_label(), e);
+ }
+ }
+ }
+
+ self.mmapped = true;
+ }
+
+ fn start_workers(&mut self) {
+ if self.pin_thread.is_none() {
+ self.start_pin_thread();
+ }
+
+ if self.unpin_thread.is_none() {
+ self.start_unpin_thread();
+ }
+ }
+
+ fn start_pin_thread(&mut self) {
+ let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
+ Ok(v) => v,
+ Err(e) => {
+ error!(
+ "{}: failed creating kill Event pair: {}",
+ self.debug_label(),
+ e
+ );
+ return;
+ }
+ };
+
+ let mem = self.mem.clone();
+ let endpoints = self.endpoints.to_vec();
+ let notifymap_mmap = self.notifymap_mmap.clone();
+ let dtt_root = self.coiommu_reg.dtt_root;
+ let dtt_level = self.coiommu_reg.dtt_level;
+ let ioevents = self
+ .ioevents
+ .iter()
+ .map(|e| e.try_clone().unwrap())
+ .collect();
+ let vfio_container = self.vfio_container.clone();
+ let pinstate = self.pinstate.clone();
+ let params = self.params;
+
+ let worker_result = thread::Builder::new()
+ .name("coiommu_pin".to_string())
+ .spawn(move || {
+ let mut worker = PinWorker {
+ mem,
+ endpoints,
+ notifymap_mmap,
+ dtt_root,
+ dtt_level,
+ ioevents,
+ vfio_container,
+ pinstate,
+ params,
+ };
+ worker.run(kill_evt);
+ worker
+ });
+
+ match worker_result {
+ Err(e) => error!(
+ "{}: failed to spawn coiommu pin worker: {}",
+ self.debug_label(),
+ e
+ ),
+ Ok(join_handle) => {
+ self.pin_thread = Some(join_handle);
+ self.pin_kill_evt = Some(self_kill_evt);
+ }
+ }
+ }
+
+ fn start_unpin_thread(&mut self) {
+ let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
+ Ok(v) => v,
+ Err(e) => {
+ error!(
+ "{}: failed creating kill Event pair: {}",
+ self.debug_label(),
+ e
+ );
+ return;
+ }
+ };
+
+ let mem = self.mem.clone();
+ let dtt_root = self.coiommu_reg.dtt_root;
+ let dtt_level = self.coiommu_reg.dtt_level;
+ let vfio_container = self.vfio_container.clone();
+ let unpin_tube = self.unpin_tube.take();
+ let pinstate = self.pinstate.clone();
+ let params = self.params;
+ let worker_result = thread::Builder::new()
+ .name("coiommu_unpin".to_string())
+ .spawn(move || {
+ let mut worker = UnpinWorker {
+ mem,
+ dtt_level,
+ dtt_root,
+ vfio_container,
+ unpin_tube,
+ pinstate,
+ params,
+ unpin_gen_threshold: 0,
+ };
+ worker.run(kill_evt);
+ worker
+ });
+
+ match worker_result {
+ Err(e) => {
+ error!(
+ "{}: failed to spawn coiommu unpin worker: {}",
+ self.debug_label(),
+ e
+ );
+ }
+ Ok(join_handle) => {
+ self.unpin_thread = Some(join_handle);
+ self.unpin_kill_evt = Some(self_kill_evt);
+ }
+ }
+ }
+
+ fn allocate_bar_address(
+ &mut self,
+ resources: &mut SystemAllocator,
+ address: PciAddress,
+ size: u64,
+ bar_num: u8,
+ name: &str,
+ ) -> PciResult<u64> {
+ let addr = resources
+ .mmio_allocator(MmioType::High)
+ .allocate_with_align(
+ size,
+ Alloc::PciBar {
+ bus: address.bus,
+ dev: address.dev,
+ func: address.func,
+ bar: bar_num,
+ },
+ name.to_string(),
+ size,
+ )
+ .map_err(|e| PciDeviceError::IoAllocationFailed(size, e))?;
+
+ let bar = PciBarConfiguration::new(
+ bar_num as usize,
+ size,
+ PciBarRegionType::Memory64BitRegion,
+ PciBarPrefetchable::Prefetchable,
+ )
+ .set_address(addr);
+
+ self.config_regs
+ .add_pci_bar(bar)
+ .map_err(|e| PciDeviceError::IoRegistrationFailed(addr, e))?;
+
+ Ok(addr)
+ }
+
+ fn read_mmio(&mut self, addr: u64, data: &mut [u8]) {
+ let bar = self.config_regs.get_bar_addr(COIOMMU_MMIO_BAR as usize);
+ let offset = addr - bar;
+ if offset >= mem::size_of::<CoIommuReg>() as u64 {
+ error!(
+ "{}: read_mmio: invalid addr 0x{:x} bar 0x{:x} offset 0x{:x}",
+ self.debug_label(),
+ addr,
+ bar,
+ offset
+ );
+ return;
+ }
+
+ // Sanity check, must be 64bit aligned accessing
+ if offset % 8 != 0 || data.len() != 8 {
+ error!(
+ "{}: read_mmio: unaligned accessing: offset 0x{:x} actual len {} expect len 8",
+ self.debug_label(),
+ offset,
+ data.len()
+ );
+ return;
+ }
+
+ let v = match offset / 8 {
+ 0 => self.coiommu_reg.dtt_root,
+ 1 => self.coiommu_reg.cmd,
+ 2 => self.coiommu_reg.dtt_level,
+ _ => return,
+ };
+
+ data.copy_from_slice(&v.to_ne_bytes());
+ }
+
+ fn write_mmio(&mut self, addr: u64, data: &[u8]) {
+ let bar = self.config_regs.get_bar_addr(COIOMMU_MMIO_BAR as usize);
+ let mmio_len = mem::size_of::<CoIommuReg>() as u64;
+ let offset = addr - bar;
+ if offset >= mmio_len {
+ if data.len() != 1 {
+ error!(
+ "{}: write_mmio: unaligned accessing: offset 0x{:x} actual len {} expect len 1",
+ self.debug_label(),
+ offset,
+ data.len()
+ );
+ return;
+ }
+
+ // Usually will not be here as this is for the per-vcpu notify
+ // register which is monitored by the ioevents. For the notify
+ // register which is not covered by the ioevents, they are not
+ // be used by the frontend driver. In case the frontend driver
+ // went here, do a simple handle to make sure the frontend driver
+ // will not be blocked, and through an error log.
+ let index = (offset - mmio_len) as usize * mem::size_of::<u64>();
+ self.notifymap_mmap.write_obj::<u64>(0, index).unwrap();
+ error!(
+ "{}: No page will be pinned as driver is accessing unused trigger register: offset 0x{:x}",
+ self.debug_label(),
+ offset
+ );
+ return;
+ }
+
+ // Sanity check, must be 64bit aligned accessing for CoIommuReg
+ if offset % 8 != 0 || data.len() != 8 {
+ error!(
+ "{}: write_mmio: unaligned accessing: offset 0x{:x} actual len {} expect len 8",
+ self.debug_label(),
+ offset,
+ data.len()
+ );
+ return;
+ }
+
+ let index = offset / 8;
+ let v = u64::from_ne_bytes(data.try_into().unwrap());
+ match index {
+ 0 => {
+ if self.coiommu_reg.dtt_root == 0 {
+ self.coiommu_reg.dtt_root = v;
+ }
+ }
+ 1 => match v {
+ // Deactivate can happen if the frontend driver in the guest
+ // fails during probing or if the CoIommu device is removed
+ // by the guest. Neither of these cases is expected, and if
+ // either happens the guest will be non-functional due to
+ // pass-through devices which rely on CoIommu not working.
+ // So just fail hard and panic.
+ COIOMMU_CMD_DEACTIVATE => {
+ panic!("{}: Deactivate is not supported", self.debug_label())
+ }
+ COIOMMU_CMD_ACTIVATE => {
+ if self.coiommu_reg.dtt_root != 0 && self.coiommu_reg.dtt_level != 0 {
+ self.start_workers();
+ }
+ }
+ COIOMMU_CMD_PARK_UNPIN => {
+ let mut pinstate = self.pinstate.lock();
+ pinstate.unpin_thread_state = UnpinThreadState::Parked;
+ if let Some(v) = pinstate.unpin_park_count.checked_add(1) {
+ pinstate.unpin_park_count = v;
+ } else {
+ panic!("{}: Park request overflowing", self.debug_label());
+ }
+ }
+ COIOMMU_CMD_UNPARK_UNPIN => {
+ let mut pinstate = self.pinstate.lock();
+ if pinstate.unpin_thread_state == UnpinThreadState::Parked {
+ if let Some(v) = pinstate.unpin_park_count.checked_sub(1) {
+ pinstate.unpin_park_count = v;
+ if pinstate.unpin_park_count == 0 {
+ if let Some(worker_thread) = &self.unpin_thread {
+ worker_thread.thread().unpark();
+ }
+ pinstate.unpin_thread_state = UnpinThreadState::Unparked;
+ }
+ } else {
+ error!("{}: Park count is already reached to 0", self.debug_label());
+ }
+ }
+ }
+ _ => {}
+ },
+ 2 => {
+ if self.coiommu_reg.dtt_level == 0 {
+ self.coiommu_reg.dtt_level = v;
+ }
+ }
+ _ => {}
+ }
+ }
+}
+
+impl PciDevice for CoIommuDev {
+ fn debug_label(&self) -> String {
+ "CoIommu".to_owned()
+ }
+
+ fn allocate_address(&mut self, resources: &mut SystemAllocator) -> PciResult<PciAddress> {
+ if self.pci_address.is_none() {
+ self.pci_address = match resources.allocate_pci(0, self.debug_label()) {
+ Some(Alloc::PciBar {
+ bus,
+ dev,
+ func,
+ bar: _,
+ }) => Some(PciAddress { bus, dev, func }),
+ _ => None,
+ }
+ }
+ self.pci_address.ok_or(PciDeviceError::PciAllocationFailed)
+ }
+
+ fn allocate_io_bars(&mut self, resources: &mut SystemAllocator) -> PciResult<Vec<BarRange>> {
+ let address = self
+ .pci_address
+ .expect("allocate_address must be called prior to allocate_io_bars");
+
+ // Allocate one bar for the structures pointed to by the capability structures.
+ let mut ranges: Vec<BarRange> = Vec::new();
+
+ let mmio_addr = self.allocate_bar_address(
+ resources,
+ address,
+ COIOMMU_MMIO_BAR_SIZE as u64,
+ COIOMMU_MMIO_BAR,
+ "coiommu-mmiobar",
+ )?;
+
+ ranges.push(BarRange {
+ addr: mmio_addr,
+ size: COIOMMU_MMIO_BAR_SIZE,
+ prefetchable: false,
+ });
+
+ Ok(ranges)
+ }
+
+ fn allocate_device_bars(
+ &mut self,
+ resources: &mut SystemAllocator,
+ ) -> PciResult<Vec<BarRange>> {
+ let address = self
+ .pci_address
+ .expect("allocate_address must be called prior to allocate_device_bars");
+
+ let mut ranges: Vec<BarRange> = Vec::new();
+
+ let topologymap_addr = self.allocate_bar_address(
+ resources,
+ address,
+ COIOMMU_TOPOLOGYMAP_SIZE as u64,
+ COIOMMU_TOPOLOGYMAP_BAR,
+ "coiommu-topology",
+ )?;
+ self.topologymap_addr = Some(topologymap_addr);
+ ranges.push(BarRange {
+ addr: topologymap_addr,
+ size: COIOMMU_TOPOLOGYMAP_SIZE as u64,
+ prefetchable: false,
+ });
+
+ let notifymap_addr = self.allocate_bar_address(
+ resources,
+ address,
+ COIOMMU_NOTIFYMAP_SIZE as u64,
+ COIOMMU_NOTIFYMAP_BAR,
+ "coiommu-notifymap",
+ )?;
+ self.notifymap_addr = Some(notifymap_addr);
+ ranges.push(BarRange {
+ addr: notifymap_addr,
+ size: COIOMMU_NOTIFYMAP_SIZE as u64,
+ prefetchable: false,
+ });
+
+ Ok(ranges)
+ }
+
+ fn read_config_register(&self, reg_idx: usize) -> u32 {
+ self.config_regs.read_reg(reg_idx)
+ }
+
+ fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
+ if reg_idx == COMMAND_REG
+ && data.len() == 2
+ && data[0] & COMMAND_REG_MEMORY_SPACE_MASK as u8 != 0
+ && !self.mmapped
+ {
+ self.mmap();
+ }
+
+ (&mut self.config_regs).write_reg(reg_idx, offset, data);
+ }
+
+ fn keep_rds(&self) -> Vec<RawDescriptor> {
+ let mut rds = vec![
+ self.vfio_container.lock().as_raw_descriptor(),
+ self.device_tube.as_raw_descriptor(),
+ self.notifymap_mem.as_raw_descriptor(),
+ self.topologymap_mem.as_raw_descriptor(),
+ ];
+ if let Some(unpin_tube) = &self.unpin_tube {
+ rds.push(unpin_tube.as_raw_descriptor());
+ }
+ rds
+ }
+
+ fn read_bar(&mut self, addr: u64, data: &mut [u8]) {
+ let mmio_bar = self.config_regs.get_bar_addr(COIOMMU_MMIO_BAR as usize);
+ let notifymap = self
+ .config_regs
+ .get_bar_addr(COIOMMU_NOTIFYMAP_BAR as usize);
+ match addr {
+ o if mmio_bar <= o && o < mmio_bar + COIOMMU_MMIO_BAR_SIZE as u64 => {
+ self.read_mmio(addr, data);
+ }
+ o if notifymap <= o && o < notifymap + COIOMMU_NOTIFYMAP_SIZE as u64 => {
+ // With coiommu device activated, the accessing the notifymap bar
+ // won't cause vmexit. If goes here, means the coiommu device is
+ // deactivated, and will not do the pin/unpin work. Thus no need
+ // to handle this notifymap read.
+ }
+ _ => {}
+ }
+ }
+
+ fn write_bar(&mut self, addr: u64, data: &[u8]) {
+ let mmio_bar = self.config_regs.get_bar_addr(COIOMMU_MMIO_BAR as usize);
+ let notifymap = self
+ .config_regs
+ .get_bar_addr(COIOMMU_NOTIFYMAP_BAR as usize);
+ match addr {
+ o if mmio_bar <= o && o < mmio_bar + COIOMMU_MMIO_BAR_SIZE as u64 => {
+ self.write_mmio(addr, data);
+ }
+ o if notifymap <= o && o < notifymap + COIOMMU_NOTIFYMAP_SIZE as u64 => {
+ // With coiommu device activated, the accessing the notifymap bar
+ // won't cause vmexit. If goes here, means the coiommu device is
+ // deactivated, and will not do the pin/unpin work. Thus no need
+ // to handle this notifymap write.
+ }
+ _ => {}
+ }
+ }
+
+ fn ioevents(&self) -> Vec<(&Event, u64, Datamatch)> {
+ let bar0 = self.config_regs.get_bar_addr(COIOMMU_MMIO_BAR as usize);
+ let notify_base = bar0 + mem::size_of::<CoIommuReg>() as u64;
+ self.ioevents
+ .iter()
+ .enumerate()
+ .map(|(i, event)| (event, notify_base + i as u64, Datamatch::AnyLength))
+ .collect()
+ }
+
+ fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
+ self.config_regs.get_bar_configuration(bar_num)
+ }
+}
+
+impl Drop for CoIommuDev {
+ fn drop(&mut self) {
+ if let Some(kill_evt) = self.pin_kill_evt.take() {
+ // Ignore the result because there is nothing we can do about it.
+ if kill_evt.write(1).is_ok() {
+ if let Some(worker_thread) = self.pin_thread.take() {
+ let _ = worker_thread.join();
+ }
+ } else {
+ error!("CoIOMMU: failed to write to kill_evt to stop pin_thread");
+ }
+ }
+
+ if let Some(kill_evt) = self.unpin_kill_evt.take() {
+ // Ignore the result because there is nothing we can do about it.
+ if kill_evt.write(1).is_ok() {
+ if let Some(worker_thread) = self.unpin_thread.take() {
+ let _ = worker_thread.join();
+ }
+ } else {
+ error!("CoIOMMU: failed to write to kill_evt to stop unpin_thread");
+ }
+ }
+ }
+}
diff --git a/devices/src/pci/mod.rs b/devices/src/pci/mod.rs
index 65ed0803e..fea98ecff 100644
--- a/devices/src/pci/mod.rs
+++ b/devices/src/pci/mod.rs
@@ -12,23 +12,33 @@ mod ac97_bus_master;
mod ac97_mixer;
#[cfg(feature = "audio")]
mod ac97_regs;
+mod coiommu;
mod msix;
+mod pci_address;
mod pci_configuration;
mod pci_device;
mod pci_root;
+mod pcie;
+mod pvpanic;
+mod stub;
mod vfio_pci;
#[cfg(feature = "audio")]
pub use self::ac97::{Ac97Backend, Ac97Dev, Ac97Parameters};
+pub use self::coiommu::{CoIommuDev, CoIommuParameters, CoIommuUnpinPolicy};
pub use self::msix::{MsixCap, MsixConfig, MsixStatus};
+pub use self::pci_address::Error as PciAddressError;
+pub use self::pci_address::PciAddress;
pub use self::pci_configuration::{
- PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability, PciCapabilityID,
- PciClassCode, PciConfiguration, PciDisplaySubclass, PciHeaderType, PciProgrammingInterface,
- PciSerialBusSubClass, PciSubclass,
+ PciBarConfiguration, PciBarIndex, PciBarPrefetchable, PciBarRegionType, PciCapability,
+ PciCapabilityID, PciClassCode, PciConfiguration, PciDisplaySubclass, PciHeaderType,
+ PciProgrammingInterface, PciSerialBusSubClass, PciSubclass, CAPABILITY_LIST_HEAD_OFFSET,
};
-pub use self::pci_device::Error as PciDeviceError;
-pub use self::pci_device::PciDevice;
-pub use self::pci_root::{PciAddress, PciConfigIo, PciConfigMmio, PciRoot};
+pub use self::pci_device::{BarRange, Error as PciDeviceError, PciDevice};
+pub use self::pci_root::{PciConfigIo, PciConfigMmio, PciRoot, PciVirtualConfigMmio};
+pub use self::pcie::{PciBridge, PcieHostRootPort, PcieRootPort};
+pub use self::pvpanic::{PvPanicCode, PvPanicPciDevice};
+pub use self::stub::{StubPciDevice, StubPciParameters};
pub use self::vfio_pci::VfioPciDevice;
/// PCI has four interrupt pins A->D.
@@ -45,3 +55,29 @@ impl PciInterruptPin {
self as u32
}
}
+
+pub const PCI_VENDOR_ID_INTEL: u16 = 0x8086;
+pub const PCI_VENDOR_ID_REDHAT: u16 = 0x1b36;
+
+/// A wrapper structure for pci device and vendor id.
+#[derive(Copy, Clone)]
+pub struct PciId {
+ vendor_id: u16,
+ device_id: u16,
+}
+
+impl PciId {
+ pub fn new(vendor_id: u16, device_id: u16) -> Self {
+ Self {
+ vendor_id,
+ device_id,
+ }
+ }
+}
+
+impl From<PciId> for u32 {
+ fn from(pci_id: PciId) -> Self {
+ // vendor ID is the lower 16 bits and device id is the upper 16 bits
+ pci_id.vendor_id as u32 | (pci_id.device_id as u32) << 16
+ }
+}
diff --git a/devices/src/pci/msix.rs b/devices/src/pci/msix.rs
index 2852aefd6..57a3b4cd3 100644
--- a/devices/src/pci/msix.rs
+++ b/devices/src/pci/msix.rs
@@ -2,14 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use crate::pci::{PciCapability, PciCapabilityID};
use base::{error, AsRawDescriptor, Error as SysError, Event, RawDescriptor, Tube, TubeError};
-
+use bit_field::*;
+use data_model::DataInit;
+use remain::sorted;
use std::convert::TryInto;
-use std::fmt::{self, Display};
+use thiserror::Error;
use vm_control::{VmIrqRequest, VmIrqResponse};
-use data_model::DataInit;
+use crate::pci::{PciCapability, PciCapabilityID};
const MAX_MSIX_VECTORS_PER_DEVICE: u16 = 2048;
pub const MSIX_TABLE_ENTRIES_MODULO: u64 = 16;
@@ -17,8 +18,9 @@ pub const MSIX_PBA_ENTRIES_MODULO: u64 = 8;
pub const BITS_PER_PBA_ENTRY: usize = 64;
const FUNCTION_MASK_BIT: u16 = 0x4000;
const MSIX_ENABLE_BIT: u16 = 0x8000;
+const MSIX_TABLE_ENTRY_MASK_BIT: u32 = 0x1;
-#[derive(Clone)]
+#[derive(Clone, Default)]
struct MsixTableEntry {
msg_addr_lo: u32,
msg_addr_hi: u32,
@@ -28,18 +30,7 @@ struct MsixTableEntry {
impl MsixTableEntry {
fn masked(&self) -> bool {
- self.vector_ctl & 0x1 == 0x1
- }
-}
-
-impl Default for MsixTableEntry {
- fn default() -> Self {
- MsixTableEntry {
- msg_addr_lo: 0,
- msg_addr_hi: 0,
- msg_data: 0,
- vector_ctl: 0,
- }
+ self.vector_ctl & MSIX_TABLE_ENTRY_MASK_BIT == MSIX_TABLE_ENTRY_MASK_BIT
}
}
@@ -52,39 +43,32 @@ struct IrqfdGsi {
pub struct MsixConfig {
table_entries: Vec<MsixTableEntry>,
pba_entries: Vec<u64>,
- irq_vec: Vec<IrqfdGsi>,
+ irq_vec: Vec<Option<IrqfdGsi>>,
masked: bool,
enabled: bool,
msi_device_socket: Tube,
msix_num: u16,
+ pci_id: u32,
+ device_name: String,
}
+#[sorted]
+#[derive(Error, Debug)]
enum MsixError {
+ #[error("AddMsiRoute failed: {0}")]
AddMsiRoute(SysError),
+ #[error("failed to receive AddMsiRoute response: {0}")]
AddMsiRouteRecv(TubeError),
+ #[error("failed to send AddMsiRoute request: {0}")]
AddMsiRouteSend(TubeError),
+ #[error("AllocateOneMsi failed: {0}")]
AllocateOneMsi(SysError),
+ #[error("failed to receive AllocateOneMsi response: {0}")]
AllocateOneMsiRecv(TubeError),
+ #[error("failed to send AllocateOneMsi request: {0}")]
AllocateOneMsiSend(TubeError),
}
-impl Display for MsixError {
- #[remain::check]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::MsixError::*;
-
- #[sorted]
- match self {
- AddMsiRoute(e) => write!(f, "AddMsiRoute failed: {}", e),
- AddMsiRouteRecv(e) => write!(f, "failed to receive AddMsiRoute response: {}", e),
- AddMsiRouteSend(e) => write!(f, "failed to send AddMsiRoute request: {}", e),
- AllocateOneMsi(e) => write!(f, "AllocateOneMsi failed: {}", e),
- AllocateOneMsiRecv(e) => write!(f, "failed to receive AllocateOneMsi response: {}", e),
- AllocateOneMsiSend(e) => write!(f, "failed to send AllocateOneMsi request: {}", e),
- }
- }
-}
-
type MsixResult<T> = std::result::Result<T, MsixError>;
pub enum MsixStatus {
@@ -94,23 +78,32 @@ pub enum MsixStatus {
}
impl MsixConfig {
- pub fn new(msix_vectors: u16, vm_socket: Tube) -> Self {
+ pub fn new(msix_vectors: u16, vm_socket: Tube, pci_id: u32, device_name: String) -> Self {
assert!(msix_vectors <= MAX_MSIX_VECTORS_PER_DEVICE);
let mut table_entries: Vec<MsixTableEntry> = Vec::new();
table_entries.resize_with(msix_vectors as usize, Default::default);
+ table_entries
+ .iter_mut()
+ .for_each(|entry| entry.vector_ctl |= MSIX_TABLE_ENTRY_MASK_BIT);
let mut pba_entries: Vec<u64> = Vec::new();
- let num_pba_entries: usize = ((msix_vectors as usize) / BITS_PER_PBA_ENTRY) + 1;
+ let num_pba_entries: usize =
+ ((msix_vectors as usize) + BITS_PER_PBA_ENTRY - 1) / BITS_PER_PBA_ENTRY;
pba_entries.resize_with(num_pba_entries, Default::default);
+ let mut irq_vec = Vec::new();
+ irq_vec.resize_with(msix_vectors.into(), || None::<IrqfdGsi>);
+
MsixConfig {
table_entries,
pba_entries,
- irq_vec: Vec::new(),
+ irq_vec,
masked: false,
enabled: false,
msi_device_socket: vm_socket,
msix_num: msix_vectors,
+ pci_id,
+ device_name,
}
}
@@ -173,7 +166,7 @@ impl MsixConfig {
self.enabled = (reg & MSIX_ENABLE_BIT) == MSIX_ENABLE_BIT;
if !old_enabled && self.enabled {
- if let Err(e) = self.msix_enable() {
+ if let Err(e) = self.msix_enable_all() {
error!("failed to enable MSI-X: {}", e);
self.enabled = false;
}
@@ -231,37 +224,55 @@ impl MsixConfig {
Ok(())
}
- fn msix_enable(&mut self) -> MsixResult<()> {
- self.irq_vec.clear();
- for i in 0..self.msix_num {
- let irqfd = Event::new().unwrap();
- let request = VmIrqRequest::AllocateOneMsi { irqfd };
- self.msi_device_socket
- .send(&request)
- .map_err(MsixError::AllocateOneMsiSend)?;
- let irq_num: u32;
- match self
- .msi_device_socket
- .recv()
- .map_err(MsixError::AllocateOneMsiRecv)?
- {
- VmIrqResponse::AllocateOneMsi { gsi } => irq_num = gsi,
- VmIrqResponse::Err(e) => return Err(MsixError::AllocateOneMsi(e)),
- _ => unreachable!(),
- }
- self.irq_vec.push(IrqfdGsi {
- irqfd: match request {
- VmIrqRequest::AllocateOneMsi { irqfd } => irqfd,
- _ => unreachable!(),
- },
- gsi: irq_num,
- });
-
- self.add_msi_route(i, irq_num)?;
+ // Enable MSI-X
+ fn msix_enable_all(&mut self) -> MsixResult<()> {
+ for index in 0..self.irq_vec.len() {
+ self.msix_enable_one(index)?;
}
Ok(())
}
+ // Use a new MSI-X vector
+ // Create a new eventfd and bind them to a new msi
+ fn msix_enable_one(&mut self, index: usize) -> MsixResult<()> {
+ if self.irq_vec[index].is_some()
+ || !self.enabled()
+ || self.masked()
+ || self.table_masked(index)
+ {
+ return Ok(());
+ }
+ let irqfd = Event::new().map_err(MsixError::AllocateOneMsi)?;
+ let request = VmIrqRequest::AllocateOneMsi {
+ irqfd,
+ device_id: self.pci_id,
+ queue_id: index as usize,
+ device_name: self.device_name.clone(),
+ };
+ self.msi_device_socket
+ .send(&request)
+ .map_err(MsixError::AllocateOneMsiSend)?;
+ let irq_num: u32 = match self
+ .msi_device_socket
+ .recv()
+ .map_err(MsixError::AllocateOneMsiRecv)?
+ {
+ VmIrqResponse::AllocateOneMsi { gsi } => gsi,
+ VmIrqResponse::Err(e) => return Err(MsixError::AllocateOneMsi(e)),
+ _ => unreachable!(),
+ };
+ self.irq_vec[index] = Some(IrqfdGsi {
+ irqfd: match request {
+ VmIrqRequest::AllocateOneMsi { irqfd, .. } => irqfd,
+ _ => unreachable!(),
+ },
+ gsi: irq_num,
+ });
+
+ self.add_msi_route(index as u16, irq_num)?;
+ Ok(())
+ }
+
/// Read MSI-X table
/// # Arguments
/// * 'offset' - the offset within the MSI-X Table
@@ -362,14 +373,31 @@ impl MsixConfig {
};
let new_entry = self.table_entries[index].clone();
+
+ // This MSI-X vector is enabled for the first time.
+ if self.enabled()
+ && !self.masked()
+ && self.irq_vec[index].is_none()
+ && old_entry.masked()
+ && !new_entry.masked()
+ {
+ if let Err(e) = self.msix_enable_one(index) {
+ error!("failed to enable MSI-X vector {}: {}", index, e);
+ self.table_entries[index].vector_ctl |= MSIX_TABLE_ENTRY_MASK_BIT;
+ }
+ return MsixStatus::EntryChanged(index);
+ }
+
if self.enabled()
&& (old_entry.msg_addr_lo != new_entry.msg_addr_lo
|| old_entry.msg_addr_hi != new_entry.msg_addr_hi
|| old_entry.msg_data != new_entry.msg_data)
{
- let irq_num = self.irq_vec[index].gsi;
- if let Err(e) = self.add_msi_route(index as u16, irq_num) {
- error!("add_msi_route failed: {}", e);
+ if let Some(irqfd_gsi) = &self.irq_vec[index] {
+ let irq_num = irqfd_gsi.gsi;
+ if let Err(e) = self.add_msi_route(index as u16, irq_num) {
+ error!("add_msi_route failed: {}", e);
+ }
}
}
@@ -467,7 +495,7 @@ impl MsixConfig {
}
fn inject_msix_and_clear_pba(&mut self, vector: usize) {
- if let Some(irq) = self.irq_vec.get(vector) {
+ if let Some(irq) = &self.irq_vec[vector] {
irq.irqfd.write(1).unwrap();
}
@@ -493,12 +521,12 @@ impl MsixConfig {
pub fn trigger(&mut self, vector: u16) {
if self.table_entries[vector as usize].masked() || self.masked() {
self.set_pba_bit(vector, true);
- } else if let Some(irq) = self.irq_vec.get(vector as usize) {
+ } else if let Some(irq) = self.irq_vec.get(vector as usize).unwrap_or(&None) {
irq.irqfd.write(1).unwrap();
}
}
- /// Return the raw fd of the MSI device socket
+ /// Return the raw descriptor of the MSI device socket
pub fn get_msi_socket(&self) -> RawDescriptor {
self.msi_device_socket.as_raw_descriptor()
}
@@ -508,11 +536,26 @@ impl MsixConfig {
/// # Arguments
/// * 'vector' - the index to the MSI-X table entry
pub fn get_irqfd(&self, vector: usize) -> Option<&Event> {
- match self.irq_vec.get(vector) {
+ match self.irq_vec.get(vector as usize).unwrap_or(&None) {
Some(irq) => Some(&irq.irqfd),
None => None,
}
}
+
+ pub fn destroy(&mut self) {
+ while let Some(irq) = self.irq_vec.pop() {
+ if let Some(irq) = irq {
+ let request = VmIrqRequest::ReleaseOneIrq {
+ gsi: irq.gsi,
+ irqfd: irq.irqfd,
+ };
+ if self.msi_device_socket.send(&request).is_err() {
+ continue;
+ }
+ let _ = self.msi_device_socket.recv::<VmIrqResponse>();
+ }
+ }
+ }
}
impl AsRawDescriptor for MsixConfig {
@@ -521,6 +564,21 @@ impl AsRawDescriptor for MsixConfig {
}
}
+/// Message Control Register
+// 10-0: MSI-X Table size
+// 13-11: Reserved
+// 14: Mask. Mask all MSI-X when set.
+// 15: Enable. Enable all MSI-X when set.
+// See <https://wiki.osdev.org/PCI#Enabling_MSI-X> for the details.
+#[bitfield]
+#[derive(Copy, Clone, Default)]
+pub struct MsixCtrl {
+ table_size: B10,
+ reserved: B4,
+ mask: B1,
+ enable: B1,
+}
+
// It is safe to implement DataInit; all members are simple numbers and any value is valid.
unsafe impl DataInit for MsixCap {}
@@ -533,11 +591,7 @@ pub struct MsixCap {
_cap_vndr: u8,
_cap_next: u8,
// Message Control Register
- // 10-0: MSI-X Table size
- // 13-11: Reserved
- // 14: Mask. Mask all MSI-X when set.
- // 15: Enable. Enable all MSI-X when set.
- msg_ctl: u16,
+ msg_ctl: MsixCtrl,
// Table. Contains the offset and the BAR indicator (BIR)
// 2-0: Table BAR indicator (BIR). Can be 0 to 5.
// 31-3: Table offset in the BAR pointed by the BIR.
@@ -554,7 +608,12 @@ impl PciCapability for MsixCap {
}
fn id(&self) -> PciCapabilityID {
- PciCapabilityID::MSIX
+ PciCapabilityID::Msix
+ }
+
+ fn writable_bits(&self) -> Vec<u32> {
+ // Only msg_ctl[15:14] is writable
+ vec![0x3000_0000, 0, 0]
}
}
@@ -569,7 +628,10 @@ impl MsixCap {
assert!(table_size < MAX_MSIX_VECTORS_PER_DEVICE);
// Set the table size and enable MSI-X.
- let msg_ctl: u16 = MSIX_ENABLE_BIT + table_size - 1;
+ let mut msg_ctl = MsixCtrl::new();
+ msg_ctl.set_enable(1);
+ // Table Size is N - 1 encoded.
+ msg_ctl.set_table_size(table_size - 1);
MsixCap {
_cap_vndr: 0,
@@ -579,4 +641,9 @@ impl MsixCap {
pba: (pba_off & 0xffff_fff8u32) | u32::from(pba_pci_bar & 0x7u8),
}
}
+
+ #[cfg(unix)]
+ pub fn msg_ctl(&self) -> MsixCtrl {
+ self.msg_ctl
+ }
}
diff --git a/devices/src/pci/pci_address.rs b/devices/src/pci/pci_address.rs
new file mode 100644
index 000000000..e43b17918
--- /dev/null
+++ b/devices/src/pci/pci_address.rs
@@ -0,0 +1,313 @@
+// Copyright 2018 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::fmt::{self, Display};
+use std::str::FromStr;
+
+use remain::sorted;
+use serde::{Deserialize, Serialize};
+use thiserror::Error as ThisError;
+
+#[derive(Debug, PartialEq)]
+pub enum PciAddressComponent {
+ Domain,
+ Bus,
+ Device,
+ Function,
+}
+
+#[derive(ThisError, Debug, PartialEq)]
+#[sorted]
+pub enum Error {
+ #[error("{0:?} out of range")]
+ ComponentOutOfRange(PciAddressComponent),
+ #[error("{0:?} failed to parse as hex")]
+ InvalidHex(PciAddressComponent),
+ #[error("Missing delimiter between {0:?} and {1:?}")]
+ MissingDelimiter(PciAddressComponent, PciAddressComponent),
+ #[error("Too many components in PCI address")]
+ TooManyComponents,
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// PCI Device Address, AKA Bus:Device.Function
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
+pub struct PciAddress {
+ pub bus: u8,
+ pub dev: u8, /* u5 */
+ pub func: u8, /* u3 */
+}
+
+impl Display for PciAddress {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let domain = 0;
+ write!(
+ f,
+ "{:04x}:{:02x}:{:02x}.{:0x}",
+ domain, self.bus, self.dev, self.func,
+ )
+ }
+}
+
+/// Construct PciAddress from string [domain:]bus:device.function.
+impl FromStr for PciAddress {
+ type Err = Error;
+
+ fn from_str(address: &str) -> std::result::Result<Self, Self::Err> {
+ let (dev_bus_domain, func) = address.rsplit_once('.').ok_or(Error::MissingDelimiter(
+ PciAddressComponent::Device,
+ PciAddressComponent::Function,
+ ))?;
+ let func = u32::from_str_radix(func, 16)
+ .map_err(|_| Error::InvalidHex(PciAddressComponent::Function))?;
+
+ let (bus_domain, dev) = dev_bus_domain
+ .rsplit_once(':')
+ .ok_or(Error::MissingDelimiter(
+ PciAddressComponent::Bus,
+ PciAddressComponent::Device,
+ ))?;
+ let dev = u32::from_str_radix(dev, 16)
+ .map_err(|_| Error::InvalidHex(PciAddressComponent::Device))?;
+
+ // Domain is optional; if unspecified, the rest of the string is the bus, and domain
+ // defaults to 0.
+ let (domain, bus) = bus_domain.rsplit_once(':').unwrap_or(("0", bus_domain));
+ let bus = u32::from_str_radix(bus, 16)
+ .map_err(|_| Error::InvalidHex(PciAddressComponent::Bus))?;
+
+ if domain.contains(':') {
+ return Err(Error::TooManyComponents);
+ }
+
+ let domain = u32::from_str_radix(domain, 16)
+ .map_err(|_| Error::InvalidHex(PciAddressComponent::Domain))?;
+
+ Self::new(domain, bus, dev, func)
+ }
+}
+
+impl PciAddress {
+ const BUS_MASK: u32 = 0x00ff;
+ const DEVICE_BITS_NUM: usize = 5;
+ const DEVICE_MASK: u32 = 0x1f;
+ const FUNCTION_BITS_NUM: usize = 3;
+ const FUNCTION_MASK: u32 = 0x07;
+ const REGISTER_OFFSET: usize = 2;
+
+ /// Construct PciAddress from separate domain, bus, device, and function numbers.
+ pub fn new(domain: u32, bus: u32, dev: u32, func: u32) -> Result<Self> {
+ if bus > Self::BUS_MASK {
+ return Err(Error::ComponentOutOfRange(PciAddressComponent::Bus));
+ }
+
+ if dev > Self::DEVICE_MASK {
+ return Err(Error::ComponentOutOfRange(PciAddressComponent::Device));
+ }
+
+ if func > Self::FUNCTION_MASK {
+ return Err(Error::ComponentOutOfRange(PciAddressComponent::Function));
+ }
+
+ // PciAddress does not store domain for now, so disallow anything other than domain 0.
+ if domain > 0 {
+ return Err(Error::ComponentOutOfRange(PciAddressComponent::Domain));
+ }
+
+ Ok(PciAddress {
+ bus: bus as u8,
+ dev: dev as u8,
+ func: func as u8,
+ })
+ }
+
+ /// Construct PciAddress and register tuple from CONFIG_ADDRESS value.
+ pub fn from_config_address(config_address: u32, register_bits_num: usize) -> (Self, usize) {
+ let bus_offset = register_bits_num + Self::FUNCTION_BITS_NUM + Self::DEVICE_BITS_NUM;
+ let bus = ((config_address >> bus_offset) & Self::BUS_MASK) as u8;
+ let dev_offset = register_bits_num + Self::FUNCTION_BITS_NUM;
+ let dev = ((config_address >> dev_offset) & Self::DEVICE_MASK) as u8;
+ let func = ((config_address >> register_bits_num) & Self::FUNCTION_MASK) as u8;
+ let register_mask: u32 = (1_u32 << (register_bits_num - Self::REGISTER_OFFSET)) - 1;
+ let register = ((config_address >> Self::REGISTER_OFFSET) & register_mask) as usize;
+
+ (PciAddress { bus, dev, func }, register)
+ }
+
+ /// Encode PciAddress into CONFIG_ADDRESS value.
+ pub fn to_config_address(&self, register: usize, register_bits_num: usize) -> u32 {
+ let bus_offset = register_bits_num + Self::FUNCTION_BITS_NUM + Self::DEVICE_BITS_NUM;
+ let dev_offset = register_bits_num + Self::FUNCTION_BITS_NUM;
+ let register_mask: u32 = (1_u32 << (register_bits_num - Self::REGISTER_OFFSET)) - 1;
+ ((Self::BUS_MASK & self.bus as u32) << bus_offset)
+ | ((Self::DEVICE_MASK & self.dev as u32) << dev_offset)
+ | ((Self::FUNCTION_MASK & self.func as u32) << register_bits_num)
+ | ((register_mask & register as u32) << Self::REGISTER_OFFSET)
+ }
+
+ /// Convert B:D:F PCI address to unsigned 32 bit integer
+ pub fn to_u32(&self) -> u32 {
+ ((Self::BUS_MASK & self.bus as u32) << (Self::FUNCTION_BITS_NUM + Self::DEVICE_BITS_NUM))
+ | ((Self::DEVICE_MASK & self.dev as u32) << Self::FUNCTION_BITS_NUM)
+ | (Self::FUNCTION_MASK & self.func as u32)
+ }
+
+ /// Returns true if the address points to PCI root host-bridge.
+ pub fn is_root(&self) -> bool {
+ matches!(
+ &self,
+ PciAddress {
+ bus: 0,
+ dev: 0,
+ func: 0
+ }
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn from_string() {
+ assert_eq!(
+ PciAddress::from_str("0000:00:00.0").unwrap(),
+ PciAddress {
+ bus: 0,
+ dev: 0,
+ func: 0
+ }
+ );
+ assert_eq!(
+ PciAddress::from_str("00:00.0").unwrap(),
+ PciAddress {
+ bus: 0,
+ dev: 0,
+ func: 0
+ }
+ );
+ assert_eq!(
+ PciAddress::from_str("01:02.3").unwrap(),
+ PciAddress {
+ bus: 1,
+ dev: 2,
+ func: 3
+ }
+ );
+ assert_eq!(
+ PciAddress::from_str("ff:1f.7").unwrap(),
+ PciAddress {
+ bus: 0xff,
+ dev: 0x1f,
+ func: 7,
+ }
+ );
+ }
+
+ #[test]
+ fn from_string_missing_func_delim() {
+ assert_eq!(
+ PciAddress::from_str("1").expect_err("parse should fail"),
+ Error::MissingDelimiter(PciAddressComponent::Device, PciAddressComponent::Function)
+ );
+ }
+
+ #[test]
+ fn from_string_missing_dev_delim() {
+ assert_eq!(
+ PciAddress::from_str("2.1").expect_err("parse should fail"),
+ Error::MissingDelimiter(PciAddressComponent::Bus, PciAddressComponent::Device)
+ );
+ }
+
+ #[test]
+ fn from_string_extra_components() {
+ assert_eq!(
+ PciAddress::from_str("0:0:0:0.0").expect_err("parse should fail"),
+ Error::TooManyComponents
+ );
+ }
+
+ #[test]
+ fn from_string_invalid_func_hex() {
+ assert_eq!(
+ PciAddress::from_str("0000:00:00.g").expect_err("parse should fail"),
+ Error::InvalidHex(PciAddressComponent::Function)
+ );
+ }
+
+ #[test]
+ fn from_string_invalid_func_range() {
+ assert_eq!(
+ PciAddress::from_str("0000:00:00.8").expect_err("parse should fail"),
+ Error::ComponentOutOfRange(PciAddressComponent::Function)
+ );
+ }
+
+ #[test]
+ fn from_string_invalid_dev_hex() {
+ assert_eq!(
+ PciAddress::from_str("0000:00:gg.0").expect_err("parse should fail"),
+ Error::InvalidHex(PciAddressComponent::Device)
+ );
+ }
+
+ #[test]
+ fn from_string_invalid_dev_range() {
+ assert_eq!(
+ PciAddress::from_str("0000:00:20.0").expect_err("parse should fail"),
+ Error::ComponentOutOfRange(PciAddressComponent::Device)
+ );
+ }
+
+ #[test]
+ fn from_string_invalid_bus_hex() {
+ assert_eq!(
+ PciAddress::from_str("0000:gg:00.0").expect_err("parse should fail"),
+ Error::InvalidHex(PciAddressComponent::Bus)
+ );
+ }
+
+ #[test]
+ fn from_string_invalid_bus_range() {
+ assert_eq!(
+ PciAddress::from_str("0000:100:00.0").expect_err("parse should fail"),
+ Error::ComponentOutOfRange(PciAddressComponent::Bus)
+ );
+ }
+
+ #[test]
+ fn from_string_invalid_domain_hex() {
+ assert_eq!(
+ PciAddress::from_str("gggg:00:00.0").expect_err("parse should fail"),
+ Error::InvalidHex(PciAddressComponent::Domain)
+ );
+ }
+
+ #[test]
+ fn from_string_invalid_domain_range() {
+ assert_eq!(
+ PciAddress::from_str("0001:00:00.0").expect_err("parse should fail"),
+ Error::ComponentOutOfRange(PciAddressComponent::Domain)
+ );
+ }
+
+ #[test]
+ fn format_simple() {
+ assert_eq!(
+ PciAddress::new(0, 1, 2, 3).unwrap().to_string(),
+ "0000:01:02.3"
+ );
+ }
+
+ #[test]
+ fn format_max() {
+ assert_eq!(
+ PciAddress::new(0, 0xff, 0x1f, 7).unwrap().to_string(),
+ "0000:ff:1f.7"
+ );
+ }
+}
diff --git a/devices/src/pci/pci_configuration.rs b/devices/src/pci/pci_configuration.rs
index 8654b0171..ad9db6942 100644
--- a/devices/src/pci/pci_configuration.rs
+++ b/devices/src/pci/pci_configuration.rs
@@ -2,29 +2,45 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+use std::convert::TryFrom;
use std::convert::TryInto;
-use std::fmt::{self, Display};
-use crate::pci::PciInterruptPin;
use base::warn;
+use remain::sorted;
+use serde::{Deserialize, Serialize};
+use thiserror::Error;
+
+use crate::pci::{PciAddress, PciInterruptPin};
// The number of 32bit registers in the config space, 256 bytes.
const NUM_CONFIGURATION_REGISTERS: usize = 64;
+pub const PCI_ID_REG: usize = 0;
pub const COMMAND_REG: usize = 1;
pub const COMMAND_REG_IO_SPACE_MASK: u32 = 0x0000_0001;
pub const COMMAND_REG_MEMORY_SPACE_MASK: u32 = 0x0000_0002;
const STATUS_REG: usize = 1;
-const STATUS_REG_CAPABILITIES_USED_MASK: u32 = 0x0010_0000;
-const BAR0_REG: usize = 4;
+pub const STATUS_REG_CAPABILITIES_USED_MASK: u32 = 0x0010_0000;
+#[cfg(unix)]
+pub const CLASS_REG: usize = 2;
+#[cfg(feature = "direct")]
+pub const CLASS_REG_REVISION_ID_OFFSET: usize = 0;
+pub const HEADER_TYPE_REG: usize = 3;
+pub const HEADER_TYPE_MULTIFUNCTION_MASK: u32 = 0x0080_0000;
+pub const BAR0_REG: usize = 4;
const BAR_IO_ADDR_MASK: u32 = 0xffff_fffc;
const BAR_IO_MIN_SIZE: u64 = 4;
const BAR_MEM_ADDR_MASK: u32 = 0xffff_fff0;
const BAR_MEM_MIN_SIZE: u64 = 16;
-const NUM_BAR_REGS: usize = 6;
-const CAPABILITY_LIST_HEAD_OFFSET: usize = 0x34;
+const BAR_ROM_MIN_SIZE: u64 = 2048;
+pub const NUM_BAR_REGS: usize = 7; // 6 normal BARs + expansion ROM BAR.
+pub const ROM_BAR_IDX: PciBarIndex = 6;
+pub const ROM_BAR_REG: usize = 12;
+pub const CAPABILITY_LIST_HEAD_OFFSET: usize = 0x34;
+#[cfg(unix)]
+pub const PCI_CAP_NEXT_POINTER: usize = 0x1;
const FIRST_CAPABILITY_OFFSET: usize = 0x40;
-const CAPABILITY_MAX_OFFSET: usize = 255;
+pub const CAPABILITY_MAX_OFFSET: usize = 255;
const INTERRUPT_LINE_PIN_REG: usize = 15;
@@ -38,7 +54,7 @@ pub enum PciHeaderType {
/// Classes of PCI nodes.
#[allow(dead_code)]
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, enumn::N, Serialize, Deserialize)]
pub enum PciClassCode {
TooOld,
MassStorage,
@@ -55,8 +71,11 @@ pub enum PciClassCode {
SerialBusController,
WirelessController,
IntelligentIoController,
+ SatelliteCommunicationController,
EncryptionController,
DataAcquisitionSignalProcessing,
+ ProcessingAccelerator,
+ NonEssentialInstrumentation,
Other = 0xff,
}
@@ -66,6 +85,23 @@ impl PciClassCode {
}
}
+#[sorted]
+#[derive(Error, Debug)]
+pub enum PciClassCodeParseError {
+ #[error("Unknown class code")]
+ Unknown,
+}
+
+impl TryFrom<u8> for PciClassCode {
+ type Error = PciClassCodeParseError;
+ fn try_from(v: u8) -> std::result::Result<PciClassCode, PciClassCodeParseError> {
+ match PciClassCode::n(v) {
+ Some(class) => Ok(class),
+ None => Err(PciClassCodeParseError::Unknown),
+ }
+ }
+}
+
/// A PCI sublcass. Each class in `PciClassCode` can specify a unique set of subclasses. This trait
/// is implemented by each subclass. It allows use of a trait object to generate configurations.
pub trait PciSubclass {
@@ -118,7 +154,7 @@ pub enum PciBridgeSubclass {
PcmciaBridge = 0x05,
NuBusBridge = 0x06,
CardBusBridge = 0x07,
- RACEwayBridge = 0x08,
+ RaceWayBridge = 0x08,
PciToPciSemiTransparentBridge = 0x09,
InfiniBrandToPciHostBridge = 0x0a,
OtherBridgeDevice = 0x80,
@@ -135,9 +171,9 @@ impl PciSubclass for PciBridgeSubclass {
#[derive(Copy, Clone)]
pub enum PciSerialBusSubClass {
Firewire = 0x00,
- ACCESSbus = 0x01,
- SSA = 0x02,
- USB = 0x03,
+ AccessBus = 0x01,
+ Ssa = 0x02,
+ Usb = 0x03,
}
impl PciSubclass for PciSerialBusSubClass {
@@ -146,6 +182,20 @@ impl PciSubclass for PciSerialBusSubClass {
}
}
+/// Subclasses for PciClassCode Other.
+#[allow(dead_code)]
+#[derive(Copy, Clone)]
+#[repr(u8)]
+pub enum PciOtherSubclass {
+ Other = 0xff,
+}
+
+impl PciSubclass for PciOtherSubclass {
+ fn get_register_value(&self) -> u8 {
+ *self as u8
+ }
+}
+
/// A PCI class programming interface. Each combination of `PciClassCode` and
/// `PciSubclass` can specify a set of register-level programming interfaces.
/// This trait is implemented by each programming interface.
@@ -163,27 +213,28 @@ pub enum PciCapabilityID {
VitalProductData = 0x03,
SlotIdentification = 0x04,
MessageSignalledInterrupts = 0x05,
- CompactPCIHotSwap = 0x06,
- PCIX = 0x07,
+ CompactPciHotSwap = 0x06,
+ Pcix = 0x07,
HyperTransport = 0x08,
VendorSpecific = 0x09,
Debugport = 0x0A,
- CompactPCICentralResourceControl = 0x0B,
- PCIStandardHotPlugController = 0x0C,
+ CompactPciCentralResourceControl = 0x0B,
+ PciStandardHotPlugController = 0x0C,
BridgeSubsystemVendorDeviceID = 0x0D,
- AGPTargetPCIPCIbridge = 0x0E,
+ AgpTargetPciPciBridge = 0x0E,
SecureDevice = 0x0F,
- PCIExpress = 0x10,
- MSIX = 0x11,
- SATADataIndexConf = 0x12,
- PCIAdvancedFeatures = 0x13,
- PCIEnhancedAllocation = 0x14,
+ PciExpress = 0x10,
+ Msix = 0x11,
+ SataDataIndexConf = 0x12,
+ PciAdvancedFeatures = 0x13,
+ PciEnhancedAllocation = 0x14,
}
/// A PCI capability list. Devices can optionally specify capabilities in their configuration space.
pub trait PciCapability {
fn bytes(&self) -> &[u8];
fn id(&self) -> PciCapabilityID;
+ fn writable_bits(&self) -> Vec<u32>;
}
/// Contains the configuration space of a PCI node.
@@ -199,31 +250,33 @@ pub struct PciConfiguration {
}
/// See pci_regs.h in kernel
-#[derive(Copy, Clone, Debug, PartialEq)]
+#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum PciBarRegionType {
Memory32BitRegion = 0,
- IORegion = 0x01,
+ IoRegion = 0x01,
Memory64BitRegion = 0x04,
}
-#[derive(Copy, Clone, Debug, PartialEq)]
+#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum PciBarPrefetchable {
NotPrefetchable = 0,
Prefetchable = 0x08,
}
-#[derive(Copy, Clone, Debug, PartialEq)]
+pub type PciBarIndex = usize;
+
+#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct PciBarConfiguration {
addr: u64,
size: u64,
- reg_idx: usize,
+ bar_idx: PciBarIndex,
region_type: PciBarRegionType,
prefetchable: PciBarPrefetchable,
}
pub struct PciBarIter<'a> {
config: &'a PciConfiguration,
- bar_num: usize,
+ bar_num: PciBarIndex,
}
impl<'a> Iterator for PciBarIter<'a> {
@@ -242,45 +295,34 @@ impl<'a> Iterator for PciBarIter<'a> {
}
}
-#[derive(Debug, PartialEq)]
+#[sorted]
+#[derive(Error, Debug, PartialEq)]
pub enum Error {
+ #[error("address {0} size {1} too big")]
BarAddressInvalid(u64, u64),
+ #[error("address {0} is not aligned to size {1}")]
BarAlignmentInvalid(u64, u64),
- BarInUse(usize),
- BarInUse64(usize),
- BarInvalid(usize),
- BarInvalid64(usize),
+ #[error("bar {0} already used")]
+ BarInUse(PciBarIndex),
+ #[error("64bit bar {0} already used (requires two regs)")]
+ BarInUse64(PciBarIndex),
+ #[error("bar {0} invalid, max {}", NUM_BAR_REGS - 1)]
+ BarInvalid(PciBarIndex),
+ #[error("64bitbar {0} invalid, requires two regs, max {}", ROM_BAR_IDX - 1)]
+ BarInvalid64(PciBarIndex),
+ #[error("expansion rom bar must be a memory region")]
+ BarInvalidRomType,
+ #[error("bar address {0} not a power of two")]
BarSizeInvalid(u64),
+ #[error("empty capabilities are invalid")]
CapabilityEmpty,
+ #[error("Invalid capability length {0}")]
CapabilityLengthInvalid(usize),
+ #[error("capability of size {0} doesn't fit")]
CapabilitySpaceFull(usize),
}
-pub type Result<T> = std::result::Result<T, Error>;
-impl std::error::Error for Error {}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
- match self {
- BarAddressInvalid(a, s) => write!(f, "address {} size {} too big", a, s),
- BarAlignmentInvalid(a, s) => write!(f, "address {} is not aligned to size {}", a, s),
- BarInUse(b) => write!(f, "bar {} already used", b),
- BarInUse64(b) => write!(f, "64bit bar {} already used(requires two regs)", b),
- BarInvalid(b) => write!(f, "bar {} invalid, max {}", b, NUM_BAR_REGS - 1),
- BarInvalid64(b) => write!(
- f,
- "64bitbar {} invalid, requires two regs, max {}",
- b,
- NUM_BAR_REGS - 1
- ),
- BarSizeInvalid(s) => write!(f, "bar address {} not a power of two", s),
- 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),
- }
- }
-}
+pub type Result<T> = std::result::Result<T, Error>;
impl PciConfiguration {
pub fn new(
@@ -313,14 +355,23 @@ impl PciConfiguration {
PciHeaderType::Device => {
registers[3] = 0x0000_0000; // Header type 0 (device)
writable_bits[15] = 0x0000_00ff; // Interrupt line (r/w)
+ registers[11] = u32::from(subsystem_id) << 16 | u32::from(subsystem_vendor_id);
}
PciHeaderType::Bridge => {
registers[3] = 0x0001_0000; // Header type 1 (bridge)
- writable_bits[9] = 0xfff0_fff0; // Memory base and limit
+ writable_bits[6] = 0x00ff_ffff; // Primary/secondary/subordinate bus number,
+ // secondary latency timer
+ registers[7] = 0x0000_00f0; // IO base > IO Limit, no IO address on secondary side at initialize
+ writable_bits[7] = 0xf900_0000; // IO base and limit, secondary status,
+ registers[8] = 0x0000_fff0; // mem base > mem Limit, no MMIO address on secondary side at initialize
+ writable_bits[8] = 0xfff0_fff0; // Memory base and limit
+ registers[9] = 0x0001_fff1; // pmem base > pmem Limit, no prefetch MMIO address on secondary side at initialize
+ writable_bits[9] = 0xfff0_fff0; // Prefetchable base and limit
+ writable_bits[10] = 0xffff_ffff; // Prefetchable base upper 32 bits
+ writable_bits[11] = 0xffff_ffff; // Prefetchable limit upper 32 bits
writable_bits[15] = 0xffff_00ff; // Bridge control (r/w), interrupt line (r/w)
}
};
- registers[11] = u32::from(subsystem_id) << 16 | u32::from(subsystem_vendor_id);
PciConfiguration {
registers,
@@ -415,20 +466,26 @@ impl PciConfiguration {
/// report this region and size to the guest kernel. Enforces a few constraints
/// (i.e, region size must be power of two, register not already used). Returns 'None' on
/// failure all, `Some(BarIndex)` on success.
- pub fn add_pci_bar(&mut self, config: PciBarConfiguration) -> Result<usize> {
- if config.reg_idx >= NUM_BAR_REGS {
- return Err(Error::BarInvalid(config.reg_idx));
+ pub fn add_pci_bar(&mut self, config: PciBarConfiguration) -> Result<PciBarIndex> {
+ if config.bar_idx >= NUM_BAR_REGS {
+ return Err(Error::BarInvalid(config.bar_idx));
}
- if self.bar_used[config.reg_idx] {
- return Err(Error::BarInUse(config.reg_idx));
+ if self.bar_used[config.bar_idx] {
+ return Err(Error::BarInUse(config.bar_idx));
}
if config.size.count_ones() != 1 {
return Err(Error::BarSizeInvalid(config.size));
}
- let min_size = if config.region_type == PciBarRegionType::IORegion {
+ if config.is_expansion_rom() && config.region_type != PciBarRegionType::Memory32BitRegion {
+ return Err(Error::BarInvalidRomType);
+ }
+
+ let min_size = if config.is_expansion_rom() {
+ BAR_ROM_MIN_SIZE
+ } else if config.region_type == PciBarRegionType::IoRegion {
BAR_IO_MIN_SIZE
} else {
BAR_MEM_MIN_SIZE
@@ -442,33 +499,34 @@ impl PciConfiguration {
return Err(Error::BarAlignmentInvalid(config.addr, config.size));
}
- let bar_idx = BAR0_REG + config.reg_idx;
+ let reg_idx = config.reg_index();
let end_addr = config
.addr
.checked_add(config.size)
.ok_or(Error::BarAddressInvalid(config.addr, config.size))?;
match config.region_type {
- PciBarRegionType::Memory32BitRegion | PciBarRegionType::IORegion => {
+ PciBarRegionType::Memory32BitRegion | PciBarRegionType::IoRegion => {
if end_addr > u64::from(u32::max_value()) {
return Err(Error::BarAddressInvalid(config.addr, config.size));
}
}
PciBarRegionType::Memory64BitRegion => {
- if config.reg_idx + 1 >= NUM_BAR_REGS {
- return Err(Error::BarInvalid64(config.reg_idx));
+ // The expansion ROM BAR cannot be used for part of a 64-bit BAR.
+ if config.bar_idx + 1 >= ROM_BAR_IDX {
+ return Err(Error::BarInvalid64(config.bar_idx));
}
if end_addr > u64::max_value() {
return Err(Error::BarAddressInvalid(config.addr, config.size));
}
- if self.bar_used[config.reg_idx + 1] {
- return Err(Error::BarInUse64(config.reg_idx));
+ if self.bar_used[config.bar_idx + 1] {
+ return Err(Error::BarInUse64(config.bar_idx));
}
- self.registers[bar_idx + 1] = (config.addr >> 32) as u32;
- self.writable_bits[bar_idx + 1] = !((config.size - 1) >> 32) as u32;
- self.bar_used[config.reg_idx + 1] = true;
+ self.registers[reg_idx + 1] = (config.addr >> 32) as u32;
+ self.writable_bits[reg_idx + 1] = !((config.size - 1) >> 32) as u32;
+ self.bar_used[config.bar_idx + 1] = true;
}
}
@@ -480,32 +538,43 @@ impl PciConfiguration {
config.prefetchable as u32 | config.region_type as u32,
)
}
- PciBarRegionType::IORegion => {
+ PciBarRegionType::IoRegion => {
self.registers[COMMAND_REG] |= COMMAND_REG_IO_SPACE_MASK;
(BAR_IO_ADDR_MASK, config.region_type as u32)
}
};
- self.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits;
- self.writable_bits[bar_idx] = !(config.size - 1) as u32;
- self.bar_used[config.reg_idx] = true;
- self.bar_configs[config.reg_idx] = Some(config);
- Ok(config.reg_idx)
+ self.registers[reg_idx] = ((config.addr as u32) & mask) | lower_bits;
+ self.writable_bits[reg_idx] = !(config.size - 1) as u32;
+ if config.is_expansion_rom() {
+ self.writable_bits[reg_idx] |= 1; // Expansion ROM enable bit.
+ }
+ self.bar_used[config.bar_idx] = true;
+ self.bar_configs[config.bar_idx] = Some(config);
+ Ok(config.bar_idx)
}
/// Returns an iterator of the currently configured base address registers.
#[allow(dead_code)] // TODO(dverkamp): remove this once used
pub fn get_bars(&self) -> PciBarIter {
PciBarIter {
- config: &self,
+ config: self,
bar_num: 0,
}
}
- fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
+ /// Returns the configuration of a base address register, if present.
+ pub fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
let config = self.bar_configs.get(bar_num)?;
if let Some(mut config) = config {
+ let command = self.read_reg(COMMAND_REG);
+ if (config.is_memory() && (command & COMMAND_REG_MEMORY_SPACE_MASK == 0))
+ || (config.is_io() && (command & COMMAND_REG_IO_SPACE_MASK == 0))
+ {
+ return None;
+ }
+
// The address may have been modified by the guest, so the value in bar_configs
// may be outdated. Replace it with the current value.
config.addr = self.get_bar_addr(bar_num);
@@ -516,13 +585,17 @@ impl PciConfiguration {
}
/// Returns the type of the given BAR region.
- pub fn get_bar_type(&self, bar_num: usize) -> Option<PciBarRegionType> {
+ pub fn get_bar_type(&self, bar_num: PciBarIndex) -> Option<PciBarRegionType> {
self.bar_configs.get(bar_num)?.map(|c| c.region_type)
}
/// Returns the address of the given BAR region.
- pub fn get_bar_addr(&self, bar_num: usize) -> u64 {
- let bar_idx = BAR0_REG + bar_num;
+ pub fn get_bar_addr(&self, bar_num: PciBarIndex) -> u64 {
+ let bar_idx = if bar_num == ROM_BAR_IDX {
+ ROM_BAR_REG
+ } else {
+ BAR0_REG + bar_num
+ };
let bar_type = match self.get_bar_type(bar_num) {
Some(t) => t,
@@ -530,7 +603,7 @@ impl PciConfiguration {
};
match bar_type {
- PciBarRegionType::IORegion => u64::from(self.registers[bar_idx] & BAR_IO_ADDR_MASK),
+ PciBarRegionType::IoRegion => u64::from(self.registers[bar_idx] & BAR_IO_ADDR_MASK),
PciBarRegionType::Memory32BitRegion => {
u64::from(self.registers[bar_idx] & BAR_MEM_ADDR_MASK)
}
@@ -578,6 +651,10 @@ impl PciConfiguration {
for (i, byte) in cap_data.bytes().iter().enumerate().skip(2) {
self.write_byte_internal(cap_offset + i, *byte, false);
}
+ let reg_idx = cap_offset / 4;
+ for (i, dword) in cap_data.writable_bits().iter().enumerate() {
+ self.writable_bits[reg_idx + i] = *dword;
+ }
self.last_capability = Some((cap_offset, total_len));
Ok(cap_offset)
}
@@ -587,29 +664,26 @@ impl PciConfiguration {
let next = offset + len;
(next + 3) & !3
}
-}
-impl Default for PciBarConfiguration {
- fn default() -> Self {
- PciBarConfiguration {
- reg_idx: 0,
- addr: 0,
- size: 0,
- region_type: PciBarRegionType::Memory32BitRegion,
- prefetchable: PciBarPrefetchable::NotPrefetchable,
+ pub fn suggested_interrupt_pin(pci_address: PciAddress) -> PciInterruptPin {
+ match pci_address.func % 4 {
+ 0 => PciInterruptPin::IntA,
+ 1 => PciInterruptPin::IntB,
+ 2 => PciInterruptPin::IntC,
+ _ => PciInterruptPin::IntD,
}
}
}
impl PciBarConfiguration {
pub fn new(
- reg_idx: usize,
+ bar_idx: PciBarIndex,
size: u64,
region_type: PciBarRegionType,
prefetchable: PciBarPrefetchable,
) -> Self {
PciBarConfiguration {
- reg_idx,
+ bar_idx,
addr: 0,
size,
region_type,
@@ -617,13 +691,20 @@ impl PciBarConfiguration {
}
}
- pub fn set_register_index(mut self, reg_idx: usize) -> Self {
- self.reg_idx = reg_idx;
- self
+ pub fn bar_index(&self) -> PciBarIndex {
+ self.bar_idx
+ }
+
+ pub fn reg_index(&self) -> usize {
+ if self.bar_idx == ROM_BAR_IDX {
+ ROM_BAR_REG
+ } else {
+ BAR0_REG + self.bar_idx
+ }
}
- pub fn get_register_index(&self) -> usize {
- self.reg_idx
+ pub fn address(&self) -> u64 {
+ self.addr
}
pub fn set_address(mut self, addr: u64) -> Self {
@@ -631,13 +712,31 @@ impl PciBarConfiguration {
self
}
- pub fn set_size(mut self, size: u64) -> Self {
- self.size = size;
- self
+ pub fn size(&self) -> u64 {
+ self.size
}
- pub fn get_size(&self) -> u64 {
- self.size
+ pub fn is_expansion_rom(&self) -> bool {
+ self.bar_idx == ROM_BAR_IDX
+ }
+
+ pub fn is_memory(&self) -> bool {
+ matches!(
+ self.region_type,
+ PciBarRegionType::Memory32BitRegion | PciBarRegionType::Memory64BitRegion
+ )
+ }
+
+ pub fn is_64bit_memory(&self) -> bool {
+ self.region_type == PciBarRegionType::Memory64BitRegion
+ }
+
+ pub fn is_io(&self) -> bool {
+ self.region_type == PciBarRegionType::IoRegion
+ }
+
+ pub fn is_prefetchable(&self) -> bool {
+ self.is_memory() && self.prefetchable == PciBarPrefetchable::Prefetchable
}
}
@@ -668,6 +767,10 @@ mod tests {
fn id(&self) -> PciCapabilityID {
PciCapabilityID::VendorSpecific
}
+
+ fn writable_bits(&self) -> Vec<u32> {
+ vec![0u32; 1]
+ }
}
#[test]
@@ -818,7 +921,7 @@ mod tests {
PciBarRegionType::Memory64BitRegion,
PciBarPrefetchable::NotPrefetchable,
)
- .set_address(0x01234567_89ABCDE0),
+ .set_address(0x0123_4567_89AB_CDE0),
)
.expect("add_pci_bar failed");
@@ -826,7 +929,7 @@ mod tests {
cfg.get_bar_type(0),
Some(PciBarRegionType::Memory64BitRegion)
);
- assert_eq!(cfg.get_bar_addr(0), 0x01234567_89ABCDE0);
+ assert_eq!(cfg.get_bar_addr(0), 0x0123_4567_89AB_CDE0);
assert_eq!(cfg.writable_bits[BAR0_REG + 1], 0xFFFFFFFF);
assert_eq!(cfg.writable_bits[BAR0_REG + 0], 0xFFFFFFF0);
@@ -834,9 +937,9 @@ mod tests {
assert_eq!(
bar_iter.next(),
Some(PciBarConfiguration {
- addr: 0x01234567_89ABCDE0,
+ addr: 0x0123_4567_89AB_CDE0,
size: 0x10,
- reg_idx: 0,
+ bar_idx: 0,
region_type: PciBarRegionType::Memory64BitRegion,
prefetchable: PciBarPrefetchable::NotPrefetchable
})
@@ -882,7 +985,7 @@ mod tests {
Some(PciBarConfiguration {
addr: 0x12345670,
size: 0x10,
- reg_idx: 0,
+ bar_idx: 0,
region_type: PciBarRegionType::Memory32BitRegion,
prefetchable: PciBarPrefetchable::NotPrefetchable
})
@@ -908,14 +1011,14 @@ mod tests {
PciBarConfiguration::new(
0,
0x4,
- PciBarRegionType::IORegion,
+ PciBarRegionType::IoRegion,
PciBarPrefetchable::NotPrefetchable,
)
.set_address(0x1230),
)
.expect("add_pci_bar failed");
- assert_eq!(cfg.get_bar_type(0), Some(PciBarRegionType::IORegion));
+ assert_eq!(cfg.get_bar_type(0), Some(PciBarRegionType::IoRegion));
assert_eq!(cfg.get_bar_addr(0), 0x1230);
assert_eq!(cfg.writable_bits[BAR0_REG], 0xFFFFFFFC);
@@ -925,8 +1028,8 @@ mod tests {
Some(PciBarConfiguration {
addr: 0x1230,
size: 0x4,
- reg_idx: 0,
- region_type: PciBarRegionType::IORegion,
+ bar_idx: 0,
+ region_type: PciBarRegionType::IoRegion,
prefetchable: PciBarPrefetchable::NotPrefetchable
})
);
@@ -955,7 +1058,7 @@ mod tests {
PciBarRegionType::Memory64BitRegion,
PciBarPrefetchable::NotPrefetchable,
)
- .set_address(0x01234567_89ABCDE0),
+ .set_address(0x0123_4567_89AB_CDE0),
)
.expect("add_pci_bar failed");
@@ -976,7 +1079,7 @@ mod tests {
PciBarConfiguration::new(
3,
0x4,
- PciBarRegionType::IORegion,
+ PciBarRegionType::IoRegion,
PciBarPrefetchable::NotPrefetchable,
)
.set_address(0x1230),
@@ -988,9 +1091,9 @@ mod tests {
assert_eq!(
bar_iter.next(),
Some(PciBarConfiguration {
- addr: 0x01234567_89ABCDE0,
+ addr: 0x0123_4567_89AB_CDE0,
size: 0x10,
- reg_idx: 0,
+ bar_idx: 0,
region_type: PciBarRegionType::Memory64BitRegion,
prefetchable: PciBarPrefetchable::NotPrefetchable
})
@@ -1000,7 +1103,7 @@ mod tests {
Some(PciBarConfiguration {
addr: 0x12345670,
size: 0x10,
- reg_idx: 2,
+ bar_idx: 2,
region_type: PciBarRegionType::Memory32BitRegion,
prefetchable: PciBarPrefetchable::NotPrefetchable
})
@@ -1010,8 +1113,8 @@ mod tests {
Some(PciBarConfiguration {
addr: 0x1230,
size: 0x4,
- reg_idx: 3,
- region_type: PciBarRegionType::IORegion,
+ bar_idx: 3,
+ region_type: PciBarRegionType::IoRegion,
prefetchable: PciBarPrefetchable::NotPrefetchable
})
);
@@ -1025,9 +1128,9 @@ mod tests {
assert_eq!(
bar_iter.next(),
Some(PciBarConfiguration {
- addr: 0xFFEEDDCC_BBAA9980,
+ addr: 0xFFEE_DDCC_BBAA_9980,
size: 0x10,
- reg_idx: 0,
+ bar_idx: 0,
region_type: PciBarRegionType::Memory64BitRegion,
prefetchable: PciBarPrefetchable::NotPrefetchable
})
@@ -1037,7 +1140,7 @@ mod tests {
Some(PciBarConfiguration {
addr: 0x12345670,
size: 0x10,
- reg_idx: 2,
+ bar_idx: 2,
region_type: PciBarRegionType::Memory32BitRegion,
prefetchable: PciBarPrefetchable::NotPrefetchable
})
@@ -1047,8 +1150,8 @@ mod tests {
Some(PciBarConfiguration {
addr: 0x1230,
size: 0x4,
- reg_idx: 3,
- region_type: PciBarRegionType::IORegion,
+ bar_idx: 3,
+ region_type: PciBarRegionType::IoRegion,
prefetchable: PciBarPrefetchable::NotPrefetchable
})
);
@@ -1075,7 +1178,7 @@ mod tests {
PciBarConfiguration::new(
0,
0x2,
- PciBarRegionType::IORegion,
+ PciBarRegionType::IoRegion,
PciBarPrefetchable::NotPrefetchable,
)
.set_address(0x1230),
@@ -1089,7 +1192,7 @@ mod tests {
PciBarConfiguration::new(
0,
0x3,
- PciBarRegionType::IORegion,
+ PciBarRegionType::IoRegion,
PciBarPrefetchable::NotPrefetchable,
)
.set_address(0x1230),
@@ -1111,4 +1214,72 @@ mod tests {
Err(Error::BarSizeInvalid(0x8))
);
}
+
+ #[test]
+ fn add_rom_bar() {
+ let mut cfg = PciConfiguration::new(
+ 0x1234,
+ 0x5678,
+ PciClassCode::MultimediaController,
+ &PciMultimediaSubclass::AudioController,
+ Some(&TestPI::Test),
+ PciHeaderType::Device,
+ 0xABCD,
+ 0x2468,
+ 0,
+ );
+
+ // Attempt to add a 64-bit memory BAR as the expansion ROM (invalid).
+ assert_eq!(
+ cfg.add_pci_bar(PciBarConfiguration::new(
+ ROM_BAR_IDX,
+ 0x1000,
+ PciBarRegionType::Memory64BitRegion,
+ PciBarPrefetchable::NotPrefetchable,
+ ),),
+ Err(Error::BarInvalidRomType)
+ );
+
+ // Attempt to add an I/O BAR as the expansion ROM (invalid).
+ assert_eq!(
+ cfg.add_pci_bar(PciBarConfiguration::new(
+ ROM_BAR_IDX,
+ 0x1000,
+ PciBarRegionType::IoRegion,
+ PciBarPrefetchable::NotPrefetchable,
+ ),),
+ Err(Error::BarInvalidRomType)
+ );
+
+ // Attempt to add a 1KB memory region as the expansion ROM (too small).
+ assert_eq!(
+ cfg.add_pci_bar(PciBarConfiguration::new(
+ ROM_BAR_IDX,
+ 1024,
+ PciBarRegionType::Memory32BitRegion,
+ PciBarPrefetchable::NotPrefetchable,
+ ),),
+ Err(Error::BarSizeInvalid(1024))
+ );
+
+ // Add a 32-bit memory BAR as the expansion ROM (valid).
+ cfg.add_pci_bar(
+ PciBarConfiguration::new(
+ ROM_BAR_IDX,
+ 0x800,
+ PciBarRegionType::Memory32BitRegion,
+ PciBarPrefetchable::NotPrefetchable,
+ )
+ .set_address(0x12345000),
+ )
+ .expect("add_pci_bar failed");
+
+ assert_eq!(
+ cfg.get_bar_type(ROM_BAR_IDX),
+ Some(PciBarRegionType::Memory32BitRegion)
+ );
+ assert_eq!(cfg.get_bar_addr(ROM_BAR_IDX), 0x12345000);
+ assert_eq!(cfg.read_reg(ROM_BAR_REG), 0x12345000);
+ assert_eq!(cfg.writable_bits[ROM_BAR_REG], 0xFFFFF801);
+ }
}
diff --git a/devices/src/pci/pci_device.rs b/devices/src/pci/pci_device.rs
index e2a1b67e2..06f3d24e9 100644
--- a/devices/src/pci/pci_device.rs
+++ b/devices/src/pci/pci_device.rs
@@ -2,64 +2,83 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fmt::{self, Display};
-
-use base::{Event, RawDescriptor};
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use acpi_tables::sdt::SDT;
+use anyhow::bail;
+use base::{error, Event, RawDescriptor};
use hypervisor::Datamatch;
+use remain::sorted;
use resources::{Error as SystemAllocatorFaliure, SystemAllocator};
+use thiserror::Error;
-use crate::bus::ConfigWriteResult;
+use crate::bus::{BusDeviceObj, BusRange, BusType, ConfigWriteResult};
use crate::pci::pci_configuration::{
- self, COMMAND_REG, COMMAND_REG_IO_SPACE_MASK, COMMAND_REG_MEMORY_SPACE_MASK,
+ self, PciBarConfiguration, BAR0_REG, COMMAND_REG, COMMAND_REG_IO_SPACE_MASK,
+ COMMAND_REG_MEMORY_SPACE_MASK, NUM_BAR_REGS, PCI_ID_REG, ROM_BAR_REG,
};
-use crate::pci::{PciAddress, PciInterruptPin};
+use crate::pci::{PciAddress, PciAddressError, PciInterruptPin};
+use crate::virtio::ipc_memory_mapper::IpcMemoryMapper;
#[cfg(feature = "audio")]
use crate::virtio::snd::vios_backend::Error as VioSError;
-use crate::{BusAccessInfo, BusDevice};
+use crate::{BusAccessInfo, BusDevice, IrqLevelEvent};
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
+ /// Invalid alignment encountered.
+ #[error("Alignment must be a power of 2")]
+ BadAlignment,
/// Setup of the device capabilities failed.
+ #[error("failed to add capability {0}")]
CapabilitiesSetup(pci_configuration::Error),
- /// Allocating space for an IO BAR failed.
- IoAllocationFailed(u64, SystemAllocatorFaliure),
- /// Registering an IO BAR failed.
- IoRegistrationFailed(u64, pci_configuration::Error),
/// Create cras client failed.
- #[cfg(feature = "audio")]
+ #[cfg(all(feature = "audio", feature = "audio_cras"))]
+ #[error("failed to create CRAS Client: {0}")]
CreateCrasClientFailed(libcras::Error),
/// Create VioS client failed.
#[cfg(feature = "audio")]
+ #[error("failed to create VioS Client: {0}")]
CreateViosClientFailed(VioSError),
- /// PCI Address allocation failure.
- PciAllocationFailed,
+ /// Allocating space for an IO BAR failed.
+ #[error("failed to allocate space for an IO BAR, size={0}: {1}")]
+ IoAllocationFailed(u64, SystemAllocatorFaliure),
+ /// supports_iommu is false.
+ #[error("Iommu is not supported")]
+ IommuNotSupported,
+ /// Registering an IO BAR failed.
+ #[error("failed to register an IO BAR, addr={0} err={1}")]
+ IoRegistrationFailed(u64, pci_configuration::Error),
+ /// Out-of-space encountered
+ #[error("Out-of-space detected")]
+ OutOfSpace,
+ /// Overflow encountered
+ #[error("base={0} + size={1} overflows")]
+ Overflow(u64, u64),
/// PCI Address is not allocated.
+ #[error("PCI address is not allocated")]
PciAddressMissing,
+ /// PCI Address parsing failure.
+ #[error("PCI address '{0}' could not be parsed: {1}")]
+ PciAddressParseFailure(String, PciAddressError),
+ /// PCI Address allocation failure.
+ #[error("failed to allocate PCI address")]
+ PciAllocationFailed,
+ /// Size of zero encountered
+ #[error("Size of zero detected")]
+ SizeZero,
}
+
pub type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- CapabilitiesSetup(e) => write!(f, "failed to add capability {}", e),
- #[cfg(feature = "audio")]
- CreateCrasClientFailed(e) => write!(f, "failed to create CRAS Client: {}", e),
- #[cfg(feature = "audio")]
- CreateViosClientFailed(e) => write!(f, "failed to create VioS Client: {}", e),
- IoAllocationFailed(size, e) => write!(
- f,
- "failed to allocate space for an IO BAR, size={}: {}",
- size, e
- ),
- IoRegistrationFailed(addr, e) => {
- write!(f, "failed to register an IO BAR, addr={} err={}", addr, e)
- }
- PciAllocationFailed => write!(f, "failed to allocate PCI address"),
- PciAddressMissing => write!(f, "PCI address is not allocated"),
- }
- }
+/// Pci Bar Range information
+#[derive(Clone)]
+pub struct BarRange {
+ /// pci bar start address
+ pub addr: u64,
+ /// pci bar size
+ pub size: u64,
+ /// pci bar is prefetchable or not, it used to set parent's bridge window
+ pub prefetchable: bool,
}
pub trait PciDevice: Send {
@@ -73,30 +92,31 @@ pub trait PciDevice: Send {
/// Assign a legacy PCI IRQ to this device.
/// The device may write to `irq_evt` to trigger an interrupt.
/// When `irq_resample_evt` is signaled, the device should re-assert `irq_evt` if necessary.
+ /// Optional irq_num can be used for default INTx allocation, device can overwrite it.
+ /// If legacy INTx is used, function shall return requested IRQ number and PCI INTx pin.
fn assign_irq(
&mut self,
- _irq_evt: Event,
- _irq_resample_evt: Event,
- _irq_num: u32,
- _irq_pin: PciInterruptPin,
- ) {
+ _irq_evt: &IrqLevelEvent,
+ _irq_num: Option<u32>,
+ ) -> Option<(u32, PciInterruptPin)> {
+ None
}
/// Allocates the needed IO BAR space using the `allocate` function which takes a size and
- /// returns an address. Returns a Vec of (address, length) tuples.
- fn allocate_io_bars(&mut self, _resources: &mut SystemAllocator) -> Result<Vec<(u64, u64)>> {
+ /// returns an address. Returns a Vec of BarRange{addr, size, prefetchable}.
+ fn allocate_io_bars(&mut self, _resources: &mut SystemAllocator) -> Result<Vec<BarRange>> {
Ok(Vec::new())
}
- /// Allocates the needed device BAR space. Returns a Vec of (address, length) tuples.
+ /// Allocates the needed device BAR space. Returns a Vec of BarRange{addr, size, prefetchable}.
/// Unlike MMIO BARs (see allocate_io_bars), device BARs are not expected to incur VM exits
/// - these BARs represent normal memory.
- fn allocate_device_bars(
- &mut self,
- _resources: &mut SystemAllocator,
- ) -> Result<Vec<(u64, u64)>> {
+ fn allocate_device_bars(&mut self, _resources: &mut SystemAllocator) -> Result<Vec<BarRange>> {
Ok(Vec::new())
}
+ /// Returns the configuration of a base address register, if present.
+ fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration>;
+
/// Register any capabilties specified by the device.
fn register_device_capabilities(&mut self) -> Result<()> {
Ok(())
@@ -118,6 +138,17 @@ pub trait PciDevice: Send {
/// * `data` - The data to write.
fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]);
+ /// Reads from a virtual config register.
+ /// * `reg_idx` - virtual config register index (in units of 4 bytes).
+ fn read_virtual_config_register(&self, _reg_idx: usize) -> u32 {
+ 0
+ }
+
+ /// Writes to a virtual config register.
+ /// * `reg_idx` - virtual config register index (in units of 4 bytes).
+ /// * `value` - the value to be written.
+ fn write_virtual_config_register(&mut self, _reg_idx: usize, _value: u32) {}
+
/// Reads from a BAR region mapped in to the device.
/// * `addr` - The guest address inside the BAR.
/// * `data` - Filled with the data from `addr`.
@@ -128,6 +159,38 @@ pub trait PciDevice: Send {
fn write_bar(&mut self, addr: u64, data: &[u8]);
/// Invoked when the device is sandboxed.
fn on_device_sandboxed(&mut self) {}
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ fn generate_acpi(&mut self, sdts: Vec<SDT>) -> Option<Vec<SDT>> {
+ Some(sdts)
+ }
+
+ /// Invoked when the device is destroyed
+ fn destroy_device(&mut self) {}
+
+ /// Get the removed children devices under pci bridge
+ fn get_removed_children_devices(&self) -> Vec<PciAddress> {
+ Vec::new()
+ }
+
+ /// if device is a pci brdige, configure pci bridge window
+ fn configure_bridge_window(
+ &mut self,
+ _resources: &mut SystemAllocator,
+ _bar_ranges: &[BarRange],
+ ) -> Result<()> {
+ Ok(())
+ }
+
+ /// Indicates whether the device supports IOMMU
+ fn supports_iommu(&self) -> bool {
+ false
+ }
+
+ /// Sets the IOMMU for the device if `supports_iommu()`
+ fn set_iommu(&mut self, _iommu: IpcMemoryMapper) -> anyhow::Result<()> {
+ bail!("Iommu not supported.");
+ }
}
impl<T: PciDevice> BusDevice for T {
@@ -135,6 +198,11 @@ impl<T: PciDevice> BusDevice for T {
PciDevice::debug_label(self)
}
+ fn device_id(&self) -> u32 {
+ // Use the PCI ID for PCI devices, which contains the PCI vendor ID and the PCI device ID
+ PciDevice::read_config_register(self, PCI_ID_REG)
+ }
+
fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
self.read_bar(info.address, data)
}
@@ -158,19 +226,86 @@ impl<T: PciDevice> BusDevice for T {
if reg_idx == COMMAND_REG {
let old_command_reg = self.read_config_register(COMMAND_REG);
+ let old_ranges = self.get_ranges();
self.write_config_register(reg_idx, offset, data);
let new_command_reg = self.read_config_register(COMMAND_REG);
+ let new_ranges = self.get_ranges();
// Inform the caller of state changes.
if (old_command_reg ^ new_command_reg) & COMMAND_REG_MEMORY_SPACE_MASK != 0 {
- result.mem_bus_new_state =
- Some((new_command_reg & COMMAND_REG_MEMORY_SPACE_MASK) != 0);
+ // Enable memory, add new_mmio into mmio_bus
+ if new_command_reg & COMMAND_REG_MEMORY_SPACE_MASK != 0 {
+ for (range, bus_type) in new_ranges.iter() {
+ if *bus_type == BusType::Mmio && range.base != 0 {
+ result.mmio_add.push(*range);
+ }
+ }
+ } else {
+ // Disable memory, remove old_mmio from mmio_bus
+ for (range, bus_type) in old_ranges.iter() {
+ if *bus_type == BusType::Mmio && range.base != 0 {
+ result.mmio_remove.push(*range);
+ }
+ }
+ }
}
if (old_command_reg ^ new_command_reg) & COMMAND_REG_IO_SPACE_MASK != 0 {
- result.io_bus_new_state = Some((new_command_reg & COMMAND_REG_IO_SPACE_MASK) != 0);
+ // Enable IO, add new_io into io_bus
+ if new_command_reg & COMMAND_REG_IO_SPACE_MASK != 0 {
+ for (range, bus_type) in new_ranges.iter() {
+ if *bus_type == BusType::Io && range.base != 0 {
+ result.io_add.push(*range);
+ }
+ }
+ } else {
+ // Disable IO, remove old_io from io_bus
+ for (range, bus_type) in old_ranges.iter() {
+ if *bus_type == BusType::Io && range.base != 0 {
+ result.io_remove.push(*range);
+ }
+ }
+ }
+ }
+ } else if (BAR0_REG..=BAR0_REG + 5).contains(&reg_idx) || reg_idx == ROM_BAR_REG {
+ let old_ranges = self.get_ranges();
+ self.write_config_register(reg_idx, offset, data);
+ let new_ranges = self.get_ranges();
+
+ for ((old_range, old_type), (new_range, new_type)) in
+ old_ranges.iter().zip(new_ranges.iter())
+ {
+ if *old_type != *new_type {
+ error!(
+ "{}: bar {:x} type changed after a bar write",
+ self.debug_label(),
+ reg_idx
+ );
+ continue;
+ }
+ if old_range.base != new_range.base {
+ if *new_type == BusType::Mmio {
+ if old_range.base != 0 {
+ result.mmio_remove.push(*old_range);
+ }
+ if new_range.base != 0 {
+ result.mmio_add.push(*new_range);
+ }
+ } else {
+ if old_range.base != 0 {
+ result.io_remove.push(*old_range);
+ }
+ if new_range.base != 0 {
+ result.io_add.push(*new_range);
+ }
+ }
+ }
}
} else {
self.write_config_register(reg_idx, offset, data);
+ let children_pci_addr = self.get_removed_children_devices();
+ if !children_pci_addr.is_empty() {
+ result.removed_pci_devices = children_pci_addr;
+ }
}
result
@@ -180,9 +315,43 @@ impl<T: PciDevice> BusDevice for T {
self.read_config_register(reg_idx)
}
+ fn virtual_config_register_write(&mut self, reg_idx: usize, value: u32) {
+ self.write_virtual_config_register(reg_idx, value);
+ }
+
+ fn virtual_config_register_read(&self, reg_idx: usize) -> u32 {
+ self.read_virtual_config_register(reg_idx)
+ }
+
fn on_sandboxed(&mut self) {
self.on_device_sandboxed();
}
+
+ fn get_ranges(&self) -> Vec<(BusRange, BusType)> {
+ let mut ranges = Vec::new();
+ for bar_num in 0..NUM_BAR_REGS {
+ if let Some(bar) = self.get_bar_configuration(bar_num) {
+ let bus_type = if bar.is_memory() {
+ BusType::Mmio
+ } else {
+ BusType::Io
+ };
+ ranges.push((
+ BusRange {
+ base: bar.address(),
+ len: bar.size(),
+ },
+ bus_type,
+ ));
+ }
+ }
+ ranges
+ }
+
+ // Invoked when the device is destroyed
+ fn destroy_device(&mut self) {
+ self.destroy_device()
+ }
}
impl<T: PciDevice + ?Sized> PciDevice for Box<T> {
@@ -198,25 +367,32 @@ impl<T: PciDevice + ?Sized> PciDevice for Box<T> {
}
fn assign_irq(
&mut self,
- irq_evt: Event,
- irq_resample_evt: Event,
- irq_num: u32,
- irq_pin: PciInterruptPin,
- ) {
- (**self).assign_irq(irq_evt, irq_resample_evt, irq_num, irq_pin)
- }
- fn allocate_io_bars(&mut self, resources: &mut SystemAllocator) -> Result<Vec<(u64, u64)>> {
+ irq_evt: &IrqLevelEvent,
+ irq_num: Option<u32>,
+ ) -> Option<(u32, PciInterruptPin)> {
+ (**self).assign_irq(irq_evt, irq_num)
+ }
+ fn allocate_io_bars(&mut self, resources: &mut SystemAllocator) -> Result<Vec<BarRange>> {
(**self).allocate_io_bars(resources)
}
- fn allocate_device_bars(&mut self, resources: &mut SystemAllocator) -> Result<Vec<(u64, u64)>> {
+ fn allocate_device_bars(&mut self, resources: &mut SystemAllocator) -> Result<Vec<BarRange>> {
(**self).allocate_device_bars(resources)
}
+ fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
+ (**self).get_bar_configuration(bar_num)
+ }
fn register_device_capabilities(&mut self) -> Result<()> {
(**self).register_device_capabilities()
}
fn ioevents(&self) -> Vec<(&Event, u64, Datamatch)> {
(**self).ioevents()
}
+ fn read_virtual_config_register(&self, reg_idx: usize) -> u32 {
+ (**self).read_virtual_config_register(reg_idx)
+ }
+ fn write_virtual_config_register(&mut self, reg_idx: usize, value: u32) {
+ (**self).write_virtual_config_register(reg_idx, value)
+ }
fn read_config_register(&self, reg_idx: usize) -> u32 {
(**self).read_config_register(reg_idx)
}
@@ -233,12 +409,52 @@ impl<T: PciDevice + ?Sized> PciDevice for Box<T> {
fn on_device_sandboxed(&mut self) {
(**self).on_device_sandboxed()
}
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ fn generate_acpi(&mut self, sdts: Vec<SDT>) -> Option<Vec<SDT>> {
+ (**self).generate_acpi(sdts)
+ }
+
+ fn destroy_device(&mut self) {
+ (**self).destroy_device();
+ }
+ fn get_removed_children_devices(&self) -> Vec<PciAddress> {
+ (**self).get_removed_children_devices()
+ }
+
+ fn configure_bridge_window(
+ &mut self,
+ resources: &mut SystemAllocator,
+ bar_ranges: &[BarRange],
+ ) -> Result<()> {
+ (**self).configure_bridge_window(resources, bar_ranges)
+ }
+}
+
+impl<T: 'static + PciDevice> BusDeviceObj for T {
+ fn as_pci_device(&self) -> Option<&dyn PciDevice> {
+ Some(self)
+ }
+ fn as_pci_device_mut(&mut self) -> Option<&mut dyn PciDevice> {
+ Some(self)
+ }
+ fn into_pci_device(self: Box<Self>) -> Option<Box<dyn PciDevice>> {
+ Some(self)
+ }
}
#[cfg(test)]
mod tests {
use super::*;
- use pci_configuration::{PciClassCode, PciConfiguration, PciHeaderType, PciMultimediaSubclass};
+ use pci_configuration::{
+ PciBarPrefetchable, PciBarRegionType, PciClassCode, PciConfiguration, PciHeaderType,
+ PciMultimediaSubclass,
+ };
+
+ const BAR0_SIZE: u64 = 0x1000;
+ const BAR2_SIZE: u64 = 0x20;
+ const BAR0_ADDR: u64 = 0xc0000000;
+ const BAR2_ADDR: u64 = 0x800;
struct TestDev {
pub config_regs: PciConfiguration,
@@ -265,9 +481,13 @@ mod tests {
fn write_bar(&mut self, _addr: u64, _data: &[u8]) {}
- fn allocate_address(&mut self, resources: &mut SystemAllocator) -> Result<PciAddress> {
+ fn allocate_address(&mut self, _resources: &mut SystemAllocator) -> Result<PciAddress> {
Err(Error::PciAllocationFailed)
}
+
+ fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
+ self.config_regs.get_bar_configuration(bar_num)
+ }
}
#[test]
@@ -286,6 +506,33 @@ mod tests {
),
};
+ let _ = test_dev.config_regs.add_pci_bar(
+ PciBarConfiguration::new(
+ 0,
+ BAR0_SIZE,
+ PciBarRegionType::Memory64BitRegion,
+ PciBarPrefetchable::Prefetchable,
+ )
+ .set_address(BAR0_ADDR),
+ );
+ let _ = test_dev.config_regs.add_pci_bar(
+ PciBarConfiguration::new(
+ 2,
+ BAR2_SIZE,
+ PciBarRegionType::IoRegion,
+ PciBarPrefetchable::NotPrefetchable,
+ )
+ .set_address(BAR2_ADDR),
+ );
+ let bar0_range = BusRange {
+ base: BAR0_ADDR,
+ len: BAR0_SIZE,
+ };
+ let bar2_range = BusRange {
+ base: BAR2_ADDR,
+ len: BAR2_SIZE,
+ };
+
// Initialize command register to an all-zeroes value.
test_dev.config_register_write(COMMAND_REG, 0, &0u32.to_le_bytes());
@@ -293,8 +540,11 @@ mod tests {
assert_eq!(
test_dev.config_register_write(COMMAND_REG, 0, &1u32.to_le_bytes()),
ConfigWriteResult {
- mem_bus_new_state: None,
- io_bus_new_state: Some(true),
+ mmio_remove: Vec::new(),
+ mmio_add: Vec::new(),
+ io_remove: Vec::new(),
+ io_add: vec![bar2_range],
+ removed_pci_devices: Vec::new(),
}
);
@@ -302,8 +552,11 @@ mod tests {
assert_eq!(
test_dev.config_register_write(COMMAND_REG, 0, &3u32.to_le_bytes()),
ConfigWriteResult {
- mem_bus_new_state: Some(true),
- io_bus_new_state: None,
+ mmio_remove: Vec::new(),
+ mmio_add: vec![bar0_range],
+ io_remove: Vec::new(),
+ io_add: Vec::new(),
+ removed_pci_devices: Vec::new(),
}
);
@@ -311,8 +564,11 @@ mod tests {
assert_eq!(
test_dev.config_register_write(COMMAND_REG, 0, &3u32.to_le_bytes()),
ConfigWriteResult {
- mem_bus_new_state: None,
- io_bus_new_state: None,
+ mmio_remove: Vec::new(),
+ mmio_add: Vec::new(),
+ io_remove: Vec::new(),
+ io_add: Vec::new(),
+ removed_pci_devices: Vec::new(),
}
);
@@ -320,17 +576,52 @@ mod tests {
assert_eq!(
test_dev.config_register_write(COMMAND_REG, 0, &2u32.to_le_bytes()),
ConfigWriteResult {
- mem_bus_new_state: None,
- io_bus_new_state: Some(false),
+ mmio_remove: Vec::new(),
+ mmio_add: Vec::new(),
+ io_remove: vec![bar2_range],
+ io_add: Vec::new(),
+ removed_pci_devices: Vec::new(),
}
);
- // Re-enable IO space and disable mem simultaneously.
+ // Disable mem space access.
assert_eq!(
- test_dev.config_register_write(COMMAND_REG, 0, &1u32.to_le_bytes()),
+ test_dev.config_register_write(COMMAND_REG, 0, &0u32.to_le_bytes()),
+ ConfigWriteResult {
+ mmio_remove: vec![bar0_range],
+ mmio_add: Vec::new(),
+ io_remove: Vec::new(),
+ io_add: Vec::new(),
+ removed_pci_devices: Vec::new(),
+ }
+ );
+
+ assert_eq!(test_dev.get_ranges(), Vec::new());
+
+ // Re-enable mem and IO space.
+ assert_eq!(
+ test_dev.config_register_write(COMMAND_REG, 0, &3u32.to_le_bytes()),
+ ConfigWriteResult {
+ mmio_remove: Vec::new(),
+ mmio_add: vec![bar0_range],
+ io_remove: Vec::new(),
+ io_add: vec![bar2_range],
+ removed_pci_devices: Vec::new(),
+ }
+ );
+
+ // Change Bar0's address
+ assert_eq!(
+ test_dev.config_register_write(BAR0_REG, 0, &0xD0000000u32.to_le_bytes()),
ConfigWriteResult {
- mem_bus_new_state: Some(false),
- io_bus_new_state: Some(true),
+ mmio_remove: vec!(bar0_range),
+ mmio_add: vec![BusRange {
+ base: 0xD0000000,
+ len: BAR0_SIZE
+ }],
+ io_remove: Vec::new(),
+ io_add: Vec::new(),
+ removed_pci_devices: Vec::new(),
}
);
}
diff --git a/devices/src/pci/pci_root.rs b/devices/src/pci/pci_root.rs
index 4eb1b3407..517964e62 100644
--- a/devices/src/pci/pci_root.rs
+++ b/devices/src/pci/pci_root.rs
@@ -4,17 +4,19 @@
use std::collections::BTreeMap;
use std::convert::TryInto;
-use std::fmt::{self, Display};
-use std::sync::Arc;
+use std::ops::Bound::Included;
+use std::sync::{Arc, Weak};
-use base::RawDescriptor;
+use base::{error, Event, RawDescriptor};
use sync::Mutex;
use crate::pci::pci_configuration::{
- PciBridgeSubclass, PciClassCode, PciConfiguration, PciHeaderType,
+ PciBarConfiguration, PciBridgeSubclass, PciClassCode, PciConfiguration, PciHeaderType,
+ HEADER_TYPE_MULTIFUNCTION_MASK, HEADER_TYPE_REG,
};
use crate::pci::pci_device::{Error, PciDevice};
-use crate::{BusAccessInfo, BusDevice};
+use crate::pci::{PciAddress, PciId, PCI_VENDOR_ID_INTEL};
+use crate::{Bus, BusAccessInfo, BusDevice, BusType};
use resources::SystemAllocator;
// A PciDevice that holds the root hub's configuration.
@@ -48,93 +50,36 @@ impl PciDevice for PciRootConfiguration {
fn read_bar(&mut self, _addr: u64, _data: &mut [u8]) {}
fn write_bar(&mut self, _addr: u64, _data: &[u8]) {}
-}
-
-/// PCI Device Address, AKA Bus:Device.Function
-#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
-pub struct PciAddress {
- pub bus: u8,
- pub dev: u8, /* u5 */
- pub func: u8, /* u3 */
-}
-impl Display for PciAddress {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "{:04x}:{:02x}.{:0x}", self.bus, self.dev, self.func)
- }
-}
-
-impl PciAddress {
- const BUS_OFFSET: usize = 16;
- const BUS_MASK: u32 = 0x00ff;
- const DEVICE_OFFSET: usize = 11;
- const DEVICE_MASK: u32 = 0x1f;
- const FUNCTION_OFFSET: usize = 8;
- const FUNCTION_MASK: u32 = 0x07;
- const REGISTER_OFFSET: usize = 2;
- const REGISTER_MASK: u32 = 0x3f;
-
- /// Construct PciAddress and register tuple from CONFIG_ADDRESS value.
- pub fn from_config_address(config_address: u32) -> (Self, usize) {
- let bus = ((config_address >> Self::BUS_OFFSET) & Self::BUS_MASK) as u8;
- let dev = ((config_address >> Self::DEVICE_OFFSET) & Self::DEVICE_MASK) as u8;
- let func = ((config_address >> Self::FUNCTION_OFFSET) & Self::FUNCTION_MASK) as u8;
- let register = ((config_address >> Self::REGISTER_OFFSET) & Self::REGISTER_MASK) as usize;
-
- (PciAddress { bus, dev, func }, register)
- }
-
- /// Construct PciAddress from string domain:bus:device.function.
- pub fn from_string(address: &str) -> Self {
- let mut func_dev_bus_domain = address
- .split(|c| c == ':' || c == '.')
- .map(|v| u8::from_str_radix(v, 16).unwrap_or_default())
- .rev()
- .collect::<Vec<u8>>();
- func_dev_bus_domain.resize(4, 0);
- PciAddress {
- bus: func_dev_bus_domain[2],
- dev: func_dev_bus_domain[1],
- func: func_dev_bus_domain[0],
- }
- }
-
- /// Encode PciAddress into CONFIG_ADDRESS value.
- pub fn to_config_address(&self, register: usize) -> u32 {
- ((Self::BUS_MASK & self.bus as u32) << Self::BUS_OFFSET)
- | ((Self::DEVICE_MASK & self.dev as u32) << Self::DEVICE_OFFSET)
- | ((Self::FUNCTION_MASK & self.func as u32) << Self::FUNCTION_OFFSET)
- | ((Self::REGISTER_MASK & register as u32) << Self::REGISTER_OFFSET)
- }
-
- /// Returns true if the address points to PCI root host-bridge.
- fn is_root(&self) -> bool {
- matches!(
- &self,
- PciAddress {
- bus: 0,
- dev: 0,
- func: 0
- }
- )
+ fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
+ self.config.get_bar_configuration(bar_num)
}
}
/// Emulates the PCI Root bridge.
+#[allow(dead_code)] // TODO(b/174705596): remove once mmio_bus and io_bus are used
pub struct PciRoot {
+ /// Memory (MMIO) bus.
+ mmio_bus: Weak<Bus>,
+ /// IO bus (x86 only - for non-x86 platforms, this is just an empty Bus).
+ io_bus: Weak<Bus>,
/// Bus configuration for the root device.
root_configuration: PciRootConfiguration,
/// Devices attached to this bridge.
devices: BTreeMap<PciAddress, Arc<Mutex<dyn BusDevice>>>,
+ /// pcie enhanced configuration access mmio base
+ pcie_cfg_mmio: Option<u64>,
}
-const PCI_VENDOR_ID_INTEL: u16 = 0x8086;
const PCI_DEVICE_ID_INTEL_82441: u16 = 0x1237;
+const PCIE_XBAR_BASE_ADDR: usize = 24;
impl PciRoot {
/// Create an empty PCI root bus.
- pub fn new() -> Self {
+ pub fn new(mmio_bus: Weak<Bus>, io_bus: Weak<Bus>) -> Self {
PciRoot {
+ mmio_bus,
+ io_bus,
root_configuration: PciRootConfiguration {
config: PciConfiguration::new(
PCI_VENDOR_ID_INTEL,
@@ -149,9 +94,15 @@ impl PciRoot {
),
},
devices: BTreeMap::new(),
+ pcie_cfg_mmio: None,
}
}
+ /// enable pcie enhanced configuration access and set base mmio
+ pub fn enable_pcie_cfg_mmio(&mut self, pcie_cfg_mmio: u64) {
+ self.pcie_cfg_mmio = Some(pcie_cfg_mmio);
+ }
+
/// Add a `device` to this root PCI bus.
pub fn add_device(&mut self, address: PciAddress, device: Arc<Mutex<dyn BusDevice>>) {
// Ignore attempt to replace PCI Root host bridge.
@@ -160,13 +111,67 @@ impl PciRoot {
}
}
+ fn remove_device(&mut self, address: PciAddress) {
+ if let Some(d) = self.devices.remove(&address) {
+ for (range, bus_type) in d.lock().get_ranges() {
+ let bus_ptr = if bus_type == BusType::Mmio {
+ match self.mmio_bus.upgrade() {
+ Some(m) => m,
+ None => continue,
+ }
+ } else {
+ match self.io_bus.upgrade() {
+ Some(i) => i,
+ None => continue,
+ }
+ };
+ let _ = bus_ptr.remove(range.base, range.len);
+ }
+ d.lock().destroy_device();
+ }
+ }
+
pub fn config_space_read(&self, address: PciAddress, register: usize) -> u32 {
if address.is_root() {
- self.root_configuration.config_register_read(register)
+ if register == PCIE_XBAR_BASE_ADDR && self.pcie_cfg_mmio.is_some() {
+ let pcie_mmio = self.pcie_cfg_mmio.unwrap() as u32;
+ pcie_mmio | 0x1
+ } else if register == (PCIE_XBAR_BASE_ADDR + 1) && self.pcie_cfg_mmio.is_some() {
+ (self.pcie_cfg_mmio.unwrap() >> 32) as u32
+ } else {
+ self.root_configuration.config_register_read(register)
+ }
} else {
- self.devices
+ let mut data = self
+ .devices
.get(&address)
- .map_or(0xffff_ffff, |d| d.lock().config_register_read(register))
+ .map_or(0xffff_ffff, |d| d.lock().config_register_read(register));
+
+ if register == HEADER_TYPE_REG {
+ // Set multifunction bit in header type if there are devices at non-zero functions
+ // in this slot.
+ if self
+ .devices
+ .range((
+ Included(&PciAddress {
+ bus: address.bus,
+ dev: address.dev,
+ func: 1,
+ }),
+ Included(&PciAddress {
+ bus: address.bus,
+ dev: address.dev,
+ func: 7,
+ }),
+ ))
+ .next()
+ .is_some()
+ {
+ data |= HEADER_TYPE_MULTIFUNCTION_MASK;
+ }
+ }
+
+ data
}
}
@@ -184,7 +189,55 @@ impl PciRoot {
self.root_configuration
.config_register_write(register, offset, data);
} else if let Some(d) = self.devices.get(&address) {
- d.lock().config_register_write(register, offset, data);
+ let res = d.lock().config_register_write(register, offset, data);
+
+ if !res.mmio_add.is_empty() || !res.mmio_remove.is_empty() {
+ let mmio_bus = match self.mmio_bus.upgrade() {
+ Some(m) => m,
+ None => return,
+ };
+ for range in &res.mmio_remove {
+ let _ = mmio_bus.remove(range.base, range.len);
+ }
+ for range in &res.mmio_add {
+ let _ = mmio_bus.insert(d.clone(), range.base, range.len);
+ }
+ }
+
+ if !res.io_add.is_empty() || !res.io_remove.is_empty() {
+ let io_bus = match self.io_bus.upgrade() {
+ Some(i) => i,
+ None => return,
+ };
+ for range in &res.io_remove {
+ let _ = io_bus.remove(range.base, range.len);
+ }
+ for range in &res.io_add {
+ let _ = io_bus.insert(d.clone(), range.base, range.len);
+ }
+ }
+
+ for remove_pci_device in res.removed_pci_devices.iter() {
+ self.remove_device(*remove_pci_device);
+ }
+ }
+ }
+
+ pub fn virtual_config_space_read(&self, address: PciAddress, register: usize) -> u32 {
+ if address.is_root() {
+ 0u32
+ } else {
+ self.devices
+ .get(&address)
+ .map_or(0u32, |d| d.lock().virtual_config_register_read(register))
+ }
+ }
+
+ pub fn virtual_config_space_write(&mut self, address: PciAddress, register: usize, value: u32) {
+ if !address.is_root() {
+ if let Some(d) = self.devices.get(&address) {
+ d.lock().virtual_config_register_write(register, value);
+ }
}
}
}
@@ -192,16 +245,21 @@ impl PciRoot {
/// Emulates PCI configuration access mechanism #1 (I/O ports 0xcf8 and 0xcfc).
pub struct PciConfigIo {
/// PCI root bridge.
- pci_root: PciRoot,
+ pci_root: Arc<Mutex<PciRoot>>,
/// Current address to read/write from (0xcf8 register, litte endian).
config_address: u32,
+ /// Event to signal that the quest requested reset via writing to 0xcf9 register.
+ reset_evt: Event,
}
impl PciConfigIo {
- pub fn new(pci_root: PciRoot) -> Self {
+ const REGISTER_BITS_NUM: usize = 8;
+
+ pub fn new(pci_root: Arc<Mutex<PciRoot>>, reset_evt: Event) -> Self {
PciConfigIo {
pci_root,
config_address: 0,
+ reset_evt,
}
}
@@ -211,8 +269,9 @@ impl PciConfigIo {
return 0xffff_ffff;
}
- let (address, register) = PciAddress::from_config_address(self.config_address);
- self.pci_root.config_space_read(address, register)
+ let (address, register) =
+ PciAddress::from_config_address(self.config_address, Self::REGISTER_BITS_NUM);
+ self.pci_root.lock().config_space_read(address, register)
}
fn config_space_write(&mut self, offset: u64, data: &[u8]) {
@@ -221,8 +280,10 @@ impl PciConfigIo {
return;
}
- let (address, register) = PciAddress::from_config_address(self.config_address);
+ let (address, register) =
+ PciAddress::from_config_address(self.config_address, Self::REGISTER_BITS_NUM);
self.pci_root
+ .lock()
.config_space_write(address, register, offset, data)
}
@@ -246,11 +307,17 @@ impl PciConfigIo {
}
}
+const PCI_RESET_CPU_BIT: u8 = 1 << 2;
+
impl BusDevice for PciConfigIo {
fn debug_label(&self) -> String {
format!("pci config io-port 0x{:03x}", self.config_address)
}
+ fn device_id(&self) -> u32 {
+ PciId::new(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82441).into()
+ }
+
fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
// `offset` is relative to 0xcf8
let value = match info.offset {
@@ -276,6 +343,11 @@ impl BusDevice for PciConfigIo {
fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
// `offset` is relative to 0xcf8
match info.offset {
+ _o @ 1 if data.len() == 1 && data[0] & PCI_RESET_CPU_BIT != 0 => {
+ if let Err(e) = self.reset_evt.write(1) {
+ error!("failed to trigger PCI 0xcf9 reset event: {}", e);
+ }
+ }
o @ 0..=3 => self.set_config_address(o, data),
o @ 4..=7 => self.config_space_write(o - 4, data),
_ => (),
@@ -286,22 +358,30 @@ impl BusDevice for PciConfigIo {
/// Emulates PCI memory-mapped configuration access mechanism.
pub struct PciConfigMmio {
/// PCI root bridge.
- pci_root: PciRoot,
+ pci_root: Arc<Mutex<PciRoot>>,
+ /// Register bit number in config address.
+ register_bit_num: usize,
}
impl PciConfigMmio {
- pub fn new(pci_root: PciRoot) -> Self {
- PciConfigMmio { pci_root }
+ pub fn new(pci_root: Arc<Mutex<PciRoot>>, register_bit_num: usize) -> Self {
+ PciConfigMmio {
+ pci_root,
+ register_bit_num,
+ }
}
fn config_space_read(&self, config_address: u32) -> u32 {
- let (address, register) = PciAddress::from_config_address(config_address);
- self.pci_root.config_space_read(address, register)
+ let (address, register) =
+ PciAddress::from_config_address(config_address, self.register_bit_num);
+ self.pci_root.lock().config_space_read(address, register)
}
fn config_space_write(&mut self, config_address: u32, offset: u64, data: &[u8]) {
- let (address, register) = PciAddress::from_config_address(config_address);
+ let (address, register) =
+ PciAddress::from_config_address(config_address, self.register_bit_num);
self.pci_root
+ .lock()
.config_space_write(address, register, offset, data)
}
}
@@ -311,6 +391,10 @@ impl BusDevice for PciConfigMmio {
"pci config mmio".to_owned()
}
+ fn device_id(&self) -> u32 {
+ PciId::new(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82441).into()
+ }
+
fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
// Only allow reads to the register boundary.
let start = info.offset as usize % 4;
@@ -335,3 +419,74 @@ impl BusDevice for PciConfigMmio {
self.config_space_write(info.offset as u32, info.offset % 4, data)
}
}
+
+/// Inspired by PCI configuration space, CrosVM provides 1024 dword virtual registers (4KiB in
+/// total) for each PCI device. The guest can use these registers to exchange device-specific
+/// information with CrosVM.
+/// All these virtual registers from all PCI devices locate in a contiguous memory region.
+/// The base address of this memory region is provided by an IntObj named VCFG in the ACPI DSDT.
+/// The offset of each register is calculated in the same way as PCIe ECAM;
+/// i.e. offset = (bus << 20) | (device << 15) | (function << 12) | (register_index << 2)
+pub struct PciVirtualConfigMmio {
+ /// PCI root bridge.
+ pci_root: Arc<Mutex<PciRoot>>,
+ /// Register bit number in config address.
+ register_bit_num: usize,
+}
+
+impl PciVirtualConfigMmio {
+ pub fn new(pci_root: Arc<Mutex<PciRoot>>, register_bit_num: usize) -> Self {
+ PciVirtualConfigMmio {
+ pci_root,
+ register_bit_num,
+ }
+ }
+}
+
+impl BusDevice for PciVirtualConfigMmio {
+ fn debug_label(&self) -> String {
+ "pci virtual config mmio".to_owned()
+ }
+
+ fn device_id(&self) -> u32 {
+ PciId::new(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82441).into()
+ }
+
+ fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
+ let value = if info.offset % 4 != 0 || data.len() != 4 {
+ error!(
+ "{} unexpected read at offset = {}, len = {}",
+ self.debug_label(),
+ info.offset,
+ data.len()
+ );
+ 0u32
+ } else {
+ let (address, register) =
+ PciAddress::from_config_address(info.offset as u32, self.register_bit_num);
+ self.pci_root
+ .lock()
+ .virtual_config_space_read(address, register)
+ };
+ data[0..4].copy_from_slice(&value.to_le_bytes()[..]);
+ }
+
+ fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
+ if info.offset % 4 != 0 || data.len() != 4 {
+ error!(
+ "{} unexpected write at offset = {}, len = {}",
+ self.debug_label(),
+ info.offset,
+ data.len()
+ );
+ return;
+ }
+ // Unwrap is safe as we verified length above
+ let value = u32::from_le_bytes(data.try_into().unwrap());
+ let (address, register) =
+ PciAddress::from_config_address(info.offset as u32, self.register_bit_num);
+ self.pci_root
+ .lock()
+ .virtual_config_space_write(address, register, value)
+ }
+}
diff --git a/devices/src/pci/pcie/mod.rs b/devices/src/pci/pcie/mod.rs
new file mode 100644
index 000000000..c9f479520
--- /dev/null
+++ b/devices/src/pci/pcie/mod.rs
@@ -0,0 +1,85 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod pci_bridge;
+mod pcie_device;
+mod pcie_host;
+mod pcie_rp;
+
+pub use pci_bridge::PciBridge;
+pub use pcie_host::PcieHostRootPort;
+pub use pcie_rp::PcieRootPort;
+
+#[allow(dead_code)]
+#[derive(Clone, Copy)]
+pub enum PcieDevicePortType {
+ PcieEndpoint = 0,
+ PcieLegacyEndpoint = 1,
+ RootPort = 4,
+ UpstreamPort = 5,
+ DownstreamPort = 6,
+ Pcie2PciBridge = 7,
+ Pci2PcieBridge = 8,
+ RCIntegratedEndpoint = 9,
+ RCEventCollector = 0xa,
+}
+
+const PCIE_CAP_LEN: usize = 0x3C;
+
+const PCIE_CAP_VERSION: u16 = 0x2;
+const PCIE_TYPE_SHIFT: u16 = 0x4;
+const PCIE_CAP_SLOT_SHIFT: u16 = 0x8;
+const PCIE_CAP_IRQ_NUM_SHIFT: u16 = 0x9;
+
+const PCIE_DEVCAP_RBER: u32 = 0x0000_8000;
+const PCIE_LINK_X1: u16 = 0x10;
+const PCIE_LINK_2_5GT: u16 = 0x01;
+
+const PCIE_SLTCAP_ABP: u32 = 0x01; // Attention Button Present
+const PCIE_SLTCAP_AIP: u32 = 0x08; // Attention Indicator Present
+const PCIE_SLTCAP_PIP: u32 = 0x10; // Power Indicator Present
+const PCIE_SLTCAP_HPS: u32 = 0x20; // Hot-Plug Surprise
+const PCIE_SLTCAP_HPC: u32 = 0x40; // Hot-Plug Capable
+
+const PCIE_SLTCTL_OFFSET: usize = 0x18;
+const PCIE_SLTCTL_PIC_OFF: u16 = 0x300;
+const PCIE_SLTCTL_AIC_OFF: u16 = 0xC0;
+const PCIE_SLTCTL_ABPE: u16 = 0x01;
+const PCIE_SLTCTL_PDCE: u16 = 0x08;
+const PCIE_SLTCTL_CCIE: u16 = 0x10;
+const PCIE_SLTCTL_HPIE: u16 = 0x20;
+
+const PCIE_SLTSTA_OFFSET: usize = 0x1A;
+const PCIE_SLTSTA_ABP: u16 = 0x0001;
+const PCIE_SLTSTA_PFD: u16 = 0x0002;
+const PCIE_SLTSTA_PDC: u16 = 0x0008;
+const PCIE_SLTSTA_CC: u16 = 0x0010;
+const PCIE_SLTSTA_PDS: u16 = 0x0040;
+const PCIE_SLTSTA_DLLSC: u16 = 0x0100;
+
+const PCIE_ROOTCTL_OFFSET: usize = 0x1C;
+const PCIE_ROOTCTL_PME_ENABLE: u16 = 0x08;
+
+const PCIE_ROOTSTA_OFFSET: usize = 0x20;
+const PCIE_ROOTSTA_PME_REQ_ID_MASK: u32 = 0xFFFF;
+const PCIE_ROOTSTA_PME_STATUS: u32 = 0x10000;
+const PCIE_ROOTSTA_PME_PENDING: u32 = 0x20000;
+
+const PMC_CAP_CONTROL_STATE_OFFSET: usize = 1;
+const PMC_CAP_PME_SUPPORT_D0: u16 = 0x800;
+const PMC_CAP_PME_SUPPORT_D3_HOT: u16 = 0x4000;
+const PMC_CAP_PME_SUPPORT_D3_COLD: u16 = 0x8000;
+const PMC_CAP_VERSION: u16 = 0x2;
+const PMC_PME_STATUS: u16 = 0x8000;
+const PMC_PME_ENABLE: u16 = 0x100;
+const PMC_POWER_STATE_MASK: u16 = 0x3;
+const PMC_POWER_STATE_D0: u16 = 0;
+const PMC_POWER_STATE_D3: u16 = 0x3;
+
+#[derive(PartialEq)]
+pub enum PciDevicePower {
+ D0 = 0,
+ D3 = 3,
+ Unsupported = 0xFF,
+}
diff --git a/devices/src/pci/pcie/pci_bridge.rs b/devices/src/pci/pcie/pci_bridge.rs
new file mode 100644
index 000000000..9fe194b0b
--- /dev/null
+++ b/devices/src/pci/pcie/pci_bridge.rs
@@ -0,0 +1,528 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::cmp::{max, min, Ordering};
+use std::sync::Arc;
+use sync::Mutex;
+
+use crate::pci::msix::{MsixCap, MsixConfig};
+use crate::pci::pci_configuration::{PciBridgeSubclass, PciSubclass, CLASS_REG};
+use crate::pci::{
+ BarRange, PciAddress, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciClassCode,
+ PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PCI_VENDOR_ID_INTEL,
+};
+use crate::PciInterruptPin;
+use base::{warn, AsRawDescriptors, Event, RawDescriptor, Tube};
+use hypervisor::Datamatch;
+use resources::{Alloc, MmioType, SystemAllocator};
+
+use crate::pci::pcie::pcie_device::PcieDevice;
+use crate::IrqLevelEvent;
+
+const BR_MSIX_TABLE_OFFSET: u64 = 0x0;
+const BR_MSIX_PBA_OFFSET: u64 = 0x100;
+const PCI_BRIDGE_BAR_SIZE: u64 = 0x1000;
+pub const BR_BUS_NUMBER_REG: usize = 0x6;
+pub const BR_MEM_REG: usize = 0x8;
+// bit[15:4] is memory base[31:20] and alignment to 1MB
+pub const BR_MEM_BASE_MASK: u32 = 0xFFF0;
+pub const BR_MEM_BASE_SHIFT: u32 = 16;
+// bit[31:20] is memory limit[31:20] and alignment to 1MB
+pub const BR_MEM_LIMIT_MASK: u32 = 0xFFF0_0000;
+pub const BR_PREF_MEM_LOW_REG: usize = 0x9;
+// bit[0] and bit[16] is 64bit memory flag
+pub const BR_PREF_MEM_64BIT: u32 = 0x001_0001;
+pub const BR_PREF_MEM_BASE_HIGH_REG: usize = 0xa;
+pub const BR_PREF_MEM_LIMIT_HIGH_REG: usize = 0xb;
+pub const BR_WINDOW_ALIGNMENT: u64 = 0x10_0000;
+// Kernel allocate at least 2MB mmio for each bridge memory window
+pub const BR_MEM_MINIMUM: u64 = 0x20_0000;
+
+/// Holds the bus range for a pci bridge
+///
+/// * primary - primary bus number
+/// * secondary - secondary bus number
+/// * subordinate - subordinate bus number
+#[derive(Debug, Copy, Clone)]
+pub struct PciBridgeBusRange {
+ pub primary: u8,
+ pub secondary: u8,
+ pub subordinate: u8,
+}
+
+pub struct PciBridge {
+ device: Arc<Mutex<dyn PcieDevice>>,
+ config: PciConfiguration,
+ pci_address: Option<PciAddress>,
+ bus_range: PciBridgeBusRange,
+ setting_bar: u8,
+ msix_config: Arc<Mutex<MsixConfig>>,
+ msix_cap_reg_idx: Option<usize>,
+ interrupt_evt: Option<IrqLevelEvent>,
+}
+
+impl PciBridge {
+ pub fn new(device: Arc<Mutex<dyn PcieDevice>>, msi_device_tube: Tube) -> Self {
+ let device_id = device.lock().get_device_id();
+ let msix_config = Arc::new(Mutex::new(MsixConfig::new(
+ 1,
+ msi_device_tube,
+ (PCI_VENDOR_ID_INTEL as u32) | (device_id as u32) << 16,
+ device.lock().debug_label(),
+ )));
+ let mut config = PciConfiguration::new(
+ PCI_VENDOR_ID_INTEL,
+ device_id,
+ PciClassCode::BridgeDevice,
+ &PciBridgeSubclass::PciToPciBridge,
+ None,
+ PciHeaderType::Bridge,
+ 0,
+ 0,
+ 0,
+ );
+
+ let bus_range = device
+ .lock()
+ .get_bus_range()
+ .expect("PciBridge's backend device must implement get_bus_range()");
+ let data = [
+ bus_range.primary,
+ bus_range.secondary,
+ bus_range.subordinate,
+ 0,
+ ];
+ config.write_reg(BR_BUS_NUMBER_REG, 0, &data[..]);
+
+ PciBridge {
+ device,
+ config,
+ pci_address: None,
+ bus_range,
+ setting_bar: 0,
+ msix_config,
+ msix_cap_reg_idx: None,
+ interrupt_evt: None,
+ }
+ }
+
+ pub fn is_pci_bridge(dev: &dyn PciDevice) -> bool {
+ let class_reg = dev.read_config_register(CLASS_REG);
+ class_reg >> 16
+ == ((PciClassCode::BridgeDevice.get_register_value() as u32) << 8)
+ | PciBridgeSubclass::PciToPciBridge.get_register_value() as u32
+ }
+
+ pub fn get_secondary_bus_num(dev: &dyn PciDevice) -> u8 {
+ (dev.read_config_register(BR_BUS_NUMBER_REG) >> 8) as u8
+ }
+
+ fn write_bridge_window(
+ &mut self,
+ window_base: u32,
+ window_size: u32,
+ pref_window_base: u64,
+ pref_window_size: u64,
+ ) {
+ // both window_base and window_size should be aligned to 1M
+ if window_base & (BR_WINDOW_ALIGNMENT as u32 - 1) == 0
+ && window_size != 0
+ && window_size & (BR_WINDOW_ALIGNMENT as u32 - 1) == 0
+ {
+ // the top of memory will be one less than a 1MB boundary
+ let limit = (window_base + window_size - BR_WINDOW_ALIGNMENT as u32) as u32;
+ let value = (window_base >> BR_MEM_BASE_SHIFT) | limit;
+ self.write_config_register(BR_MEM_REG, 0, &value.to_le_bytes());
+ }
+
+ // both pref_window_base and pref_window_size should be aligned to 1M
+ if pref_window_base & (BR_WINDOW_ALIGNMENT - 1) == 0
+ && pref_window_size != 0
+ && pref_window_size & (BR_WINDOW_ALIGNMENT - 1) == 0
+ {
+ // the top of memory will be one less than a 1MB boundary
+ let limit = pref_window_base + pref_window_size - BR_WINDOW_ALIGNMENT;
+ let low_value = ((pref_window_base as u32) >> BR_MEM_BASE_SHIFT)
+ | (limit as u32)
+ | BR_PREF_MEM_64BIT;
+ self.write_config_register(BR_PREF_MEM_LOW_REG, 0, &low_value.to_le_bytes());
+ let high_base_value = (pref_window_base >> 32) as u32;
+ self.write_config_register(
+ BR_PREF_MEM_BASE_HIGH_REG,
+ 0,
+ &high_base_value.to_le_bytes(),
+ );
+ let high_top_value = (limit >> 32) as u32;
+ self.write_config_register(
+ BR_PREF_MEM_LIMIT_HIGH_REG,
+ 0,
+ &high_top_value.to_le_bytes(),
+ );
+ }
+ }
+
+ pub fn get_secondary_num(&self) -> u8 {
+ self.bus_range.secondary
+ }
+
+ pub fn get_subordinate_num(&self) -> u8 {
+ self.bus_range.subordinate
+ }
+}
+
+impl PciDevice for PciBridge {
+ fn debug_label(&self) -> String {
+ self.device.lock().debug_label()
+ }
+
+ fn allocate_address(
+ &mut self,
+ resources: &mut SystemAllocator,
+ ) -> std::result::Result<PciAddress, PciDeviceError> {
+ let address = self.device.lock().allocate_address(resources)?;
+ self.pci_address = Some(address);
+ Ok(address)
+ }
+
+ fn keep_rds(&self) -> Vec<RawDescriptor> {
+ let mut rds = Vec::new();
+ if let Some(interrupt_evt) = &self.interrupt_evt {
+ rds.extend(interrupt_evt.as_raw_descriptors());
+ }
+ let descriptor = self.msix_config.lock().get_msi_socket();
+ rds.push(descriptor);
+ rds
+ }
+
+ fn assign_irq(
+ &mut self,
+ irq_evt: &IrqLevelEvent,
+ irq_num: Option<u32>,
+ ) -> Option<(u32, PciInterruptPin)> {
+ self.interrupt_evt = Some(irq_evt.try_clone().ok()?);
+ let msix_config_clone = self.msix_config.clone();
+ self.device.lock().clone_interrupt(msix_config_clone);
+
+ let gsi = irq_num?;
+ let pin = self.pci_address.map_or(
+ PciInterruptPin::IntA,
+ PciConfiguration::suggested_interrupt_pin,
+ );
+ self.config.set_irq(gsi as u8, pin);
+
+ Some((gsi, pin))
+ }
+
+ fn allocate_io_bars(
+ &mut self,
+ resources: &mut SystemAllocator,
+ ) -> std::result::Result<Vec<BarRange>, PciDeviceError> {
+ let address = self
+ .pci_address
+ .expect("allocate_address must be called prior to allocate_io_bars");
+ // Pci bridge need one bar for msix
+ let mut ranges: Vec<BarRange> = Vec::new();
+ let bar_addr = resources
+ .mmio_allocator(MmioType::Low)
+ .allocate_with_align(
+ PCI_BRIDGE_BAR_SIZE,
+ Alloc::PciBar {
+ bus: address.bus,
+ dev: address.dev,
+ func: address.func,
+ bar: 0,
+ },
+ "pcie_rootport_bar".to_string(),
+ PCI_BRIDGE_BAR_SIZE,
+ )
+ .map_err(|e| PciDeviceError::IoAllocationFailed(PCI_BRIDGE_BAR_SIZE, e))?;
+ let config = PciBarConfiguration::new(
+ 0,
+ PCI_BRIDGE_BAR_SIZE,
+ PciBarRegionType::Memory32BitRegion,
+ PciBarPrefetchable::NotPrefetchable,
+ )
+ .set_address(bar_addr);
+ self.setting_bar =
+ self.config
+ .add_pci_bar(config)
+ .map_err(|e| PciDeviceError::IoRegistrationFailed(bar_addr, e))? as u8;
+ ranges.push(BarRange {
+ addr: bar_addr,
+ size: PCI_BRIDGE_BAR_SIZE,
+ prefetchable: false,
+ });
+
+ let msix_cap = MsixCap::new(
+ self.setting_bar,
+ self.msix_config.lock().num_vectors(),
+ BR_MSIX_TABLE_OFFSET as u32,
+ self.setting_bar,
+ BR_MSIX_PBA_OFFSET as u32,
+ );
+ let msix_cap_reg = self
+ .config
+ .add_capability(&msix_cap)
+ .map_err(PciDeviceError::CapabilitiesSetup)?;
+ self.msix_cap_reg_idx = Some(msix_cap_reg / 4);
+
+ Ok(ranges)
+ }
+
+ fn allocate_device_bars(
+ &mut self,
+ _resources: &mut SystemAllocator,
+ ) -> std::result::Result<Vec<BarRange>, PciDeviceError> {
+ Ok(Vec::new())
+ }
+
+ fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
+ self.config.get_bar_configuration(bar_num)
+ }
+
+ fn register_device_capabilities(&mut self) -> std::result::Result<(), PciDeviceError> {
+ let caps = self.device.lock().get_caps();
+ for cap in caps {
+ let cap_reg = self
+ .config
+ .add_capability(&*cap)
+ .map_err(PciDeviceError::CapabilitiesSetup)?;
+
+ self.device
+ .lock()
+ .set_capability_reg_idx(cap.id(), cap_reg / 4);
+ }
+
+ Ok(())
+ }
+
+ fn ioevents(&self) -> Vec<(&Event, u64, Datamatch)> {
+ Vec::new()
+ }
+
+ fn read_config_register(&self, reg_idx: usize) -> u32 {
+ let mut data: u32 = self.config.read_reg(reg_idx);
+ if let Some(msix_cap_reg_idx) = self.msix_cap_reg_idx {
+ if msix_cap_reg_idx == reg_idx {
+ data = self.msix_config.lock().read_msix_capability(data);
+ return data;
+ }
+ }
+
+ self.device.lock().read_config(reg_idx, &mut data);
+ data
+ }
+
+ fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
+ if let Some(msix_cap_reg_idx) = self.msix_cap_reg_idx {
+ if msix_cap_reg_idx == reg_idx {
+ self.msix_config.lock().write_msix_capability(offset, data);
+ }
+ }
+
+ // Suppose kernel won't modify primary/secondary/subordinate bus number,
+ // if it indeed modify, print a warning
+ if reg_idx == BR_BUS_NUMBER_REG {
+ let len = data.len();
+ if offset == 0 && len == 1 && data[0] != self.bus_range.primary {
+ warn!(
+ "kernel modify primary bus number: {} -> {}",
+ self.bus_range.primary, data[0]
+ );
+ } else if offset == 0 && len == 2 {
+ if data[0] != self.bus_range.primary {
+ warn!(
+ "kernel modify primary bus number: {} -> {}",
+ self.bus_range.primary, data[0]
+ );
+ }
+ if data[1] != self.bus_range.secondary {
+ warn!(
+ "kernel modify secondary bus number: {} -> {}",
+ self.bus_range.secondary, data[1]
+ );
+ }
+ } else if offset == 1 && len == 1 && data[0] != self.bus_range.secondary {
+ warn!(
+ "kernel modify secondary bus number: {} -> {}",
+ self.bus_range.secondary, data[0]
+ );
+ } else if offset == 2 && len == 1 && data[0] != self.bus_range.subordinate {
+ warn!(
+ "kernel modify subordinate bus number: {} -> {}",
+ self.bus_range.subordinate, data[0]
+ );
+ }
+ }
+
+ self.device.lock().write_config(reg_idx, offset, data);
+
+ (&mut self.config).write_reg(reg_idx, offset, data)
+ }
+
+ fn read_bar(&mut self, addr: u64, data: &mut [u8]) {
+ // The driver is only allowed to do aligned, properly sized access.
+ let bar0 = self.config.get_bar_addr(self.setting_bar as usize);
+ let offset = addr - bar0;
+ match offset.cmp(&BR_MSIX_PBA_OFFSET) {
+ Ordering::Less => {
+ self.msix_config
+ .lock()
+ .read_msix_table(offset - BR_MSIX_TABLE_OFFSET, data);
+ }
+ Ordering::Equal => {
+ self.msix_config
+ .lock()
+ .read_pba_entries(offset - BR_MSIX_PBA_OFFSET, data);
+ }
+ Ordering::Greater => (),
+ }
+ }
+
+ fn write_bar(&mut self, addr: u64, data: &[u8]) {
+ let bar0 = self.config.get_bar_addr(self.setting_bar as usize);
+ let offset = addr - bar0;
+ match offset.cmp(&BR_MSIX_PBA_OFFSET) {
+ Ordering::Less => {
+ self.msix_config
+ .lock()
+ .write_msix_table(offset - BR_MSIX_TABLE_OFFSET, data);
+ }
+ Ordering::Equal => {
+ self.msix_config
+ .lock()
+ .write_pba_entries(offset - BR_MSIX_PBA_OFFSET, data);
+ }
+ Ordering::Greater => (),
+ }
+ }
+
+ fn get_removed_children_devices(&self) -> Vec<PciAddress> {
+ self.device.lock().get_removed_devices()
+ }
+
+ fn configure_bridge_window(
+ &mut self,
+ resources: &mut SystemAllocator,
+ bar_ranges: &[BarRange],
+ ) -> std::result::Result<(), PciDeviceError> {
+ let address = self
+ .pci_address
+ .expect("allocate_address must be called prior to configure_bridge_window");
+
+ let mut window_base: u64 = u64::MAX;
+ let mut window_size: u64 = 0;
+ let mut pref_window_base: u64 = u64::MAX;
+ let mut pref_window_size: u64 = 0;
+
+ if self.device.lock().hotplug_implemented() {
+ // Bridge for children hotplug, get desired bridge window size and reserve
+ // it for guest OS use
+ let (win_size, pref_win_size) = self.device.lock().get_bridge_window_size();
+ if win_size != 0 {
+ window_size = win_size;
+ }
+
+ if pref_win_size != 0 {
+ pref_window_size = pref_win_size;
+ }
+ } else {
+ // Bridge has children connected, get bridge window size from children
+ for &BarRange {
+ addr,
+ size,
+ prefetchable,
+ } in bar_ranges.iter()
+ {
+ if prefetchable {
+ pref_window_base = min(pref_window_base, addr);
+ pref_window_size = max(pref_window_size, addr + size - pref_window_base);
+ } else {
+ window_base = min(window_base, addr);
+ window_size = max(window_size, addr + size - window_base);
+ }
+ }
+ }
+
+ if window_size == 0 {
+ // Allocate at least 2MB bridge winodw
+ window_size = BR_MEM_MINIMUM;
+ }
+ // align window_size to 1MB
+ if window_size & (BR_WINDOW_ALIGNMENT - 1) != 0 {
+ window_size = (window_size + BR_WINDOW_ALIGNMENT - 1) & (!(BR_WINDOW_ALIGNMENT - 1));
+ }
+ // if window_base isn't set, allocate a new one
+ if window_base == u64::MAX {
+ match resources.mmio_allocator(MmioType::Low).allocate_with_align(
+ window_size,
+ Alloc::PciBridgeWindow {
+ bus: address.bus,
+ dev: address.dev,
+ func: address.func,
+ },
+ "pci_bridge_window".to_string(),
+ BR_WINDOW_ALIGNMENT,
+ ) {
+ Ok(addr) => window_base = addr,
+ Err(e) => warn!(
+ "{} failed to allocate bridge window: {}",
+ self.debug_label(),
+ e
+ ),
+ }
+ } else {
+ // align window_base to 1MB
+ if window_base & (BR_WINDOW_ALIGNMENT - 1) != 0 {
+ window_base =
+ (window_base + BR_WINDOW_ALIGNMENT - 1) & (!(BR_WINDOW_ALIGNMENT - 1));
+ }
+ }
+
+ if pref_window_size == 0 {
+ // Allocate at least 2MB prefetch bridge window
+ pref_window_size = BR_MEM_MINIMUM;
+ }
+ // align pref_window_size to 1MB
+ if pref_window_size & (BR_WINDOW_ALIGNMENT - 1) != 0 {
+ pref_window_size =
+ (pref_window_size + BR_WINDOW_ALIGNMENT - 1) & (!(BR_WINDOW_ALIGNMENT - 1));
+ }
+ // if pref_window_base isn't set, allocate a new one
+ if pref_window_base == u64::MAX {
+ match resources
+ .mmio_allocator(MmioType::High)
+ .allocate_with_align(
+ pref_window_size,
+ Alloc::PciBridgeWindow {
+ bus: address.bus,
+ dev: address.dev,
+ func: address.func,
+ },
+ "pci_bridge_window".to_string(),
+ BR_WINDOW_ALIGNMENT,
+ ) {
+ Ok(addr) => pref_window_base = addr,
+ Err(e) => warn!(
+ "{} failed to allocate bridge window: {}",
+ self.debug_label(),
+ e
+ ),
+ }
+ } else {
+ // align pref_window_base to 1MB
+ if pref_window_base & (BR_WINDOW_ALIGNMENT - 1) != 0 {
+ pref_window_base =
+ (window_base + BR_WINDOW_ALIGNMENT - 1) & (!(BR_WINDOW_ALIGNMENT - 1));
+ }
+ }
+
+ self.write_bridge_window(
+ window_base as u32,
+ window_size as u32,
+ pref_window_base,
+ pref_window_size,
+ );
+ Ok(())
+ }
+}
diff --git a/devices/src/pci/pcie/pcie_device.rs b/devices/src/pci/pcie/pcie_device.rs
new file mode 100644
index 000000000..b7215e041
--- /dev/null
+++ b/devices/src/pci/pcie/pcie_device.rs
@@ -0,0 +1,267 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use std::sync::Arc;
+use sync::Mutex;
+
+use crate::pci::pci_configuration::PciCapabilityID;
+use crate::pci::pcie::pci_bridge::PciBridgeBusRange;
+use crate::pci::pcie::*;
+use crate::pci::{MsixConfig, PciAddress, PciCapability, PciDeviceError};
+use data_model::DataInit;
+use resources::SystemAllocator;
+
+pub trait PcieDevice: Send {
+ fn get_device_id(&self) -> u16;
+ fn debug_label(&self) -> String;
+ fn allocate_address(
+ &mut self,
+ resources: &mut SystemAllocator,
+ ) -> std::result::Result<PciAddress, PciDeviceError>;
+ fn read_config(&self, reg_idx: usize, data: &mut u32);
+ fn write_config(&mut self, reg_idx: usize, offset: u64, data: &[u8]);
+ fn clone_interrupt(&mut self, msix_config: Arc<Mutex<MsixConfig>>);
+ fn get_caps(&self) -> Vec<Box<dyn PciCapability>>;
+ fn set_capability_reg_idx(&mut self, id: PciCapabilityID, reg_idx: usize);
+ fn get_bus_range(&self) -> Option<PciBridgeBusRange> {
+ None
+ }
+ fn get_removed_devices(&self) -> Vec<PciAddress>;
+
+ /// Hotplug capability is implemented on this bridge or not.
+ /// Return true, the children pci devices could be connected through hotplug
+ /// Return false, the children pci devices should be connected statically
+ fn hotplug_implemented(&self) -> bool;
+
+ /// Get bridge window size to cover children's mmio size
+ /// (u64, u64) -> (non_prefetchable window size, prefetchable_window_size)
+ fn get_bridge_window_size(&self) -> (u64, u64);
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct PcieCap {
+ _cap_vndr: u8,
+ _cap_next: u8,
+ pcie_cap: u16,
+ dev_cap: u32,
+ dev_control: u16,
+ dev_status: u16,
+ link_cap: u32,
+ link_control: u16,
+ link_status: u16,
+ slot_cap: u32,
+ slot_control: u16,
+ slot_status: u16,
+ root_control: u16,
+ root_cap: u16,
+ root_status: u32,
+ dev_cap_2: u32,
+ dev_control_2: u16,
+ dev_status_2: u16,
+ link_cap_2: u32,
+ link_control_2: u16,
+ link_status_2: u16,
+ slot_cap_2: u32,
+ slot_control_2: u16,
+ slot_status_2: u16,
+}
+// It is safe to implement DataInit; all members are simple numbers and any value is valid.
+unsafe impl DataInit for PcieCap {}
+
+impl PciCapability for PcieCap {
+ fn bytes(&self) -> &[u8] {
+ self.as_slice()
+ }
+
+ fn id(&self) -> PciCapabilityID {
+ PciCapabilityID::PciExpress
+ }
+
+ fn writable_bits(&self) -> Vec<u32> {
+ vec![
+ 0u32,
+ 0,
+ 0xf_ffff,
+ 0,
+ 0x3000_0fff,
+ 0,
+ 0x11f_1fff,
+ 0x1f,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ ]
+ }
+}
+
+impl PcieCap {
+ pub fn new(device_type: PcieDevicePortType, slot: bool, irq_num: u16) -> Self {
+ let mut pcie_cap = PCIE_CAP_VERSION;
+ pcie_cap |= (device_type as u16) << PCIE_TYPE_SHIFT;
+ if slot {
+ pcie_cap |= 1 << PCIE_CAP_SLOT_SHIFT;
+ }
+ pcie_cap |= irq_num << PCIE_CAP_IRQ_NUM_SHIFT;
+
+ let dev_cap = PCIE_DEVCAP_RBER;
+ let link_cap = (PCIE_LINK_X1 | PCIE_LINK_2_5GT) as u32;
+ let link_status = PCIE_LINK_X1 | PCIE_LINK_2_5GT;
+
+ let mut slot_cap: u32 = 0;
+ let mut slot_control: u16 = 0;
+ if slot {
+ slot_cap = PCIE_SLTCAP_ABP
+ | PCIE_SLTCAP_AIP
+ | PCIE_SLTCAP_PIP
+ | PCIE_SLTCAP_HPS
+ | PCIE_SLTCAP_HPC;
+ slot_control = PCIE_SLTCTL_PIC_OFF | PCIE_SLTCTL_AIC_OFF;
+ }
+
+ PcieCap {
+ _cap_vndr: 0,
+ _cap_next: 0,
+ pcie_cap,
+ dev_cap,
+ dev_control: 0,
+ dev_status: 0,
+ link_cap,
+ link_control: 0,
+ link_status,
+ slot_cap,
+ slot_control,
+ slot_status: 0,
+ root_control: 0,
+ root_cap: 0,
+ root_status: 0,
+ dev_cap_2: 0,
+ dev_control_2: 0,
+ dev_status_2: 0,
+ link_cap_2: 0,
+ link_control_2: 0,
+ link_status_2: 0,
+ slot_cap_2: 0,
+ slot_control_2: 0,
+ slot_status_2: 0,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct PciPmcCap {
+ _cap_vndr: u8,
+ _cap_next: u8,
+ pmc_cap: u16,
+ pmc_control_status: u16,
+ padding: u16,
+}
+
+// It is safe to implement DataInit; all members are simple numbers and any value is valid.
+unsafe impl DataInit for PciPmcCap {}
+
+impl PciCapability for PciPmcCap {
+ fn bytes(&self) -> &[u8] {
+ self.as_slice()
+ }
+
+ fn id(&self) -> PciCapabilityID {
+ PciCapabilityID::PowerManagement
+ }
+
+ fn writable_bits(&self) -> Vec<u32> {
+ vec![0u32, 0x8103]
+ }
+}
+
+impl PciPmcCap {
+ pub fn new() -> Self {
+ let pmc_cap: u16 = PMC_CAP_PME_SUPPORT_D0
+ | PMC_CAP_PME_SUPPORT_D3_HOT
+ | PMC_CAP_PME_SUPPORT_D3_COLD
+ | PMC_CAP_VERSION;
+ PciPmcCap {
+ _cap_vndr: 0,
+ _cap_next: 0,
+ pmc_cap,
+ pmc_control_status: 0,
+ padding: 0,
+ }
+ }
+}
+
+pub struct PmcConfig {
+ power_control_status: u16,
+}
+
+impl PmcConfig {
+ pub fn new() -> Self {
+ PmcConfig {
+ power_control_status: 0,
+ }
+ }
+
+ pub fn read(&self, data: &mut u32) {
+ *data = self.power_control_status as u32;
+ }
+
+ pub fn write(&mut self, offset: u64, data: &[u8]) {
+ if offset > 1 {
+ return;
+ }
+
+ if offset == 0 {
+ self.power_control_status &= !PMC_POWER_STATE_MASK;
+ self.power_control_status |= data[0] as u16 & PMC_POWER_STATE_MASK;
+ }
+
+ let write_data = if offset == 0 && (data.len() == 2 || data.len() == 4) {
+ Some((data[1] as u16) << 8)
+ } else if offset == 1 && data.len() == 1 {
+ Some((data[0] as u16) << 8)
+ } else {
+ None
+ };
+
+ if let Some(write_data) = write_data {
+ if write_data & PMC_PME_STATUS != 0 {
+ // clear PME_STATUS
+ self.power_control_status &= !PMC_PME_STATUS;
+ }
+
+ if write_data & PMC_PME_ENABLE != 0 {
+ self.power_control_status |= PMC_PME_ENABLE;
+ } else {
+ self.power_control_status &= !PMC_PME_ENABLE;
+ }
+ }
+ }
+
+ /// If device is in D3 and PME is enabled, set PME status, then device could
+ /// inject a pme interrupt into guest
+ pub fn should_trigger_pme(&mut self) -> bool {
+ if self.power_control_status & PMC_POWER_STATE_MASK == PMC_POWER_STATE_D3
+ && self.power_control_status & PMC_PME_ENABLE != 0
+ {
+ self.power_control_status |= PMC_PME_STATUS;
+
+ return true;
+ }
+
+ false
+ }
+
+ /// Get device power status
+ pub fn get_power_status(&self) -> PciDevicePower {
+ match self.power_control_status & PMC_POWER_STATE_MASK {
+ PMC_POWER_STATE_D0 => PciDevicePower::D0,
+ PMC_POWER_STATE_D3 => PciDevicePower::D3,
+ _ => PciDevicePower::Unsupported,
+ }
+ }
+}
diff --git a/devices/src/pci/pcie/pcie_host.rs b/devices/src/pci/pcie/pcie_host.rs
new file mode 100644
index 000000000..aae1ccbf0
--- /dev/null
+++ b/devices/src/pci/pcie/pcie_host.rs
@@ -0,0 +1,462 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#[cfg(feature = "direct")]
+use std::fs::read_to_string;
+use std::fs::{read, write, File, OpenOptions};
+use std::os::unix::fs::FileExt;
+use std::path::{Path, PathBuf};
+use std::sync::Arc;
+use std::thread;
+
+use anyhow::{anyhow, bail, Context, Result};
+#[cfg(feature = "direct")]
+use base::warn;
+use base::{error, Tube};
+use data_model::DataInit;
+use sync::Mutex;
+use vm_control::{VmRequest, VmResponse};
+
+use crate::pci::{PciCapabilityID, PciClassCode};
+
+use crate::pci::pci_configuration::{
+ PciBridgeSubclass, CAPABILITY_LIST_HEAD_OFFSET, HEADER_TYPE_REG, PCI_CAP_NEXT_POINTER,
+};
+#[cfg(feature = "direct")]
+use crate::pci::pci_configuration::{CLASS_REG, CLASS_REG_REVISION_ID_OFFSET};
+
+use crate::pci::pcie::pci_bridge::{
+ PciBridgeBusRange, BR_BUS_NUMBER_REG, BR_MEM_BASE_MASK, BR_MEM_BASE_SHIFT, BR_MEM_LIMIT_MASK,
+ BR_MEM_MINIMUM, BR_MEM_REG, BR_PREF_MEM_64BIT, BR_PREF_MEM_BASE_HIGH_REG,
+ BR_PREF_MEM_LIMIT_HIGH_REG, BR_PREF_MEM_LOW_REG, BR_WINDOW_ALIGNMENT,
+};
+
+use crate::pci::pcie::*;
+
+// Host Pci device's sysfs config file
+struct PciHostConfig {
+ config_file: File,
+}
+
+impl PciHostConfig {
+ // Create a new host pci device's sysfs config file
+ fn new(host_sysfs_path: &Path) -> Result<Self> {
+ let mut config_path = PathBuf::new();
+ config_path.push(host_sysfs_path);
+ config_path.push("config");
+ let f = OpenOptions::new()
+ .write(true)
+ .read(true)
+ .open(config_path.as_path())
+ .with_context(|| format!("failed to open: {}", config_path.display()))?;
+ Ok(PciHostConfig { config_file: f })
+ }
+
+ // Read host pci device's config register
+ fn read_config<T: DataInit>(&self, offset: u64) -> T {
+ let length = std::mem::size_of::<T>();
+ let mut buf = vec![0u8; length];
+ if offset % length as u64 != 0 {
+ error!(
+ "read_config, offset {} isn't aligned to length {}",
+ offset, length
+ );
+ } else if let Err(e) = self.config_file.read_exact_at(&mut buf, offset) {
+ error!("failed to read host sysfs config: {}", e);
+ }
+
+ T::from_slice(&buf)
+ .copied()
+ .expect("failed to convert host sysfs config data from slice")
+ }
+
+ // write host pci device's config register
+ #[allow(dead_code)]
+ fn write_config(&self, offset: u64, data: &[u8]) {
+ if offset % data.len() as u64 != 0 {
+ error!(
+ "write_config, offset {} isn't aligned to length {}",
+ offset,
+ data.len()
+ );
+ return;
+ }
+ if let Err(e) = self.config_file.write_all_at(data, offset) {
+ error!("failed to write host sysfs config: {}", e);
+ }
+ }
+}
+
+// Find the added pcie endpoint device
+fn visit_children(dir: &Path) -> Result<PathBuf> {
+ // Each pci device has a sysfs directory
+ if !dir.is_dir() {
+ bail!("{} isn't directory", dir.display());
+ }
+
+ let class_path = dir.join("class");
+ let class_id = read(class_path.as_path())
+ .with_context(|| format!("failed to read {}", class_path.display()))?;
+ // If the device isn't pci bridge, it is the target pcie endpoint device
+ if !class_id.starts_with("0x0604".as_bytes()) {
+ return Ok(dir.to_path_buf());
+ }
+
+ // Loop device sysfs subdirectory
+ let entries = dir
+ .read_dir()
+ .with_context(|| format!("failed to read dir {}", dir.display()))?;
+ for entry in entries {
+ let sub_dir = match entry {
+ Ok(sub) => sub,
+ _ => continue,
+ };
+
+ if !sub_dir.path().is_dir() {
+ continue;
+ }
+
+ let name = sub_dir
+ .file_name()
+ .into_string()
+ .map_err(|_| anyhow!("failed to get dir name"))?;
+ // Child pci device has name format 0000:xx:xx.x, length is 12
+ if name.len() != 12 || !name.starts_with("0000:") {
+ continue;
+ }
+ let child_path = dir.join(name);
+ match visit_children(child_path.as_path()) {
+ Ok(child) => return Ok(child),
+ Err(_) => continue,
+ }
+ }
+ Err(anyhow!(
+ "pcie child endpoint device isn't exist in {}",
+ dir.display()
+ ))
+}
+
+struct HotplugWorker {
+ host_name: String,
+}
+
+impl HotplugWorker {
+ fn run(&self, vm_socket: Arc<Mutex<Tube>>, child_exist: Arc<Mutex<bool>>) -> Result<()> {
+ let mut host_sysfs = PathBuf::new();
+ host_sysfs.push("/sys/bus/pci/devices/");
+ host_sysfs.push(self.host_name.clone());
+ let rescan_path = host_sysfs.join("rescan");
+ // Let pcie root port rescan to find the added or removed children devices
+ write(rescan_path.as_path(), "1")
+ .with_context(|| format!("failed to write {}", rescan_path.display()))?;
+
+ // If child device existed, but code run here again, this means host has a
+ // hotplug out event, after the above rescan, host should find the removed
+ // child device, and host vfio-pci kernel driver should notify crosvm vfio-pci
+ // devie such hotplug out event, so nothing is needed to do here, just return
+ // it now.
+ let mut child_exist = child_exist.lock();
+ if *child_exist {
+ return Ok(());
+ }
+
+ // Probe the new added pcied endpoint device
+ let child = visit_children(host_sysfs.as_path())?;
+
+ // In order to bind device to vfio-pci driver, get device VID and DID
+ let vendor_path = child.join("vendor");
+ let vendor_id = read(vendor_path.as_path())
+ .with_context(|| format!("failed to read {}", vendor_path.display()))?;
+ // Remove the first two elements 0x
+ let prefix: &str = "0x";
+ let vendor = match vendor_id.strip_prefix(prefix.as_bytes()) {
+ Some(v) => v.to_vec(),
+ None => vendor_id,
+ };
+ let device_path = child.join("device");
+ let device_id = read(device_path.as_path())
+ .with_context(|| format!("failed to read {}", device_path.display()))?;
+ // Remove the first two elements 0x
+ let device = match device_id.strip_prefix(prefix.as_bytes()) {
+ Some(d) => d.to_vec(),
+ None => device_id,
+ };
+ let new_id = vec![
+ String::from_utf8_lossy(&vendor),
+ String::from_utf8_lossy(&device),
+ ]
+ .join(" ");
+ write("/sys/bus/pci/drivers/vfio-pci/new_id", &new_id)
+ .with_context(|| format!("failed to write {} into vfio-pci/new_id", new_id))?;
+
+ // Request to hotplug the new added pcie device into guest
+ let request = VmRequest::VfioCommand {
+ vfio_path: child.clone(),
+ add: true,
+ };
+ vm_socket
+ .lock()
+ .send(&request)
+ .with_context(|| format!("failed to send hotplug request for {}", child.display()))?;
+ vm_socket.lock().recv::<VmResponse>().with_context(|| {
+ format!("failed to receive hotplug response for {}", child.display())
+ })?;
+
+ *child_exist = true;
+
+ Ok(())
+ }
+}
+
+const PCI_CONFIG_DEVICE_ID: u64 = 0x02;
+const PCI_BASE_CLASS_CODE: u64 = 0x0B;
+const PCI_SUB_CLASS_CODE: u64 = 0x0A;
+
+/// Pcie root port device has a corresponding host pcie root port.
+pub struct PcieHostRootPort {
+ host_config: PciHostConfig,
+ host_name: String,
+ hotplug_in_process: Arc<Mutex<bool>>,
+ hotplug_child_exist: Arc<Mutex<bool>>,
+ vm_socket: Arc<Mutex<Tube>>,
+ #[cfg(feature = "direct")]
+ sysfs_path: Option<PathBuf>,
+ #[cfg(feature = "direct")]
+ header_type_reg: Option<u32>,
+}
+
+impl PcieHostRootPort {
+ /// Create PcieHostRootPort, host_syfsfs_patch specify host pcie root port
+ /// sysfs path.
+ pub fn new(host_sysfs_path: &Path, socket: Tube) -> Result<Self> {
+ let host_config = PciHostConfig::new(host_sysfs_path)?;
+ let host_name = host_sysfs_path
+ .file_name()
+ .unwrap()
+ .to_str()
+ .unwrap()
+ .to_owned();
+ let base_class: u8 = host_config.read_config(PCI_BASE_CLASS_CODE);
+ if base_class != PciClassCode::BridgeDevice.get_register_value() {
+ return Err(anyhow!("host {} isn't bridge", host_name));
+ }
+ let sub_class: u8 = host_config.read_config(PCI_SUB_CLASS_CODE);
+ if sub_class != PciBridgeSubclass::PciToPciBridge as u8 {
+ return Err(anyhow!("host {} isn't pci to pci bridge", host_name));
+ }
+
+ let mut pcie_cap_reg: u8 = 0;
+
+ let mut cap_next: u8 = host_config.read_config(CAPABILITY_LIST_HEAD_OFFSET as u64);
+ let mut counter: u16 = 0;
+ while cap_next != 0 && counter < 256 {
+ let cap_id: u8 = host_config.read_config(cap_next.into());
+ if cap_id == PciCapabilityID::PciExpress as u8 {
+ pcie_cap_reg = cap_next;
+ break;
+ }
+ let offset = cap_next as u64 + PCI_CAP_NEXT_POINTER as u64;
+ cap_next = host_config.read_config(offset);
+ counter += 1;
+ }
+
+ if pcie_cap_reg == 0 {
+ return Err(anyhow!("host {} isn't pcie device", host_name));
+ }
+
+ let device_cap: u8 = host_config.read_config(pcie_cap_reg as u64 + PCIE_CAP_VERSION as u64);
+ if (device_cap >> PCIE_TYPE_SHIFT) != PcieDevicePortType::RootPort as u8 {
+ return Err(anyhow!("host {} isn't pcie root port", host_name));
+ }
+
+ #[cfg(feature = "direct")]
+ let (sysfs_path, header_type_reg) =
+ match PcieHostRootPort::coordinated_pm(host_sysfs_path, true) {
+ Ok(_) => {
+ // Cache the dword at offset 0x0c (cacheline size, latency timer,
+ // header type, BIST).
+ // When using the "direct" feature, this dword can be accessed for
+ // device power state. Directly accessing a device's physical PCI
+ // config space in D3cold state causes a hang. We treat the cacheline
+ // size, latency timer and header type field as immutable in the
+ // guest.
+ let reg: u32 = host_config.read_config((HEADER_TYPE_REG as u64) * 4);
+ (Some(host_sysfs_path.to_path_buf()), Some(reg))
+ }
+ Err(e) => {
+ warn!("coordinated_pm not supported: {}", e);
+ (None, None)
+ }
+ };
+
+ Ok(PcieHostRootPort {
+ host_config,
+ host_name,
+ hotplug_in_process: Arc::new(Mutex::new(false)),
+ hotplug_child_exist: Arc::new(Mutex::new(false)),
+ vm_socket: Arc::new(Mutex::new(socket)),
+ #[cfg(feature = "direct")]
+ sysfs_path,
+ #[cfg(feature = "direct")]
+ header_type_reg,
+ })
+ }
+
+ pub fn get_bus_range(&self) -> PciBridgeBusRange {
+ let bus_num: u32 = self.host_config.read_config((BR_BUS_NUMBER_REG * 4) as u64);
+ let primary = (bus_num & 0xFF) as u8;
+ let secondary = ((bus_num >> 8) & 0xFF) as u8;
+ let subordinate = ((bus_num >> 16) & 0xFF) as u8;
+
+ PciBridgeBusRange {
+ primary,
+ secondary,
+ subordinate,
+ }
+ }
+
+ pub fn read_device_id(&self) -> u16 {
+ self.host_config.read_config::<u16>(PCI_CONFIG_DEVICE_ID)
+ }
+
+ pub fn host_name(&self) -> String {
+ self.host_name.clone()
+ }
+
+ pub fn read_config(&self, reg_idx: usize, data: &mut u32) {
+ if reg_idx == HEADER_TYPE_REG {
+ #[cfg(feature = "direct")]
+ if let Some(header_type_reg) = self.header_type_reg {
+ let mut v = header_type_reg.to_le_bytes();
+ // HACK
+ // Reads from the "BIST" register are interpreted as device
+ // PCI power state
+ v[3] = self.power_state().unwrap_or_else(|e| {
+ error!("Failed to get device power state: {}", e);
+ 5 // unknown state
+ });
+ *data = u32::from_le_bytes(v);
+ return;
+ }
+ *data = self.host_config.read_config((HEADER_TYPE_REG as u64) * 4)
+ }
+ }
+
+ #[allow(unused_variables)]
+ pub fn write_config(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
+ #[cfg(feature = "direct")]
+ if self.sysfs_path.is_some()
+ && reg_idx == CLASS_REG
+ && offset == CLASS_REG_REVISION_ID_OFFSET as u64
+ && data.len() == 1
+ {
+ // HACK
+ // Byte writes to the "Revision ID" register are interpreted as PM
+ // op calls
+ if let Err(e) = self.op_call(data[0]) {
+ error!("Failed to perform op call: {}", e);
+ }
+ }
+ }
+
+ pub fn get_bridge_window_size(&self) -> (u64, u64) {
+ let br_memory: u32 = self.host_config.read_config(BR_MEM_REG as u64 * 4);
+ let mem_base = (br_memory & BR_MEM_BASE_MASK) << BR_MEM_BASE_SHIFT;
+ let mem_limit = br_memory & BR_MEM_LIMIT_MASK;
+ let mem_size = if mem_limit > mem_base {
+ (mem_limit - mem_base) as u64 + BR_WINDOW_ALIGNMENT
+ } else {
+ BR_MEM_MINIMUM
+ };
+ let br_pref_mem_low: u32 = self.host_config.read_config(BR_PREF_MEM_LOW_REG as u64 * 4);
+ let pref_mem_base_low = (br_pref_mem_low & BR_MEM_BASE_MASK) << BR_MEM_BASE_SHIFT;
+ let pref_mem_limit_low = br_pref_mem_low & BR_MEM_LIMIT_MASK;
+ let mut pref_mem_base: u64 = pref_mem_base_low as u64;
+ let mut pref_mem_limit: u64 = pref_mem_limit_low as u64;
+ if br_pref_mem_low & BR_PREF_MEM_64BIT == BR_PREF_MEM_64BIT {
+ // 64bit prefetch memory
+ let pref_mem_base_high: u32 = self
+ .host_config
+ .read_config(BR_PREF_MEM_BASE_HIGH_REG as u64 * 4);
+ let pref_mem_limit_high: u32 = self
+ .host_config
+ .read_config(BR_PREF_MEM_LIMIT_HIGH_REG as u64 * 4);
+ pref_mem_base = ((pref_mem_base_high as u64) << 32) | (pref_mem_base_low as u64);
+ pref_mem_limit = ((pref_mem_limit_high as u64) << 32) | (pref_mem_limit_low as u64);
+ }
+ let pref_mem_size = if pref_mem_limit > pref_mem_base {
+ pref_mem_limit - pref_mem_base + BR_WINDOW_ALIGNMENT
+ } else {
+ BR_MEM_MINIMUM
+ };
+
+ (mem_size, pref_mem_size)
+ }
+
+ pub fn hotplug_probe(&mut self) {
+ if *self.hotplug_in_process.lock() {
+ return;
+ }
+
+ let hotplug_process = self.hotplug_in_process.clone();
+ let child_exist = self.hotplug_child_exist.clone();
+ let socket = self.vm_socket.clone();
+ let name = self.host_name.clone();
+ let _ = thread::Builder::new()
+ .name("pcie_hotplug".to_string())
+ .spawn(move || {
+ let mut hotplug = hotplug_process.lock();
+ *hotplug = true;
+ let hotplug_worker = HotplugWorker { host_name: name };
+ let _ = hotplug_worker.run(socket, child_exist);
+ *hotplug = false;
+ });
+ }
+
+ pub fn hot_unplug(&mut self) {
+ *self.hotplug_child_exist.lock() = false;
+ }
+
+ #[cfg(feature = "direct")]
+ fn coordinated_pm(host_sysfs_path: &Path, enter: bool) -> Result<()> {
+ let path = Path::new(host_sysfs_path).join("power/coordinated");
+ write(&path, if enter { "enter\n" } else { "exit\n" })
+ .with_context(|| format!("Failed to write to {}", path.to_string_lossy()))
+ }
+
+ #[cfg(feature = "direct")]
+ fn power_state(&self) -> Result<u8> {
+ let path = Path::new(&self.sysfs_path.as_ref().unwrap()).join("power_state");
+ let state = read_to_string(&path)
+ .with_context(|| format!("Failed to read from {}", path.to_string_lossy()))?;
+ match state.as_str() {
+ "D0\n" => Ok(0),
+ "D1\n" => Ok(1),
+ "D2\n" => Ok(2),
+ "D3hot\n" => Ok(3),
+ "D3cold\n" => Ok(4),
+ "unknown\n" => Ok(5),
+ _ => Err(std::io::Error::new(
+ std::io::ErrorKind::InvalidData,
+ "invalid state",
+ ))?,
+ }
+ }
+
+ #[cfg(feature = "direct")]
+ fn op_call(&self, id: u8) -> Result<()> {
+ let path = Path::new(self.sysfs_path.as_ref().unwrap()).join("power/op_call");
+ write(&path, &[id])
+ .with_context(|| format!("Failed to write to {}", path.to_string_lossy()))
+ }
+}
+
+#[cfg(feature = "direct")]
+impl Drop for PcieHostRootPort {
+ fn drop(&mut self) {
+ if self.sysfs_path.is_some() {
+ let _ = PcieHostRootPort::coordinated_pm(self.sysfs_path.as_ref().unwrap(), false);
+ }
+ }
+}
diff --git a/devices/src/pci/pcie/pcie_rp.rs b/devices/src/pci/pcie/pcie_rp.rs
new file mode 100644
index 000000000..06f591e18
--- /dev/null
+++ b/devices/src/pci/pcie/pcie_rp.rs
@@ -0,0 +1,530 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::str::FromStr;
+use std::sync::Arc;
+use sync::Mutex;
+
+use crate::bus::{HostHotPlugKey, HotPlugBus};
+use crate::pci::pci_configuration::PciCapabilityID;
+use crate::pci::{MsixConfig, PciAddress, PciCapability, PciDeviceError};
+
+use crate::pci::pcie::pci_bridge::PciBridgeBusRange;
+use crate::pci::pcie::pcie_device::{PciPmcCap, PcieCap, PcieDevice, PmcConfig};
+use crate::pci::pcie::pcie_host::PcieHostRootPort;
+use crate::pci::pcie::*;
+
+use anyhow::{anyhow, Result};
+use base::warn;
+use data_model::DataInit;
+use resources::{Alloc, SystemAllocator};
+use vm_control::GpeNotify;
+
+// reserve 8MB memory window
+const PCIE_RP_BR_MEM_SIZE: u64 = 0x80_0000;
+// reserve 64MB prefetch window
+const PCIE_RP_BR_PREF_MEM_SIZE: u64 = 0x400_0000;
+
+const PCIE_RP_DID: u16 = 0x3420;
+pub struct PcieRootPort {
+ pcie_cap_reg_idx: Option<usize>,
+ msix_config: Option<Arc<Mutex<MsixConfig>>>,
+ pmc_config: PmcConfig,
+ pmc_cap_reg_idx: Option<usize>,
+ pci_address: Option<PciAddress>,
+ slot_control: Option<u16>,
+ slot_status: u16,
+ root_control: u16,
+ root_status: u32,
+ hp_interrupt_pending: bool,
+ pme_pending_request_id: Option<PciAddress>,
+ bus_range: PciBridgeBusRange,
+ downstream_device: Option<(PciAddress, Option<HostHotPlugKey>)>,
+ removed_downstream: Option<PciAddress>,
+ pcie_host: Option<PcieHostRootPort>,
+ prepare_hotplug: bool,
+}
+
+impl PcieRootPort {
+ /// Constructs a new PCIE root port
+ pub fn new(secondary_bus_num: u8, slot_implemented: bool) -> Self {
+ let bus_range = PciBridgeBusRange {
+ primary: 0,
+ secondary: secondary_bus_num,
+ subordinate: secondary_bus_num,
+ };
+ PcieRootPort {
+ pcie_cap_reg_idx: None,
+ msix_config: None,
+ pmc_config: PmcConfig::new(),
+ pmc_cap_reg_idx: None,
+ pci_address: None,
+ slot_control: if slot_implemented {
+ Some(PCIE_SLTCTL_PIC_OFF | PCIE_SLTCTL_AIC_OFF)
+ } else {
+ None
+ },
+ slot_status: 0,
+ root_control: 0,
+ root_status: 0,
+ hp_interrupt_pending: false,
+ pme_pending_request_id: None,
+ bus_range,
+ downstream_device: None,
+ removed_downstream: None,
+ pcie_host: None,
+ prepare_hotplug: false,
+ }
+ }
+
+ /// Constructs a new PCIE root port which associated with the host physical pcie RP
+ pub fn new_from_host(pcie_host: PcieHostRootPort, slot_implemented: bool) -> Result<Self> {
+ let bus_range = pcie_host.get_bus_range();
+ // if physical pcie root port isn't on bus 0, ignore this physical pcie root port.
+ if bus_range.primary != 0 {
+ return Err(anyhow!(
+ "physical pcie RP isn't on bus 0: {}",
+ bus_range.primary
+ ));
+ }
+
+ Ok(PcieRootPort {
+ pcie_cap_reg_idx: None,
+ msix_config: None,
+ pmc_config: PmcConfig::new(),
+ pmc_cap_reg_idx: None,
+ pci_address: None,
+ slot_control: if slot_implemented {
+ Some(PCIE_SLTCTL_PIC_OFF | PCIE_SLTCTL_AIC_OFF)
+ } else {
+ None
+ },
+ slot_status: 0,
+ root_control: 0,
+ root_status: 0,
+ hp_interrupt_pending: false,
+ pme_pending_request_id: None,
+ bus_range,
+ downstream_device: None,
+ removed_downstream: None,
+ pcie_host: Some(pcie_host),
+ prepare_hotplug: false,
+ })
+ }
+
+ fn get_slot_control(&self) -> u16 {
+ if let Some(slot_control) = self.slot_control {
+ return slot_control;
+ }
+ 0
+ }
+
+ fn read_pcie_cap(&self, offset: usize, data: &mut u32) {
+ if offset == PCIE_SLTCTL_OFFSET {
+ *data = ((self.slot_status as u32) << 16) | (self.get_slot_control() as u32);
+ } else if offset == PCIE_ROOTCTL_OFFSET {
+ *data = self.root_control as u32;
+ } else if offset == PCIE_ROOTSTA_OFFSET {
+ *data = self.root_status;
+ }
+ }
+
+ fn write_pcie_cap(&mut self, offset: usize, data: &[u8]) {
+ self.removed_downstream = None;
+ match offset {
+ PCIE_SLTCTL_OFFSET => {
+ let value = match u16::from_slice(data) {
+ Some(&v) => v,
+ None => {
+ warn!("write SLTCTL isn't word, len: {}", data.len());
+ return;
+ }
+ };
+
+ // if slot is populated, power indicator is off,
+ // it will detach devices
+ let old_control = self.get_slot_control();
+ match self.slot_control.as_mut() {
+ Some(v) => *v = value,
+ None => return,
+ }
+ if (self.slot_status & PCIE_SLTSTA_PDS != 0)
+ && (value & PCIE_SLTCTL_PIC_OFF == PCIE_SLTCTL_PIC_OFF)
+ && (old_control & PCIE_SLTCTL_PIC_OFF != PCIE_SLTCTL_PIC_OFF)
+ {
+ if let Some((guest_pci_addr, _)) = self.downstream_device {
+ self.removed_downstream = Some(guest_pci_addr);
+ self.downstream_device = None;
+ }
+ self.slot_status &= !PCIE_SLTSTA_PDS;
+ self.slot_status |= PCIE_SLTSTA_PDC;
+ self.trigger_hp_interrupt();
+ }
+
+ if old_control != value {
+ // send Command completed events
+ self.slot_status |= PCIE_SLTSTA_CC;
+ self.trigger_cc_interrupt();
+ }
+ }
+ PCIE_SLTSTA_OFFSET => {
+ if self.slot_control.is_none() {
+ return;
+ }
+ let value = match u16::from_slice(data) {
+ Some(v) => *v,
+ None => {
+ warn!("write SLTSTA isn't word, len: {}", data.len());
+ return;
+ }
+ };
+ if value & PCIE_SLTSTA_ABP != 0 {
+ self.slot_status &= !PCIE_SLTSTA_ABP;
+ }
+ if value & PCIE_SLTSTA_PFD != 0 {
+ self.slot_status &= !PCIE_SLTSTA_PFD;
+ }
+ if value & PCIE_SLTSTA_PDC != 0 {
+ self.slot_status &= !PCIE_SLTSTA_PDC;
+ }
+ if value & PCIE_SLTSTA_CC != 0 {
+ self.slot_status &= !PCIE_SLTSTA_CC;
+ }
+ if value & PCIE_SLTSTA_DLLSC != 0 {
+ self.slot_status &= !PCIE_SLTSTA_DLLSC;
+ }
+ }
+ PCIE_ROOTCTL_OFFSET => match u16::from_slice(data) {
+ Some(v) => self.root_control = *v,
+ None => warn!("write root control isn't word, len: {}", data.len()),
+ },
+ PCIE_ROOTSTA_OFFSET => match u32::from_slice(data) {
+ Some(v) => {
+ if *v & PCIE_ROOTSTA_PME_STATUS != 0 {
+ if let Some(request_id) = self.pme_pending_request_id {
+ self.root_status &= !PCIE_ROOTSTA_PME_PENDING;
+ let req_id = ((request_id.bus as u32) << 8)
+ | ((request_id.dev as u32) << 3)
+ | (request_id.func as u32);
+ self.root_status &= !PCIE_ROOTSTA_PME_REQ_ID_MASK;
+ self.root_status |= req_id;
+ self.root_status |= PCIE_ROOTSTA_PME_STATUS;
+ self.pme_pending_request_id = None;
+ self.trigger_pme_interrupt();
+ } else {
+ self.root_status &= !PCIE_ROOTSTA_PME_STATUS;
+ if self.hp_interrupt_pending {
+ self.hp_interrupt_pending = false;
+ self.trigger_hp_interrupt();
+ }
+ }
+ }
+ }
+ None => warn!("write root status isn't dword, len: {}", data.len()),
+ },
+ _ => (),
+ }
+ }
+
+ fn trigger_interrupt(&self) {
+ if let Some(msix_config) = &self.msix_config {
+ let mut msix_config = msix_config.lock();
+ if msix_config.enabled() {
+ msix_config.trigger(0)
+ }
+ }
+ }
+
+ fn trigger_cc_interrupt(&self) {
+ if (self.get_slot_control() & PCIE_SLTCTL_CCIE) != 0
+ && (self.slot_status & PCIE_SLTSTA_CC) != 0
+ {
+ self.trigger_interrupt()
+ }
+ }
+
+ fn trigger_hp_interrupt(&self) {
+ let slot_control = self.get_slot_control();
+ if (slot_control & PCIE_SLTCTL_HPIE) != 0
+ && (self.slot_status & slot_control & (PCIE_SLTCTL_ABPE | PCIE_SLTCTL_PDCE)) != 0
+ {
+ self.trigger_interrupt()
+ }
+ }
+
+ fn trigger_pme_interrupt(&self) {
+ if (self.root_control & PCIE_ROOTCTL_PME_ENABLE) != 0
+ && (self.root_status & PCIE_ROOTSTA_PME_STATUS) != 0
+ {
+ self.trigger_interrupt()
+ }
+ }
+
+ fn inject_pme(&mut self) {
+ if (self.root_status & PCIE_ROOTSTA_PME_STATUS) != 0 {
+ self.root_status |= PCIE_ROOTSTA_PME_PENDING;
+ self.pme_pending_request_id = self.pci_address;
+ } else {
+ let request_id = self.pci_address.unwrap();
+ let req_id = ((request_id.bus as u32) << 8)
+ | ((request_id.dev as u32) << 3)
+ | (request_id.func as u32);
+ self.root_status &= !PCIE_ROOTSTA_PME_REQ_ID_MASK;
+ self.root_status |= req_id;
+ self.pme_pending_request_id = None;
+ self.root_status |= PCIE_ROOTSTA_PME_STATUS;
+ self.trigger_pme_interrupt();
+ }
+ }
+
+ // when RP is D3, HP interrupt is disabled by pcie driver, so inject a PME to wakeup
+ // RP first, then inject HP interrupt.
+ fn trigger_hp_or_pme_interrupt(&mut self) {
+ if self.pmc_config.should_trigger_pme() {
+ self.hp_interrupt_pending = true;
+ self.inject_pme();
+ } else {
+ self.trigger_hp_interrupt();
+ }
+ }
+}
+
+impl PcieDevice for PcieRootPort {
+ fn get_device_id(&self) -> u16 {
+ match &self.pcie_host {
+ Some(host) => host.read_device_id(),
+ None => PCIE_RP_DID,
+ }
+ }
+
+ fn debug_label(&self) -> String {
+ match &self.pcie_host {
+ Some(host) => host.host_name(),
+ None => "PcieRootPort".to_string(),
+ }
+ }
+
+ fn allocate_address(
+ &mut self,
+ resources: &mut SystemAllocator,
+ ) -> std::result::Result<PciAddress, PciDeviceError> {
+ if self.pci_address.is_none() {
+ match &self.pcie_host {
+ Some(host) => {
+ let address = PciAddress::from_str(&host.host_name())
+ .map_err(|e| PciDeviceError::PciAddressParseFailure(host.host_name(), e))?;
+ if resources.reserve_pci(
+ Alloc::PciBar {
+ bus: address.bus,
+ dev: address.dev,
+ func: address.func,
+ bar: 0,
+ },
+ host.host_name(),
+ ) {
+ self.pci_address = Some(address);
+ } else {
+ self.pci_address = None;
+ }
+ }
+ None => match resources.allocate_pci(self.bus_range.primary, self.debug_label()) {
+ Some(Alloc::PciBar {
+ bus,
+ dev,
+ func,
+ bar: _,
+ }) => self.pci_address = Some(PciAddress { bus, dev, func }),
+ _ => self.pci_address = None,
+ },
+ }
+ }
+ self.pci_address.ok_or(PciDeviceError::PciAllocationFailed)
+ }
+
+ fn clone_interrupt(&mut self, msix_config: Arc<Mutex<MsixConfig>>) {
+ self.msix_config = Some(msix_config);
+ }
+
+ fn get_caps(&self) -> Vec<Box<dyn PciCapability>> {
+ vec![
+ Box::new(PcieCap::new(
+ PcieDevicePortType::RootPort,
+ self.slot_control.is_some(),
+ 0,
+ )),
+ Box::new(PciPmcCap::new()),
+ ]
+ }
+
+ fn set_capability_reg_idx(&mut self, id: PciCapabilityID, reg_idx: usize) {
+ match id {
+ PciCapabilityID::PciExpress => self.pcie_cap_reg_idx = Some(reg_idx),
+ PciCapabilityID::PowerManagement => self.pmc_cap_reg_idx = Some(reg_idx),
+ _ => (),
+ }
+ }
+
+ fn read_config(&self, reg_idx: usize, data: &mut u32) {
+ if let Some(pcie_cap_reg_idx) = self.pcie_cap_reg_idx {
+ if reg_idx >= pcie_cap_reg_idx && reg_idx < pcie_cap_reg_idx + (PCIE_CAP_LEN / 4) {
+ let offset = (reg_idx - pcie_cap_reg_idx) * 4;
+ self.read_pcie_cap(offset, data);
+ }
+ }
+ if let Some(pmc_cap_reg_idx) = self.pmc_cap_reg_idx {
+ if reg_idx == pmc_cap_reg_idx + PMC_CAP_CONTROL_STATE_OFFSET {
+ self.pmc_config.read(data);
+ }
+ }
+ if let Some(host) = &self.pcie_host {
+ // pcie host may override some config registers
+ host.read_config(reg_idx, data);
+ }
+ }
+
+ fn write_config(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
+ if let Some(pcie_cap_reg_idx) = self.pcie_cap_reg_idx {
+ if reg_idx >= pcie_cap_reg_idx && reg_idx < pcie_cap_reg_idx + (PCIE_CAP_LEN / 4) {
+ let delta = ((reg_idx - pcie_cap_reg_idx) * 4) + offset as usize;
+ self.write_pcie_cap(delta, data);
+ }
+ }
+ if let Some(pmc_cap_reg_idx) = self.pmc_cap_reg_idx {
+ if reg_idx == pmc_cap_reg_idx + PMC_CAP_CONTROL_STATE_OFFSET {
+ let old_status = self.pmc_config.get_power_status();
+ self.pmc_config.write(offset, data);
+ let new_status = self.pmc_config.get_power_status();
+ if old_status == PciDevicePower::D3
+ && new_status == PciDevicePower::D0
+ && self.prepare_hotplug
+ {
+ if let Some(host) = self.pcie_host.as_mut() {
+ host.hotplug_probe();
+ self.prepare_hotplug = false;
+ }
+ }
+ }
+ }
+ if let Some(host) = self.pcie_host.as_mut() {
+ // device may write data to host, or do something at specific register write
+ host.write_config(reg_idx, offset, data);
+ }
+ }
+
+ fn get_bus_range(&self) -> Option<PciBridgeBusRange> {
+ Some(self.bus_range)
+ }
+
+ fn get_removed_devices(&self) -> Vec<PciAddress> {
+ let mut removed_devices = Vec::new();
+ if let Some(removed_downstream) = self.removed_downstream {
+ removed_devices.push(removed_downstream);
+ }
+
+ removed_devices
+ }
+
+ fn hotplug_implemented(&self) -> bool {
+ self.slot_control.is_some()
+ }
+
+ fn get_bridge_window_size(&self) -> (u64, u64) {
+ if let Some(host) = &self.pcie_host {
+ host.get_bridge_window_size()
+ } else {
+ (PCIE_RP_BR_MEM_SIZE, PCIE_RP_BR_PREF_MEM_SIZE)
+ }
+ }
+}
+
+impl HotPlugBus for PcieRootPort {
+ fn hot_plug(&mut self, addr: PciAddress) {
+ match self.downstream_device {
+ Some((guest_addr, _)) => {
+ if guest_addr != addr {
+ return;
+ }
+ }
+ None => return,
+ }
+
+ self.slot_status = self.slot_status | PCIE_SLTSTA_PDS | PCIE_SLTSTA_PDC | PCIE_SLTSTA_ABP;
+ self.trigger_hp_or_pme_interrupt();
+ }
+
+ fn hot_unplug(&mut self, addr: PciAddress) {
+ match self.downstream_device {
+ Some((guest_addr, _)) => {
+ if guest_addr != addr {
+ return;
+ }
+ }
+ None => return,
+ }
+
+ self.slot_status = self.slot_status | PCIE_SLTSTA_PDC | PCIE_SLTSTA_ABP;
+ self.trigger_hp_or_pme_interrupt();
+
+ if let Some(host) = self.pcie_host.as_mut() {
+ host.hot_unplug();
+ }
+ }
+
+ fn is_match(&self, host_addr: PciAddress) -> Option<u8> {
+ let _ = self.slot_control?;
+
+ if self.downstream_device.is_none()
+ && ((host_addr.bus >= self.bus_range.secondary
+ && host_addr.bus <= self.bus_range.subordinate)
+ || self.pcie_host.is_none())
+ {
+ Some(self.bus_range.secondary)
+ } else {
+ None
+ }
+ }
+
+ fn add_hotplug_device(&mut self, host_key: HostHotPlugKey, guest_addr: PciAddress) {
+ if self.slot_control.is_none() {
+ return;
+ }
+
+ self.downstream_device = Some((guest_addr, Some(host_key)))
+ }
+
+ fn get_hotplug_device(&self, host_key: HostHotPlugKey) -> Option<PciAddress> {
+ if let Some((guest_address, Some(host_info))) = &self.downstream_device {
+ match host_info {
+ HostHotPlugKey::Vfio { host_addr } => {
+ let saved_addr = *host_addr;
+ match host_key {
+ HostHotPlugKey::Vfio { host_addr } => {
+ if host_addr == saved_addr {
+ return Some(*guest_address);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ None
+ }
+}
+
+impl GpeNotify for PcieRootPort {
+ fn notify(&mut self) {
+ if self.slot_control.is_none() {
+ return;
+ }
+
+ if self.pcie_host.is_some() {
+ self.prepare_hotplug = true;
+ }
+
+ if self.pmc_config.should_trigger_pme() {
+ self.inject_pme();
+ }
+ }
+}
diff --git a/devices/src/pci/pvpanic.rs b/devices/src/pci/pvpanic.rs
new file mode 100644
index 000000000..780618663
--- /dev/null
+++ b/devices/src/pci/pvpanic.rs
@@ -0,0 +1,245 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! pvpanic is a simulated device, through which a guest panic event is sent to a VMM.
+//! This was initially developed for qemu with linux in-tree drivers and opensource
+//! driver for windows also exist now.
+//! https://fossies.org/linux/qemu/docs/specs/pvpanic.txt
+//!
+//! This implementation emulates pci interface for pvpanic virtual device.
+
+// TODO(218575411): Support pvpanic on windows crosvm.
+#![cfg_attr(windows, allow(dead_code))]
+
+use std::fmt;
+
+use base::RawDescriptor;
+use base::{self, error, Tube};
+use resources::{Alloc, MmioType, SystemAllocator};
+
+use crate::pci::pci_configuration::{
+ PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciClassCode, PciConfiguration,
+ PciHeaderType, PciOtherSubclass,
+};
+use crate::pci::pci_device::{self, BarRange, PciDevice, Result};
+use crate::pci::{PciAddress, PciDeviceError, PCI_VENDOR_ID_REDHAT};
+
+const PCI_DEVICE_ID_REDHAT_PVPANIC: u16 = 0x0011;
+const PCI_PVPANIC_REVISION_ID: u8 = 1;
+
+const PVPANIC_REG_NUM: u8 = 0;
+const PVPANIC_REG_SIZE: u64 = 0x10;
+
+// Guest panicked
+pub const PVPANIC_PANICKED: u8 = 1 << 0;
+// Guest kexeced crash kernel
+pub const PVPANIC_CRASH_LOADED: u8 = 1 << 1;
+
+const PVPANIC_CAPABILITIES: u8 = PVPANIC_PANICKED | PVPANIC_CRASH_LOADED;
+
+#[repr(u8)]
+#[derive(PartialEq)]
+pub enum PvPanicCode {
+ Panicked = PVPANIC_PANICKED,
+ CrashLoaded = PVPANIC_CRASH_LOADED,
+ Unknown = 0xFF,
+}
+
+impl PvPanicCode {
+ pub fn from_u8(val: u8) -> PvPanicCode {
+ match val {
+ PVPANIC_PANICKED => PvPanicCode::Panicked,
+ PVPANIC_CRASH_LOADED => PvPanicCode::CrashLoaded,
+ _ => PvPanicCode::Unknown,
+ }
+ }
+}
+
+impl fmt::Display for PvPanicCode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ PvPanicCode::Panicked => write!(f, "Guest panicked"),
+ PvPanicCode::CrashLoaded => write!(f, "Guest panicked and crash kernel loaded"),
+ PvPanicCode::Unknown => write!(f, "Guest panicked with unknown code"),
+ }
+ }
+}
+
+pub struct PvPanicPciDevice {
+ pci_address: Option<PciAddress>,
+ config_regs: PciConfiguration,
+ evt_tube: Tube,
+}
+
+impl PvPanicPciDevice {
+ pub fn new(evt_tube: Tube) -> PvPanicPciDevice {
+ let config_regs = PciConfiguration::new(
+ PCI_VENDOR_ID_REDHAT,
+ PCI_DEVICE_ID_REDHAT_PVPANIC,
+ PciClassCode::Other,
+ &PciOtherSubclass::Other,
+ None,
+ PciHeaderType::Device,
+ 0xFF,
+ 0xFF,
+ PCI_PVPANIC_REVISION_ID,
+ );
+
+ Self {
+ pci_address: None,
+ config_regs,
+ evt_tube,
+ }
+ }
+}
+
+impl PciDevice for PvPanicPciDevice {
+ fn debug_label(&self) -> String {
+ "PvPanic".to_owned()
+ }
+
+ fn allocate_address(&mut self, resources: &mut SystemAllocator) -> Result<PciAddress> {
+ if self.pci_address.is_none() {
+ self.pci_address = match resources.allocate_pci(0, self.debug_label()) {
+ Some(Alloc::PciBar {
+ bus,
+ dev,
+ func,
+ bar: _,
+ }) => Some(PciAddress { bus, dev, func }),
+ _ => None,
+ }
+ }
+ self.pci_address.ok_or(PciDeviceError::PciAllocationFailed)
+ }
+
+ fn allocate_io_bars(&mut self, resources: &mut SystemAllocator) -> Result<Vec<BarRange>> {
+ let address = self
+ .pci_address
+ .expect("allocate_address must be called prior to allocate_io_bars");
+ let mut ranges: Vec<BarRange> = Vec::new();
+ let pvpanic_reg_addr = resources
+ .mmio_allocator(MmioType::Low)
+ .allocate_with_align(
+ PVPANIC_REG_SIZE,
+ Alloc::PciBar {
+ bus: address.bus,
+ dev: address.dev,
+ func: address.func,
+ bar: PVPANIC_REG_NUM,
+ },
+ "pvpanic_reg".to_string(),
+ PVPANIC_REG_SIZE,
+ )
+ .map_err(|e| pci_device::Error::IoAllocationFailed(PVPANIC_REG_SIZE, e))?;
+ let pvpanic_config = PciBarConfiguration::new(
+ PVPANIC_REG_NUM.into(),
+ PVPANIC_REG_SIZE,
+ PciBarRegionType::Memory32BitRegion,
+ PciBarPrefetchable::NotPrefetchable,
+ )
+ .set_address(pvpanic_reg_addr);
+ self.config_regs
+ .add_pci_bar(pvpanic_config)
+ .map_err(|e| pci_device::Error::IoRegistrationFailed(pvpanic_reg_addr, e))?;
+ ranges.push(BarRange {
+ addr: pvpanic_reg_addr,
+ size: PVPANIC_REG_SIZE,
+ prefetchable: false,
+ });
+
+ Ok(ranges)
+ }
+
+ fn keep_rds(&self) -> Vec<RawDescriptor> {
+ Vec::new()
+ }
+
+ fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
+ self.config_regs.get_bar_configuration(bar_num)
+ }
+
+ fn read_config_register(&self, reg_idx: usize) -> u32 {
+ self.config_regs.read_reg(reg_idx)
+ }
+
+ fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
+ self.config_regs.write_reg(reg_idx, offset, data)
+ }
+
+ fn read_bar(&mut self, addr: u64, data: &mut [u8]) {
+ let mmio_addr = self.config_regs.get_bar_addr(PVPANIC_REG_NUM as usize);
+ data[0] = if addr == mmio_addr && data.len() == 1 {
+ PVPANIC_CAPABILITIES
+ } else {
+ 0
+ };
+ }
+
+ fn write_bar(&mut self, addr: u64, data: &[u8]) {
+ let mmio_addr = self.config_regs.get_bar_addr(PVPANIC_REG_NUM as usize);
+ if addr != mmio_addr || data.len() != 1 {
+ return;
+ }
+ if let Err(e) = self.evt_tube.send::<u8>(&data[0]) {
+ error!("Failed to send to the panic event: {}", e);
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use resources::{MemRegion, SystemAllocator, SystemAllocatorConfig};
+
+ #[test]
+ fn pvpanic_read_write() {
+ let mut allocator = SystemAllocator::new(
+ SystemAllocatorConfig {
+ io: Some(MemRegion {
+ base: 0x1000,
+ size: 0x2000,
+ }),
+ low_mmio: MemRegion {
+ base: 0x2000_0000,
+ size: 0x1000_0000,
+ },
+ high_mmio: MemRegion {
+ base: 0x1_0000_0000,
+ size: 0x1000_0000,
+ },
+ platform_mmio: None,
+ first_irq: 5,
+ },
+ None,
+ &[],
+ )
+ .unwrap();
+
+ let (evt_rdtube, evt_wrtube) = Tube::pair().unwrap();
+ let mut device = PvPanicPciDevice::new(evt_wrtube);
+
+ assert!(device.allocate_address(&mut allocator).is_ok());
+ assert!(device.allocate_io_bars(&mut allocator).is_ok());
+
+ let mut data: [u8; 1] = [0; 1];
+
+ // Read from an invalid addr
+ device.read_bar(0, &mut data);
+ assert_eq!(data[0], 0);
+
+ // Read from the valid addr
+ let mmio_addr = device.config_regs.get_bar_addr(PVPANIC_REG_NUM as usize);
+ device.read_bar(mmio_addr, &mut data);
+ assert_eq!(data[0], PVPANIC_CAPABILITIES);
+
+ // Write to the valid addr.
+ data[0] = PVPANIC_CRASH_LOADED;
+ device.write_bar(mmio_addr, &data);
+
+ // Verify the event
+ let val = evt_rdtube.recv::<u8>().unwrap();
+ assert_eq!(val, PVPANIC_CRASH_LOADED);
+ }
+}
diff --git a/devices/src/pci/stub.rs b/devices/src/pci/stub.rs
new file mode 100644
index 000000000..08b17434b
--- /dev/null
+++ b/devices/src/pci/stub.rs
@@ -0,0 +1,187 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Implements a stub PCI device. This can be used to put a device on the PCI bus that will
+//! show up in PCI device enumeration with the configured parameters. The device will otherwise be
+//! non-functional, in particular it doesn't have any BARs, IRQs etc. and neither will it handle
+//! config register interactions.
+//!
+//! The motivation for stub PCI devices is the case of multifunction PCI devices getting passed
+//! through via VFIO to the guest. Per PCI device enumeration, functions other than 0 will only be
+//! scanned if function 0 is present. A stub PCI device is useful in that situation to present
+//! something to the guest on function 0.
+
+use base::RawDescriptor;
+use resources::{Alloc, SystemAllocator};
+
+use crate::pci::pci_configuration::{
+ PciBarConfiguration, PciClassCode, PciConfiguration, PciHeaderType, PciProgrammingInterface,
+ PciSubclass,
+};
+use crate::pci::pci_device::{PciDevice, Result};
+use crate::pci::{PciAddress, PciDeviceError};
+use serde::{Deserialize, Serialize};
+
+#[derive(Serialize, Deserialize)]
+pub struct StubPciParameters {
+ pub address: PciAddress,
+ pub vendor_id: u16,
+ pub device_id: u16,
+ pub class: PciClassCode,
+ pub subclass: u8,
+ pub programming_interface: u8,
+ pub subsystem_vendor_id: u16,
+ pub subsystem_device_id: u16,
+ pub revision_id: u8,
+}
+
+pub struct StubPciDevice {
+ requested_address: PciAddress,
+ assigned_address: Option<PciAddress>,
+ config_regs: PciConfiguration,
+}
+
+struct NumericPciSubClass(u8);
+
+impl PciSubclass for NumericPciSubClass {
+ fn get_register_value(&self) -> u8 {
+ self.0
+ }
+}
+
+struct NumericPciProgrammingInterface(u8);
+
+impl PciProgrammingInterface for NumericPciProgrammingInterface {
+ fn get_register_value(&self) -> u8 {
+ self.0
+ }
+}
+
+impl StubPciDevice {
+ pub fn new(config: &StubPciParameters) -> StubPciDevice {
+ let config_regs = PciConfiguration::new(
+ config.vendor_id,
+ config.device_id,
+ config.class,
+ &NumericPciSubClass(config.subclass),
+ Some(&NumericPciProgrammingInterface(
+ config.programming_interface,
+ )),
+ PciHeaderType::Device,
+ config.subsystem_vendor_id,
+ config.subsystem_device_id,
+ config.revision_id,
+ );
+
+ Self {
+ requested_address: config.address,
+ assigned_address: None,
+ config_regs,
+ }
+ }
+}
+
+impl PciDevice for StubPciDevice {
+ fn debug_label(&self) -> String {
+ "Stub".to_owned()
+ }
+
+ fn allocate_address(&mut self, resources: &mut SystemAllocator) -> Result<PciAddress> {
+ if self.assigned_address.is_none() {
+ if resources.reserve_pci(
+ Alloc::PciBar {
+ bus: self.requested_address.bus,
+ dev: self.requested_address.dev,
+ func: self.requested_address.func,
+ bar: 0,
+ },
+ self.debug_label(),
+ ) {
+ self.assigned_address = Some(self.requested_address);
+ }
+ }
+ self.assigned_address
+ .ok_or(PciDeviceError::PciAllocationFailed)
+ }
+
+ fn keep_rds(&self) -> Vec<RawDescriptor> {
+ Vec::new()
+ }
+
+ fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
+ self.config_regs.get_bar_configuration(bar_num)
+ }
+
+ fn read_config_register(&self, reg_idx: usize) -> u32 {
+ self.config_regs.read_reg(reg_idx)
+ }
+
+ fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
+ (&mut self.config_regs).write_reg(reg_idx, offset, data)
+ }
+
+ fn read_bar(&mut self, _addr: u64, _data: &mut [u8]) {}
+
+ fn write_bar(&mut self, _addr: u64, _data: &[u8]) {}
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use resources::{MemRegion, SystemAllocator, SystemAllocatorConfig};
+
+ const CONFIG: StubPciParameters = StubPciParameters {
+ address: PciAddress {
+ bus: 0x0a,
+ dev: 0x0b,
+ func: 0x1,
+ },
+ vendor_id: 2,
+ device_id: 3,
+ class: PciClassCode::MultimediaController,
+ subclass: 5,
+ programming_interface: 6,
+ subsystem_vendor_id: 7,
+ subsystem_device_id: 8,
+ revision_id: 9,
+ };
+
+ #[test]
+ fn configuration() {
+ let device = StubPciDevice::new(&CONFIG);
+
+ assert_eq!(device.read_config_register(0), 0x0003_0002);
+ assert_eq!(device.read_config_register(2), 0x04_05_06_09);
+ assert_eq!(device.read_config_register(11), 0x0008_0007);
+ }
+
+ #[test]
+ fn address_allocation() {
+ let mut allocator = SystemAllocator::new(
+ SystemAllocatorConfig {
+ io: Some(MemRegion {
+ base: 0x1000,
+ size: 0x2000,
+ }),
+ low_mmio: MemRegion {
+ base: 0x2000_0000,
+ size: 0x1000_0000,
+ },
+ high_mmio: MemRegion {
+ base: 0x1_0000_0000,
+ size: 0x1000_0000,
+ },
+ platform_mmio: None,
+ first_irq: 5,
+ },
+ None,
+ &[],
+ )
+ .unwrap();
+ let mut device = StubPciDevice::new(&CONFIG);
+
+ assert!(device.allocate_address(&mut allocator).is_ok());
+ assert!(allocator.release_pci(0xa, 0xb, 1));
+ }
+}
diff --git a/devices/src/pci/vfio_pci.rs b/devices/src/pci/vfio_pci.rs
index 46ee5372b..ca9dc38c7 100644
--- a/devices/src/pci/vfio_pci.rs
+++ b/devices/src/pci/vfio_pci.rs
@@ -2,97 +2,57 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#[cfg(feature = "direct")]
+use anyhow::Context;
+use std::cmp::{max, min, Reverse};
+use std::collections::{BTreeMap, BTreeSet};
+use std::fs;
+#[cfg(feature = "direct")]
+use std::path::Path;
+use std::path::PathBuf;
+use std::str::FromStr;
use std::sync::Arc;
+use std::thread;
use std::u32;
+use sync::Mutex;
use base::{
- error, pagesize, AsRawDescriptor, Event, MappedRegion, MemoryMapping, MemoryMappingBuilder,
- RawDescriptor, Tube,
+ error, pagesize, warn, AsRawDescriptor, AsRawDescriptors, Event, PollToken, RawDescriptor,
+ Tube, WaitContext,
};
-use hypervisor::Datamatch;
+use hypervisor::{Datamatch, MemSlot};
use resources::{Alloc, MmioType, SystemAllocator};
use vfio_sys::*;
-use vm_control::{VmIrqRequest, VmIrqResponse, VmMemoryRequest, VmMemoryResponse};
+use vm_control::{
+ VmIrqRequest, VmIrqResponse, VmMemoryDestination, VmMemoryRequest, VmMemoryResponse,
+ VmMemorySource, VmRequest, VmResponse,
+};
use crate::pci::msix::{
- MsixConfig, BITS_PER_PBA_ENTRY, MSIX_PBA_ENTRIES_MODULO, MSIX_TABLE_ENTRIES_MODULO,
+ MsixConfig, MsixStatus, BITS_PER_PBA_ENTRY, MSIX_PBA_ENTRIES_MODULO, MSIX_TABLE_ENTRIES_MODULO,
};
-use crate::pci::pci_device::{Error as PciDeviceError, PciDevice};
-use crate::pci::{PciAddress, PciClassCode, PciInterruptPin};
+#[cfg(feature = "direct")]
+use crate::pci::pci_configuration::{CLASS_REG, CLASS_REG_REVISION_ID_OFFSET, HEADER_TYPE_REG};
+use crate::pci::pci_device::{BarRange, Error as PciDeviceError, PciDevice};
+use crate::pci::{
+ PciAddress, PciBarConfiguration, PciBarIndex, PciBarPrefetchable, PciBarRegionType,
+ PciClassCode, PciId, PciInterruptPin, PCI_VENDOR_ID_INTEL,
+};
-use crate::vfio::{VfioDevice, VfioIrqType};
+use crate::vfio::{VfioDevice, VfioError, VfioIrqType, VfioPciConfig};
+use crate::IrqLevelEvent;
const PCI_VENDOR_ID: u32 = 0x0;
-const INTEL_VENDOR_ID: u16 = 0x8086;
+const PCI_DEVICE_ID: u32 = 0x2;
const PCI_COMMAND: u32 = 0x4;
const PCI_COMMAND_MEMORY: u8 = 0x2;
const PCI_BASE_CLASS_CODE: u32 = 0x0B;
-
+const PCI_INTERRUPT_NUM: u32 = 0x3C;
const PCI_INTERRUPT_PIN: u32 = 0x3D;
-struct VfioPciConfig {
- device: Arc<VfioDevice>,
-}
-
-impl VfioPciConfig {
- fn new(device: Arc<VfioDevice>) -> Self {
- VfioPciConfig { device }
- }
-
- #[allow(dead_code)]
- fn read_config_byte(&self, offset: u32) -> u8 {
- let mut data: [u8; 1] = [0];
- self.device
- .region_read(VFIO_PCI_CONFIG_REGION_INDEX, data.as_mut(), offset.into());
-
- data[0]
- }
-
- #[allow(dead_code)]
- fn read_config_word(&self, offset: u32) -> u16 {
- let mut data: [u8; 2] = [0, 0];
- self.device
- .region_read(VFIO_PCI_CONFIG_REGION_INDEX, data.as_mut(), offset.into());
-
- u16::from_le_bytes(data)
- }
-
- #[allow(dead_code)]
- fn read_config_dword(&self, offset: u32) -> u32 {
- let mut data: [u8; 4] = [0, 0, 0, 0];
- self.device
- .region_read(VFIO_PCI_CONFIG_REGION_INDEX, data.as_mut(), offset.into());
-
- u32::from_le_bytes(data)
- }
-
- #[allow(dead_code)]
- fn write_config_byte(&self, buf: u8, offset: u32) {
- self.device.region_write(
- VFIO_PCI_CONFIG_REGION_INDEX,
- ::std::slice::from_ref(&buf),
- offset.into(),
- )
- }
-
- #[allow(dead_code)]
- fn write_config_word(&self, buf: u16, offset: u32) {
- let data: [u8; 2] = buf.to_le_bytes();
- self.device
- .region_write(VFIO_PCI_CONFIG_REGION_INDEX, &data, offset.into())
- }
-
- #[allow(dead_code)]
- fn write_config_dword(&self, buf: u32, offset: u32) {
- let data: [u8; 4] = buf.to_le_bytes();
- self.device
- .region_write(VFIO_PCI_CONFIG_REGION_INDEX, &data, offset.into())
- }
-}
-
const PCI_CAPABILITY_LIST: u32 = 0x34;
const PCI_CAP_ID_MSI: u8 = 0x05;
const PCI_CAP_ID_MSIX: u8 = 0x11;
@@ -117,6 +77,7 @@ const MSI_LENGTH_64BIT_WITH_MASK: u32 = 0x18;
enum VfioMsiChange {
Disable,
Enable,
+ FunctionChanged,
}
struct VfioMsiCap {
@@ -129,11 +90,19 @@ struct VfioMsiCap {
vm_socket_irq: Tube,
irqfd: Option<Event>,
gsi: Option<u32>,
+ device_id: u32,
+ device_name: String,
}
impl VfioMsiCap {
- fn new(config: &VfioPciConfig, msi_cap_start: u32, vm_socket_irq: Tube) -> Self {
- let msi_ctl = config.read_config_word(msi_cap_start + PCI_MSI_FLAGS);
+ fn new(
+ config: &VfioPciConfig,
+ msi_cap_start: u32,
+ vm_socket_irq: Tube,
+ device_id: u32,
+ device_name: String,
+ ) -> Self {
+ let msi_ctl: u16 = config.read_config(msi_cap_start + PCI_MSI_FLAGS);
VfioMsiCap {
offset: msi_cap_start,
@@ -145,22 +114,17 @@ impl VfioMsiCap {
vm_socket_irq,
irqfd: None,
gsi: None,
+ device_id,
+ device_name,
}
}
fn is_msi_reg(&self, index: u64, len: usize) -> bool {
- let msi_len: u32 = if self.is_64bit {
- if self.mask_cap {
- MSI_LENGTH_64BIT_WITH_MASK
- } else {
- MSI_LENGTH_64BIT_WITHOUT_MASK
- }
- } else {
- if self.mask_cap {
- MSI_LENGTH_32BIT_WITH_MASK
- } else {
- MSI_LENGTH_32BIT_WITHOUT_MASK
- }
+ let msi_len = match (self.is_64bit, self.mask_cap) {
+ (true, true) => MSI_LENGTH_64BIT_WITH_MASK,
+ (true, false) => MSI_LENGTH_64BIT_WITHOUT_MASK,
+ (false, true) => MSI_LENGTH_32BIT_WITH_MASK,
+ (false, false) => MSI_LENGTH_32BIT_WITHOUT_MASK,
};
index >= self.offset as u64
@@ -262,12 +226,17 @@ impl VfioMsiCap {
},
};
- let request = VmIrqRequest::AllocateOneMsi { irqfd };
+ let request = VmIrqRequest::AllocateOneMsi {
+ irqfd,
+ device_id: self.device_id,
+ queue_id: 0,
+ device_name: self.device_name.clone(),
+ };
let request_result = self.vm_socket_irq.send(&request);
// Stash the irqfd in self immediately because we used take above.
self.irqfd = match request {
- VmIrqRequest::AllocateOneMsi { irqfd } => Some(irqfd),
+ VmIrqRequest::AllocateOneMsi { irqfd, .. } => Some(irqfd),
_ => unreachable!(),
};
@@ -293,6 +262,17 @@ impl VfioMsiCap {
fn get_msi_irqfd(&self) -> Option<&Event> {
self.irqfd.as_ref()
}
+
+ fn destroy(&mut self) {
+ if let Some(gsi) = self.gsi {
+ if let Some(irqfd) = self.irqfd.take() {
+ let request = VmIrqRequest::ReleaseOneIrq { gsi, irqfd };
+ if self.vm_socket_irq.send(&request).is_ok() {
+ let _ = self.vm_socket_irq.recv::<VmIrqResponse>();
+ }
+ }
+ }
+ }
}
// MSI-X registers in MSI-X capability
@@ -311,29 +291,56 @@ struct VfioMsixCap {
table_size: u16,
table_pci_bar: u32,
table_offset: u64,
+ table_size_bytes: u64,
pba_pci_bar: u32,
pba_offset: u64,
+ pba_size_bytes: u64,
+ msix_interrupt_evt: Vec<Event>,
}
impl VfioMsixCap {
- fn new(config: &VfioPciConfig, msix_cap_start: u32, vm_socket_irq: Tube) -> Self {
- let msix_ctl = config.read_config_word(msix_cap_start + PCI_MSIX_FLAGS);
- let table_size = (msix_ctl & PCI_MSIX_FLAGS_QSIZE) + 1;
- let table = config.read_config_dword(msix_cap_start + PCI_MSIX_TABLE);
+ fn new(
+ config: &VfioPciConfig,
+ msix_cap_start: u32,
+ vm_socket_irq: Tube,
+ pci_id: u32,
+ device_name: String,
+ ) -> Self {
+ let msix_ctl: u16 = config.read_config(msix_cap_start + PCI_MSIX_FLAGS);
+ let table: u32 = config.read_config(msix_cap_start + PCI_MSIX_TABLE);
let table_pci_bar = table & PCI_MSIX_TABLE_BIR;
let table_offset = (table & PCI_MSIX_TABLE_OFFSET) as u64;
- let pba = config.read_config_dword(msix_cap_start + PCI_MSIX_PBA);
+ let pba: u32 = config.read_config(msix_cap_start + PCI_MSIX_PBA);
let pba_pci_bar = pba & PCI_MSIX_PBA_BIR;
let pba_offset = (pba & PCI_MSIX_PBA_OFFSET) as u64;
+ let mut table_size = (msix_ctl & PCI_MSIX_FLAGS_QSIZE) as u64 + 1;
+ if table_pci_bar == pba_pci_bar
+ && pba_offset > table_offset
+ && (table_offset + table_size * MSIX_TABLE_ENTRIES_MODULO) > pba_offset
+ {
+ table_size = (pba_offset - table_offset) / MSIX_TABLE_ENTRIES_MODULO;
+ }
+
+ let table_size_bytes = table_size * MSIX_TABLE_ENTRIES_MODULO;
+ let pba_size_bytes = ((table_size + BITS_PER_PBA_ENTRY as u64 - 1)
+ / BITS_PER_PBA_ENTRY as u64)
+ * MSIX_PBA_ENTRIES_MODULO;
+ let mut msix_interrupt_evt = Vec::new();
+ for _ in 0..table_size {
+ msix_interrupt_evt.push(Event::new().expect("failed to create msix interrupt"));
+ }
VfioMsixCap {
- config: MsixConfig::new(table_size, vm_socket_irq),
+ config: MsixConfig::new(table_size as u16, vm_socket_irq, pci_id, device_name),
offset: msix_cap_start,
- table_size,
+ table_size: table_size as u16,
table_pci_bar,
table_offset,
+ table_size_bytes,
pba_pci_bar,
pba_offset,
+ pba_size_bytes,
+ msix_interrupt_evt,
}
}
@@ -351,25 +358,37 @@ impl VfioMsixCap {
fn write_msix_control(&mut self, data: &[u8]) -> Option<VfioMsiChange> {
let old_enabled = self.config.enabled();
+ let old_masked = self.config.masked();
self.config
.write_msix_capability(PCI_MSIX_FLAGS.into(), data);
let new_enabled = self.config.enabled();
+ let new_masked = self.config.masked();
+
if !old_enabled && new_enabled {
Some(VfioMsiChange::Enable)
} else if old_enabled && !new_enabled {
Some(VfioMsiChange::Disable)
+ } else if new_enabled && old_masked != new_masked {
+ Some(VfioMsiChange::FunctionChanged)
} else {
None
}
}
fn is_msix_table(&self, bar_index: u32, offset: u64) -> bool {
- let table_size: u64 = (self.table_size * (MSIX_TABLE_ENTRIES_MODULO as u16)).into();
bar_index == self.table_pci_bar
&& offset >= self.table_offset
- && offset < self.table_offset + table_size
+ && offset < self.table_offset + self.table_size_bytes
+ }
+
+ fn get_msix_table(&self, bar_index: u32) -> Option<(u64, u64)> {
+ if bar_index == self.table_pci_bar {
+ Some((self.table_offset, self.table_size_bytes))
+ } else {
+ None
+ }
}
fn read_table(&self, offset: u64, data: &mut [u8]) {
@@ -377,18 +396,23 @@ impl VfioMsixCap {
self.config.read_msix_table(offset, data);
}
- fn write_table(&mut self, offset: u64, data: &[u8]) {
+ fn write_table(&mut self, offset: u64, data: &[u8]) -> MsixStatus {
let offset = offset - self.table_offset;
- self.config.write_msix_table(offset, data);
+ self.config.write_msix_table(offset, data)
}
fn is_msix_pba(&self, bar_index: u32, offset: u64) -> bool {
- let pba_size: u64 = (((self.table_size + BITS_PER_PBA_ENTRY as u16 - 1)
- / BITS_PER_PBA_ENTRY as u16)
- * MSIX_PBA_ENTRIES_MODULO as u16) as u64;
bar_index == self.pba_pci_bar
&& offset >= self.pba_offset
- && offset < self.pba_offset + pba_size
+ && offset < self.pba_offset + self.pba_size_bytes
+ }
+
+ fn get_msix_pba(&self, bar_index: u32) -> Option<(u64, u64)> {
+ if bar_index == self.pba_pci_bar {
+ Some((self.pba_offset, self.pba_size_bytes))
+ } else {
+ None
+ }
}
fn read_pba(&self, offset: u64, data: &mut [u8]) {
@@ -401,34 +425,224 @@ impl VfioMsixCap {
self.config.write_pba_entries(offset, data);
}
- fn is_msix_bar(&self, bar_index: u32) -> bool {
- bar_index == self.table_pci_bar || bar_index == self.pba_pci_bar
+ fn get_msix_irqfd(&self, index: usize) -> Option<&Event> {
+ let irqfd = self.config.get_irqfd(index);
+ if let Some(fd) = irqfd {
+ if self.msix_vector_masked(index) {
+ Some(&self.msix_interrupt_evt[index])
+ } else {
+ Some(fd)
+ }
+ } else {
+ None
+ }
}
- fn get_msix_irqfds(&self) -> Option<Vec<&Event>> {
+ fn get_msix_irqfds(&self) -> Vec<Option<&Event>> {
let mut irqfds = Vec::new();
for i in 0..self.table_size {
- let irqfd = self.config.get_irqfd(i as usize);
- if let Some(fd) = irqfd {
- irqfds.push(fd);
- } else {
- return None;
+ irqfds.push(self.get_msix_irqfd(i as usize));
+ }
+
+ irqfds
+ }
+
+ fn table_size(&self) -> usize {
+ self.table_size.into()
+ }
+
+ fn clone_msix_evt(&self) -> Vec<Event> {
+ self.msix_interrupt_evt
+ .iter()
+ .map(|irq| irq.try_clone().unwrap())
+ .collect()
+ }
+
+ fn msix_vector_masked(&self, index: usize) -> bool {
+ !self.config.enabled() || self.config.masked() || self.config.table_masked(index)
+ }
+
+ fn trigger(&mut self, index: usize) {
+ self.config.trigger(index as u16);
+ }
+
+ fn destroy(&mut self) {
+ self.config.destroy()
+ }
+}
+
+struct VfioResourceAllocator {
+ // memory regions unoccupied by VFIO resources
+ // stores sets of (start, end) tuples, where `end` is the address of the
+ // last byte in the region
+ regions: BTreeSet<(u64, u64)>,
+}
+
+impl VfioResourceAllocator {
+ // Creates a new `VfioResourceAllocator` for managing VFIO resources.
+ // Can return `Err` if `base` + `size` overflows a u64.
+ //
+ // * `base` - The starting address of the range to manage.
+ // * `size` - The size of the address range in bytes.
+ fn new(base: u64, size: u64) -> Result<Self, PciDeviceError> {
+ if size == 0 {
+ return Err(PciDeviceError::SizeZero);
+ }
+ let end = base
+ .checked_add(size - 1)
+ .ok_or(PciDeviceError::Overflow(base, size))?;
+ let mut regions = BTreeSet::new();
+ regions.insert((base, end));
+ Ok(VfioResourceAllocator { regions })
+ }
+
+ /// Allocates a range of addresses from the managed region with a minimal alignment.
+ /// Returns allocated_address.
+ pub fn allocate_with_align(
+ &mut self,
+ size: u64,
+ alignment: u64,
+ ) -> Result<u64, PciDeviceError> {
+ if size == 0 {
+ return Err(PciDeviceError::SizeZero);
+ }
+ if !alignment.is_power_of_two() {
+ return Err(PciDeviceError::BadAlignment);
+ }
+
+ // finds first region matching alignment and size.
+ match self
+ .regions
+ .iter()
+ .find(|range| {
+ match range.0 % alignment {
+ 0 => range.0.checked_add(size - 1),
+ r => range.0.checked_add(size - 1 + alignment - r),
+ }
+ .map_or(false, |end| end <= range.1)
+ })
+ .cloned()
+ {
+ Some(slot) => {
+ self.regions.remove(&slot);
+ let start = match slot.0 % alignment {
+ 0 => slot.0,
+ r => slot.0 + alignment - r,
+ };
+ let end = start + size - 1;
+ if slot.0 < start {
+ self.regions.insert((slot.0, start - 1));
+ }
+ if slot.1 > end {
+ self.regions.insert((end + 1, slot.1));
+ }
+ Ok(start)
}
+ None => Err(PciDeviceError::OutOfSpace),
}
+ }
- Some(irqfds)
+ // Allocates a range of addresses from the managed region with a required location.
+ // Returns a new range of addresses excluding the required range.
+ fn allocate_at(&mut self, start: u64, size: u64) -> Result<(), PciDeviceError> {
+ if size == 0 {
+ return Err(PciDeviceError::SizeZero);
+ }
+ let end = start
+ .checked_add(size - 1)
+ .ok_or(PciDeviceError::OutOfSpace)?;
+ while let Some(slot) = self
+ .regions
+ .iter()
+ .find(|range| (start <= range.1 && end >= range.0))
+ .cloned()
+ {
+ self.regions.remove(&slot);
+ if slot.0 < start {
+ self.regions.insert((slot.0, start - 1));
+ }
+ if slot.1 > end {
+ self.regions.insert((end + 1, slot.1));
+ }
+ }
+ Ok(())
}
}
-struct MmioInfo {
- bar_index: u32,
- start: u64,
- length: u64,
+struct VfioPciWorker {
+ vm_socket: Tube,
+ name: String,
+ msix_cap: Option<Arc<Mutex<VfioMsixCap>>>,
}
-struct IoInfo {
- bar_index: u32,
+impl VfioPciWorker {
+ fn run(&mut self, req_irq_evt: Event, kill_evt: Event, msix_evt: Vec<Event>) {
+ #[derive(PollToken)]
+ enum Token {
+ ReqIrq,
+ Kill,
+ MsixIrqi { index: usize },
+ }
+
+ let wait_ctx: WaitContext<Token> = match WaitContext::build_with(&[
+ (&req_irq_evt, Token::ReqIrq),
+ (&kill_evt, Token::Kill),
+ ]) {
+ Ok(pc) => pc,
+ Err(e) => {
+ error!(
+ "{} failed creating vfio WaitContext: {}",
+ self.name.clone(),
+ e
+ );
+ return;
+ }
+ };
+
+ for (index, msix_int) in msix_evt.iter().enumerate() {
+ wait_ctx
+ .add(msix_int, Token::MsixIrqi { index })
+ .expect("Failed to create vfio WaitContext for msix interrupt event")
+ }
+
+ 'wait: loop {
+ let events = match wait_ctx.wait() {
+ Ok(v) => v,
+ Err(e) => {
+ error!("{} failed polling vfio events: {}", self.name.clone(), e);
+ break;
+ }
+ };
+
+ for event in events.iter().filter(|e| e.is_readable) {
+ match event.token {
+ Token::MsixIrqi { index } => {
+ if let Some(msix_cap) = &self.msix_cap {
+ msix_cap.lock().trigger(index);
+ }
+ }
+ Token::ReqIrq => {
+ let mut sysfs_path = PathBuf::new();
+ sysfs_path.push("/sys/bus/pci/devices/");
+ sysfs_path.push(self.name.clone());
+ let request = VmRequest::VfioCommand {
+ vfio_path: sysfs_path,
+ add: false,
+ };
+ if self.vm_socket.send(&request).is_ok() {
+ if let Err(e) = self.vm_socket.recv::<VmResponse>() {
+ error!("{} failed to remove vfio_device: {}", self.name.clone(), e);
+ } else {
+ break 'wait;
+ }
+ }
+ }
+ Token::Kill => break 'wait,
+ }
+ }
+ }
+ }
}
enum DeviceData {
@@ -439,56 +653,83 @@ enum DeviceData {
pub struct VfioPciDevice {
device: Arc<VfioDevice>,
config: VfioPciConfig,
+ hotplug_bus_number: Option<u8>, // hot plug device has bus number specified at device creation.
+ guest_address: Option<PciAddress>,
pci_address: Option<PciAddress>,
- interrupt_evt: Option<Event>,
- interrupt_resample_evt: Option<Event>,
- mmio_regions: Vec<MmioInfo>,
- io_regions: Vec<IoInfo>,
+ interrupt_evt: Option<IrqLevelEvent>,
+ mmio_regions: Vec<PciBarConfiguration>,
+ io_regions: Vec<PciBarConfiguration>,
msi_cap: Option<VfioMsiCap>,
- msix_cap: Option<VfioMsixCap>,
+ msix_cap: Option<Arc<Mutex<VfioMsixCap>>>,
irq_type: Option<VfioIrqType>,
vm_socket_mem: Tube,
device_data: Option<DeviceData>,
-
- // scratch MemoryMapping to avoid unmap beform vm exit
- mem: Vec<MemoryMapping>,
+ kill_evt: Option<Event>,
+ worker_thread: Option<thread::JoinHandle<VfioPciWorker>>,
+ vm_socket_vm: Option<Tube>,
+ #[cfg(feature = "direct")]
+ sysfs_path: Option<PathBuf>,
+ #[cfg(feature = "direct")]
+ header_type_reg: Option<u32>,
+
+ mapped_mmio_bars: BTreeMap<PciBarIndex, (u64, Vec<MemSlot>)>,
}
impl VfioPciDevice {
/// Constructs a new Vfio Pci device for the give Vfio device
pub fn new(
+ #[cfg(feature = "direct")] sysfs_path: &Path,
device: VfioDevice,
+ hotplug_bus_number: Option<u8>,
+ guest_address: Option<PciAddress>,
vfio_device_socket_msi: Tube,
vfio_device_socket_msix: Tube,
vfio_device_socket_mem: Tube,
+ vfio_device_socket_vm: Option<Tube>,
) -> Self {
let dev = Arc::new(device);
let config = VfioPciConfig::new(Arc::clone(&dev));
let mut msi_socket = Some(vfio_device_socket_msi);
let mut msix_socket = Some(vfio_device_socket_msix);
let mut msi_cap: Option<VfioMsiCap> = None;
- let mut msix_cap: Option<VfioMsixCap> = None;
+ let mut msix_cap: Option<Arc<Mutex<VfioMsixCap>>> = None;
+
+ let mut cap_next: u32 = config.read_config::<u8>(PCI_CAPABILITY_LIST).into();
+ let vendor_id: u16 = config.read_config(PCI_VENDOR_ID);
+ let device_id: u16 = config.read_config(PCI_DEVICE_ID);
+
+ let pci_id = PciId::new(vendor_id, device_id);
- let mut cap_next: u32 = config.read_config_byte(PCI_CAPABILITY_LIST).into();
while cap_next != 0 {
- let cap_id = config.read_config_byte(cap_next);
+ let cap_id: u8 = config.read_config(cap_next);
if cap_id == PCI_CAP_ID_MSI {
if let Some(msi_socket) = msi_socket.take() {
- msi_cap = Some(VfioMsiCap::new(&config, cap_next, msi_socket));
+ msi_cap = Some(VfioMsiCap::new(
+ &config,
+ cap_next,
+ msi_socket,
+ pci_id.into(),
+ dev.device_name().to_string(),
+ ));
}
} else if cap_id == PCI_CAP_ID_MSIX {
if let Some(msix_socket) = msix_socket.take() {
- msix_cap = Some(VfioMsixCap::new(&config, cap_next, msix_socket));
+ msix_cap = Some(Arc::new(Mutex::new(VfioMsixCap::new(
+ &config,
+ cap_next,
+ msix_socket,
+ pci_id.into(),
+ dev.device_name().to_string(),
+ ))));
}
}
let offset = cap_next + PCI_MSI_NEXT_POINTER;
- cap_next = config.read_config_byte(offset).into();
+ cap_next = config.read_config::<u8>(offset).into();
}
- let vendor_id = config.read_config_word(PCI_VENDOR_ID);
- let class_code = config.read_config_byte(PCI_BASE_CLASS_CODE);
+ let class_code: u8 = config.read_config(PCI_BASE_CLASS_CODE);
- let is_intel_gfx = vendor_id == INTEL_VENDOR_ID
+ let is_intel_gfx = vendor_id == PCI_VENDOR_ID_INTEL
&& class_code == PciClassCode::DisplayController.get_register_value();
let device_data = if is_intel_gfx {
Some(DeviceData::IntelGfxData {
@@ -498,12 +739,32 @@ impl VfioPciDevice {
None
};
+ #[cfg(feature = "direct")]
+ let (sysfs_path, header_type_reg) = match VfioPciDevice::coordinated_pm(sysfs_path, true) {
+ Ok(_) => {
+ // Cache the dword at offset 0x0c (cacheline size, latency timer,
+ // header type, BIST).
+ // When using the "direct" feature, this dword can be accessed for
+ // device power state. Directly accessing a device's physical PCI
+ // config space in D3cold state causes a hang. We treat the cacheline
+ // size, latency timer and header type field as immutable in the
+ // guest.
+ let reg: u32 = config.read_config((HEADER_TYPE_REG as u32) * 4);
+ (Some(sysfs_path.to_path_buf()), Some(reg))
+ }
+ Err(e) => {
+ warn!("coordinated_pm not supported: {}", e);
+ (None, None)
+ }
+ };
+
VfioPciDevice {
device: dev,
config,
+ hotplug_bus_number,
+ guest_address,
pci_address: None,
interrupt_evt: None,
- interrupt_resample_evt: None,
mmio_regions: Vec::new(),
io_regions: Vec::new(),
msi_cap,
@@ -511,7 +772,14 @@ impl VfioPciDevice {
irq_type: None,
vm_socket_mem: vfio_device_socket_mem,
device_data,
- mem: Vec::new(),
+ kill_evt: None,
+ worker_thread: None,
+ vm_socket_vm: vfio_device_socket_vm,
+ #[cfg(feature = "direct")]
+ sysfs_path,
+ #[cfg(feature = "direct")]
+ header_type_reg,
+ mapped_mmio_bars: BTreeMap::new(),
}
}
@@ -527,14 +795,10 @@ impl VfioPciDevice {
ret
}
- fn find_region(&self, addr: u64) -> Option<MmioInfo> {
+ fn find_region(&self, addr: u64) -> Option<PciBarConfiguration> {
for mmio_info in self.mmio_regions.iter() {
- if addr >= mmio_info.start && addr < mmio_info.start + mmio_info.length {
- return Some(MmioInfo {
- bar_index: mmio_info.bar_index,
- start: mmio_info.start,
- length: mmio_info.length,
- });
+ if addr >= mmio_info.address() && addr < mmio_info.address() + mmio_info.size() {
+ return Some(*mmio_info);
}
}
@@ -542,45 +806,40 @@ impl VfioPciDevice {
}
fn enable_intx(&mut self) {
- if self.interrupt_evt.is_none() || self.interrupt_resample_evt.is_none() {
- return;
- }
-
if let Some(ref interrupt_evt) = self.interrupt_evt {
- let mut fds = Vec::new();
- fds.push(interrupt_evt);
- if let Err(e) = self.device.irq_enable(fds, VFIO_PCI_INTX_IRQ_INDEX) {
- error!("Intx enable failed: {}", e);
+ if let Err(e) = self.device.irq_enable(
+ &[Some(interrupt_evt.get_trigger())],
+ VFIO_PCI_INTX_IRQ_INDEX,
+ 0,
+ ) {
+ error!("{} Intx enable failed: {}", self.debug_label(), e);
return;
}
- if let Some(ref irq_resample_evt) = self.interrupt_resample_evt {
- if let Err(e) = self.device.irq_mask(VFIO_PCI_INTX_IRQ_INDEX) {
- error!("Intx mask failed: {}", e);
- self.disable_intx();
- return;
- }
- if let Err(e) = self
- .device
- .resample_virq_enable(irq_resample_evt, VFIO_PCI_INTX_IRQ_INDEX)
- {
- error!("resample enable failed: {}", e);
- self.disable_intx();
- return;
- }
- if let Err(e) = self.device.irq_unmask(VFIO_PCI_INTX_IRQ_INDEX) {
- error!("Intx unmask failed: {}", e);
- self.disable_intx();
- return;
- }
+ if let Err(e) = self.device.irq_mask(VFIO_PCI_INTX_IRQ_INDEX) {
+ error!("{} Intx mask failed: {}", self.debug_label(), e);
+ self.disable_intx();
+ return;
+ }
+ if let Err(e) = self
+ .device
+ .resample_virq_enable(interrupt_evt.get_resample(), VFIO_PCI_INTX_IRQ_INDEX)
+ {
+ error!("{} resample enable failed: {}", self.debug_label(), e);
+ self.disable_intx();
+ return;
}
+ if let Err(e) = self.device.irq_unmask(VFIO_PCI_INTX_IRQ_INDEX) {
+ error!("{} Intx unmask failed: {}", self.debug_label(), e);
+ self.disable_intx();
+ return;
+ }
+ self.irq_type = Some(VfioIrqType::Intx);
}
-
- self.irq_type = Some(VfioIrqType::Intx);
}
fn disable_intx(&mut self) {
if let Err(e) = self.device.irq_disable(VFIO_PCI_INTX_IRQ_INDEX) {
- error!("Intx disable failed: {}", e);
+ error!("{} Intx disable failed: {}", self.debug_label(), e);
}
self.irq_type = None;
}
@@ -617,10 +876,11 @@ impl VfioPciDevice {
}
};
- let mut fds = Vec::new();
- fds.push(irqfd);
- if let Err(e) = self.device.irq_enable(fds, VFIO_PCI_MSI_IRQ_INDEX) {
- error!("failed to enable msi: {}", e);
+ if let Err(e) = self
+ .device
+ .irq_enable(&[Some(irqfd)], VFIO_PCI_MSI_IRQ_INDEX, 0)
+ {
+ error!("{} failed to enable msi: {}", self.debug_label(), e);
self.enable_intx();
return;
}
@@ -630,58 +890,168 @@ impl VfioPciDevice {
fn disable_msi(&mut self) {
if let Err(e) = self.device.irq_disable(VFIO_PCI_MSI_IRQ_INDEX) {
- error!("failed to disable msi: {}", e);
+ error!("{} failed to disable msi: {}", self.debug_label(), e);
return;
}
+ self.irq_type = None;
self.enable_intx();
}
fn enable_msix(&mut self) {
- self.disable_irqs();
-
- let irqfds = match &self.msix_cap {
- Some(cap) => cap.get_msix_irqfds(),
- None => return,
- };
+ if self.msix_cap.is_none() {
+ return;
+ }
- if let Some(descriptors) = irqfds {
- if let Err(e) = self.device.irq_enable(descriptors, VFIO_PCI_MSIX_IRQ_INDEX) {
- error!("failed to enable msix: {}", e);
- self.enable_intx();
- return;
+ self.disable_irqs();
+ let cap = self.msix_cap.as_ref().unwrap().lock();
+ let vector_in_use = cap.get_msix_irqfds().iter().any(|&irq| irq.is_some());
+
+ let mut failed = false;
+ if !vector_in_use {
+ // If there are no msix vectors currently in use, we explicitly assign a new eventfd
+ // to vector 0. Then we enable it and immediately disable it, so that vfio will
+ // activate physical device. If there are available msix vectors, just enable them
+ // instead.
+ let fd = Event::new().expect("failed to create event");
+ let table_size = cap.table_size();
+ let mut irqfds = vec![None; table_size];
+ irqfds[0] = Some(&fd);
+ for fd in irqfds.iter_mut().skip(1) {
+ *fd = None;
+ }
+ if let Err(e) = self.device.irq_enable(&irqfds, VFIO_PCI_MSIX_IRQ_INDEX, 0) {
+ error!("{} failed to enable msix: {}", self.debug_label(), e);
+ failed = true;
+ }
+ irqfds[0] = None;
+ if let Err(e) = self.device.irq_enable(&irqfds, VFIO_PCI_MSIX_IRQ_INDEX, 0) {
+ error!("{} failed to enable msix: {}", self.debug_label(), e);
+ failed = true;
}
} else {
+ let result = self
+ .device
+ .irq_enable(&cap.get_msix_irqfds(), VFIO_PCI_MSIX_IRQ_INDEX, 0);
+ if let Err(e) = result {
+ error!("{} failed to enable msix: {}", self.debug_label(), e);
+ failed = true;
+ }
+ }
+
+ std::mem::drop(cap);
+ if failed {
self.enable_intx();
return;
}
-
self.irq_type = Some(VfioIrqType::Msix);
}
fn disable_msix(&mut self) {
+ if self.msix_cap.is_none() {
+ return;
+ }
if let Err(e) = self.device.irq_disable(VFIO_PCI_MSIX_IRQ_INDEX) {
- error!("failed to disable msix: {}", e);
+ error!("{} failed to disable msix: {}", self.debug_label(), e);
return;
}
-
+ self.irq_type = None;
self.enable_intx();
}
- fn add_bar_mmap(&self, index: u32, bar_addr: u64) -> Vec<MemoryMapping> {
- let mut mem_map: Vec<MemoryMapping> = Vec::new();
+ fn msix_vectors_update(&self) -> Result<(), VfioError> {
+ if let Some(cap) = &self.msix_cap {
+ self.device
+ .irq_enable(&cap.lock().get_msix_irqfds(), VFIO_PCI_MSIX_IRQ_INDEX, 0)?;
+ }
+ Ok(())
+ }
+
+ fn msix_vector_update(&self, index: usize, irqfd: Option<&Event>) {
+ if let Err(e) = self
+ .device
+ .irq_enable(&[irqfd], VFIO_PCI_MSIX_IRQ_INDEX, index as u32)
+ {
+ error!(
+ "{} failed to update msix vector {}: {}",
+ self.debug_label(),
+ index,
+ e
+ );
+ }
+ }
+
+ fn add_bar_mmap_msix(
+ &self,
+ bar_index: u32,
+ bar_mmaps: Vec<vfio_region_sparse_mmap_area>,
+ ) -> Vec<vfio_region_sparse_mmap_area> {
+ let msix_cap = &self.msix_cap.as_ref().unwrap().lock();
+ let mut msix_mmaps: Vec<(u64, u64)> = Vec::new();
+
+ if let Some(t) = msix_cap.get_msix_table(bar_index) {
+ msix_mmaps.push(t);
+ }
+ if let Some(p) = msix_cap.get_msix_pba(bar_index) {
+ msix_mmaps.push(p);
+ }
+
+ if msix_mmaps.is_empty() {
+ return bar_mmaps;
+ }
+
+ let mut mmaps: Vec<vfio_region_sparse_mmap_area> = Vec::with_capacity(bar_mmaps.len());
+ let pgmask = (pagesize() as u64) - 1;
+
+ for mmap in bar_mmaps.iter() {
+ let mmap_offset = mmap.offset as u64;
+ let mmap_size = mmap.size as u64;
+ let mut to_mmap = match VfioResourceAllocator::new(mmap_offset, mmap_size) {
+ Ok(a) => a,
+ Err(e) => {
+ error!("{} add_bar_mmap_msix failed: {}", self.debug_label(), e);
+ mmaps.clear();
+ return mmaps;
+ }
+ };
+
+ // table/pba offsets are qword-aligned - align to page size
+ for &(msix_offset, msix_size) in msix_mmaps.iter() {
+ if msix_offset >= mmap_offset && msix_offset < mmap_offset + mmap_size {
+ let begin = max(msix_offset, mmap_offset) & !pgmask;
+ let end =
+ (min(msix_offset + msix_size, mmap_offset + mmap_size) + pgmask) & !pgmask;
+ if end > begin {
+ if let Err(e) = to_mmap.allocate_at(begin, end - begin) {
+ error!("add_bar_mmap_msix failed: {}", e);
+ }
+ }
+ }
+ }
+
+ for mmap in to_mmap.regions {
+ mmaps.push(vfio_region_sparse_mmap_area {
+ offset: mmap.0,
+ size: mmap.1 - mmap.0 + 1,
+ });
+ }
+ }
+
+ mmaps
+ }
+
+ fn add_bar_mmap(&self, index: u32, bar_addr: u64) -> Vec<MemSlot> {
+ let mut mmaps_slots: Vec<MemSlot> = Vec::new();
if self.device.get_region_flags(index) & VFIO_REGION_INFO_FLAG_MMAP != 0 {
// the bar storing msix table and pba couldn't mmap.
// these bars should be trapped, so that msix could be emulated.
- if let Some(msix_cap) = &self.msix_cap {
- if msix_cap.is_msix_bar(index) {
- return mem_map;
- }
- }
+ let mut mmaps = self.device.get_region_mmap(index);
- let mmaps = self.device.get_region_mmap(index);
+ if self.msix_cap.is_some() {
+ mmaps = self.add_bar_mmap_msix(index, mmaps);
+ }
if mmaps.is_empty() {
- return mem_map;
+ return mmaps_slots;
}
for mmap in mmaps.iter() {
@@ -696,11 +1066,14 @@ impl VfioPciDevice {
};
if self
.vm_socket_mem
- .send(&VmMemoryRequest::RegisterMmapMemory {
- descriptor,
- size: mmap_size as usize,
- offset,
- gpa: guest_map_start,
+ .send(&VmMemoryRequest::RegisterMemory {
+ source: VmMemorySource::Descriptor {
+ descriptor,
+ offset,
+ size: mmap_size,
+ },
+ dest: VmMemoryDestination::GuestPhysicalAddress(guest_map_start),
+ read_only: false,
})
.is_err()
{
@@ -712,50 +1085,425 @@ impl VfioPciDevice {
Err(_) => break,
};
match response {
- VmMemoryResponse::Ok => {
- // Even if vm has mapped this region, but it is in vm main process,
- // device process doesn't has this mapping, but vfio_dma_map() need it
- // in device process, so here map it again.
- let mmap = match MemoryMappingBuilder::new(mmap_size as usize)
- .from_file(self.device.device_file())
- .offset(offset)
- .build()
- {
- Ok(v) => v,
- Err(_e) => break,
- };
- let host = (&mmap).as_ptr() as u64;
- let pgsz = pagesize() as u64;
- let size = (mmap_size + pgsz - 1) / pgsz * pgsz;
- // Safe because the given guest_map_start is valid guest bar address. and
- // the host pointer is correct and valid guaranteed by MemoryMapping interface.
- // The size will be extened to page size aligned if it is not which is also
- // safe because VFIO actually maps the BAR with page size aligned size.
- match unsafe { self.device.vfio_dma_map(guest_map_start, size, host) } {
- Ok(_) => mem_map.push(mmap),
- Err(e) => {
- error!(
- "{}, index: {}, bar_addr:0x{:x}, host:0x{:x}",
- e, index, bar_addr, host
- );
- break;
- }
- }
+ VmMemoryResponse::RegisterMemory { pfn: _, slot } => {
+ mmaps_slots.push(slot);
}
_ => break,
}
}
}
- mem_map
+ mmaps_slots
+ }
+
+ fn remove_bar_mmap(&self, mmap_slots: &[MemSlot]) {
+ for mmap_slot in mmap_slots {
+ if self
+ .vm_socket_mem
+ .send(&VmMemoryRequest::UnregisterMemory(*mmap_slot))
+ .is_err()
+ {
+ error!("failed to send UnregisterMemory request");
+ return;
+ }
+ if self.vm_socket_mem.recv::<VmMemoryResponse>().is_err() {
+ error!("failed to receive UnregisterMemory response");
+ }
+ }
+ }
+
+ fn disable_bars_mmap(&mut self) {
+ for (_, (_, mmap_slots)) in self.mapped_mmio_bars.iter() {
+ self.remove_bar_mmap(mmap_slots);
+ }
+ self.mapped_mmio_bars.clear();
}
- fn enable_bars_mmap(&mut self) {
+ fn commit_bars_mmap(&mut self) {
+ // Unmap all bars before remapping bars, to prevent issues with overlap
+ let mut needs_map = Vec::new();
for mmio_info in self.mmio_regions.iter() {
- let mut mem_map = self.add_bar_mmap(mmio_info.bar_index, mmio_info.start);
- self.mem.append(&mut mem_map);
+ let bar_idx = mmio_info.bar_index();
+ let addr = mmio_info.address();
+
+ if let Some((cur_addr, slots)) = self.mapped_mmio_bars.remove(&bar_idx) {
+ if cur_addr == addr {
+ self.mapped_mmio_bars.insert(bar_idx, (cur_addr, slots));
+ continue;
+ } else {
+ self.remove_bar_mmap(&slots);
+ }
+ }
+
+ if addr != 0 {
+ needs_map.push((bar_idx, addr));
+ }
+ }
+
+ for (bar_idx, addr) in needs_map.iter() {
+ let slots = self.add_bar_mmap(*bar_idx as u32, *addr);
+ self.mapped_mmio_bars.insert(*bar_idx, (*addr, slots));
}
}
+
+ fn close(&mut self) {
+ if let Some(msi) = self.msi_cap.as_mut() {
+ msi.destroy();
+ }
+ if let Some(msix) = &self.msix_cap {
+ msix.lock().destroy();
+ }
+ self.disable_bars_mmap();
+ self.device.close();
+ }
+
+ fn start_work_thread(&mut self) {
+ let vm_socket = match self.vm_socket_vm.take() {
+ Some(socket) => socket,
+ None => return,
+ };
+
+ let req_evt = match Event::new() {
+ Ok(evt) => {
+ if let Err(e) = self
+ .device
+ .irq_enable(&[Some(&evt)], VFIO_PCI_REQ_IRQ_INDEX, 0)
+ {
+ error!("{} enable req_irq failed: {}", self.debug_label(), e);
+ return;
+ }
+ evt
+ }
+ Err(_) => return,
+ };
+
+ let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
+ Ok(v) => v,
+ Err(e) => {
+ error!(
+ "{} failed creating kill Event pair: {}",
+ self.debug_label(),
+ e
+ );
+ return;
+ }
+ };
+ self.kill_evt = Some(self_kill_evt);
+
+ let mut msix_evt = Vec::new();
+ if let Some(msix_cap) = &self.msix_cap {
+ msix_evt = msix_cap.lock().clone_msix_evt();
+ }
+
+ let name = self.device.device_name().to_string();
+ let msix_cap = self.msix_cap.clone();
+ let worker_result = thread::Builder::new()
+ .name("vfio_pci".to_string())
+ .spawn(move || {
+ let mut worker = VfioPciWorker {
+ vm_socket,
+ name,
+ msix_cap,
+ };
+ worker.run(req_evt, kill_evt, msix_evt);
+ worker
+ });
+
+ match worker_result {
+ Err(e) => {
+ error!(
+ "{} failed to spawn vfio_pci worker: {}",
+ self.debug_label(),
+ e
+ );
+ }
+ Ok(join_handle) => {
+ self.worker_thread = Some(join_handle);
+ }
+ }
+ }
+
+ fn collect_bars(&mut self) -> Vec<PciBarConfiguration> {
+ let mut i = VFIO_PCI_BAR0_REGION_INDEX;
+ let mut mem_bars: Vec<PciBarConfiguration> = Vec::new();
+
+ while i <= VFIO_PCI_ROM_REGION_INDEX {
+ let mut low: u32 = 0xffffffff;
+ let offset: u32 = if i == VFIO_PCI_ROM_REGION_INDEX {
+ 0x30
+ } else {
+ 0x10 + i * 4
+ };
+ self.config.write_config(low, offset);
+ low = self.config.read_config(offset);
+
+ let low_flag = low & 0xf;
+ let is_64bit = low_flag & 0x4 == 0x4;
+ if (low_flag & 0x1 == 0 || i == VFIO_PCI_ROM_REGION_INDEX) && low != 0 {
+ let mut upper: u32 = 0xffffffff;
+ if is_64bit {
+ self.config.write_config(upper, offset + 4);
+ upper = self.config.read_config(offset + 4);
+ }
+
+ low &= 0xffff_fff0;
+ let mut size: u64 = u64::from(upper);
+ size <<= 32;
+ size |= u64::from(low);
+ size = !size + 1;
+ let region_type = if is_64bit {
+ PciBarRegionType::Memory64BitRegion
+ } else {
+ PciBarRegionType::Memory32BitRegion
+ };
+ let prefetch = if low_flag & 0x8 == 0x8 {
+ PciBarPrefetchable::Prefetchable
+ } else {
+ PciBarPrefetchable::NotPrefetchable
+ };
+ mem_bars.push(PciBarConfiguration::new(
+ i as usize,
+ size,
+ region_type,
+ prefetch,
+ ));
+ } else if low_flag & 0x1 == 0x1 {
+ let size = !(low & 0xffff_fffc) + 1;
+ self.io_regions.push(PciBarConfiguration::new(
+ i as usize,
+ size.into(),
+ PciBarRegionType::IoRegion,
+ PciBarPrefetchable::NotPrefetchable,
+ ));
+ }
+
+ if is_64bit {
+ i += 2;
+ } else {
+ i += 1;
+ }
+ }
+ mem_bars
+ }
+
+ fn configure_barmem(&mut self, bar_info: &PciBarConfiguration, bar_addr: u64) {
+ let offset: u32 = bar_info.reg_index() as u32 * 4;
+ let mmio_region = *bar_info;
+ self.mmio_regions.push(mmio_region.set_address(bar_addr));
+
+ let val: u32 = self.config.read_config(offset);
+ let low = ((bar_addr & !0xf) as u32) | (val & 0xf);
+ self.config.write_config(low, offset);
+ if bar_info.is_64bit_memory() {
+ let upper = (bar_addr >> 32) as u32;
+ self.config.write_config(upper, offset + 4);
+ }
+ }
+
+ fn allocate_root_barmem(
+ &mut self,
+ mem_bars: &[PciBarConfiguration],
+ resources: &mut SystemAllocator,
+ ) -> Result<Vec<BarRange>, PciDeviceError> {
+ let address = self.pci_address.unwrap();
+ let mut ranges: Vec<BarRange> = Vec::new();
+ for mem_bar in mem_bars {
+ let mmio_type = if mem_bar.is_64bit_memory() {
+ MmioType::High
+ } else {
+ MmioType::Low
+ };
+ let bar_size = mem_bar.size();
+ let mut bar_addr: u64 = 0;
+ // Don't allocate mmio for hotplug device, OS will allocate it from
+ // its parent's bridge window.
+ if self.hotplug_bus_number.is_none() {
+ bar_addr = resources
+ .mmio_allocator(mmio_type)
+ .allocate_with_align(
+ bar_size,
+ Alloc::PciBar {
+ bus: address.bus,
+ dev: address.dev,
+ func: address.func,
+ bar: mem_bar.bar_index() as u8,
+ },
+ "vfio_bar".to_string(),
+ bar_size,
+ )
+ .map_err(|e| PciDeviceError::IoAllocationFailed(bar_size, e))?;
+ ranges.push(BarRange {
+ addr: bar_addr,
+ size: bar_size,
+ prefetchable: mem_bar.is_prefetchable(),
+ });
+ }
+ self.configure_barmem(mem_bar, bar_addr);
+ }
+ Ok(ranges)
+ }
+
+ fn allocate_nonroot_barmem(
+ &mut self,
+ mem_bars: &mut [PciBarConfiguration],
+ resources: &mut SystemAllocator,
+ ) -> Result<Vec<BarRange>, PciDeviceError> {
+ const NON_PREFETCHABLE: usize = 0;
+ const PREFETCHABLE: usize = 1;
+ const ARRAY_SIZE: usize = 2;
+ let mut membars: [Vec<PciBarConfiguration>; ARRAY_SIZE] = [Vec::new(), Vec::new()];
+ let mut allocator: [VfioResourceAllocator; ARRAY_SIZE] = [
+ match VfioResourceAllocator::new(0, u32::MAX as u64) {
+ Ok(a) => a,
+ Err(e) => {
+ error!(
+ "{} init nonroot VfioResourceAllocator failed: {}",
+ self.debug_label(),
+ e
+ );
+ return Err(e);
+ }
+ },
+ match VfioResourceAllocator::new(0, u64::MAX) {
+ Ok(a) => a,
+ Err(e) => {
+ error!(
+ "{} init nonroot VfioResourceAllocator failed: {}",
+ self.debug_label(),
+ e
+ );
+ return Err(e);
+ }
+ },
+ ];
+ let mut memtype: [MmioType; ARRAY_SIZE] = [MmioType::Low, MmioType::High];
+ // the window must be 1M-aligned as per the PCI spec
+ let mut window_sz: [u64; ARRAY_SIZE] = [0; 2];
+ let mut alignment: [u64; ARRAY_SIZE] = [0x100000; 2];
+
+ // Descend by bar size, this could reduce allocated size for all the bars.
+ mem_bars.sort_by_key(|a| Reverse(a.size()));
+ for mem_bar in mem_bars {
+ let prefetchable = mem_bar.is_prefetchable();
+ let is_64bit = mem_bar.is_64bit_memory();
+
+ // if one prefetchable bar is 32bit, all the prefetchable bars should be in Low MMIO,
+ // as all the prefetchable bars should be in one region
+ if prefetchable && !is_64bit {
+ memtype[PREFETCHABLE] = MmioType::Low;
+ }
+ let i = if prefetchable {
+ PREFETCHABLE
+ } else {
+ NON_PREFETCHABLE
+ };
+ let bar_size = mem_bar.size();
+ let start = match allocator[i].allocate_with_align(bar_size, bar_size) {
+ Ok(s) => s,
+ Err(e) => {
+ error!(
+ "{} nonroot allocate_wit_align failed: {}",
+ self.debug_label(),
+ e
+ );
+ return Err(e);
+ }
+ };
+ window_sz[i] = max(window_sz[i], start + bar_size);
+ alignment[i] = max(alignment[i], bar_size);
+ let mem_info = (*mem_bar).set_address(start);
+ membars[i].push(mem_info);
+ }
+
+ let address = self.pci_address.unwrap();
+ let mut ranges: Vec<BarRange> = Vec::new();
+ for (index, bars) in membars.iter().enumerate() {
+ if bars.is_empty() {
+ continue;
+ }
+
+ let i = if index == 1 {
+ PREFETCHABLE
+ } else {
+ NON_PREFETCHABLE
+ };
+ let mut window_addr: u64 = 0;
+ // Don't allocate mmio for hotplug device, OS will allocate it from
+ // its parent's bridge window.
+ if self.hotplug_bus_number.is_none() {
+ window_sz[i] = (window_sz[i] + 0xfffff) & !0xfffff;
+ let alloc = if i == NON_PREFETCHABLE {
+ Alloc::PciBridgeWindow {
+ bus: address.bus,
+ dev: address.dev,
+ func: address.func,
+ }
+ } else {
+ Alloc::PciBridgePrefetchWindow {
+ bus: address.bus,
+ dev: address.dev,
+ func: address.func,
+ }
+ };
+ window_addr = resources
+ .mmio_allocator(memtype[i])
+ .allocate_with_align(
+ window_sz[i],
+ alloc,
+ "vfio_bar_window".to_string(),
+ alignment[i],
+ )
+ .map_err(|e| PciDeviceError::IoAllocationFailed(window_sz[i], e))?;
+ for mem_info in bars {
+ let bar_addr = window_addr + mem_info.address();
+ ranges.push(BarRange {
+ addr: bar_addr,
+ size: mem_info.size(),
+ prefetchable: mem_info.is_prefetchable(),
+ });
+ }
+ }
+
+ for mem_info in bars {
+ let bar_addr = window_addr + mem_info.address();
+ self.configure_barmem(mem_info, bar_addr);
+ }
+ }
+ Ok(ranges)
+ }
+
+ #[cfg(feature = "direct")]
+ fn coordinated_pm(sysfs_path: &Path, enter: bool) -> anyhow::Result<()> {
+ let path = Path::new(sysfs_path).join("power/coordinated");
+ fs::write(&path, if enter { "enter\n" } else { "exit\n" })
+ .with_context(|| format!("Failed to write to {}", path.to_string_lossy()))
+ }
+
+ #[cfg(feature = "direct")]
+ fn power_state(&self) -> anyhow::Result<u8> {
+ let path = Path::new(&self.sysfs_path.as_ref().unwrap()).join("power_state");
+ let state = fs::read_to_string(&path)
+ .with_context(|| format!("Failed to read from {}", path.to_string_lossy()))?;
+ match state.as_str() {
+ "D0\n" => Ok(0),
+ "D1\n" => Ok(1),
+ "D2\n" => Ok(2),
+ "D3hot\n" => Ok(3),
+ "D3cold\n" => Ok(4),
+ "unknown\n" => Ok(5),
+ _ => Err(std::io::Error::new(
+ std::io::ErrorKind::InvalidData,
+ "invalid state",
+ ))?,
+ }
+ }
+
+ #[cfg(feature = "direct")]
+ fn op_call(&self, id: u8) -> anyhow::Result<()> {
+ let path = Path::new(self.sysfs_path.as_ref().unwrap()).join("power/op_call");
+ fs::write(&path, &[id])
+ .with_context(|| format!("Failed to write to {}", path.to_string_lossy()))
+ }
}
impl PciDevice for VfioPciDevice {
@@ -768,7 +1516,19 @@ impl PciDevice for VfioPciDevice {
resources: &mut SystemAllocator,
) -> Result<PciAddress, PciDeviceError> {
if self.pci_address.is_none() {
- let address = PciAddress::from_string(self.device.device_name());
+ let mut address = self.guest_address.unwrap_or(
+ PciAddress::from_str(self.device.device_name()).map_err(|e| {
+ PciDeviceError::PciAddressParseFailure(self.device.device_name().clone(), e)
+ })?,
+ );
+ if let Some(bus_num) = self.hotplug_bus_number {
+ // Caller specify pcie bus number for hotplug device
+ address.bus = bus_num;
+ // devfn should be 0, otherwise pcie root port couldn't detect it
+ address.dev = 0;
+ address.func = 0;
+ }
+
if resources.reserve_pci(
Alloc::PciBar {
bus: address.bus,
@@ -787,132 +1547,83 @@ impl PciDevice for VfioPciDevice {
fn keep_rds(&self) -> Vec<RawDescriptor> {
let mut rds = self.device.keep_rds();
if let Some(ref interrupt_evt) = self.interrupt_evt {
- rds.push(interrupt_evt.as_raw_descriptor());
- }
- if let Some(ref interrupt_resample_evt) = self.interrupt_resample_evt {
- rds.push(interrupt_resample_evt.as_raw_descriptor());
+ rds.extend(interrupt_evt.as_raw_descriptors());
}
rds.push(self.vm_socket_mem.as_raw_descriptor());
if let Some(msi_cap) = &self.msi_cap {
rds.push(msi_cap.vm_socket_irq.as_raw_descriptor());
}
if let Some(msix_cap) = &self.msix_cap {
- rds.push(msix_cap.config.as_raw_descriptor());
+ rds.push(msix_cap.lock().config.as_raw_descriptor());
}
rds
}
fn assign_irq(
&mut self,
- irq_evt: Event,
- irq_resample_evt: Event,
- irq_num: u32,
- _irq_pin: PciInterruptPin,
- ) {
- self.config.write_config_byte(irq_num as u8, 0x3C);
- self.interrupt_evt = Some(irq_evt);
- self.interrupt_resample_evt = Some(irq_resample_evt);
+ irq_evt: &IrqLevelEvent,
+ _irq_num: Option<u32>,
+ ) -> Option<(u32, PciInterruptPin)> {
+ // Is INTx configured?
+ let pin = match self.config.read_config::<u8>(PCI_INTERRUPT_PIN) {
+ 1 => Some(PciInterruptPin::IntA),
+ 2 => Some(PciInterruptPin::IntB),
+ 3 => Some(PciInterruptPin::IntC),
+ 4 => Some(PciInterruptPin::IntD),
+ _ => None,
+ }?;
+
+ // Keep event/resample event references.
+ self.interrupt_evt = Some(irq_evt.try_clone().ok()?);
// enable INTX
- if self.config.read_config_byte(PCI_INTERRUPT_PIN) > 0 {
- self.enable_intx();
- }
+ self.enable_intx();
+
+ // TODO: replace sysfs/irq value parsing with vfio interface
+ // reporting host allocated interrupt number and type.
+ let mut path = PathBuf::from("/sys/bus/pci/devices");
+ path.push(self.device.device_name());
+ path.push("irq");
+ let gsi = fs::read_to_string(path)
+ .map(|v| v.trim().parse::<u32>().unwrap_or(0))
+ .unwrap_or(0);
+
+ self.config.write_config(gsi as u8, PCI_INTERRUPT_NUM);
+
+ Some((gsi, pin))
}
fn allocate_io_bars(
&mut self,
resources: &mut SystemAllocator,
- ) -> Result<Vec<(u64, u64)>, PciDeviceError> {
- let mut ranges = Vec::new();
- let mut i = VFIO_PCI_BAR0_REGION_INDEX;
+ ) -> Result<Vec<BarRange>, PciDeviceError> {
let address = self
.pci_address
- .expect("allocate_address must be called prior to allocate_io_bars");
-
- while i <= VFIO_PCI_ROM_REGION_INDEX {
- let mut low: u32 = 0xffffffff;
- let offset: u32;
- if i == VFIO_PCI_ROM_REGION_INDEX {
- offset = 0x30;
- } else {
- offset = 0x10 + i * 4;
- }
- self.config.write_config_dword(low, offset);
- low = self.config.read_config_dword(offset);
-
- let low_flag = low & 0xf;
- let is_64bit = low_flag & 0x4 == 0x4;
- if (low_flag & 0x1 == 0 || i == VFIO_PCI_ROM_REGION_INDEX) && low != 0 {
- let mut upper: u32 = 0xffffffff;
- if is_64bit {
- self.config.write_config_dword(upper, offset + 4);
- upper = self.config.read_config_dword(offset + 4);
- }
-
- low &= 0xffff_fff0;
- let mut size: u64 = u64::from(upper);
- size <<= 32;
- size |= u64::from(low);
- size = !size + 1;
- let mmio_type = match is_64bit {
- false => MmioType::Low,
- true => MmioType::High,
- };
- let bar_addr = resources
- .mmio_allocator(mmio_type)
- .allocate_with_align(
- size,
- Alloc::PciBar {
- bus: address.bus,
- dev: address.dev,
- func: address.func,
- bar: i as u8,
- },
- "vfio_bar".to_string(),
- size,
- )
- .map_err(|e| PciDeviceError::IoAllocationFailed(size, e))?;
- ranges.push((bar_addr, size));
- self.mmio_regions.push(MmioInfo {
- bar_index: i,
- start: bar_addr,
- length: size,
- });
+ .expect("allocate_address must be called prior to allocate_device_bars");
- low = bar_addr as u32;
- low |= low_flag;
- self.config.write_config_dword(low, offset);
- if is_64bit {
- upper = (bar_addr >> 32) as u32;
- self.config.write_config_dword(upper, offset + 4);
- }
- } else if low_flag & 0x1 == 0x1 {
- self.io_regions.push(IoInfo { bar_index: i });
- }
+ let mut mem_bars = self.collect_bars();
- if is_64bit {
- i += 2;
- } else {
- i += 1;
- }
- }
+ let ranges = if address.bus == 0 {
+ self.allocate_root_barmem(&mem_bars, resources)?
+ } else {
+ self.allocate_nonroot_barmem(&mut mem_bars, resources)?
+ };
// Quirk, enable igd memory for guest vga arbitrate, otherwise kernel vga arbitrate
// driver doesn't claim this vga device, then xorg couldn't boot up.
if self.is_intel_gfx() {
- let mut cmd = self.config.read_config_byte(PCI_COMMAND);
+ let mut cmd = self.config.read_config::<u8>(PCI_COMMAND);
cmd |= PCI_COMMAND_MEMORY;
- self.config.write_config_byte(cmd, PCI_COMMAND);
+ self.config.write_config(cmd, PCI_COMMAND);
}
-
Ok(ranges)
}
fn allocate_device_bars(
&mut self,
resources: &mut SystemAllocator,
- ) -> Result<Vec<(u64, u64)>, PciDeviceError> {
- let mut ranges = Vec::new();
+ ) -> Result<Vec<BarRange>, PciDeviceError> {
+ let mut ranges: Vec<BarRange> = Vec::new();
if !self.is_intel_gfx() {
return Ok(ranges);
@@ -921,7 +1632,7 @@ impl PciDevice for VfioPciDevice {
// Make intel gfx's opregion as mmio bar, and allocate a gpa for it
// then write this gpa into pci cfg register
if let Some((index, size)) = self.device.get_cap_type_info(
- VFIO_REGION_TYPE_PCI_VENDOR_TYPE | (INTEL_VENDOR_ID as u32),
+ VFIO_REGION_TYPE_PCI_VENDOR_TYPE | (PCI_VENDOR_ID_INTEL as u32),
VFIO_REGION_SUBTYPE_INTEL_IGD_OPREGION,
) {
let address = self
@@ -940,22 +1651,45 @@ impl PciDevice for VfioPciDevice {
"vfio_bar".to_string(),
)
.map_err(|e| PciDeviceError::IoAllocationFailed(size, e))?;
- ranges.push((bar_addr, size));
+ ranges.push(BarRange {
+ addr: bar_addr,
+ size,
+ prefetchable: false,
+ });
self.device_data = Some(DeviceData::IntelGfxData {
opregion_index: index,
});
- self.mmio_regions.push(MmioInfo {
- bar_index: index,
- start: bar_addr,
- length: size,
- });
- self.config.write_config_dword(bar_addr as u32, 0xFC);
+ self.mmio_regions.push(
+ PciBarConfiguration::new(
+ index as usize,
+ size,
+ PciBarRegionType::Memory32BitRegion,
+ PciBarPrefetchable::NotPrefetchable,
+ )
+ .set_address(bar_addr),
+ );
+ self.config.write_config(bar_addr as u32, 0xFC);
}
Ok(ranges)
}
+ fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
+ for region in self.mmio_regions.iter().chain(self.io_regions.iter()) {
+ if region.bar_index() == bar_num {
+ let command: u8 = self.config.read_config(PCI_COMMAND);
+ if (region.is_memory() && (command & PCI_COMMAND_MEMORY == 0)) || region.is_io() {
+ return None;
+ } else {
+ return Some(*region);
+ }
+ }
+ }
+
+ None
+ }
+
fn register_device_capabilities(&mut self) -> Result<(), PciDeviceError> {
Ok(())
}
@@ -965,18 +1699,34 @@ impl PciDevice for VfioPciDevice {
}
fn read_config_register(&self, reg_idx: usize) -> u32 {
- let reg: u32 = (reg_idx * 4) as u32;
+ #[cfg(feature = "direct")]
+ if reg_idx == HEADER_TYPE_REG {
+ if let Some(header_type_reg) = self.header_type_reg {
+ let mut v = header_type_reg.to_le_bytes();
+ // HACK
+ // Reads from the "BIST" register are interpreted as device
+ // PCI power state
+ v[3] = self.power_state().unwrap_or_else(|e| {
+ error!("Failed to get device power state: {}", e);
+ 5 // unknown state
+ });
+ return u32::from_le_bytes(v);
+ }
+ }
- let mut config = self.config.read_config_dword(reg);
+ let reg: u32 = (reg_idx * 4) as u32;
+ let mut config: u32 = self.config.read_config(reg);
// Ignore IO bar
if (0x10..=0x24).contains(&reg) {
- for io_info in self.io_regions.iter() {
- if io_info.bar_index * 4 + 0x10 == reg {
+ let bar_idx = (reg as usize - 0x10) / 4;
+ if let Some(bar) = self.get_bar_configuration(bar_idx) {
+ if bar.is_io() {
config = 0;
}
}
} else if let Some(msix_cap) = &self.msix_cap {
+ let msix_cap = msix_cap.lock();
if msix_cap.is_msix_control_reg(reg, 4) {
msix_cap.read_msix_control(&mut config);
}
@@ -991,6 +1741,26 @@ impl PciDevice for VfioPciDevice {
}
fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) {
+ // When guest write config register at the first time, start worker thread
+ if self.worker_thread.is_none() && self.vm_socket_vm.is_some() {
+ self.start_work_thread();
+ };
+
+ #[cfg(feature = "direct")]
+ if self.sysfs_path.is_some()
+ && reg_idx == CLASS_REG
+ && offset == CLASS_REG_REVISION_ID_OFFSET as u64
+ && data.len() == 1
+ {
+ // HACK
+ // Byte writes to the "Revision ID" register are interpreted as PM
+ // op calls
+ if let Err(e) = self.op_call(data[0]) {
+ error!("Failed to perform op call: {}", e);
+ }
+ return;
+ }
+
let start = (reg_idx * 4) as u64 + offset;
let mut msi_change: Option<VfioMsiChange> = None;
@@ -1003,39 +1773,106 @@ impl PciDevice for VfioPciDevice {
match msi_change {
Some(VfioMsiChange::Enable) => self.enable_msi(),
Some(VfioMsiChange::Disable) => self.disable_msi(),
- None => (),
+ _ => (),
}
msi_change = None;
- if let Some(msix_cap) = self.msix_cap.as_mut() {
+ if let Some(msix_cap) = &self.msix_cap {
+ let mut msix_cap = msix_cap.lock();
if msix_cap.is_msix_control_reg(start as u32, data.len() as u32) {
msi_change = msix_cap.write_msix_control(data);
}
}
+
match msi_change {
Some(VfioMsiChange::Enable) => self.enable_msix(),
Some(VfioMsiChange::Disable) => self.disable_msix(),
- None => (),
+ Some(VfioMsiChange::FunctionChanged) => {
+ if let Err(e) = self.msix_vectors_update() {
+ error!("update msix vectors failed: {}", e);
+ }
+ }
+ _ => (),
}
+ self.device
+ .region_write(VFIO_PCI_CONFIG_REGION_INDEX, data, start);
+
// if guest enable memory access, then enable bar mappable once
if start == PCI_COMMAND as u64
&& data.len() == 2
&& data[0] & PCI_COMMAND_MEMORY == PCI_COMMAND_MEMORY
- && self.mem.is_empty()
{
- self.enable_bars_mmap();
+ self.commit_bars_mmap();
+ } else if (0x10..=0x24).contains(&start) && data.len() == 4 {
+ let bar_idx = (start as u32 - 0x10) / 4;
+ let value: [u8; 4] = [data[0], data[1], data[2], data[3]];
+ let val = u32::from_le_bytes(value);
+ let mut modify = false;
+ for region in self.mmio_regions.iter_mut() {
+ if region.bar_index() == bar_idx as usize {
+ let old_addr = region.address();
+ let new_addr = val & 0xFFFFFFF0;
+ if !region.is_64bit_memory() && (old_addr as u32) != new_addr {
+ // Change 32bit bar address
+ *region = region.set_address(u64::from(new_addr));
+ modify = true;
+ } else if region.is_64bit_memory() && (old_addr as u32) != new_addr {
+ // Change 64bit bar low address
+ *region =
+ region.set_address(u64::from(new_addr) | ((old_addr >> 32) << 32));
+ modify = true;
+ }
+ break;
+ } else if region.is_64bit_memory()
+ && ((bar_idx % 2) == 1)
+ && (region.bar_index() + 1 == bar_idx as usize)
+ {
+ // Change 64bit bar high address
+ let old_addr = region.address();
+ if val != (old_addr >> 32) as u32 {
+ let mut new_addr = (u64::from(val)) << 32;
+ new_addr |= old_addr & 0xFFFFFFFF;
+ *region = region.set_address(new_addr);
+ modify = true;
+ }
+ break;
+ }
+ }
+ if modify {
+ // if bar is changed under memory enabled, mmap the
+ // new bar immediately.
+ let cmd = self.config.read_config::<u8>(PCI_COMMAND);
+ if cmd & PCI_COMMAND_MEMORY == PCI_COMMAND_MEMORY {
+ self.commit_bars_mmap();
+ }
+ }
}
+ }
- self.device
- .region_write(VFIO_PCI_CONFIG_REGION_INDEX, data, start);
+ fn read_virtual_config_register(&self, reg_idx: usize) -> u32 {
+ warn!(
+ "{} read unsupported register {}",
+ self.debug_label(),
+ reg_idx
+ );
+ 0
+ }
+
+ fn write_virtual_config_register(&mut self, reg_idx: usize, _value: u32) {
+ warn!(
+ "{} write unsupported register {}",
+ self.debug_label(),
+ reg_idx
+ )
}
fn read_bar(&mut self, addr: u64, data: &mut [u8]) {
if let Some(mmio_info) = self.find_region(addr) {
- let offset = addr - mmio_info.start;
- let bar_index = mmio_info.bar_index;
+ let offset = addr - mmio_info.address();
+ let bar_index = mmio_info.bar_index() as u32;
if let Some(msix_cap) = &self.msix_cap {
+ let msix_cap = msix_cap.lock();
if msix_cap.is_msix_table(bar_index, offset) {
msix_cap.read_table(offset, data);
return;
@@ -1054,19 +1891,24 @@ impl PciDevice for VfioPciDevice {
if let Some(device_data) = &self.device_data {
match *device_data {
DeviceData::IntelGfxData { opregion_index } => {
- if opregion_index == mmio_info.bar_index {
+ if opregion_index == mmio_info.bar_index() as u32 {
return;
}
}
}
}
- let offset = addr - mmio_info.start;
- let bar_index = mmio_info.bar_index;
+ let offset = addr - mmio_info.address();
+ let bar_index = mmio_info.bar_index() as u32;
- if let Some(msix_cap) = self.msix_cap.as_mut() {
+ if let Some(msix_cap) = &self.msix_cap {
+ let mut msix_cap = msix_cap.lock();
if msix_cap.is_msix_table(bar_index, offset) {
- msix_cap.write_table(offset, data);
+ let behavior = msix_cap.write_table(offset, data);
+ if let MsixStatus::EntryChanged(index) = behavior {
+ let irqfd = msix_cap.get_msix_irqfd(index);
+ self.msix_vector_update(index, irqfd);
+ }
return;
} else if msix_cap.is_msix_pba(bar_index, offset) {
msix_cap.write_pba(offset, data);
@@ -1077,4 +1919,98 @@ impl PciDevice for VfioPciDevice {
self.device.region_write(bar_index, data, offset);
}
}
+
+ fn destroy_device(&mut self) {
+ self.close();
+ }
+}
+
+impl Drop for VfioPciDevice {
+ fn drop(&mut self) {
+ #[cfg(feature = "direct")]
+ if self.sysfs_path.is_some() {
+ let _ = VfioPciDevice::coordinated_pm(self.sysfs_path.as_ref().unwrap(), false);
+ }
+
+ if let Some(kill_evt) = self.kill_evt.take() {
+ let _ = kill_evt.write(1);
+ }
+
+ if let Some(worker_thread) = self.worker_thread.take() {
+ let _ = worker_thread.join();
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::VfioResourceAllocator;
+
+ #[test]
+ fn no_overlap() {
+ // regions [32, 95]
+ let mut memory = VfioResourceAllocator::new(32, 64).unwrap();
+ memory.allocate_at(0, 16).unwrap();
+ memory.allocate_at(100, 16).unwrap();
+
+ let mut iter = memory.regions.iter();
+ assert_eq!(iter.next(), Some(&(32, 95)));
+ }
+
+ #[test]
+ fn full_overlap() {
+ // regions [32, 95]
+ let mut memory = VfioResourceAllocator::new(32, 64).unwrap();
+ // regions [32, 47], [64, 95]
+ memory.allocate_at(48, 16).unwrap();
+ // regions [64, 95]
+ memory.allocate_at(32, 16).unwrap();
+
+ let mut iter = memory.regions.iter();
+ assert_eq!(iter.next(), Some(&(64, 95)));
+ }
+
+ #[test]
+ fn partial_overlap_one() {
+ // regions [32, 95]
+ let mut memory = VfioResourceAllocator::new(32, 64).unwrap();
+ // regions [32, 47], [64, 95]
+ memory.allocate_at(48, 16).unwrap();
+ // regions [32, 39], [64, 95]
+ memory.allocate_at(40, 16).unwrap();
+
+ let mut iter = memory.regions.iter();
+ assert_eq!(iter.next(), Some(&(32, 39)));
+ assert_eq!(iter.next(), Some(&(64, 95)));
+ }
+
+ #[test]
+ fn partial_overlap_two() {
+ // regions [32, 95]
+ let mut memory = VfioResourceAllocator::new(32, 64).unwrap();
+ // regions [32, 47], [64, 95]
+ memory.allocate_at(48, 16).unwrap();
+ // regions [32, 39], [72, 95]
+ memory.allocate_at(40, 32).unwrap();
+
+ let mut iter = memory.regions.iter();
+ assert_eq!(iter.next(), Some(&(32, 39)));
+ assert_eq!(iter.next(), Some(&(72, 95)));
+ }
+
+ #[test]
+ fn partial_overlap_three() {
+ // regions [32, 95]
+ let mut memory = VfioResourceAllocator::new(32, 64).unwrap();
+ // regions [32, 39], [48, 95]
+ memory.allocate_at(40, 8).unwrap();
+ // regions [32, 39], [48, 63], [72, 95]
+ memory.allocate_at(64, 8).unwrap();
+ // regions [32, 35], [76, 95]
+ memory.allocate_at(36, 40).unwrap();
+
+ let mut iter = memory.regions.iter();
+ assert_eq!(iter.next(), Some(&(32, 35)));
+ assert_eq!(iter.next(), Some(&(76, 95)));
+ }
}
diff --git a/devices/src/pit.rs b/devices/src/pit.rs
index 63fa5be39..d0dc999f4 100644
--- a/devices/src/pit.rs
+++ b/devices/src/pit.rs
@@ -3,7 +3,6 @@
// found in the LICENSE file.
// Based heavily on GCE VMM's pit.cc.
-use std::fmt::{self, Display};
use std::io::Error as IoError;
use std::sync::Arc;
use std::thread;
@@ -15,7 +14,9 @@ use base::{
use bit_field::BitField1;
use bit_field::*;
use hypervisor::{PitChannelState, PitRWMode, PitRWState, PitState};
+use remain::sorted;
use sync::Mutex;
+use thiserror::Error;
#[cfg(not(test))]
use base::Clock;
@@ -147,38 +148,29 @@ const NANOS_PER_SEC: u64 = 1_000_000_000;
const MAX_TIMER_FREQ: u32 = 65536;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum PitError {
- TimerCreateError(SysError),
+ /// Error while cloning event for worker thread.
+ #[error("failed to clone event: {0}")]
+ CloneEvent(SysError),
+ /// Error while creating event.
+ #[error("failed to create event: {0}")]
+ CreateEvent(SysError),
/// Creating WaitContext failed.
+ #[error("failed to create poll context: {0}")]
CreateWaitContext(SysError),
- /// Error while waiting for events.
- WaitError(SysError),
/// Error while trying to create worker thread.
+ #[error("failed to spawn thread: {0}")]
SpawnThread(IoError),
- /// Error while creating event.
- CreateEvent(SysError),
- /// Error while cloning event for worker thread.
- CloneEvent(SysError),
-}
-
-impl Display for PitError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::PitError::*;
-
- match self {
- TimerCreateError(e) => write!(f, "failed to create pit counter due to timer fd: {}", e),
- CreateWaitContext(e) => write!(f, "failed to create poll context: {}", e),
- WaitError(err) => write!(f, "failed to wait for events: {}", err),
- SpawnThread(err) => write!(f, "failed to spawn thread: {}", err),
- CreateEvent(err) => write!(f, "failed to create event: {}", err),
- CloneEvent(err) => write!(f, "failed to clone event: {}", err),
- }
- }
+ /// Error while trying to create timer.
+ #[error("failed to create pit counter due to timer fd: {0}")]
+ TimerCreateError(SysError),
+ /// Error while waiting for events.
+ #[error("failed to wait for events: {0}")]
+ WaitError(SysError),
}
-impl std::error::Error for PitError {}
-
type PitResult<T> = std::result::Result<T, PitError>;
pub struct Pit {
@@ -1270,7 +1262,7 @@ mod tests {
CommandAccess::CommandRWBoth,
);
- advance_by_ticks(&mut data, (3 * FREQUENCY_HZ).into());
+ advance_by_ticks(&mut data, 3 * FREQUENCY_HZ);
read_counter(&mut data.pit, 0, 0, CommandAccess::CommandRWBoth);
}
diff --git a/devices/src/pl030.rs b/devices/src/pl030.rs
index f295b96d3..ae2408221 100644
--- a/devices/src/pl030.rs
+++ b/devices/src/pl030.rs
@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use base::{warn, Event};
+use base::warn;
use std::convert::TryFrom;
use std::time::{SystemTime, UNIX_EPOCH};
-use crate::{BusAccessInfo, BusDevice};
+use crate::{BusAccessInfo, BusDevice, IrqEdgeEvent};
// Register offsets
// Data register
@@ -36,7 +36,7 @@ pub const PL030_AMBA_MASK: u32 = 0x000FFFFF;
/// An emulated ARM pl030 RTC
pub struct Pl030 {
// Event to be used to interrupt the guest for an alarm event
- alarm_evt: Event,
+ alarm_evt: IrqEdgeEvent,
// This is the delta we subtract from current time to get the
// counter value
@@ -60,7 +60,7 @@ fn get_epoch_time() -> u32 {
impl Pl030 {
/// Constructs a Pl030 device
- pub fn new(evt: Event) -> Pl030 {
+ pub fn new(evt: IrqEdgeEvent) -> Pl030 {
Pl030 {
alarm_evt: evt,
counter_delta_time: get_epoch_time(),
@@ -100,7 +100,7 @@ impl BusDevice for Pl030 {
if reg_val == 0 {
self.interrupt_active = false;
} else {
- self.alarm_evt.write(1).unwrap();
+ self.alarm_evt.trigger().unwrap();
self.interrupt_active = true;
}
}
@@ -161,7 +161,7 @@ mod tests {
#[test]
fn test_interrupt_status_register() {
- let event = Event::new().unwrap();
+ let event = IrqEdgeEvent::new().unwrap();
let mut device = Pl030::new(event.try_clone().unwrap());
let mut register = [0, 0, 0, 0];
@@ -169,7 +169,7 @@ mod tests {
device.write(pl030_bus_address(RTCEOI), &[1, 0, 0, 0]);
device.read(pl030_bus_address(RTCSTAT), &mut register);
assert_eq!(register, [1, 0, 0, 0]);
- assert_eq!(event.read().unwrap(), 1);
+ assert_eq!(event.get_trigger().read().unwrap(), 1);
// clear interrupt
device.write(pl030_bus_address(RTCEOI), &[0, 0, 0, 0]);
@@ -179,7 +179,7 @@ mod tests {
#[test]
fn test_match_register() {
- let mut device = Pl030::new(Event::new().unwrap());
+ let mut device = Pl030::new(IrqEdgeEvent::new().unwrap());
let mut register = [0, 0, 0, 0];
device.write(pl030_bus_address(RTCMR), &[1, 2, 3, 4]);
diff --git a/devices/src/platform/mod.rs b/devices/src/platform/mod.rs
new file mode 100644
index 000000000..25a98d120
--- /dev/null
+++ b/devices/src/platform/mod.rs
@@ -0,0 +1,9 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Implements platform devices and busses.
+
+mod vfio_platform;
+
+pub use self::vfio_platform::VfioPlatformDevice;
diff --git a/devices/src/platform/vfio_platform.rs b/devices/src/platform/vfio_platform.rs
new file mode 100644
index 000000000..f064c2969
--- /dev/null
+++ b/devices/src/platform/vfio_platform.rs
@@ -0,0 +1,286 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use crate::vfio::{VfioDevice, VfioError, VfioIrq};
+use crate::{BusAccessInfo, BusDevice, BusDeviceObj, IrqEdgeEvent, IrqLevelEvent};
+use anyhow::{bail, Context, Result};
+use base::{
+ error, pagesize, AsRawDescriptor, AsRawDescriptors, Event, MappedRegion, MemoryMapping,
+ MemoryMappingBuilder, RawDescriptor, Tube,
+};
+use resources::SystemAllocator;
+use std::fs::File;
+use std::sync::Arc;
+use std::u32;
+use vfio_sys::*;
+use vm_control::{VmMemoryDestination, VmMemoryRequest, VmMemoryResponse, VmMemorySource};
+
+struct MmioInfo {
+ index: u32,
+ start: u64,
+ length: u64,
+}
+
+pub struct VfioPlatformDevice {
+ device: Arc<VfioDevice>,
+ interrupt_edge_evt: Vec<IrqEdgeEvent>,
+ interrupt_level_evt: Vec<IrqLevelEvent>,
+ mmio_regions: Vec<MmioInfo>,
+ vm_socket_mem: Tube,
+ // scratch MemoryMapping to avoid unmap beform vm exit
+ mem: Vec<MemoryMapping>,
+}
+
+impl BusDevice for VfioPlatformDevice {
+ fn debug_label(&self) -> String {
+ format!("vfio {} device", self.device.device_name())
+ }
+
+ fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
+ self.read_mmio(info.address, data)
+ }
+
+ fn write(&mut self, info: BusAccessInfo, data: &[u8]) {
+ self.write_mmio(info.address, data)
+ }
+}
+
+impl BusDeviceObj for VfioPlatformDevice {
+ fn as_platform_device(&self) -> Option<&VfioPlatformDevice> {
+ Some(self)
+ }
+ fn as_platform_device_mut(&mut self) -> Option<&mut VfioPlatformDevice> {
+ Some(self)
+ }
+ fn into_platform_device(self: Box<Self>) -> Option<Box<VfioPlatformDevice>> {
+ Some(self)
+ }
+}
+
+impl VfioPlatformDevice {
+ /// Constructs a new Vfio Platform device for the given Vfio device
+ pub fn new(device: VfioDevice, vfio_device_socket_mem: Tube) -> Self {
+ let dev = Arc::new(device);
+ VfioPlatformDevice {
+ device: dev,
+ interrupt_edge_evt: Vec::new(),
+ interrupt_level_evt: Vec::new(),
+ mmio_regions: Vec::new(),
+ vm_socket_mem: vfio_device_socket_mem,
+ mem: Vec::new(),
+ }
+ }
+
+ pub fn get_platform_irqs(&self) -> Result<Vec<VfioIrq>, VfioError> {
+ self.device.get_irqs()
+ }
+
+ pub fn irq_is_automask(&self, irq: &VfioIrq) -> bool {
+ irq.flags & VFIO_IRQ_INFO_AUTOMASKED != 0
+ }
+
+ fn setup_irq_resample(&mut self, resample_evt: &Event, index: u32) -> Result<()> {
+ self.device.irq_mask(index).context("Intx mask failed")?;
+ self.device
+ .resample_virq_enable(resample_evt, index)
+ .context("resample enable failed")?;
+ self.device
+ .irq_unmask(index)
+ .context("Intx unmask failed")?;
+ Ok(())
+ }
+
+ pub fn assign_edge_platform_irq(&mut self, irq_evt: &IrqEdgeEvent, index: u32) -> Result<()> {
+ let interrupt_evt = irq_evt.try_clone().context("failed to clone irq event")?;
+ self.device
+ .irq_enable(&[Some(interrupt_evt.get_trigger())], index, 0)
+ .context("platform irq enable failed")?;
+ self.interrupt_edge_evt.push(interrupt_evt);
+ Ok(())
+ }
+
+ pub fn assign_level_platform_irq(&mut self, irq_evt: &IrqLevelEvent, index: u32) -> Result<()> {
+ let interrupt_evt = irq_evt.try_clone().context("failed to clone irq event")?;
+ self.device
+ .irq_enable(&[Some(interrupt_evt.get_trigger())], index, 0)
+ .context("platform irq enable failed")?;
+ if let Err(e) = self.setup_irq_resample(interrupt_evt.get_resample(), index) {
+ self.disable_irqs(index);
+ bail!("failed to set up irq resampling: {}", e);
+ }
+ self.interrupt_level_evt.push(interrupt_evt);
+ Ok(())
+ }
+
+ fn find_region(&self, addr: u64) -> Option<MmioInfo> {
+ for mmio_info in self.mmio_regions.iter() {
+ if addr >= mmio_info.start && addr < mmio_info.start + mmio_info.length {
+ return Some(MmioInfo {
+ index: mmio_info.index,
+ start: mmio_info.start,
+ length: mmio_info.length,
+ });
+ }
+ }
+ None
+ }
+
+ pub fn allocate_regions(
+ &mut self,
+ resources: &mut SystemAllocator,
+ ) -> Result<Vec<(u64, u64)>, resources::Error> {
+ let mut ranges = Vec::new();
+ for i in 0..self.device.get_region_count() {
+ let size = self.device.get_region_size(i);
+ let alloc_id = resources.get_anon_alloc();
+ let allocator = resources
+ .mmio_platform_allocator()
+ .ok_or(resources::Error::MissingPlatformMMIOAddresses)?;
+ let start_addr = allocator.allocate_with_align(
+ size,
+ alloc_id,
+ "vfio_mmio".to_string(),
+ pagesize() as u64,
+ )?;
+ ranges.push((start_addr, size));
+
+ self.mmio_regions.push(MmioInfo {
+ index: i,
+ start: start_addr,
+ length: size,
+ });
+ }
+ Ok(ranges)
+ }
+
+ fn region_mmap(&self, index: u32, start_addr: u64) -> Vec<MemoryMapping> {
+ let mut mem_map: Vec<MemoryMapping> = Vec::new();
+ if self.device.get_region_flags(index) & VFIO_REGION_INFO_FLAG_MMAP != 0 {
+ let mmaps = self.device.get_region_mmap(index);
+ if mmaps.is_empty() {
+ return mem_map;
+ }
+
+ for mmap in mmaps.iter() {
+ let mmap_offset = mmap.offset;
+ let mmap_size = mmap.size;
+ let guest_map_start = start_addr + mmap_offset;
+ let region_offset = self.device.get_region_offset(index);
+ let offset = region_offset + mmap_offset;
+ let descriptor = match self.device.device_file().try_clone() {
+ Ok(device_file) => device_file.into(),
+ Err(_) => break,
+ };
+ if self
+ .vm_socket_mem
+ .send(&VmMemoryRequest::RegisterMemory {
+ source: VmMemorySource::Descriptor {
+ descriptor,
+ offset,
+ size: mmap_size,
+ },
+ dest: VmMemoryDestination::GuestPhysicalAddress(guest_map_start),
+ read_only: false,
+ })
+ .is_err()
+ {
+ break;
+ }
+
+ let response: VmMemoryResponse = match self.vm_socket_mem.recv() {
+ Ok(res) => res,
+ Err(_) => break,
+ };
+ match response {
+ VmMemoryResponse::Ok => {
+ // Even if vm has mapped this region, but it is in vm main process,
+ // device process doesn't has this mapping, but vfio_dma_map() need it
+ // in device process, so here map it again.
+ let mmap = match MemoryMappingBuilder::new(mmap_size as usize)
+ .from_file(self.device.device_file())
+ .offset(offset)
+ .build()
+ {
+ Ok(v) => v,
+ Err(_e) => break,
+ };
+ let host = (&mmap).as_ptr() as u64;
+ // Safe because the given guest_map_start is valid guest bar address. and
+ // the host pointer is correct and valid guaranteed by MemoryMapping interface.
+ match unsafe {
+ self.device
+ .vfio_dma_map(guest_map_start, mmap_size, host, true)
+ } {
+ Ok(_) => mem_map.push(mmap),
+ Err(e) => {
+ error!(
+ "{}, index: {}, start_addr:0x{:x}, host:0x{:x}",
+ e, index, start_addr, host
+ );
+ break;
+ }
+ }
+ }
+ _ => break,
+ }
+ }
+ }
+
+ mem_map
+ }
+
+ fn regions_mmap(&mut self) {
+ for mmio_info in self.mmio_regions.iter() {
+ let mut mem_map = self.region_mmap(mmio_info.index, mmio_info.start);
+ self.mem.append(&mut mem_map);
+ }
+ }
+
+ fn disable_irqs(&mut self, index: u32) {
+ if let Err(e) = self.device.irq_disable(index) {
+ error!("Platform irq disable failed: {}", e);
+ }
+ }
+
+ fn read_mmio(&mut self, addr: u64, data: &mut [u8]) {
+ if let Some(mmio_info) = self.find_region(addr) {
+ let offset = addr - mmio_info.start;
+ let index = mmio_info.index;
+ self.device.region_read(index, data, offset);
+ }
+ // We have no other way than wait for 1st access and then do the mmap,
+ // so that next accesses are dual-stage MMU accelerated.
+ self.regions_mmap();
+ }
+
+ fn write_mmio(&mut self, addr: u64, data: &[u8]) {
+ if let Some(mmio_info) = self.find_region(addr) {
+ let offset = addr - mmio_info.start;
+ let index = mmio_info.index;
+ self.device.region_write(index, data, offset);
+ }
+ // We have no other way than wait for 1st access and then do the mmap,
+ // so that next accesses are dual-stage MMU accelerated.
+ self.regions_mmap();
+ }
+
+ pub fn keep_rds(&self) -> Vec<RawDescriptor> {
+ let mut rds = self.device.keep_rds();
+
+ for irq_evt in self.interrupt_edge_evt.iter() {
+ rds.extend(irq_evt.as_raw_descriptors());
+ }
+
+ for irq_evt in self.interrupt_level_evt.iter() {
+ rds.extend(irq_evt.as_raw_descriptors());
+ }
+
+ rds.push(self.vm_socket_mem.as_raw_descriptor());
+ rds
+ }
+
+ /// Gets the vfio device backing `File`.
+ pub fn device_file(&self) -> &File {
+ self.device.device_file()
+ }
+}
diff --git a/devices/src/proxy.rs b/devices/src/proxy.rs
index 4afa7b38c..41b8d2ac4 100644
--- a/devices/src/proxy.rs
+++ b/devices/src/proxy.rs
@@ -5,35 +5,30 @@
//! Runs hardware devices in child processes.
use std::ffi::CString;
-use std::fmt::{self, Display};
use std::time::Duration;
use base::{error, AsRawDescriptor, RawDescriptor, Tube, TubeError};
use libc::{self, pid_t};
use minijail::{self, Minijail};
+use remain::sorted;
use serde::{Deserialize, Serialize};
+use thiserror::Error;
use crate::bus::ConfigWriteResult;
-use crate::{BusAccessInfo, BusDevice};
+use crate::pci::PciAddress;
+use crate::{BusAccessInfo, BusDevice, BusRange, BusType};
/// Errors for proxy devices.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
+ #[error("Failed to fork jail process: {0}")]
ForkingJail(minijail::Error),
+ #[error("Failed to configure tube: {0}")]
Tube(TubeError),
}
-pub type Result<T> = std::result::Result<T, Error>;
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
- match self {
- ForkingJail(e) => write!(f, "Failed to fork jail process: {}", e),
- Tube(e) => write!(f, "Failed to configure tube: {}.", e),
- }
- }
-}
+pub type Result<T> = std::result::Result<T, Error>;
const SOCKET_TIMEOUT_MS: u64 = 2000;
@@ -55,7 +50,13 @@ enum Command {
len: u32,
data: [u8; 4],
},
+ ReadVirtualConfig(u32),
+ WriteVirtualConfig {
+ reg_idx: u32,
+ value: u32,
+ },
Shutdown,
+ GetRanges,
}
#[derive(Debug, Serialize, Deserialize)]
enum CommandResult {
@@ -63,9 +64,14 @@ enum CommandResult {
ReadResult([u8; 8]),
ReadConfigResult(u32),
WriteConfigResult {
- mem_bus_new_state: Option<bool>,
- io_bus_new_state: Option<bool>,
+ mmio_remove: Vec<BusRange>,
+ mmio_add: Vec<BusRange>,
+ io_remove: Vec<BusRange>,
+ io_add: Vec<BusRange>,
+ removed_pci_devices: Vec<PciAddress>,
},
+ ReadVirtualConfigResult(u32),
+ GetRangesResult(Vec<(BusRange, BusType)>),
}
fn child_proc<D: BusDevice>(tube: Tube, device: &mut D) {
@@ -106,14 +112,30 @@ fn child_proc<D: BusDevice>(tube: Tube, device: &mut D) {
let res =
device.config_register_write(reg_idx as usize, offset as u64, &data[0..len]);
tube.send(&CommandResult::WriteConfigResult {
- mem_bus_new_state: res.mem_bus_new_state,
- io_bus_new_state: res.io_bus_new_state,
+ mmio_remove: res.mmio_remove,
+ mmio_add: res.mmio_add,
+ io_remove: res.io_remove,
+ io_add: res.io_add,
+ removed_pci_devices: res.removed_pci_devices,
})
}
+ Command::ReadVirtualConfig(idx) => {
+ let val = device.virtual_config_register_read(idx as usize);
+ tube.send(&CommandResult::ReadVirtualConfigResult(val))
+ }
+ Command::WriteVirtualConfig { reg_idx, value } => {
+ device.virtual_config_register_write(reg_idx as usize, value);
+ // Command::WriteVirtualConfig does not have a result.
+ Ok(())
+ }
Command::Shutdown => {
running = false;
tube.send(&CommandResult::Ok)
}
+ Command::GetRanges => {
+ let ranges = device.get_ranges();
+ tube.send(&CommandResult::GetRangesResult(ranges))
+ }
};
if let Err(e) = res {
error!("child device process failed send: {}", e);
@@ -150,6 +172,11 @@ impl ProxyDevice {
let (child_tube, parent_tube) = Tube::pair().map_err(Error::Tube)?;
keep_rds.push(child_tube.as_raw_descriptor());
+
+ // Deduplicate the FDs since minijail expects this.
+ keep_rds.sort_unstable();
+ keep_rds.dedup();
+
// Forking here is safe as long as the program is still single threaded.
let pid = unsafe {
match jail.fork(Some(&keep_rds)).map_err(Error::ForkingJail)? {
@@ -237,8 +264,11 @@ impl BusDevice for ProxyDevice {
let reg_idx = reg_idx as u32;
let offset = offset as u32;
if let Some(CommandResult::WriteConfigResult {
- mem_bus_new_state,
- io_bus_new_state,
+ mmio_remove,
+ mmio_add,
+ io_remove,
+ io_add,
+ removed_pci_devices,
}) = self.sync_send(&Command::WriteConfig {
reg_idx,
offset,
@@ -246,8 +276,11 @@ impl BusDevice for ProxyDevice {
data: buffer,
}) {
ConfigWriteResult {
- mem_bus_new_state,
- io_bus_new_state,
+ mmio_remove,
+ mmio_add,
+ io_remove,
+ io_add,
+ removed_pci_devices,
}
} else {
Default::default()
@@ -263,6 +296,20 @@ impl BusDevice for ProxyDevice {
}
}
+ fn virtual_config_register_write(&mut self, reg_idx: usize, value: u32) {
+ let reg_idx = reg_idx as u32;
+ self.send_no_result(&Command::WriteVirtualConfig { reg_idx, value });
+ }
+
+ fn virtual_config_register_read(&self, reg_idx: usize) -> u32 {
+ let res = self.sync_send(&Command::ReadVirtualConfig(reg_idx as u32));
+ if let Some(CommandResult::ReadVirtualConfigResult(val)) = res {
+ val
+ } else {
+ 0
+ }
+ }
+
fn read(&mut self, info: BusAccessInfo, data: &mut [u8]) {
let len = data.len() as u32;
if let Some(CommandResult::ReadResult(buffer)) =
@@ -283,6 +330,14 @@ impl BusDevice for ProxyDevice {
data: buffer,
});
}
+
+ fn get_ranges(&self) -> Vec<(BusRange, BusType)> {
+ if let Some(CommandResult::GetRangesResult(ranges)) = self.sync_send(&Command::GetRanges) {
+ ranges
+ } else {
+ Default::default()
+ }
+ }
}
impl Drop for ProxyDevice {
diff --git a/devices/src/register_space/register.rs b/devices/src/register_space/register.rs
index 8851290dd..407a8ee1e 100644
--- a/devices/src/register_space/register.rs
+++ b/devices/src/register_space/register.rs
@@ -67,7 +67,7 @@ pub trait RegisterValue:
{
// Get byte of the offset.
fn get_byte(&self, offset: usize) -> u8 {
- let val: u64 = self.clone().into();
+ let val: u64 = (*self).into();
(val >> (offset * 8)) as u8
}
// Set masked bits.
diff --git a/devices/src/serial.rs b/devices/src/serial.rs
index 710f30002..92ca66683 100644
--- a/devices/src/serial.rs
+++ b/devices/src/serial.rs
@@ -9,10 +9,11 @@ use std::sync::mpsc::{channel, Receiver, TryRecvError};
use std::sync::Arc;
use std::thread::{self};
-use base::{error, Event, RawDescriptor, Result};
+use base::{error, Event, FileSync, RawDescriptor, Result};
+use hypervisor::ProtectionType;
use crate::bus::BusAccessInfo;
-use crate::{BusDevice, ProtectionType, SerialDevice};
+use crate::{BusDevice, SerialDevice};
const LOOP_SIZE: usize = 0x40;
@@ -88,6 +89,8 @@ impl SerialDevice for Serial {
interrupt_evt: Event,
input: Option<Box<dyn io::Read + Send>>,
out: Option<Box<dyn io::Write + Send>>,
+ _sync: Option<Box<dyn FileSync + Send>>,
+ _out_timestamp: bool,
_keep_rds: Vec<RawDescriptor>,
) -> Serial {
Serial {
@@ -423,16 +426,15 @@ mod tests {
intr_evt,
None,
Some(Box::new(serial_out.clone())),
+ None,
+ false,
Vec::new(),
);
- serial.write(serial_bus_address(DATA), &['a' as u8]);
- serial.write(serial_bus_address(DATA), &['b' as u8]);
- serial.write(serial_bus_address(DATA), &['c' as u8]);
- assert_eq!(
- serial_out.buf.lock().as_slice(),
- &['a' as u8, 'b' as u8, 'c' as u8]
- );
+ serial.write(serial_bus_address(DATA), &[b'a']);
+ serial.write(serial_bus_address(DATA), &[b'b']);
+ serial.write(serial_bus_address(DATA), &[b'c']);
+ assert_eq!(serial_out.buf.lock().as_slice(), &[b'a', b'b', b'c']);
}
#[test]
@@ -444,22 +446,22 @@ mod tests {
ProtectionType::Unprotected,
intr_evt.try_clone().unwrap(),
None,
- Some(Box::new(serial_out.clone())),
+ Some(Box::new(serial_out)),
+ None,
+ false,
Vec::new(),
);
serial.write(serial_bus_address(IER), &[IER_RECV_BIT]);
- serial
- .queue_input_bytes(&['a' as u8, 'b' as u8, 'c' as u8])
- .unwrap();
+ serial.queue_input_bytes(&[b'a', b'b', b'c']).unwrap();
assert_eq!(intr_evt.read(), Ok(1));
let mut data = [0u8; 1];
serial.read(serial_bus_address(DATA), &mut data[..]);
- assert_eq!(data[0], 'a' as u8);
+ assert_eq!(data[0], b'a');
serial.read(serial_bus_address(DATA), &mut data[..]);
- assert_eq!(data[0], 'b' as u8);
+ assert_eq!(data[0], b'b');
serial.read(serial_bus_address(DATA), &mut data[..]);
- assert_eq!(data[0], 'c' as u8);
+ assert_eq!(data[0], b'c');
}
}
diff --git a/devices/src/serial_device.rs b/devices/src/serial_device.rs
index 9cbd57bf2..e07fa28c9 100644
--- a/devices/src/serial_device.rs
+++ b/devices/src/serial_device.rs
@@ -2,19 +2,313 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::io;
+use std::fmt::{self, Display};
+use std::fs::OpenOptions;
+use std::io::{self, stdin, stdout};
+use std::path::PathBuf;
-use crate::ProtectionType;
-use base::{Event, RawDescriptor};
+use base::{error, open_file, syslog, AsRawDescriptor, Event, FileSync, RawDescriptor};
+use hypervisor::ProtectionType;
+use remain::sorted;
+use serde::{Deserialize, Serialize};
+use thiserror::Error as ThisError;
-/// Abstraction over serial-like devices that can be created given an event and optional input and
-/// output streams.
-pub trait SerialDevice {
- fn new(
+pub use crate::sys::serial_device::SerialDevice;
+use crate::sys::serial_device::*;
+
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum Error {
+ #[error("Unable to clone an Event: {0}")]
+ CloneEvent(base::Error),
+ #[error("Unable to open/create file: {0}")]
+ FileError(std::io::Error),
+ #[error("Serial device path is invalid")]
+ InvalidPath,
+ #[error("Invalid serial hardware: {0}")]
+ InvalidSerialHardware(String),
+ #[error("Invalid serial type: {0}")]
+ InvalidSerialType(String),
+ #[error("Serial device type file requires a path")]
+ PathRequired,
+ #[error("Failed to create unbound socket")]
+ SocketCreateFailed,
+ #[error("Unable to open system type serial: {0}")]
+ SystemTypeError(std::io::Error),
+ #[error("Serial device type {0} not implemented")]
+ Unimplemented(SerialType),
+}
+
+/// Enum for possible type of serial devices
+#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
+#[serde(rename_all = "kebab-case")]
+pub enum SerialType {
+ File,
+ Stdout,
+ Sink,
+ Syslog,
+ #[cfg_attr(unix, serde(rename = "unix"))]
+ #[cfg_attr(windows, serde(rename = "namedpipe"))]
+ SystemSerialType,
+}
+
+impl Default for SerialType {
+ fn default() -> Self {
+ Self::Sink
+ }
+}
+
+impl Display for SerialType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let s = match &self {
+ SerialType::File => "File".to_string(),
+ SerialType::Stdout => "Stdout".to_string(),
+ SerialType::Sink => "Sink".to_string(),
+ SerialType::Syslog => "Syslog".to_string(),
+ SerialType::SystemSerialType => SYSTEM_SERIAL_TYPE_NAME.to_string(),
+ };
+
+ write!(f, "{}", s)
+ }
+}
+
+/// Serial device hardware types
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+pub enum SerialHardware {
+ Serial, // Standard PC-style (8250/16550 compatible) UART
+ VirtioConsole, // virtio-console device
+}
+
+impl Default for SerialHardware {
+ fn default() -> Self {
+ Self::Serial
+ }
+}
+
+impl Display for SerialHardware {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let s = match &self {
+ SerialHardware::Serial => "serial".to_string(),
+ SerialHardware::VirtioConsole => "virtio-console".to_string(),
+ };
+
+ write!(f, "{}", s)
+ }
+}
+
+fn serial_parameters_default_num() -> u8 {
+ 1
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
+#[serde(deny_unknown_fields, default)]
+pub struct SerialParameters {
+ #[serde(rename = "type")]
+ pub type_: SerialType,
+ pub hardware: SerialHardware,
+ pub path: Option<PathBuf>,
+ pub input: Option<PathBuf>,
+ #[serde(default = "serial_parameters_default_num")]
+ pub num: u8,
+ pub console: bool,
+ pub earlycon: bool,
+ pub stdin: bool,
+ pub out_timestamp: bool,
+}
+
+impl SerialParameters {
+ /// Helper function to create a serial device from the defined parameters.
+ ///
+ /// # Arguments
+ /// * `evt` - event used for interrupt events
+ /// * `keep_rds` - Vector of FDs required by this device if it were sandboxed in a child
+ /// process. `evt` will always be added to this vector by this function.
+ pub fn create_serial_device<T: SerialDevice>(
+ &self,
protected_vm: ProtectionType,
- interrupt_evt: Event,
- input: Option<Box<dyn io::Read + Send>>,
- output: Option<Box<dyn io::Write + Send>>,
- keep_rds: Vec<RawDescriptor>,
- ) -> Self;
+ evt: &Event,
+ keep_rds: &mut Vec<RawDescriptor>,
+ ) -> std::result::Result<T, Error> {
+ let evt = evt.try_clone().map_err(Error::CloneEvent)?;
+ keep_rds.push(evt.as_raw_descriptor());
+ let input: Option<Box<dyn io::Read + Send>> = if let Some(input_path) = &self.input {
+ let input_path = input_path.as_path();
+
+ let input_file = open_file(input_path, OpenOptions::new().read(true))
+ .map_err(|e| Error::FileError(e.into()))?;
+
+ keep_rds.push(input_file.as_raw_descriptor());
+ Some(Box::new(input_file))
+ } else if self.stdin {
+ keep_rds.push(stdin().as_raw_descriptor());
+ Some(Box::new(ConsoleInput))
+ } else {
+ None
+ };
+ let (output, sync): (
+ Option<Box<dyn io::Write + Send>>,
+ Option<Box<dyn FileSync + Send>>,
+ ) = match self.type_ {
+ SerialType::Stdout => {
+ keep_rds.push(stdout().as_raw_descriptor());
+ (Some(Box::new(stdout())), None)
+ }
+ SerialType::Sink => (None, None),
+ SerialType::Syslog => {
+ syslog::push_descriptors(keep_rds);
+ (
+ Some(Box::new(syslog::Syslogger::new(
+ syslog::Priority::Info,
+ syslog::Facility::Daemon,
+ ))),
+ None,
+ )
+ }
+ SerialType::File => match &self.path {
+ Some(path) => {
+ let file = open_file(path, OpenOptions::new().append(true).create(true))
+ .map_err(|e| Error::FileError(e.into()))?;
+ let sync = file.try_clone().map_err(Error::FileError)?;
+
+ keep_rds.push(file.as_raw_descriptor());
+ keep_rds.push(sync.as_raw_descriptor());
+
+ (Some(Box::new(file)), Some(Box::new(sync)))
+ }
+ None => return Err(Error::PathRequired),
+ },
+ SerialType::SystemSerialType => {
+ return create_system_type_serial_device(self, protected_vm, evt, input, keep_rds);
+ }
+ };
+ Ok(T::new(
+ protected_vm,
+ evt,
+ input,
+ output,
+ sync,
+ self.out_timestamp,
+ keep_rds.to_vec(),
+ ))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use serde_keyvalue::*;
+
+ fn from_serial_arg(options: &str) -> Result<SerialParameters, ParseError> {
+ from_key_values(options)
+ }
+
+ #[test]
+ fn params_from_key_values() {
+ // Defaults
+ let params = from_serial_arg("").unwrap();
+ assert_eq!(
+ params,
+ SerialParameters {
+ type_: SerialType::Sink,
+ hardware: SerialHardware::Serial,
+ path: None,
+ input: None,
+ num: 1,
+ console: false,
+ earlycon: false,
+ stdin: false,
+ out_timestamp: false,
+ }
+ );
+
+ // type parameter
+ let params = from_serial_arg("type=file").unwrap();
+ assert_eq!(params.type_, SerialType::File);
+ let params = from_serial_arg("type=stdout").unwrap();
+ assert_eq!(params.type_, SerialType::Stdout);
+ let params = from_serial_arg("type=sink").unwrap();
+ assert_eq!(params.type_, SerialType::Sink);
+ let params = from_serial_arg("type=syslog").unwrap();
+ assert_eq!(params.type_, SerialType::Syslog);
+ #[cfg(unix)]
+ let opt = "type=unix";
+ #[cfg(window)]
+ let opt = "type=namedpipe";
+ let params = from_serial_arg(opt).unwrap();
+ assert_eq!(params.type_, SerialType::SystemSerialType);
+ let params = from_serial_arg("type=foobar");
+ assert!(params.is_err());
+
+ // hardware parameter
+ let params = from_serial_arg("hardware=serial").unwrap();
+ assert_eq!(params.hardware, SerialHardware::Serial);
+ let params = from_serial_arg("hardware=virtio-console").unwrap();
+ assert_eq!(params.hardware, SerialHardware::VirtioConsole);
+ let params = from_serial_arg("hardware=foobar");
+ assert!(params.is_err());
+
+ // path parameter
+ let params = from_serial_arg("path=/test/path").unwrap();
+ assert_eq!(params.path, Some("/test/path".into()));
+ let params = from_serial_arg("path");
+ assert!(params.is_err());
+
+ // input parameter
+ let params = from_serial_arg("input=/path/to/input").unwrap();
+ assert_eq!(params.input, Some("/path/to/input".into()));
+ let params = from_serial_arg("input");
+ assert!(params.is_err());
+
+ // console parameter
+ let params = from_serial_arg("console").unwrap();
+ assert!(params.console);
+ let params = from_serial_arg("console=true").unwrap();
+ assert!(params.console);
+ let params = from_serial_arg("console=false").unwrap();
+ assert!(!params.console);
+ let params = from_serial_arg("console=foobar");
+ assert!(params.is_err());
+
+ // earlycon parameter
+ let params = from_serial_arg("earlycon").unwrap();
+ assert!(params.earlycon);
+ let params = from_serial_arg("earlycon=true").unwrap();
+ assert!(params.earlycon);
+ let params = from_serial_arg("earlycon=false").unwrap();
+ assert!(!params.earlycon);
+ let params = from_serial_arg("earlycon=foobar");
+ assert!(params.is_err());
+
+ // stdin parameter
+ let params = from_serial_arg("stdin").unwrap();
+ assert!(params.stdin);
+ let params = from_serial_arg("stdin=true").unwrap();
+ assert!(params.stdin);
+ let params = from_serial_arg("stdin=false").unwrap();
+ assert!(!params.stdin);
+ let params = from_serial_arg("stdin=foobar");
+ assert!(params.is_err());
+
+ // all together
+ let params = from_serial_arg("type=stdout,path=/some/path,hardware=virtio-console,num=5,earlycon,console,stdin,input=/some/input,out_timestamp").unwrap();
+ assert_eq!(
+ params,
+ SerialParameters {
+ type_: SerialType::Stdout,
+ hardware: SerialHardware::VirtioConsole,
+ path: Some("/some/path".into()),
+ input: Some("/some/input".into()),
+ num: 5,
+ console: true,
+ earlycon: true,
+ stdin: true,
+ out_timestamp: true,
+ }
+ );
+
+ // invalid field
+ let params = from_serial_arg("type=stdout,foo=bar");
+ assert!(params.is_err());
+ }
}
diff --git a/devices/src/software_tpm.rs b/devices/src/software_tpm.rs
new file mode 100644
index 000000000..43c4068f1
--- /dev/null
+++ b/devices/src/software_tpm.rs
@@ -0,0 +1,33 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Software TPM backend using the TPM2 simulator from the `tpm2` crate.
+
+use std::env;
+use std::fs;
+use std::path::Path;
+
+use anyhow::Context;
+use tpm2::Simulator;
+
+use super::virtio::TpmBackend;
+
+pub struct SoftwareTpm {
+ simulator: Simulator,
+}
+
+impl SoftwareTpm {
+ pub fn new<P: AsRef<Path>>(storage: P) -> anyhow::Result<Self> {
+ fs::create_dir_all(storage.as_ref()).context("failed to create directory for simulator")?;
+ env::set_current_dir(storage).context("failed to change into simulator directory")?;
+ let simulator = Simulator::singleton_in_current_directory();
+ Ok(SoftwareTpm { simulator })
+ }
+}
+
+impl TpmBackend for SoftwareTpm {
+ fn execute_command<'a>(&'a mut self, command: &[u8]) -> &'a [u8] {
+ self.simulator.execute_command(command)
+ }
+}
diff --git a/devices/src/sys.rs b/devices/src/sys.rs
new file mode 100644
index 000000000..f21fffb1f
--- /dev/null
+++ b/devices/src/sys.rs
@@ -0,0 +1,13 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cfg_if::cfg_if! {
+ if #[cfg(unix)] {
+ mod unix;
+ pub(crate) use unix::*;
+ } else if #[cfg(windows)] {
+ mod windows;
+ pub(crate) use windows::*;
+ }
+}
diff --git a/devices/src/sys/unix.rs b/devices/src/sys/unix.rs
new file mode 100644
index 000000000..b58978166
--- /dev/null
+++ b/devices/src/sys/unix.rs
@@ -0,0 +1,5 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+pub(crate) mod serial_device;
diff --git a/devices/src/sys/unix/serial_device.rs b/devices/src/sys/unix/serial_device.rs
new file mode 100644
index 000000000..d8fb677e7
--- /dev/null
+++ b/devices/src/sys/unix/serial_device.rs
@@ -0,0 +1,195 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::serial_device::{Error, SerialParameters};
+use base::{error, AsRawDescriptor, Event, FileSync, RawDescriptor};
+use base::{info, read_raw_stdin};
+use hypervisor::ProtectionType;
+use std::borrow::Cow;
+use std::fs::OpenOptions;
+use std::io;
+use std::io::{ErrorKind, Write};
+use std::os::unix::net::UnixDatagram;
+use std::path::{Path, PathBuf};
+use std::thread;
+use std::time::Duration;
+
+pub const SYSTEM_SERIAL_TYPE_NAME: &str = "UnixSocket";
+
+// This wrapper is used in place of the libstd native version because we don't want
+// buffering for stdin.
+pub struct ConsoleInput;
+impl io::Read for ConsoleInput {
+ fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
+ read_raw_stdin(out).map_err(|e| e.into())
+ }
+}
+
+/// Abstraction over serial-like devices that can be created given an event and optional input and
+/// output streams.
+pub trait SerialDevice {
+ fn new(
+ protected_vm: ProtectionType,
+ interrupt_evt: Event,
+ input: Option<Box<dyn io::Read + Send>>,
+ output: Option<Box<dyn io::Write + Send>>,
+ sync: Option<Box<dyn FileSync + Send>>,
+ out_timestamp: bool,
+ keep_rds: Vec<RawDescriptor>,
+ ) -> Self;
+}
+
+// The maximum length of a path that can be used as the address of a
+// unix socket. Note that this includes the null-terminator.
+pub const MAX_SOCKET_PATH_LENGTH: usize = 108;
+
+struct WriteSocket {
+ sock: UnixDatagram,
+ buf: String,
+}
+
+const BUF_CAPACITY: usize = 1024;
+
+impl WriteSocket {
+ pub fn new(s: UnixDatagram) -> WriteSocket {
+ WriteSocket {
+ sock: s,
+ buf: String::with_capacity(BUF_CAPACITY),
+ }
+ }
+
+ pub fn send_buf(&self, buf: &[u8]) -> io::Result<usize> {
+ const SEND_RETRY: usize = 2;
+ let mut sent = 0;
+ for _ in 0..SEND_RETRY {
+ match self.sock.send(buf) {
+ Ok(bytes_sent) => {
+ sent = bytes_sent;
+ break;
+ }
+ Err(e) => info!("Send error: {:?}", e),
+ }
+ }
+ Ok(sent)
+ }
+}
+
+impl io::Write for WriteSocket {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ let parsed_str = String::from_utf8_lossy(buf);
+
+ let last_newline_idx = match parsed_str.rfind('\n') {
+ Some(newline_idx) => Some(self.buf.len() + newline_idx),
+ None => None,
+ };
+ self.buf.push_str(&parsed_str);
+
+ match last_newline_idx {
+ Some(last_newline_idx) => {
+ for line in (self.buf[..last_newline_idx]).lines() {
+ if self.send_buf(line.as_bytes()).is_err() {
+ break;
+ }
+ }
+ self.buf.drain(..=last_newline_idx);
+ }
+ None => {
+ if self.buf.len() >= BUF_CAPACITY {
+ if let Err(e) = self.send_buf(self.buf.as_bytes()) {
+ info!("Couldn't send full buffer. {:?}", e);
+ }
+ self.buf.clear();
+ }
+ }
+ }
+ Ok(buf.len())
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+}
+
+pub(crate) fn create_system_type_serial_device<T: SerialDevice>(
+ param: &SerialParameters,
+ protected_vm: ProtectionType,
+ evt: Event,
+ input: Option<Box<dyn io::Read + Send>>,
+ keep_rds: &mut Vec<RawDescriptor>,
+) -> std::result::Result<T, Error> {
+ match &param.path {
+ Some(path) => {
+ // If the path is longer than 107 characters,
+ // then we won't be able to connect directly
+ // to it. Instead we can shorten the path by
+ // opening the containing directory and using
+ // /proc/self/fd/*/ to access it via a shorter
+ // path.
+ let mut path_cow = Cow::<Path>::Borrowed(path);
+ let mut _dir_fd = None;
+ if path.as_os_str().len() >= MAX_SOCKET_PATH_LENGTH {
+ let mut short_path = PathBuf::with_capacity(MAX_SOCKET_PATH_LENGTH);
+ short_path.push("/proc/self/fd/");
+
+ // We don't actually want to open this
+ // directory for reading, but the stdlib
+ // requires all files be opened as at
+ // least one of readable, writeable, or
+ // appeandable.
+ let dir = OpenOptions::new()
+ .read(true)
+ .open(path.parent().ok_or(Error::InvalidPath)?)
+ .map_err(Error::FileError)?;
+
+ short_path.push(dir.as_raw_descriptor().to_string());
+ short_path.push(path.file_name().ok_or(Error::InvalidPath)?);
+ path_cow = Cow::Owned(short_path);
+ _dir_fd = Some(dir);
+ }
+
+ // The shortened path may still be too long,
+ // in which case we must give up here.
+ if path_cow.as_os_str().len() >= MAX_SOCKET_PATH_LENGTH {
+ return Err(Error::InvalidPath);
+ }
+
+ // There's a race condition between
+ // vmlog_forwarder making the logging socket and
+ // crosvm starting up, so we loop here until it's
+ // available.
+ let sock = UnixDatagram::unbound().map_err(Error::FileError)?;
+ loop {
+ match sock.connect(&path_cow) {
+ Ok(_) => break,
+ Err(e) => {
+ match e.kind() {
+ ErrorKind::NotFound | ErrorKind::ConnectionRefused => {
+ // logging socket doesn't
+ // exist yet, sleep for 10 ms
+ // and try again.
+ thread::sleep(Duration::from_millis(10))
+ }
+ _ => {
+ error!("Unexpected error connecting to logging socket: {:?}", e);
+ return Err(Error::FileError(e));
+ }
+ }
+ }
+ };
+ }
+ keep_rds.push(sock.as_raw_descriptor());
+ let output: Option<Box<dyn Write + Send>> = Some(Box::new(WriteSocket::new(sock)));
+ return Ok(T::new(
+ protected_vm,
+ evt,
+ input,
+ output,
+ None,
+ false,
+ keep_rds.to_vec(),
+ ));
+ }
+ None => return Err(Error::PathRequired),
+ }
+}
diff --git a/devices/src/sys/windows.rs b/devices/src/sys/windows.rs
new file mode 100644
index 000000000..b58978166
--- /dev/null
+++ b/devices/src/sys/windows.rs
@@ -0,0 +1,5 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+pub(crate) mod serial_device;
diff --git a/devices/src/sys/windows/serial_device.rs b/devices/src/sys/windows/serial_device.rs
new file mode 100644
index 000000000..b207f0630
--- /dev/null
+++ b/devices/src/sys/windows/serial_device.rs
@@ -0,0 +1,80 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::serial_device::{Error, SerialParameters};
+use base::named_pipes;
+use base::named_pipes::{BlockingMode, FramingMode};
+use base::FileSync;
+use base::{AsRawDescriptor, Event, RawDescriptor};
+use hypervisor::ProtectionType;
+use std::io::{self};
+use std::path::Path;
+
+pub use base::Console as ConsoleInput;
+
+pub const SYSTEM_SERIAL_TYPE_NAME: &str = "NamedPipe";
+
+/// Abstraction over serial-like devices that can be created given an event and optional input and
+/// output streams.
+pub trait SerialDevice {
+ fn new(
+ protected_vm: ProtectionType,
+ interrupt_evt: Event,
+ input: Option<Box<dyn io::Read + Send>>,
+ output: Option<Box<dyn io::Write + Send>>,
+ sync: Option<Box<dyn FileSync + Send>>,
+ out_timestamp: bool,
+ keep_rds: Vec<RawDescriptor>,
+ ) -> Self;
+ fn new_pipe(
+ protected_vm: ProtectionType,
+ interrupt_evt: Event,
+ pipe_in: named_pipes::PipeConnection,
+ pipe_out: named_pipes::PipeConnection,
+ keep_rds: Vec<RawDescriptor>,
+ ) -> Self;
+}
+
+pub(crate) fn create_system_type_serial_device<T: SerialDevice>(
+ param: &SerialParameters,
+ protected_vm: ProtectionType,
+ evt: Event,
+ _input: Option<Box<dyn io::Read + Send>>,
+ keep_rds: &mut Vec<RawDescriptor>,
+) -> std::result::Result<T, Error> {
+ match &param.path {
+ None => return Err(Error::PathRequired),
+ Some(path) => {
+ // We must create this pipe in non-blocking mode because a blocking
+ // read in one thread will block a write in another thread having a
+ // handle to the same end of the pipe, which will hang the
+ // emulator. This does mean that the event loop writing to the
+ // pipe's output will need to swallow errors caused by writing to
+ // the pipe when it's not ready; but in practice this does not seem
+ // to cause a problem.
+ let pipe_in = named_pipes::create_server_pipe(
+ path.to_str().unwrap(),
+ &FramingMode::Byte,
+ &BlockingMode::NoWait,
+ 0, // default timeout
+ named_pipes::DEFAULT_BUFFER_SIZE,
+ false,
+ )
+ .map_err(Error::SystemTypeError)?;
+
+ let pipe_out = pipe_in.try_clone().map_err(Error::SystemTypeError)?;
+
+ keep_rds.push(pipe_in.as_raw_descriptor());
+ keep_rds.push(pipe_out.as_raw_descriptor());
+
+ return Ok(T::new_pipe(
+ protected_vm,
+ evt,
+ pipe_in,
+ pipe_out,
+ keep_rds.to_vec(),
+ ));
+ }
+ }
+}
diff --git a/devices/src/usb/host_backend/error.rs b/devices/src/usb/host_backend/error.rs
index 8d089233a..aad8f5449 100644
--- a/devices/src/usb/host_backend/error.rs
+++ b/devices/src/usb/host_backend/error.rs
@@ -7,75 +7,68 @@ use crate::usb::xhci::xhci_transfer::Error as XhciTransferError;
use crate::utils::Error as UtilsError;
use base::TubeError;
-use std::fmt::{self, Display};
+use remain::sorted;
+use thiserror::Error;
use usb_util::Error as UsbUtilError;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
+ #[error("failed to add to event loop: {0}")]
AddToEventLoop(UtilsError),
- StartAsyncJobQueue(UtilsError),
- QueueAsyncJob(UtilsError),
+ #[error("backend provider is in a bad state")]
+ BadBackendProviderState,
+ #[error("xhci transfer is in a bad state")]
+ BadXhciTransferState,
+ #[error("failed to get buffer length: {0}")]
+ BufferLen(BufferError),
+ #[error("failed to clear halt: {0}")]
+ ClearHalt(UsbUtilError),
+ #[error("failed to create contro tube: {0}")]
+ CreateControlTube(TubeError),
+ #[error("failed to create libusb context: {0}")]
CreateLibUsbContext(UsbUtilError),
+ #[error("failed to create transfer: {0}")]
+ CreateTransfer(UsbUtilError),
+ #[error("failed to get active config: {0}")]
GetActiveConfig(UsbUtilError),
+ #[error("failed to get config descriptor: {0}")]
+ GetConfigDescriptor(UsbUtilError),
+ #[error("failed to get device descriptor: {0}")]
+ GetDeviceDescriptor(UsbUtilError),
+ #[error("failed to get endpoint descriptor for ep: {0}")]
+ GetEndpointDescriptor(u8),
+ #[error("failed to get endpoint type")]
+ GetEndpointType,
+ /// Cannot get interface descriptor for (interface, altsetting).
+ #[error("failed to get interface descriptor for interface {0}, alt setting {1}")]
+ GetInterfaceDescriptor(u8, u8),
+ #[error("failed to get xhci transfer type: {0}")]
+ GetXhciTransferType(XhciTransferError),
+ #[error("request missing required data buffer")]
+ MissingRequiredBuffer,
+ #[error("failed to queue async job: {0}")]
+ QueueAsyncJob(UtilsError),
+ #[error("failed to read buffer: {0}")]
+ ReadBuffer(BufferError),
+ #[error("failed to read control tube: {0}")]
+ ReadControlTube(TubeError),
+ #[error("failed to reset: {0}")]
+ Reset(UsbUtilError),
+ #[error("failed to set active config: {0}")]
SetActiveConfig(UsbUtilError),
+ #[error("failed to set interface alt setting: {0}")]
SetInterfaceAltSetting(UsbUtilError),
- ClearHalt(UsbUtilError),
- CreateTransfer(UsbUtilError),
- Reset(UsbUtilError),
- GetEndpointType,
- CreateControlTube(TubeError),
+ #[error("failed to setup control tube: {0}")]
SetupControlTube(TubeError),
- ReadControlTube(TubeError),
- WriteControlTube(TubeError),
- GetXhciTransferType(XhciTransferError),
+ #[error("failed to start async job queue: {0}")]
+ StartAsyncJobQueue(UtilsError),
+ #[error("xhci transfer completed: {0}")]
TransferComplete(XhciTransferError),
- ReadBuffer(BufferError),
+ #[error("failed to write buffer: {0}")]
WriteBuffer(BufferError),
- BufferLen(BufferError),
- /// Cannot get interface descriptor for (interface, altsetting).
- GetInterfaceDescriptor((u8, u8)),
- GetEndpointDescriptor(u8),
- BadXhciTransferState,
- BadBackendProviderState,
-}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- AddToEventLoop(e) => write!(f, "failed to add to event loop: {}", e),
- StartAsyncJobQueue(e) => write!(f, "failed to start async job queue: {}", e),
- QueueAsyncJob(e) => write!(f, "failed to queue async job: {}", e),
- CreateLibUsbContext(e) => write!(f, "failed to create libusb context: {:?}", e),
- GetActiveConfig(e) => write!(f, "failed to get active config: {:?}", e),
- SetActiveConfig(e) => write!(f, "failed to set active config: {:?}", e),
- SetInterfaceAltSetting(e) => write!(f, "failed to set interface alt setting: {:?}", e),
- ClearHalt(e) => write!(f, "failed to clear halt: {:?}", e),
- CreateTransfer(e) => write!(f, "failed to create transfer: {:?}", e),
- Reset(e) => write!(f, "failed to reset: {:?}", e),
- GetEndpointType => write!(f, "failed to get endpoint type"),
- CreateControlTube(e) => write!(f, "failed to create contro tube: {}", e),
- SetupControlTube(e) => write!(f, "failed to setup control tube: {}", e),
- ReadControlTube(e) => write!(f, "failed to read control tube: {}", e),
- WriteControlTube(e) => write!(f, "failed to write control tube: {}", e),
- GetXhciTransferType(e) => write!(f, "failed to get xhci transfer type: {}", e),
- TransferComplete(e) => write!(f, "xhci transfer completed: {}", e),
- ReadBuffer(e) => write!(f, "failed to read buffer: {}", e),
- WriteBuffer(e) => write!(f, "failed to write buffer: {}", e),
- BufferLen(e) => write!(f, "failed to get buffer length: {}", e),
- GetInterfaceDescriptor((i, alt_setting)) => write!(
- f,
- "failed to get interface descriptor for interface {}, alt setting {}",
- i, alt_setting
- ),
- GetEndpointDescriptor(ep_idx) => {
- write!(f, "failed to get endpoint descriptor for ep: {}", ep_idx)
- }
- BadXhciTransferState => write!(f, "xhci transfer is in a bad state"),
- BadBackendProviderState => write!(f, "backend provider is in a bad state"),
- }
- }
+ #[error("failed to write control tube: {0}")]
+ WriteControlTube(TubeError),
}
pub type Result<T> = std::result::Result<T, Error>;
diff --git a/devices/src/usb/host_backend/host_backend_device_provider.rs b/devices/src/usb/host_backend/host_backend_device_provider.rs
index 787bb8fe0..8919e02ad 100644
--- a/devices/src/usb/host_backend/host_backend_device_provider.rs
+++ b/devices/src/usb/host_backend/host_backend_device_provider.rs
@@ -11,6 +11,8 @@ use crate::usb::xhci::usb_hub::UsbHub;
use crate::usb::xhci::xhci_backend_device_provider::XhciBackendDeviceProvider;
use crate::utils::AsyncJobQueue;
use crate::utils::{EventHandler, EventLoop, FailHandle};
+
+use anyhow::Context;
use base::{error, AsRawDescriptor, Descriptor, RawDescriptor, Tube, WatchingEvents};
use std::collections::HashMap;
use std::mem;
@@ -189,11 +191,18 @@ impl ProviderInner {
error!("failed to reset device after attach: {:?}", e);
}
- let host_device = Box::new(HostDevice::new(
+ let host_device = match HostDevice::new(
self.fail_handle.clone(),
self.job_queue.clone(),
arc_mutex_device,
- ));
+ ) {
+ Ok(host_device) => Box::new(host_device),
+ Err(e) => {
+ error!("failed to initialize HostDevice: {}", e);
+ return UsbControlResult::FailedToInitHostDevice;
+ }
+ };
+
let port = self.usb_hub.connect_backend(host_device);
match port {
Ok(port) => {
@@ -266,10 +275,9 @@ impl ProviderInner {
}
impl EventHandler for ProviderInner {
- fn on_event(&self) -> std::result::Result<(), ()> {
- self.on_event_helper().map_err(|e| {
- error!("host backend device provider failed: {}", e);
- })
+ fn on_event(&self) -> anyhow::Result<()> {
+ self.on_event_helper()
+ .context("host backend device provider failed")
}
}
@@ -278,7 +286,10 @@ struct UsbUtilEventHandler {
}
impl EventHandler for UsbUtilEventHandler {
- fn on_event(&self) -> std::result::Result<(), ()> {
- self.device.lock().poll_transfers().map_err(|_e| ())
+ fn on_event(&self) -> anyhow::Result<()> {
+ self.device
+ .lock()
+ .poll_transfers()
+ .context("UsbUtilEventHandler poll_transfers failed")
}
}
diff --git a/devices/src/usb/host_backend/host_device.rs b/devices/src/usb/host_backend/host_device.rs
index 6a49ee5ea..0475edc24 100644
--- a/devices/src/usb/host_backend/host_device.rs
+++ b/devices/src/usb/host_backend/host_device.rs
@@ -20,7 +20,8 @@ use std::mem;
use sync::Mutex;
use usb_util::{
ConfigDescriptorTree, ControlRequestDataPhaseTransferDirection, ControlRequestRecipient,
- Device, StandardControlRequest, Transfer, TransferStatus, UsbRequestSetup,
+ DescriptorHeader, DescriptorType, Device, InterfaceDescriptor, StandardControlRequest,
+ Transfer, TransferStatus, UsbRequestSetup,
};
#[derive(PartialEq)]
@@ -60,8 +61,8 @@ impl HostDevice {
fail_handle: Arc<dyn FailHandle>,
job_queue: Arc<AsyncJobQueue>,
device: Arc<Mutex<Device>>,
- ) -> HostDevice {
- HostDevice {
+ ) -> Result<HostDevice> {
+ let mut host_device = HostDevice {
fail_handle,
endpoints: vec![],
device,
@@ -71,53 +72,95 @@ impl HostDevice {
control_request_setup: UsbRequestSetup::new(0, 0, 0, 0, 0),
executed: false,
job_queue,
- }
+ };
+
+ let cur_config = host_device
+ .device
+ .lock()
+ .get_active_configuration()
+ .map_err(Error::GetActiveConfig)?;
+ let config_descriptor = host_device
+ .device
+ .lock()
+ .get_config_descriptor(cur_config)
+ .map_err(Error::GetActiveConfig)?;
+ host_device.claim_interfaces(&config_descriptor);
+
+ Ok(host_device)
}
// Check for requests that should be intercepted and emulated using libusb
// functions rather than passed directly to the device.
// Returns true if the request has been intercepted or false if the request
// should be passed through to the device.
- fn intercepted_control_transfer(&mut self, xhci_transfer: &XhciTransfer) -> Result<bool> {
+ fn intercepted_control_transfer(
+ &mut self,
+ xhci_transfer: &XhciTransfer,
+ buffer: &Option<ScatterGatherBuffer>,
+ ) -> Result<bool> {
let direction = self.control_request_setup.get_direction();
let recipient = self.control_request_setup.get_recipient();
- let standard_request = self.control_request_setup.get_standard_request();
-
- if direction != ControlRequestDataPhaseTransferDirection::HostToDevice {
- // Only host to device requests are intercepted currently.
+ let standard_request = if let Some(req) = self.control_request_setup.get_standard_request()
+ {
+ req
+ } else {
+ // Unknown control requests will be passed through to the device.
return Ok(false);
- }
+ };
- let status = match standard_request {
- Some(StandardControlRequest::SetAddress) => {
- if recipient != ControlRequestRecipient::Device {
- return Ok(false);
- }
+ let (status, bytes_transferred) = match (standard_request, recipient, direction) {
+ (
+ StandardControlRequest::SetAddress,
+ ControlRequestRecipient::Device,
+ ControlRequestDataPhaseTransferDirection::HostToDevice,
+ ) => {
usb_debug!("host device handling set address");
let addr = self.control_request_setup.value as u32;
self.set_address(addr);
- TransferStatus::Completed
+ (TransferStatus::Completed, 0)
}
- Some(StandardControlRequest::SetConfiguration) => {
- if recipient != ControlRequestRecipient::Device {
- return Ok(false);
- }
+ (
+ StandardControlRequest::SetConfiguration,
+ ControlRequestRecipient::Device,
+ ControlRequestDataPhaseTransferDirection::HostToDevice,
+ ) => {
usb_debug!("host device handling set config");
- self.set_config()?
+ (self.set_config()?, 0)
}
- Some(StandardControlRequest::SetInterface) => {
- if recipient != ControlRequestRecipient::Interface {
- return Ok(false);
- }
+ (
+ StandardControlRequest::SetInterface,
+ ControlRequestRecipient::Interface,
+ ControlRequestDataPhaseTransferDirection::HostToDevice,
+ ) => {
usb_debug!("host device handling set interface");
- self.set_interface()?
+ (self.set_interface()?, 0)
+ }
+ (
+ StandardControlRequest::ClearFeature,
+ ControlRequestRecipient::Endpoint,
+ ControlRequestDataPhaseTransferDirection::HostToDevice,
+ ) => {
+ usb_debug!("host device handling clear feature");
+ (self.clear_feature()?, 0)
}
- Some(StandardControlRequest::ClearFeature) => {
- if recipient != ControlRequestRecipient::Endpoint {
+ (
+ StandardControlRequest::GetDescriptor,
+ ControlRequestRecipient::Device,
+ ControlRequestDataPhaseTransferDirection::DeviceToHost,
+ ) => {
+ let descriptor_type = (self.control_request_setup.value >> 8) as u8;
+ if descriptor_type == DescriptorType::Configuration as u8 {
+ usb_debug!("host device handling get config descriptor");
+ let buffer = if let Some(buffer) = buffer {
+ buffer
+ } else {
+ return Err(Error::MissingRequiredBuffer);
+ };
+
+ self.get_config_descriptor_filtered(buffer)?
+ } else {
return Ok(false);
}
- usb_debug!("host device handling clear feature");
- self.clear_feature()?
}
_ => {
// Other requests will be passed through to the device.
@@ -126,7 +169,7 @@ impl HostDevice {
};
xhci_transfer
- .on_transfer_complete(&status, 0)
+ .on_transfer_complete(&status, bytes_transferred)
.map_err(Error::TransferComplete)?;
Ok(true)
}
@@ -136,7 +179,7 @@ impl HostDevice {
xhci_transfer: Arc<XhciTransfer>,
buffer: Option<ScatterGatherBuffer>,
) -> Result<()> {
- if self.intercepted_control_transfer(&xhci_transfer)? {
+ if self.intercepted_control_transfer(&xhci_transfer, &buffer)? {
return Ok(());
}
@@ -189,7 +232,7 @@ impl HostDevice {
{
if let Some(buffer) = &buffer {
buffer
- .write(&control_request_data)
+ .write(control_request_data)
.map_err(Error::WriteBuffer)?;
}
}
@@ -230,10 +273,10 @@ impl HostDevice {
fn handle_control_transfer(&mut self, transfer: XhciTransfer) -> Result<()> {
let xhci_transfer = Arc::new(transfer);
- match xhci_transfer
+ let transfer_type = xhci_transfer
.get_transfer_type()
- .map_err(Error::GetXhciTransferType)?
- {
+ .map_err(Error::GetXhciTransferType)?;
+ match transfer_type {
XhciTransferType::SetupStage(setup) => {
if self.ctl_ep_state != ControlEndpointState::SetupStage {
error!("Control endpoint is in an inconsistant state");
@@ -277,7 +320,10 @@ impl HostDevice {
}
_ => {
// Non control transfer should not be handled in this function.
- error!("Non control (could be noop) transfer sent to control endpoint.");
+ error!(
+ "Non control {} transfer sent to control endpoint.",
+ transfer_type,
+ );
xhci_transfer
.on_transfer_complete(&TransferStatus::Completed, 0)
.map_err(Error::TransferComplete)?;
@@ -294,27 +340,25 @@ impl HostDevice {
config
);
self.release_interfaces();
- if self.device.lock().get_num_configurations() > 1 {
- let cur_config = match self.device.lock().get_active_configuration() {
- Ok(c) => Some(c),
- Err(e) => {
- // The device may be in the default state, in which case
- // GET_CONFIGURATION may fail. Assume the device needs to be
- // reconfigured.
- usb_debug!("Failed to get active configuration: {}", e);
- error!("Failed to get active configuration: {}", e);
- None
- }
- };
- if Some(config) != cur_config {
- self.device
- .lock()
- .set_active_configuration(config)
- .map_err(Error::SetActiveConfig)?;
+
+ let cur_config = match self.device.lock().get_active_configuration() {
+ Ok(c) => Some(c),
+ Err(e) => {
+ // The device may be in the default state, in which case
+ // GET_CONFIGURATION may fail. Assume the device needs to be
+ // reconfigured.
+ usb_debug!("Failed to get active configuration: {}", e);
+ error!("Failed to get active configuration: {}", e);
+ None
}
- } else {
- usb_debug!("Only one configuration - not calling set_active_configuration");
+ };
+ if Some(config) != cur_config {
+ self.device
+ .lock()
+ .set_active_configuration(config)
+ .map_err(Error::SetActiveConfig)?;
}
+
let config_descriptor = self
.device
.lock()
@@ -363,6 +407,63 @@ impl HostDevice {
Ok(TransferStatus::Completed)
}
+ // Execute a Get Descriptor control request with type Configuration.
+ // This function is used to return a filtered version of the host device's configuration
+ // descriptor that only includes the interfaces in `self.claimed_interfaces`.
+ fn get_config_descriptor_filtered(
+ &mut self,
+ buffer: &ScatterGatherBuffer,
+ ) -> Result<(TransferStatus, u32)> {
+ let descriptor_index = self.control_request_setup.value as u8;
+ usb_debug!(
+ "get_config_descriptor_filtered config index: {}",
+ descriptor_index,
+ );
+
+ let config_descriptor = self
+ .device
+ .lock()
+ .get_config_descriptor_by_index(descriptor_index)
+ .map_err(Error::GetConfigDescriptor)?;
+
+ let device = self.device.lock();
+ let device_descriptor = device.get_device_descriptor_tree();
+
+ let config_start = config_descriptor.offset();
+ let config_end = config_start + config_descriptor.wTotalLength as usize;
+ let mut descriptor_data = device_descriptor.raw()[config_start..config_end].to_vec();
+
+ if config_descriptor.bConfigurationValue
+ == device
+ .get_active_configuration()
+ .map_err(Error::GetActiveConfig)?
+ {
+ for i in 0..config_descriptor.bNumInterfaces {
+ if !self.claimed_interfaces.contains(&i) {
+ // Rewrite descriptors for unclaimed interfaces to vendor-specific class.
+ // This prevents them from being recognized by the guest drivers.
+ let alt_setting = self.alt_settings.get(&i).unwrap_or(&0);
+ let interface = config_descriptor
+ .get_interface_descriptor(i, *alt_setting)
+ .ok_or(Error::GetInterfaceDescriptor(i, *alt_setting))?;
+ let mut interface_data: InterfaceDescriptor = **interface;
+ interface_data.bInterfaceClass = 0xFF;
+ interface_data.bInterfaceSubClass = 0xFF;
+ interface_data.bInterfaceProtocol = 0xFF;
+
+ let interface_start =
+ interface.offset() + mem::size_of::<DescriptorHeader>() - config_start;
+ let interface_end = interface_start + mem::size_of::<InterfaceDescriptor>();
+ descriptor_data[interface_start..interface_end]
+ .copy_from_slice(interface_data.as_slice());
+ }
+ }
+ }
+
+ let bytes_transferred = buffer.write(&descriptor_data).map_err(Error::WriteBuffer)?;
+ Ok((TransferStatus::Completed, bytes_transferred as u32))
+ }
+
fn claim_interfaces(&mut self, config_descriptor: &ConfigDescriptorTree) {
for i in 0..config_descriptor.num_interfaces() {
match self.device.lock().claim_interface(i) {
@@ -383,7 +484,7 @@ impl HostDevice {
let alt_setting = self.alt_settings.get(i).unwrap_or(&0);
let interface = config_descriptor
.get_interface_descriptor(*i, *alt_setting)
- .ok_or(Error::GetInterfaceDescriptor((*i, *alt_setting)))?;
+ .ok_or(Error::GetInterfaceDescriptor(*i, *alt_setting))?;
for ep_idx in 0..interface.bNumEndpoints {
let ep_dp = interface
.get_endpoint_descriptor(ep_idx)
diff --git a/devices/src/usb/xhci/command_ring_controller.rs b/devices/src/usb/xhci/command_ring_controller.rs
index 70b671c2d..2987c0d34 100644
--- a/devices/src/usb/xhci/command_ring_controller.rs
+++ b/devices/src/usb/xhci/command_ring_controller.rs
@@ -15,49 +15,44 @@ use super::xhci_abi::{
};
use super::xhci_regs::{valid_slot_id, MAX_SLOTS};
use crate::utils::EventLoop;
+
+use anyhow::Context;
use base::{error, warn, Error as SysError, Event};
-use std::fmt::{self, Display};
+use remain::sorted;
use std::sync::Arc;
use sync::Mutex;
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory};
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- WriteEvent(SysError),
- SendInterrupt(InterrupterError),
- CastTrb(TrbError),
+ #[error("bad slot id: {0}")]
BadSlotId(u8),
- StopEndpoint(DeviceSlotError),
+ #[error("failed to cast trb: {0}")]
+ CastTrb(TrbError),
+ #[error("failed to config endpoint: {0}")]
ConfigEndpoint(DeviceSlotError),
- SetAddress(DeviceSlotError),
- SetDequeuePointer(DeviceSlotError),
- EvaluateContext(DeviceSlotError),
+ #[error("failed to disable slot: {0}")]
DisableSlot(DeviceSlotError),
+ #[error("failed to evaluate context: {0}")]
+ EvaluateContext(DeviceSlotError),
+ #[error("failed to reset slot: {0}")]
ResetSlot(DeviceSlotError),
+ #[error("failed to send interrupt: {0}")]
+ SendInterrupt(InterrupterError),
+ #[error("failed to set address: {0}")]
+ SetAddress(DeviceSlotError),
+ #[error("failed to set dequeue pointer: {0}")]
+ SetDequeuePointer(DeviceSlotError),
+ #[error("failed to stop endpoint: {0}")]
+ StopEndpoint(DeviceSlotError),
+ #[error("failed to write event: {0}")]
+ WriteEvent(SysError),
}
type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- WriteEvent(e) => write!(f, "failed to write event: {}", e),
- SendInterrupt(e) => write!(f, "failed to send interrupt: {}", e),
- CastTrb(e) => write!(f, "failed to cast trb: {}", e),
- BadSlotId(id) => write!(f, "bad slot id: {}", id),
- StopEndpoint(e) => write!(f, "failed to stop endpoint: {}", e),
- ConfigEndpoint(e) => write!(f, "failed to config endpoint: {}", e),
- SetAddress(e) => write!(f, "failed to set address: {}", e),
- SetDequeuePointer(e) => write!(f, "failed to set dequeue pointer: {}", e),
- EvaluateContext(e) => write!(f, "failed to evaluate context: {}", e),
- DisableSlot(e) => write!(f, "failed to disable slot: {}", e),
- ResetSlot(e) => write!(f, "failed to reset slot: {}", e),
- }
- }
-}
-
pub type CommandRingController = RingBufferController<CommandRingTrbHandler>;
pub type CommandRingControllerError = RingBufferControllerError;
@@ -338,7 +333,7 @@ impl TransferDescriptorHandler for CommandRingTrbHandler {
&self,
descriptor: TransferDescriptor,
complete_event: Event,
- ) -> std::result::Result<(), ()> {
+ ) -> anyhow::Result<()> {
// Command descriptor always consist of a single TRB.
assert_eq!(descriptor.len(), 1);
let atrb = &descriptor[0];
@@ -389,8 +384,6 @@ impl TransferDescriptorHandler for CommandRingTrbHandler {
}
}
};
- command_result.map_err(|e| {
- error!("command failed: {}", e);
- })
+ command_result.context("command ring TRB failed")
}
}
diff --git a/devices/src/usb/xhci/device_slot.rs b/devices/src/usb/xhci/device_slot.rs
index e24776339..06df8237d 100644
--- a/devices/src/usb/xhci/device_slot.rs
+++ b/devices/src/usb/xhci/device_slot.rs
@@ -17,54 +17,47 @@ use crate::usb::xhci::ring_buffer_stop_cb::{fallible_closure, RingBufferStopCall
use crate::utils::{EventLoop, FailHandle};
use base::error;
use bit_field::Error as BitFieldError;
-use std::fmt::{self, Display};
+use remain::sorted;
use std::mem::size_of;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use sync::Mutex;
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
+ #[error("bad device context: {0}")]
+ BadDeviceContextAddr(GuestAddress),
+ #[error("bad input context address: {0}")]
+ BadInputContextAddr(GuestAddress),
+ #[error("device slot get a bad port id: {0}")]
BadPortId(u8),
- ReadGuestMemory(GuestMemoryError),
- WriteGuestMemory(GuestMemoryError),
- WeakReferenceUpgrade,
+ #[error("callback failed")]
CallbackFailed,
- GetSlotContextState(BitFieldError),
+ #[error("failed to create transfer controller: {0}")]
+ CreateTransferController(TransferRingControllerError),
+ #[error("failed to get endpoint state: {0}")]
GetEndpointState(BitFieldError),
+ #[error("failed to get port: {0}")]
GetPort(u8),
+ #[error("failed to get slot context state: {0}")]
+ GetSlotContextState(BitFieldError),
+ #[error("failed to get trc: {0}")]
GetTrc(u8),
- BadInputContextAddr(GuestAddress),
- BadDeviceContextAddr(GuestAddress),
- CreateTransferController(TransferRingControllerError),
+ #[error("failed to read guest memory: {0}")]
+ ReadGuestMemory(GuestMemoryError),
+ #[error("failed to reset port: {0}")]
ResetPort(HostBackendProviderError),
+ #[error("failed to upgrade weak reference")]
+ WeakReferenceUpgrade,
+ #[error("failed to write guest memory: {0}")]
+ WriteGuestMemory(GuestMemoryError),
}
type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- BadPortId(id) => write!(f, "device slot get a bad port id: {}", id),
- ReadGuestMemory(e) => write!(f, "failed to read guest memory: {}", e),
- WriteGuestMemory(e) => write!(f, "failed to write guest memory: {}", e),
- WeakReferenceUpgrade => write!(f, "failed to upgrade weak reference"),
- CallbackFailed => write!(f, "callback failed"),
- GetSlotContextState(e) => write!(f, "failed to get slot context state: {}", e),
- GetEndpointState(e) => write!(f, "failed to get endpoint state: {}", e),
- GetPort(v) => write!(f, "failed to get port: {}", v),
- GetTrc(v) => write!(f, "failed to get trc: {}", v),
- BadInputContextAddr(addr) => write!(f, "bad input context address: {}", addr),
- BadDeviceContextAddr(addr) => write!(f, "bad device context: {}", addr),
- CreateTransferController(e) => write!(f, "failed to create transfer controller: {}", e),
- ResetPort(e) => write!(f, "failed to reset port: {}", e),
- }
- }
-}
-
/// See spec 4.5.1 for dci.
/// index 0: Control endpoint. Device Context Index: 1.
/// index 1: Endpoint 1 out. Device Context Index: 2
@@ -584,7 +577,7 @@ impl DeviceSlot {
slot: &Arc<DeviceSlot>,
mut callback: C,
) -> Result<()> {
- let weak_s = Arc::downgrade(&slot);
+ let weak_s = Arc::downgrade(slot);
let auto_callback =
RingBufferStopCallback::new(fallible_closure(fail_handle, move || -> Result<()> {
let s = weak_s.upgrade().ok_or(Error::WeakReferenceUpgrade)?;
diff --git a/devices/src/usb/xhci/event_ring.rs b/devices/src/usb/xhci/event_ring.rs
index 46dad4a2c..8b006c9f4 100644
--- a/devices/src/usb/xhci/event_ring.rs
+++ b/devices/src/usb/xhci/event_ring.rs
@@ -3,42 +3,35 @@
// found in the LICENSE file.
use data_model::DataInit;
-use std::fmt::{self, Display};
+use remain::sorted;
use std::mem::size_of;
use std::sync::atomic::{fence, Ordering};
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
use super::xhci_abi::*;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- Uninitialized,
- EventRingFull,
+ #[error("event ring has a bad enqueue pointer: {0}")]
BadEnqueuePointer(GuestAddress),
- BadSegTableIndex(u16),
+ #[error("event ring has a bad seg table addr: {0}")]
BadSegTableAddress(GuestAddress),
+ #[error("event ring has a bad seg table index: {0}")]
+ BadSegTableIndex(u16),
+ #[error("event ring is full")]
+ EventRingFull,
+ #[error("event ring cannot read from guest memory: {0}")]
MemoryRead(GuestMemoryError),
+ #[error("event ring cannot write to guest memory: {0}")]
MemoryWrite(GuestMemoryError),
+ #[error("event ring is uninitialized")]
+ Uninitialized,
}
type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- Uninitialized => write!(f, "event ring is uninitialized"),
- EventRingFull => write!(f, "event ring is full"),
- BadEnqueuePointer(addr) => write!(f, "event ring has a bad enqueue pointer: {}", addr),
- BadSegTableIndex(i) => write!(f, "event ring has a bad seg table index: {}", i),
- BadSegTableAddress(addr) => write!(f, "event ring has a bad seg table addr: {}", addr),
- MemoryRead(e) => write!(f, "event ring cannot read from guest memory: {}", e),
- MemoryWrite(e) => write!(f, "event ring cannot write to guest memory: {}", e),
- }
- }
-}
-
/// Event rings are segmented circular buffers used to pass event TRBs from the xHCI device back to
/// the guest. Each event ring is associated with a single interrupter. See section 4.9.4 of the
/// xHCI specification for more details.
@@ -218,8 +211,8 @@ mod test {
#[test]
fn test_uninited() {
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
- let mut er = EventRing::new(gm.clone());
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
+ let mut er = EventRing::new(gm);
let trb = Trb::new();
match er.add_event(trb).err().unwrap() {
Error::Uninitialized => {}
@@ -232,7 +225,7 @@ mod test {
#[test]
fn test_event_ring() {
let trb_size = size_of::<Trb>() as u64;
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
let mut er = EventRing::new(gm.clone());
let mut st_entries = [EventRingSegmentTableEntry::new(); 3];
st_entries[0].set_ring_segment_base_address(0x100);
@@ -264,7 +257,7 @@ mod test {
trb.set_control(1);
assert_eq!(er.is_empty(), true);
assert_eq!(er.is_full().unwrap(), false);
- assert_eq!(er.add_event(trb.clone()).unwrap(), ());
+ assert!(er.add_event(trb).is_ok());
assert_eq!(er.is_full().unwrap(), false);
assert_eq!(er.is_empty(), false);
let t: Trb = gm.read_obj_from_addr(GuestAddress(0x100)).unwrap();
@@ -272,7 +265,7 @@ mod test {
assert_eq!(t.get_cycle(), true);
trb.set_control(2);
- assert_eq!(er.add_event(trb.clone()).unwrap(), ());
+ assert!(er.add_event(trb).is_ok());
assert_eq!(er.is_full().unwrap(), false);
assert_eq!(er.is_empty(), false);
let t: Trb = gm
@@ -282,7 +275,7 @@ mod test {
assert_eq!(t.get_cycle(), true);
trb.set_control(3);
- assert_eq!(er.add_event(trb.clone()).unwrap(), ());
+ assert!(er.add_event(trb).is_ok());
assert_eq!(er.is_full().unwrap(), false);
assert_eq!(er.is_empty(), false);
let t: Trb = gm
@@ -293,7 +286,7 @@ mod test {
// Fill second table.
trb.set_control(4);
- assert_eq!(er.add_event(trb.clone()).unwrap(), ());
+ assert!(er.add_event(trb).is_ok());
assert_eq!(er.is_full().unwrap(), false);
assert_eq!(er.is_empty(), false);
let t: Trb = gm.read_obj_from_addr(GuestAddress(0x200)).unwrap();
@@ -301,7 +294,7 @@ mod test {
assert_eq!(t.get_cycle(), true);
trb.set_control(5);
- assert_eq!(er.add_event(trb.clone()).unwrap(), ());
+ assert!(er.add_event(trb).is_ok());
assert_eq!(er.is_full().unwrap(), false);
assert_eq!(er.is_empty(), false);
let t: Trb = gm
@@ -311,7 +304,7 @@ mod test {
assert_eq!(t.get_cycle(), true);
trb.set_control(6);
- assert_eq!(er.add_event(trb.clone()).unwrap(), ());
+ assert!(er.add_event(trb).is_ok());
assert_eq!(er.is_full().unwrap(), false);
assert_eq!(er.is_empty(), false);
let t: Trb = gm
@@ -322,7 +315,7 @@ mod test {
// Fill third table.
trb.set_control(7);
- assert_eq!(er.add_event(trb.clone()).unwrap(), ());
+ assert!(er.add_event(trb).is_ok());
assert_eq!(er.is_full().unwrap(), false);
assert_eq!(er.is_empty(), false);
let t: Trb = gm.read_obj_from_addr(GuestAddress(0x300)).unwrap();
@@ -330,7 +323,7 @@ mod test {
assert_eq!(t.get_cycle(), true);
trb.set_control(8);
- assert_eq!(er.add_event(trb.clone()).unwrap(), ());
+ assert!(er.add_event(trb).is_ok());
// There is only one last trb. Considered full.
assert_eq!(er.is_full().unwrap(), true);
assert_eq!(er.is_empty(), false);
@@ -341,7 +334,7 @@ mod test {
assert_eq!(t.get_cycle(), true);
// Add the last trb will result in error.
- match er.add_event(trb.clone()) {
+ match er.add_event(trb) {
Err(Error::EventRingFull) => {}
_ => panic!("er should be full"),
};
@@ -353,7 +346,7 @@ mod test {
// Fill the last trb of the third table.
trb.set_control(9);
- assert_eq!(er.add_event(trb.clone()).unwrap(), ());
+ assert!(er.add_event(trb).is_ok());
// There is only one last trb. Considered full.
assert_eq!(er.is_full().unwrap(), true);
assert_eq!(er.is_empty(), false);
@@ -364,7 +357,7 @@ mod test {
assert_eq!(t.get_cycle(), true);
// Add the last trb will result in error.
- match er.add_event(trb.clone()) {
+ match er.add_event(trb) {
Err(Error::EventRingFull) => {}
_ => panic!("er should be full"),
};
@@ -376,7 +369,7 @@ mod test {
// Fill first table again.
trb.set_control(10);
- assert_eq!(er.add_event(trb.clone()).unwrap(), ());
+ assert!(er.add_event(trb).is_ok());
assert_eq!(er.is_full().unwrap(), false);
assert_eq!(er.is_empty(), false);
let t: Trb = gm.read_obj_from_addr(GuestAddress(0x100)).unwrap();
@@ -385,7 +378,7 @@ mod test {
assert_eq!(t.get_cycle(), false);
trb.set_control(11);
- assert_eq!(er.add_event(trb.clone()).unwrap(), ());
+ assert!(er.add_event(trb).is_ok());
assert_eq!(er.is_full().unwrap(), false);
assert_eq!(er.is_empty(), false);
let t: Trb = gm
@@ -395,7 +388,7 @@ mod test {
assert_eq!(t.get_cycle(), false);
trb.set_control(12);
- assert_eq!(er.add_event(trb.clone()).unwrap(), ());
+ assert!(er.add_event(trb).is_ok());
assert_eq!(er.is_full().unwrap(), false);
assert_eq!(er.is_empty(), false);
let t: Trb = gm
diff --git a/devices/src/usb/xhci/interrupter.rs b/devices/src/usb/xhci/interrupter.rs
index 04d3d9f2b..3fc48ee61 100644
--- a/devices/src/usb/xhci/interrupter.rs
+++ b/devices/src/usb/xhci/interrupter.rs
@@ -10,33 +10,27 @@ use super::xhci_abi::{
use super::xhci_regs::*;
use crate::register_space::Register;
use base::{Error as SysError, Event};
-use std::fmt::{self, Display};
+use remain::sorted;
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory};
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- CastTrb(TrbError),
+ #[error("cannot add event: {0}")]
AddEvent(EventRingError),
- SetSegTableSize(EventRingError),
- SetSegTableBaseAddr(EventRingError),
+ #[error("cannot cast trb: {0}")]
+ CastTrb(TrbError),
+ #[error("cannot send interrupt: {0}")]
SendInterrupt(SysError),
+ #[error("cannot set seg table base addr: {0}")]
+ SetSegTableBaseAddr(EventRingError),
+ #[error("cannot set seg table size: {0}")]
+ SetSegTableSize(EventRingError),
}
type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
- match self {
- CastTrb(e) => write!(f, "cannot cast trb: {}", e),
- AddEvent(e) => write!(f, "cannot add event: {}", e),
- SetSegTableSize(e) => write!(f, "cannot set seg table size: {}", e),
- SetSegTableBaseAddr(e) => write!(f, "cannot set seg table base addr: {}", e),
- SendInterrupt(e) => write!(f, "cannot send interrupt: {}", e),
- }
- }
-}
-
/// See spec 4.17 for interrupters. Controller can send an event back to guest kernel driver
/// through interrupter.
pub struct Interrupter {
diff --git a/devices/src/usb/xhci/intr_resample_handler.rs b/devices/src/usb/xhci/intr_resample_handler.rs
index 883558d52..2526226cf 100644
--- a/devices/src/usb/xhci/intr_resample_handler.rs
+++ b/devices/src/usb/xhci/intr_resample_handler.rs
@@ -4,6 +4,8 @@
use super::interrupter::Interrupter;
use crate::utils::{EventHandler, EventLoop};
+
+use anyhow::Context;
use base::{error, Event, WatchingEvents};
use std::sync::Arc;
use sync::Mutex;
@@ -37,15 +39,12 @@ impl IntrResampleHandler {
Some(handler)
}
}
+
impl EventHandler for IntrResampleHandler {
- fn on_event(&self) -> Result<(), ()> {
- match self.resample_evt.read() {
- Ok(_) => {}
- Err(e) => {
- error!("cannot read resample evt: {}", e);
- return Err(());
- }
- }
+ fn on_event(&self) -> anyhow::Result<()> {
+ self.resample_evt
+ .read()
+ .context("cannot read resample evt")?;
usb_debug!("resample triggered");
let mut interrupter = self.interrupter.lock();
if !interrupter.event_ring_is_empty() {
@@ -54,10 +53,7 @@ impl EventHandler for IntrResampleHandler {
// component is sending interrupt at the same time.
// This might result in one more interrupt than we want. It's handled by
// kernel correctly.
- if let Err(e) = interrupter.interrupt() {
- error!("cannot send interrupt: {}", e);
- return Err(());
- }
+ interrupter.interrupt().context("cannot send interrupt")?;
}
Ok(())
}
diff --git a/devices/src/usb/xhci/ring_buffer.rs b/devices/src/usb/xhci/ring_buffer.rs
index 64ca6f7f5..d3ac8596f 100644
--- a/devices/src/usb/xhci/ring_buffer.rs
+++ b/devices/src/usb/xhci/ring_buffer.rs
@@ -5,33 +5,27 @@
use super::xhci_abi::{
AddressedTrb, Error as TrbError, LinkTrb, TransferDescriptor, Trb, TrbCast, TrbType,
};
+use remain::sorted;
use std::fmt::{self, Display};
use std::mem::size_of;
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- ReadGuestMemory(GuestMemoryError),
+ #[error("bad dequeue pointer: {0}")]
BadDequeuePointer(GuestAddress),
+ #[error("cannot cast trb: {0}")]
CastTrb(TrbError),
+ #[error("cannot read guest memory: {0}")]
+ ReadGuestMemory(GuestMemoryError),
+ #[error("cannot get trb chain bit: {0}")]
TrbChain(TrbError),
}
type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- ReadGuestMemory(e) => write!(f, "cannot read guest memory: {}", e),
- BadDequeuePointer(addr) => write!(f, "bad dequeue pointer: {}", addr),
- CastTrb(e) => write!(f, "cannot cast trb: {}", e),
- TrbChain(e) => write!(f, "cannot get trb chain bit: {}", e),
- }
- }
-}
-
/// Ring Buffer is segmented circular buffer in guest memory containing work items
/// called transfer descriptors, each of which consists of one or more TRBs.
/// Ring buffer logic is shared between transfer ring and command ring.
@@ -154,7 +148,7 @@ mod test {
#[test]
fn ring_test_dequeue() {
let trb_size = size_of::<Trb>() as u64;
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
let mut transfer_ring = RingBuffer::new(String::new(), gm.clone());
// Structure of ring buffer:
@@ -166,8 +160,7 @@ mod test {
trb.set_trb_type(TrbType::Normal);
trb.set_data_buffer(1);
trb.set_chain(true);
- gm.write_obj_at_addr(trb.clone(), GuestAddress(0x100))
- .unwrap();
+ gm.write_obj_at_addr(trb, GuestAddress(0x100)).unwrap();
trb.set_data_buffer(2);
gm.write_obj_at_addr(trb, GuestAddress(0x100 + trb_size))
@@ -233,15 +226,14 @@ mod test {
#[test]
fn transfer_ring_test_dequeue_failure() {
let trb_size = size_of::<Trb>() as u64;
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
let mut transfer_ring = RingBuffer::new(String::new(), gm.clone());
let mut trb = NormalTrb::new();
trb.set_trb_type(TrbType::Normal);
trb.set_data_buffer(1);
trb.set_chain(true);
- gm.write_obj_at_addr(trb.clone(), GuestAddress(0x100))
- .unwrap();
+ gm.write_obj_at_addr(trb, GuestAddress(0x100)).unwrap();
trb.set_data_buffer(2);
gm.write_obj_at_addr(trb, GuestAddress(0x100 + trb_size))
@@ -268,7 +260,7 @@ mod test {
#[test]
fn ring_test_toggle_cycle() {
let trb_size = size_of::<Trb>() as u64;
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
let mut transfer_ring = RingBuffer::new(String::new(), gm.clone());
let mut trb = NormalTrb::new();
@@ -276,8 +268,7 @@ mod test {
trb.set_data_buffer(1);
trb.set_chain(false);
trb.set_cycle(false);
- gm.write_obj_at_addr(trb.clone(), GuestAddress(0x100))
- .unwrap();
+ gm.write_obj_at_addr(trb, GuestAddress(0x100)).unwrap();
let mut ltrb = LinkTrb::new();
ltrb.set_trb_type(TrbType::Link);
@@ -308,8 +299,7 @@ mod test {
trb.set_trb_type(TrbType::Normal);
trb.set_data_buffer(2);
trb.set_cycle(true); // Link TRB toggled the cycle.
- gm.write_obj_at_addr(trb.clone(), GuestAddress(0x100))
- .unwrap();
+ gm.write_obj_at_addr(trb, GuestAddress(0x100)).unwrap();
// Read new transfer descriptor.
let descriptor = transfer_ring
@@ -336,8 +326,7 @@ mod test {
trb.set_trb_type(TrbType::Normal);
trb.set_data_buffer(3);
trb.set_cycle(false); // Link TRB toggled the cycle.
- gm.write_obj_at_addr(trb.clone(), GuestAddress(0x100))
- .unwrap();
+ gm.write_obj_at_addr(trb, GuestAddress(0x100)).unwrap();
// Read new transfer descriptor.
let descriptor = transfer_ring
diff --git a/devices/src/usb/xhci/ring_buffer_controller.rs b/devices/src/usb/xhci/ring_buffer_controller.rs
index 3d26a54f6..49ff719a7 100644
--- a/devices/src/usb/xhci/ring_buffer_controller.rs
+++ b/devices/src/usb/xhci/ring_buffer_controller.rs
@@ -9,30 +9,25 @@ use std::fmt::{self, Display};
use std::sync::{Arc, MutexGuard};
use sync::Mutex;
+use anyhow::Context;
use base::{error, Error as SysError, Event, WatchingEvents};
+use remain::sorted;
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory};
use super::ring_buffer::RingBuffer;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
+ #[error("failed to add event to event loop: {0}")]
AddEvent(utils::Error),
+ #[error("failed to create event: {0}")]
CreateEvent(SysError),
}
type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- AddEvent(e) => write!(f, "failed to add event to event loop: {}", e),
- CreateEvent(e) => write!(f, "failed to create event: {}", e),
- }
- }
-}
-
#[derive(PartialEq, Copy, Clone)]
enum RingBufferState {
/// Running: RingBuffer is running, consuming transfer descriptor.
@@ -52,7 +47,8 @@ pub trait TransferDescriptorHandler {
&self,
descriptor: TransferDescriptor,
complete_event: Event,
- ) -> std::result::Result<(), ()>;
+ ) -> anyhow::Result<()>;
+
/// Stop is called when trying to stop ring buffer controller. Returns true when stop must be
/// performed asynchronously. This happens because the handler is handling some descriptor
/// asynchronously, the stop callback of ring buffer controller must be called after the
@@ -184,15 +180,9 @@ impl<T> EventHandler for RingBufferController<T>
where
T: 'static + TransferDescriptorHandler + Send,
{
- fn on_event(&self) -> std::result::Result<(), ()> {
+ fn on_event(&self) -> anyhow::Result<()> {
// `self.event` triggers ring buffer controller to run, the value read is not important.
- match self.event.read() {
- Ok(_) => {}
- Err(e) => {
- error!("cannot read from event: {}", e);
- return Err(());
- }
- }
+ let _ = self.event.read().context("cannot read from event")?;
let mut state = self.state.lock();
match *state {
@@ -206,13 +196,10 @@ where
RingBufferState::Running => {}
}
- let transfer_descriptor = match self.lock_ring_buffer().dequeue_transfer_descriptor() {
- Ok(t) => t,
- Err(e) => {
- error!("cannot dequeue transfer descriptor: {}", e);
- return Err(());
- }
- };
+ let transfer_descriptor = self
+ .lock_ring_buffer()
+ .dequeue_transfer_descriptor()
+ .context("cannot dequeue transfer descriptor")?;
let transfer_descriptor = match transfer_descriptor {
Some(t) => t,
@@ -223,13 +210,7 @@ where
}
};
- let event = match self.event.try_clone() {
- Ok(evt) => evt,
- Err(e) => {
- error!("cannot clone event: {}", e);
- return Err(());
- }
- };
+ let event = self.event.try_clone().context("cannot clone event")?;
self.handler
.lock()
.handle_transfer_descriptor(transfer_descriptor, event)
@@ -251,7 +232,7 @@ mod tests {
&self,
descriptor: TransferDescriptor,
complete_event: Event,
- ) -> std::result::Result<(), ()> {
+ ) -> anyhow::Result<()> {
for atrb in descriptor {
assert_eq!(atrb.trb.get_trb_type().unwrap(), TrbType::Normal);
self.sender.send(atrb.trb.get_parameter() as i32).unwrap();
@@ -263,7 +244,7 @@ mod tests {
fn setup_mem() -> GuestMemory {
let trb_size = size_of::<Trb>() as u64;
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
// Structure of ring buffer:
// 0x100 --> 0x200 --> 0x300
@@ -274,8 +255,7 @@ mod tests {
trb.set_trb_type(TrbType::Normal);
trb.set_data_buffer(1);
trb.set_chain(true);
- gm.write_obj_at_addr(trb.clone(), GuestAddress(0x100))
- .unwrap();
+ gm.write_obj_at_addr(trb, GuestAddress(0x100)).unwrap();
trb.set_data_buffer(2);
gm.write_obj_at_addr(trb, GuestAddress(0x100 + trb_size))
diff --git a/devices/src/usb/xhci/ring_buffer_stop_cb.rs b/devices/src/usb/xhci/ring_buffer_stop_cb.rs
index 82eea62b8..503276fea 100644
--- a/devices/src/usb/xhci/ring_buffer_stop_cb.rs
+++ b/devices/src/usb/xhci/ring_buffer_stop_cb.rs
@@ -12,14 +12,14 @@ use std::sync::{Arc, Mutex};
/// The callback might not be invoked in certain cases. Don't depend this for safety.
#[derive(Clone)]
pub struct RingBufferStopCallback {
- inner: Arc<Mutex<RingBufferStopCallbackInner>>,
+ _inner: Arc<Mutex<RingBufferStopCallbackInner>>,
}
impl RingBufferStopCallback {
/// Create new callback from closure.
pub fn new<C: 'static + FnMut() + Send>(cb: C) -> RingBufferStopCallback {
RingBufferStopCallback {
- inner: Arc::new(Mutex::new(RingBufferStopCallbackInner {
+ _inner: Arc::new(Mutex::new(RingBufferStopCallbackInner {
callback: Box::new(cb),
})),
}
diff --git a/devices/src/usb/xhci/scatter_gather_buffer.rs b/devices/src/usb/xhci/scatter_gather_buffer.rs
index d67e92bd1..e3130dca5 100644
--- a/devices/src/usb/xhci/scatter_gather_buffer.rs
+++ b/devices/src/usb/xhci/scatter_gather_buffer.rs
@@ -6,36 +6,29 @@ use super::xhci_abi::{
AddressedTrb, Error as TrbError, NormalTrb, TransferDescriptor, TrbCast, TrbType,
};
use bit_field::Error as BitFieldError;
-use std::fmt::{self, Display};
+use remain::sorted;
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- ReadGuestMemory(GuestMemoryError),
- WriteGuestMemory(GuestMemoryError),
- UnknownTrbType(BitFieldError),
- CastTrb(TrbError),
+ #[error("should not build buffer from trb type: {0:?}")]
BadTrbType(TrbType),
+ #[error("cannot cast trb: {0}")]
+ CastTrb(TrbError),
+ #[error("immediate data longer than allowed: {0}")]
ImmediateDataTooLong(usize),
+ #[error("cannot read guest memory: {0}")]
+ ReadGuestMemory(GuestMemoryError),
+ #[error("unknown trb type: {0}")]
+ UnknownTrbType(BitFieldError),
+ #[error("cannot write guest memory: {0}")]
+ WriteGuestMemory(GuestMemoryError),
}
type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- ReadGuestMemory(e) => write!(f, "cannot read guest memory: {}", e),
- WriteGuestMemory(e) => write!(f, "cannot write guest memory: {}", e),
- UnknownTrbType(e) => write!(f, "unknown trb type: {}", e),
- CastTrb(e) => write!(f, "cannot cast trb: {}", e),
- BadTrbType(t) => write!(f, "should not build buffer from trb type: {:?}", t),
- ImmediateDataTooLong(l) => write!(f, "immediate data longer than allowed: {}", l),
- }
- }
-}
-
/// See xHCI spec 3.2.8 for scatter/gather transfer. It's used in bulk/interrupt transfers. See
/// 3.2.10 for details.
pub struct ScatterGatherBuffer {
@@ -94,7 +87,7 @@ impl ScatterGatherBuffer {
let mut total_size = 0usize;
let mut offset = 0;
for atrb in &self.td {
- let (guest_address, len) = self.get_trb_data(&atrb)?;
+ let (guest_address, len) = self.get_trb_data(atrb)?;
let buffer_len = {
if offset == buffer.len() {
return Ok(total_size);
@@ -121,7 +114,7 @@ impl ScatterGatherBuffer {
let mut total_size = 0usize;
let mut offset = 0;
for atrb in &self.td {
- let (guest_address, len) = self.get_trb_data(&atrb)?;
+ let (guest_address, len) = self.get_trb_data(atrb)?;
let buffer_len = {
if offset == buffer.len() {
return Ok(total_size);
@@ -151,7 +144,7 @@ mod test {
#[test]
fn scatter_gather_buffer_test() {
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
let mut td = TransferDescriptor::new();
// In this td, we are going to have scatter buffer at 0x100, length 4, 0x200 length 2 and
@@ -199,7 +192,7 @@ mod test {
#[test]
fn immediate_data_test() {
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
let mut td = TransferDescriptor::new();
let expected_immediate_data: [u8; 8] = [0xDE, 0xAD, 0xBE, 0xEF, 0xF0, 0x0D, 0xCA, 0xFE];
@@ -214,7 +207,7 @@ mod test {
gm.write_obj_at_addr(trb, GuestAddress(0xc00)).unwrap();
- let buffer = ScatterGatherBuffer::new(gm.clone(), td).unwrap();
+ let buffer = ScatterGatherBuffer::new(gm, td).unwrap();
let mut data_read = [0; 8];
buffer.read(&mut data_read).unwrap();
diff --git a/devices/src/usb/xhci/transfer_ring_controller.rs b/devices/src/usb/xhci/transfer_ring_controller.rs
index ff2ebea4d..d46928cf4 100644
--- a/devices/src/usb/xhci/transfer_ring_controller.rs
+++ b/devices/src/usb/xhci/transfer_ring_controller.rs
@@ -6,7 +6,9 @@ use crate::usb::xhci::ring_buffer_controller::{
Error as RingBufferControllerError, RingBufferController, TransferDescriptorHandler,
};
use crate::utils::EventLoop;
-use base::{error, Event};
+
+use anyhow::Context;
+use base::Event;
use std::sync::Arc;
use sync::Mutex;
use vm_memory::GuestMemory;
@@ -36,7 +38,7 @@ impl TransferDescriptorHandler for TransferRingTrbHandler {
&self,
descriptor: TransferDescriptor,
completion_event: Event,
- ) -> Result<(), ()> {
+ ) -> anyhow::Result<()> {
let xhci_transfer = self.transfer_manager.create_transfer(
self.mem.clone(),
self.port.clone(),
@@ -46,9 +48,9 @@ impl TransferDescriptorHandler for TransferRingTrbHandler {
descriptor,
completion_event,
);
- xhci_transfer.send_to_backend_if_valid().map_err(|e| {
- error!("failed to send transfer to backend: {}", e);
- })
+ xhci_transfer
+ .send_to_backend_if_valid()
+ .context("failed to send transfer to backend")
}
fn stop(&self) -> bool {
diff --git a/devices/src/usb/xhci/usb_hub.rs b/devices/src/usb/xhci/usb_hub.rs
index d54eaa91f..d529e106b 100644
--- a/devices/src/usb/xhci/usb_hub.rs
+++ b/devices/src/usb/xhci/usb_hub.rs
@@ -10,63 +10,41 @@ use super::xhci_regs::{
USB3_PORTS_END, USB3_PORTS_START, USB_STS_PORT_CHANGE_DETECT,
};
use crate::register_space::Register;
-use std::fmt::{self, Display};
+use remain::sorted;
use std::sync::{Arc, MutexGuard};
use sync::Mutex;
+use thiserror::Error;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
+ #[error("all suitable ports already attached")]
AllPortsAttached,
+ #[error("device already detached from port {0}")]
AlreadyDetached(u8),
+ #[error("failed to attach device to port {port_id}: {reason}")]
Attach {
port_id: u8,
reason: InterrupterError,
},
+ #[error("failed to detach device from port {port_id}: {reason}")]
Detach {
port_id: u8,
reason: InterrupterError,
},
+ #[error("device {bus}:{addr}:{vid:04x}:{pid:04x} is not attached")]
NoSuchDevice {
bus: u8,
addr: u8,
vid: u16,
pid: u16,
},
+ #[error("port {0} does not exist")]
NoSuchPort(u8),
}
type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- AllPortsAttached => write!(f, "all suitable ports already attached"),
- AlreadyDetached(port_id) => write!(f, "device already detached from port {}", port_id),
- Attach { port_id, reason } => {
- write!(f, "failed to attach device to port {}: {}", port_id, reason)
- }
- Detach { port_id, reason } => write!(
- f,
- "failed to detach device from port {}: {}",
- port_id, reason
- ),
- NoSuchDevice {
- bus,
- addr,
- vid,
- pid,
- } => write!(
- f,
- "device {}:{}:{:04x}:{:04x} is not attached",
- bus, addr, vid, pid
- ),
- NoSuchPort(port_id) => write!(f, "port {} does not exist", port_id),
- }
- }
-}
-
/// A port on usb hub. It could have a device connected to it.
pub struct UsbPort {
ty: BackendType,
diff --git a/devices/src/usb/xhci/xhci.rs b/devices/src/usb/xhci/xhci.rs
index 85f6de8f0..56e9a0431 100644
--- a/devices/src/usb/xhci/xhci.rs
+++ b/devices/src/usb/xhci/xhci.rs
@@ -15,54 +15,50 @@ use crate::usb::host_backend::{
host_backend_device_provider::HostBackendDeviceProvider,
};
use crate::utils::{Error as UtilsError, EventLoop, FailHandle};
-use base::{error, Event};
-use std::fmt::{self, Display};
+use crate::IrqLevelEvent;
+use base::error;
+use remain::sorted;
use std::sync::Arc;
use std::thread;
use sync::Mutex;
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory};
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- StartEventLoop(UtilsError),
+ #[error("failed to clone irq event: {0}")]
+ CloneIrqEvent(base::Error),
+ #[error("failed to clone resample event: {0}")]
+ CloneResampleEvent(base::Error),
+ #[error("failed to create command ring controller: {0}")]
+ CreateCommandRingController(CommandRingControllerError),
+ #[error("failed to enable interrupter: {0}")]
+ EnableInterrupter(InterrupterError),
+ #[error("failed to get device slot: {0}")]
GetDeviceSlot(u8),
- StartResampleHandler,
+ #[error("failed to reset port")]
+ ResetPort,
+ #[error("failed to ring doorbell: {0}")]
+ RingDoorbell(DeviceSlotError),
+ #[error("failed to send interrupt: {0}")]
SendInterrupt(InterrupterError),
- EnableInterrupter(InterrupterError),
+ #[error("failed to set event handler busy: {0}")]
+ SetEventHandlerBusy(InterrupterError),
+ #[error("failed to set interrupter moderation: {0}")]
SetModeration(InterrupterError),
+ #[error("failed to setup event ring: {0}")]
SetupEventRing(InterrupterError),
- SetEventHandlerBusy(InterrupterError),
+ #[error("failed to start event loop: {0}")]
+ StartEventLoop(UtilsError),
+ #[error("failed to start backend provider: {0}")]
StartProvider(HostBackendProviderError),
- RingDoorbell(DeviceSlotError),
- CreateCommandRingController(CommandRingControllerError),
- ResetPort,
+ #[error("failed to start resample handler")]
+ StartResampleHandler,
}
type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- StartEventLoop(e) => write!(f, "failed to start event loop: {}", e),
- GetDeviceSlot(i) => write!(f, "failed to get device slot: {}", i),
- StartResampleHandler => write!(f, "failed to start resample handler"),
- SendInterrupt(e) => write!(f, "failed to send interrupter: {}", e),
- EnableInterrupter(e) => write!(f, "failed to enable interrupter: {}", e),
- SetModeration(e) => write!(f, "failed to set interrupter moderation: {}", e),
- SetupEventRing(e) => write!(f, "failed to setup event ring: {}", e),
- SetEventHandlerBusy(e) => write!(f, "failed to set event handler busy: {}", e),
- StartProvider(e) => write!(f, "failed to start backend provider: {}", e),
- RingDoorbell(e) => write!(f, "failed to ring doorbell: {}", e),
- CreateCommandRingController(e) => {
- write!(f, "failed to create command ring controller: {}", e)
- }
- ResetPort => write!(f, "failed to reset port"),
- }
- }
-}
-
/// xHCI controller implementation.
pub struct Xhci {
fail_handle: Arc<dyn FailHandle>,
@@ -87,15 +83,22 @@ impl Xhci {
fail_handle: Arc<dyn FailHandle>,
mem: GuestMemory,
device_provider: HostBackendDeviceProvider,
- irq_evt: Event,
- irq_resample_evt: Event,
+ interrupt_evt: IrqLevelEvent,
regs: XhciRegs,
) -> Result<Arc<Self>> {
let (event_loop, join_handle) =
EventLoop::start("xhci".to_string(), Some(fail_handle.clone()))
.map_err(Error::StartEventLoop)?;
+ let irq_evt = interrupt_evt
+ .get_trigger()
+ .try_clone()
+ .map_err(Error::CloneIrqEvent)?;
let interrupter = Arc::new(Mutex::new(Interrupter::new(mem.clone(), irq_evt, &regs)));
let event_loop = Arc::new(event_loop);
+ let irq_resample_evt = interrupt_evt
+ .get_resample()
+ .try_clone()
+ .map_err(Error::CloneResampleEvent)?;
let intr_resample_handler =
IntrResampleHandler::start(&event_loop, interrupter.clone(), irq_resample_evt)
.ok_or(Error::StartResampleHandler)?;
diff --git a/devices/src/usb/xhci/xhci_abi.rs b/devices/src/usb/xhci/xhci_abi.rs
index ba5f78afd..bde8a5bf9 100644
--- a/devices/src/usb/xhci/xhci_abi.rs
+++ b/devices/src/usb/xhci/xhci_abi.rs
@@ -5,28 +5,22 @@
use bit_field::Error as BitFieldError;
use bit_field::*;
use data_model::DataInit;
+use remain::sorted;
use std::fmt::{self, Display};
+use thiserror::Error;
use vm_memory::GuestAddress;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- UnknownTrbType(BitFieldError),
+ #[error("cannot cast trb from raw memory")]
CannotCastTrb,
+ #[error("we got an unknown trb type value: {0}")]
+ UnknownTrbType(BitFieldError),
}
type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- UnknownTrbType(e) => write!(f, "we got an unknown trb type value: {}", e),
- CannotCastTrb => write!(f, "cannot cast trb from raw memory"),
- }
- }
-}
-
// Fixed size of all TRB types.
const TRB_SIZE: usize = 16;
diff --git a/devices/src/usb/xhci/xhci_controller.rs b/devices/src/usb/xhci/xhci_controller.rs
index 0889b356a..132dd39e9 100644
--- a/devices/src/usb/xhci/xhci_controller.rs
+++ b/devices/src/usb/xhci/xhci_controller.rs
@@ -3,16 +3,19 @@
// found in the LICENSE file.
use crate::pci::{
- PciAddress, PciBarConfiguration, PciClassCode, PciConfiguration, PciDevice, PciDeviceError,
- PciHeaderType, PciInterruptPin, PciProgrammingInterface, PciSerialBusSubClass,
+ BarRange, PciAddress, PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciClassCode,
+ PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciInterruptPin,
+ PciProgrammingInterface, PciSerialBusSubClass,
};
+
use crate::register_space::{Register, RegisterSpace};
use crate::usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider;
use crate::usb::xhci::xhci::Xhci;
use crate::usb::xhci::xhci_backend_device_provider::XhciBackendDeviceProvider;
use crate::usb::xhci::xhci_regs::{init_xhci_mmio_space_and_regs, XhciRegs};
use crate::utils::FailHandle;
-use base::{error, Event, RawDescriptor};
+use crate::IrqLevelEvent;
+use base::{error, AsRawDescriptor, RawDescriptor};
use resources::{Alloc, MmioType, SystemAllocator};
use std::mem;
use std::sync::atomic::{AtomicBool, Ordering};
@@ -79,8 +82,7 @@ enum XhciControllerState {
},
IrqAssigned {
device_provider: HostBackendDeviceProvider,
- irq_evt: Event,
- irq_resample_evt: Event,
+ irq_evt: IrqLevelEvent,
},
Initialized {
mmio: RegisterSpace,
@@ -106,7 +108,7 @@ impl XhciController {
0x01b73, // fresco logic, (google = 0x1ae0)
0x1000, // fresco logic pdk. This chip has broken msi. See kernel xhci-pci.c
PciClassCode::SerialBusController,
- &PciSerialBusSubClass::USB,
+ &PciSerialBusSubClass::Usb,
Some(&UsbControllerProgrammingInterface::Usb3HostController),
PciHeaderType::Device,
0,
@@ -129,7 +131,6 @@ impl XhciController {
XhciControllerState::IrqAssigned {
device_provider,
irq_evt,
- irq_resample_evt,
} => {
let (mmio, regs) = init_xhci_mmio_space_and_regs();
let fail_handle: Arc<dyn FailHandle> = Arc::new(XhciFailHandle::new(&regs));
@@ -138,7 +139,6 @@ impl XhciController {
self.mem.clone(),
device_provider,
irq_evt,
- irq_resample_evt,
regs,
) {
Ok(xhci) => Some(xhci),
@@ -172,7 +172,7 @@ impl PciDevice for XhciController {
resources: &mut SystemAllocator,
) -> Result<PciAddress, PciDeviceError> {
if self.pci_address.is_none() {
- self.pci_address = match resources.allocate_pci(self.debug_label()) {
+ self.pci_address = match resources.allocate_pci(0, self.debug_label()) {
Some(Alloc::PciBar {
bus,
dev,
@@ -188,6 +188,15 @@ impl PciDevice for XhciController {
fn keep_rds(&self) -> Vec<RawDescriptor> {
match &self.state {
XhciControllerState::Created { device_provider } => device_provider.keep_rds(),
+ XhciControllerState::IrqAssigned {
+ device_provider,
+ irq_evt,
+ } => {
+ let mut keep_rds = device_provider.keep_rds();
+ keep_rds.push(irq_evt.get_trigger().as_raw_descriptor());
+ keep_rds.push(irq_evt.get_resample().as_raw_descriptor());
+ keep_rds
+ }
_ => {
error!("xhci controller is in a wrong state");
vec![]
@@ -197,30 +206,33 @@ impl PciDevice for XhciController {
fn assign_irq(
&mut self,
- irq_evt: Event,
- irq_resample_evt: Event,
- irq_num: u32,
- irq_pin: PciInterruptPin,
- ) {
+ irq_evt: &IrqLevelEvent,
+ irq_num: Option<u32>,
+ ) -> Option<(u32, PciInterruptPin)> {
+ let gsi = irq_num?;
+ let pin = self.pci_address.map_or(
+ PciInterruptPin::IntA,
+ PciConfiguration::suggested_interrupt_pin,
+ );
match mem::replace(&mut self.state, XhciControllerState::Unknown) {
XhciControllerState::Created { device_provider } => {
- self.config_regs.set_irq(irq_num as u8, irq_pin);
+ self.config_regs.set_irq(gsi as u8, pin);
self.state = XhciControllerState::IrqAssigned {
device_provider,
- irq_evt,
- irq_resample_evt,
+ irq_evt: irq_evt.try_clone().ok()?,
}
}
_ => {
error!("xhci controller is in a wrong state");
}
}
+ Some((gsi, pin))
}
fn allocate_io_bars(
&mut self,
resources: &mut SystemAllocator,
- ) -> std::result::Result<Vec<(u64, u64)>, PciDeviceError> {
+ ) -> std::result::Result<Vec<BarRange>, PciDeviceError> {
let address = self
.pci_address
.expect("assign_address must be called prior to allocate_io_bars");
@@ -239,14 +251,25 @@ impl PciDevice for XhciController {
XHCI_BAR0_SIZE,
)
.map_err(|e| PciDeviceError::IoAllocationFailed(XHCI_BAR0_SIZE, e))?;
- let bar0_config = PciBarConfiguration::default()
- .set_register_index(0)
- .set_address(bar0_addr)
- .set_size(XHCI_BAR0_SIZE);
+ let bar0_config = PciBarConfiguration::new(
+ 0,
+ XHCI_BAR0_SIZE,
+ PciBarRegionType::Memory32BitRegion,
+ PciBarPrefetchable::NotPrefetchable,
+ )
+ .set_address(bar0_addr);
self.config_regs
.add_pci_bar(bar0_config)
.map_err(|e| PciDeviceError::IoRegistrationFailed(bar0_addr, e))?;
- Ok(vec![(bar0_addr, XHCI_BAR0_SIZE)])
+ Ok(vec![BarRange {
+ addr: bar0_addr,
+ size: XHCI_BAR0_SIZE,
+ prefetchable: false,
+ }])
+ }
+
+ fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
+ self.config_regs.get_bar_configuration(bar_num)
}
fn read_config_register(&self, reg_idx: usize) -> u32 {
diff --git a/devices/src/usb/xhci/xhci_transfer.rs b/devices/src/usb/xhci/xhci_transfer.rs
index a00868489..83dce9a9c 100644
--- a/devices/src/usb/xhci/xhci_transfer.rs
+++ b/devices/src/usb/xhci/xhci_transfer.rs
@@ -12,47 +12,41 @@ use super::xhci_abi::{
use super::xhci_regs::MAX_INTERRUPTER;
use base::{error, Error as SysError, Event};
use bit_field::Error as BitFieldError;
+use remain::sorted;
use std::cmp::min;
use std::fmt::{self, Display};
use std::mem;
use std::sync::{Arc, Weak};
use sync::Mutex;
+use thiserror::Error;
use usb_util::{TransferStatus, UsbRequestSetup};
use vm_memory::GuestMemory;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- TrbType(BitFieldError),
- CastTrb(TrbError),
- TransferLength(TrbError),
+ #[error("unexpected trb type: {0:?}")]
BadTrbType(TrbType),
- WriteCompletionEvent(SysError),
+ #[error("cannot cast trb: {0}")]
+ CastTrb(TrbError),
+ #[error("cannot create transfer buffer: {0}")]
CreateBuffer(BufferError),
+ #[error("cannot detach from port: {0}")]
DetachPort(HubError),
+ #[error("cannot send interrupt: {0}")]
SendInterrupt(InterrupterError),
+ #[error("failed to submit transfer to backend")]
SubmitTransfer,
+ #[error("cannot get transfer length: {0}")]
+ TransferLength(TrbError),
+ #[error("cannot get trb type: {0}")]
+ TrbType(BitFieldError),
+ #[error("cannot write completion event: {0}")]
+ WriteCompletionEvent(SysError),
}
type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- TrbType(e) => write!(f, "cannot get trb type: {}", e),
- CastTrb(e) => write!(f, "cannot cast trb: {}", e),
- TransferLength(e) => write!(f, "cannot get transfer length: {}", e),
- BadTrbType(t) => write!(f, "unexpected trb type: {:?}", t),
- WriteCompletionEvent(e) => write!(f, "cannot write completion event: {}", e),
- CreateBuffer(e) => write!(f, "cannot create transfer buffer: {}", e),
- DetachPort(e) => write!(f, "cannot detach from port: {}", e),
- SendInterrupt(e) => write!(f, "cannot send interrupter: {}", e),
- SubmitTransfer => write!(f, "failed to submit transfer to backend"),
- }
- }
-}
-
/// Type of usb endpoints.
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum TransferDirection {
@@ -108,6 +102,21 @@ pub enum XhciTransferType {
Noop,
}
+impl Display for XhciTransferType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::XhciTransferType::*;
+
+ match self {
+ Normal(_) => write!(f, "Normal"),
+ SetupStage(_) => write!(f, "SetupStage"),
+ DataStage(_) => write!(f, "DataStage"),
+ StatusStage => write!(f, "StatusStage"),
+ Isochronous(_) => write!(f, "Isochronous"),
+ Noop => write!(f, "Noop"),
+ }
+ }
+}
+
impl XhciTransferType {
/// Analyze transfer descriptor and return transfer type.
pub fn new(mem: GuestMemory, td: TransferDescriptor) -> Result<XhciTransferType> {
@@ -416,7 +425,7 @@ impl XhciTransfer {
fn validate_transfer(&self) -> Result<bool> {
let mut valid = true;
for atrb in &self.transfer_trbs {
- if !trb_is_valid(&atrb) {
+ if !trb_is_valid(atrb) {
self.interrupter
.lock()
.send_transfer_event_trb(
diff --git a/devices/src/utils/async_job_queue.rs b/devices/src/utils/async_job_queue.rs
index 9e41cb400..72956118e 100644
--- a/devices/src/utils/async_job_queue.rs
+++ b/devices/src/utils/async_job_queue.rs
@@ -4,7 +4,9 @@
use super::{Error, Result};
use super::{EventHandler, EventLoop};
-use base::{error, Event, WatchingEvents};
+
+use anyhow::Context;
+use base::{Event, WatchingEvents};
use std::mem;
use std::sync::Arc;
use sync::Mutex;
@@ -40,17 +42,11 @@ impl AsyncJobQueue {
}
impl EventHandler for AsyncJobQueue {
- fn on_event(&self) -> std::result::Result<(), ()> {
+ fn on_event(&self) -> anyhow::Result<()> {
// We want to read out the event, but the value is not important.
- match self.evt.read() {
- Ok(_) => {}
- Err(e) => {
- error!("read event failed {}", e);
- return Err(());
- }
- }
+ let _ = self.evt.read().context("read event failed")?;
- let jobs = mem::replace(&mut *self.jobs.lock(), Vec::new());
+ let jobs = mem::take(&mut *self.jobs.lock());
for mut cb in jobs {
cb();
}
diff --git a/devices/src/utils/error.rs b/devices/src/utils/error.rs
index 453b052f9..886152951 100644
--- a/devices/src/utils/error.rs
+++ b/devices/src/utils/error.rs
@@ -3,37 +3,28 @@
// found in the LICENSE file.
use base::Error as SysError;
-use std::fmt::{self, Display};
+use remain::sorted;
+use thiserror::Error;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- EventLoopAlreadyFailed,
+ #[error("failed to create event: {0}")]
CreateEvent(SysError),
- ReadEvent(SysError),
- WriteEvent(SysError),
+ #[error("failed to create poll context: {0}")]
CreateWaitContext(SysError),
+ #[error("event loop already failed due to previous errors")]
+ EventLoopAlreadyFailed,
+ #[error("failed to read event: {0}")]
+ ReadEvent(SysError),
+ #[error("failed to start thread: {0}")]
+ StartThread(std::io::Error),
+ #[error("failed to add fd to poll context: {0}")]
WaitContextAddDescriptor(SysError),
+ #[error("failed to delete fd from poll context: {0}")]
WaitContextDeleteDescriptor(SysError),
- StartThread(std::io::Error),
-}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- EventLoopAlreadyFailed => write!(f, "event loop already failed due to previous errors"),
- CreateEvent(e) => write!(f, "failed to create event: {}", e),
- ReadEvent(e) => write!(f, "failed to read event: {}", e),
- WriteEvent(e) => write!(f, "failed to write event: {}", e),
- CreateWaitContext(e) => write!(f, "failed to create poll context: {}", e),
- WaitContextAddDescriptor(e) => write!(f, "failed to add fd to poll context: {}", e),
- WaitContextDeleteDescriptor(e) => {
- write!(f, "failed to delete fd from poll context: {}", e)
- }
- StartThread(e) => write!(f, "failed to start thread: {}", e),
- }
- }
+ #[error("failed to write event: {0}")]
+ WriteEvent(SysError),
}
pub type Result<T> = std::result::Result<T, Error>;
diff --git a/devices/src/utils/event_loop.rs b/devices/src/utils/event_loop.rs
index f99d18a2b..f2bb8fda2 100644
--- a/devices/src/utils/event_loop.rs
+++ b/devices/src/utils/event_loop.rs
@@ -4,8 +4,8 @@
use super::error::{Error, Result};
use base::{
- error, warn, wrap_descriptor, AsRawDescriptor, Descriptor, EpollContext, EpollEvents, Event,
- RawDescriptor, WatchingEvents,
+ error, warn, AsRawDescriptor, Descriptor, EpollContext, EpollEvents, Event, RawDescriptor,
+ WatchingEvents,
};
use std::collections::BTreeMap;
use std::mem::drop;
@@ -48,7 +48,7 @@ pub struct EventLoop {
/// Interface for event handler.
pub trait EventHandler: Send + Sync {
- fn on_event(&self) -> std::result::Result<(), ()>;
+ fn on_event(&self) -> anyhow::Result<()>;
}
impl EventLoop {
@@ -65,11 +65,8 @@ impl EventLoop {
Arc::new(Mutex::new(BTreeMap::new()));
let poll_ctx: EpollContext<Descriptor> = EpollContext::new()
.and_then(|pc| {
- pc.add(
- &wrap_descriptor(&stop_evt),
- Descriptor(stop_evt.as_raw_descriptor()),
- )
- .and(Ok(pc))
+ pc.add(&stop_evt, Descriptor(stop_evt.as_raw_descriptor()))
+ .and(Ok(pc))
})
.map_err(Error::CreateWaitContext)?;
@@ -99,39 +96,40 @@ impl EventLoop {
}
};
for event in &events {
- if event.token().as_raw_descriptor() == stop_evt.as_raw_descriptor() {
+ let fd = event.token().as_raw_descriptor();
+ if fd == stop_evt.as_raw_descriptor() {
return;
+ }
+
+ let mut locked = fd_callbacks.lock();
+ let weak_handler = match locked.get(&fd) {
+ Some(cb) => cb.clone(),
+ None => {
+ warn!("callback for fd {} already removed", fd);
+ continue;
+ }
+ };
+
+ // If the file descriptor is hung up, remove it after calling the handler
+ // one final time.
+ let mut remove = event.hungup();
+
+ if let Some(handler) = weak_handler.upgrade() {
+ // Drop lock before triggering the event.
+ drop(locked);
+ if let Err(e) = handler.on_event() {
+ error!("removing event handler due to error: {:#}", e);
+ remove = true;
+ }
+ locked = fd_callbacks.lock();
} else {
- let fd = event.token().as_raw_descriptor();
- let mut locked = fd_callbacks.lock();
- let weak_handler = match locked.get(&fd) {
- Some(cb) => cb.clone(),
- None => {
- warn!("callback for fd {} already removed", fd);
- continue;
- }
- };
- match weak_handler.upgrade() {
- Some(handler) => {
- // Drop lock before triggering the event.
- drop(locked);
- match handler.on_event() {
- Ok(()) => {}
- Err(_) => {
- error!("event loop stopping due to handle event error");
- fail_handle.fail();
- return;
- }
- };
- }
- // If the handler is already gone, we remove the fd.
- None => {
- let _ = poll_ctx.delete(&Descriptor(fd));
- if locked.remove(&fd).is_none() {
- error!("fail to remove handler for file descriptor {}", fd);
- }
- }
- };
+ // If the handler is already gone, we remove the fd.
+ remove = true;
+ }
+
+ if remove {
+ let _ = poll_ctx.delete(&event.token());
+ let _ = locked.remove(&fd);
}
}
}
@@ -162,7 +160,7 @@ impl EventLoop {
// This might fail due to epoll syscall. Check epoll_ctl(2).
self.poll_ctx
.add_fd_with_events(
- &wrap_descriptor(descriptor),
+ descriptor,
events,
Descriptor(descriptor.as_raw_descriptor()),
)
@@ -178,7 +176,7 @@ impl EventLoop {
}
// This might fail due to epoll syscall. Check epoll_ctl(2).
self.poll_ctx
- .delete(&wrap_descriptor(descriptor))
+ .delete(descriptor)
.map_err(Error::WaitContextDeleteDescriptor)?;
self.handlers.lock().remove(&descriptor.as_raw_descriptor());
Ok(())
@@ -208,7 +206,7 @@ mod tests {
}
impl EventHandler for EventLoopTestHandler {
- fn on_event(&self) -> std::result::Result<(), ()> {
+ fn on_event(&self) -> anyhow::Result<()> {
self.evt.read().unwrap();
*self.val.lock().unwrap() += 1;
self.cvar.notify_one();
@@ -239,7 +237,12 @@ mod tests {
)
.unwrap();
self_evt.write(1).unwrap();
- let _ = h.cvar.wait(h.val.lock().unwrap()).unwrap();
+ {
+ let mut val = h.val.lock().unwrap();
+ while *val < 1 {
+ val = h.cvar.wait(val).unwrap();
+ }
+ }
l.stop();
j.join().unwrap();
assert_eq!(*(h.val.lock().unwrap()), 1);
diff --git a/devices/src/vfio.rs b/devices/src/vfio.rs
index 9fda0d65d..f99d7440b 100644
--- a/devices/src/vfio.rs
+++ b/devices/src/vfio.rs
@@ -2,94 +2,130 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use data_model::vec_with_array_field;
+use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::CString;
-use std::fmt;
use std::fs::{File, OpenOptions};
use std::io;
use std::mem;
+use std::os::raw::c_ulong;
use std::os::unix::prelude::FileExt;
use std::path::{Path, PathBuf};
+use std::slice;
use std::sync::Arc;
use std::u32;
-use sync::Mutex;
+use crate::IommuDevType;
+use base::error;
use base::{
- ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref, ioctl_with_val, warn,
- AsRawDescriptor, Error, Event, FromRawDescriptor, RawDescriptor, SafeDescriptor,
+ ioctl, ioctl_with_mut_ptr, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref, ioctl_with_val,
+ warn, AsRawDescriptor, Error, Event, FromRawDescriptor, RawDescriptor, SafeDescriptor,
};
+use data_model::{vec_with_array_field, DataInit};
use hypervisor::{DeviceKind, Vm};
-use vm_memory::GuestMemory;
-
+use once_cell::sync::OnceCell;
+use remain::sorted;
+use resources::address_allocator::AddressAllocator;
+use resources::{Alloc, Error as ResourcesError};
+use sync::Mutex;
+use thiserror::Error;
use vfio_sys::*;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum VfioError {
- OpenContainer(io::Error),
- OpenGroup(io::Error),
- GetGroupStatus(Error),
- GroupViable,
- VfioApiVersion,
- VfioType1V2,
- GroupSetContainer(Error),
+ #[error("failed to borrow global vfio container")]
+ BorrowVfioContainer,
+ #[error("failed to duplicate VfioContainer")]
+ ContainerDupError,
+ #[error("failed to set container's IOMMU driver type as VfioType1V2: {0}")]
ContainerSetIOMMU(Error),
- GroupGetDeviceFD(Error),
+ #[error("failed to create KVM vfio device: {0}")]
CreateVfioKvmDevice(Error),
- KvmSetDeviceAttr(Error),
- VfioDeviceGetInfo(Error),
- VfioDeviceGetRegionInfo(Error),
+ #[error("failed to get Group Status: {0}")]
+ GetGroupStatus(Error),
+ #[error("failed to get vfio device fd: {0}")]
+ GroupGetDeviceFD(Error),
+ #[error("failed to add vfio group into vfio container: {0}")]
+ GroupSetContainer(Error),
+ #[error("group is inviable")]
+ GroupViable,
+ #[error("invalid region index: {0}")]
+ InvalidIndex(u32),
+ #[error("invalid file path")]
InvalidPath,
+ #[error("failed to add guest memory map into iommu table: {0}")]
IommuDmaMap(Error),
+ #[error("failed to remove guest memory map from iommu table: {0}")]
IommuDmaUnmap(Error),
- VfioIrqEnable(Error),
+ #[error("failed to get IOMMU cap info from host")]
+ IommuGetCapInfo,
+ #[error("failed to get IOMMU info from host: {0}")]
+ IommuGetInfo(Error),
+ #[error("failed to set KVM vfio device's attribute: {0}")]
+ KvmSetDeviceAttr(Error),
+ #[error("AddressAllocator is unavailable")]
+ NoRescAlloc,
+ #[error("failed to open /dev/vfio/vfio container: {0}")]
+ OpenContainer(io::Error),
+ #[error("failed to open /dev/vfio/$group_num group: {0}")]
+ OpenGroup(io::Error),
+ #[error("resources error: {0}")]
+ Resources(ResourcesError),
+ #[error(
+ "vfio API version doesn't match with VFIO_API_VERSION defined in vfio_sys/src/vfio.rs"
+ )]
+ VfioApiVersion,
+ #[error("failed to get vfio device's info or info doesn't match: {0}")]
+ VfioDeviceGetInfo(Error),
+ #[error("failed to get vfio device's region info: {0}")]
+ VfioDeviceGetRegionInfo(Error),
+ #[error("failed to disable vfio deviece's irq: {0}")]
VfioIrqDisable(Error),
- VfioIrqUnmask(Error),
+ #[error("failed to enable vfio deviece's irq: {0}")]
+ VfioIrqEnable(Error),
+ #[error("failed to mask vfio deviece's irq: {0}")]
VfioIrqMask(Error),
+ #[error("failed to unmask vfio deviece's irq: {0}")]
+ VfioIrqUnmask(Error),
+ #[error("container dones't support VfioType1V2 IOMMU driver type")]
+ VfioType1V2,
}
-impl fmt::Display for VfioError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- VfioError::OpenContainer(e) => write!(f, "failed to open /dev/vfio/vfio container: {}", e),
- VfioError::OpenGroup(e) => write!(f, "failed to open /dev/vfio/$group_num group: {}", e),
- VfioError::GetGroupStatus(e) => write!(f, "failed to get Group Status: {}", e),
- VfioError::GroupViable => write!(f, "group is inviable"),
- VfioError::VfioApiVersion => write!(f, "vfio API version doesn't match with VFIO_API_VERSION defined in vfio_sys/srv/vfio.rs"),
- VfioError::VfioType1V2 => write!(f, "container dones't support VfioType1V2 IOMMU driver type"),
- VfioError::GroupSetContainer(e) => write!(f, "failed to add vfio group into vfio container: {}", e),
- VfioError::ContainerSetIOMMU(e) => write!(f, "failed to set container's IOMMU driver type as VfioType1V2: {}", e),
- VfioError::GroupGetDeviceFD(e) => write!(f, "failed to get vfio device fd: {}", e),
- VfioError::CreateVfioKvmDevice(e) => write!(f, "failed to create KVM vfio device: {}", e),
- VfioError::KvmSetDeviceAttr(e) => write!(f, "failed to set KVM vfio device's attribute: {}", e),
- VfioError::VfioDeviceGetInfo(e) => write!(f, "failed to get vfio device's info or info doesn't match: {}", e),
- VfioError::VfioDeviceGetRegionInfo(e) => write!(f, "failed to get vfio device's region info: {}", e),
- VfioError::InvalidPath => write!(f,"invalid file path"),
- VfioError::IommuDmaMap(e) => write!(f, "failed to add guest memory map into iommu table: {}", e),
- VfioError::IommuDmaUnmap(e) => write!(f, "failed to remove guest memory map from iommu table: {}", e),
- VfioError::VfioIrqEnable(e) => write!(f, "failed to enable vfio deviece's irq: {}", e),
- VfioError::VfioIrqDisable(e) => write!(f, "failed to disable vfio deviece's irq: {}", e),
- VfioError::VfioIrqUnmask(e) => write!(f, "failed to unmask vfio deviece's irq: {}", e),
- VfioError::VfioIrqMask(e) => write!(f, "failed to mask vfio deviece's irq: {}", e),
- }
- }
-}
+type Result<T> = std::result::Result<T, VfioError>;
fn get_error() -> Error {
Error::last()
}
+static KVM_VFIO_FILE: OnceCell<SafeDescriptor> = OnceCell::new();
+
+enum KvmVfioGroupOps {
+ Add,
+ Delete,
+}
+
+#[repr(u32)]
+enum IommuType {
+ Type1V2 = VFIO_TYPE1v2_IOMMU,
+}
+
/// VfioContainer contain multi VfioGroup, and delegate an IOMMU domain table
pub struct VfioContainer {
container: File,
- kvm_vfio_dev: Option<SafeDescriptor>,
- groups: HashMap<u32, Arc<VfioGroup>>,
+ groups: HashMap<u32, Arc<Mutex<VfioGroup>>>,
+}
+
+fn extract_vfio_struct<T>(bytes: &[u8], offset: usize) -> T
+where
+ T: DataInit,
+{
+ T::from_reader(&bytes[offset..(offset + mem::size_of::<T>())]).expect("malformed kernel data")
}
const VFIO_API_VERSION: u8 = 0;
impl VfioContainer {
- /// Open VfioContainer
- pub fn new() -> Result<Self, VfioError> {
+ pub fn new() -> Result<Self> {
let container = OpenOptions::new()
.read(true)
.write(true)
@@ -104,39 +140,58 @@ impl VfioContainer {
Ok(VfioContainer {
container,
- kvm_vfio_dev: None,
groups: HashMap::new(),
})
}
- fn check_extension(&self, val: u32) -> bool {
- if val != VFIO_TYPE1_IOMMU && val != VFIO_TYPE1v2_IOMMU {
- panic!("IOMMU type error");
+ // Construct a VfioContainer from an exist container file.
+ pub fn new_from_container(container: File) -> Result<Self> {
+ // Safe as file is vfio container descriptor and ioctl is defined by kernel.
+ let version = unsafe { ioctl(&container, VFIO_GET_API_VERSION()) };
+ if version as u8 != VFIO_API_VERSION {
+ return Err(VfioError::VfioApiVersion);
}
+ Ok(VfioContainer {
+ container,
+ groups: HashMap::new(),
+ })
+ }
+
+ fn is_group_set(&self, group_id: u32) -> bool {
+ self.groups.get(&group_id).is_some()
+ }
+
+ fn check_extension(&self, val: IommuType) -> bool {
// Safe as file is vfio container and make sure val is valid.
- let ret = unsafe { ioctl_with_val(self, VFIO_CHECK_EXTENSION(), val.into()) };
+ let ret = unsafe { ioctl_with_val(self, VFIO_CHECK_EXTENSION(), val as c_ulong) };
ret == 1
}
- fn set_iommu(&self, val: u32) -> i32 {
- if val != VFIO_TYPE1_IOMMU && val != VFIO_TYPE1v2_IOMMU {
- panic!("IOMMU type error");
- }
-
+ fn set_iommu(&self, val: IommuType) -> i32 {
// Safe as file is vfio container and make sure val is valid.
- unsafe { ioctl_with_val(self, VFIO_SET_IOMMU(), val.into()) }
+ unsafe { ioctl_with_val(self, VFIO_SET_IOMMU(), val as c_ulong) }
}
- unsafe fn vfio_dma_map(&self, iova: u64, size: u64, user_addr: u64) -> Result<(), VfioError> {
- let dma_map = vfio_iommu_type1_dma_map {
+ pub unsafe fn vfio_dma_map(
+ &self,
+ iova: u64,
+ size: u64,
+ user_addr: u64,
+ write_en: bool,
+ ) -> Result<()> {
+ let mut dma_map = vfio_iommu_type1_dma_map {
argsz: mem::size_of::<vfio_iommu_type1_dma_map>() as u32,
- flags: VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
+ flags: VFIO_DMA_MAP_FLAG_READ,
vaddr: user_addr,
iova,
size,
};
+ if write_en {
+ dma_map.flags |= VFIO_DMA_MAP_FLAG_WRITE;
+ }
+
let ret = ioctl_with_ref(self, VFIO_IOMMU_MAP_DMA(), &dma_map);
if ret != 0 {
return Err(VfioError::IommuDmaMap(get_error()));
@@ -145,12 +200,13 @@ impl VfioContainer {
Ok(())
}
- fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<(), VfioError> {
+ pub fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
let mut dma_unmap = vfio_iommu_type1_dma_unmap {
argsz: mem::size_of::<vfio_iommu_type1_dma_unmap>() as u32,
flags: 0,
iova,
size,
+ ..Default::default()
};
// Safe as file is vfio container, dma_unmap is constructed by us, and
@@ -163,52 +219,143 @@ impl VfioContainer {
Ok(())
}
- fn init(&mut self, vm: &impl Vm, guest_mem: &GuestMemory) -> Result<(), VfioError> {
- if !self.check_extension(VFIO_TYPE1v2_IOMMU) {
- return Err(VfioError::VfioType1V2);
+ pub fn vfio_get_iommu_page_size_mask(&self) -> Result<u64> {
+ let mut iommu_info = vfio_iommu_type1_info {
+ argsz: mem::size_of::<vfio_iommu_type1_info>() as u32,
+ flags: 0,
+ iova_pgsizes: 0,
+ ..Default::default()
+ };
+
+ // Safe as file is vfio container, iommu_info has valid values,
+ // and we check the return value
+ let ret = unsafe { ioctl_with_mut_ref(self, VFIO_IOMMU_GET_INFO(), &mut iommu_info) };
+ if ret != 0 || (iommu_info.flags & VFIO_IOMMU_INFO_PGSIZES) == 0 {
+ return Err(VfioError::IommuGetInfo(get_error()));
}
- if self.set_iommu(VFIO_TYPE1v2_IOMMU) < 0 {
- return Err(VfioError::ContainerSetIOMMU(get_error()));
+ Ok(iommu_info.iova_pgsizes)
+ }
+
+ pub fn vfio_iommu_iova_get_iova_ranges(&self) -> Result<Vec<vfio_iova_range>> {
+ // Query the buffer size needed fetch the capabilities.
+ let mut iommu_info_argsz = vfio_iommu_type1_info {
+ argsz: mem::size_of::<vfio_iommu_type1_info>() as u32,
+ flags: 0,
+ iova_pgsizes: 0,
+ ..Default::default()
+ };
+
+ // Safe as file is vfio container, iommu_info_argsz has valid values,
+ // and we check the return value
+ let ret = unsafe { ioctl_with_mut_ref(self, VFIO_IOMMU_GET_INFO(), &mut iommu_info_argsz) };
+ if ret != 0 {
+ return Err(VfioError::IommuGetInfo(get_error()));
+ }
+
+ if (iommu_info_argsz.flags & VFIO_IOMMU_INFO_CAPS) == 0 {
+ return Err(VfioError::IommuGetCapInfo);
+ }
+
+ let mut iommu_info = vec_with_array_field::<vfio_iommu_type1_info, u8>(
+ iommu_info_argsz.argsz as usize - mem::size_of::<vfio_iommu_type1_info>(),
+ );
+ iommu_info[0].argsz = iommu_info_argsz.argsz;
+ // Safe as file is vfio container, iommu_info has valid values,
+ // and we check the return value
+ let ret =
+ unsafe { ioctl_with_mut_ptr(self, VFIO_IOMMU_GET_INFO(), iommu_info.as_mut_ptr()) };
+ if ret != 0 {
+ return Err(VfioError::IommuGetInfo(get_error()));
}
- // Add all guest memory regions into vfio container's iommu table,
- // then vfio kernel driver could access guest memory from gfn
- guest_mem.with_regions(|_index, guest_addr, size, host_addr, _mmap, _fd_offset| {
- // Safe because the guest regions are guaranteed not to overlap
- unsafe { self.vfio_dma_map(guest_addr.0, size as u64, host_addr as u64) }
- })?;
+ // Safe because we initialized iommu_info with enough space, u8 has less strict
+ // alignment, and since it will no longer be mutated.
+ let info_bytes = unsafe {
+ std::slice::from_raw_parts(
+ iommu_info.as_ptr() as *const u8,
+ iommu_info_argsz.argsz as usize,
+ )
+ };
+
+ if (iommu_info[0].flags & VFIO_IOMMU_INFO_CAPS) == 0 {
+ return Err(VfioError::IommuGetCapInfo);
+ }
- let vfio_descriptor = vm
- .create_device(DeviceKind::Vfio)
- .map_err(VfioError::CreateVfioKvmDevice)?;
- self.kvm_vfio_dev = Some(vfio_descriptor);
+ let mut offset = iommu_info[0].cap_offset as usize;
+ while offset != 0 {
+ let header = extract_vfio_struct::<vfio_info_cap_header>(info_bytes, offset);
+
+ if header.id == VFIO_IOMMU_TYPE1_INFO_CAP_IOVA_RANGE as u16 && header.version == 1 {
+ let iova_header = extract_vfio_struct::<vfio_iommu_type1_info_cap_iova_range_header>(
+ info_bytes, offset,
+ );
+ let range_offset = offset + mem::size_of::<vfio_iommu_type1_info_cap_iova_range>();
+ let mut ret = Vec::new();
+ for i in 0..iova_header.nr_iovas {
+ ret.push(extract_vfio_struct::<vfio_iova_range>(
+ info_bytes,
+ range_offset + i as usize * mem::size_of::<vfio_iova_range>(),
+ ));
+ }
+ return Ok(ret);
+ }
+ offset = header.next as usize;
+ }
+
+ Err(VfioError::IommuGetCapInfo)
+ }
+
+ fn init_vfio_iommu(&mut self) -> Result<()> {
+ if !self.check_extension(IommuType::Type1V2) {
+ return Err(VfioError::VfioType1V2);
+ }
+
+ if self.set_iommu(IommuType::Type1V2) < 0 {
+ return Err(VfioError::ContainerSetIOMMU(get_error()));
+ }
Ok(())
}
- fn get_group(
+ fn get_group_with_vm(
&mut self,
id: u32,
vm: &impl Vm,
- guest_mem: &GuestMemory,
- ) -> Result<Arc<VfioGroup>, VfioError> {
+ iommu_enabled: bool,
+ ) -> Result<Arc<Mutex<VfioGroup>>> {
match self.groups.get(&id) {
Some(group) => Ok(group.clone()),
None => {
- let group = Arc::new(VfioGroup::new(self, id)?);
-
+ let group = Arc::new(Mutex::new(VfioGroup::new(self, id)?));
if self.groups.is_empty() {
- // Before the first group is added into container, do once cotainer
- // initialize for a vm
- self.init(vm, guest_mem)?;
+ // Before the first group is added into container, do once per
+ // container initialization.
+ self.init_vfio_iommu()?;
+
+ if !iommu_enabled {
+ vm.get_memory().with_regions(
+ |_index, guest_addr, size, host_addr, _mmap, _fd_offset| {
+ // Safe because the guest regions are guaranteed not to overlap
+ unsafe {
+ self.vfio_dma_map(
+ guest_addr.0,
+ size as u64,
+ host_addr as u64,
+ true,
+ )
+ }
+ },
+ )?;
+ }
}
- let kvm_vfio_file = self
- .kvm_vfio_dev
- .as_ref()
- .expect("kvm vfio device should exist");
- group.kvm_device_add_group(kvm_vfio_file)?;
+ let kvm_vfio_file = KVM_VFIO_FILE
+ .get_or_try_init(|| vm.create_device(DeviceKind::Vfio))
+ .map_err(VfioError::CreateVfioKvmDevice)?;
+ group
+ .lock()
+ .kvm_device_set_group(kvm_vfio_file, KvmVfioGroupOps::Add)?;
self.groups.insert(id, group.clone());
@@ -216,6 +363,58 @@ impl VfioContainer {
}
}
}
+
+ fn get_group(&mut self, id: u32) -> Result<Arc<Mutex<VfioGroup>>> {
+ match self.groups.get(&id) {
+ Some(group) => Ok(group.clone()),
+ None => {
+ let group = Arc::new(Mutex::new(VfioGroup::new(self, id)?));
+
+ if self.groups.is_empty() {
+ // Before the first group is added into container, do once per
+ // container initialization.
+ self.init_vfio_iommu()?;
+ }
+
+ self.groups.insert(id, group.clone());
+ Ok(group)
+ }
+ }
+ }
+
+ fn remove_group(&mut self, id: u32, reduce: bool) {
+ let mut remove = false;
+
+ if let Some(group) = self.groups.get(&id) {
+ if reduce {
+ group.lock().reduce_device_num();
+ }
+ if group.lock().device_num() == 0 {
+ let kvm_vfio_file = KVM_VFIO_FILE.get().expect("kvm vfio file isn't created");
+ if group
+ .lock()
+ .kvm_device_set_group(kvm_vfio_file, KvmVfioGroupOps::Delete)
+ .is_err()
+ {
+ warn!("failing in remove vfio group from kvm device");
+ }
+ remove = true;
+ }
+ }
+
+ if remove {
+ self.groups.remove(&id);
+ }
+ }
+
+ pub fn into_raw_descriptor(&self) -> Result<RawDescriptor> {
+ let raw_descriptor = unsafe { libc::dup(self.container.as_raw_descriptor()) };
+ if raw_descriptor < 0 {
+ Err(VfioError::ContainerDupError)
+ } else {
+ Ok(raw_descriptor)
+ }
+ }
}
impl AsRawDescriptor for VfioContainer {
@@ -226,14 +425,12 @@ impl AsRawDescriptor for VfioContainer {
struct VfioGroup {
group: File,
+ device_num: u32,
}
impl VfioGroup {
- fn new(container: &VfioContainer, id: u32) -> Result<Self, VfioError> {
- let mut group_path = String::from("/dev/vfio/");
- let s_id = &id;
- group_path.push_str(s_id.to_string().as_str());
-
+ fn new(container: &VfioContainer, id: u32) -> Result<Self> {
+ let group_path = format!("/dev/vfio/{}", id);
let group_file = OpenOptions::new()
.read(true)
.write(true)
@@ -269,17 +466,46 @@ impl VfioGroup {
return Err(VfioError::GroupSetContainer(get_error()));
}
- Ok(VfioGroup { group: group_file })
+ Ok(VfioGroup {
+ group: group_file,
+ device_num: 0,
+ })
}
- fn kvm_device_add_group(&self, kvm_vfio_file: &SafeDescriptor) -> Result<(), VfioError> {
+ fn get_group_id<P: AsRef<Path>>(sysfspath: P) -> Result<u32> {
+ let mut uuid_path = PathBuf::new();
+ uuid_path.push(sysfspath);
+ uuid_path.push("iommu_group");
+ let group_path = uuid_path.read_link().map_err(|_| VfioError::InvalidPath)?;
+ let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?;
+ let group_str = group_osstr.to_str().ok_or(VfioError::InvalidPath)?;
+ let group_id = group_str
+ .parse::<u32>()
+ .map_err(|_| VfioError::InvalidPath)?;
+
+ Ok(group_id)
+ }
+
+ fn kvm_device_set_group(
+ &self,
+ kvm_vfio_file: &SafeDescriptor,
+ ops: KvmVfioGroupOps,
+ ) -> Result<()> {
let group_descriptor = self.as_raw_descriptor();
let group_descriptor_ptr = &group_descriptor as *const i32;
- let vfio_dev_attr = kvm_sys::kvm_device_attr {
- flags: 0,
- group: kvm_sys::KVM_DEV_VFIO_GROUP,
- attr: kvm_sys::KVM_DEV_VFIO_GROUP_ADD as u64,
- addr: group_descriptor_ptr as u64,
+ let vfio_dev_attr = match ops {
+ KvmVfioGroupOps::Add => kvm_sys::kvm_device_attr {
+ flags: 0,
+ group: kvm_sys::KVM_DEV_VFIO_GROUP,
+ attr: kvm_sys::KVM_DEV_VFIO_GROUP_ADD as u64,
+ addr: group_descriptor_ptr as u64,
+ },
+ KvmVfioGroupOps::Delete => kvm_sys::kvm_device_attr {
+ flags: 0,
+ group: kvm_sys::KVM_DEV_VFIO_GROUP,
+ attr: kvm_sys::KVM_DEV_VFIO_GROUP_DEL as u64,
+ addr: group_descriptor_ptr as u64,
+ },
};
// Safe as we are the owner of vfio_dev_fd and vfio_dev_attr which are valid value,
@@ -297,7 +523,7 @@ impl VfioGroup {
Ok(())
}
- fn get_device(&self, name: &str) -> Result<File, VfioError> {
+ fn get_device(&self, name: &str) -> Result<File> {
let path: CString = CString::new(name.as_bytes()).expect("CString::new() failed");
let path_ptr = path.as_ptr();
@@ -310,6 +536,18 @@ impl VfioGroup {
// Safe as ret is valid FD
Ok(unsafe { File::from_raw_descriptor(ret) })
}
+
+ fn add_device_num(&mut self) {
+ self.device_num += 1;
+ }
+
+ fn reduce_device_num(&mut self) {
+ self.device_num -= 1;
+ }
+
+ fn device_num(&self) -> u32 {
+ self.device_num
+ }
}
impl AsRawDescriptor for VfioGroup {
@@ -318,6 +556,107 @@ impl AsRawDescriptor for VfioGroup {
}
}
+/// A helper trait for managing VFIO setup
+pub trait VfioCommonTrait: Send + Sync {
+ /// The single place to create a VFIO container for a PCI endpoint.
+ ///
+ /// The policy to determine whether an individual or a shared VFIO container
+ /// will be created for this device is governed by the physical PCI topology,
+ /// and the argument iommu_enabled.
+ ///
+ /// # Arguments
+ ///
+ /// * `sysfspath` - the path to the PCI device, e.g. /sys/bus/pci/devices/0000:02:00.0
+ /// * `iommu_enabled` - whether virtio IOMMU is enabled on this device
+ fn vfio_get_container<P: AsRef<Path>>(
+ iommu_dev: IommuDevType,
+ sysfspath: Option<P>,
+ ) -> Result<Arc<Mutex<VfioContainer>>>;
+}
+
+thread_local! {
+
+ // One VFIO container is shared by all VFIO devices that don't
+ // attach to the virtio IOMMU device
+ static NO_IOMMU_CONTAINER: RefCell<Option<Arc<Mutex<VfioContainer>>>> = RefCell::new(None);
+
+ // For IOMMU enabled devices, all VFIO groups that share the same IOVA space
+ // are managed by one VFIO container
+ static IOMMU_CONTAINERS: RefCell<Option<Vec<Arc<Mutex<VfioContainer>>>>> = RefCell::new(Some(Default::default()));
+
+ // One VFIO container is shared by all VFIO devices that
+ // attach to the CoIOMMU device
+ static COIOMMU_CONTAINER: RefCell<Option<Arc<Mutex<VfioContainer>>>> = RefCell::new(None);
+}
+
+pub struct VfioCommonSetup;
+
+impl VfioCommonTrait for VfioCommonSetup {
+ fn vfio_get_container<P: AsRef<Path>>(
+ iommu_dev: IommuDevType,
+ sysfspath: Option<P>,
+ ) -> Result<Arc<Mutex<VfioContainer>>> {
+ match iommu_dev {
+ IommuDevType::NoIommu => {
+ // One VFIO container is used for all IOMMU disabled groups
+ NO_IOMMU_CONTAINER.with(|v| {
+ if v.borrow().is_some() {
+ if let Some(ref container) = *v.borrow() {
+ Ok(container.clone())
+ } else {
+ Err(VfioError::BorrowVfioContainer)
+ }
+ } else {
+ let container = Arc::new(Mutex::new(VfioContainer::new()?));
+ *v.borrow_mut() = Some(container.clone());
+ Ok(container)
+ }
+ })
+ }
+ IommuDevType::VirtioIommu => {
+ let path = sysfspath.ok_or(VfioError::InvalidPath)?;
+ let group_id = VfioGroup::get_group_id(path)?;
+
+ // One VFIO container is used for all devices belong to one VFIO group
+ IOMMU_CONTAINERS.with(|v| {
+ if let Some(ref mut containers) = *v.borrow_mut() {
+ let container = containers
+ .iter()
+ .find(|container| container.lock().is_group_set(group_id));
+
+ match container {
+ None => {
+ let container = Arc::new(Mutex::new(VfioContainer::new()?));
+ containers.push(container.clone());
+ Ok(container)
+ }
+ Some(container) => Ok(container.clone()),
+ }
+ } else {
+ Err(VfioError::BorrowVfioContainer)
+ }
+ })
+ }
+ IommuDevType::CoIommu => {
+ // One VFIO container is used for devices attached to CoIommu
+ COIOMMU_CONTAINER.with(|v| {
+ if v.borrow().is_some() {
+ if let Some(ref container) = *v.borrow() {
+ Ok(container.clone())
+ } else {
+ Err(VfioError::BorrowVfioContainer)
+ }
+ } else {
+ let container = Arc::new(Mutex::new(VfioContainer::new()?));
+ *v.borrow_mut() = Some(container.clone());
+ Ok(container)
+ }
+ })
+ }
+ }
+ }
+}
+
/// Vfio Irq type used to enable/disable/mask/unmask vfio irq
pub enum VfioIrqType {
Intx,
@@ -325,7 +664,23 @@ pub enum VfioIrqType {
Msix,
}
-struct VfioRegion {
+/// Vfio Irq information used to assign and enable/disable/mask/unmask vfio irq
+pub struct VfioIrq {
+ pub flags: u32,
+ pub index: u32,
+}
+
+/// Address on VFIO memory region.
+#[derive(Debug, Default, Clone)]
+pub struct VfioRegionAddr {
+ /// region number.
+ pub index: u32,
+ /// offset in the region.
+ pub addr: u64,
+}
+
+#[derive(Debug)]
+pub struct VfioRegion {
// flags for this region: read/write/mmap
flags: u32,
size: u64,
@@ -343,62 +698,129 @@ pub struct VfioDevice {
name: String,
container: Arc<Mutex<VfioContainer>>,
group_descriptor: RawDescriptor,
+ group_id: u32,
// vec for vfio device's regions
regions: Vec<VfioRegion>,
+
+ iova_alloc: Option<Arc<Mutex<AddressAllocator>>>,
}
impl VfioDevice {
/// Create a new vfio device, then guest read/write on this device could be
/// transfered into kernel vfio.
/// sysfspath specify the vfio device path in sys file system.
- pub fn new(
- sysfspath: &Path,
+ pub fn new_passthrough<P: AsRef<Path>>(
+ sysfspath: &P,
vm: &impl Vm,
- guest_mem: &GuestMemory,
container: Arc<Mutex<VfioContainer>>,
- ) -> Result<Self, VfioError> {
- let mut uuid_path = PathBuf::new();
- uuid_path.push(sysfspath);
- uuid_path.push("iommu_group");
- let group_path = uuid_path.read_link().map_err(|_| VfioError::InvalidPath)?;
- let group_osstr = group_path.file_name().ok_or(VfioError::InvalidPath)?;
- let group_str = group_osstr.to_str().ok_or(VfioError::InvalidPath)?;
- let group_id = group_str
- .parse::<u32>()
- .map_err(|_| VfioError::InvalidPath)?;
-
- let group = container.lock().get_group(group_id, vm, guest_mem)?;
- let name_osstr = sysfspath.file_name().ok_or(VfioError::InvalidPath)?;
+ iommu_enabled: bool,
+ ) -> Result<Self> {
+ let group_id = VfioGroup::get_group_id(&sysfspath)?;
+
+ let group = container
+ .lock()
+ .get_group_with_vm(group_id, vm, iommu_enabled)?;
+ let name_osstr = sysfspath
+ .as_ref()
+ .file_name()
+ .ok_or(VfioError::InvalidPath)?;
let name_str = name_osstr.to_str().ok_or(VfioError::InvalidPath)?;
let name = String::from(name_str);
- let dev = group.get_device(&name)?;
+ let dev = group.lock().get_device(&name)?;
let regions = Self::get_regions(&dev)?;
+ group.lock().add_device_num();
+ let group_descriptor = group.lock().as_raw_descriptor();
Ok(VfioDevice {
dev,
name,
container,
- group_descriptor: group.as_raw_descriptor(),
+ group_descriptor,
+ group_id,
regions,
+ iova_alloc: None,
})
}
+ pub fn new<P: AsRef<Path>>(
+ sysfspath: &P,
+ container: Arc<Mutex<VfioContainer>>,
+ ) -> Result<Self> {
+ let group_id = VfioGroup::get_group_id(&sysfspath)?;
+ let group = container.lock().get_group(group_id)?;
+ let name_osstr = sysfspath
+ .as_ref()
+ .file_name()
+ .ok_or(VfioError::InvalidPath)?;
+ let name_str = name_osstr.to_str().ok_or(VfioError::InvalidPath)?;
+ let name = String::from(name_str);
+
+ let dev = match group.lock().get_device(&name) {
+ Ok(dev) => dev,
+ Err(e) => {
+ container.lock().remove_group(group_id, false);
+ return Err(e);
+ }
+ };
+ let regions = match Self::get_regions(&dev) {
+ Ok(regions) => regions,
+ Err(e) => {
+ container.lock().remove_group(group_id, false);
+ return Err(e);
+ }
+ };
+ group.lock().add_device_num();
+ let group_descriptor = group.lock().as_raw_descriptor();
+
+ let iova_ranges = container
+ .lock()
+ .vfio_iommu_iova_get_iova_ranges()?
+ .into_iter()
+ .map(|r| std::ops::RangeInclusive::new(r.start, r.end));
+ let iova_alloc = AddressAllocator::new_from_list(iova_ranges, None, None)
+ .map_err(VfioError::Resources)?;
+
+ Ok(VfioDevice {
+ dev,
+ name,
+ container,
+ group_descriptor,
+ group_id,
+ regions,
+ iova_alloc: Some(Arc::new(Mutex::new(iova_alloc))),
+ })
+ }
+
+ /// Returns the file for this device.
+ pub fn dev_file(&self) -> &File {
+ &self.dev
+ }
+
/// Returns PCI device name, formatted as BUS:DEVICE.FUNCTION string.
pub fn device_name(&self) -> &String {
&self.name
}
/// Enable vfio device's irq and associate Irqfd Event with device.
- /// When MSIx is enabled, multi vectors will be supported, so descriptors is vector and the vector
- /// length is the num of MSIx vectors
- pub fn irq_enable(&self, descriptors: Vec<&Event>, index: u32) -> Result<(), VfioError> {
+ /// When MSIx is enabled, multi vectors will be supported, and vectors starting from subindex to subindex +
+ /// descriptors length will be assigned with irqfd in the descriptors array.
+ /// when index = VFIO_PCI_REQ_IRQ_INDEX, kernel vfio will trigger this event when physical device
+ /// is removed.
+ /// If descriptor is None, -1 is assigned to the irq. A value of -1 is used to either de-assign
+ /// interrupts if already assigned or skip un-assigned interrupts.
+ pub fn irq_enable(
+ &self,
+ descriptors: &[Option<&Event>],
+ index: u32,
+ subindex: u32,
+ ) -> Result<()> {
let count = descriptors.len();
let u32_size = mem::size_of::<u32>();
let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(count);
irq_set[0].argsz = (mem::size_of::<vfio_irq_set>() + count * u32_size) as u32;
irq_set[0].flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
irq_set[0].index = index;
- irq_set[0].start = 0;
+ irq_set[0].start = subindex;
irq_set[0].count = count as u32;
// irq_set.data could be none, bool or descriptor according to flags, so irq_set.data
@@ -408,7 +830,10 @@ impl VfioDevice {
let mut data = unsafe { irq_set[0].data.as_mut_slice(count * u32_size) };
for descriptor in descriptors.iter().take(count) {
let (left, right) = data.split_at_mut(u32_size);
- left.copy_from_slice(&descriptor.as_raw_descriptor().to_ne_bytes()[..]);
+ match descriptor {
+ Some(fd) => left.copy_from_slice(&fd.as_raw_descriptor().to_ne_bytes()[..]),
+ None => left.copy_from_slice(&(-1i32).to_ne_bytes()[..]),
+ }
data = right;
}
@@ -430,7 +855,7 @@ impl VfioDevice {
/// This function enable resample irqfd and let vfio kernel could get EOI notification.
///
/// descriptor: should be resample IrqFd.
- pub fn resample_virq_enable(&self, descriptor: &Event, index: u32) -> Result<(), VfioError> {
+ pub fn resample_virq_enable(&self, descriptor: &Event, index: u32) -> Result<()> {
let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(1);
irq_set[0].argsz = (mem::size_of::<vfio_irq_set>() + mem::size_of::<u32>()) as u32;
irq_set[0].flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_UNMASK;
@@ -457,7 +882,7 @@ impl VfioDevice {
}
/// disable vfio device's irq and disconnect Irqfd Event with device
- pub fn irq_disable(&self, index: u32) -> Result<(), VfioError> {
+ pub fn irq_disable(&self, index: u32) -> Result<()> {
let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(0);
irq_set[0].argsz = mem::size_of::<vfio_irq_set>() as u32;
irq_set[0].flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_TRIGGER;
@@ -475,7 +900,7 @@ impl VfioDevice {
}
/// Unmask vfio device irq
- pub fn irq_unmask(&self, index: u32) -> Result<(), VfioError> {
+ pub fn irq_unmask(&self, index: u32) -> Result<()> {
let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(0);
irq_set[0].argsz = mem::size_of::<vfio_irq_set>() as u32;
irq_set[0].flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_UNMASK;
@@ -493,7 +918,7 @@ impl VfioDevice {
}
/// Mask vfio device irq
- pub fn irq_mask(&self, index: u32) -> Result<(), VfioError> {
+ pub fn irq_mask(&self, index: u32) -> Result<()> {
let mut irq_set = vec_with_array_field::<vfio_irq_set, u32>(0);
irq_set[0].argsz = mem::size_of::<vfio_irq_set>() as u32;
irq_set[0].flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_MASK;
@@ -510,27 +935,99 @@ impl VfioDevice {
}
}
+ fn validate_dev_info(dev_info: &mut vfio_device_info) -> Result<()> {
+ if (dev_info.flags & VFIO_DEVICE_FLAGS_PCI) != 0 {
+ if dev_info.num_regions < VFIO_PCI_CONFIG_REGION_INDEX + 1
+ || dev_info.num_irqs < VFIO_PCI_MSIX_IRQ_INDEX + 1
+ {
+ return Err(VfioError::VfioDeviceGetInfo(get_error()));
+ }
+ return Ok(());
+ } else if (dev_info.flags & VFIO_DEVICE_FLAGS_PLATFORM) != 0 {
+ return Ok(());
+ }
+
+ Err(VfioError::VfioDeviceGetInfo(get_error()))
+ }
+
+ /// Get and validate VFIO device information.
+ pub fn check_device_info(&self) -> Result<vfio_device_info> {
+ let mut dev_info = vfio_device_info {
+ argsz: mem::size_of::<vfio_device_info>() as u32,
+ flags: 0,
+ num_regions: 0,
+ num_irqs: 0,
+ ..Default::default()
+ };
+
+ // Safe as we are the owner of device_file and dev_info which are valid value,
+ // and we verify the return value.
+ let ret = unsafe {
+ ioctl_with_mut_ref(self.device_file(), VFIO_DEVICE_GET_INFO(), &mut dev_info)
+ };
+ if ret < 0 {
+ return Err(VfioError::VfioDeviceGetInfo(get_error()));
+ }
+
+ Self::validate_dev_info(&mut dev_info)?;
+ Ok(dev_info)
+ }
+
+ /// Query interrupt information
+ /// return: Vector of interrupts information, each of which contains flags and index
+ pub fn get_irqs(&self) -> Result<Vec<VfioIrq>> {
+ let dev_info = self.check_device_info()?;
+ let mut irqs: Vec<VfioIrq> = Vec::new();
+
+ for i in 0..dev_info.num_irqs {
+ let argsz = mem::size_of::<vfio_irq_info>() as u32;
+ let mut irq_info = vfio_irq_info {
+ argsz,
+ flags: 0,
+ index: i,
+ count: 0,
+ };
+ // Safe as we are the owner of dev and dev_info which are valid value,
+ // and we verify the return value.
+ let ret = unsafe {
+ ioctl_with_mut_ref(
+ self.device_file(),
+ VFIO_DEVICE_GET_IRQ_INFO(),
+ &mut irq_info,
+ )
+ };
+ if ret < 0 || irq_info.count != 1 {
+ return Err(VfioError::VfioDeviceGetInfo(get_error()));
+ }
+
+ let irq = VfioIrq {
+ flags: irq_info.flags,
+ index: irq_info.index,
+ };
+ irqs.push(irq);
+ }
+ Ok(irqs)
+ }
+
#[allow(clippy::cast_ptr_alignment)]
- fn get_regions(dev: &File) -> Result<Vec<VfioRegion>, VfioError> {
+ fn get_regions(dev: &File) -> Result<Vec<VfioRegion>> {
let mut regions: Vec<VfioRegion> = Vec::new();
let mut dev_info = vfio_device_info {
argsz: mem::size_of::<vfio_device_info>() as u32,
flags: 0,
num_regions: 0,
num_irqs: 0,
+ ..Default::default()
};
// Safe as we are the owner of dev and dev_info which are valid value,
// and we verify the return value.
let mut ret = unsafe { ioctl_with_mut_ref(dev, VFIO_DEVICE_GET_INFO(), &mut dev_info) };
- if ret < 0
- || (dev_info.flags & VFIO_DEVICE_FLAGS_PCI) == 0
- || dev_info.num_regions < VFIO_PCI_CONFIG_REGION_INDEX + 1
- || dev_info.num_irqs < VFIO_PCI_MSIX_IRQ_INDEX + 1
- {
+ if ret < 0 {
return Err(VfioError::VfioDeviceGetInfo(get_error()));
}
- for i in VFIO_PCI_BAR0_REGION_INDEX..dev_info.num_regions {
+ Self::validate_dev_info(&mut dev_info)?;
+ for i in 0..dev_info.num_regions {
let argsz = mem::size_of::<vfio_region_info>() as u32;
let mut reg_info = vfio_region_info {
argsz,
@@ -590,7 +1087,7 @@ impl VfioDevice {
let info_ptr = region_with_cap.as_ptr() as *mut u8;
let mut offset = region_with_cap[0].region_info.cap_offset;
while offset != 0 {
- if offset + cap_header_sz >= region_info_sz {
+ if offset + cap_header_sz > region_info_sz {
break;
}
// Safe, as cap_header struct is in this function allocated region_with_cap
@@ -599,7 +1096,7 @@ impl VfioDevice {
let cap_header =
unsafe { &*(cap_ptr as *mut u8 as *const vfio_info_cap_header) };
if cap_header.id as u32 == VFIO_REGION_INFO_CAP_SPARSE_MMAP {
- if offset + mmap_cap_sz >= region_info_sz {
+ if offset + mmap_cap_sz > region_info_sz {
break;
}
// cap_ptr is vfio_region_info_cap_sparse_mmap here
@@ -631,6 +1128,11 @@ impl VfioDevice {
unsafe { &*(cap_ptr as *mut u8 as *const vfio_region_info_cap_type) };
cap_info = Some((cap_type_info.type_, cap_type_info.subtype));
+ } else if cap_header.id as u32 == VFIO_REGION_INFO_CAP_MSIX_MAPPABLE {
+ mmaps.push(vfio_region_sparse_mmap_area {
+ offset: region_with_cap[0].region_info.offset,
+ size: region_with_cap[0].region_info.size,
+ });
}
offset = cap_header.next;
@@ -683,6 +1185,24 @@ impl VfioDevice {
}
}
+ /// get a region's size
+ /// return: Region size from the start of vfio device descriptor
+ pub fn get_region_size(&self, index: u32) -> u64 {
+ match self.regions.get(index as usize) {
+ Some(v) => v.size,
+ None => {
+ warn!("get_region_size with invalid index: {}", index);
+ 0
+ }
+ }
+ }
+
+ /// get a number of regions
+ /// return: Number of regions of vfio device descriptor
+ pub fn get_region_count(&self) -> u32 {
+ self.regions.len() as u32
+ }
+
/// get a region's mmap info vector
pub fn get_region_mmap(&self, index: u32) -> Vec<vfio_region_sparse_mmap_area> {
match self.regions.get(index as usize) {
@@ -714,35 +1234,54 @@ impl VfioDevice {
None
}
+ /// Returns file offset corresponding to the given `VfioRegionAddr`.
+ /// The offset can be used when reading/writing the VFIO device's FD directly.
+ pub fn get_offset_for_addr(&self, addr: &VfioRegionAddr) -> Result<u64> {
+ let region = self
+ .regions
+ .get(addr.index as usize)
+ .ok_or(VfioError::InvalidIndex(addr.index))?;
+ Ok(region.offset + addr.addr)
+ }
+
/// Read region's data from VFIO device into buf
/// index: region num
/// buf: data destination and buf length is read size
/// addr: offset in the region
pub fn region_read(&self, index: u32, buf: &mut [u8], addr: u64) {
- let stub: &VfioRegion;
- match self.regions.get(index as usize) {
- Some(v) => stub = v,
- None => {
- warn!("region read with invalid index: {}", index);
- return;
- }
- }
+ let stub: &VfioRegion = self
+ .regions
+ .get(index as usize)
+ .unwrap_or_else(|| panic!("tried to read VFIO with an invalid index: {}", index));
let size = buf.len() as u64;
if size > stub.size || addr + size > stub.size {
- warn!(
- "region read with invalid parameter, index: {}, add: {:x}, size: {:x}",
+ panic!(
+ "tried to read VFIO region with invalid arguments: index={}, addr=0x{:x}, size=0x{:x}",
index, addr, size
);
- return;
}
- if let Err(e) = self.dev.read_exact_at(buf, stub.offset + addr) {
- warn!(
- "Failed to read region in index: {}, addr: {:x}, error: {}",
- index, addr, e
- );
- }
+ self.dev
+ .read_exact_at(buf, stub.offset + addr)
+ .unwrap_or_else(|e| {
+ panic!(
+ "failed to read region: index={}, addr=0x{:x}, error={}",
+ index, addr, e
+ )
+ });
+ }
+
+ /// Reads a value from the specified `VfioRegionAddr.addr` + `offset`.
+ pub fn region_read_from_addr<T: DataInit>(&self, addr: &VfioRegionAddr, offset: u64) -> T {
+ let mut val = mem::MaybeUninit::zeroed();
+ // Safe because we have zero-initialized `size_of::<T>()` bytes.
+ let buf =
+ unsafe { slice::from_raw_parts_mut(val.as_mut_ptr() as *mut u8, mem::size_of::<T>()) };
+ self.region_read(addr.index, buf, addr.addr + offset);
+ // Safe because any bit pattern is valid for a type that implements
+ // DataInit.
+ unsafe { val.assume_init() }
}
/// write the data from buf into a vfio device region
@@ -750,42 +1289,44 @@ impl VfioDevice {
/// buf: data src and buf length is write size
/// addr: offset in the region
pub fn region_write(&self, index: u32, buf: &[u8], addr: u64) {
- let stub: &VfioRegion;
- match self.regions.get(index as usize) {
- Some(v) => stub = v,
- None => {
- warn!("region write with invalid index: {}", index);
- return;
- }
- }
+ let stub: &VfioRegion = self
+ .regions
+ .get(index as usize)
+ .unwrap_or_else(|| panic!("tried to write VFIO with an invalid index: {}", index));
let size = buf.len() as u64;
if size > stub.size
|| addr + size > stub.size
|| (stub.flags & VFIO_REGION_INFO_FLAG_WRITE) == 0
{
- warn!(
- "region write with invalid parameter,indxe: {}, add: {:x}, size: {:x}",
+ panic!(
+ "tried to write VFIO region with invalid arguments: index={}, addr=0x{:x}, size=0x{:x}",
index, addr, size
);
- return;
}
- if let Err(e) = self.dev.write_all_at(buf, stub.offset + addr) {
- warn!(
- "Failed to write region in index: {}, addr: {:x}, error: {}",
- index, addr, e
- );
- }
+ self.dev
+ .write_all_at(buf, stub.offset + addr)
+ .unwrap_or_else(|e| {
+ panic!(
+ "failed to write region: index={}, addr=0x{:x}, error={}",
+ index, addr, e
+ )
+ });
+ }
+
+ /// Writes data into the specified `VfioRegionAddr.addr` + `offset`.
+ pub fn region_write_to_addr<T: DataInit>(&self, val: &T, addr: &VfioRegionAddr, offset: u64) {
+ self.region_write(addr.index, val.as_slice(), addr.addr + offset);
}
/// get vfio device's descriptors which are passed into minijail process
pub fn keep_rds(&self) -> Vec<RawDescriptor> {
- let mut rds = Vec::new();
- rds.push(self.dev.as_raw_descriptor());
- rds.push(self.group_descriptor);
- rds.push(self.container.lock().as_raw_descriptor());
- rds
+ vec![
+ self.dev.as_raw_descriptor(),
+ self.group_descriptor,
+ self.container.lock().as_raw_descriptor(),
+ ]
}
/// Add (iova, user_addr) map into vfio container iommu table
@@ -794,17 +1335,90 @@ impl VfioDevice {
iova: u64,
size: u64,
user_addr: u64,
- ) -> Result<(), VfioError> {
- self.container.lock().vfio_dma_map(iova, size, user_addr)
+ write_en: bool,
+ ) -> Result<()> {
+ self.container
+ .lock()
+ .vfio_dma_map(iova, size, user_addr, write_en)
}
/// Remove (iova, user_addr) map from vfio container iommu table
- pub fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<(), VfioError> {
+ pub fn vfio_dma_unmap(&self, iova: u64, size: u64) -> Result<()> {
self.container.lock().vfio_dma_unmap(iova, size)
}
+ pub fn vfio_get_iommu_page_size_mask(&self) -> Result<u64> {
+ self.container.lock().vfio_get_iommu_page_size_mask()
+ }
+
+ pub fn alloc_iova(&self, size: u64, align_size: u64, alloc: Alloc) -> Result<u64> {
+ match &self.iova_alloc {
+ None => Err(VfioError::NoRescAlloc),
+ Some(iova_alloc) => iova_alloc
+ .lock()
+ .allocate_with_align(size, alloc, "alloc_iova".to_owned(), align_size)
+ .map_err(VfioError::Resources),
+ }
+ }
+
/// Gets the vfio device backing `File`.
pub fn device_file(&self) -> &File {
&self.dev
}
+
+ /// close vfio device
+ pub fn close(&self) {
+ self.container.lock().remove_group(self.group_id, true);
+ }
+}
+
+pub struct VfioPciConfig {
+ device: Arc<VfioDevice>,
+}
+
+impl VfioPciConfig {
+ pub fn new(device: Arc<VfioDevice>) -> Self {
+ VfioPciConfig { device }
+ }
+
+ pub fn read_config<T: DataInit>(&self, offset: u32) -> T {
+ let mut buf = vec![0u8; std::mem::size_of::<T>()];
+ self.device
+ .region_read(VFIO_PCI_CONFIG_REGION_INDEX, &mut buf, offset.into());
+ T::from_slice(&buf)
+ .copied()
+ .expect("failed to convert config data from slice")
+ }
+
+ pub fn write_config<T: DataInit>(&self, config: T, offset: u32) {
+ self.device.region_write(
+ VFIO_PCI_CONFIG_REGION_INDEX,
+ config.as_slice(),
+ offset.into(),
+ );
+ }
+
+ /// Set the VFIO device this config refers to as the bus master.
+ pub fn set_bus_master(&self) {
+ /// Constant definitions from `linux/pci_regs.h`.
+ const PCI_COMMAND: u32 = 0x4;
+ /// Enable bus mastering
+ const PCI_COMMAND_MASTER: u16 = 0x4;
+
+ let mut cmd: u16 = self.read_config(PCI_COMMAND);
+
+ if cmd & PCI_COMMAND_MASTER != 0 {
+ return;
+ }
+
+ cmd |= PCI_COMMAND_MASTER;
+
+ self.write_config(cmd, PCI_COMMAND);
+ }
+}
+
+impl AsRawDescriptor for VfioDevice {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.dev.as_raw_descriptor()
+ }
}
diff --git a/devices/src/virtio/async_utils.rs b/devices/src/virtio/async_utils.rs
new file mode 100644
index 000000000..9f2f2f62a
--- /dev/null
+++ b/devices/src/virtio/async_utils.rs
@@ -0,0 +1,51 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Virtio device async helper functions.
+
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use anyhow::{Context, Result};
+use base::Event;
+use cros_async::{EventAsync, Executor};
+
+use super::{Interrupt, SignalableInterrupt};
+
+/// Async task that waits for a signal from `event`. Once this event is readable, exit. Exiting
+/// this future will cause the main loop to break and the worker thread to exit.
+pub async fn await_and_exit(ex: &Executor, event: Event) -> Result<()> {
+ let event_async = EventAsync::new(event.0, ex).context("failed to create EventAsync")?;
+ let _ = event_async.next_val().await;
+ Ok(())
+}
+
+/// Async task that resamples the status of the interrupt when the guest sends a request by
+/// signalling the resample event associated with the interrupt.
+pub async fn handle_irq_resample(ex: &Executor, interrupt: Rc<RefCell<Interrupt>>) -> Result<()> {
+ // Clone resample_evt if interrupt has one.
+ // This is a separate block so that we do not hold a RefCell borrow across await.
+ let resample_evt = if let Some(resample_evt) = interrupt.borrow().get_resample_evt() {
+ let resample_evt = resample_evt
+ .try_clone()
+ .context("resample_evt.try_clone() failed")?;
+ Some(EventAsync::new(resample_evt.0, ex).context("failed to create async resample event")?)
+ } else {
+ None
+ };
+
+ if let Some(resample_evt) = resample_evt {
+ loop {
+ let _ = resample_evt
+ .next_val()
+ .await
+ .context("failed to read resample event")?;
+ interrupt.borrow().do_interrupt_resample();
+ }
+ } else {
+ // No resample event; park the future.
+ let () = futures::future::pending().await;
+ }
+ Ok(())
+}
diff --git a/devices/src/virtio/balloon.rs b/devices/src/virtio/balloon.rs
index a76765d47..82645b45c 100644
--- a/devices/src/virtio/balloon.rs
+++ b/devices/src/virtio/balloon.rs
@@ -4,7 +4,6 @@
use std::cell::RefCell;
use std::rc::Rc;
-use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
@@ -12,35 +11,44 @@ use futures::{channel::mpsc, pin_mut, StreamExt};
use remain::sorted;
use thiserror::Error as ThisError;
-use base::{self, error, info, warn, AsRawDescriptor, AsyncTube, Event, RawDescriptor, Tube};
-use cros_async::{select6, EventAsync, Executor};
+use balloon_control::{BalloonStats, BalloonTubeCommand, BalloonTubeResult};
+use base::{self, error, warn, AsRawDescriptor, Event, RawDescriptor, Tube};
+use cros_async::{
+ block_on, select6, select7, sync::Mutex as AsyncMutex, AsyncTube, EventAsync, Executor,
+};
use data_model::{DataInit, Le16, Le32, Le64};
-use vm_control::{BalloonControlCommand, BalloonControlResult, BalloonStats};
use vm_memory::{GuestAddress, GuestMemory};
use super::{
- copy_config, descriptor_utils, DescriptorChain, Interrupt, Queue, Reader, SignalableInterrupt,
- VirtioDevice, TYPE_BALLOON,
+ async_utils, copy_config, descriptor_utils, DescriptorChain, Interrupt, Queue, Reader,
+ SignalableInterrupt, VirtioDevice, TYPE_BALLOON,
};
+use crate::{UnpinRequest, UnpinResponse};
#[sorted]
#[derive(ThisError, Debug)]
pub enum BalloonError {
+ /// Failed an async await
+ #[error("failed async await: {0}")]
+ AsyncAwait(cros_async::AsyncError),
/// Failed to create async message receiver.
#[error("failed to create async message receiver: {0}")]
CreatingMessageReceiver(base::TubeError),
/// Failed to receive command message.
#[error("failed to receive command message: {0}")]
ReceivingCommand(base::TubeError),
+ /// Failed to send command response.
+ #[error("failed to send command response: {0}")]
+ SendResponse(base::TubeError),
/// Failed to write config event.
#[error("failed to write config event: {0}")]
WritingConfigEvent(base::Error),
}
pub type Result<T> = std::result::Result<T, BalloonError>;
-// Balloon has three virt IO queues: Inflate, Deflate, and Stats.
+// Balloon implements four virt IO queues: Inflate, Deflate, Stats, Event.
const QUEUE_SIZE: u16 = 128;
-const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE, QUEUE_SIZE];
+const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE, QUEUE_SIZE, QUEUE_SIZE];
const VIRTIO_BALLOON_PFN_SHIFT: u32 = 12;
const VIRTIO_BALLOON_PF_SIZE: u64 = 1 << VIRTIO_BALLOON_PFN_SHIFT;
@@ -50,6 +58,11 @@ const VIRTIO_BALLOON_F_MUST_TELL_HOST: u32 = 0; // Tell before reclaiming pages
const VIRTIO_BALLOON_F_STATS_VQ: u32 = 1; // Stats reporting enabled
const VIRTIO_BALLOON_F_DEFLATE_ON_OOM: u32 = 2; // Deflate balloon on OOM
+// These feature bits are part of the proposal:
+// https://lists.oasis-open.org/archives/virtio-comment/202201/msg00139.html
+const VIRTIO_BALLOON_F_RESPONSIVE_DEVICE: u32 = 6; // Device actively watching guest memory
+const VIRTIO_BALLOON_F_EVENTS_VQ: u32 = 7; // Event vq is enabled
+
// virtio_balloon_config is the balloon device configuration space defined by the virtio spec.
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
@@ -61,11 +74,15 @@ struct virtio_balloon_config {
// Safe because it only has data and has no implicit padding.
unsafe impl DataInit for virtio_balloon_config {}
-// BalloonConfig is modified by the worker and read from the device thread.
+// BalloonState is shared by the worker and device thread.
#[derive(Default)]
-struct BalloonConfig {
- num_pages: AtomicUsize,
- actual_pages: AtomicUsize,
+struct BalloonState {
+ num_pages: u32,
+ actual_pages: u32,
+ // Flag indicating that the balloon is in the process of a failable update. This
+ // is set by an Adjust command that has allow_failure set, and is cleared when the
+ // Adjusted success/failure response is sent.
+ failable_update: bool,
}
// The constants defining stats types in virtio_baloon_stat
@@ -79,6 +96,8 @@ const VIRTIO_BALLOON_S_AVAIL: u16 = 6;
const VIRTIO_BALLOON_S_CACHES: u16 = 7;
const VIRTIO_BALLOON_S_HTLB_PGALLOC: u16 = 8;
const VIRTIO_BALLOON_S_HTLB_PGFAIL: u16 = 9;
+const VIRTIO_BALLOON_S_NONSTANDARD_SHMEM: u16 = 65534;
+const VIRTIO_BALLOON_S_NONSTANDARD_UNEVICTABLE: u16 = 65535;
// BalloonStat is used to deserialize stats from the stats_queue.
#[derive(Copy, Clone)]
@@ -104,13 +123,45 @@ impl BalloonStat {
VIRTIO_BALLOON_S_CACHES => stats.disk_caches = val,
VIRTIO_BALLOON_S_HTLB_PGALLOC => stats.hugetlb_allocations = val,
VIRTIO_BALLOON_S_HTLB_PGFAIL => stats.hugetlb_failures = val,
+ VIRTIO_BALLOON_S_NONSTANDARD_SHMEM => stats.shared_memory = val,
+ VIRTIO_BALLOON_S_NONSTANDARD_UNEVICTABLE => stats.unevictable_memory = val,
_ => (),
}
}
}
+const VIRTIO_BALLOON_EVENT_PRESSURE: u32 = 1;
+const VIRTIO_BALLOON_EVENT_PUFF_FAILURE: u32 = 2;
+
+#[repr(C)]
+#[derive(Copy, Clone, Default)]
+struct virtio_balloon_event_header {
+ evt_type: Le32,
+}
+
+fn send_adjusted_response(tube: &Tube, num_pages: u32) -> std::result::Result<(), base::TubeError> {
+ let num_bytes = (num_pages as u64) << VIRTIO_BALLOON_PFN_SHIFT;
+ let result = BalloonTubeResult::Adjusted { num_bytes };
+ tube.send(&result)
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for virtio_balloon_event_header {}
+
+fn invoke_desc_handler<F>(ranges: Vec<(u64, u64)>, desc_handler: &mut F)
+where
+ F: FnMut(GuestAddress, u64),
+{
+ for range in ranges {
+ desc_handler(GuestAddress(range.0), range.1);
+ }
+}
+
// Processes one message's list of addresses.
+// Unpin requests for each inflate range will be sent via `inflate_tube`
+// if provided, and then `desc_handler` will be called for each inflate range.
fn handle_address_chain<F>(
+ inflate_tube: &Option<Tube>,
avail_desc: DescriptorChain,
mem: &GuestMemory,
desc_handler: &mut F,
@@ -125,6 +176,7 @@ where
let mut range_start = 0;
let mut range_size = 0;
let mut reader = Reader::new(mem.clone(), avail_desc)?;
+ let mut inflate_ranges: Vec<(u64, u64)> = Vec::new();
for res in reader.iter::<Le32>() {
let pfn = match res {
Ok(pfn) => pfn,
@@ -143,15 +195,44 @@ where
// Discontinuity, so flush the previous range. Note range_size
// will be 0 on the first iteration, so skip that.
if range_size != 0 {
- desc_handler(GuestAddress(range_start), range_size);
+ inflate_ranges.push((range_start, range_size));
}
range_start = guest_address;
range_size = VIRTIO_BALLOON_PF_SIZE;
}
}
if range_size != 0 {
- desc_handler(GuestAddress(range_start), range_size);
+ inflate_ranges.push((range_start, range_size));
}
+
+ if let Some(tube) = inflate_tube {
+ let unpin_ranges = inflate_ranges
+ .iter()
+ .map(|v| {
+ (
+ v.0 >> VIRTIO_BALLOON_PFN_SHIFT,
+ v.1 / VIRTIO_BALLOON_PF_SIZE,
+ )
+ })
+ .collect();
+ let req = UnpinRequest {
+ ranges: unpin_ranges,
+ };
+ if let Err(e) = tube.send(&req) {
+ error!("failed to send unpin request: {}", e);
+ } else {
+ match tube.recv() {
+ Ok(resp) => match resp {
+ UnpinResponse::Success => invoke_desc_handler(inflate_ranges, desc_handler),
+ UnpinResponse::Failed => error!("failed to handle unpin request"),
+ },
+ Err(e) => error!("failed to handle get unpin response: {}", e),
+ }
+ }
+ } else {
+ invoke_desc_handler(inflate_ranges, desc_handler);
+ }
+
Ok(())
}
@@ -160,6 +241,7 @@ async fn handle_queue<F>(
mem: &GuestMemory,
mut queue: Queue,
mut queue_event: EventAsync,
+ inflate_tube: &Option<Tube>,
interrupt: Rc<RefCell<Interrupt>>,
mut desc_handler: F,
) where
@@ -174,14 +256,28 @@ async fn handle_queue<F>(
Ok(d) => d,
};
let index = avail_desc.index;
- if let Err(e) = handle_address_chain(avail_desc, mem, &mut desc_handler) {
+ if let Err(e) = handle_address_chain(inflate_tube, avail_desc, mem, &mut desc_handler) {
error!("balloon: failed to process inflate addresses: {}", e);
}
queue.add_used(mem, index, 0);
- interrupt.borrow_mut().signal_used_queue(queue.vector);
+ queue.trigger_interrupt(mem, &*interrupt.borrow());
}
}
+fn parse_balloon_stats(reader: &mut Reader) -> BalloonStats {
+ let mut stats: BalloonStats = Default::default();
+ for res in reader.iter::<BalloonStat>() {
+ match res {
+ Ok(stat) => stat.update_stats(&mut stats),
+ Err(e) => {
+ error!("error while reading stats: {}", e);
+ break;
+ }
+ };
+ }
+ stats
+}
+
// Async task that handles the stats queue. Note that the cadence of this is driven by requests for
// balloon stats from the control pipe.
// The guests queues an initial buffer on boot, which is read and then this future will block until
@@ -190,12 +286,34 @@ async fn handle_stats_queue(
mem: &GuestMemory,
mut queue: Queue,
mut queue_event: EventAsync,
- mut stats_rx: mpsc::Receiver<()>,
+ mut stats_rx: mpsc::Receiver<u64>,
command_tube: &Tube,
- config: Arc<BalloonConfig>,
+ state: Arc<AsyncMutex<BalloonState>>,
interrupt: Rc<RefCell<Interrupt>>,
) {
+ // Consume the first stats buffer sent from the guest at startup. It was not
+ // requested by anyone, and the stats are stale.
+ let mut index = match queue.next_async(mem, &mut queue_event).await {
+ Err(e) => {
+ error!("Failed to read descriptor {}", e);
+ return;
+ }
+ Ok(d) => d.index,
+ };
loop {
+ // Wait for a request to read the stats.
+ let id = match stats_rx.next().await {
+ Some(id) => id,
+ None => {
+ error!("stats signal tube was closed");
+ break;
+ }
+ };
+
+ // Request a new stats_desc to the guest.
+ queue.add_used(mem, index, 0);
+ queue.trigger_interrupt(mem, &*interrupt.borrow());
+
let stats_desc = match queue.next_async(mem, &mut queue_event).await {
Err(e) => {
error!("Failed to read descriptor {}", e);
@@ -203,7 +321,7 @@ async fn handle_stats_queue(
}
Ok(d) => d,
};
- let index = stats_desc.index;
+ index = stats_desc.index;
let mut reader = match Reader::new(mem.clone(), stats_desc) {
Ok(r) => r,
Err(e) => {
@@ -211,34 +329,75 @@ async fn handle_stats_queue(
continue;
}
};
- let mut stats: BalloonStats = Default::default();
- for res in reader.iter::<BalloonStat>() {
- match res {
- Ok(stat) => stat.update_stats(&mut stats),
- Err(e) => {
- error!("error while reading stats: {}", e);
- break;
- }
- };
- }
- let actual_pages = config.actual_pages.load(Ordering::Relaxed) as u64;
- let result = BalloonControlResult::Stats {
+ let stats = parse_balloon_stats(&mut reader);
+
+ let actual_pages = state.lock().await.actual_pages as u64;
+ let result = BalloonTubeResult::Stats {
balloon_actual: actual_pages << VIRTIO_BALLOON_PFN_SHIFT,
stats,
+ id,
};
if let Err(e) = command_tube.send(&result) {
error!("failed to send stats result: {}", e);
}
+ }
+}
- // Wait for a request to read the stats again.
- if stats_rx.next().await.is_none() {
- error!("stats signal tube was closed");
- break;
- }
+async fn handle_event(
+ state: Arc<AsyncMutex<BalloonState>>,
+ interrupt: Rc<RefCell<Interrupt>>,
+ r: &mut Reader,
+ command_tube: &Tube,
+) -> Result<()> {
+ match r.read_obj::<virtio_balloon_event_header>() {
+ Ok(hdr) => match hdr.evt_type.to_native() {
+ VIRTIO_BALLOON_EVENT_PRESSURE => {
+ // TODO(b/213962590): See how this can be integrated this into memory rebalancing
+ }
+ VIRTIO_BALLOON_EVENT_PUFF_FAILURE => {
+ let mut state = state.lock().await;
+ if state.failable_update {
+ state.num_pages = state.actual_pages;
+ interrupt.borrow().signal_config_changed();
+
+ state.failable_update = false;
+ return send_adjusted_response(command_tube, state.actual_pages)
+ .map_err(BalloonError::SendResponse);
+ }
+ }
+ _ => {
+ warn!("Unknown event {}", hdr.evt_type.to_native());
+ }
+ },
+ Err(e) => error!("Failed to parse event header {:?}", e),
+ }
+ Ok(())
+}
- // Request a new stats_desc to the guest.
- queue.add_used(&mem, index, 0);
- interrupt.borrow_mut().signal_used_queue(queue.vector);
+// Async task that handles the events queue.
+async fn handle_events_queue(
+ mem: &GuestMemory,
+ mut queue: Queue,
+ mut queue_event: EventAsync,
+ state: Arc<AsyncMutex<BalloonState>>,
+ interrupt: Rc<RefCell<Interrupt>>,
+ command_tube: &Tube,
+) -> Result<()> {
+ loop {
+ let avail_desc = queue
+ .next_async(mem, &mut queue_event)
+ .await
+ .map_err(BalloonError::AsyncAwait)?;
+ let index = avail_desc.index;
+ match Reader::new(mem.clone(), avail_desc) {
+ Ok(mut r) => {
+ handle_event(state.clone(), interrupt.clone(), &mut r, command_tube).await?
+ }
+ Err(e) => error!("balloon: failed to CREATE Reader: {}", e),
+ };
+
+ queue.add_used(mem, index, 0);
+ queue.trigger_interrupt(mem, &*interrupt.borrow());
}
}
@@ -247,21 +406,33 @@ async fn handle_stats_queue(
async fn handle_command_tube(
command_tube: &AsyncTube,
interrupt: Rc<RefCell<Interrupt>>,
- config: Arc<BalloonConfig>,
- mut stats_tx: mpsc::Sender<()>,
+ state: Arc<AsyncMutex<BalloonState>>,
+ mut stats_tx: mpsc::Sender<u64>,
) -> Result<()> {
loop {
match command_tube.next().await {
Ok(command) => match command {
- BalloonControlCommand::Adjust { num_bytes } => {
- let num_pages = (num_bytes >> VIRTIO_BALLOON_PFN_SHIFT) as usize;
- info!("balloon config changed to consume {} pages", num_pages);
-
- config.num_pages.store(num_pages, Ordering::Relaxed);
- interrupt.borrow_mut().signal_config_changed();
+ BalloonTubeCommand::Adjust {
+ num_bytes,
+ allow_failure,
+ } => {
+ let num_pages = (num_bytes >> VIRTIO_BALLOON_PFN_SHIFT) as u32;
+ let mut state = state.lock().await;
+
+ state.num_pages = num_pages;
+ interrupt.borrow().signal_config_changed();
+
+ if allow_failure {
+ if num_pages == state.actual_pages {
+ send_adjusted_response(command_tube, num_pages)
+ .map_err(BalloonError::SendResponse)?;
+ } else {
+ state.failable_update = true;
+ }
+ }
}
- BalloonControlCommand::Stats => {
- if let Err(e) = stats_tx.try_send(()) {
+ BalloonTubeCommand::Stats { id } => {
+ if let Err(e) = stats_tx.try_send(id) {
error!("failed to signal the stat handler: {}", e);
}
}
@@ -273,49 +444,23 @@ async fn handle_command_tube(
}
}
-// Async task that resamples the status of the interrupt when the guest sends a request by
-// signalling the resample event associated with the interrupt.
-async fn handle_irq_resample(ex: &Executor, interrupt: Rc<RefCell<Interrupt>>) {
- let resample_evt = if let Some(resample_evt) = interrupt.borrow_mut().get_resample_evt() {
- let resample_evt = resample_evt.try_clone().unwrap();
- let resample_evt = EventAsync::new(resample_evt.0, ex).unwrap();
- Some(resample_evt)
- } else {
- None
- };
- if let Some(resample_evt) = resample_evt {
- while resample_evt.next_val().await.is_ok() {
- interrupt.borrow_mut().do_interrupt_resample();
- }
- } else {
- // no resample event, park the future.
- let () = futures::future::pending().await;
- }
-}
-
-// Async task that waits for a signal from the kill event given to the device at startup. Once this event is
-// readable, exit. Exiting this future will cause the main loop to break and the worker thread to
-// exit.
-async fn wait_kill(kill_evt: EventAsync) {
- let _ = kill_evt.next_val().await;
-}
-
// The main worker thread. Initialized the asynchronous worker tasks and passes them to the executor
// to be processed.
fn run_worker(
mut queue_evts: Vec<Event>,
mut queues: Vec<Queue>,
command_tube: Tube,
+ inflate_tube: Option<Tube>,
interrupt: Interrupt,
kill_evt: Event,
mem: GuestMemory,
- config: Arc<BalloonConfig>,
-) -> Tube {
+ state: Arc<AsyncMutex<BalloonState>>,
+) -> Option<Tube> {
// Wrap the interrupt in a `RefCell` so it can be shared between async functions.
let interrupt = Rc::new(RefCell::new(interrupt));
let ex = Executor::new().unwrap();
- let command_tube = command_tube.into_async_tube(&ex).unwrap();
+ let command_tube = AsyncTube::new(&ex, command_tube).unwrap();
// We need a block to release all references to command_tube at the end before returning it.
{
@@ -326,6 +471,7 @@ fn run_worker(
&mem,
queues.remove(0),
inflate_event,
+ &inflate_tube,
interrupt.clone(),
|guest_address, len| {
if let Err(e) = mem.remove_range(guest_address, len) {
@@ -342,13 +488,16 @@ fn run_worker(
&mem,
queues.remove(0),
deflate_event,
+ &None,
interrupt.clone(),
|_, _| {}, // Ignore these.
);
pin_mut!(deflate);
- // The third queue is used for stats messages
- let (stats_tx, stats_rx) = mpsc::channel::<()>(1);
+ // The third queue is used for stats messages. The message type is the
+ // id of the stats request, so we can detect if there are any stale
+ // stats results that were queued during an error condition.
+ let (stats_tx, stats_rx) = mpsc::channel::<u64>(1);
let stats_event =
EventAsync::new(queue_evts.remove(0).0, &ex).expect("failed to set up the stats event");
let stats = handle_stats_queue(
@@ -357,67 +506,123 @@ fn run_worker(
stats_event,
stats_rx,
&command_tube,
- config.clone(),
+ state.clone(),
interrupt.clone(),
);
pin_mut!(stats);
// Future to handle command messages that resize the balloon.
- let command = handle_command_tube(&command_tube, interrupt.clone(), config, stats_tx);
+ let command =
+ handle_command_tube(&command_tube, interrupt.clone(), state.clone(), stats_tx);
pin_mut!(command);
// Process any requests to resample the irq value.
- let resample = handle_irq_resample(&ex, interrupt.clone());
+ let resample = async_utils::handle_irq_resample(&ex, interrupt.clone());
pin_mut!(resample);
// Exit if the kill event is triggered.
- let kill_evt = EventAsync::new(kill_evt.0, &ex).expect("failed to set up the kill event");
- let kill = wait_kill(kill_evt);
+ let kill = async_utils::await_and_exit(&ex, kill_evt);
pin_mut!(kill);
- if let Err(e) = ex.run_until(select6(inflate, deflate, stats, command, resample, kill)) {
+ let res = if !queues.is_empty() {
+ let events_event = EventAsync::new(queue_evts.remove(0).0, &ex)
+ .expect("failed to set up the events event");
+ let events = handle_events_queue(
+ &mem,
+ queues.remove(0),
+ events_event,
+ state,
+ interrupt,
+ &command_tube,
+ );
+ pin_mut!(events);
+
+ ex.run_until(select7(
+ inflate, deflate, stats, command, resample, kill, events,
+ ))
+ .map(|_| ())
+ } else {
+ ex.run_until(select6(inflate, deflate, stats, command, resample, kill))
+ .map(|_| ())
+ };
+
+ if let Err(e) = res {
error!("error happened in executor: {}", e);
}
}
- command_tube.into()
+ inflate_tube
}
/// Virtio device for memory balloon inflation/deflation.
pub struct Balloon {
- command_tube: Option<Tube>,
- config: Arc<BalloonConfig>,
+ command_tube: Tube,
+ inflate_tube: Option<Tube>,
+ state: Arc<AsyncMutex<BalloonState>>,
features: u64,
+ acked_features: u64,
kill_evt: Option<Event>,
- worker_thread: Option<thread::JoinHandle<Tube>>,
+ worker_thread: Option<thread::JoinHandle<Option<Tube>>>,
+}
+
+/// Operation mode of the balloon.
+#[derive(PartialEq)]
+pub enum BalloonMode {
+ /// The driver can access pages in the balloon (i.e. F_DEFLATE_ON_OOM)
+ Relaxed,
+ /// The driver cannot access pages in the balloon. Implies F_RESPONSIVE_DEVICE.
+ Strict,
}
impl Balloon {
/// Creates a new virtio balloon device.
- pub fn new(base_features: u64, command_tube: Tube) -> Result<Balloon> {
+ /// To let Balloon able to successfully release the memory which are pinned
+ /// by CoIOMMU to host, the inflate_tube will be used to send the inflate
+ /// ranges to CoIOMMU with UnpinRequest/UnpinResponse messages, so that The
+ /// memory in the inflate range can be unpinned first.
+ pub fn new(
+ base_features: u64,
+ command_tube: Tube,
+ inflate_tube: Option<Tube>,
+ init_balloon_size: u64,
+ mode: BalloonMode,
+ ) -> Result<Balloon> {
+ let features = base_features
+ | 1 << VIRTIO_BALLOON_F_MUST_TELL_HOST
+ | 1 << VIRTIO_BALLOON_F_STATS_VQ
+ | 1 << VIRTIO_BALLOON_F_EVENTS_VQ
+ | if mode == BalloonMode::Strict {
+ 1 << VIRTIO_BALLOON_F_RESPONSIVE_DEVICE
+ } else {
+ 1 << VIRTIO_BALLOON_F_DEFLATE_ON_OOM
+ };
+
Ok(Balloon {
- command_tube: Some(command_tube),
- config: Arc::new(BalloonConfig {
- num_pages: AtomicUsize::new(0),
- actual_pages: AtomicUsize::new(0),
- }),
+ command_tube,
+ inflate_tube,
+ state: Arc::new(AsyncMutex::new(BalloonState {
+ num_pages: (init_balloon_size >> VIRTIO_BALLOON_PFN_SHIFT) as u32,
+ actual_pages: 0,
+ failable_update: false,
+ })),
kill_evt: None,
worker_thread: None,
- features: base_features
- | 1 << VIRTIO_BALLOON_F_MUST_TELL_HOST
- | 1 << VIRTIO_BALLOON_F_STATS_VQ
- | 1 << VIRTIO_BALLOON_F_DEFLATE_ON_OOM,
+ features,
+ acked_features: 0,
})
}
fn get_config(&self) -> virtio_balloon_config {
- let num_pages = self.config.num_pages.load(Ordering::Relaxed) as u32;
- let actual_pages = self.config.actual_pages.load(Ordering::Relaxed) as u32;
+ let state = block_on(self.state.lock());
virtio_balloon_config {
- num_pages: num_pages.into(),
- actual: actual_pages.into(),
+ num_pages: state.num_pages.into(),
+ actual: state.actual_pages.into(),
}
}
+
+ fn event_queue_enabled(&self) -> bool {
+ (self.acked_features & ((1 << VIRTIO_BALLOON_F_EVENTS_VQ) as u64)) != 0
+ }
}
impl Drop for Balloon {
@@ -435,7 +640,11 @@ impl Drop for Balloon {
impl VirtioDevice for Balloon {
fn keep_rds(&self) -> Vec<RawDescriptor> {
- vec![self.command_tube.as_ref().unwrap().as_raw_descriptor()]
+ let mut rds = vec![self.command_tube.as_raw_descriptor()];
+ if let Some(inflate_tube) = &self.inflate_tube {
+ rds.push(inflate_tube.as_raw_descriptor());
+ }
+ rds
}
fn device_type(&self) -> u32 {
@@ -453,17 +662,27 @@ impl VirtioDevice for Balloon {
fn write_config(&mut self, offset: u64, data: &[u8]) {
let mut config = self.get_config();
copy_config(config.as_mut_slice(), offset, data, 0);
- self.config
- .actual_pages
- .store(config.actual.to_native() as usize, Ordering::Relaxed);
+ let mut state = block_on(self.state.lock());
+ state.actual_pages = config.actual.to_native();
+
+ if state.failable_update && state.actual_pages == state.num_pages {
+ state.failable_update = false;
+ if let Err(e) = send_adjusted_response(&self.command_tube, state.num_pages) {
+ error!("Failed to send response {:?}", e);
+ }
+ }
}
fn features(&self) -> u64 {
self.features
}
- fn ack_features(&mut self, value: u64) {
- self.features &= value;
+ fn ack_features(&mut self, mut value: u64) {
+ if value & !self.features != 0 {
+ warn!("virtio_balloon got unknown feature ack {:x}", value);
+ value &= self.features;
+ }
+ self.acked_features |= value;
}
fn activate(
@@ -473,7 +692,8 @@ impl VirtioDevice for Balloon {
queues: Vec<Queue>,
queue_evts: Vec<Event>,
) {
- if queues.len() != QUEUE_SIZES.len() || queue_evts.len() != QUEUE_SIZES.len() {
+ let expected_queues = if self.event_queue_enabled() { 4 } else { 3 };
+ if queues.len() != expected_queues || queue_evts.len() != expected_queues {
return;
}
@@ -486,8 +706,16 @@ impl VirtioDevice for Balloon {
};
self.kill_evt = Some(self_kill_evt);
- let config = self.config.clone();
- let command_tube = self.command_tube.take().unwrap();
+ let state = self.state.clone();
+ #[allow(deprecated)]
+ let command_tube = match self.command_tube.try_clone() {
+ Ok(tube) => tube,
+ Err(e) => {
+ error!("failed to clone command tube {:?}", e);
+ return;
+ }
+ };
+ let inflate_tube = self.inflate_tube.take();
let worker_result = thread::Builder::new()
.name("virtio_balloon".to_string())
.spawn(move || {
@@ -495,10 +723,11 @@ impl VirtioDevice for Balloon {
queue_evts,
queues,
command_tube,
+ inflate_tube,
interrupt,
kill_evt,
mem,
- config,
+ state,
)
});
@@ -526,8 +755,8 @@ impl VirtioDevice for Balloon {
error!("{}: failed to get back resources", self.debug_label());
return false;
}
- Ok(command_tube) => {
- self.command_tube = Some(command_tube);
+ Ok(inflate_tube) => {
+ self.inflate_tube = inflate_tube;
return true;
}
}
@@ -547,7 +776,7 @@ mod tests {
// Check that the memory addresses are parsed correctly by 'handle_address_chain' and passed
// to the closure.
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
memory
.write_obj_at_addr(0x10u32, GuestAddress(0x100))
.unwrap();
@@ -565,7 +794,7 @@ mod tests {
.expect("create_descriptor_chain failed");
let mut addrs = Vec::new();
- let res = handle_address_chain(chain, &memory, &mut |guest_address, len| {
+ let res = handle_address_chain(&None, chain, &memory, &mut |guest_address, len| {
addrs.push((guest_address, len));
});
assert!(res.is_ok());
diff --git a/devices/src/virtio/block_async.rs b/devices/src/virtio/block/asynchronous.rs
index dc04227c7..ffb6bd38b 100644
--- a/devices/src/virtio/block_async.rs
+++ b/devices/src/virtio/block/asynchronous.rs
@@ -3,7 +3,6 @@
// found in the LICENSE file.
use std::cell::RefCell;
-use std::cmp::{max, min};
use std::io::{self, Write};
use std::mem::size_of;
use std::rc::Rc;
@@ -20,133 +19,25 @@ use thiserror::Error as ThisError;
use base::Error as SysError;
use base::Result as SysResult;
-use base::{
- error, info, iov_max, warn, AsRawDescriptor, AsyncTube, Event, RawDescriptor, Timer, Tube,
- TubeError,
-};
+use base::{error, info, warn, AsRawDescriptor, Event, RawDescriptor, Timer, Tube, TubeError};
use cros_async::{
- select5, sync::Mutex as AsyncMutex, AsyncError, EventAsync, Executor, SelectResult, TimerAsync,
+ select5, sync::Mutex as AsyncMutex, AsyncError, AsyncTube, EventAsync, Executor, SelectResult,
+ TimerAsync,
};
-use data_model::{DataInit, Le16, Le32, Le64};
+use data_model::DataInit;
use disk::{AsyncDisk, ToAsyncDisk};
use vm_control::{DiskControlCommand, DiskControlResult};
use vm_memory::GuestMemory;
-use super::{
- copy_config, DescriptorChain, DescriptorError, Interrupt, Queue, Reader, SignalableInterrupt,
- VirtioDevice, Writer, TYPE_BLOCK,
+use super::common::*;
+use crate::virtio::{
+ async_utils, block::sys::*, copy_config, DescriptorChain, DescriptorError, Interrupt, Queue,
+ Reader, SignalableInterrupt, VirtioDevice, Writer, TYPE_BLOCK,
};
const QUEUE_SIZE: u16 = 256;
const NUM_QUEUES: u16 = 16;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES as usize];
-const SECTOR_SHIFT: u8 = 9;
-const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
-const MAX_DISCARD_SECTORS: u32 = u32::MAX;
-const MAX_WRITE_ZEROES_SECTORS: u32 = u32::MAX;
-// Arbitrary limits for number of discard/write zeroes segments.
-const MAX_DISCARD_SEG: u32 = 32;
-const MAX_WRITE_ZEROES_SEG: u32 = 32;
-// Hard-coded to 64 KiB (in 512-byte sectors) for now,
-// but this should probably be based on cluster size for qcow.
-const DISCARD_SECTOR_ALIGNMENT: u32 = 128;
-
-const ID_LEN: usize = 20;
-
-/// Virtio block device identifier.
-/// This is an ASCII string terminated by a \0, unless all 20 bytes are used,
-/// in which case the \0 terminator is omitted.
-pub type BlockId = [u8; ID_LEN];
-
-const VIRTIO_BLK_T_IN: u32 = 0;
-const VIRTIO_BLK_T_OUT: u32 = 1;
-const VIRTIO_BLK_T_FLUSH: u32 = 4;
-const VIRTIO_BLK_T_GET_ID: u32 = 8;
-const VIRTIO_BLK_T_DISCARD: u32 = 11;
-const VIRTIO_BLK_T_WRITE_ZEROES: u32 = 13;
-
-const VIRTIO_BLK_S_OK: u8 = 0;
-const VIRTIO_BLK_S_IOERR: u8 = 1;
-const VIRTIO_BLK_S_UNSUPP: u8 = 2;
-
-const VIRTIO_BLK_F_SEG_MAX: u32 = 2;
-const VIRTIO_BLK_F_RO: u32 = 5;
-const VIRTIO_BLK_F_BLK_SIZE: u32 = 6;
-const VIRTIO_BLK_F_FLUSH: u32 = 9;
-const VIRTIO_BLK_F_MQ: u32 = 12;
-const VIRTIO_BLK_F_DISCARD: u32 = 13;
-const VIRTIO_BLK_F_WRITE_ZEROES: u32 = 14;
-
-#[derive(Copy, Clone, Debug, Default)]
-#[repr(C)]
-struct virtio_blk_geometry {
- cylinders: Le16,
- heads: u8,
- sectors: u8,
-}
-
-// Safe because it only has data and has no implicit padding.
-unsafe impl DataInit for virtio_blk_geometry {}
-
-#[derive(Copy, Clone, Debug, Default)]
-#[repr(C)]
-struct virtio_blk_topology {
- physical_block_exp: u8,
- alignment_offset: u8,
- min_io_size: Le16,
- opt_io_size: Le32,
-}
-
-// Safe because it only has data and has no implicit padding.
-unsafe impl DataInit for virtio_blk_topology {}
-
-#[derive(Copy, Clone, Debug, Default)]
-#[repr(C, packed)]
-pub(crate) struct virtio_blk_config {
- capacity: Le64,
- size_max: Le32,
- seg_max: Le32,
- geometry: virtio_blk_geometry,
- blk_size: Le32,
- topology: virtio_blk_topology,
- writeback: u8,
- unused0: u8,
- pub num_queues: Le16,
- max_discard_sectors: Le32,
- max_discard_seg: Le32,
- discard_sector_alignment: Le32,
- max_write_zeroes_sectors: Le32,
- max_write_zeroes_seg: Le32,
- write_zeroes_may_unmap: u8,
- unused1: [u8; 3],
-}
-
-// Safe because it only has data and has no implicit padding.
-unsafe impl DataInit for virtio_blk_req_header {}
-
-#[derive(Copy, Clone, Debug, Default)]
-#[repr(C)]
-struct virtio_blk_req_header {
- req_type: Le32,
- reserved: Le32,
- sector: Le64,
-}
-
-// Safe because it only has data and has no implicit padding.
-unsafe impl DataInit for virtio_blk_config {}
-
-#[derive(Copy, Clone, Debug, Default)]
-#[repr(C)]
-struct virtio_blk_discard_write_zeroes {
- sector: Le64,
- num_sectors: Le32,
- flags: Le32,
-}
-
-const VIRTIO_BLK_DISCARD_WRITE_ZEROES_FLAG_UNMAP: u32 = 1 << 0;
-
-// Safe because it only has data and has no implicit padding.
-unsafe impl DataInit for virtio_blk_discard_write_zeroes {}
#[sorted]
#[derive(ThisError, Debug)]
@@ -218,9 +109,11 @@ impl ExecuteError {
}
}
-// Errors that happen in block outside of executing a request.
+/// Errors that happen in block outside of executing a request.
+/// This includes errors during resize and flush operations.
+#[sorted]
#[derive(ThisError, Debug)]
-enum OtherError {
+pub enum ControlError {
#[error("couldn't create an async resample event: {0}")]
AsyncResampleCreate(AsyncError),
#[error("couldn't clone the resample event: {0}")]
@@ -233,12 +126,32 @@ enum OtherError {
ReadResampleEvent(AsyncError),
}
-struct DiskState {
- disk_image: Box<dyn AsyncDisk>,
- disk_size: Arc<AtomicU64>,
- read_only: bool,
- sparse: bool,
- id: Option<BlockId>,
+/// Tracks the state of an anynchronous disk.
+pub struct DiskState {
+ pub disk_image: Box<dyn AsyncDisk>,
+ pub disk_size: Arc<AtomicU64>,
+ pub read_only: bool,
+ pub sparse: bool,
+ pub id: Option<BlockId>,
+}
+
+impl DiskState {
+ /// Creates a `DiskState` with the given params.
+ pub fn new(
+ disk_image: Box<dyn AsyncDisk>,
+ disk_size: Arc<AtomicU64>,
+ read_only: bool,
+ sparse: bool,
+ id: Option<BlockId>,
+ ) -> DiskState {
+ DiskState {
+ disk_image,
+ disk_size,
+ read_only,
+ sparse,
+ id,
+ }
+ }
}
async fn process_one_request(
@@ -272,7 +185,9 @@ async fn process_one_request(
{
Ok(()) => VIRTIO_BLK_S_OK,
Err(e) => {
- error!("failed executing disk request: {}", e);
+ if !matches!(e, ExecuteError::Unsupported(VIRTIO_BLK_T_GET_ID)) {
+ error!("failed executing disk request: {}", e);
+ }
e.status()
}
};
@@ -283,12 +198,13 @@ async fn process_one_request(
Ok(available_bytes)
}
-async fn process_one_request_task(
+/// Process one descriptor chain asynchronously.
+pub async fn process_one_chain<I: SignalableInterrupt>(
queue: Rc<RefCell<Queue>>,
avail_desc: DescriptorChain,
disk_state: Rc<AsyncMutex<DiskState>>,
mem: GuestMemory,
- interrupt: Rc<RefCell<Interrupt>>,
+ interrupt: &I,
flush_timer: Rc<RefCell<TimerAsync>>,
flush_timer_armed: Rc<RefCell<bool>>,
) {
@@ -306,20 +222,19 @@ async fn process_one_request_task(
let mut queue = queue.borrow_mut();
queue.add_used(&mem, descriptor_index, len as u32);
- queue.trigger_interrupt(&mem, &*interrupt.borrow());
- queue.update_int_required(&mem);
+ queue.trigger_interrupt(&mem, interrupt);
}
// There is one async task running `handle_queue` per virtio queue in use.
// Receives messages from the guest and queues a task to complete the operations with the async
// executor.
-async fn handle_queue(
- ex: &Executor,
- mem: &GuestMemory,
+pub async fn handle_queue<I: SignalableInterrupt + Clone + 'static>(
+ ex: Executor,
+ mem: GuestMemory,
disk_state: Rc<AsyncMutex<DiskState>>,
queue: Rc<RefCell<Queue>>,
evt: EventAsync,
- interrupt: Rc<RefCell<Interrupt>>,
+ interrupt: I,
flush_timer: Rc<RefCell<TimerAsync>>,
flush_timer_armed: Rc<RefCell<bool>>,
) {
@@ -329,55 +244,30 @@ async fn handle_queue(
continue;
}
while let Some(descriptor_chain) = queue.borrow_mut().pop(&mem) {
- ex.spawn_local(process_one_request_task(
- Rc::clone(&queue),
- descriptor_chain,
- Rc::clone(&disk_state),
- mem.clone(),
- Rc::clone(&interrupt),
- Rc::clone(&flush_timer),
- Rc::clone(&flush_timer_armed),
- ))
- .detach();
- }
- }
-}
-
-async fn handle_irq_resample(
- ex: &Executor,
- interrupt: Rc<RefCell<Interrupt>>,
-) -> result::Result<(), OtherError> {
- let resample_evt = if let Some(resample_evt) = interrupt.borrow().get_resample_evt() {
- let resample_evt = resample_evt
- .try_clone()
- .map_err(OtherError::CloneResampleEvent)?;
- let resample_evt =
- EventAsync::new(resample_evt.0, ex).map_err(OtherError::AsyncResampleCreate)?;
- Some(resample_evt)
- } else {
- None
- };
- if let Some(resample_evt) = resample_evt {
- loop {
- let _ = resample_evt
- .next_val()
+ let queue = Rc::clone(&queue);
+ let disk_state = Rc::clone(&disk_state);
+ let mem = mem.clone();
+ let interrupt = interrupt.clone();
+ let flush_timer = Rc::clone(&flush_timer);
+ let flush_timer_armed = Rc::clone(&flush_timer_armed);
+
+ ex.spawn_local(async move {
+ process_one_chain(
+ queue,
+ descriptor_chain,
+ disk_state,
+ mem,
+ &interrupt,
+ flush_timer,
+ flush_timer_armed,
+ )
.await
- .map_err(OtherError::ReadResampleEvent)?;
- interrupt.borrow().do_interrupt_resample();
+ })
+ .detach();
}
- } else {
- // no resample event, park the future.
- let () = futures::future::pending().await;
- Ok(())
}
}
-async fn wait_kill(kill_evt: EventAsync) {
- // Once this event is readable, exit. Exiting this future will cause the main loop to
- // break and the device process to exit.
- let _ = kill_evt.next_val().await;
-}
-
async fn handle_command_tube(
command_tube: &Option<AsyncTube>,
interrupt: Rc<RefCell<Interrupt>>,
@@ -399,11 +289,13 @@ async fn handle_command_tube(
}
};
+ let resp_clone = resp.clone();
command_tube
- .send(&resp)
+ .send(resp_clone)
+ .await
.map_err(ExecuteError::SendingResponse)?;
if let DiskControlResult::Ok = resp {
- interrupt.borrow_mut().signal_config_changed();
+ interrupt.borrow().signal_config_changed();
}
}
Err(e) => return Err(ExecuteError::ReceivingCommand(e)),
@@ -442,13 +334,14 @@ async fn resize(disk_state: Rc<AsyncMutex<DiskState>>, new_size: u64) -> DiskCon
DiskControlResult::Ok
}
-async fn flush_disk(
+/// Periodically flushes the disk when the given timer fires.
+pub async fn flush_disk(
disk_state: Rc<AsyncMutex<DiskState>>,
timer: TimerAsync,
armed: Rc<RefCell<bool>>,
-) -> Result<(), OtherError> {
+) -> Result<(), ControlError> {
loop {
- timer.next_val().await.map_err(OtherError::FlushTimer)?;
+ timer.next_val().await.map_err(ControlError::FlushTimer)?;
if !*armed.borrow() {
continue;
}
@@ -463,7 +356,7 @@ async fn flush_disk(
.disk_image
.fsync()
.await
- .map_err(OtherError::FsyncDisk)?;
+ .map_err(ControlError::FsyncDisk)?;
}
}
@@ -483,13 +376,24 @@ fn run_worker(
queue_evts: Vec<Event>,
kill_evt: Event,
) -> Result<(), String> {
- // Wrap the interupt in a `RefCell` so it can be shared between async functions.
+ if queues.len() != queue_evts.len() {
+ return Err("Number of queues and events must match.".to_string());
+ }
+
let interrupt = Rc::new(RefCell::new(interrupt));
// One flush timer per disk.
let timer = Timer::new().expect("Failed to create a timer");
let flush_timer_armed = Rc::new(RefCell::new(false));
+ // Process any requests to resample the irq value.
+ let resample = async_utils::handle_irq_resample(&ex, Rc::clone(&interrupt));
+ pin_mut!(resample);
+
+ // Handles control requests.
+ let control = handle_command_tube(control_tube, Rc::clone(&interrupt), disk_state.clone());
+ pin_mut!(control);
+
// Handle all the queues in one sub-select call.
let flush_timer = Rc::new(RefCell::new(
TimerAsync::new(
@@ -499,6 +403,7 @@ fn run_worker(
)
.expect("Failed to create an async timer"),
));
+
let queue_handlers =
queues
.into_iter()
@@ -507,17 +412,13 @@ fn run_worker(
EventAsync::new(e.0, &ex).expect("Failed to create async event for queue")
}))
.map(|(queue, event)| {
- // alias some refs so the lifetimes work.
- let mem = &mem;
- let disk_state = &disk_state;
- let interrupt = &interrupt;
handle_queue(
- &ex,
- mem,
- Rc::clone(&disk_state),
+ ex.clone(),
+ mem.clone(),
+ Rc::clone(disk_state),
Rc::clone(&queue),
event,
- interrupt.clone(),
+ Rc::clone(&interrupt),
Rc::clone(&flush_timer),
Rc::clone(&flush_timer_armed),
)
@@ -527,20 +428,11 @@ fn run_worker(
// Flushes the disk periodically.
let flush_timer = TimerAsync::new(timer.0, &ex).expect("Failed to create an async timer");
- let disk_flush = flush_disk(disk_state.clone(), flush_timer, flush_timer_armed.clone());
+ let disk_flush = flush_disk(disk_state.clone(), flush_timer, flush_timer_armed);
pin_mut!(disk_flush);
- // Handles control requests.
- let control = handle_command_tube(control_tube, interrupt.clone(), disk_state.clone());
- pin_mut!(control);
-
- // Process any requests to resample the irq value.
- let resample = handle_irq_resample(&ex, interrupt.clone());
- pin_mut!(resample);
-
// Exit if the kill event is triggered.
- let kill_evt = EventAsync::new(kill_evt.0, &ex).expect("Failed to create async kill event fd");
- let kill = wait_kill(kill_evt);
+ let kill = async_utils::await_and_exit(&ex, kill_evt);
pin_mut!(kill);
match ex.run_until(select5(queue_handlers, disk_flush, control, resample, kill)) {
@@ -575,23 +467,6 @@ pub struct BlockAsync {
control_tube: Option<Tube>,
}
-fn build_config_space(disk_size: u64, seg_max: u32, block_size: u32) -> virtio_blk_config {
- virtio_blk_config {
- // If the image is not a multiple of the sector size, the tail bits are not exposed.
- capacity: Le64::from(disk_size >> SECTOR_SHIFT),
- seg_max: Le32::from(seg_max),
- blk_size: Le32::from(block_size),
- num_queues: Le16::from(NUM_QUEUES),
- max_discard_sectors: Le32::from(MAX_DISCARD_SECTORS),
- discard_sector_alignment: Le32::from(DISCARD_SECTOR_ALIGNMENT),
- max_write_zeroes_sectors: Le32::from(MAX_WRITE_ZEROES_SECTORS),
- write_zeroes_may_unmap: 1,
- max_discard_seg: Le32::from(MAX_DISCARD_SEG),
- max_write_zeroes_seg: Le32::from(MAX_WRITE_ZEROES_SEG),
- ..Default::default()
- }
-}
-
impl BlockAsync {
/// Create a new virtio block device that operates on the given AsyncDisk.
pub fn new(
@@ -619,26 +494,9 @@ impl BlockAsync {
);
}
- let mut avail_features: u64 = base_features;
- avail_features |= 1 << VIRTIO_BLK_F_FLUSH;
- if read_only {
- avail_features |= 1 << VIRTIO_BLK_F_RO;
- } else {
- if sparse {
- avail_features |= 1 << VIRTIO_BLK_F_DISCARD;
- }
- avail_features |= 1 << VIRTIO_BLK_F_WRITE_ZEROES;
- }
- avail_features |= 1 << VIRTIO_BLK_F_SEG_MAX;
- avail_features |= 1 << VIRTIO_BLK_F_BLK_SIZE;
- avail_features |= 1 << VIRTIO_BLK_F_MQ;
+ let avail_features = build_avail_features(base_features, read_only, sparse, true);
- let seg_max = min(max(iov_max(), 1), u32::max_value() as usize) as u32;
-
- // Since we do not currently support indirect descriptors, the maximum
- // number of segments must be smaller than the queue size.
- // In addition, the request header and status each consume a descriptor.
- let seg_max = min(seg_max, u32::from(QUEUE_SIZE) - 2);
+ let seg_max = get_seg_max(QUEUE_SIZE);
Ok(BlockAsync {
kill_evt: None,
@@ -865,7 +723,7 @@ impl VirtioDevice for BlockAsync {
fn read_config(&self, offset: u64, data: &mut [u8]) {
let config_space = {
let disk_size = self.disk_size.load(Ordering::Acquire);
- build_config_space(disk_size, self.seg_max, self.block_size)
+ build_config_space(disk_size, self.seg_max, self.block_size, NUM_QUEUES)
};
copy_config(data, 0, config_space.as_slice(), offset);
}
@@ -897,8 +755,9 @@ impl VirtioDevice for BlockAsync {
.name("virtio_blk".to_string())
.spawn(move || {
let ex = Executor::new().expect("Failed to create an executor");
+
let async_control = control_tube
- .map(|c| c.into_async_tube(&ex).expect("failed to create async tube"));
+ .map(|c| AsyncTube::new(&ex, c).expect("failed to create async tube"));
let async_image = match disk_image.to_async_disk(&ex) {
Ok(d) => d,
Err(e) => panic!("Failed to create async disk {}", e),
@@ -976,13 +835,15 @@ mod tests {
use std::mem::size_of_val;
use std::sync::atomic::AtomicU64;
+ use data_model::{Le32, Le64};
use disk::SingleFileDisk;
+ use hypervisor::ProtectionType;
use tempfile::TempDir;
use vm_memory::GuestAddress;
use crate::virtio::base_features;
+ use crate::virtio::block::common::*;
use crate::virtio::descriptor_utils::{create_descriptor_chain, DescriptorType};
- use crate::ProtectionType;
use super::*;
@@ -1035,8 +896,8 @@ mod tests {
let b = BlockAsync::new(features, Box::new(f), false, true, 512, None, None).unwrap();
// writable device should set VIRTIO_BLK_F_FLUSH + VIRTIO_BLK_F_DISCARD
// + VIRTIO_BLK_F_WRITE_ZEROES + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE
- // + VIRTIO_BLK_F_SEG_MAX + VIRTIO_BLK_F_MQ
- assert_eq!(0x100007244, b.features());
+ // + VIRTIO_BLK_F_SEG_MAX + VIRTIO_BLK_F_MQ + VIRTIO_RING_F_EVENT_IDX
+ assert_eq!(0x120007244, b.features());
}
// read-write block device, non-sparse
@@ -1046,8 +907,8 @@ mod tests {
let b = BlockAsync::new(features, Box::new(f), false, false, 512, None, None).unwrap();
// read-only device should set VIRTIO_BLK_F_FLUSH and VIRTIO_BLK_F_RO
// + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE + VIRTIO_BLK_F_SEG_MAX
- // + VIRTIO_BLK_F_MQ
- assert_eq!(0x100005244, b.features());
+ // + VIRTIO_BLK_F_MQ + VIRTIO_RING_F_EVENT_IDX
+ assert_eq!(0x120005244, b.features());
}
// read-only block device
@@ -1057,8 +918,8 @@ mod tests {
let b = BlockAsync::new(features, Box::new(f), true, true, 512, None, None).unwrap();
// read-only device should set VIRTIO_BLK_F_FLUSH and VIRTIO_BLK_F_RO
// + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE + VIRTIO_BLK_F_SEG_MAX
- // + VIRTIO_BLK_F_MQ
- assert_eq!(0x100001264, b.features());
+ // + VIRTIO_BLK_F_MQ + VIRTIO_RING_F_EVENT_IDX
+ assert_eq!(0x120001264, b.features());
}
}
diff --git a/devices/src/virtio/block.rs b/devices/src/virtio/block/block.rs
index e9a202427..10f969868 100644
--- a/devices/src/virtio/block.rs
+++ b/devices/src/virtio/block/block.rs
@@ -2,10 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::cmp::{max, min};
-use std::fmt::{self, Display};
use std::io::{self, Write};
use std::mem::size_of;
+use std::path::PathBuf;
use std::result;
use std::sync::Arc;
use std::thread;
@@ -15,217 +14,71 @@ use std::u32;
use base::Error as SysError;
use base::Result as SysResult;
use base::{
- error, info, iov_max, warn, AsRawDescriptor, Event, PollToken, RawDescriptor, Timer, Tube,
- WaitContext,
+ error, info, warn, AsRawDescriptor, Event, PollToken, RawDescriptor, Timer, Tube, WaitContext,
};
-use data_model::{DataInit, Le16, Le32, Le64};
+use data_model::DataInit;
use disk::DiskFile;
+use remain::sorted;
+use serde::{Deserialize, Deserializer};
use sync::Mutex;
+use thiserror::Error;
use vm_control::{DiskControlCommand, DiskControlResult};
use vm_memory::GuestMemory;
-use super::{
- copy_config, DescriptorChain, DescriptorError, Interrupt, Queue, Reader, SignalableInterrupt,
- VirtioDevice, Writer, TYPE_BLOCK,
+use super::common::*;
+use crate::virtio::{
+ block::sys::*, copy_config, DescriptorChain, DescriptorError, Interrupt, Queue, Reader,
+ SignalableInterrupt, VirtioDevice, Writer, TYPE_BLOCK,
};
const QUEUE_SIZE: u16 = 256;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
-const SECTOR_SHIFT: u8 = 9;
-const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
-const MAX_DISCARD_SECTORS: u32 = u32::MAX;
-const MAX_WRITE_ZEROES_SECTORS: u32 = u32::MAX;
-// Arbitrary limits for number of discard/write zeroes segments.
-const MAX_DISCARD_SEG: u32 = 32;
-const MAX_WRITE_ZEROES_SEG: u32 = 32;
-// Hard-coded to 64 KiB (in 512-byte sectors) for now,
-// but this should probably be based on cluster size for qcow.
-const DISCARD_SECTOR_ALIGNMENT: u32 = 128;
-
-const ID_LEN: usize = 20;
-
-/// Virtio block device identifier.
-/// This is an ASCII string terminated by a \0, unless all 20 bytes are used,
-/// in which case the \0 terminator is omitted.
-pub type BlockId = [u8; ID_LEN];
-
-const VIRTIO_BLK_T_IN: u32 = 0;
-const VIRTIO_BLK_T_OUT: u32 = 1;
-const VIRTIO_BLK_T_FLUSH: u32 = 4;
-const VIRTIO_BLK_T_GET_ID: u32 = 8;
-const VIRTIO_BLK_T_DISCARD: u32 = 11;
-const VIRTIO_BLK_T_WRITE_ZEROES: u32 = 13;
-
-const VIRTIO_BLK_S_OK: u8 = 0;
-const VIRTIO_BLK_S_IOERR: u8 = 1;
-const VIRTIO_BLK_S_UNSUPP: u8 = 2;
-
-const VIRTIO_BLK_F_SEG_MAX: u32 = 2;
-const VIRTIO_BLK_F_RO: u32 = 5;
-const VIRTIO_BLK_F_BLK_SIZE: u32 = 6;
-const VIRTIO_BLK_F_FLUSH: u32 = 9;
-const VIRTIO_BLK_F_DISCARD: u32 = 13;
-const VIRTIO_BLK_F_WRITE_ZEROES: u32 = 14;
-
-#[derive(Copy, Clone, Debug, Default)]
-#[repr(C)]
-struct virtio_blk_geometry {
- cylinders: Le16,
- heads: u8,
- sectors: u8,
-}
-
-// Safe because it only has data and has no implicit padding.
-unsafe impl DataInit for virtio_blk_geometry {}
-
-#[derive(Copy, Clone, Debug, Default)]
-#[repr(C)]
-struct virtio_blk_topology {
- physical_block_exp: u8,
- alignment_offset: u8,
- min_io_size: Le16,
- opt_io_size: Le32,
-}
-
-// Safe because it only has data and has no implicit padding.
-unsafe impl DataInit for virtio_blk_topology {}
-
-#[derive(Copy, Clone, Debug, Default)]
-#[repr(C, packed)]
-struct virtio_blk_config {
- capacity: Le64,
- size_max: Le32,
- seg_max: Le32,
- geometry: virtio_blk_geometry,
- blk_size: Le32,
- topology: virtio_blk_topology,
- writeback: u8,
- unused0: [u8; 3],
- max_discard_sectors: Le32,
- max_discard_seg: Le32,
- discard_sector_alignment: Le32,
- max_write_zeroes_sectors: Le32,
- max_write_zeroes_seg: Le32,
- write_zeroes_may_unmap: u8,
- unused1: [u8; 3],
-}
-
-// Safe because it only has data and has no implicit padding.
-unsafe impl DataInit for virtio_blk_req_header {}
-
-#[derive(Copy, Clone, Debug, Default)]
-#[repr(C)]
-struct virtio_blk_req_header {
- req_type: Le32,
- reserved: Le32,
- sector: Le64,
-}
-
-// Safe because it only has data and has no implicit padding.
-unsafe impl DataInit for virtio_blk_config {}
+const NUM_QUEUES: u16 = 1;
-#[derive(Copy, Clone, Debug, Default)]
-#[repr(C)]
-struct virtio_blk_discard_write_zeroes {
- sector: Le64,
- num_sectors: Le32,
- flags: Le32,
-}
-
-const VIRTIO_BLK_DISCARD_WRITE_ZEROES_FLAG_UNMAP: u32 = 1 << 0;
-
-// Safe because it only has data and has no implicit padding.
-unsafe impl DataInit for virtio_blk_discard_write_zeroes {}
-
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
enum ExecuteError {
+ #[error("failed to copy ID string: {0}")]
CopyId(io::Error),
+ #[error("virtio descriptor error: {0}")]
Descriptor(DescriptorError),
- Read(io::Error),
- WriteStatus(io::Error),
+ #[error("failed to perform discard or write zeroes; sector={sector} num_sectors={num_sectors} flags={flags}; {ioerr:?}")]
+ DiscardWriteZeroes {
+ ioerr: Option<io::Error>,
+ sector: u64,
+ num_sectors: u32,
+ flags: u32,
+ },
/// Error arming the flush timer.
+ #[error("failed to flush: {0}")]
Flush(io::Error),
+ #[error("not enough space in descriptor chain to write status")]
+ MissingStatus,
+ #[error("out of range")]
+ OutOfRange,
+ #[error("failed to read message: {0}")]
+ Read(io::Error),
+ #[error("io error reading {length} bytes from sector {sector}: {desc_error}")]
ReadIo {
length: usize,
sector: u64,
desc_error: io::Error,
},
+ #[error("read only; request_type={request_type}")]
+ ReadOnly { request_type: u32 },
+ #[error("timer error: {0}")]
Timer(SysError),
+ #[error("unsupported ({0})")]
+ Unsupported(u32),
+ #[error("io error writing {length} bytes to sector {sector}: {desc_error}")]
WriteIo {
length: usize,
sector: u64,
desc_error: io::Error,
},
- DiscardWriteZeroes {
- ioerr: Option<io::Error>,
- sector: u64,
- num_sectors: u32,
- flags: u32,
- },
- ReadOnly {
- request_type: u32,
- },
- OutOfRange,
- MissingStatus,
- Unsupported(u32),
-}
-
-impl Display for ExecuteError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::ExecuteError::*;
-
- match self {
- CopyId(e) => write!(f, "failed to copy ID string: {}", e),
- Descriptor(e) => write!(f, "virtio descriptor error: {}", e),
- Read(e) => write!(f, "failed to read message: {}", e),
- WriteStatus(e) => write!(f, "failed to write request status: {}", e),
- Flush(e) => write!(f, "failed to flush: {}", e),
- ReadIo {
- length,
- sector,
- desc_error,
- } => write!(
- f,
- "io error reading {} bytes from sector {}: {}",
- length, sector, desc_error,
- ),
- Timer(e) => write!(f, "{}", e),
- WriteIo {
- length,
- sector,
- desc_error,
- } => write!(
- f,
- "io error writing {} bytes to sector {}: {}",
- length, sector, desc_error,
- ),
- DiscardWriteZeroes {
- ioerr: Some(ioerr),
- sector,
- num_sectors,
- flags,
- } => write!(
- f,
- "failed to perform discard or write zeroes; sector={} num_sectors={} flags={}; {}",
- sector, num_sectors, flags, ioerr,
- ),
- DiscardWriteZeroes {
- ioerr: None,
- sector,
- num_sectors,
- flags,
- } => write!(
- f,
- "failed to perform discard or write zeroes; sector={} num_sectors={} flags={}",
- sector, num_sectors, flags,
- ),
- ReadOnly { request_type } => write!(f, "read only; request_type={}", request_type),
- OutOfRange => write!(f, "out of range"),
- MissingStatus => write!(f, "not enough space in descriptor chain to write status"),
- Unsupported(n) => write!(f, "unsupported ({})", n),
- }
- }
+ #[error("failed to write request status: {0}")]
+ WriteStatus(io::Error),
}
impl ExecuteError {
@@ -248,6 +101,53 @@ impl ExecuteError {
}
}
+fn block_option_sparse_default() -> bool {
+ true
+}
+fn block_option_block_size_default() -> u32 {
+ 512
+}
+
+/// Maximum length of a `DiskOption` identifier.
+///
+/// This is based on the virtio-block ID length limit.
+pub const DISK_ID_LEN: usize = 20;
+
+fn deserialize_disk_id<'de, D: Deserializer<'de>>(
+ deserializer: D,
+) -> Result<Option<[u8; DISK_ID_LEN]>, D::Error> {
+ let id = String::deserialize(deserializer)?;
+
+ if id.len() > DISK_ID_LEN {
+ return Err(serde::de::Error::custom(format!(
+ "disk id must be {} or fewer characters",
+ DISK_ID_LEN
+ )));
+ }
+
+ let mut ret = [0u8; DISK_ID_LEN];
+ // Slicing id to value's length will never panic
+ // because we checked that value will fit into id above.
+ ret[..id.len()].copy_from_slice(id.as_bytes());
+ Ok(Some(ret))
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+#[serde(deny_unknown_fields)]
+pub struct DiskOption {
+ pub path: PathBuf,
+ #[serde(default, rename = "ro")]
+ pub read_only: bool,
+ #[serde(default = "block_option_sparse_default")]
+ pub sparse: bool,
+ #[serde(default)]
+ pub o_direct: bool,
+ #[serde(default = "block_option_block_size_default")]
+ pub block_size: u32,
+ #[serde(default, deserialize_with = "deserialize_disk_id")]
+ pub id: Option<[u8; DISK_ID_LEN]>,
+}
+
struct Worker {
interrupt: Interrupt,
queues: Vec<Queue>,
@@ -298,7 +198,9 @@ impl Worker {
) {
Ok(()) => VIRTIO_BLK_S_OK,
Err(e) => {
- error!("failed executing disk request: {}", e);
+ if !matches!(e, ExecuteError::Unsupported(VIRTIO_BLK_T_GET_ID)) {
+ error!("failed executing disk request: {}", e);
+ }
e.status()
}
};
@@ -507,22 +409,6 @@ pub struct Block {
control_tube: Option<Tube>,
}
-fn build_config_space(disk_size: u64, seg_max: u32, block_size: u32) -> virtio_blk_config {
- virtio_blk_config {
- // If the image is not a multiple of the sector size, the tail bits are not exposed.
- capacity: Le64::from(disk_size >> SECTOR_SHIFT),
- seg_max: Le32::from(seg_max),
- blk_size: Le32::from(block_size),
- max_discard_sectors: Le32::from(MAX_DISCARD_SECTORS),
- discard_sector_alignment: Le32::from(DISCARD_SECTOR_ALIGNMENT),
- max_write_zeroes_sectors: Le32::from(MAX_WRITE_ZEROES_SECTORS),
- write_zeroes_may_unmap: 1,
- max_discard_seg: Le32::from(MAX_DISCARD_SEG),
- max_write_zeroes_seg: Le32::from(MAX_WRITE_ZEROES_SEG),
- ..Default::default()
- }
-}
-
impl Block {
/// Create a new virtio block device that operates on the given DiskFile.
pub fn new(
@@ -550,25 +436,9 @@ impl Block {
);
}
- let mut avail_features: u64 = base_features;
- avail_features |= 1 << VIRTIO_BLK_F_FLUSH;
- if read_only {
- avail_features |= 1 << VIRTIO_BLK_F_RO;
- } else {
- if sparse {
- avail_features |= 1 << VIRTIO_BLK_F_DISCARD;
- }
- avail_features |= 1 << VIRTIO_BLK_F_WRITE_ZEROES;
- }
- avail_features |= 1 << VIRTIO_BLK_F_SEG_MAX;
- avail_features |= 1 << VIRTIO_BLK_F_BLK_SIZE;
-
- let seg_max = min(max(iov_max(), 1), u32::max_value() as usize) as u32;
+ let avail_features = build_avail_features(base_features, read_only, sparse, false);
- // Since we do not currently support indirect descriptors, the maximum
- // number of segments must be smaller than the queue size.
- // In addition, the request header and status each consume a descriptor.
- let seg_max = min(seg_max, u32::from(QUEUE_SIZE) - 2);
+ let seg_max = get_seg_max(QUEUE_SIZE);
Ok(Block {
kill_evt: None,
@@ -777,8 +647,8 @@ impl VirtioDevice for Block {
fn read_config(&self, offset: u64, data: &mut [u8]) {
let config_space = {
- let disk_size = self.disk_size.lock();
- build_config_space(*disk_size, self.seg_max, self.block_size)
+ let disk_size = *self.disk_size.lock();
+ build_config_space(disk_size, self.seg_max, self.block_size, NUM_QUEUES)
};
copy_config(data, 0, config_space.as_slice(), offset);
}
@@ -868,12 +738,16 @@ impl VirtioDevice for Block {
#[cfg(test)]
mod tests {
use std::mem::size_of_val;
+
+ use data_model::{Le32, Le64};
+ use hypervisor::ProtectionType;
+ use serde_keyvalue::*;
use tempfile::tempfile;
use vm_memory::GuestAddress;
use crate::virtio::base_features;
+ use crate::virtio::block::common::*;
use crate::virtio::descriptor_utils::{create_descriptor_chain, DescriptorType};
- use crate::ProtectionType;
use super::*;
@@ -916,8 +790,8 @@ mod tests {
let b = Block::new(features, Box::new(f), false, true, 512, None, None).unwrap();
// writable device should set VIRTIO_BLK_F_FLUSH + VIRTIO_BLK_F_DISCARD
// + VIRTIO_BLK_F_WRITE_ZEROES + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE
- // + VIRTIO_BLK_F_SEG_MAX
- assert_eq!(0x100006244, b.features());
+ // + VIRTIO_BLK_F_SEG_MAX + VIRTIO_RING_F_EVENT_IDX
+ assert_eq!(0x120006244, b.features());
}
// read-write block device, non-sparse
@@ -927,8 +801,8 @@ mod tests {
let b = Block::new(features, Box::new(f), false, false, 512, None, None).unwrap();
// writable device should set VIRTIO_BLK_F_FLUSH
// + VIRTIO_BLK_F_WRITE_ZEROES + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE
- // + VIRTIO_BLK_F_SEG_MAX
- assert_eq!(0x100004244, b.features());
+ // + VIRTIO_BLK_F_SEG_MAX + VIRTIO_RING_F_EVENT_IDX
+ assert_eq!(0x120004244, b.features());
}
// read-only block device
@@ -938,7 +812,8 @@ mod tests {
let b = Block::new(features, Box::new(f), true, true, 512, None, None).unwrap();
// read-only device should set VIRTIO_BLK_F_FLUSH and VIRTIO_BLK_F_RO
// + VIRTIO_F_VERSION_1 + VIRTIO_BLK_F_BLK_SIZE + VIRTIO_BLK_F_SEG_MAX
- assert_eq!(0x100000264, b.features());
+ // + VIRTIO_RING_F_EVENT_IDX
+ assert_eq!(0x120000264, b.features());
}
}
@@ -1109,4 +984,155 @@ mod tests {
let returned_id = mem.read_obj_from_addr::<[u8; 20]>(id_offset).unwrap();
assert_eq!(returned_id, *id);
}
+
+ fn from_block_arg(options: &str) -> Result<DiskOption, ParseError> {
+ from_key_values(options)
+ }
+
+ #[test]
+ fn params_from_key_values() {
+ // Path argument is mandatory.
+ let err = from_block_arg("").unwrap_err();
+ assert_eq!(
+ err,
+ ParseError {
+ kind: ErrorKind::SerdeError("missing field `path`".into()),
+ pos: 0,
+ }
+ );
+
+ // Path is the default argument.
+ let params = from_block_arg("/path/to/disk.img").unwrap();
+ assert_eq!(
+ params,
+ DiskOption {
+ path: "/path/to/disk.img".into(),
+ read_only: false,
+ sparse: true,
+ o_direct: false,
+ block_size: 512,
+ id: None,
+ }
+ );
+
+ // Explicitly-specified path.
+ let params = from_block_arg("path=/path/to/disk.img").unwrap();
+ assert_eq!(
+ params,
+ DiskOption {
+ path: "/path/to/disk.img".into(),
+ read_only: false,
+ sparse: true,
+ o_direct: false,
+ block_size: 512,
+ id: None,
+ }
+ );
+
+ // read_only
+ let params = from_block_arg("/some/path.img,ro").unwrap();
+ assert_eq!(
+ params,
+ DiskOption {
+ path: "/some/path.img".into(),
+ read_only: true,
+ sparse: true,
+ o_direct: false,
+ block_size: 512,
+ id: None,
+ }
+ );
+
+ // sparse
+ let params = from_block_arg("/some/path.img,sparse").unwrap();
+ assert_eq!(
+ params,
+ DiskOption {
+ path: "/some/path.img".into(),
+ read_only: false,
+ sparse: true,
+ o_direct: false,
+ block_size: 512,
+ id: None,
+ }
+ );
+ let params = from_block_arg("/some/path.img,sparse=false").unwrap();
+ assert_eq!(
+ params,
+ DiskOption {
+ path: "/some/path.img".into(),
+ read_only: false,
+ sparse: false,
+ o_direct: false,
+ block_size: 512,
+ id: None,
+ }
+ );
+
+ // o_direct
+ let params = from_block_arg("/some/path.img,o_direct").unwrap();
+ assert_eq!(
+ params,
+ DiskOption {
+ path: "/some/path.img".into(),
+ read_only: false,
+ sparse: true,
+ o_direct: true,
+ block_size: 512,
+ id: None,
+ }
+ );
+
+ // block_size
+ let params = from_block_arg("/some/path.img,block_size=128").unwrap();
+ assert_eq!(
+ params,
+ DiskOption {
+ path: "/some/path.img".into(),
+ read_only: false,
+ sparse: true,
+ o_direct: false,
+ block_size: 128,
+ id: None,
+ }
+ );
+
+ // id
+ let params = from_block_arg("/some/path.img,id=DISK").unwrap();
+ assert_eq!(
+ params,
+ DiskOption {
+ path: "/some/path.img".into(),
+ read_only: false,
+ sparse: true,
+ o_direct: false,
+ block_size: 512,
+ id: Some(*b"DISK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
+ }
+ );
+ let err = from_block_arg("/some/path.img,id=DISK_ID_IS_WAY_TOO_LONG").unwrap_err();
+ assert_eq!(
+ err,
+ ParseError {
+ kind: ErrorKind::SerdeError("disk id must be 20 or fewer characters".into()),
+ pos: 0,
+ }
+ );
+
+ // All together
+ let params =
+ from_block_arg("/some/path.img,block_size=256,ro,sparse=false,id=DISK_LABEL,o_direct")
+ .unwrap();
+ assert_eq!(
+ params,
+ DiskOption {
+ path: "/some/path.img".into(),
+ read_only: true,
+ sparse: false,
+ o_direct: true,
+ block_size: 256,
+ id: Some(*b"DISK_LABEL\0\0\0\0\0\0\0\0\0\0"),
+ }
+ );
+ }
}
diff --git a/devices/src/virtio/block/common.rs b/devices/src/virtio/block/common.rs
new file mode 100644
index 000000000..9d38cf3e4
--- /dev/null
+++ b/devices/src/virtio/block/common.rs
@@ -0,0 +1,161 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use data_model::{DataInit, Le16, Le32, Le64};
+
+pub const SECTOR_SHIFT: u8 = 9;
+pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;
+pub const MAX_DISCARD_SECTORS: u32 = u32::MAX;
+pub const MAX_WRITE_ZEROES_SECTORS: u32 = u32::MAX;
+// Arbitrary limits for number of discard/write zeroes segments.
+pub const MAX_DISCARD_SEG: u32 = 32;
+pub const MAX_WRITE_ZEROES_SEG: u32 = 32;
+// Hard-coded to 64 KiB (in 512-byte sectors) for now,
+// but this should probably be based on cluster size for qcow.
+pub const DISCARD_SECTOR_ALIGNMENT: u32 = 128;
+
+pub const ID_LEN: usize = 20;
+
+/// Virtio block device identifier.
+/// This is an ASCII string terminated by a \0, unless all 20 bytes are used,
+/// in which case the \0 terminator is omitted.
+pub type BlockId = [u8; ID_LEN];
+
+pub const VIRTIO_BLK_T_IN: u32 = 0;
+pub const VIRTIO_BLK_T_OUT: u32 = 1;
+pub const VIRTIO_BLK_T_FLUSH: u32 = 4;
+pub const VIRTIO_BLK_T_GET_ID: u32 = 8;
+pub const VIRTIO_BLK_T_DISCARD: u32 = 11;
+pub const VIRTIO_BLK_T_WRITE_ZEROES: u32 = 13;
+
+pub const VIRTIO_BLK_S_OK: u8 = 0;
+pub const VIRTIO_BLK_S_IOERR: u8 = 1;
+pub const VIRTIO_BLK_S_UNSUPP: u8 = 2;
+
+pub const VIRTIO_BLK_F_SEG_MAX: u32 = 2;
+pub const VIRTIO_BLK_F_RO: u32 = 5;
+pub const VIRTIO_BLK_F_BLK_SIZE: u32 = 6;
+pub const VIRTIO_BLK_F_FLUSH: u32 = 9;
+pub const VIRTIO_BLK_F_MQ: u32 = 12;
+pub const VIRTIO_BLK_F_DISCARD: u32 = 13;
+pub const VIRTIO_BLK_F_WRITE_ZEROES: u32 = 14;
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C)]
+pub struct virtio_blk_geometry {
+ cylinders: Le16,
+ heads: u8,
+ sectors: u8,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for virtio_blk_geometry {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C)]
+pub struct virtio_blk_topology {
+ physical_block_exp: u8,
+ alignment_offset: u8,
+ min_io_size: Le16,
+ opt_io_size: Le32,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for virtio_blk_topology {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+pub struct virtio_blk_config {
+ pub capacity: Le64,
+ pub size_max: Le32,
+ pub seg_max: Le32,
+ pub geometry: virtio_blk_geometry,
+ pub blk_size: Le32,
+ pub topology: virtio_blk_topology,
+ pub writeback: u8,
+ pub unused0: u8,
+ pub num_queues: Le16,
+ pub max_discard_sectors: Le32,
+ pub max_discard_seg: Le32,
+ pub discard_sector_alignment: Le32,
+ pub max_write_zeroes_sectors: Le32,
+ pub max_write_zeroes_seg: Le32,
+ pub write_zeroes_may_unmap: u8,
+ pub unused1: [u8; 3],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for virtio_blk_config {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C)]
+pub(crate) struct virtio_blk_req_header {
+ pub req_type: Le32,
+ pub reserved: Le32,
+ pub sector: Le64,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for virtio_blk_req_header {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C)]
+pub(crate) struct virtio_blk_discard_write_zeroes {
+ pub sector: Le64,
+ pub num_sectors: Le32,
+ pub flags: Le32,
+}
+
+pub(crate) const VIRTIO_BLK_DISCARD_WRITE_ZEROES_FLAG_UNMAP: u32 = 1 << 0;
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for virtio_blk_discard_write_zeroes {}
+
+/// Builds and returns the config structure used to specify block features.
+pub fn build_config_space(
+ disk_size: u64,
+ seg_max: u32,
+ block_size: u32,
+ num_queues: u16,
+) -> virtio_blk_config {
+ virtio_blk_config {
+ // If the image is not a multiple of the sector size, the tail bits are not exposed.
+ capacity: Le64::from(disk_size >> SECTOR_SHIFT),
+ seg_max: Le32::from(seg_max),
+ blk_size: Le32::from(block_size),
+ num_queues: Le16::from(num_queues),
+ max_discard_sectors: Le32::from(MAX_DISCARD_SECTORS),
+ discard_sector_alignment: Le32::from(DISCARD_SECTOR_ALIGNMENT),
+ max_write_zeroes_sectors: Le32::from(MAX_WRITE_ZEROES_SECTORS),
+ write_zeroes_may_unmap: 1,
+ max_discard_seg: Le32::from(MAX_DISCARD_SEG),
+ max_write_zeroes_seg: Le32::from(MAX_WRITE_ZEROES_SEG),
+ ..Default::default()
+ }
+}
+
+/// Returns the feature flags given the specified attributes.
+pub fn build_avail_features(
+ base_features: u64,
+ read_only: bool,
+ sparse: bool,
+ multi_queue: bool,
+) -> u64 {
+ let mut avail_features = base_features;
+ avail_features |= 1 << VIRTIO_BLK_F_FLUSH;
+ if read_only {
+ avail_features |= 1 << VIRTIO_BLK_F_RO;
+ } else {
+ if sparse {
+ avail_features |= 1 << VIRTIO_BLK_F_DISCARD;
+ }
+ avail_features |= 1 << VIRTIO_BLK_F_WRITE_ZEROES;
+ }
+ avail_features |= 1 << VIRTIO_BLK_F_SEG_MAX;
+ avail_features |= 1 << VIRTIO_BLK_F_BLK_SIZE;
+ if multi_queue {
+ avail_features |= 1 << VIRTIO_BLK_F_MQ;
+ }
+ avail_features
+}
diff --git a/devices/src/virtio/block/mod.rs b/devices/src/virtio/block/mod.rs
new file mode 100644
index 000000000..d564fc4a2
--- /dev/null
+++ b/devices/src/virtio/block/mod.rs
@@ -0,0 +1,12 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+pub mod asynchronous;
+pub mod block;
+pub(crate) mod common;
+pub(crate) mod sys;
+
+pub use asynchronous::{BlockAsync, DiskState};
+pub use block::Block;
+pub use common::*;
diff --git a/devices/src/virtio/block/sys/mod.rs b/devices/src/virtio/block/sys/mod.rs
new file mode 100644
index 000000000..99fe22911
--- /dev/null
+++ b/devices/src/virtio/block/sys/mod.rs
@@ -0,0 +1,13 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cfg_if::cfg_if! {
+ if #[cfg(unix)] {
+ mod unix;
+ pub use self::unix::*;
+ } else if #[cfg(windows)] {
+ mod windows;
+ pub use self::windows::*;
+ }
+}
diff --git a/devices/src/virtio/block/sys/unix.rs b/devices/src/virtio/block/sys/unix.rs
new file mode 100644
index 000000000..a51e87582
--- /dev/null
+++ b/devices/src/virtio/block/sys/unix.rs
@@ -0,0 +1,15 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use base::iov_max;
+use std::cmp::{max, min};
+
+pub fn get_seg_max(queue_size: u16) -> u32 {
+ let seg_max = min(max(iov_max(), 1), u32::max_value() as usize) as u32;
+
+ // Since we do not currently support indirect descriptors, the maximum
+ // number of segments must be smaller than the queue size.
+ // In addition, the request header and status each consume a descriptor.
+ min(seg_max, u32::from(queue_size) - 2)
+}
diff --git a/devices/src/virtio/block/sys/windows.rs b/devices/src/virtio/block/sys/windows.rs
new file mode 100644
index 000000000..d4b2661e1
--- /dev/null
+++ b/devices/src/virtio/block/sys/windows.rs
@@ -0,0 +1,8 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+pub fn get_seg_max(_queue_size: u16) -> u32 {
+ // Allow a single segment per request, since vectored I/O is not implemented for Windows yet.
+ 1
+}
diff --git a/devices/src/virtio/console.rs b/devices/src/virtio/console.rs
index 9aa789a5d..d0d665a1e 100644
--- a/devices/src/virtio/console.rs
+++ b/devices/src/virtio/console.rs
@@ -2,210 +2,242 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+use std::collections::VecDeque;
use std::io::{self, Read, Write};
-use std::sync::mpsc::{channel, Receiver, TryRecvError};
+use std::ops::DerefMut;
+use std::result;
+use std::sync::Arc;
use std::thread;
-use base::{error, Event, PollToken, RawDescriptor, WaitContext};
+use base::{error, Event, FileSync, PollToken, RawDescriptor, WaitContext};
use data_model::{DataInit, Le16, Le32};
+use hypervisor::ProtectionType;
+use remain::sorted;
+use sync::Mutex;
+use thiserror::Error as ThisError;
use vm_memory::GuestMemory;
use super::{
base_features, copy_config, Interrupt, Queue, Reader, SignalableInterrupt, VirtioDevice,
Writer, TYPE_CONSOLE,
};
-use crate::{ProtectionType, SerialDevice};
+use crate::SerialDevice;
-const QUEUE_SIZE: u16 = 256;
+pub(crate) const QUEUE_SIZE: u16 = 256;
// For now, just implement port 0 (receiveq and transmitq).
// If VIRTIO_CONSOLE_F_MULTIPORT is implemented, more queues will be needed.
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE];
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum ConsoleError {
+ /// There are no more available descriptors to receive into
+ #[error("no rx descriptors available")]
+ RxDescriptorsExhausted,
+}
+
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
-struct virtio_console_config {
- cols: Le16,
- rows: Le16,
- max_nr_ports: Le32,
- emerg_wr: Le32,
+pub struct virtio_console_config {
+ pub cols: Le16,
+ pub rows: Le16,
+ pub max_nr_ports: Le32,
+ pub emerg_wr: Le32,
}
// Safe because it only has data and has no implicit padding.
unsafe impl DataInit for virtio_console_config {}
-struct Worker {
- mem: GuestMemory,
- interrupt: Interrupt,
- input: Option<Box<dyn io::Read + Send>>,
- output: Option<Box<dyn io::Write + Send>>,
-}
-
-fn write_output(output: &mut Box<dyn io::Write>, data: &[u8]) -> io::Result<()> {
- output.write_all(&data)?;
- output.flush()
-}
-
-impl Worker {
- fn process_transmit_request(
- mut reader: Reader,
- output: &mut Box<dyn io::Write>,
- ) -> io::Result<u32> {
- let len = reader.available_bytes();
- let mut data = vec![0u8; len];
- reader.read_exact(&mut data)?;
- write_output(output, &data)?;
- Ok(0)
- }
-
- fn process_transmit_queue(
- &mut self,
- transmit_queue: &mut Queue,
- output: &mut Box<dyn io::Write>,
- ) {
- let mut needs_interrupt = false;
- while let Some(avail_desc) = transmit_queue.pop(&self.mem) {
- let desc_index = avail_desc.index;
+/// Checks for input from `buffer` and transfers it to the receive queue, if any.
+///
+/// # Arguments
+///
+/// * `mem` - The GuestMemory to write the data into
+/// * `interrupt` - SignalableInterrupt used to signal that the queue has been used
+/// * `buffer` - Ring buffer providing data to put into the guest
+/// * `receive_queue` - The receive virtio Queue
+pub fn handle_input<I: SignalableInterrupt>(
+ mem: &GuestMemory,
+ interrupt: &I,
+ buffer: &mut VecDeque<u8>,
+ receive_queue: &mut Queue,
+) -> result::Result<(), ConsoleError> {
+ loop {
+ let desc = receive_queue
+ .peek(mem)
+ .ok_or(ConsoleError::RxDescriptorsExhausted)?;
+ let desc_index = desc.index;
+ // TODO(morg): Handle extra error cases as Err(ConsoleError) instead of just returning.
+ let mut writer = match Writer::new(mem.clone(), desc) {
+ Ok(w) => w,
+ Err(e) => {
+ error!("console: failed to create Writer: {}", e);
+ return Ok(());
+ }
+ };
- let reader = match Reader::new(self.mem.clone(), avail_desc) {
- Ok(r) => r,
- Err(e) => {
- error!("console: failed to create reader: {}", e);
- transmit_queue.add_used(&self.mem, desc_index, 0);
- needs_interrupt = true;
- continue;
- }
+ while writer.available_bytes() > 0 && !buffer.is_empty() {
+ let (buffer_front, buffer_back) = buffer.as_slices();
+ let buffer_chunk = if !buffer_front.is_empty() {
+ buffer_front
+ } else {
+ buffer_back
};
+ let written = writer.write(buffer_chunk).unwrap();
+ drop(buffer.drain(..written));
+ }
- let len = match Self::process_transmit_request(reader, output) {
- Ok(written) => written,
- Err(e) => {
- error!("console: process_transmit_request failed: {}", e);
- 0
- }
- };
+ let bytes_written = writer.bytes_written() as u32;
- transmit_queue.add_used(&self.mem, desc_index, len);
- needs_interrupt = true;
+ if bytes_written > 0 {
+ receive_queue.pop_peeked(mem);
+ receive_queue.add_used(mem, desc_index, bytes_written);
+ receive_queue.trigger_interrupt(mem, interrupt);
}
- if needs_interrupt {
- self.interrupt.signal_used_queue(transmit_queue.vector);
+ if bytes_written == 0 {
+ return Ok(());
}
}
+}
- // Start a thread that reads self.input and sends the input back via the returned channel.
- //
- // `in_avail_evt` will be triggered by the thread when new input is available.
- fn spawn_input_thread(&mut self, in_avail_evt: &Event) -> Option<Receiver<Vec<u8>>> {
- let mut rx = match self.input.take() {
- Some(input) => input,
- None => return None,
+/// Processes the data taken from the given transmit queue into the output sink.
+///
+/// # Arguments
+///
+/// * `mem` - The GuestMemory to take the data from
+/// * `interrupt` - SignalableInterrupt used to signal (if required) that the queue has been used
+/// * `transmit_queue` - The transmit virtio Queue
+/// * `output` - The output sink we are going to write the data into
+pub fn process_transmit_queue<I: SignalableInterrupt>(
+ mem: &GuestMemory,
+ interrupt: &I,
+ transmit_queue: &mut Queue,
+ output: &mut dyn io::Write,
+) {
+ let mut needs_interrupt = false;
+ while let Some(avail_desc) = transmit_queue.pop(mem) {
+ let desc_index = avail_desc.index;
+
+ let reader = match Reader::new(mem.clone(), avail_desc) {
+ Ok(r) => r,
+ Err(e) => {
+ error!("console: failed to create reader: {}", e);
+ transmit_queue.add_used(mem, desc_index, 0);
+ needs_interrupt = true;
+ continue;
+ }
};
- let (send_channel, recv_channel) = channel();
-
- let thread_in_avail_evt = match in_avail_evt.try_clone() {
- Ok(evt) => evt,
+ let len = match process_transmit_request(reader, output) {
+ Ok(written) => written,
Err(e) => {
- error!("failed to clone in_avail_evt: {}", e);
- return None;
+ error!("console: process_transmit_request failed: {}", e);
+ 0
}
};
- // The input thread runs in detached mode and will exit when channel is disconnected because
- // the console device has been dropped.
- let res = thread::Builder::new()
- .name("console_input".to_string())
- .spawn(move || {
- loop {
- let mut rx_buf = vec![0u8; 1 << 12];
- match rx.read(&mut rx_buf) {
- Ok(0) => break, // Assume the stream of input has ended.
- Ok(size) => {
- rx_buf.truncate(size);
- if send_channel.send(rx_buf).is_err() {
- // The receiver has disconnected.
- break;
- }
- thread_in_avail_evt.write(1).unwrap();
- }
- Err(e) => {
- // Being interrupted is not an error, but everything else is.
- if e.kind() != io::ErrorKind::Interrupted {
- error!(
- "failed to read for bytes to queue into console device: {}",
- e
- );
- break;
- }
- }
- }
- }
- });
- if let Err(e) = res {
- error!("failed to spawn input thread: {}", e);
- return None;
- }
- Some(recv_channel)
+ transmit_queue.add_used(mem, desc_index, len);
+ needs_interrupt = true;
}
- // Check for input from `in_channel_opt` and transfer it to the receive queue, if any.
- fn handle_input(
- &mut self,
- in_channel_opt: &mut Option<Receiver<Vec<u8>>>,
- receive_queue: &mut Queue,
- ) {
- let in_channel = match in_channel_opt.as_ref() {
- Some(v) => v,
- None => return,
- };
+ if needs_interrupt {
+ transmit_queue.trigger_interrupt(mem, interrupt);
+ }
+}
- while let Some(desc) = receive_queue.peek(&self.mem) {
- let desc_index = desc.index;
- let mut writer = match Writer::new(self.mem.clone(), desc) {
- Ok(w) => w,
- Err(e) => {
- error!("console: failed to create Writer: {}", e);
- break;
- }
- };
+struct Worker {
+ mem: GuestMemory,
+ interrupt: Interrupt,
+ input: Option<Arc<Mutex<VecDeque<u8>>>>,
+ output: Box<dyn io::Write + Send>,
+ kill_evt: Event,
+ in_avail_evt: Event,
+ receive_queue: Queue,
+ receive_evt: Event,
+ transmit_queue: Queue,
+ transmit_evt: Event,
+}
+
+fn write_output(output: &mut dyn io::Write, data: &[u8]) -> io::Result<()> {
+ output.write_all(data)?;
+ output.flush()
+}
- let mut disconnected = false;
- while writer.available_bytes() > 0 {
- match in_channel.try_recv() {
- Ok(data) => {
- writer.write_all(&data).unwrap();
+/// Starts a thread that reads rx and sends the input back via the returned buffer.
+///
+/// The caller should listen on `in_avail_evt` for events. When `in_avail_evt` signals that data
+/// is available, the caller should lock the returned `Mutex` and read data out of the inner
+/// `VecDeque`. The data should be removed from the beginning of the `VecDeque` as it is processed.
+///
+/// # Arguments
+///
+/// * `rx` - Data source that the reader thread will wait on to send data back to the buffer
+/// * `in_avail_evt` - Event triggered by the thread when new input is available on the buffer
+pub fn spawn_input_thread(
+ mut rx: Box<dyn io::Read + Send>,
+ in_avail_evt: &Event,
+) -> Option<Arc<Mutex<VecDeque<u8>>>> {
+ let buffer = Arc::new(Mutex::new(VecDeque::<u8>::new()));
+ let buffer_cloned = buffer.clone();
+
+ let thread_in_avail_evt = match in_avail_evt.try_clone() {
+ Ok(evt) => evt,
+ Err(e) => {
+ error!("failed to clone in_avail_evt: {}", e);
+ return None;
+ }
+ };
+
+ // The input thread runs in detached mode.
+ let res = thread::Builder::new()
+ .name("console_input".to_string())
+ .spawn(move || {
+ let mut rx_buf = [0u8; 1 << 12];
+ loop {
+ match rx.read(&mut rx_buf) {
+ Ok(0) => break, // Assume the stream of input has ended.
+ Ok(size) => {
+ buffer.lock().extend(&rx_buf[0..size]);
+ thread_in_avail_evt.write(1).unwrap();
}
- Err(TryRecvError::Empty) => break,
- Err(TryRecvError::Disconnected) => {
- disconnected = true;
- break;
+ Err(e) => {
+ // Being interrupted is not an error, but everything else is.
+ if e.kind() != io::ErrorKind::Interrupted {
+ error!(
+ "failed to read for bytes to queue into console device: {}",
+ e
+ );
+ break;
+ }
}
}
}
-
- let bytes_written = writer.bytes_written() as u32;
-
- if bytes_written > 0 {
- receive_queue.pop_peeked(&self.mem);
- receive_queue.add_used(&self.mem, desc_index, bytes_written);
- self.interrupt.signal_used_queue(receive_queue.vector);
- }
-
- if disconnected {
- // Set in_channel to None so that future handle_input calls exit early.
- in_channel_opt.take();
- return;
- }
-
- if bytes_written == 0 {
- break;
- }
- }
+ });
+ if let Err(e) = res {
+ error!("failed to spawn input thread: {}", e);
+ return None;
}
+ Some(buffer_cloned)
+}
+
+/// Writes the available data from the reader into the given output queue.
+///
+/// # Arguments
+///
+/// * `reader` - The Reader with the data we want to write.
+/// * `output` - The output sink we are going to write the data to.
+pub fn process_transmit_request(mut reader: Reader, output: &mut dyn io::Write) -> io::Result<u32> {
+ let len = reader.available_bytes();
+ let mut data = vec![0u8; len];
+ reader.read_exact(&mut data)?;
+ write_output(output, &data)?;
+ Ok(0)
+}
- fn run(&mut self, mut queues: Vec<Queue>, mut queue_evts: Vec<Event>, kill_evt: Event) {
+impl Worker {
+ fn run(&mut self) {
#[derive(PollToken)]
enum Token {
ReceiveQueueAvailable,
@@ -215,32 +247,11 @@ impl Worker {
Kill,
}
- // Device -> driver
- let (mut receive_queue, receive_evt) = (queues.remove(0), queue_evts.remove(0));
-
- // Driver -> device
- let (mut transmit_queue, transmit_evt) = (queues.remove(0), queue_evts.remove(0));
-
- let in_avail_evt = match Event::new() {
- Ok(evt) => evt,
- Err(e) => {
- error!("failed creating Event: {}", e);
- return;
- }
- };
-
- // Spawn a separate thread to poll self.input.
- // A thread is used because io::Read only provides a blocking interface, and there is no
- // generic way to add an io::Read instance to a poll context (it may not be backed by a file
- // descriptor). Moving the blocking read call to a separate thread and sending data back to
- // the main worker thread with an event for notification bridges this gap.
- let mut in_channel = self.spawn_input_thread(&in_avail_evt);
-
let wait_ctx: WaitContext<Token> = match WaitContext::build_with(&[
- (&transmit_evt, Token::TransmitQueueAvailable),
- (&receive_evt, Token::ReceiveQueueAvailable),
- (&in_avail_evt, Token::InputAvailable),
- (&kill_evt, Token::Kill),
+ (&self.transmit_evt, Token::TransmitQueueAvailable),
+ (&self.receive_evt, Token::ReceiveQueueAvailable),
+ (&self.in_avail_evt, Token::InputAvailable),
+ (&self.kill_evt, Token::Kill),
]) {
Ok(pc) => pc,
Err(e) => {
@@ -258,11 +269,6 @@ impl Worker {
}
}
- let mut output: Box<dyn io::Write> = match self.output.take() {
- Some(o) => o,
- None => Box::new(io::sink()),
- };
-
'wait: loop {
let events = match wait_ctx.wait() {
Ok(v) => v,
@@ -275,25 +281,56 @@ impl Worker {
for event in events.iter().filter(|e| e.is_readable) {
match event.token {
Token::TransmitQueueAvailable => {
- if let Err(e) = transmit_evt.read() {
+ if let Err(e) = self.transmit_evt.read() {
error!("failed reading transmit queue Event: {}", e);
break 'wait;
}
- self.process_transmit_queue(&mut transmit_queue, &mut output);
+ process_transmit_queue(
+ &self.mem,
+ &self.interrupt,
+ &mut self.transmit_queue,
+ &mut self.output,
+ );
}
Token::ReceiveQueueAvailable => {
- if let Err(e) = receive_evt.read() {
+ if let Err(e) = self.receive_evt.read() {
error!("failed reading receive queue Event: {}", e);
break 'wait;
}
- self.handle_input(&mut in_channel, &mut receive_queue);
+ if let Some(in_buf_ref) = self.input.as_ref() {
+ match handle_input(
+ &self.mem,
+ &self.interrupt,
+ in_buf_ref.lock().deref_mut(),
+ &mut self.receive_queue,
+ ) {
+ Ok(()) => {}
+ // Console errors are no-ops, so just continue.
+ Err(_) => {
+ continue;
+ }
+ }
+ }
}
Token::InputAvailable => {
- if let Err(e) = in_avail_evt.read() {
+ if let Err(e) = self.in_avail_evt.read() {
error!("failed reading in_avail_evt: {}", e);
break 'wait;
}
- self.handle_input(&mut in_channel, &mut receive_queue);
+ if let Some(in_buf_ref) = self.input.as_ref() {
+ match handle_input(
+ &self.mem,
+ &self.interrupt,
+ in_buf_ref.lock().deref_mut(),
+ &mut self.receive_queue,
+ ) {
+ Ok(()) => {}
+ // Console errors are no-ops, so just continue.
+ Err(_) => {
+ continue;
+ }
+ }
+ }
}
Token::InterruptResample => {
self.interrupt.interrupt_resample();
@@ -305,12 +342,18 @@ impl Worker {
}
}
+enum ConsoleInput {
+ FromRead(Box<dyn io::Read + Send>),
+ FromThread(Arc<Mutex<VecDeque<u8>>>),
+}
+
/// Virtio console device.
pub struct Console {
base_features: u64,
kill_evt: Option<Event>,
+ in_avail_evt: Option<Event>,
worker_thread: Option<thread::JoinHandle<Worker>>,
- input: Option<Box<dyn io::Read + Send>>,
+ input: Option<ConsoleInput>,
output: Option<Box<dyn io::Write + Send>>,
keep_rds: Vec<RawDescriptor>,
}
@@ -321,13 +364,16 @@ impl SerialDevice for Console {
_evt: Event,
input: Option<Box<dyn io::Read + Send>>,
output: Option<Box<dyn io::Write + Send>>,
+ _sync: Option<Box<dyn FileSync + Send>>,
+ _out_timestamp: bool,
keep_rds: Vec<RawDescriptor>,
) -> Console {
Console {
base_features: base_features(protected_vm),
+ in_avail_evt: None,
kill_evt: None,
worker_thread: None,
- input,
+ input: input.map(ConsoleInput::FromRead),
output,
keep_rds,
}
@@ -376,8 +422,8 @@ impl VirtioDevice for Console {
&mut self,
mem: GuestMemory,
interrupt: Interrupt,
- queues: Vec<Queue>,
- queue_evts: Vec<Event>,
+ mut queues: Vec<Queue>,
+ mut queue_evts: Vec<Event>,
) {
if queues.len() < 2 || queue_evts.len() < 2 {
return;
@@ -392,8 +438,40 @@ impl VirtioDevice for Console {
};
self.kill_evt = Some(self_kill_evt);
- let input = self.input.take();
- let output = self.output.take();
+ if self.in_avail_evt.is_none() {
+ self.in_avail_evt = match Event::new() {
+ Ok(evt) => Some(evt),
+ Err(e) => {
+ error!("failed creating Event: {}", e);
+ return;
+ }
+ };
+ }
+ let in_avail_evt = match self.in_avail_evt.as_ref().unwrap().try_clone() {
+ Ok(v) => v,
+ Err(e) => {
+ error!("failed creating input available Event pair: {}", e);
+ return;
+ }
+ };
+
+ // Spawn a separate thread to poll self.input.
+ // A thread is used because io::Read only provides a blocking interface, and there is no
+ // generic way to add an io::Read instance to a poll context (it may not be backed by a file
+ // descriptor). Moving the blocking read call to a separate thread and sending data back to
+ // the main worker thread with an event for notification bridges this gap.
+ let input = match self.input.take() {
+ Some(ConsoleInput::FromRead(read)) => {
+ let buffer = spawn_input_thread(read, self.in_avail_evt.as_ref().unwrap());
+ if buffer.is_none() {
+ error!("failed creating input thread");
+ };
+ buffer
+ }
+ Some(ConsoleInput::FromThread(buffer)) => Some(buffer),
+ None => None,
+ };
+ let output = self.output.take().unwrap_or_else(|| Box::new(io::sink()));
let worker_result = thread::Builder::new()
.name("virtio_console".to_string())
@@ -403,8 +481,16 @@ impl VirtioDevice for Console {
interrupt,
input,
output,
+ in_avail_evt,
+ kill_evt,
+ // Device -> driver
+ receive_queue: queues.remove(0),
+ receive_evt: queue_evts.remove(0),
+ // Driver -> device
+ transmit_queue: queues.remove(0),
+ transmit_evt: queue_evts.remove(0),
};
- worker.run(queues, queue_evts, kill_evt);
+ worker.run();
worker
});
@@ -433,8 +519,8 @@ impl VirtioDevice for Console {
return false;
}
Ok(worker) => {
- self.input = worker.input;
- self.output = worker.output;
+ self.input = worker.input.map(ConsoleInput::FromThread);
+ self.output = Some(worker.output);
return true;
}
}
diff --git a/devices/src/virtio/descriptor_utils.rs b/devices/src/virtio/descriptor_utils.rs
index fc839b610..d49fd425c 100644
--- a/devices/src/virtio/descriptor_utils.rs
+++ b/devices/src/virtio/descriptor_utils.rs
@@ -5,56 +5,44 @@
use std::borrow::Cow;
use std::cmp;
use std::convert::TryInto;
-use std::fmt::{self, Display};
-use std::io::{self, Read, Write};
+use std::io::{self, Write};
use std::iter::FromIterator;
use std::marker::PhantomData;
-use std::mem::{size_of, MaybeUninit};
use std::ptr::copy_nonoverlapping;
use std::result;
use std::sync::Arc;
+use anyhow::Context;
use base::{FileReadWriteAtVolatile, FileReadWriteVolatile};
use cros_async::MemRegion;
use data_model::{DataInit, Le16, Le32, Le64, VolatileMemoryError, VolatileSlice};
use disk::AsyncDisk;
+use remain::sorted;
use smallvec::SmallVec;
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory};
use super::DescriptorChain;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
+ #[error("the combined length of all the buffers in a `DescriptorChain` would overflow")]
DescriptorChainOverflow,
+ #[error("descriptor guest memory error: {0}")]
GuestMemoryError(vm_memory::GuestMemoryError),
- InvalidChain,
+ #[error("invalid descriptor chain: {0:#}")]
+ InvalidChain(anyhow::Error),
+ #[error("descriptor I/O error: {0}")]
IoError(io::Error),
+ #[error("`DescriptorChain` split is out of bounds: {0}")]
SplitOutOfBounds(usize),
+ #[error("volatile memory error: {0}")]
VolatileMemoryError(VolatileMemoryError),
}
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- DescriptorChainOverflow => write!(
- f,
- "the combined length of all the buffers in a `DescriptorChain` would overflow"
- ),
- GuestMemoryError(e) => write!(f, "descriptor guest memory error: {}", e),
- InvalidChain => write!(f, "invalid descriptor chain"),
- IoError(e) => write!(f, "descriptor I/O error: {}", e),
- SplitOutOfBounds(off) => write!(f, "`DescriptorChain` split is out of bounds: {}", off),
- VolatileMemoryError(e) => write!(f, "volatile memory error: {}", e),
- }
- }
-}
-
pub type Result<T> = result::Result<T, Error>;
-impl std::error::Error for Error {}
-
#[derive(Clone)]
struct DescriptorChainRegions {
regions: SmallVec<[MemRegion; 16]>,
@@ -232,35 +220,44 @@ impl<'a, T: DataInit> Iterator for ReaderIterator<'a, T> {
}
}
+/// Get all the mem regions from a `DescriptorChain` iterator, regardless if the `DescriptorChain`
+/// contains GPAs (guest physical address), or IOVAs (io virtual address). IOVAs will
+/// be translated to GPAs via IOMMU.
+pub fn get_mem_regions<I>(mem: &GuestMemory, vals: I) -> Result<SmallVec<[MemRegion; 16]>>
+where
+ I: Iterator<Item = DescriptorChain>,
+{
+ let mut total_len: usize = 0;
+ let mut regions = SmallVec::new();
+
+ // TODO(jstaron): Update this code to take the indirect descriptors into account.
+ for desc in vals {
+ // Verify that summing the descriptor sizes does not overflow.
+ // This can happen if a driver tricks a device into reading/writing more data than
+ // fits in a `usize`.
+ total_len = total_len
+ .checked_add(desc.len as usize)
+ .ok_or(Error::DescriptorChainOverflow)?;
+
+ for r in desc.get_mem_regions().map_err(Error::InvalidChain)? {
+ // Check that all the regions are totally contained in GuestMemory.
+ mem.get_slice_at_addr(r.gpa, r.len.try_into().expect("u32 doesn't fit in usize"))
+ .map_err(Error::GuestMemoryError)?;
+
+ regions.push(MemRegion {
+ offset: r.gpa.offset(),
+ len: r.len.try_into().expect("u32 doesn't fit in usize"),
+ });
+ }
+ }
+
+ Ok(regions)
+}
+
impl Reader {
/// Construct a new Reader wrapper over `desc_chain`.
pub fn new(mem: GuestMemory, desc_chain: DescriptorChain) -> Result<Reader> {
- // TODO(jstaron): Update this code to take the indirect descriptors into account.
- let mut total_len: usize = 0;
- let regions = desc_chain
- .into_iter()
- .readable()
- .map(|desc| {
- // Verify that summing the descriptor sizes does not overflow.
- // This can happen if a driver tricks a device into reading more data than
- // fits in a `usize`.
- total_len = total_len
- .checked_add(desc.len as usize)
- .ok_or(Error::DescriptorChainOverflow)?;
-
- // Check that all the regions are totally contained in GuestMemory.
- mem.get_slice_at_addr(
- desc.addr,
- desc.len.try_into().expect("u32 doesn't fit in usize"),
- )
- .map_err(Error::GuestMemoryError)?;
-
- Ok(MemRegion {
- offset: desc.addr.0,
- len: desc.len.try_into().expect("u32 doesn't fit in usize"),
- })
- })
- .collect::<Result<SmallVec<[MemRegion; 16]>>>()?;
+ let regions = get_mem_regions(&mem, desc_chain.into_iter().readable())?;
Ok(Reader {
mem,
regions: DescriptorChainRegions {
@@ -273,19 +270,7 @@ impl Reader {
/// Reads an object from the descriptor chain buffer.
pub fn read_obj<T: DataInit>(&mut self) -> io::Result<T> {
- let mut obj = MaybeUninit::<T>::uninit();
-
- // Safe because `MaybeUninit` guarantees that the pointer is valid for
- // `size_of::<T>()` bytes.
- let buf = unsafe {
- ::std::slice::from_raw_parts_mut(obj.as_mut_ptr() as *mut u8, size_of::<T>())
- };
-
- self.read_exact(buf)?;
-
- // Safe because any type that implements `DataInit` can be considered initialized
- // even if it is filled with random data.
- Ok(unsafe { obj.assume_init() })
+ T::from_reader(self)
}
/// Reads objects by consuming all the remaining data in the descriptor chain buffer and returns
@@ -305,6 +290,24 @@ impl Reader {
}
}
+ /// Reads data into a volatile slice up to the minimum of the slice's length or the number of
+ /// bytes remaining. Returns the number of bytes read.
+ pub fn read_to_volatile_slice(&mut self, slice: VolatileSlice) -> usize {
+ let mut read = 0usize;
+ let mut dst = slice;
+ for src in self.get_remaining() {
+ src.copy_to_volatile_slice(dst);
+ let copied = std::cmp::min(src.size(), dst.size());
+ read += copied;
+ dst = match dst.offset(copied) {
+ Ok(v) => v,
+ Err(_) => break, // The slice is fully consumed
+ };
+ }
+ self.regions.consume(read);
+ read
+ }
+
/// Reads data from the descriptor chain buffer into a file descriptor.
/// Returns the number of bytes read from the descriptor chain buffer.
/// The number of bytes read can be less than `count` if there isn't
@@ -505,30 +508,7 @@ pub struct Writer {
impl Writer {
/// Construct a new Writer wrapper over `desc_chain`.
pub fn new(mem: GuestMemory, desc_chain: DescriptorChain) -> Result<Writer> {
- let mut total_len: usize = 0;
- let regions = desc_chain
- .into_iter()
- .writable()
- .map(|desc| {
- // Verify that summing the descriptor sizes does not overflow.
- // This can happen if a driver tricks a device into writing more data than
- // fits in a `usize`.
- total_len = total_len
- .checked_add(desc.len as usize)
- .ok_or(Error::DescriptorChainOverflow)?;
-
- mem.get_slice_at_addr(
- desc.addr,
- desc.len.try_into().expect("u32 doesn't fit in usize"),
- )
- .map_err(Error::GuestMemoryError)?;
-
- Ok(MemRegion {
- offset: desc.addr.0,
- len: desc.len.try_into().expect("u32 doesn't fit in usize"),
- })
- })
- .collect::<Result<SmallVec<[MemRegion; 16]>>>()?;
+ let regions = get_mem_regions(&mem, desc_chain.into_iter().writable())?;
Ok(Writer {
mem,
regions: DescriptorChainRegions {
@@ -566,6 +546,24 @@ impl Writer {
self.regions.available_bytes()
}
+ /// Reads data into a volatile slice up to the minimum of the slice's length or the number of
+ /// bytes remaining. Returns the number of bytes read.
+ pub fn write_from_volatile_slice(&mut self, slice: VolatileSlice) -> usize {
+ let mut written = 0usize;
+ let mut src = slice;
+ for dst in self.get_remaining() {
+ src.copy_to_volatile_slice(dst);
+ let copied = std::cmp::min(src.size(), dst.size());
+ written += copied;
+ src = match src.offset(copied) {
+ Ok(v) => v,
+ Err(_) => break, // The slice is fully consumed
+ };
+ }
+ self.regions.consume(written);
+ written
+ }
+
/// Writes data to the descriptor chain buffer from a file descriptor.
/// Returns the number of bytes written to the descriptor chain buffer.
/// The number of bytes written can be less than `count` if
@@ -686,6 +684,21 @@ impl Writer {
self.regions.bytes_consumed()
}
+ /// Returns a `&[VolatileSlice]` that represents all the remaining data in this `Writer`.
+ /// Calling this method does not actually advance the current position of the `Writer` in the
+ /// buffer and callers should call `consume_bytes` to advance the `Writer`. Not calling
+ /// `consume_bytes` with the amount of data copied into the returned `VolatileSlice`s will
+ /// result in that that data being overwritten the next time data is written into the `Writer`.
+ pub fn get_remaining(&self) -> SmallVec<[VolatileSlice; 16]> {
+ self.regions.get_remaining(&self.mem)
+ }
+
+ /// Consumes `amt` bytes from the underlying descriptor chain. If `amt` is larger than the
+ /// remaining data left in this `Reader`, then all remaining data will be consumed.
+ pub fn consume_bytes(&mut self, amt: usize) {
+ self.regions.consume(amt)
+ }
+
/// Splits this `Writer` into two at the given offset in the `DescriptorChain` buffer. After the
/// split, `self` will be able to write up to `offset` bytes while the returned `Writer` can
/// write up to `available_bytes() - offset` bytes. If `offset > self.available_bytes()`, then
@@ -776,24 +789,27 @@ pub fn create_descriptor_chain(
let offset = size + spaces_between_regions;
buffers_start_addr = buffers_start_addr
.checked_add(offset as u64)
- .ok_or(Error::InvalidChain)?;
+ .context("Invalid buffers_start_addr)")
+ .map_err(Error::InvalidChain)?;
let _ = memory.write_obj_at_addr(
desc,
descriptor_array_addr
.checked_add(index as u64 * std::mem::size_of::<virtq_desc>() as u64)
- .ok_or(Error::InvalidChain)?,
+ .context("Invalid descriptor_array_addr")
+ .map_err(Error::InvalidChain)?,
);
}
- DescriptorChain::checked_new(memory, descriptor_array_addr, 0x100, 0, 0)
- .ok_or(Error::InvalidChain)
+ DescriptorChain::checked_new(memory, descriptor_array_addr, 0x100, 0, 0, None)
+ .map_err(Error::InvalidChain)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
+ use std::io::Read;
use tempfile::tempfile;
use cros_async::Executor;
@@ -803,7 +819,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -818,14 +834,14 @@ mod tests {
0,
)
.expect("create_descriptor_chain failed");
- let mut reader = Reader::new(memory.clone(), chain).expect("failed to create Reader");
+ let mut reader = Reader::new(memory, chain).expect("failed to create Reader");
assert_eq!(reader.available_bytes(), 106);
assert_eq!(reader.bytes_read(), 0);
- let mut buffer = [0 as u8; 64];
- if let Err(_) = reader.read_exact(&mut buffer) {
- panic!("read_exact should not fail here");
- }
+ let mut buffer = [0u8; 64];
+ reader
+ .read_exact(&mut buffer)
+ .expect("read_exact should not fail here");
assert_eq!(reader.available_bytes(), 42);
assert_eq!(reader.bytes_read(), 64);
@@ -844,7 +860,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -859,19 +875,19 @@ mod tests {
0,
)
.expect("create_descriptor_chain failed");
- let mut writer = Writer::new(memory.clone(), chain).expect("failed to create Writer");
+ let mut writer = Writer::new(memory, chain).expect("failed to create Writer");
assert_eq!(writer.available_bytes(), 106);
assert_eq!(writer.bytes_written(), 0);
- let mut buffer = [0 as u8; 64];
- if let Err(_) = writer.write_all(&mut buffer) {
- panic!("write_all should not fail here");
- }
+ let buffer = [0; 64];
+ writer
+ .write_all(&buffer)
+ .expect("write_all should not fail here");
assert_eq!(writer.available_bytes(), 42);
assert_eq!(writer.bytes_written(), 64);
- match writer.write(&mut buffer) {
+ match writer.write(&buffer) {
Err(_) => panic!("write should not fail here"),
Ok(length) => assert_eq!(length, 42),
}
@@ -885,7 +901,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -895,7 +911,7 @@ mod tests {
0,
)
.expect("create_descriptor_chain failed");
- let mut reader = Reader::new(memory.clone(), chain).expect("failed to create Reader");
+ let mut reader = Reader::new(memory, chain).expect("failed to create Reader");
assert_eq!(reader.available_bytes(), 0);
assert_eq!(reader.bytes_read(), 0);
@@ -910,7 +926,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -920,7 +936,7 @@ mod tests {
0,
)
.expect("create_descriptor_chain failed");
- let mut writer = Writer::new(memory.clone(), chain).expect("failed to create Writer");
+ let mut writer = Writer::new(memory, chain).expect("failed to create Writer");
assert_eq!(writer.available_bytes(), 0);
assert_eq!(writer.bytes_written(), 0);
@@ -935,7 +951,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -946,7 +962,7 @@ mod tests {
)
.expect("create_descriptor_chain failed");
- let mut reader = Reader::new(memory.clone(), chain).expect("failed to create Reader");
+ let mut reader = Reader::new(memory, chain).expect("failed to create Reader");
// Open a file in read-only mode so writes to it to trigger an I/O error.
let mut ro_file = File::open("/dev/zero").expect("failed to open /dev/zero");
@@ -965,7 +981,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -976,7 +992,7 @@ mod tests {
)
.expect("create_descriptor_chain failed");
- let mut writer = Writer::new(memory.clone(), chain).expect("failed to create Writer");
+ let mut writer = Writer::new(memory, chain).expect("failed to create Writer");
let mut file = tempfile().unwrap();
@@ -995,7 +1011,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -1014,7 +1030,7 @@ mod tests {
.expect("create_descriptor_chain failed");
let mut reader =
Reader::new(memory.clone(), chain.clone()).expect("failed to create Reader");
- let mut writer = Writer::new(memory.clone(), chain).expect("failed to create Writer");
+ let mut writer = Writer::new(memory, chain).expect("failed to create Writer");
assert_eq!(reader.bytes_read(), 0);
assert_eq!(writer.bytes_written(), 0);
@@ -1044,7 +1060,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let secret: Le32 = 0x12345678.into();
@@ -1059,9 +1075,9 @@ mod tests {
.expect("create_descriptor_chain failed");
let mut writer =
Writer::new(memory.clone(), chain_writer).expect("failed to create Writer");
- if let Err(_) = writer.write_obj(secret) {
- panic!("write_obj should not fail here");
- }
+ writer
+ .write_obj(secret)
+ .expect("write_obj should not fail here");
// Now create new descriptor chain pointing to the same memory and try to read it.
let chain_reader = create_descriptor_chain(
@@ -1072,8 +1088,7 @@ mod tests {
123,
)
.expect("create_descriptor_chain failed");
- let mut reader =
- Reader::new(memory.clone(), chain_reader).expect("failed to create Reader");
+ let mut reader = Reader::new(memory, chain_reader).expect("failed to create Reader");
match reader.read_obj::<Le32>() {
Err(_) => panic!("read_obj should not fail here"),
Ok(read_secret) => assert_eq!(read_secret, secret),
@@ -1085,7 +1100,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -1096,10 +1111,9 @@ mod tests {
)
.expect("create_descriptor_chain failed");
- let mut reader = Reader::new(memory.clone(), chain).expect("failed to create Reader");
+ let mut reader = Reader::new(memory, chain).expect("failed to create Reader");
- let mut buf = Vec::with_capacity(1024);
- buf.resize(1024, 0);
+ let mut buf = vec![0; 1024];
assert_eq!(
reader
@@ -1115,7 +1129,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -1132,7 +1146,7 @@ mod tests {
0,
)
.expect("create_descriptor_chain failed");
- let mut reader = Reader::new(memory.clone(), chain).expect("failed to create Reader");
+ let mut reader = Reader::new(memory, chain).expect("failed to create Reader");
let other = reader.split_at(32);
assert_eq!(reader.available_bytes(), 32);
@@ -1144,7 +1158,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -1161,7 +1175,7 @@ mod tests {
0,
)
.expect("create_descriptor_chain failed");
- let mut reader = Reader::new(memory.clone(), chain).expect("failed to create Reader");
+ let mut reader = Reader::new(memory, chain).expect("failed to create Reader");
let other = reader.split_at(24);
assert_eq!(reader.available_bytes(), 24);
@@ -1173,7 +1187,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -1190,7 +1204,7 @@ mod tests {
0,
)
.expect("create_descriptor_chain failed");
- let mut reader = Reader::new(memory.clone(), chain).expect("failed to create Reader");
+ let mut reader = Reader::new(memory, chain).expect("failed to create Reader");
let other = reader.split_at(128);
assert_eq!(reader.available_bytes(), 128);
@@ -1202,7 +1216,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -1219,7 +1233,7 @@ mod tests {
0,
)
.expect("create_descriptor_chain failed");
- let mut reader = Reader::new(memory.clone(), chain).expect("failed to create Reader");
+ let mut reader = Reader::new(memory, chain).expect("failed to create Reader");
let other = reader.split_at(0);
assert_eq!(reader.available_bytes(), 0);
@@ -1231,7 +1245,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -1248,7 +1262,7 @@ mod tests {
0,
)
.expect("create_descriptor_chain failed");
- let mut reader = Reader::new(memory.clone(), chain).expect("failed to create Reader");
+ let mut reader = Reader::new(memory, chain).expect("failed to create Reader");
let other = reader.split_at(256);
assert_eq!(
@@ -1263,7 +1277,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -1273,7 +1287,7 @@ mod tests {
0,
)
.expect("create_descriptor_chain failed");
- let mut reader = Reader::new(memory.clone(), chain).expect("failed to create Reader");
+ let mut reader = Reader::new(memory, chain).expect("failed to create Reader");
let mut buf = vec![0u8; 64];
assert_eq!(
@@ -1287,7 +1301,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -1297,7 +1311,7 @@ mod tests {
0,
)
.expect("create_descriptor_chain failed");
- let mut writer = Writer::new(memory.clone(), chain).expect("failed to create Writer");
+ let mut writer = Writer::new(memory, chain).expect("failed to create Writer");
let buf = vec![0xdeu8; 64];
assert_eq!(
@@ -1311,7 +1325,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let vs: Vec<Le64> = vec![
0x0101010101010101.into(),
0x0202020202020202.into(),
@@ -1339,7 +1353,7 @@ mod tests {
0,
)
.expect("create_descriptor_chain failed");
- let mut reader = Reader::new(memory.clone(), read_chain).expect("failed to create Reader");
+ let mut reader = Reader::new(memory, read_chain).expect("failed to create Reader");
let vs_read = reader
.collect::<io::Result<Vec<Le64>>, _>()
.expect("failed to collect() values");
@@ -1351,7 +1365,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -1372,7 +1386,7 @@ mod tests {
let Reader {
mem: _,
mut regions,
- } = Reader::new(memory.clone(), chain).expect("failed to create Reader");
+ } = Reader::new(memory, chain).expect("failed to create Reader");
let drain = regions
.get_remaining_regions_with_count(::std::usize::MAX)
@@ -1409,7 +1423,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -1470,7 +1484,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
@@ -1506,7 +1520,7 @@ mod tests {
use DescriptorType::*;
let memory_start_addr = GuestAddress(0x0);
- let memory = GuestMemory::new(&vec![(memory_start_addr, 0x10000)]).unwrap();
+ let memory = GuestMemory::new(&[(memory_start_addr, 0x10000)]).unwrap();
let chain = create_descriptor_chain(
&memory,
diff --git a/devices/src/virtio/fs/mod.rs b/devices/src/virtio/fs/mod.rs
index bc8c28f01..12f7ddc98 100644
--- a/devices/src/virtio/fs/mod.rs
+++ b/devices/src/virtio/fs/mod.rs
@@ -2,15 +2,17 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fmt;
use std::io;
use std::mem;
-use std::sync::{Arc, Mutex};
+use std::sync::Arc;
use std::thread;
use base::{error, warn, AsRawDescriptor, Error as SysError, Event, RawDescriptor, Tube};
use data_model::{DataInit, Le32};
+use remain::sorted;
use resources::Alloc;
+use sync::Mutex;
+use thiserror::Error;
use vm_control::{FsMappingRequest, VmResponse};
use vm_memory::GuestMemory;
@@ -32,6 +34,8 @@ use fuse::Server;
use passthrough::PassthroughFs;
use worker::Worker;
+pub use worker::process_fs_queue;
+
// The fs device does not have a fixed number of queues.
pub const QUEUE_SIZE: u16 = 1024;
@@ -48,7 +52,7 @@ pub const FS_MAX_TAG_LEN: usize = 36;
/// kernel/include/uapi/linux/virtio_fs.h
#[repr(C, packed)]
#[derive(Clone, Copy)]
-pub(crate) struct virtio_fs_config {
+pub struct virtio_fs_config {
/// Filesystem name (UTF-8, not NUL-terminated, padded with NULs)
pub tag: [u8; FS_MAX_TAG_LEN],
/// Number of request queues
@@ -59,70 +63,56 @@ pub(crate) struct virtio_fs_config {
unsafe impl DataInit for virtio_fs_config {}
/// Errors that may occur during the creation or operation of an Fs device.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- /// The tag for the Fs device was too long to fit in the config space.
- TagTooLong(usize),
/// Failed to create the file system.
+ #[error("failed to create file system: {0}")]
CreateFs(io::Error),
/// Creating WaitContext failed.
+ #[error("failed to create WaitContext: {0}")]
CreateWaitContext(SysError),
- /// Error while polling for events.
- WaitError(SysError),
- /// Error while reading from the virtio queue's Event.
- ReadQueueEvent(SysError),
+ /// Error happened in FUSE.
+ #[error("fuse error: {0}")]
+ FuseError(fuse::Error),
+ /// Failed to get the securebits for the worker thread.
+ #[error("failed to get securebits for the worker thread: {0}")]
+ GetSecurebits(SysError),
+ /// The `len` field of the header is too small.
+ #[error("DescriptorChain is invalid: {0}")]
+ InvalidDescriptorChain(DescriptorError),
/// A request is missing readable descriptors.
+ #[error("request does not have any readable descriptors")]
NoReadableDescriptors,
/// A request is missing writable descriptors.
+ #[error("request does not have any writable descriptors")]
NoWritableDescriptors,
+ /// Error while reading from the virtio queue's Event.
+ #[error("failed to read from virtio queue Event: {0}")]
+ ReadQueueEvent(SysError),
+ /// Failed to set the securebits for the worker thread.
+ #[error("failed to set securebits for the worker thread: {0}")]
+ SetSecurebits(SysError),
/// Failed to signal the virio used queue.
+ #[error("failed to signal used queue: {0}")]
SignalUsedQueue(SysError),
- /// The `len` field of the header is too small.
- InvalidDescriptorChain(DescriptorError),
- /// Error happened in FUSE.
- FuseError(fuse::Error),
- /// Failed to get the securebits for the worker thread.
- GetSecurebits(io::Error),
- /// Failed to set the securebits for the worker thread.
- SetSecurebits(io::Error),
+ /// The tag for the Fs device was too long to fit in the config space.
+ #[error("Fs device tag is too long: len = {0}, max = {}", FS_MAX_TAG_LEN)]
+ TagTooLong(usize),
+ /// Calling unshare to disassociate FS attributes from parent failed.
+ #[error("failed to unshare fs from parent: {0}")]
+ UnshareFromParent(SysError),
+ /// Error while polling for events.
+ #[error("failed to wait for events: {0}")]
+ WaitError(SysError),
}
-impl ::std::error::Error for Error {}
-
impl From<fuse::Error> for Error {
fn from(err: fuse::Error) -> Error {
Error::FuseError(err)
}
}
-impl fmt::Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use Error::*;
- match self {
- TagTooLong(len) => write!(
- f,
- "Fs device tag is too long: len = {}, max = {}",
- len, FS_MAX_TAG_LEN
- ),
- CreateFs(err) => write!(f, "failed to create file system: {}", err),
- CreateWaitContext(err) => write!(f, "failed to create WaitContext: {}", err),
- WaitError(err) => write!(f, "failed to wait for events: {}", err),
- ReadQueueEvent(err) => write!(f, "failed to read from virtio queue Event: {}", err),
- NoReadableDescriptors => write!(f, "request does not have any readable descriptors"),
- NoWritableDescriptors => write!(f, "request does not have any writable descriptors"),
- SignalUsedQueue(err) => write!(f, "failed to signal used queue: {}", err),
- InvalidDescriptorChain(err) => write!(f, "DescriptorChain is invalid: {}", err),
- FuseError(err) => write!(f, "fuse error: {}", err),
- GetSecurebits(err) => {
- write!(f, "failed to get securebits for the worker thread: {}", err)
- }
- SetSecurebits(err) => {
- write!(f, "failed to set securebits for the worker thread: {}", err)
- }
- }
- }
-}
-
pub type Result<T> = ::std::result::Result<T, Error>;
pub struct Fs {
@@ -174,7 +164,7 @@ impl Fs {
}
fn stop_workers(&mut self) {
- for (kill_evt, handle) in mem::replace(&mut self.workers, Vec::new()) {
+ for (kill_evt, handle) in mem::take(&mut self.workers) {
if let Err(e) = kill_evt.write(1) {
error!("failed to kill virtio-fs worker thread: {}", e);
continue;
@@ -247,6 +237,7 @@ impl VirtioDevice for Fs {
}
let fs = self.fs.take().expect("missing file system implementation");
+ let use_dax = fs.cfg().use_dax;
let server = Arc::new(Server::new(fs));
let irq = Arc::new(interrupt);
@@ -255,7 +246,7 @@ impl VirtioDevice for Fs {
// Set up shared memory for DAX.
// TODO(b/176129399): Remove cfg! once DAX is supported on ARM.
- if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
+ if cfg!(any(target_arch = "x86", target_arch = "x86_64")) && use_dax {
// Create the shared memory region now before we start processing requests.
let request = FsMappingRequest::AllocateSharedMemoryRegion(
self.pci_bar.as_ref().cloned().expect("No pci_bar"),
@@ -314,6 +305,10 @@ impl VirtioDevice for Fs {
}
fn get_device_bars(&mut self, address: PciAddress) -> Vec<PciBarConfiguration> {
+ if self.fs.as_ref().map_or(false, |fs| !fs.cfg().use_dax) {
+ return vec![];
+ }
+
self.pci_bar = Some(Alloc::PciBar {
bus: address.bus,
dev: address.dev,
@@ -330,6 +325,10 @@ impl VirtioDevice for Fs {
}
fn get_device_caps(&self) -> Vec<Box<dyn PciCapability>> {
+ if self.fs.as_ref().map_or(false, |fs| !fs.cfg().use_dax) {
+ return vec![];
+ }
+
vec![Box::new(VirtioPciShmCap::new(
PciCapabilityType::SharedMemoryConfig,
FS_BAR_NUM,
diff --git a/devices/src/virtio/fs/passthrough.rs b/devices/src/virtio/fs/passthrough.rs
index dfeee627a..845bd98a5 100644
--- a/devices/src/virtio/fs/passthrough.rs
+++ b/devices/src/virtio/fs/passthrough.rs
@@ -2,23 +2,27 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::borrow::Cow;
-use std::cmp;
-use std::collections::btree_map;
-use std::collections::BTreeMap;
-use std::ffi::{CStr, CString};
-use std::fs::File;
-use std::io;
-use std::mem::{self, size_of, MaybeUninit};
-use std::os::raw::{c_int, c_long};
-use std::str::FromStr;
-use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
-use std::sync::Arc;
-use std::time::Duration;
+use std::{
+ borrow::Cow,
+ cmp,
+ collections::{btree_map, BTreeMap},
+ ffi::{CStr, CString},
+ fs::File,
+ io,
+ mem::{self, size_of, MaybeUninit},
+ os::raw::{c_int, c_long},
+ ptr::{addr_of, addr_of_mut},
+ str::FromStr,
+ sync::{
+ atomic::{AtomicBool, AtomicU64, Ordering},
+ Arc,
+ },
+ time::Duration,
+};
use base::{
- error, ioctl_ior_nr, ioctl_iow_nr, ioctl_iowr_nr, ioctl_with_mut_ptr, ioctl_with_ptr,
- AsRawDescriptor, FromRawDescriptor, RawDescriptor,
+ error, ioctl_ior_nr, ioctl_iow_nr, ioctl_iowr_nr, ioctl_with_mut_ptr, ioctl_with_ptr, syscall,
+ AsRawDescriptor, FileFlags, FromRawDescriptor, RawDescriptor,
};
use data_model::DataInit;
use fuse::filesystem::{
@@ -30,6 +34,15 @@ use fuse::sys::WRITE_KILL_PRIV;
use fuse::Mapper;
use sync::Mutex;
+#[cfg(feature = "chromeos")]
+use {
+ protobuf::Message,
+ system_api::client::OrgChromiumArcQuota,
+ system_api::UserDataAuth::{
+ SetMediaRWDataFileProjectIdReply, SetMediaRWDataFileProjectIdRequest,
+ },
+};
+
use crate::virtio::fs::caps::{Capability, Caps, Set as CapSet, Value as CapValue};
use crate::virtio::fs::multikey::MultikeyBTreeMap;
use crate::virtio::fs::read_dir::ReadDir;
@@ -45,6 +58,10 @@ const SELINUX_XATTR: &[u8] = b"security.selinux";
const FSCRYPT_KEY_DESCRIPTOR_SIZE: usize = 8;
const FSCRYPT_KEY_IDENTIFIER_SIZE: usize = 16;
+// 25 seconds is the default timeout for dbus-send.
+#[cfg(feature = "chromeos")]
+const DEFAULT_DBUS_TIMEOUT: Duration = Duration::from_secs(25);
+
#[repr(C)]
#[derive(Clone, Copy)]
struct fscrypt_policy_v1 {
@@ -90,12 +107,12 @@ ioctl_iowr_nr!(FS_IOC_GET_ENCRYPTION_POLICY_EX, 'f' as u32, 22, [u8; 9]);
#[repr(C)]
#[derive(Clone, Copy)]
struct fsxattr {
- _fsx_xflags: u32, /* xflags field value (get/set) */
- _fsx_extsize: u32, /* extsize field value (get/set)*/
- _fsx_nextents: u32, /* nextents field value (get) */
- _fsx_projid: u32, /* project identifier (get/set) */
- _fsx_cowextsize: u32, /* CoW extsize field value (get/set)*/
- _fsx_pad: [u8; 8],
+ fsx_xflags: u32, /* xflags field value (get/set) */
+ fsx_extsize: u32, /* extsize field value (get/set)*/
+ fsx_nextents: u32, /* nextents field value (get) */
+ fsx_projid: u32, /* project identifier (get/set) */
+ fsx_cowextsize: u32, /* CoW extsize field value (get/set)*/
+ fsx_pad: [u8; 8],
}
unsafe impl DataInit for fsxattr {}
@@ -111,6 +128,33 @@ ioctl_iow_nr!(FS_IOC32_SETFLAGS, 'f' as u32, 2, u32);
ioctl_ior_nr!(FS_IOC64_GETFLAGS, 'f' as u32, 1, u64);
ioctl_iow_nr!(FS_IOC64_SETFLAGS, 'f' as u32, 2, u64);
+#[repr(C)]
+#[derive(Clone, Copy)]
+struct fsverity_enable_arg {
+ _version: u32,
+ _hash_algorithm: u32,
+ _block_size: u32,
+ salt_size: u32,
+ salt_ptr: u64,
+ sig_size: u32,
+ __reserved1: u32,
+ sig_ptr: u64,
+ __reserved2: [u64; 11],
+}
+unsafe impl DataInit for fsverity_enable_arg {}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+struct fsverity_digest {
+ _digest_algorithm: u16,
+ digest_size: u16,
+ // __u8 digest[];
+}
+unsafe impl DataInit for fsverity_digest {}
+
+ioctl_iow_nr!(FS_IOC_ENABLE_VERITY, 'f' as u32, 133, fsverity_enable_arg);
+ioctl_iowr_nr!(FS_IOC_MEASURE_VERITY, 'f' as u32, 134, fsverity_digest);
+
type Inode = u64;
type Handle = u64;
@@ -254,13 +298,22 @@ fn set_creds(
ScopedGid::new(gid, oldgid).and_then(|gid| Ok((ScopedUid::new(uid, olduid)?, gid)))
}
-struct ScopedUmask<'a> {
+struct ScopedUmask {
old: libc::mode_t,
mask: libc::mode_t,
- _factory: &'a mut Umask,
}
-impl<'a> Drop for ScopedUmask<'a> {
+impl ScopedUmask {
+ fn new(mask: libc::mode_t) -> ScopedUmask {
+ ScopedUmask {
+ // Safe because this doesn't modify any memory and always succeeds.
+ old: unsafe { libc::umask(mask) },
+ mask,
+ }
+ }
+}
+
+impl Drop for ScopedUmask {
fn drop(&mut self) {
// Safe because this doesn't modify any memory and always succeeds.
let previous = unsafe { libc::umask(self.old) };
@@ -271,19 +324,6 @@ impl<'a> Drop for ScopedUmask<'a> {
}
}
-struct Umask;
-
-impl Umask {
- fn set(&mut self, mask: libc::mode_t) -> ScopedUmask {
- ScopedUmask {
- // Safe because this doesn't modify any memory and always succeeds.
- old: unsafe { libc::umask(mask) },
- mask,
- _factory: self,
- }
- }
-}
-
struct ScopedFsetid(Caps);
impl Drop for ScopedFsetid {
fn drop(&mut self) {
@@ -314,7 +354,7 @@ fn ebadf() -> io::Error {
io::Error::from_raw_os_error(libc::EBADF)
}
-fn stat<F: AsRawDescriptor>(f: &F) -> io::Result<libc::stat64> {
+fn stat<F: AsRawDescriptor + ?Sized>(f: &F) -> io::Result<libc::stat64> {
let mut st = MaybeUninit::<libc::stat64>::zeroed();
// Safe because this is a constant value and a valid C string.
@@ -322,20 +362,17 @@ fn stat<F: AsRawDescriptor>(f: &F) -> io::Result<libc::stat64> {
// Safe because the kernel will only write data in `st` and we check the return
// value.
- let res = unsafe {
+ syscall!(unsafe {
libc::fstatat64(
f.as_raw_descriptor(),
pathname.as_ptr(),
st.as_mut_ptr(),
libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW,
)
- };
- if res >= 0 {
- // Safe because the kernel guarantees that the struct is now fully initialized.
- Ok(unsafe { st.assume_init() })
- } else {
- Err(io::Error::last_os_error())
- }
+ })?;
+
+ // Safe because the kernel guarantees that the struct is now fully initialized.
+ Ok(unsafe { st.assume_init() })
}
fn statat<D: AsRawDescriptor>(dir: &D, name: &CStr) -> io::Result<libc::stat64> {
@@ -343,20 +380,17 @@ fn statat<D: AsRawDescriptor>(dir: &D, name: &CStr) -> io::Result<libc::stat64>
// Safe because the kernel will only write data in `st` and we check the return
// value.
- let res = unsafe {
+ syscall!(unsafe {
libc::fstatat64(
dir.as_raw_descriptor(),
name.as_ptr(),
st.as_mut_ptr(),
libc::AT_SYMLINK_NOFOLLOW,
)
- };
- if res >= 0 {
- // Safe because the kernel guarantees that the struct is now fully initialized.
- Ok(unsafe { st.assume_init() })
- } else {
- Err(io::Error::last_os_error())
- }
+ })?;
+
+ // Safe because the kernel guarantees that the struct is now fully initialized.
+ Ok(unsafe { st.assume_init() })
}
/// The caching policy that the file system should report to the FUSE client. By default the FUSE
@@ -448,6 +482,32 @@ pub struct Config {
///
/// The default value for this option is `false`.
pub ascii_casefold: bool,
+
+ // UIDs which are privileged to perform quota-related operations. We cannot perform a CAP_FOWNER
+ // check so we consult this list when the VM tries to set the project quota and the process uid
+ // doesn't match the owner uid. In that case, all uids in this list are treated as if they have
+ // CAP_FOWNER.
+ #[cfg(feature = "chromeos")]
+ pub privileged_quota_uids: Vec<libc::uid_t>,
+
+ /// Use DAX for shared files.
+ ///
+ /// Enabling DAX can improve performance for frequently accessed files by mapping regions of the
+ /// file directly into the VM's memory region, allowing direct access with the cost of slightly
+ /// increased latency the first time the file is accessed. Additionally, since the mapping is
+ /// shared directly from the host kernel's file cache, enabling DAX can improve performance even
+ /// when the cache policy is `Never`.
+ ///
+ /// The default value for this option is `false`.
+ pub use_dax: bool,
+
+ /// Enable support for POSIX acls.
+ ///
+ /// Enable POSIX acl support for the shared directory. This requires that the underlying file
+ /// system also supports POSIX acls.
+ ///
+ /// The default value for this option is `true`.
+ pub posix_acl: bool,
}
impl Default for Config {
@@ -459,6 +519,10 @@ impl Default for Config {
writeback: false,
rewrite_security_xattrs: false,
ascii_casefold: false,
+ #[cfg(feature = "chromeos")]
+ privileged_quota_uids: Default::default(),
+ use_dax: false,
+ posix_acl: true,
}
}
}
@@ -494,13 +558,11 @@ pub struct PassthroughFs {
// Whether zero message opendir is supported by the kernel driver.
zero_message_opendir: AtomicBool,
- // Used to ensure that only one thread at a time uses chdir(). Since chdir() affects the
- // process-wide CWD, we cannot allow more than one thread to do it at the same time.
- chdir_mutex: Mutex<()>,
-
- // Used when creating files / directories / nodes. Since the umask is process-wide, we can only
- // allow one thread at a time to change it.
- umask: Mutex<Umask>,
+ // Used to communicate with other processes using D-Bus.
+ #[cfg(feature = "chromeos")]
+ dbus_connection: Option<Mutex<dbus::blocking::Connection>>,
+ #[cfg(feature = "chromeos")]
+ dbus_fd: Option<std::os::unix::io::RawFd>,
cfg: Config,
}
@@ -511,16 +573,29 @@ impl PassthroughFs {
let proc_cstr = unsafe { CStr::from_bytes_with_nul_unchecked(PROC_CSTR) };
// Safe because this doesn't modify any memory and we check the return value.
- let raw_descriptor = unsafe {
- libc::openat(
+ let raw_descriptor = syscall!(unsafe {
+ libc::openat64(
libc::AT_FDCWD,
proc_cstr.as_ptr(),
libc::O_PATH | libc::O_NOFOLLOW | libc::O_CLOEXEC,
)
+ })?;
+
+ // Privileged UIDs can use D-Bus to perform some operations.
+ #[cfg(feature = "chromeos")]
+ let (dbus_connection, dbus_fd) = if cfg.privileged_quota_uids.is_empty() {
+ (None, None)
+ } else {
+ let mut channel = dbus::channel::Channel::get_private(dbus::channel::BusType::System)
+ .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
+ channel.set_watch_enabled(true);
+ let dbus_fd = channel.watch().fd;
+ channel.set_watch_enabled(false);
+ (
+ Some(Mutex::new(dbus::blocking::Connection::from(channel))),
+ Some(dbus_fd),
+ )
};
- if raw_descriptor < 0 {
- return Err(io::Error::last_os_error());
- }
// Safe because we just opened this descriptor.
let proc = unsafe { File::from_raw_descriptor(raw_descriptor) };
@@ -538,14 +613,27 @@ impl PassthroughFs {
zero_message_open: AtomicBool::new(false),
zero_message_opendir: AtomicBool::new(false),
- chdir_mutex: Mutex::new(()),
- umask: Mutex::new(Umask),
+ #[cfg(feature = "chromeos")]
+ dbus_connection,
+ #[cfg(feature = "chromeos")]
+ dbus_fd,
+
cfg,
})
}
+ pub fn cfg(&self) -> &Config {
+ &self.cfg
+ }
+
pub fn keep_rds(&self) -> Vec<RawDescriptor> {
- vec![self.proc.as_raw_descriptor()]
+ #[cfg_attr(not(feature = "chromeos"), allow(unused_mut))]
+ let mut keep_rds = vec![self.proc.as_raw_descriptor()];
+ #[cfg(feature = "chromeos")]
+ if let Some(fd) = self.dbus_fd {
+ keep_rds.push(fd);
+ }
+ keep_rds
}
fn rewrite_xattr_name<'xattr>(&self, name: &'xattr CStr) -> Cow<'xattr, CStr> {
@@ -592,16 +680,13 @@ impl PassthroughFs {
// really check `flags` because if the kernel can't handle poorly specified flags then we
// have much bigger problems. Also, clear the `O_NOFOLLOW` flag if it is set since we need
// to follow the `/proc/self/fd` symlink to get the file.
- let raw_descriptor = unsafe {
- libc::openat(
+ let raw_descriptor = syscall!(unsafe {
+ libc::openat64(
self.proc.as_raw_descriptor(),
pathname.as_ptr(),
(flags | libc::O_CLOEXEC) & !(libc::O_NOFOLLOW | libc::O_DIRECT),
)
- };
- if raw_descriptor < 0 {
- return Err(io::Error::last_os_error());
- }
+ })?;
// Safe because we just opened this descriptor.
Ok(unsafe { File::from_raw_descriptor(raw_descriptor) })
@@ -704,13 +789,13 @@ impl PassthroughFs {
}
// Safe because this doesn't modify any memory and we check the return value.
- let fd = unsafe { libc::openat(parent.as_raw_descriptor(), name.as_ptr(), flags) };
- if fd < 0 {
- return Err(io::Error::last_os_error());
- }
-
- // Safe because we just opened this fd.
- let f = unsafe { File::from_raw_descriptor(fd) };
+ let f = unsafe {
+ File::from_raw_descriptor(syscall!(libc::openat64(
+ parent.as_raw_descriptor(),
+ name.as_ptr(),
+ flags
+ ))?)
+ };
Ok(self.add_entry(f, st, flags))
}
@@ -768,29 +853,21 @@ impl PassthroughFs {
fn do_unlink(&self, parent: &InodeData, name: &CStr, flags: libc::c_int) -> io::Result<()> {
// Safe because this doesn't modify any memory and we check the return value.
- let res = unsafe { libc::unlinkat(parent.as_raw_descriptor(), name.as_ptr(), flags) };
- if res == 0 {
- Ok(())
- } else {
- Err(io::Error::last_os_error())
- }
+ syscall!(unsafe { libc::unlinkat(parent.as_raw_descriptor(), name.as_ptr(), flags) })?;
+ Ok(())
}
fn do_fsync<F: AsRawDescriptor>(&self, file: &F, datasync: bool) -> io::Result<()> {
// Safe because this doesn't modify any memory and we check the return value.
- let res = unsafe {
+ syscall!(unsafe {
if datasync {
libc::fdatasync(file.as_raw_descriptor())
} else {
libc::fsync(file.as_raw_descriptor())
}
- };
+ })?;
- if res == 0 {
- Ok(())
- } else {
- Err(io::Error::last_os_error())
- }
+ Ok(())
}
// Changes the CWD to `self.proc`, runs `f`, and then changes the CWD back to the root
@@ -802,7 +879,6 @@ impl PassthroughFs {
F: FnOnce() -> T,
{
let root = self.find_inode(ROOT_ID).expect("failed to find root inode");
- let chdir_lock = self.chdir_mutex.lock();
// Safe because this doesn't modify any memory and we check the return value. Since the
// fchdir should never fail we just use debug_asserts.
@@ -826,7 +902,6 @@ impl PassthroughFs {
io::Error::last_os_error()
);
- mem::drop(chdir_lock);
res
}
@@ -919,6 +994,7 @@ impl PassthroughFs {
fn set_fsxattr<R: io::Read>(
&self,
+ #[cfg_attr(not(feature = "chromeos"), allow(unused_variables))] ctx: Context,
inode: Inode,
handle: Handle,
r: R,
@@ -929,10 +1005,57 @@ impl PassthroughFs {
self.find_handle(handle, inode)?
};
- let attr = fsxattr::from_reader(r)?;
+ let in_attr = fsxattr::from_reader(r)?;
+
+ #[cfg(feature = "chromeos")]
+ let st = stat(&*data)?;
+
+ // Changing quota project ID requires CAP_FOWNER or being file owner.
+ // Here we use privileged_quota_uids because we cannot perform a CAP_FOWNER check.
+ #[cfg(feature = "chromeos")]
+ if ctx.uid == st.st_uid || self.cfg.privileged_quota_uids.contains(&ctx.uid) {
+ // Get the current fsxattr.
+ let mut buf = MaybeUninit::<fsxattr>::zeroed();
+ // Safe because the kernel will only write to `buf` and we check the return value.
+ let res = unsafe { ioctl_with_mut_ptr(&*data, FS_IOC_FSGETXATTR(), buf.as_mut_ptr()) };
+ if res < 0 {
+ return Ok(IoctlReply::Done(Err(io::Error::last_os_error())));
+ }
+ // Safe because the kernel guarantees that the policy is now initialized.
+ let current_attr = unsafe { buf.assume_init() };
+
+ // Project ID cannot be changed inside a user namespace.
+ // Use UserDataAuth to avoid this restriction.
+ if current_attr.fsx_projid != in_attr.fsx_projid {
+ let connection = self.dbus_connection.as_ref().unwrap().lock();
+ let proxy = connection.with_proxy(
+ "org.chromium.UserDataAuth",
+ "/org/chromium/UserDataAuth",
+ DEFAULT_DBUS_TIMEOUT,
+ );
+ let mut proto: SetMediaRWDataFileProjectIdRequest = Message::new();
+ proto.project_id = in_attr.fsx_projid;
+ // Safe because data is a valid file descriptor.
+ let fd = unsafe { dbus::arg::OwnedFd::new(base::clone_descriptor(&*data)?) };
+ match proxy.set_media_rwdata_file_project_id(fd, proto.write_to_bytes().unwrap()) {
+ Ok(r) => {
+ let r = protobuf::parse_from_bytes::<SetMediaRWDataFileProjectIdReply>(&r)
+ .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
+ if !r.success {
+ return Ok(IoctlReply::Done(Err(io::Error::from_raw_os_error(
+ r.error,
+ ))));
+ }
+ }
+ Err(e) => {
+ return Err(io::Error::new(io::ErrorKind::Other, e));
+ }
+ };
+ }
+ }
// Safe because this doesn't modify any memory and we check the return value.
- let res = unsafe { ioctl_with_ptr(&*data, FS_IOC_FSSETXATTR(), &attr) };
+ let res = unsafe { ioctl_with_ptr(&*data, FS_IOC_FSSETXATTR(), &in_attr) };
if res < 0 {
Ok(IoctlReply::Done(Err(io::Error::last_os_error())))
} else {
@@ -977,6 +1100,170 @@ impl PassthroughFs {
Ok(IoctlReply::Done(Ok(Vec::new())))
}
}
+
+ fn enable_verity<R: io::Read>(
+ &self,
+ inode: Inode,
+ handle: Handle,
+ mut r: R,
+ ) -> io::Result<IoctlReply> {
+ let inode_data = self.find_inode(inode)?;
+
+ // These match the return codes from `fsverity_ioctl_enable` in the kernel.
+ match inode_data.filetype {
+ FileType::Regular => {}
+ FileType::Directory => return Err(io::Error::from_raw_os_error(libc::EISDIR)),
+ FileType::Other => return Err(io::Error::from_raw_os_error(libc::EINVAL)),
+ }
+
+ {
+ // We cannot enable verity while holding a writable fd so get a new one, if necessary.
+ let mut file = inode_data.file.lock();
+ let mut flags = file.1;
+ match flags & libc::O_ACCMODE {
+ libc::O_WRONLY | libc::O_RDWR => {
+ flags &= !libc::O_ACCMODE;
+ flags |= libc::O_RDONLY;
+
+ // We need to get a read-only handle for this file.
+ let newfile = self.open_fd(file.0.as_raw_descriptor(), libc::O_RDONLY)?;
+ *file = (newfile, flags);
+ }
+ libc::O_RDONLY => {}
+ _ => panic!("Unexpected flags: {:#x}", flags),
+ }
+ }
+
+ let data: Arc<dyn AsRawDescriptor> = if self.zero_message_open.load(Ordering::Relaxed) {
+ inode_data
+ } else {
+ let data = self.find_handle(handle, inode)?;
+
+ {
+ // We can't enable verity while holding a writable fd. We don't know whether the file
+ // was opened for writing so check it here. We don't expect this to be a frequent
+ // operation so the extra latency should be fine.
+ let mut file = data.file.lock();
+ let flags = FileFlags::from_file(&*file).map_err(io::Error::from)?;
+ match flags {
+ FileFlags::ReadWrite | FileFlags::Write => {
+ // We need to get a read-only handle for this file.
+ *file = self.open_fd(file.as_raw_descriptor(), libc::O_RDONLY)?;
+ }
+ FileFlags::Read => {}
+ }
+ }
+
+ data
+ };
+
+ let mut arg = fsverity_enable_arg::from_reader(&mut r)?;
+
+ let mut salt;
+ if arg.salt_size > 0 {
+ if arg.salt_size > self.max_buffer_size() {
+ return Ok(IoctlReply::Done(Err(io::Error::from_raw_os_error(
+ libc::ENOMEM,
+ ))));
+ }
+ salt = vec![0; arg.salt_size as usize];
+ r.read_exact(&mut salt)?;
+ arg.salt_ptr = salt.as_ptr() as usize as u64;
+ } else {
+ arg.salt_ptr = 0;
+ }
+
+ let mut sig;
+ if arg.sig_size > 0 {
+ if arg.sig_size > self.max_buffer_size() {
+ return Ok(IoctlReply::Done(Err(io::Error::from_raw_os_error(
+ libc::ENOMEM,
+ ))));
+ }
+ sig = vec![0; arg.sig_size as usize];
+ r.read_exact(&mut sig)?;
+ arg.sig_ptr = sig.as_ptr() as usize as u64;
+ } else {
+ arg.sig_ptr = 0;
+ }
+
+ // Safe because this doesn't modify any memory and we check the return value.
+ let res = unsafe { ioctl_with_ptr(&*data, FS_IOC_ENABLE_VERITY(), &arg) };
+ if res < 0 {
+ Ok(IoctlReply::Done(Err(io::Error::last_os_error())))
+ } else {
+ Ok(IoctlReply::Done(Ok(Vec::new())))
+ }
+ }
+
+ fn measure_verity<R: io::Read>(
+ &self,
+ inode: Inode,
+ handle: Handle,
+ r: R,
+ out_size: u32,
+ ) -> io::Result<IoctlReply> {
+ let data: Arc<dyn AsRawDescriptor> = if self.zero_message_open.load(Ordering::Relaxed) {
+ self.find_inode(inode)?
+ } else {
+ self.find_handle(handle, inode)?
+ };
+
+ let digest = fsverity_digest::from_reader(r)?;
+
+ // Taken from fs/verity/fsverity_private.h.
+ const FS_VERITY_MAX_DIGEST_SIZE: u16 = 64;
+
+ // This digest size is what the fsverity command line utility uses.
+ const DIGEST_SIZE: u16 = FS_VERITY_MAX_DIGEST_SIZE * 2 + 1;
+ const BUFLEN: usize = size_of::<fsverity_digest>() + DIGEST_SIZE as usize;
+ const ROUNDED_LEN: usize =
+ (BUFLEN + size_of::<fsverity_digest>() - 1) / size_of::<fsverity_digest>();
+
+ // Make sure we get a properly aligned allocation.
+ let mut buf = [MaybeUninit::<fsverity_digest>::uninit(); ROUNDED_LEN];
+
+ // Safe because we are only writing data and not reading uninitialized memory.
+ unsafe {
+ // TODO: Replace with `MaybeUninit::slice_as_mut_ptr` once it is stabilized.
+ addr_of_mut!((*(buf.as_mut_ptr() as *mut fsverity_digest)).digest_size)
+ .write(DIGEST_SIZE)
+ };
+
+ // Safe because this will only modify `buf` and we check the return value.
+ let res = unsafe { ioctl_with_mut_ptr(&*data, FS_IOC_MEASURE_VERITY(), buf.as_mut_ptr()) };
+ if res < 0 {
+ Ok(IoctlReply::Done(Err(io::Error::last_os_error())))
+ } else {
+ // Safe because this value was initialized by us already and then overwritten by the
+ // kernel.
+ // TODO: Replace with `MaybeUninit::slice_as_ptr` once it is stabilized.
+ let digest_size =
+ unsafe { addr_of!((*(buf.as_ptr() as *const fsverity_digest)).digest_size).read() };
+ let outlen = size_of::<fsverity_digest>() as u32 + u32::from(digest_size);
+
+ // The kernel guarantees this but it doesn't hurt to be paranoid.
+ debug_assert!(outlen <= (ROUNDED_LEN * size_of::<fsverity_digest>()) as u32);
+ if digest.digest_size < digest_size || out_size < outlen {
+ return Ok(IoctlReply::Done(Err(io::Error::from_raw_os_error(
+ libc::EOVERFLOW,
+ ))));
+ }
+
+ // Safe because any bit pattern is valid for `MaybeUninit<u8>` and `fsverity_digest`
+ // doesn't contain any references.
+ let buf: [MaybeUninit<u8>; ROUNDED_LEN * size_of::<fsverity_digest>()] =
+ unsafe { mem::transmute(buf) };
+
+ // Casting to `*const [u8]` is safe because the kernel guarantees that the first
+ // `outlen` bytes of `buf` are initialized and `MaybeUninit<u8>` is guaranteed to have
+ // the same layout as `u8`.
+ // TODO: Replace with `MaybeUninit::slice_assume_init_ref` once it is stabilized.
+ let buf =
+ unsafe { &*(&buf[..outlen as usize] as *const [MaybeUninit<u8>] as *const [u8]) };
+ Ok(IoctlReply::Done(Ok(buf.to_vec())))
+ }
+ }
}
fn forget_one(
@@ -1034,7 +1321,7 @@ fn strip_xattr_prefix(buf: &mut Vec<u8>) {
}
let mut pos = 0;
- while let Some(name) = next_cstr(&buf, pos) {
+ while let Some(name) = next_cstr(buf, pos) {
if !name.starts_with(USER_VIRTIOFS_XATTR) {
pos += name.len();
continue;
@@ -1057,7 +1344,7 @@ impl FileSystem for PassthroughFs {
let flags = libc::O_DIRECTORY | libc::O_NOFOLLOW | libc::O_CLOEXEC;
// Safe because this doesn't modify any memory and we check the return value.
- let raw_descriptor = unsafe { libc::openat(libc::AT_FDCWD, root.as_ptr(), flags) };
+ let raw_descriptor = unsafe { libc::openat64(libc::AT_FDCWD, root.as_ptr(), flags) };
if raw_descriptor < 0 {
return Err(io::Error::last_os_error());
}
@@ -1092,8 +1379,10 @@ impl FileSystem for PassthroughFs {
let mut opts = FsOptions::DO_READDIRPLUS
| FsOptions::READDIRPLUS_AUTO
| FsOptions::EXPORT_SUPPORT
- | FsOptions::DONT_MASK
- | FsOptions::POSIX_ACL;
+ | FsOptions::DONT_MASK;
+ if self.cfg.posix_acl {
+ opts |= FsOptions::POSIX_ACL;
+ }
if self.cfg.writeback && capable.contains(FsOptions::WRITEBACK_CACHE) {
opts |= FsOptions::WRITEBACK_CACHE;
self.writeback.store(true, Ordering::Relaxed);
@@ -1122,13 +1411,10 @@ impl FileSystem for PassthroughFs {
let mut out = MaybeUninit::<libc::statvfs64>::zeroed();
// Safe because this will only modify `out` and we check the return value.
- let res = unsafe { libc::fstatvfs64(data.as_raw_descriptor(), out.as_mut_ptr()) };
- if res == 0 {
- // Safe because the kernel guarantees that `out` has been initialized.
- Ok(unsafe { out.assume_init() })
- } else {
- Err(io::Error::last_os_error())
- }
+ syscall!(unsafe { libc::fstatvfs64(data.as_raw_descriptor(), out.as_mut_ptr()) })?;
+
+ // Safe because the kernel guarantees that `out` has been initialized.
+ Ok(unsafe { out.assume_init() })
}
fn lookup(&self, _ctx: Context, parent: Inode, name: &CStr) -> io::Result<Entry> {
@@ -1194,18 +1480,14 @@ impl FileSystem for PassthroughFs {
let data = self.find_inode(parent)?;
let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?;
- let res = {
- let mut um = self.umask.lock();
- let _scoped_umask = um.set(umask);
+ {
+ let _scoped_umask = ScopedUmask::new(umask);
// Safe because this doesn't modify any memory and we check the return value.
- unsafe { libc::mkdirat(data.as_raw_descriptor(), name.as_ptr(), mode) }
- };
- if res == 0 {
- self.do_lookup(&data, name)
- } else {
- Err(io::Error::last_os_error())
+ syscall!(unsafe { libc::mkdirat(data.as_raw_descriptor(), name.as_ptr(), mode) })?;
}
+
+ self.do_lookup(&data, name)
}
fn rmdir(&self, _ctx: Context, parent: Inode, name: &CStr) -> io::Result<()> {
@@ -1282,22 +1564,18 @@ impl FileSystem for PassthroughFs {
let current_dir = unsafe { CStr::from_bytes_with_nul_unchecked(b".\0") };
let fd = {
- let mut um = self.umask.lock();
- let _scoped_umask = um.set(umask);
+ let _scoped_umask = ScopedUmask::new(umask);
// Safe because this doesn't modify any memory and we check the return value.
- unsafe {
- libc::openat(
+ syscall!(unsafe {
+ libc::openat64(
data.as_raw_descriptor(),
current_dir.as_ptr(),
tmpflags,
mode,
)
- }
+ })?
};
- if fd < 0 {
- return Err(io::Error::last_os_error());
- }
// Safe because we just opened this fd.
let tmpfile = unsafe { File::from_raw_descriptor(fd) };
@@ -1323,17 +1601,15 @@ impl FileSystem for PassthroughFs {
(flags as i32 | libc::O_CREAT | libc::O_CLOEXEC | libc::O_NOFOLLOW) & !libc::O_DIRECT;
let fd = {
- let mut um = self.umask.lock();
- let _scoped_umask = um.set(umask);
+ let _scoped_umask = ScopedUmask::new(umask);
// Safe because this doesn't modify any memory and we check the return value. We don't
// really check `flags` because if the kernel can't handle poorly specified flags then
// we have much bigger problems.
- unsafe { libc::openat(data.as_raw_descriptor(), name.as_ptr(), create_flags, mode) }
+ syscall!(unsafe {
+ libc::openat64(data.as_raw_descriptor(), name.as_ptr(), create_flags, mode)
+ })?
};
- if fd < 0 {
- return Err(io::Error::last_os_error());
- }
// Safe because we just opened this fd.
let file = unsafe { File::from_raw_descriptor(fd) };
@@ -1487,17 +1763,14 @@ impl FileSystem for PassthroughFs {
if valid.contains(SetattrValid::MODE) {
// Safe because this doesn't modify any memory and we check the return value.
- let res = unsafe {
+ syscall!(unsafe {
match data {
Data::Handle(_, fd) => libc::fchmod(fd, attr.st_mode),
Data::ProcPath(ref p) => {
libc::fchmodat(self.proc.as_raw_descriptor(), p.as_ptr(), attr.st_mode, 0)
}
}
- };
- if res < 0 {
- return Err(io::Error::last_os_error());
- }
+ })?;
}
if valid.intersects(SetattrValid::UID | SetattrValid::GID) {
@@ -1518,7 +1791,7 @@ impl FileSystem for PassthroughFs {
let empty = unsafe { CStr::from_bytes_with_nul_unchecked(EMPTY_CSTR) };
// Safe because this doesn't modify any memory and we check the return value.
- let res = unsafe {
+ syscall!(unsafe {
libc::fchownat(
inode_data.as_raw_descriptor(),
empty.as_ptr(),
@@ -1526,25 +1799,19 @@ impl FileSystem for PassthroughFs {
gid,
libc::AT_EMPTY_PATH | libc::AT_SYMLINK_NOFOLLOW,
)
- };
- if res < 0 {
- return Err(io::Error::last_os_error());
- }
+ })?;
}
if valid.contains(SetattrValid::SIZE) {
// Safe because this doesn't modify any memory and we check the return value.
- let res = match data {
+ syscall!(match data {
Data::Handle(_, fd) => unsafe { libc::ftruncate64(fd, attr.st_size) },
_ => {
// There is no `ftruncateat` so we need to get a new fd and truncate it.
let f = self.open_inode(&inode_data, libc::O_NONBLOCK | libc::O_RDWR)?;
unsafe { libc::ftruncate64(f.as_raw_descriptor(), attr.st_size) }
}
- };
- if res < 0 {
- return Err(io::Error::last_os_error());
- }
+ })?;
}
if valid.intersects(SetattrValid::ATIME | SetattrValid::MTIME) {
@@ -1574,15 +1841,14 @@ impl FileSystem for PassthroughFs {
}
// Safe because this doesn't modify any memory and we check the return value.
- let res = match data {
- Data::Handle(_, fd) => unsafe { libc::futimens(fd, tvs.as_ptr()) },
- Data::ProcPath(ref p) => unsafe {
- libc::utimensat(self.proc.as_raw_descriptor(), p.as_ptr(), tvs.as_ptr(), 0)
- },
- };
- if res < 0 {
- return Err(io::Error::last_os_error());
- }
+ syscall!(unsafe {
+ match data {
+ Data::Handle(_, fd) => libc::futimens(fd, tvs.as_ptr()),
+ Data::ProcPath(ref p) => {
+ libc::utimensat(self.proc.as_raw_descriptor(), p.as_ptr(), tvs.as_ptr(), 0)
+ }
+ }
+ })?;
}
self.do_getattr(&inode_data)
@@ -1603,7 +1869,7 @@ impl FileSystem for PassthroughFs {
// Safe because this doesn't modify any memory and we check the return value.
// TODO: Switch to libc::renameat2 once https://github.com/rust-lang/libc/pull/1508 lands
// and we have glibc 2.28.
- let res = unsafe {
+ syscall!(unsafe {
libc::syscall(
libc::SYS_renameat2,
old_inode.as_raw_descriptor(),
@@ -1612,12 +1878,8 @@ impl FileSystem for PassthroughFs {
newname.as_ptr(),
flags,
)
- };
- if res == 0 {
- Ok(())
- } else {
- Err(io::Error::last_os_error())
- }
+ })?;
+ Ok(())
}
fn mknod(
@@ -1633,26 +1895,21 @@ impl FileSystem for PassthroughFs {
let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?;
- let res = {
- let mut um = self.umask.lock();
- let _scoped_umask = um.set(umask);
+ {
+ let _scoped_umask = ScopedUmask::new(umask);
// Safe because this doesn't modify any memory and we check the return value.
- unsafe {
+ syscall!(unsafe {
libc::mknodat(
data.as_raw_descriptor(),
name.as_ptr(),
mode as libc::mode_t,
rdev as libc::dev_t,
)
- }
- };
-
- if res < 0 {
- Err(io::Error::last_os_error())
- } else {
- self.do_lookup(&data, name)
+ })?;
}
+
+ self.do_lookup(&data, name)
}
fn link(
@@ -1669,7 +1926,7 @@ impl FileSystem for PassthroughFs {
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
// Safe because this doesn't modify any memory and we check the return value.
- let res = unsafe {
+ syscall!(unsafe {
libc::linkat(
self.proc.as_raw_descriptor(),
path.as_ptr(),
@@ -1677,12 +1934,9 @@ impl FileSystem for PassthroughFs {
newname.as_ptr(),
libc::AT_SYMLINK_FOLLOW,
)
- };
- if res == 0 {
- self.do_lookup(&new_inode, newname)
- } else {
- Err(io::Error::last_os_error())
- }
+ })?;
+
+ self.do_lookup(&new_inode, newname)
}
fn symlink(
@@ -1697,13 +1951,11 @@ impl FileSystem for PassthroughFs {
let (_uid, _gid) = set_creds(ctx.uid, ctx.gid)?;
// Safe because this doesn't modify any memory and we check the return value.
- let res =
- unsafe { libc::symlinkat(linkname.as_ptr(), data.as_raw_descriptor(), name.as_ptr()) };
- if res == 0 {
- self.do_lookup(&data, name)
- } else {
- Err(io::Error::last_os_error())
- }
+ syscall!(unsafe {
+ libc::symlinkat(linkname.as_ptr(), data.as_raw_descriptor(), name.as_ptr())
+ })?;
+
+ self.do_lookup(&data, name)
}
fn readlink(&self, _ctx: Context, inode: Inode) -> io::Result<Vec<u8>> {
@@ -1715,17 +1967,14 @@ impl FileSystem for PassthroughFs {
let empty = unsafe { CStr::from_bytes_with_nul_unchecked(EMPTY_CSTR) };
// Safe because this will only modify the contents of `buf` and we check the return value.
- let res = unsafe {
+ let res = syscall!(unsafe {
libc::readlinkat(
data.as_raw_descriptor(),
empty.as_ptr(),
buf.as_mut_ptr() as *mut libc::c_char,
buf.len(),
)
- };
- if res < 0 {
- return Err(io::Error::last_os_error());
- }
+ })?;
buf.resize(res as usize, 0);
Ok(buf)
@@ -1748,18 +1997,15 @@ impl FileSystem for PassthroughFs {
// behavior by doing the same thing (dup-ing the fd and then immediately closing it). Safe
// because this doesn't modify any memory and we check the return values.
unsafe {
- let newfd = libc::fcntl(data.as_raw_descriptor(), libc::F_DUPFD_CLOEXEC, 0);
-
- if newfd < 0 {
- return Err(io::Error::last_os_error());
- }
+ let newfd = syscall!(libc::fcntl(
+ data.as_raw_descriptor(),
+ libc::F_DUPFD_CLOEXEC,
+ 0
+ ))?;
- if libc::close(newfd) < 0 {
- Err(io::Error::last_os_error())
- } else {
- Ok(())
- }
+ syscall!(libc::close(newfd))?;
}
+ Ok(())
}
fn fsync(&self, _ctx: Context, inode: Inode, datasync: bool, handle: Handle) -> io::Result<()> {
@@ -1855,7 +2101,7 @@ impl FileSystem for PassthroughFs {
let data = self.find_inode(inode)?;
let name = self.rewrite_xattr_name(name);
- let res = if data.filetype == FileType::Other {
+ if data.filetype == FileType::Other {
// For non-regular files and directories, we cannot open the fd normally. Instead we
// emulate an _at syscall by changing the CWD to /proc, running the path based syscall,
// and then setting the CWD back to the root directory.
@@ -1863,19 +2109,21 @@ impl FileSystem for PassthroughFs {
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
// Safe because this doesn't modify any memory and we check the return value.
- self.with_proc_chdir(|| unsafe {
- libc::setxattr(
- path.as_ptr(),
- name.as_ptr(),
- value.as_ptr() as *const libc::c_void,
- value.len() as libc::size_t,
- flags as c_int,
- )
- })
+ syscall!(self.with_proc_chdir(|| {
+ unsafe {
+ libc::setxattr(
+ path.as_ptr(),
+ name.as_ptr(),
+ value.as_ptr() as *const libc::c_void,
+ value.len() as libc::size_t,
+ flags as c_int,
+ )
+ }
+ }))?;
} else {
// For regular files and directories, we can just use fsetxattr. Safe because this
// doesn't modify any memory and we check the return value.
- unsafe {
+ syscall!(unsafe {
libc::fsetxattr(
data.as_raw_descriptor(),
name.as_ptr(),
@@ -1883,14 +2131,10 @@ impl FileSystem for PassthroughFs {
value.len() as libc::size_t,
flags as c_int,
)
- }
- };
-
- if res < 0 {
- Err(io::Error::last_os_error())
- } else {
- Ok(())
+ })?;
}
+
+ Ok(())
}
fn getxattr(
@@ -1933,29 +2177,25 @@ impl FileSystem for PassthroughFs {
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
// Safe because this will only modify `buf` and we check the return value.
- self.with_proc_chdir(|| unsafe {
+ syscall!(self.with_proc_chdir(|| unsafe {
libc::listxattr(
path.as_ptr(),
buf.as_mut_ptr() as *mut libc::c_char,
buf.len() as libc::size_t,
)
- })
+ }))?
} else {
// For regular files and directories, we can just flistxattr. Safe because this will only
// write to `buf` and we check the return value.
- unsafe {
+ syscall!(unsafe {
libc::flistxattr(
data.as_raw_descriptor(),
buf.as_mut_ptr() as *mut libc::c_char,
buf.len() as libc::size_t,
)
- }
+ })?
};
- if res < 0 {
- return Err(io::Error::last_os_error());
- }
-
if size == 0 {
Ok(ListxattrReply::Count(res as u32))
} else {
@@ -1978,7 +2218,7 @@ impl FileSystem for PassthroughFs {
let data = self.find_inode(inode)?;
let name = self.rewrite_xattr_name(name);
- let res = if data.filetype == FileType::Other {
+ if data.filetype == FileType::Other {
// For non-regular files and directories, we cannot open the fd normally. Instead we
// emulate an _at syscall by changing the CWD to /proc, running the path based syscall,
// and then setting the CWD back to the root directory.
@@ -1986,18 +2226,16 @@ impl FileSystem for PassthroughFs {
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
// Safe because this doesn't modify any memory and we check the return value.
- self.with_proc_chdir(|| unsafe { libc::removexattr(path.as_ptr(), name.as_ptr()) })
+ syscall!(
+ self.with_proc_chdir(|| unsafe { libc::removexattr(path.as_ptr(), name.as_ptr()) })
+ )?;
} else {
// For regular files and directories, we can just use fremovexattr. Safe because this
// doesn't modify any memory and we check the return value.
- unsafe { libc::fremovexattr(data.as_raw_descriptor(), name.as_ptr()) }
- };
-
- if res == 0 {
- Ok(())
- } else {
- Err(io::Error::last_os_error())
+ syscall!(unsafe { libc::fremovexattr(data.as_raw_descriptor(), name.as_ptr()) })?;
}
+
+ Ok(())
}
fn fallocate(
@@ -2037,24 +2275,21 @@ impl FileSystem for PassthroughFs {
let fd = data.as_raw_descriptor();
// Safe because this doesn't modify any memory and we check the return value.
- let res = unsafe {
+ syscall!(unsafe {
libc::fallocate64(
fd,
mode as libc::c_int,
offset as libc::off64_t,
length as libc::off64_t,
)
- };
- if res == 0 {
- Ok(())
- } else {
- Err(io::Error::last_os_error())
- }
+ })?;
+
+ Ok(())
}
fn ioctl<R: io::Read>(
&self,
- _ctx: Context,
+ ctx: Context,
inode: Inode,
handle: Handle,
_flags: IoctlFlags,
@@ -2071,6 +2306,8 @@ impl FileSystem for PassthroughFs {
const SET_FLAGS32: u32 = FS_IOC32_SETFLAGS() as u32;
const GET_FLAGS64: u32 = FS_IOC64_GETFLAGS() as u32;
const SET_FLAGS64: u32 = FS_IOC64_SETFLAGS() as u32;
+ const ENABLE_VERITY: u32 = FS_IOC_ENABLE_VERITY() as u32;
+ const MEASURE_VERITY: u32 = FS_IOC_MEASURE_VERITY() as u32;
match cmd {
GET_ENCRYPTION_POLICY_EX => self.get_encryption_policy_ex(inode, handle, r),
@@ -2085,7 +2322,7 @@ impl FileSystem for PassthroughFs {
if in_size < size_of::<fsxattr>() as u32 {
Err(io::Error::from_raw_os_error(libc::EINVAL))
} else {
- self.set_fsxattr(inode, handle, r)
+ self.set_fsxattr(ctx, inode, handle, r)
}
}
GET_FLAGS32 | GET_FLAGS64 => {
@@ -2102,6 +2339,22 @@ impl FileSystem for PassthroughFs {
self.set_flags(inode, handle, r)
}
}
+ ENABLE_VERITY => {
+ if in_size < size_of::<fsverity_enable_arg>() as u32 {
+ Err(io::Error::from_raw_os_error(libc::ENOMEM))
+ } else {
+ self.enable_verity(inode, handle, r)
+ }
+ }
+ MEASURE_VERITY => {
+ if in_size < size_of::<fsverity_digest>() as u32
+ || out_size < size_of::<fsverity_digest>() as u32
+ {
+ Err(io::Error::from_raw_os_error(libc::ENOMEM))
+ } else {
+ self.measure_verity(inode, handle, r, out_size)
+ }
+ }
_ => Err(io::Error::from_raw_os_error(libc::ENOTTY)),
}
}
@@ -2134,7 +2387,7 @@ impl FileSystem for PassthroughFs {
let src = src_data.as_raw_descriptor();
let dst = dst_data.as_raw_descriptor();
- let res = unsafe {
+ Ok(syscall!(unsafe {
libc::syscall(
libc::SYS_copy_file_range,
src,
@@ -2144,13 +2397,7 @@ impl FileSystem for PassthroughFs {
length,
flags,
)
- };
-
- if res >= 0 {
- Ok(res as usize)
- } else {
- Err(io::Error::last_os_error())
- }
+ })? as usize)
}
fn set_up_mapping<M: Mapper>(
@@ -2164,6 +2411,10 @@ impl FileSystem for PassthroughFs {
prot: u32,
mapper: M,
) -> io::Result<()> {
+ if !self.cfg.use_dax {
+ return Err(io::Error::from_raw_os_error(libc::ENOSYS));
+ }
+
let read = prot & libc::PROT_READ as u32 != 0;
let write = prot & libc::PROT_WRITE as u32 != 0;
let mmap_flags = match (read, write) {
@@ -2205,6 +2456,10 @@ impl FileSystem for PassthroughFs {
}
fn remove_mapping<M: Mapper>(&self, msgs: &[RemoveMappingOne], mapper: M) -> io::Result<()> {
+ if !self.cfg.use_dax {
+ return Err(io::Error::from_raw_os_error(libc::ENOSYS));
+ }
+
for RemoveMappingOne { moffset, len } in msgs {
mapper.unmap(*moffset, *len)?;
}
@@ -2267,15 +2522,15 @@ mod tests {
strip_xattr_prefix(&mut actual);
assert_eq!(&actual[..], &no_strippable_names[..]);
- let only_strippable_names = b"user.virtiofs.security.sehash\0user.virtiofs.security.wtf\0";
+ let only_strippable_names = b"user.virtiofs.security.sehash\0user.virtiofs.security.wat\0";
let mut actual = only_strippable_names.to_vec();
strip_xattr_prefix(&mut actual);
- assert_eq!(&actual[..], b"security.sehash\0security.wtf\0");
+ assert_eq!(&actual[..], b"security.sehash\0security.wat\0");
- let mixed_names = b"user.virtiofs.security.sehash\0security.selinux\0user.virtiofs.security.wtf\0user.foobar\0";
+ let mixed_names = b"user.virtiofs.security.sehash\0security.selinux\0user.virtiofs.security.wat\0user.foobar\0";
let mut actual = mixed_names.to_vec();
strip_xattr_prefix(&mut actual);
- let expected = b"security.sehash\0security.selinux\0security.wtf\0user.foobar\0";
+ let expected = b"security.sehash\0security.selinux\0security.wat\0user.foobar\0";
assert_eq!(&actual[..], &expected[..]);
let no_nul_with_prefix = b"user.virtiofs.security.sehash";
diff --git a/devices/src/virtio/fs/worker.rs b/devices/src/virtio/fs/worker.rs
index 19fec1159..f94cf286a 100644
--- a/devices/src/virtio/fs/worker.rs
+++ b/devices/src/virtio/fs/worker.rs
@@ -6,10 +6,11 @@ use std::convert::{TryFrom, TryInto};
use std::fs::File;
use std::io;
use std::os::unix::io::AsRawFd;
-use std::sync::{Arc, Mutex};
+use std::sync::Arc;
-use base::{error, Event, PollToken, SafeDescriptor, Tube, WaitContext};
+use base::{error, syscall, Event, PollToken, SafeDescriptor, Tube, WaitContext};
use fuse::filesystem::{FileSystem, ZeroCopyReader, ZeroCopyWriter};
+use sync::Mutex;
use vm_control::{FsMappingRequest, VmResponse};
use vm_memory::GuestMemory;
@@ -19,6 +20,8 @@ use crate::virtio::{Interrupt, Queue, Reader, SignalableInterrupt, Writer};
impl fuse::Reader for Reader {}
impl fuse::Writer for Writer {
+ type ClosureWriter = Self;
+
fn write_at<F>(&mut self, offset: usize, f: F) -> io::Result<usize>
where
F: Fn(&mut Self) -> io::Result<usize>,
@@ -55,10 +58,7 @@ impl Mapper {
}
fn process_request(&self, request: &FsMappingRequest) -> io::Result<()> {
- let tube = self.tube.lock().map_err(|e| {
- error!("failed to lock tube: {}", e);
- io::Error::from_raw_os_error(libc::EINVAL)
- })?;
+ let tube = self.tube.lock();
tube.send(request).map_err(|e| {
error!("failed to send request {:?}: {}", request, e);
@@ -133,6 +133,30 @@ pub struct Worker<F: FileSystem + Sync> {
slot: u32,
}
+pub fn process_fs_queue<I: SignalableInterrupt, F: FileSystem + Sync>(
+ mem: &GuestMemory,
+ interrupt: &I,
+ queue: &mut Queue,
+ server: &Arc<fuse::Server<F>>,
+ tube: &Arc<Mutex<Tube>>,
+ slot: u32,
+) -> Result<()> {
+ let mapper = Mapper::new(Arc::clone(tube), slot);
+ while let Some(avail_desc) = queue.pop(mem) {
+ let reader =
+ Reader::new(mem.clone(), avail_desc.clone()).map_err(Error::InvalidDescriptorChain)?;
+ let writer =
+ Writer::new(mem.clone(), avail_desc.clone()).map_err(Error::InvalidDescriptorChain)?;
+
+ let total = server.handle_message(reader, writer, &mapper)?;
+
+ queue.add_used(mem, avail_desc.index, total as u32);
+ queue.trigger_interrupt(mem, &*interrupt);
+ }
+
+ Ok(())
+}
+
impl<F: FileSystem + Sync> Worker<F> {
pub fn new(
mem: GuestMemory,
@@ -152,31 +176,6 @@ impl<F: FileSystem + Sync> Worker<F> {
}
}
- fn process_queue(&mut self) -> Result<()> {
- let mut needs_interrupt = false;
-
- let mapper = Mapper::new(Arc::clone(&self.tube), self.slot);
- while let Some(avail_desc) = self.queue.pop(&self.mem) {
- let reader = Reader::new(self.mem.clone(), avail_desc.clone())
- .map_err(Error::InvalidDescriptorChain)?;
- let writer = Writer::new(self.mem.clone(), avail_desc.clone())
- .map_err(Error::InvalidDescriptorChain)?;
-
- let total = self.server.handle_message(reader, writer, &mapper)?;
-
- self.queue
- .add_used(&self.mem, avail_desc.index, total as u32);
-
- needs_interrupt = true;
- }
-
- if needs_interrupt {
- self.irq.signal_used_queue(self.queue.vector);
- }
-
- Ok(())
- }
-
pub fn run(
&mut self,
queue_evt: Event,
@@ -189,18 +188,19 @@ impl<F: FileSystem + Sync> Worker<F> {
const SECBIT_NO_SETUID_FIXUP: i32 = 1 << 2;
// Safe because this doesn't modify any memory and we check the return value.
- let mut securebits = unsafe { libc::prctl(libc::PR_GET_SECUREBITS) };
- if securebits < 0 {
- return Err(Error::GetSecurebits(io::Error::last_os_error()));
- }
+ let mut securebits = syscall!(unsafe { libc::prctl(libc::PR_GET_SECUREBITS) })
+ .map_err(Error::GetSecurebits)?;
securebits |= SECBIT_NO_SETUID_FIXUP;
// Safe because this doesn't modify any memory and we check the return value.
- let ret = unsafe { libc::prctl(libc::PR_SET_SECUREBITS, securebits) };
- if ret < 0 {
- return Err(Error::SetSecurebits(io::Error::last_os_error()));
- }
+ syscall!(unsafe { libc::prctl(libc::PR_SET_SECUREBITS, securebits) })
+ .map_err(Error::SetSecurebits)?;
+
+ // To avoid extra locking, unshare filesystem attributes from parent. This includes the
+ // current working directory and umask.
+ // Safe because this doesn't modify any memory and we check the return value.
+ syscall!(unsafe { libc::unshare(libc::CLONE_FS) }).map_err(Error::UnshareFromParent)?;
#[derive(PollToken)]
enum Token {
@@ -230,7 +230,14 @@ impl<F: FileSystem + Sync> Worker<F> {
match event.token {
Token::QueueReady => {
queue_evt.read().map_err(Error::ReadQueueEvent)?;
- if let Err(e) = self.process_queue() {
+ if let Err(e) = process_fs_queue(
+ &self.mem,
+ &*self.irq,
+ &mut self.queue,
+ &self.server,
+ &self.tube,
+ self.slot,
+ ) {
error!("virtio-fs transport error: {}", e);
return Err(e);
}
diff --git a/devices/src/virtio/gpu/bindgen.sh b/devices/src/virtio/gpu/bindgen.sh
new file mode 100755
index 000000000..7c1dbad7c
--- /dev/null
+++ b/devices/src/virtio/gpu/bindgen.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Regenerate virtio-gpu udmabuf bindgen bindings.
+
+set -euo pipefail
+cd "$(dirname "${BASH_SOURCE[0]}")/../../../.."
+
+source tools/impl/bindgen-common.sh
+
+bindgen_generate \
+ --allowlist-type='udmabuf_.*' \
+ --allowlist-var="UDMABUF_.*" \
+ "${BINDGEN_LINUX}/include/uapi/linux/udmabuf.h" \
+ | replace_linux_int_types | rustfmt \
+ > devices/src/virtio/gpu/udmabuf_bindings.rs
diff --git a/devices/src/virtio/gpu/mod.rs b/devices/src/virtio/gpu/mod.rs
index 1d77e4ff0..0c8aab5a2 100644
--- a/devices/src/virtio/gpu/mod.rs
+++ b/devices/src/virtio/gpu/mod.rs
@@ -10,19 +10,18 @@ mod virtio_gpu;
use std::cell::RefCell;
use std::collections::{BTreeMap, VecDeque};
use std::convert::TryFrom;
-use std::i64;
use std::io::Read;
use std::mem::{self, size_of};
-use std::num::NonZeroU8;
use std::path::PathBuf;
use std::rc::Rc;
use std::sync::Arc;
use std::thread;
-use std::time::Duration;
+
+use anyhow::Context;
use base::{
- debug, error, warn, AsRawDescriptor, AsRawDescriptors, Event, ExternalMapping, PollToken,
- RawDescriptor, Tube, WaitContext,
+ debug, error, warn, AsRawDescriptor, Event, ExternalMapping, PollToken, RawDescriptor,
+ SafeDescriptor, Tube, WaitContext,
};
use data_model::*;
@@ -33,6 +32,7 @@ use rutabaga_gfx::*;
use resources::Alloc;
+use serde::{Deserialize, Serialize};
use sync::Mutex;
use vm_memory::{GuestAddress, GuestMemory};
@@ -44,6 +44,11 @@ use super::{
use super::{PciCapabilityType, VirtioPciShmCap};
use self::protocol::*;
+pub use self::protocol::{
+ virtio_gpu_config, VIRTIO_GPU_F_CONTEXT_INIT, VIRTIO_GPU_F_CREATE_GUEST_HANDLE,
+ VIRTIO_GPU_F_EDID, VIRTIO_GPU_F_RESOURCE_BLOB, VIRTIO_GPU_F_RESOURCE_SYNC,
+ VIRTIO_GPU_F_RESOURCE_UUID, VIRTIO_GPU_F_VIRGL, VIRTIO_GPU_SHM_ID_HOST_VISIBLE,
+};
use self::virtio_gpu::VirtioGpu;
use crate::pci::{
@@ -53,17 +58,32 @@ use crate::pci::{
pub const DEFAULT_DISPLAY_WIDTH: u32 = 1280;
pub const DEFAULT_DISPLAY_HEIGHT: u32 = 1024;
-#[derive(Copy, Clone, Debug, PartialEq)]
+#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GpuMode {
Mode2D,
ModeVirglRenderer,
ModeGfxstream,
}
-#[derive(Debug)]
+#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
+pub struct GpuDisplayParameters {
+ pub width: u32,
+ pub height: u32,
+}
+
+impl Default for GpuDisplayParameters {
+ fn default() -> Self {
+ GpuDisplayParameters {
+ width: DEFAULT_DISPLAY_WIDTH,
+ height: DEFAULT_DISPLAY_HEIGHT,
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(default)]
pub struct GpuParameters {
- pub display_width: u32,
- pub display_height: u32,
+ pub displays: Vec<GpuDisplayParameters>,
pub renderer_use_egl: bool,
pub renderer_use_gles: bool,
pub renderer_use_glx: bool,
@@ -79,18 +99,16 @@ pub struct GpuParameters {
// First queue is for virtio gpu commands. Second queue is for cursor commands, which we expect
// there to be fewer of.
-const QUEUE_SIZES: &[u16] = &[256, 16];
-const FENCE_POLL_MS: u64 = 1;
+pub const QUEUE_SIZES: &[u16] = &[256, 16];
-const GPU_BAR_NUM: u8 = 4;
-const GPU_BAR_OFFSET: u64 = 0;
-const GPU_BAR_SIZE: u64 = 1 << 28;
+pub const GPU_BAR_NUM: u8 = 4;
+pub const GPU_BAR_OFFSET: u64 = 0;
+pub const GPU_BAR_SIZE: u64 = 1 << 28;
impl Default for GpuParameters {
fn default() -> Self {
GpuParameters {
- display_width: DEFAULT_DISPLAY_WIDTH,
- display_height: DEFAULT_DISPLAY_HEIGHT,
+ displays: vec![],
renderer_use_egl: true,
renderer_use_gles: true,
renderer_use_glx: false,
@@ -98,7 +116,11 @@ impl Default for GpuParameters {
gfxstream_use_guest_angle: false,
gfxstream_use_syncfd: true,
use_vulkan: false,
- mode: GpuMode::ModeVirglRenderer,
+ mode: if cfg!(feature = "virgl_renderer") {
+ GpuMode::ModeVirglRenderer
+ } else {
+ GpuMode::Mode2D
+ },
cache_path: None,
cache_size: None,
udmabuf: false,
@@ -115,11 +137,94 @@ pub struct VirtioScanoutBlobData {
pub offsets: [u32; 4],
}
+#[derive(PartialEq, Eq, PartialOrd, Ord)]
+enum VirtioGpuRing {
+ Global,
+ ContextSpecific { ctx_id: u32, ring_idx: u8 },
+}
+
+struct FenceDescriptor {
+ ring: VirtioGpuRing,
+ fence_id: u64,
+ index: u16,
+ len: u32,
+}
+
+#[derive(Default)]
+pub struct FenceState {
+ descs: Vec<FenceDescriptor>,
+ completed_fences: BTreeMap<VirtioGpuRing, u64>,
+}
+
+pub trait QueueReader {
+ fn pop(&self, mem: &GuestMemory) -> Option<DescriptorChain>;
+ fn add_used(&self, mem: &GuestMemory, desc_index: u16, len: u32);
+ fn signal_used(&self, mem: &GuestMemory);
+}
+
+struct LocalQueueReader {
+ queue: RefCell<Queue>,
+ interrupt: Arc<Interrupt>,
+}
+
+impl LocalQueueReader {
+ fn new(queue: Queue, interrupt: &Arc<Interrupt>) -> Self {
+ Self {
+ queue: RefCell::new(queue),
+ interrupt: interrupt.clone(),
+ }
+ }
+}
+
+impl QueueReader for LocalQueueReader {
+ fn pop(&self, mem: &GuestMemory) -> Option<DescriptorChain> {
+ self.queue.borrow_mut().pop(mem)
+ }
+
+ fn add_used(&self, mem: &GuestMemory, desc_index: u16, len: u32) {
+ self.queue.borrow_mut().add_used(mem, desc_index, len)
+ }
+
+ fn signal_used(&self, mem: &GuestMemory) {
+ self.queue
+ .borrow_mut()
+ .trigger_interrupt(mem, &*self.interrupt);
+ }
+}
+
+#[derive(Clone)]
+struct SharedQueueReader {
+ queue: Arc<Mutex<Queue>>,
+ interrupt: Arc<Interrupt>,
+}
+
+impl SharedQueueReader {
+ fn new(queue: Queue, interrupt: &Arc<Interrupt>) -> Self {
+ Self {
+ queue: Arc::new(Mutex::new(queue)),
+ interrupt: interrupt.clone(),
+ }
+ }
+}
+
+impl QueueReader for SharedQueueReader {
+ fn pop(&self, mem: &GuestMemory) -> Option<DescriptorChain> {
+ self.queue.lock().pop(mem)
+ }
+
+ fn add_used(&self, mem: &GuestMemory, desc_index: u16, len: u32) {
+ self.queue.lock().add_used(mem, desc_index, len)
+ }
+
+ fn signal_used(&self, mem: &GuestMemory) {
+ self.queue.lock().trigger_interrupt(mem, &*self.interrupt);
+ }
+}
+
/// Initializes the virtio_gpu state tracker.
fn build(
- possible_displays: &[DisplayBackend],
- display_width: u32,
- display_height: u32,
+ display_backends: &[DisplayBackend],
+ display_params: Vec<GpuDisplayParameters>,
rutabaga_builder: RutabagaBuilder,
event_devices: Vec<EventDevice>,
gpu_device_tube: Tube,
@@ -127,10 +232,12 @@ fn build(
map_request: Arc<Mutex<Option<ExternalMapping>>>,
external_blob: bool,
udmabuf: bool,
+ fence_handler: RutabagaFenceHandler,
+ render_server_fd: Option<SafeDescriptor>,
) -> Option<VirtioGpu> {
let mut display_opt = None;
- for display in possible_displays {
- match display.build() {
+ for display_backend in display_backends {
+ match display_backend.build() {
Ok(c) => {
display_opt = Some(c);
break;
@@ -149,8 +256,7 @@ fn build(
VirtioGpu::new(
display,
- display_width,
- display_height,
+ display_params,
rutabaga_builder,
event_devices,
gpu_device_tube,
@@ -158,74 +264,85 @@ fn build(
map_request,
external_blob,
udmabuf,
+ fence_handler,
+ render_server_fd,
)
}
-struct ReturnDescriptor {
- index: u16,
- len: u32,
-}
-
-struct FenceDescriptor {
- desc_fence: RutabagaFenceData,
- index: u16,
- len: u32,
-}
-
-fn fence_ctx_equal(desc_fence: &RutabagaFenceData, completed: &RutabagaFenceData) -> bool {
- let desc_fence_ctx = desc_fence.flags & VIRTIO_GPU_FLAG_INFO_FENCE_CTX_IDX != 0;
- let completed_fence_ctx = completed.flags & VIRTIO_GPU_FLAG_INFO_FENCE_CTX_IDX != 0;
-
- // Both fences on global timeline -- only case with upstream kernel. The rest of the logic
- // is for per fence context prototype.
- if !completed_fence_ctx && !desc_fence_ctx {
- return true;
- }
-
- // One fence is on global timeline
- if desc_fence_ctx != completed_fence_ctx {
- return false;
- }
+/// Create a handler that writes into the completed fence queue
+pub fn create_fence_handler<Q>(
+ mem: GuestMemory,
+ ctrl_queue: Q,
+ fence_state: Arc<Mutex<FenceState>>,
+) -> RutabagaFenceHandler
+where
+ Q: QueueReader + Send + Clone + 'static,
+{
+ RutabagaFenceClosure::new(move |completed_fence| {
+ let mut signal = false;
+
+ {
+ let ring = match completed_fence.flags & VIRTIO_GPU_FLAG_INFO_RING_IDX {
+ 0 => VirtioGpuRing::Global,
+ _ => VirtioGpuRing::ContextSpecific {
+ ctx_id: completed_fence.ctx_id,
+ ring_idx: completed_fence.ring_idx,
+ },
+ };
- // Different 3D contexts
- if desc_fence.ctx_id != completed.ctx_id {
- return false;
- }
+ let mut fence_state = fence_state.lock();
+ fence_state.descs.retain(|f_desc| {
+ if f_desc.ring == ring && f_desc.fence_id <= completed_fence.fence_id {
+ ctrl_queue.add_used(&mem, f_desc.index, f_desc.len);
+ signal = true;
+ return false;
+ }
+ true
+ });
+ // Update the last completed fence for this context
+ fence_state
+ .completed_fences
+ .insert(ring, completed_fence.fence_id);
+ }
- // Different fence contexts with same 3D context
- if desc_fence.fence_ctx_idx != completed.fence_ctx_idx {
- return false;
- }
+ if signal {
+ ctrl_queue.signal_used(&mem);
+ }
+ })
+}
- true
+pub struct ReturnDescriptor {
+ pub index: u16,
+ pub len: u32,
}
-struct Frontend {
- return_ctrl_descriptors: VecDeque<ReturnDescriptor>,
+pub struct Frontend {
+ fence_state: Arc<Mutex<FenceState>>,
return_cursor_descriptors: VecDeque<ReturnDescriptor>,
- fence_descriptors: Vec<FenceDescriptor>,
virtio_gpu: VirtioGpu,
}
impl Frontend {
- fn new(virtio_gpu: VirtioGpu) -> Frontend {
+ fn new(virtio_gpu: VirtioGpu, fence_state: Arc<Mutex<FenceState>>) -> Frontend {
Frontend {
- return_ctrl_descriptors: Default::default(),
+ fence_state,
return_cursor_descriptors: Default::default(),
- fence_descriptors: Default::default(),
virtio_gpu,
}
}
- fn display(&mut self) -> &Rc<RefCell<GpuDisplay>> {
+ /// Returns the internal connection to the compositor and its associated state.
+ pub fn display(&mut self) -> &Rc<RefCell<GpuDisplay>> {
self.virtio_gpu.display()
}
- fn process_display(&mut self) -> bool {
+ /// Processes the internal `display` events and returns `true` if any display was closed.
+ pub fn process_display(&mut self) -> bool {
self.virtio_gpu.process_display()
}
- fn process_resource_bridge(&mut self, resource_bridge: &Tube) {
+ /// Processes incoming requests on `resource_bridge`.
+ pub fn process_resource_bridge(&mut self, resource_bridge: &Tube) -> anyhow::Result<()> {
let response = match resource_bridge.recv() {
Ok(ResourceRequest::GetBuffer { id }) => self.virtio_gpu.export_resource(id),
Ok(ResourceRequest::GetFence { seqno }) => {
@@ -236,15 +353,14 @@ impl Frontend {
Err(_) => ResourceResponse::Invalid,
}
}
- Err(e) => {
- error!("error receiving resource bridge request: {}", e);
- return;
- }
+ Err(e) => return Err(e).context("Error receiving resource bridge request"),
};
- if let Err(e) = resource_bridge.send(&response) {
- error!("error sending resource bridge request: {}", e);
- }
+ resource_bridge
+ .send(&response)
+ .context("Error sending resource bridge response")?;
+
+ Ok(())
}
fn process_gpu_command(
@@ -326,12 +442,15 @@ impl Frontend {
}
GpuCommand::UpdateCursor(info) => self.virtio_gpu.update_cursor(
info.resource_id.to_native(),
+ info.pos.scanout_id.to_native(),
+ info.pos.x.into(),
+ info.pos.y.into(),
+ ),
+ GpuCommand::MoveCursor(info) => self.virtio_gpu.move_cursor(
+ info.pos.scanout_id.to_native(),
info.pos.x.into(),
info.pos.y.into(),
),
- GpuCommand::MoveCursor(info) => self
- .virtio_gpu
- .move_cursor(info.pos.x.into(), info.pos.y.into()),
GpuCommand::ResourceAssignUuid(info) => {
let resource_id = info.resource_id.to_native();
self.virtio_gpu.resource_assign_uuid(resource_id)
@@ -440,9 +559,7 @@ impl Frontend {
};
let entry_count = info.nr_entries.to_native();
- if entry_count > VIRTIO_GPU_MAX_IOVEC_ENTRIES
- || (reader.available_bytes() == 0 && entry_count > 0)
- {
+ if reader.available_bytes() == 0 && entry_count > 0 {
return Err(GpuResponse::ErrUnspec);
}
@@ -518,7 +635,8 @@ impl Frontend {
desc.len as usize >= size_of::<virtio_gpu_ctrl_hdr>() && !desc.is_write_only()
}
- fn process_queue(&mut self, mem: &GuestMemory, queue: &mut Queue) -> bool {
+ /// Processes virtio messages on `queue`.
+ pub fn process_queue(&mut self, mem: &GuestMemory, queue: &dyn QueueReader) -> bool {
let mut signal_used = false;
while let Some(desc) = queue.pop(mem) {
if Frontend::validate_desc(&desc) {
@@ -530,13 +648,13 @@ impl Frontend {
if let Some(ret_desc) =
self.process_descriptor(mem, desc.index, &mut reader, &mut writer)
{
- queue.add_used(&mem, ret_desc.index, ret_desc.len);
+ queue.add_used(mem, ret_desc.index, ret_desc.len);
signal_used = true;
}
}
(_, Err(e)) | (Err(e), _) => {
debug!("invalid descriptor: {}", e);
- queue.add_used(&mem, desc.index, 0);
+ queue.add_used(mem, desc.index, 0);
signal_used = true;
}
}
@@ -551,7 +669,7 @@ impl Frontend {
desc.is_write_only(),
virtio_gpu_cmd_str(likely_type.to_native())
);
- queue.add_used(&mem, desc.index, 0);
+ queue.add_used(mem, desc.index, 0);
signal_used = true;
}
}
@@ -589,23 +707,22 @@ impl Frontend {
let mut fence_id = 0;
let mut ctx_id = 0;
let mut flags = 0;
- let mut info = 0;
+ let mut ring_idx = 0;
if let Some(cmd) = gpu_cmd {
let ctrl_hdr = cmd.ctrl_hdr();
if ctrl_hdr.flags.to_native() & VIRTIO_GPU_FLAG_FENCE != 0 {
flags = ctrl_hdr.flags.to_native();
fence_id = ctrl_hdr.fence_id.to_native();
ctx_id = ctrl_hdr.ctx_id.to_native();
- // The only possible current value for hdr info.
- info = ctrl_hdr.info.to_native();
+ ring_idx = ctrl_hdr.ring_idx;
- let fence_data = RutabagaFenceData {
+ let fence = RutabagaFence {
flags,
fence_id,
ctx_id,
- fence_ctx_idx: info,
+ ring_idx,
};
- gpu_response = match self.virtio_gpu.create_fence(fence_data) {
+ gpu_response = match self.virtio_gpu.create_fence(fence) {
Ok(_) => gpu_response,
Err(fence_resp) => {
warn!("create_fence {} -> {:?}", fence_id, fence_resp);
@@ -617,27 +734,33 @@ impl Frontend {
// Prepare the response now, even if it is going to wait until
// fence is complete.
- match gpu_response.encode(flags, fence_id, ctx_id, info, writer) {
+ match gpu_response.encode(flags, fence_id, ctx_id, ring_idx, writer) {
Ok(l) => len = l,
Err(e) => debug!("ctrl queue response encode error: {}", e),
}
if flags & VIRTIO_GPU_FLAG_FENCE != 0 {
- self.fence_descriptors.push(FenceDescriptor {
- desc_fence: RutabagaFenceData {
- flags,
+ let ring = match flags & VIRTIO_GPU_FLAG_INFO_RING_IDX {
+ 0 => VirtioGpuRing::Global,
+ _ => VirtioGpuRing::ContextSpecific { ctx_id, ring_idx },
+ };
+
+ // In case the fence is signaled immediately after creation, don't add a return
+ // FenceDescriptor.
+ let mut fence_state = self.fence_state.lock();
+ if fence_id > *fence_state.completed_fences.get(&ring).unwrap_or(&0) {
+ fence_state.descs.push(FenceDescriptor {
+ ring,
fence_id,
- ctx_id,
- fence_ctx_idx: info,
- },
- index: desc_index,
- len,
- });
+ index: desc_index,
+ len,
+ });
- return None;
+ return None;
+ }
}
- // No fence, respond now.
+ // No fence (or already completed fence), respond now.
}
Some(ReturnDescriptor {
index: desc_index,
@@ -645,42 +768,22 @@ impl Frontend {
})
}
- fn return_cursor(&mut self) -> Option<ReturnDescriptor> {
+ pub fn return_cursor(&mut self) -> Option<ReturnDescriptor> {
self.return_cursor_descriptors.pop_front()
}
- fn return_ctrl(&mut self) -> Option<ReturnDescriptor> {
- self.return_ctrl_descriptors.pop_front()
- }
-
- fn fence_poll(&mut self) {
- let completed_fences = self.virtio_gpu.fence_poll();
- let return_descs = &mut self.return_ctrl_descriptors;
-
- self.fence_descriptors.retain(|f_desc| {
- for completed in &completed_fences {
- if fence_ctx_equal(&f_desc.desc_fence, completed)
- && f_desc.desc_fence.fence_id <= completed.fence_id
- {
- return_descs.push_back(ReturnDescriptor {
- index: f_desc.index,
- len: f_desc.len,
- });
- return false;
- }
- }
- true
- })
+ pub fn poll(&self) {
+ self.virtio_gpu.poll();
}
}
struct Worker {
- interrupt: Interrupt,
+ interrupt: Arc<Interrupt>,
exit_evt: Event,
mem: GuestMemory,
- ctrl_queue: Queue,
+ ctrl_queue: SharedQueueReader,
ctrl_evt: Event,
- cursor_queue: Queue,
+ cursor_queue: LocalQueueReader,
cursor_evt: Event,
resource_bridges: Vec<Tube>,
kill_evt: Event,
@@ -697,6 +800,7 @@ impl Worker {
InterruptResample,
Kill,
ResourceBridge { index: usize },
+ VirtioGpuPoll,
}
let wait_ctx: WaitContext<Token> = match WaitContext::build_with(&[
@@ -727,6 +831,13 @@ impl Worker {
}
}
+ if let Some(poll_desc) = self.state.virtio_gpu.poll_descriptor() {
+ if let Err(e) = wait_ctx.add(&poll_desc, Token::VirtioGpuPoll) {
+ error!("failed adding poll eventfd to WaitContext: {}", e);
+ return;
+ }
+ }
+
// TODO(davidriley): The entire main loop processing is somewhat racey and incorrect with
// respect to cursor vs control queue processing. As both currently and originally
// written, while the control queue is only processed/read from after the the cursor queue
@@ -738,14 +849,7 @@ impl Worker {
// Declare this outside the loop so we don't keep allocating and freeing the vector.
let mut process_resource_bridge = Vec::with_capacity(self.resource_bridges.len());
'wait: loop {
- // If there are outstanding fences, wake up early to poll them.
- let duration = if !self.state.fence_descriptors.is_empty() {
- Duration::from_millis(FENCE_POLL_MS)
- } else {
- Duration::new(i64::MAX as u64, 0)
- };
-
- let events = match wait_ctx.wait_timeout(duration) {
+ let events = match wait_ctx.wait() {
Ok(v) => v,
Err(e) => {
error!("failed polling for events: {}", e);
@@ -779,7 +883,7 @@ impl Worker {
}
Token::CursorQueue => {
let _ = self.cursor_evt.read();
- if self.state.process_queue(&self.mem, &mut self.cursor_queue) {
+ if self.state.process_queue(&self.mem, &self.cursor_queue) {
signal_used_cursor = true;
}
}
@@ -795,6 +899,9 @@ impl Worker {
Token::InterruptResample => {
self.interrupt.interrupt_resample();
}
+ Token::VirtioGpuPoll => {
+ self.state.poll();
+ }
Token::Kill => {
break 'wait;
}
@@ -807,14 +914,7 @@ impl Worker {
signal_used_cursor = true;
}
- if ctrl_available && self.state.process_queue(&self.mem, &mut self.ctrl_queue) {
- signal_used_ctrl = true;
- }
-
- self.state.fence_poll();
-
- while let Some(desc) = self.state.return_ctrl() {
- self.ctrl_queue.add_used(&self.mem, desc.index, desc.len);
+ if ctrl_available && self.state.process_queue(&self.mem, &self.ctrl_queue) {
signal_used_ctrl = true;
}
@@ -828,16 +928,22 @@ impl Worker {
self.resource_bridges.iter().zip(&process_resource_bridge)
{
if should_process {
- self.state.process_resource_bridge(bridge);
+ if let Err(e) = self.state.process_resource_bridge(bridge) {
+ error!("Failed to process resource bridge: {:#}", e);
+ error!("Removing that resource bridge from the wait context.");
+ wait_ctx.delete(bridge).unwrap_or_else(|e| {
+ error!("Failed to remove faulty resource bridge: {:#}", e)
+ });
+ }
}
}
if signal_used_ctrl {
- self.interrupt.signal_used_queue(self.ctrl_queue.vector);
+ self.ctrl_queue.signal_used(&self.mem);
}
if signal_used_cursor {
- self.interrupt.signal_used_queue(self.cursor_queue.vector);
+ self.cursor_queue.signal_used(&self.mem);
}
}
}
@@ -875,34 +981,31 @@ pub struct Gpu {
kill_evt: Option<Event>,
config_event: bool,
worker_thread: Option<thread::JoinHandle<()>>,
- num_scanouts: NonZeroU8,
display_backends: Vec<DisplayBackend>,
- display_width: u32,
- display_height: u32,
+ display_params: Vec<GpuDisplayParameters>,
rutabaga_builder: Option<RutabagaBuilder>,
pci_bar: Option<Alloc>,
map_request: Arc<Mutex<Option<ExternalMapping>>>,
external_blob: bool,
rutabaga_component: RutabagaComponentType,
base_features: u64,
- mem: GuestMemory,
udmabuf: bool,
+ render_server_fd: Option<SafeDescriptor>,
}
impl Gpu {
pub fn new(
exit_evt: Event,
gpu_device_tube: Option<Tube>,
- num_scanouts: NonZeroU8,
resource_bridges: Vec<Tube>,
display_backends: Vec<DisplayBackend>,
gpu_parameters: &GpuParameters,
+ render_server_fd: Option<SafeDescriptor>,
event_devices: Vec<EventDevice>,
map_request: Arc<Mutex<Option<ExternalMapping>>>,
external_blob: bool,
base_features: u64,
channels: BTreeMap<String, PathBuf>,
- mem: GuestMemory,
) -> Gpu {
let virglrenderer_flags = VirglRendererFlags::new()
.use_egl(gpu_parameters.renderer_use_egl)
@@ -910,7 +1013,10 @@ impl Gpu {
.use_glx(gpu_parameters.renderer_use_glx)
.use_surfaceless(gpu_parameters.renderer_use_surfaceless)
.use_external_blob(external_blob)
- .use_venus(gpu_parameters.use_vulkan);
+ .use_venus(gpu_parameters.use_vulkan)
+ .use_render_server(render_server_fd.is_some())
+ .use_thread_sync(true)
+ .use_async_fence_cb(true);
let gfxstream_flags = GfxstreamFlags::new()
.use_egl(gpu_parameters.renderer_use_egl)
.use_gles(gpu_parameters.renderer_use_gles)
@@ -918,7 +1024,8 @@ impl Gpu {
.use_surfaceless(gpu_parameters.renderer_use_surfaceless)
.use_guest_angle(gpu_parameters.gfxstream_use_guest_angle)
.use_syncfd(gpu_parameters.gfxstream_use_syncfd)
- .use_vulkan(gpu_parameters.use_vulkan);
+ .use_vulkan(gpu_parameters.use_vulkan)
+ .use_async_fence_cb(true);
let mut rutabaga_channels: Vec<RutabagaChannel> = Vec::new();
for (channel_name, path) in &channels {
@@ -942,9 +1049,16 @@ impl Gpu {
GpuMode::ModeGfxstream => RutabagaComponentType::Gfxstream,
};
+ let mut display_width = DEFAULT_DISPLAY_WIDTH;
+ let mut display_height = DEFAULT_DISPLAY_HEIGHT;
+ if !gpu_parameters.displays.is_empty() {
+ display_width = gpu_parameters.displays[0].width;
+ display_height = gpu_parameters.displays[0].height;
+ }
+
let rutabaga_builder = RutabagaBuilder::new(component)
- .set_display_width(gpu_parameters.display_width)
- .set_display_height(gpu_parameters.display_height)
+ .set_display_width(display_width)
+ .set_display_height(display_height)
.set_virglrenderer_flags(virglrenderer_flags)
.set_gfxstream_flags(gfxstream_flags)
.set_rutabaga_channels(rutabaga_channels_opt);
@@ -952,26 +1066,62 @@ impl Gpu {
Gpu {
exit_evt,
gpu_device_tube,
- num_scanouts,
resource_bridges,
event_devices,
config_event: false,
kill_evt: None,
worker_thread: None,
display_backends,
- display_width: gpu_parameters.display_width,
- display_height: gpu_parameters.display_height,
+ display_params: gpu_parameters.displays.clone(),
rutabaga_builder: Some(rutabaga_builder),
pci_bar: None,
map_request,
external_blob,
rutabaga_component: component,
base_features,
- mem,
udmabuf: gpu_parameters.udmabuf,
+ render_server_fd,
}
}
+ /// Initializes the internal device state so that it can begin processing virtqueues.
+ pub fn initialize_frontend(
+ &mut self,
+ fence_state: Arc<Mutex<FenceState>>,
+ fence_handler: RutabagaFenceHandler,
+ ) -> Option<Frontend> {
+ let tube = self.gpu_device_tube.take()?;
+ let pci_bar = self.pci_bar.take()?;
+ let rutabaga_builder = self.rutabaga_builder.take()?;
+ let render_server_fd = self.render_server_fd.take();
+ let event_devices = self.event_devices.split_off(0);
+
+ build(
+ &self.display_backends,
+ self.display_params.clone(),
+ rutabaga_builder,
+ event_devices,
+ tube,
+ pci_bar,
+ self.map_request.clone(),
+ self.external_blob,
+ self.udmabuf,
+ fence_handler,
+ render_server_fd,
+ )
+ .map(|vgpu| Frontend::new(vgpu, fence_state))
+ }
+
+ /// Returns the device tube to the main process.
+ pub fn device_tube(&self) -> Option<&Tube> {
+ self.gpu_device_tube.as_ref()
+ }
+
+ /// Sets the device tube to the main process.
+ pub fn set_device_tube(&mut self, tube: Tube) {
+ self.gpu_device_tube = Some(tube);
+ }
+
fn get_config(&self) -> virtio_gpu_config {
let mut events_read = 0;
if self.config_event {
@@ -1005,7 +1155,7 @@ impl Gpu {
virtio_gpu_config {
events_read: Le32::from(events_read),
events_clear: Le32::from(0),
- num_scanouts: Le32::from(self.num_scanouts.get() as u32),
+ num_scanouts: Le32::from(self.display_params.len() as u32),
num_capsets: Le32::from(num_capsets),
}
}
@@ -1034,14 +1184,14 @@ impl VirtioDevice for Gpu {
keep_rds.push(libc::STDERR_FILENO);
}
- if self.udmabuf {
- keep_rds.append(&mut self.mem.as_raw_descriptors());
- }
-
if let Some(ref gpu_device_tube) = self.gpu_device_tube {
keep_rds.push(gpu_device_tube.as_raw_descriptor());
}
+ if let Some(ref render_server_fd) = self.render_server_fd {
+ keep_rds.push(render_server_fd.as_raw_descriptor());
+ }
+
keep_rds.push(self.exit_evt.as_raw_descriptor());
for bridge in &self.resource_bridges {
keep_rds.push(bridge.as_raw_descriptor());
@@ -1125,19 +1275,21 @@ impl VirtioDevice for Gpu {
};
self.kill_evt = Some(self_kill_evt);
- let resource_bridges = mem::replace(&mut self.resource_bridges, Vec::new());
+ let resource_bridges = mem::take(&mut self.resource_bridges);
- let ctrl_queue = queues.remove(0);
+ let irq = Arc::new(interrupt);
+ let ctrl_queue = SharedQueueReader::new(queues.remove(0), &irq);
let ctrl_evt = queue_evts.remove(0);
- let cursor_queue = queues.remove(0);
+ let cursor_queue = LocalQueueReader::new(queues.remove(0), &irq);
let cursor_evt = queue_evts.remove(0);
let display_backends = self.display_backends.clone();
- let display_width = self.display_width;
- let display_height = self.display_height;
+ let display_params = self.display_params.clone();
let event_devices = self.event_devices.split_off(0);
let map_request = Arc::clone(&self.map_request);
let external_blob = self.external_blob;
let udmabuf = self.udmabuf;
+ let fence_state = Arc::new(Mutex::new(Default::default()));
+ let render_server_fd = self.render_server_fd.take();
if let (Some(gpu_device_tube), Some(pci_bar), Some(rutabaga_builder)) = (
self.gpu_device_tube.take(),
self.pci_bar.take(),
@@ -1147,10 +1299,15 @@ impl VirtioDevice for Gpu {
thread::Builder::new()
.name("virtio_gpu".to_string())
.spawn(move || {
+ let fence_handler = create_fence_handler(
+ mem.clone(),
+ ctrl_queue.clone(),
+ fence_state.clone(),
+ );
+
let virtio_gpu = match build(
&display_backends,
- display_width,
- display_height,
+ display_params,
rutabaga_builder,
event_devices,
gpu_device_tube,
@@ -1158,22 +1315,24 @@ impl VirtioDevice for Gpu {
map_request,
external_blob,
udmabuf,
+ fence_handler,
+ render_server_fd,
) {
Some(backend) => backend,
None => return,
};
Worker {
- interrupt,
+ interrupt: irq,
exit_evt,
mem,
- ctrl_queue,
+ ctrl_queue: ctrl_queue.clone(),
ctrl_evt,
cursor_queue,
cursor_evt,
resource_bridges,
kill_evt,
- state: Frontend::new(virtio_gpu),
+ state: Frontend::new(virtio_gpu, fence_state),
}
.run()
});
diff --git a/devices/src/virtio/gpu/protocol.rs b/devices/src/virtio/gpu/protocol.rs
index c2f9b3eb8..80877ee8e 100644
--- a/devices/src/virtio/gpu/protocol.rs
+++ b/devices/src/virtio/gpu/protocol.rs
@@ -15,11 +15,13 @@ use std::str::from_utf8;
use super::super::DescriptorError;
use super::{Reader, Writer};
-use base::Error as SysError;
+use base::Error as BaseError;
use base::{ExternalMappingError, TubeError};
use data_model::{DataInit, Le32, Le64};
use gpu_display::GpuDisplayError;
+use remain::sorted;
use rutabaga_gfx::RutabagaError;
+use thiserror::Error;
use crate::virtio::gpu::udmabuf::UdmabufError;
@@ -27,10 +29,10 @@ pub const VIRTIO_GPU_F_VIRGL: u32 = 0;
pub const VIRTIO_GPU_F_EDID: u32 = 1;
pub const VIRTIO_GPU_F_RESOURCE_UUID: u32 = 2;
pub const VIRTIO_GPU_F_RESOURCE_BLOB: u32 = 3;
-/* The following capabilities are not upstreamed. */
pub const VIRTIO_GPU_F_CONTEXT_INIT: u32 = 4;
-pub const VIRTIO_GPU_F_CREATE_GUEST_HANDLE: u32 = 5;
-pub const VIRTIO_GPU_F_RESOURCE_SYNC: u32 = 6;
+/* The following capabilities are not upstreamed. */
+pub const VIRTIO_GPU_F_RESOURCE_SYNC: u32 = 5;
+pub const VIRTIO_GPU_F_CREATE_GUEST_HANDLE: u32 = 6;
pub const VIRTIO_GPU_UNDEFINED: u32 = 0x0;
@@ -97,9 +99,6 @@ pub const VIRTIO_GPU_BLOB_FLAG_CREATE_GUEST_HANDLE: u32 = 0x0008;
pub const VIRTIO_GPU_SHM_ID_NONE: u8 = 0x0000;
pub const VIRTIO_GPU_SHM_ID_HOST_VISIBLE: u8 = 0x0001;
-/* This matches the limit in udmabuf.c */
-pub const VIRTIO_GPU_MAX_IOVEC_ENTRIES: u32 = 1024;
-
pub fn virtio_gpu_cmd_str(cmd: u32) -> &'static str {
match cmd {
VIRTIO_GPU_CMD_GET_DISPLAY_INFO => "VIRTIO_GPU_CMD_GET_DISPLAY_INFO",
@@ -146,8 +145,7 @@ pub fn virtio_gpu_cmd_str(cmd: u32) -> &'static str {
}
pub const VIRTIO_GPU_FLAG_FENCE: u32 = 1 << 0;
-/* Fence context index info flag not upstreamed. */
-pub const VIRTIO_GPU_FLAG_INFO_FENCE_CTX_IDX: u32 = 1 << 1;
+pub const VIRTIO_GPU_FLAG_INFO_RING_IDX: u32 = 1 << 1;
#[derive(Copy, Clone, Debug, Default)]
#[repr(C)]
@@ -156,7 +154,8 @@ pub struct virtio_gpu_ctrl_hdr {
pub flags: Le32,
pub fence_id: Le64,
pub ctx_id: Le32,
- pub info: Le32,
+ pub ring_idx: u8,
+ pub padding: [u8; 3],
}
unsafe impl DataInit for virtio_gpu_ctrl_hdr {}
@@ -367,7 +366,7 @@ pub struct virtio_gpu_resource_create_3d {
unsafe impl DataInit for virtio_gpu_resource_create_3d {}
-/* VIRTIO_GPU_CMD_CTX_CREATE (context_init not upstreamed) */
+/* VIRTIO_GPU_CMD_CTX_CREATE */
pub const VIRTIO_GPU_CONTEXT_INIT_CAPSET_ID_MASK: u32 = 1 << 0;
#[derive(Copy)]
#[repr(C)]
@@ -436,7 +435,6 @@ unsafe impl DataInit for virtio_gpu_cmd_submit {}
pub const VIRTIO_GPU_CAPSET_VIRGL: u32 = 1;
pub const VIRTIO_GPU_CAPSET_VIRGL2: u32 = 2;
-/* New capset IDs (not upstreamed) */
pub const VIRTIO_GPU_CAPSET_GFXSTREAM: u32 = 3;
pub const VIRTIO_GPU_CAPSET_VENUS: u32 = 4;
pub const VIRTIO_GPU_CAPSET_CROSS_DOMAIN: u32 = 5;
@@ -638,30 +636,18 @@ pub enum GpuCommand {
/// An error indicating something went wrong decoding a `GpuCommand`. These correspond to
/// `VIRTIO_GPU_CMD_*`.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum GpuCommandDecodeError {
- /// The command referenced an inaccessible area of memory.
- Memory(DescriptorError),
/// The type of the command was invalid.
+ #[error("invalid command type ({0})")]
InvalidType(u32),
/// An I/O error occurred.
+ #[error("an I/O error occurred: {0}")]
IO(io::Error),
-}
-
-impl Display for GpuCommandDecodeError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::GpuCommandDecodeError::*;
-
- match self {
- Memory(e) => write!(
- f,
- "command referenced an inaccessible area of memory: {}",
- e,
- ),
- InvalidType(n) => write!(f, "invalid command type ({})", n),
- IO(e) => write!(f, "an I/O error occurred: {}", e),
- }
- }
+ /// The command referenced an inaccessible area of memory.
+ #[error("command referenced an inaccessible area of memory: {0}")]
+ Memory(DescriptorError),
}
impl From<DescriptorError> for GpuCommandDecodeError {
@@ -805,8 +791,8 @@ pub enum GpuResponse {
map_info: u32,
},
ErrUnspec,
- ErrMsg(TubeError),
- ErrSys(SysError),
+ ErrTube(TubeError),
+ ErrBase(BaseError),
ErrRutabaga(RutabagaError),
ErrDisplay(GpuDisplayError),
ErrMapping(ExternalMappingError),
@@ -823,7 +809,7 @@ pub enum GpuResponse {
impl From<TubeError> for GpuResponse {
fn from(e: TubeError) -> GpuResponse {
- GpuResponse::ErrMsg(e)
+ GpuResponse::ErrTube(e)
}
}
@@ -855,8 +841,8 @@ impl Display for GpuResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::GpuResponse::*;
match self {
- ErrMsg(e) => write!(f, "msg-on-socket error: {}", e),
- ErrSys(e) => write!(f, "system error: {}", e),
+ ErrTube(e) => write!(f, "tube error: {}", e),
+ ErrBase(e) => write!(f, "base error: {}", e),
ErrRutabaga(e) => write!(f, "renderer error: {}", e),
ErrDisplay(e) => write!(f, "display error: {}", e),
ErrScanout { num_scanouts } => write!(f, "non-zero scanout: {}", num_scanouts),
@@ -867,33 +853,21 @@ impl Display for GpuResponse {
}
/// An error indicating something went wrong decoding a `GpuCommand`.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum GpuResponseEncodeError {
+ /// An I/O error occurred.
+ #[error("an I/O error occurred: {0}")]
+ IO(io::Error),
/// The response was encoded to an inaccessible area of memory.
+ #[error("response was encoded to an inaccessible area of memory: {0}")]
Memory(DescriptorError),
/// More displays than are valid were in a `OkDisplayInfo`.
+ #[error("{0} is more displays than are valid")]
TooManyDisplays(usize),
/// More planes than are valid were in a `OkResourcePlaneInfo`.
+ #[error("{0} is more planes than are valid")]
TooManyPlanes(usize),
- /// An I/O error occurred.
- IO(io::Error),
-}
-
-impl Display for GpuResponseEncodeError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::GpuResponseEncodeError::*;
-
- match self {
- Memory(e) => write!(
- f,
- "response was encoded to an inaccessible area of memory: {}",
- e,
- ),
- TooManyDisplays(n) => write!(f, "{} is more displays than are valid", n),
- TooManyPlanes(n) => write!(f, "{} is more planes than are valid", n),
- IO(e) => write!(f, "an I/O error occurred: {}", e),
- }
- }
}
impl From<DescriptorError> for GpuResponseEncodeError {
@@ -917,7 +891,7 @@ impl GpuResponse {
flags: u32,
fence_id: u64,
ctx_id: u32,
- info: u32,
+ ring_idx: u8,
resp: &mut Writer,
) -> Result<u32, GpuResponseEncodeError> {
let hdr = virtio_gpu_ctrl_hdr {
@@ -925,7 +899,8 @@ impl GpuResponse {
flags: Le32::from(flags),
fence_id: Le64::from(fence_id),
ctx_id: Le32::from(ctx_id),
- info: Le32::from(info),
+ ring_idx,
+ padding: Default::default(),
};
let len = match *self {
GpuResponse::OkDisplayInfo(ref info) => {
@@ -1032,8 +1007,8 @@ impl GpuResponse {
GpuResponse::OkResourceUuid { .. } => VIRTIO_GPU_RESP_OK_RESOURCE_UUID,
GpuResponse::OkMapInfo { .. } => VIRTIO_GPU_RESP_OK_MAP_INFO,
GpuResponse::ErrUnspec => VIRTIO_GPU_RESP_ERR_UNSPEC,
- GpuResponse::ErrMsg(_) => VIRTIO_GPU_RESP_ERR_UNSPEC,
- GpuResponse::ErrSys(_) => VIRTIO_GPU_RESP_ERR_UNSPEC,
+ GpuResponse::ErrTube(_) => VIRTIO_GPU_RESP_ERR_UNSPEC,
+ GpuResponse::ErrBase(_) => VIRTIO_GPU_RESP_ERR_UNSPEC,
GpuResponse::ErrRutabaga(_) => VIRTIO_GPU_RESP_ERR_UNSPEC,
GpuResponse::ErrDisplay(_) => VIRTIO_GPU_RESP_ERR_UNSPEC,
GpuResponse::ErrMapping(_) => VIRTIO_GPU_RESP_ERR_UNSPEC,
diff --git a/devices/src/virtio/gpu/udmabuf.rs b/devices/src/virtio/gpu/udmabuf.rs
index 812080bcc..54be58c0f 100644
--- a/devices/src/virtio/gpu/udmabuf.rs
+++ b/devices/src/virtio/gpu/udmabuf.rs
@@ -7,20 +7,21 @@
use std::fs::{File, OpenOptions};
use std::os::raw::c_uint;
+use std::io;
use std::path::Path;
-use std::{fmt, io};
use base::{
- ioctl_iow_nr, ioctl_with_ptr, pagesize, AsRawDescriptor, FromRawDescriptor, MappedRegion,
- SafeDescriptor,
+ ioctl_iow_nr, ioctl_with_ptr, pagesize, FromRawDescriptor, MappedRegion, SafeDescriptor,
};
-use data_model::{FlexibleArray, FlexibleArrayWrapper};
+use data_model::{flexible_array_impl, FlexibleArray, FlexibleArrayWrapper};
use rutabaga_gfx::{RutabagaHandle, RUTABAGA_MEM_HANDLE_TYPE_DMABUF};
use super::udmabuf_bindings::*;
+use remain::sorted;
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
const UDMABUF_IOCTL_BASE: c_uint = 0x75;
@@ -33,45 +34,20 @@ ioctl_iow_nr!(
udmabuf_create_list
);
-// It's possible to make the flexible array trait implementation a macro one day...
-impl FlexibleArray<udmabuf_create_item> for udmabuf_create_list {
- fn set_len(&mut self, len: usize) {
- self.count = len as u32;
- }
-
- fn get_len(&self) -> usize {
- self.count as usize
- }
-
- fn get_slice(&self, len: usize) -> &[udmabuf_create_item] {
- unsafe { self.list.as_slice(len) }
- }
-
- fn get_mut_slice(&mut self, len: usize) -> &mut [udmabuf_create_item] {
- unsafe { self.list.as_mut_slice(len) }
- }
-}
-
+flexible_array_impl!(udmabuf_create_list, udmabuf_create_item, count, list);
type UdmabufCreateList = FlexibleArrayWrapper<udmabuf_create_list, udmabuf_create_item>;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum UdmabufError {
+ #[error("failed to create buffer: {0:?}")]
+ DmabufCreationFail(io::Error),
+ #[error("failed to open udmabuf driver: {0:?}")]
DriverOpenFailed(io::Error),
- NotPageAligned,
+ #[error("failed to get region offset: {0:?}")]
InvalidOffset(GuestMemoryError),
- DmabufCreationFail(io::Error),
-}
-
-impl fmt::Display for UdmabufError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::UdmabufError::*;
- match self {
- DriverOpenFailed(e) => write!(f, "failed to open udmabuf driver: {:?}", e),
- NotPageAligned => write!(f, "All guest addresses must aligned to 4KiB"),
- InvalidOffset(e) => write!(f, "failed to get region offset: {:?}", e),
- DmabufCreationFail(e) => write!(f, "failed to create buffer: {:?}", e),
- }
- }
+ #[error("All guest addresses must aligned to 4KiB")]
+ NotPageAligned,
}
/// The result of an operation in this file.
@@ -98,7 +74,7 @@ fn memory_offset(mem: &GuestMemory, guest_addr: GuestAddress, len: u64) -> Udmab
return Err(GuestMemoryError::InvalidGuestAddress(guest_addr));
}
- return Ok(memfd_offset + map_offset);
+ Ok(memfd_offset + map_offset)
})
.map_err(UdmabufError::InvalidOffset)
}
@@ -186,7 +162,7 @@ mod tests {
let start_addr2 = GuestAddress(0x1100);
let start_addr3 = GuestAddress(0x2100);
- let mem = GuestMemory::new(&vec![
+ let mem = GuestMemory::new(&[
(start_addr1, 0x1000),
(start_addr2, 0x1000),
(start_addr3, 0x1000),
@@ -233,10 +209,10 @@ mod tests {
let mem = GuestMemory::new(&sg_list[..]).unwrap();
let mut udmabuf_create_list = vec![
- (start_addr3, 0x1000 as usize),
- (start_addr2, 0x1000 as usize),
- (start_addr1, 0x1000 as usize),
- (GuestAddress(0x4000), 0x1000 as usize),
+ (start_addr3, 0x1000),
+ (start_addr2, 0x1000),
+ (start_addr1, 0x1000),
+ (GuestAddress(0x4000), 0x1000),
];
let result = driver.create_udmabuf(&mem, &udmabuf_create_list[..]);
diff --git a/devices/src/virtio/gpu/udmabuf_bindings.rs b/devices/src/virtio/gpu/udmabuf_bindings.rs
index dcd89a6f5..5e172ea43 100644
--- a/devices/src/virtio/gpu/udmabuf_bindings.rs
+++ b/devices/src/virtio/gpu/udmabuf_bindings.rs
@@ -1,29 +1,25 @@
-// Copyright 2021 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+/* automatically generated by tools/bindgen-all-the-things */
-/* automatically generated by rust-bindgen, though the exact command remains
- * lost to history. Should be easy to duplicate, if needed.
- */
-
-#![allow(dead_code)]
+#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
#[repr(C)]
#[derive(Default)]
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
impl<T> __IncompleteArrayField<T> {
#[inline]
- pub fn new() -> Self {
+ pub const fn new() -> Self {
__IncompleteArrayField(::std::marker::PhantomData, [])
}
#[inline]
- pub unsafe fn as_ptr(&self) -> *const T {
- ::std::mem::transmute(self)
+ pub fn as_ptr(&self) -> *const T {
+ self as *const _ as *const T
}
#[inline]
- pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
- ::std::mem::transmute(self)
+ pub fn as_mut_ptr(&mut self) -> *mut T {
+ self as *mut _ as *mut T
}
#[inline]
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
@@ -39,36 +35,27 @@ impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
fmt.write_str("__IncompleteArrayField")
}
}
-impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
- #[inline]
- fn clone(&self) -> Self {
- Self::new()
- }
-}
pub const UDMABUF_FLAGS_CLOEXEC: u32 = 1;
-pub type __u32 = ::std::os::raw::c_uint;
-pub type __u64 = ::std::os::raw::c_ulonglong;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct udmabuf_create {
- pub memfd: __u32,
- pub flags: __u32,
- pub offset: __u64,
- pub size: __u64,
+ pub memfd: u32,
+ pub flags: u32,
+ pub offset: u64,
+ pub size: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct udmabuf_create_item {
- pub memfd: __u32,
- pub __pad: __u32,
- pub offset: __u64,
- pub size: __u64,
+ pub memfd: u32,
+ pub __pad: u32,
+ pub offset: u64,
+ pub size: u64,
}
#[repr(C)]
-#[repr(align(8))]
#[derive(Debug, Default)]
pub struct udmabuf_create_list {
- pub flags: __u32,
- pub count: __u32,
+ pub flags: u32,
+ pub count: u32,
pub list: __IncompleteArrayField<udmabuf_create_item>,
}
diff --git a/devices/src/virtio/gpu/virtio_gpu.rs b/devices/src/virtio/gpu/virtio_gpu.rs
index 2e7abb32d..1c4f4f689 100644
--- a/devices/src/virtio/gpu/virtio_gpu.rs
+++ b/devices/src/virtio/gpu/virtio_gpu.rs
@@ -9,15 +9,16 @@ use std::rc::Rc;
use std::result::Result;
use std::sync::Arc;
+use crate::virtio::gpu::GpuDisplayParameters;
use crate::virtio::resource_bridge::{BufferInfo, PlaneInfo, ResourceInfo, ResourceResponse};
-use base::{error, AsRawDescriptor, ExternalMapping, Tube};
+use base::{error, ExternalMapping, SafeDescriptor, Tube};
use data_model::VolatileSlice;
use gpu_display::*;
use rutabaga_gfx::{
- ResourceCreate3D, ResourceCreateBlob, Rutabaga, RutabagaBuilder, RutabagaFenceData,
- RutabagaIovec, Transfer3D,
+ ResourceCreate3D, ResourceCreateBlob, Rutabaga, RutabagaBuilder, RutabagaFence,
+ RutabagaFenceHandler, RutabagaIovec, Transfer3D,
};
use libc::c_void;
@@ -35,7 +36,7 @@ use sync::Mutex;
use vm_memory::{GuestAddress, GuestMemory};
-use vm_control::{MemSlot, VmMemoryRequest, VmMemoryResponse};
+use vm_control::{MemSlot, VmMemoryDestination, VmMemoryRequest, VmMemoryResponse, VmMemorySource};
struct VirtioGpuResource {
resource_id: u32,
@@ -44,7 +45,7 @@ struct VirtioGpuResource {
size: u64,
slot: Option<MemSlot>,
scanout_data: Option<VirtioScanoutBlobData>,
- display_import: Option<(Rc<RefCell<GpuDisplay>>, u32)>,
+ display_import: Option<u32>,
}
impl VirtioGpuResource {
@@ -61,22 +62,203 @@ impl VirtioGpuResource {
display_import: None,
}
}
+}
+
+struct VirtioGpuScanout {
+ width: u32,
+ height: u32,
+ surface_id: Option<u32>,
+ resource_id: Option<NonZeroU32>,
+ scanout_type: SurfaceType,
+ // If this scanout is a primary scanout, the scanout id.
+ scanout_id: Option<u32>,
+ // If this scanout is a cursor scanout, the scanout that this is cursor is overlayed onto.
+ parent_surface_id: Option<u32>,
+}
+
+impl VirtioGpuScanout {
+ fn new(width: u32, height: u32, scanout_id: u32) -> VirtioGpuScanout {
+ VirtioGpuScanout {
+ width,
+ height,
+ scanout_type: SurfaceType::Scanout,
+ scanout_id: Some(scanout_id),
+ surface_id: None,
+ resource_id: None,
+ parent_surface_id: None,
+ }
+ }
+
+ fn new_cursor() -> VirtioGpuScanout {
+ // Per virtio spec: "The mouse cursor image is a normal resource, except that it must be
+ // 64x64 in size."
+ VirtioGpuScanout {
+ width: 64,
+ height: 64,
+ scanout_type: SurfaceType::Cursor,
+ scanout_id: None,
+ surface_id: None,
+ resource_id: None,
+ parent_surface_id: None,
+ }
+ }
+
+ fn create_surface(
+ &mut self,
+ display: &Rc<RefCell<GpuDisplay>>,
+ new_parent_surface_id: Option<u32>,
+ ) -> VirtioGpuResult {
+ let mut need_to_create = false;
+
+ if self.surface_id.is_none() {
+ need_to_create = true;
+ }
+
+ if self.parent_surface_id != new_parent_surface_id {
+ self.parent_surface_id = new_parent_surface_id;
+ need_to_create = true;
+ }
+
+ if !need_to_create {
+ return Ok(OkNoData);
+ }
+
+ self.release_surface(display);
+
+ let mut display = display.borrow_mut();
+
+ let surface_id = display.create_surface(
+ self.parent_surface_id,
+ self.width,
+ self.height,
+ self.scanout_type,
+ )?;
+
+ if let Some(scanout_id) = self.scanout_id {
+ display.set_scanout_id(surface_id, scanout_id)?;
+ }
+
+ self.surface_id = Some(surface_id);
+
+ Ok(OkNoData)
+ }
+
+ fn release_surface(&mut self, display: &Rc<RefCell<GpuDisplay>>) {
+ if let Some(surface_id) = self.surface_id {
+ display.borrow_mut().release_surface(surface_id);
+ }
+
+ self.surface_id = None;
+ }
+
+ fn set_position(&self, display: &Rc<RefCell<GpuDisplay>>, x: u32, y: u32) -> VirtioGpuResult {
+ if let Some(surface_id) = self.surface_id {
+ display.borrow_mut().set_position(surface_id, x, y)?;
+ }
+ Ok(OkNoData)
+ }
- /// Returns the dimensions of the VirtioGpuResource.
- pub fn dimensions(&self) -> (u32, u32) {
- (self.width, self.height)
+ fn commit(&self, display: &Rc<RefCell<GpuDisplay>>) -> VirtioGpuResult {
+ if let Some(surface_id) = self.surface_id {
+ display.borrow_mut().commit(surface_id)?;
+ }
+ Ok(OkNoData)
+ }
+
+ fn flush(
+ &mut self,
+ display: &Rc<RefCell<GpuDisplay>>,
+ resource: &mut VirtioGpuResource,
+ rutabaga: &mut Rutabaga,
+ ) -> VirtioGpuResult {
+ let surface_id = match self.surface_id {
+ Some(id) => id,
+ _ => return Ok(OkNoData),
+ };
+
+ if let Some(import_id) =
+ VirtioGpuScanout::import_resource_to_display(display, resource, rutabaga)
+ {
+ display.borrow_mut().flip_to(surface_id, import_id)?;
+ return Ok(OkNoData);
+ }
+
+ // Import failed, fall back to a copy.
+ let mut display = display.borrow_mut();
+
+ // Prevent overwriting a buffer that is currently being used by the compositor.
+ if display.next_buffer_in_use(surface_id) {
+ return Ok(OkNoData);
+ }
+
+ let fb = display
+ .framebuffer_region(surface_id, 0, 0, self.width, self.height)
+ .ok_or(ErrUnspec)?;
+
+ let mut transfer = Transfer3D::new_2d(0, 0, self.width, self.height);
+ transfer.stride = fb.stride();
+ rutabaga.transfer_read(
+ 0,
+ resource.resource_id,
+ transfer,
+ Some(fb.as_volatile_slice()),
+ )?;
+
+ display.flip(surface_id);
+ Ok(OkNoData)
+ }
+
+ fn import_resource_to_display(
+ display: &Rc<RefCell<GpuDisplay>>,
+ resource: &mut VirtioGpuResource,
+ rutabaga: &mut Rutabaga,
+ ) -> Option<u32> {
+ if let Some(import_id) = resource.display_import {
+ return Some(import_id);
+ }
+
+ let dmabuf = rutabaga.export_blob(resource.resource_id).ok()?;
+ let query = rutabaga.query(resource.resource_id).ok()?;
+
+ let (width, height, format, stride, offset) = match resource.scanout_data {
+ Some(data) => (
+ data.width,
+ data.height,
+ data.drm_format.into(),
+ data.strides[0],
+ data.offsets[0],
+ ),
+ None => (
+ resource.width,
+ resource.height,
+ query.drm_fourcc,
+ query.strides[0],
+ query.offsets[0],
+ ),
+ };
+
+ let import_id = display
+ .borrow_mut()
+ .import_memory(
+ &dmabuf.os_handle,
+ offset,
+ stride,
+ query.modifier,
+ width,
+ height,
+ format,
+ )
+ .ok()?;
+ resource.display_import = Some(import_id);
+ Some(import_id)
}
}
/// Handles functionality related to displays, input events and hypervisor memory management.
pub struct VirtioGpu {
display: Rc<RefCell<GpuDisplay>>,
- display_width: u32,
- display_height: u32,
- scanout_resource_id: Option<NonZeroU32>,
- scanout_surface_id: Option<u32>,
- cursor_resource_id: Option<NonZeroU32>,
- cursor_surface_id: Option<u32>,
+ scanouts: Vec<VirtioGpuScanout>,
+ cursor_scanout: VirtioGpuScanout,
// Maps event devices to scanout number.
event_devices: Map<u32, u32>,
gpu_device_tube: Tube,
@@ -114,8 +296,7 @@ impl VirtioGpu {
/// Creates a new instance of the VirtioGpu state tracker.
pub fn new(
display: GpuDisplay,
- display_width: u32,
- display_height: u32,
+ display_params: Vec<GpuDisplayParameters>,
rutabaga_builder: RutabagaBuilder,
event_devices: Vec<EventDevice>,
gpu_device_tube: Tube,
@@ -123,9 +304,11 @@ impl VirtioGpu {
map_request: Arc<Mutex<Option<ExternalMapping>>>,
external_blob: bool,
udmabuf: bool,
+ fence_handler: RutabagaFenceHandler,
+ render_server_fd: Option<SafeDescriptor>,
) -> Option<VirtioGpu> {
let rutabaga = rutabaga_builder
- .build()
+ .build(fence_handler, render_server_fd)
.map_err(|e| error!("failed to build rutabaga {}", e))
.ok()?;
@@ -138,15 +321,24 @@ impl VirtioGpu {
);
}
+ let scanouts = display_params
+ .iter()
+ .enumerate()
+ .map(|(display_index, &display_param)| {
+ VirtioGpuScanout::new(
+ display_param.width,
+ display_param.height,
+ display_index as u32,
+ )
+ })
+ .collect::<Vec<_>>();
+ let cursor_scanout = VirtioGpuScanout::new_cursor();
+
let mut virtio_gpu = VirtioGpu {
display: Rc::new(RefCell::new(display)),
- display_width,
- display_height,
+ scanouts,
+ cursor_scanout,
event_devices: Default::default(),
- scanout_resource_id: None,
- scanout_surface_id: None,
- cursor_resource_id: None,
- cursor_surface_id: None,
gpu_device_tube,
pci_bar,
map_request,
@@ -170,21 +362,11 @@ impl VirtioGpu {
pub fn import_event_device(
&mut self,
event_device: EventDevice,
- scanout: u32,
+ scanout_id: u32,
) -> VirtioGpuResult {
- // TODO(zachr): support more than one scanout.
- if scanout != 0 {
- return Err(ErrScanout {
- num_scanouts: scanout,
- });
- }
-
let mut display = self.display.borrow_mut();
let event_device_id = display.import_event_device(event_device)?;
- if let Some(s) = self.scanout_surface_id {
- display.attach_event_device(s, event_device_id)
- }
- self.event_devices.insert(event_device_id, scanout);
+ self.event_devices.insert(event_device_id, scanout_id);
Ok(OkNoData)
}
@@ -194,50 +376,44 @@ impl VirtioGpu {
}
/// Gets the list of supported display resolutions as a slice of `(width, height)` tuples.
- pub fn display_info(&self) -> [(u32, u32); 1] {
- [(self.display_width, self.display_height)]
+ pub fn display_info(&self) -> Vec<(u32, u32)> {
+ self.scanouts
+ .iter()
+ .map(|scanout| (scanout.width, scanout.height))
+ .collect::<Vec<_>>()
}
- /// Processes the internal `display` events and returns `true` if the main display was closed.
+ /// Processes the internal `display` events and returns `true` if any display was closed.
pub fn process_display(&mut self) -> bool {
let mut display = self.display.borrow_mut();
- display.dispatch_events();
- self.scanout_surface_id
- .map(|s| display.close_requested(s))
- .unwrap_or(false)
+ let result = display.dispatch_events();
+ match result {
+ Ok(_) => (),
+ Err(e) => error!("failed to dispatch events: {}", e),
+ }
+
+ for scanout in &self.scanouts {
+ let close_requested = scanout
+ .surface_id
+ .map(|surface_id| display.close_requested(surface_id))
+ .unwrap_or(false);
+
+ if close_requested {
+ return true;
+ }
+ }
+
+ false
}
/// Sets the given resource id as the source of scanout to the display.
pub fn set_scanout(
&mut self,
- _scanout_id: u32,
+ scanout_id: u32,
resource_id: u32,
scanout_data: Option<VirtioScanoutBlobData>,
) -> VirtioGpuResult {
- let mut display = self.display.borrow_mut();
- /// b/186580833.
- /// Remove the part of deleting surface when resource_id is 0.
- /// This is a workaround to solve the issue of black display.
- /// Observation is when Surfaceflinger falls back to client composition,
- /// host receives set_scanout 0 0, and then set scanout 0 <some valid resid>.
- /// The first 0 0 removes the surface, the second creates a new surface
- /// with id++, which will be more than 0 and be ignorned in vnc or webrtc
- let resource = self
- .resources
- .get_mut(&resource_id)
- .ok_or(ErrInvalidResourceId)?;
-
- resource.scanout_data = scanout_data;
- self.scanout_resource_id = NonZeroU32::new(resource_id);
- if self.scanout_surface_id.is_none() {
- let surface_id =
- display.create_surface(None, self.display_width, self.display_height)?;
- self.scanout_surface_id = Some(surface_id);
- for event_device_id in self.event_devices.keys() {
- display.attach_event_device(surface_id, *event_device_id);
- }
- }
- Ok(OkNoData)
+ self.update_scanout_resource(SurfaceType::Scanout, scanout_id, scanout_data, resource_id)
}
/// If the resource is the scanout resource, flush it to the display.
@@ -246,175 +422,50 @@ impl VirtioGpu {
return Ok(OkNoData);
}
- if let (Some(scanout_resource_id), Some(scanout_surface_id)) =
- (self.scanout_resource_id, self.scanout_surface_id)
- {
- if scanout_resource_id.get() == resource_id {
- self.flush_resource_to_surface(resource_id, scanout_surface_id)?;
- }
- }
-
- if let (Some(cursor_resource_id), Some(cursor_surface_id)) =
- (self.cursor_resource_id, self.cursor_surface_id)
- {
- if cursor_resource_id.get() == resource_id {
- self.flush_resource_to_surface(resource_id, cursor_surface_id)?;
- }
- }
-
- Ok(OkNoData)
- }
+ let resource = self
+ .resources
+ .get_mut(&resource_id)
+ .ok_or(ErrInvalidResourceId)?;
- /// Attempts to import the given resource into the display. Only works with Wayland displays.
- pub fn import_to_display(&mut self, resource_id: u32) -> Option<u32> {
- let resource = match self.resources.get_mut(&resource_id) {
- Some(resource) => resource,
- _ => return None,
+ // `resource_id` has already been verified to be non-zero
+ let resource_id = match NonZeroU32::new(resource_id) {
+ Some(id) => Some(id),
+ None => return Ok(OkNoData),
};
- if let Some((self_display, import)) = &resource.display_import {
- if Rc::ptr_eq(self_display, &self.display) {
- return Some(*import);
+ for scanout in &mut self.scanouts {
+ if scanout.resource_id == resource_id {
+ scanout.flush(&self.display, resource, &mut self.rutabaga)?;
}
}
-
- let dmabuf = self.rutabaga.export_blob(resource.resource_id).ok()?;
- let query = self.rutabaga.query(resource.resource_id).ok()?;
-
- let (width, height, format, stride, offset) = match resource.scanout_data {
- Some(data) => (
- data.width,
- data.height,
- data.drm_format.into(),
- data.strides[0],
- data.offsets[0],
- ),
- None => (
- resource.width,
- resource.height,
- query.drm_fourcc,
- query.strides[0],
- query.offsets[0],
- ),
- };
-
- match self.display.borrow_mut().import_dmabuf(
- dmabuf.os_handle.as_raw_descriptor(),
- offset,
- stride,
- query.modifier,
- width,
- height,
- format,
- ) {
- Ok(import_id) => {
- resource.display_import = Some((self.display.clone(), import_id));
- Some(import_id)
- }
- Err(e) => {
- error!("failed to import dmabuf for display: {}", e);
- None
- }
+ if self.cursor_scanout.resource_id == resource_id {
+ self.cursor_scanout
+ .flush(&self.display, resource, &mut self.rutabaga)?;
}
- }
-
- /// Attempts to import the given resource into the display, otherwise falls back to rutabaga
- /// copies.
- pub fn flush_resource_to_surface(
- &mut self,
- resource_id: u32,
- surface_id: u32,
- ) -> VirtioGpuResult {
- if let Some(import_id) = self.import_to_display(resource_id) {
- self.display.borrow_mut().flip_to(surface_id, import_id);
- return Ok(OkNoData);
- }
-
- if !self.resources.contains_key(&resource_id) {
- return Err(ErrInvalidResourceId);
- }
-
- // Import failed, fall back to a copy.
- let mut display = self.display.borrow_mut();
- // Prevent overwriting a buffer that is currently being used by the compositor.
- if display.next_buffer_in_use(surface_id) {
- return Ok(OkNoData);
- }
-
- let fb = display
- .framebuffer_region(surface_id, 0, 0, self.display_width, self.display_height)
- .ok_or(ErrUnspec)?;
-
- let mut transfer = Transfer3D::new_2d(0, 0, self.display_width, self.display_height);
- transfer.stride = fb.stride();
- self.rutabaga
- .transfer_read(0, resource_id, transfer, Some(fb.as_volatile_slice()))?;
- display.flip(surface_id);
Ok(OkNoData)
}
/// Updates the cursor's memory to the given resource_id, and sets its position to the given
/// coordinates.
- pub fn update_cursor(&mut self, resource_id: u32, x: u32, y: u32) -> VirtioGpuResult {
- if resource_id == 0 {
- if let Some(surface_id) = self.cursor_surface_id.take() {
- self.display.borrow_mut().release_surface(surface_id);
- }
- self.cursor_resource_id = None;
- return Ok(OkNoData);
- }
-
- let (resource_width, resource_height) = self
- .resources
- .get_mut(&resource_id)
- .ok_or(ErrInvalidResourceId)?
- .dimensions();
-
- self.cursor_resource_id = NonZeroU32::new(resource_id);
-
- if self.cursor_surface_id.is_none() {
- self.cursor_surface_id = Some(self.display.borrow_mut().create_surface(
- self.scanout_surface_id,
- resource_width,
- resource_height,
- )?);
- }
-
- let cursor_surface_id = self.cursor_surface_id.unwrap();
- self.display
- .borrow_mut()
- .set_position(cursor_surface_id, x, y);
+ pub fn update_cursor(
+ &mut self,
+ resource_id: u32,
+ scanout_id: u32,
+ x: u32,
+ y: u32,
+ ) -> VirtioGpuResult {
+ self.update_scanout_resource(SurfaceType::Cursor, scanout_id, None, resource_id)?;
- // Gets the resource's pixels into the display by importing the buffer.
- if let Some(import_id) = self.import_to_display(resource_id) {
- self.display
- .borrow_mut()
- .flip_to(cursor_surface_id, import_id);
- return Ok(OkNoData);
- }
+ self.cursor_scanout.set_position(&self.display, x, y)?;
- // Importing failed, so try copying the pixels into the surface's slower shared memory
- // framebuffer.
- if let Some(fb) = self.display.borrow_mut().framebuffer(cursor_surface_id) {
- let mut transfer = Transfer3D::new_2d(0, 0, resource_width, resource_height);
- transfer.stride = fb.stride();
- self.rutabaga
- .transfer_read(0, resource_id, transfer, Some(fb.as_volatile_slice()))?;
- }
- self.display.borrow_mut().flip(cursor_surface_id);
- Ok(OkNoData)
+ self.flush_resource(resource_id)
}
/// Moves the cursor's position to the given coordinates.
- pub fn move_cursor(&mut self, x: u32, y: u32) -> VirtioGpuResult {
- if let Some(cursor_surface_id) = self.cursor_surface_id {
- if let Some(scanout_surface_id) = self.scanout_surface_id {
- let mut display = self.display.borrow_mut();
- display.set_position(cursor_surface_id, x, y);
- display.commit(scanout_surface_id);
- }
- }
+ pub fn move_cursor(&mut self, _scanout_id: u32, x: u32, y: u32) -> VirtioGpuResult {
+ self.cursor_scanout.set_position(&self.display, x, y)?;
+ self.cursor_scanout.commit(&self.display)?;
Ok(OkNoData)
}
@@ -501,16 +552,22 @@ impl VirtioGpu {
self.rutabaga.force_ctx_0()
}
- /// Creates a fence with the RutabagaFenceData that can be used to determine when the previous
+ /// Creates a fence with the RutabagaFence that can be used to determine when the previous
/// command completed.
- pub fn create_fence(&mut self, rutabaga_fence_data: RutabagaFenceData) -> VirtioGpuResult {
- self.rutabaga.create_fence(rutabaga_fence_data)?;
+ pub fn create_fence(&mut self, rutabaga_fence: RutabagaFence) -> VirtioGpuResult {
+ self.rutabaga.create_fence(rutabaga_fence)?;
Ok(OkNoData)
}
- /// Returns an array of RutabagaFenceData, describing completed fences.
- pub fn fence_poll(&mut self) -> Vec<RutabagaFenceData> {
- self.rutabaga.poll()
+ /// Polls the Rutabaga backend.
+ pub fn poll(&self) {
+ self.rutabaga.poll();
+ }
+
+ /// Gets a pollable eventfd that signals the device to wakeup and poll the
+ /// Rutabaga backend.
+ pub fn poll_descriptor(&self) -> Option<SafeDescriptor> {
+ self.rutabaga.poll_descriptor()
}
/// Creates a 3D resource with the given properties and resource_id.
@@ -640,44 +697,48 @@ impl VirtioGpu {
let map_info = self.rutabaga.map_info(resource_id).map_err(|_| ErrUnspec)?;
let vulkan_info_opt = self.rutabaga.vulkan_info(resource_id).ok();
- let export = self.rutabaga.export_blob(resource_id);
-
- let request = match export {
- Ok(export) => match vulkan_info_opt {
- Some(vulkan_info) => VmMemoryRequest::RegisterVulkanMemoryAtPciBarOffset {
- alloc: self.pci_bar,
+ let source = if let Ok(export) = self.rutabaga.export_blob(resource_id) {
+ match vulkan_info_opt {
+ Some(vulkan_info) => VmMemorySource::Vulkan {
descriptor: export.os_handle,
handle_type: export.handle_type,
memory_idx: vulkan_info.memory_idx,
physical_device_idx: vulkan_info.physical_device_idx,
- offset,
size: resource.size,
},
- None => VmMemoryRequest::RegisterFdAtPciBarOffset(
- self.pci_bar,
- export.os_handle,
- resource.size as usize,
- offset,
- ),
- },
- Err(_) => {
- if self.external_blob {
- return Err(ErrUnspec);
- }
+ None => VmMemorySource::Descriptor {
+ descriptor: export.os_handle,
+ offset: 0,
+ size: resource.size,
+ },
+ }
+ } else {
+ if self.external_blob {
+ return Err(ErrUnspec);
+ }
- let mapping = self.rutabaga.map(resource_id)?;
- // Scope for lock
- {
- let mut map_req = self.map_request.lock();
- if map_req.is_some() {
- return Err(ErrUnspec);
- }
- *map_req = Some(mapping);
+ let mapping = self.rutabaga.map(resource_id)?;
+ // Scope for lock
+ {
+ let mut map_req = self.map_request.lock();
+ if map_req.is_some() {
+ return Err(ErrUnspec);
}
- VmMemoryRequest::RegisterHostPointerAtPciBarOffset(self.pci_bar, offset)
+ *map_req = Some(mapping);
+ }
+ VmMemorySource::ExternalMapping {
+ size: resource.size,
}
};
+ let request = VmMemoryRequest::RegisterMemory {
+ source,
+ dest: VmMemoryDestination::ExistingAllocation {
+ allocation: self.pci_bar,
+ offset,
+ },
+ read_only: false,
+ };
self.gpu_device_tube.send(&request)?;
let response = self.gpu_device_tube.recv()?;
@@ -686,7 +747,7 @@ impl VirtioGpu {
resource.slot = Some(slot);
Ok(OkMapInfo { map_info })
}
- VmMemoryResponse::Err(e) => Err(ErrSys(e)),
+ VmMemoryResponse::Err(e) => Err(ErrBase(e)),
_ => Err(ErrUnspec),
}
}
@@ -708,7 +769,7 @@ impl VirtioGpu {
resource.slot = None;
Ok(OkNoData)
}
- VmMemoryResponse::Err(e) => Err(ErrSys(e)),
+ VmMemoryResponse::Err(e) => Err(ErrBase(e)),
_ => Err(ErrUnspec),
}
}
@@ -763,4 +824,74 @@ impl VirtioGpu {
Err(_) => OkNoData,
}
}
+
+ fn update_scanout_resource(
+ &mut self,
+ scanout_type: SurfaceType,
+ scanout_id: u32,
+ scanout_data: Option<VirtioScanoutBlobData>,
+ resource_id: u32,
+ ) -> VirtioGpuResult {
+ let mut scanout: &mut VirtioGpuScanout;
+ let mut scanout_parent_surface_id = None;
+
+ match scanout_type {
+ SurfaceType::Cursor => {
+ let parent_scanout_id = scanout_id;
+
+ scanout_parent_surface_id = self
+ .scanouts
+ .get(parent_scanout_id as usize)
+ .ok_or(ErrInvalidScanoutId)
+ .map(|parent_scanout| parent_scanout.surface_id)?;
+
+ scanout = &mut self.cursor_scanout;
+ }
+ SurfaceType::Scanout => {
+ scanout = self
+ .scanouts
+ .get_mut(scanout_id as usize)
+ .ok_or(ErrInvalidScanoutId)?;
+ }
+ };
+
+ // Virtio spec: "The driver can use resource_id = 0 to disable a scanout."
+ if resource_id == 0 {
+ // Ignore any initial set_scanout(..., resource_id: 0) calls.
+ if scanout.resource_id.is_some() {
+ scanout.release_surface(&self.display);
+ }
+
+ scanout.resource_id = None;
+ return Ok(OkNoData);
+ }
+
+ let resource = self
+ .resources
+ .get_mut(&resource_id)
+ .ok_or(ErrInvalidResourceId)?;
+
+ // Ensure scanout has a display surface.
+ match scanout_type {
+ SurfaceType::Cursor => {
+ if let Some(scanout_parent_surface_id) = scanout_parent_surface_id {
+ scanout.create_surface(&self.display, Some(scanout_parent_surface_id))?;
+ }
+ }
+ SurfaceType::Scanout => {
+ scanout.create_surface(&self.display, None)?;
+ }
+ }
+
+ resource.scanout_data = scanout_data;
+
+ // `resource_id` has already been verified to be non-zero
+ let resource_id = match NonZeroU32::new(resource_id) {
+ Some(id) => id,
+ None => return Ok(OkNoData),
+ };
+ scanout.resource_id = Some(resource_id);
+
+ Ok(OkNoData)
+ }
}
diff --git a/devices/src/virtio/input/defaults.rs b/devices/src/virtio/input/defaults.rs
index 57ad94ef4..2bb8bcdd9 100644
--- a/devices/src/virtio/input/defaults.rs
+++ b/devices/src/virtio/input/defaults.rs
@@ -10,13 +10,19 @@ use super::virtio_input_bitmap;
use super::virtio_input_device_ids;
use super::VirtioInputConfig;
+fn name_with_index(device_name: &[u8], idx: u32) -> Vec<u8> {
+ let mut ret = device_name.to_vec();
+ ret.extend_from_slice(idx.to_string().as_bytes());
+ ret
+}
+
/// Instantiates a VirtioInputConfig object with the default configuration for a trackpad. It
/// supports touch, left button and right button events, as well as X and Y axis.
-pub fn new_trackpad_config(width: u32, height: u32) -> VirtioInputConfig {
+pub fn new_trackpad_config(idx: u32, width: u32, height: u32) -> VirtioInputConfig {
VirtioInputConfig::new(
virtio_input_device_ids::new(0, 0, 0, 0),
- b"Crosvm Virtio Trackpad".to_vec(),
- b"virtio-trackpad".to_vec(),
+ name_with_index(b"Crosvm Virtio Trackpad ", idx),
+ name_with_index(b"virtio-trackpad-", idx),
virtio_input_bitmap::new([0u8; 128]),
default_trackpad_events(),
default_trackpad_absinfo(width, height),
@@ -25,11 +31,11 @@ pub fn new_trackpad_config(width: u32, height: u32) -> VirtioInputConfig {
/// Instantiates a VirtioInputConfig object with the default configuration for a mouse.
/// It supports left, right and middle buttons, as wel as X, Y and wheel relative axes.
-pub fn new_mouse_config() -> VirtioInputConfig {
+pub fn new_mouse_config(idx: u32) -> VirtioInputConfig {
VirtioInputConfig::new(
virtio_input_device_ids::new(0, 0, 0, 0),
- b"Crosvm Virtio Mouse".to_vec(),
- b"virtio-mouse".to_vec(),
+ name_with_index(b"Crosvm Virtio Mouse ", idx),
+ name_with_index(b"virtio-mouse-", idx),
virtio_input_bitmap::new([0u8; 128]),
default_mouse_events(),
BTreeMap::new(),
@@ -38,11 +44,11 @@ pub fn new_mouse_config() -> VirtioInputConfig {
/// Instantiates a VirtioInputConfig object with the default configuration for a keyboard.
/// It supports the same keys as a en-us keyboard and the CAPSLOCK, NUMLOCK and SCROLLLOCK leds.
-pub fn new_keyboard_config() -> VirtioInputConfig {
+pub fn new_keyboard_config(idx: u32) -> VirtioInputConfig {
VirtioInputConfig::new(
virtio_input_device_ids::new(0, 0, 0, 0),
- b"Crosvm Virtio Keyboard".to_vec(),
- b"virtio-keyboard".to_vec(),
+ name_with_index(b"Crosvm Virtio Keyboard ", idx),
+ name_with_index(b"virtio-keyboard-", idx),
virtio_input_bitmap::new([0u8; 128]),
default_keyboard_events(),
BTreeMap::new(),
@@ -50,11 +56,11 @@ pub fn new_keyboard_config() -> VirtioInputConfig {
}
/// Instantiates a VirtioInputConfig object with the default configuration for a collection of switches.
-pub fn new_switches_config() -> VirtioInputConfig {
+pub fn new_switches_config(idx: u32) -> VirtioInputConfig {
VirtioInputConfig::new(
virtio_input_device_ids::new(0, 0, 0, 0),
- b"Crosvm Virtio Switches".to_vec(),
- b"virtio-switches".to_vec(),
+ name_with_index(b"Crosvm Virtio Switches ", idx),
+ name_with_index(b"virtio-switches-", idx),
virtio_input_bitmap::new([0u8; 128]),
default_switch_events(),
BTreeMap::new(),
@@ -63,11 +69,11 @@ pub fn new_switches_config() -> VirtioInputConfig {
/// Instantiates a VirtioInputConfig object with the default configuration for a touchscreen (no
/// multitouch support).
-pub fn new_single_touch_config(width: u32, height: u32) -> VirtioInputConfig {
+pub fn new_single_touch_config(idx: u32, width: u32, height: u32) -> VirtioInputConfig {
VirtioInputConfig::new(
virtio_input_device_ids::new(0, 0, 0, 0),
- b"Crosvm Virtio Touchscreen".to_vec(),
- b"virtio-touchscreen".to_vec(),
+ name_with_index(b"Crosvm Virtio Touchscreen ", idx),
+ name_with_index(b"virtio-touchscreen-", idx),
virtio_input_bitmap::from_bits(&[INPUT_PROP_DIRECT]),
default_touchscreen_events(),
default_touchscreen_absinfo(width, height),
@@ -76,11 +82,11 @@ pub fn new_single_touch_config(width: u32, height: u32) -> VirtioInputConfig {
/// Instantiates a VirtioInputConfig object with the default configuration for a multitouch
/// touchscreen.
-pub fn new_multi_touch_config(width: u32, height: u32) -> VirtioInputConfig {
+pub fn new_multi_touch_config(idx: u32, width: u32, height: u32) -> VirtioInputConfig {
VirtioInputConfig::new(
virtio_input_device_ids::new(0, 0, 0, 0),
- b"Crosvm Virtio Multitouch Touchscreen".to_vec(),
- b"virtio-touchscreen".to_vec(),
+ name_with_index(b"Crosvm Virtio Multitouch Touchscreen ", idx),
+ name_with_index(b"virtio-touchscreen-", idx),
virtio_input_bitmap::from_bits(&[INPUT_PROP_DIRECT]),
default_multitouchscreen_events(),
default_multitouchscreen_absinfo(width, height, 10, 10),
@@ -322,8 +328,8 @@ mod tests {
#[test]
fn test_new_switches_config() {
- let config = new_switches_config();
- assert_eq!(config.serial_name, b"virtio-switches".to_vec());
+ let config = new_switches_config(0);
+ assert_eq!(config.serial_name, b"virtio-switches-0".to_vec());
let events = config.supported_events;
assert_eq!(events.len(), 1);
@@ -331,7 +337,7 @@ mod tests {
// The bitmap should contain SW_CNT=0x10+1=17 ones,
// where each one is packed into the u8 bitmap.
- let mut expected_bitmap = [0 as u8; 128];
+ let mut expected_bitmap = [0_u8; 128];
expected_bitmap[0] = 0b11111111u8;
expected_bitmap[1] = 0b11111111u8;
expected_bitmap[2] = 0b1u8;
diff --git a/devices/src/virtio/input/evdev.rs b/devices/src/virtio/input/evdev.rs
index 8d6348380..ce553f8a8 100644
--- a/devices/src/virtio/input/evdev.rs
+++ b/devices/src/virtio/input/evdev.rs
@@ -234,12 +234,12 @@ pub fn supported_events<T: AsRawDescriptor>(
/// Gets the absolute axes of an event device (see EVIOCGABS ioctl for details).
pub fn abs_info<T: AsRawDescriptor>(descriptor: &T) -> BTreeMap<u16, virtio_input_absinfo> {
- let mut ret: BTreeMap<u16, virtio_input_absinfo> = BTreeMap::new();
+ let mut map: BTreeMap<u16, virtio_input_absinfo> = BTreeMap::new();
for abs in 0..ABS_MAX {
// Create a new one, zero-ed out every time to avoid carry-overs.
let mut abs_info = evdev_abs_info::new();
- let len = unsafe {
+ let ret = unsafe {
// Safe because the kernel won't write more than size of evdev_buffer and we check the
// return value
ioctl_with_mut_ref(
@@ -248,11 +248,11 @@ pub fn abs_info<T: AsRawDescriptor>(descriptor: &T) -> BTreeMap<u16, virtio_inpu
&mut abs_info,
)
};
- if len > 0 {
- ret.insert(abs, virtio_input_absinfo::from(abs_info));
+ if ret == 0 {
+ map.insert(abs, virtio_input_absinfo::from(abs_info));
}
}
- ret
+ map
}
/// Grabs an event device (see EVIOCGGRAB ioctl for details). After this function succeeds the given
diff --git a/devices/src/virtio/input/event_source.rs b/devices/src/virtio/input/event_source.rs
index e17812298..bdce4c005 100644
--- a/devices/src/virtio/input/event_source.rs
+++ b/devices/src/virtio/input/event_source.rs
@@ -252,11 +252,11 @@ mod tests {
}
impl SourceMock {
- fn new(evts: &Vec<input_event>) -> SourceMock {
+ fn new(evts: &[input_event]) -> SourceMock {
let mut events: Vec<u8> = vec![];
for evt in evts {
for byte in evt.as_slice() {
- events.push(byte.clone());
+ events.push(*byte);
}
}
SourceMock { events }
@@ -282,7 +282,7 @@ mod tests {
#[test]
fn empty_new() {
- let mut source = EventSourceImpl::new(SourceMock::new(&vec![]), 128);
+ let mut source = EventSourceImpl::new(SourceMock::new(&[]), 128);
assert_eq!(
source.available_events(),
0,
@@ -297,7 +297,7 @@ mod tests {
#[test]
fn empty_receive() {
- let mut source = EventSourceImpl::new(SourceMock::new(&vec![]), 128);
+ let mut source = EventSourceImpl::new(SourceMock::new(&[]), 128);
assert_eq!(
source.receive_events::<input_event>().unwrap(),
0,
@@ -358,9 +358,9 @@ mod tests {
evts.len(),
"should receive all events"
);
- for idx in 0..EVENT_COUNT {
+ for expected_evt in evts[..EVENT_COUNT].iter() {
let evt = source.pop_available_event().unwrap();
- assert_events_match(&evt, &evts[idx]);
+ assert_events_match(&evt, expected_evt);
}
assert_eq!(
source.available_events(),
diff --git a/devices/src/virtio/input/mod.rs b/devices/src/virtio/input/mod.rs
index 30cd724b2..c063164e8 100644
--- a/devices/src/virtio/input/mod.rs
+++ b/devices/src/virtio/input/mod.rs
@@ -12,6 +12,8 @@ use self::constants::*;
use base::{error, warn, AsRawDescriptor, Event, PollToken, RawDescriptor, WaitContext};
use data_model::{DataInit, Le16, Le32};
+use remain::sorted;
+use thiserror::Error;
use vm_memory::GuestMemory;
use self::event_source::{EvdevEventSource, EventSource, SocketEventSource};
@@ -21,7 +23,6 @@ use super::{
};
use linux_input_sys::{virtio_input_event, InputEventDecoder};
use std::collections::BTreeMap;
-use std::fmt::{self, Display};
use std::io::Read;
use std::io::Write;
use std::thread;
@@ -30,64 +31,52 @@ const EVENT_QUEUE_SIZE: u16 = 64;
const STATUS_QUEUE_SIZE: u16 = 64;
const QUEUE_SIZES: &[u16] = &[EVENT_QUEUE_SIZE, STATUS_QUEUE_SIZE];
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum InputError {
- /// Failed to write events to the source
- EventsWriteError(std::io::Error),
- /// Failed to read events from the source
- EventsReadError(std::io::Error),
+ // Virtio descriptor error
+ #[error("virtio descriptor error: {0}")]
+ Descriptor(DescriptorError),
+ // Failed to get axis information of event device
+ #[error("failed to get axis information of event device: {0}")]
+ EvdevAbsInfoError(base::Error),
+ // Failed to get event types supported by device
+ #[error("failed to get event types supported by device: {0}")]
+ EvdevEventTypesError(base::Error),
+ // Failed to grab event device
+ #[error("failed to grab event device: {0}")]
+ EvdevGrabError(base::Error),
// Failed to get name of event device
+ #[error("failed to get id of event device: {0}")]
EvdevIdError(base::Error),
// Failed to get name of event device
+ #[error("failed to get name of event device: {0}")]
EvdevNameError(base::Error),
- // Failed to get serial name of event device
- EvdevSerialError(base::Error),
// Failed to get properties of event device
+ #[error("failed to get properties of event device: {0}")]
EvdevPropertiesError(base::Error),
- // Failed to get event types supported by device
- EvdevEventTypesError(base::Error),
- // Failed to get axis information of event device
- EvdevAbsInfoError(base::Error),
- // Failed to grab event device
- EvdevGrabError(base::Error),
+ // Failed to get serial name of event device
+ #[error("failed to get serial name of event device: {0}")]
+ EvdevSerialError(base::Error),
+ /// Failed to read events from the source
+ #[error("failed to read events from the source: {0}")]
+ EventsReadError(std::io::Error),
+ /// Failed to write events to the source
+ #[error("failed to write events to the source: {0}")]
+ EventsWriteError(std::io::Error),
// Detected error on guest side
+ #[error("detected error on guest side: {0}")]
GuestError(String),
- // Virtio descriptor error
- Descriptor(DescriptorError),
// Error while reading from virtqueue
+ #[error("failed to read from virtqueue: {0}")]
ReadQueue(std::io::Error),
// Error while writing to virtqueue
+ #[error("failed to write to virtqueue: {0}")]
WriteQueue(std::io::Error),
}
pub type Result<T> = std::result::Result<T, InputError>;
-impl Display for InputError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::InputError::*;
-
- match self {
- EventsWriteError(e) => write!(f, "failed to write events to the source: {}", e),
- EventsReadError(e) => write!(f, "failed to read events from the source: {}", e),
- EvdevIdError(e) => write!(f, "failed to get id of event device: {}", e),
- EvdevNameError(e) => write!(f, "failed to get name of event device: {}", e),
- EvdevSerialError(e) => write!(f, "failed to get serial name of event device: {}", e),
- EvdevPropertiesError(e) => write!(f, "failed to get properties of event device: {}", e),
- EvdevEventTypesError(e) => {
- write!(f, "failed to get event types supported by device: {}", e)
- }
- EvdevAbsInfoError(e) => {
- write!(f, "failed to get axis information of event device: {}", e)
- }
- EvdevGrabError(e) => write!(f, "failed to grab event device: {}", e),
- GuestError(s) => write!(f, "detected error on guest side: {}", s),
- Descriptor(e) => write!(f, "virtio descriptor error: {}", e),
- ReadQueue(e) => write!(f, "failed to read from virtqueue: {}", e),
- WriteQueue(e) => write!(f, "failed to write to virtqueue: {}", e),
- }
- }
-}
-
#[derive(Copy, Clone, Default, Debug)]
#[repr(C)]
pub struct virtio_input_device_ids {
@@ -297,7 +286,7 @@ impl VirtioInputConfig {
);
cfg.set_payload_bitmap(&events_bm);
} else if let Some(supported_codes) = self.supported_events.get(&ev_type) {
- cfg.set_payload_bitmap(&supported_codes);
+ cfg.set_payload_bitmap(supported_codes);
}
}
VIRTIO_INPUT_CFG_ABS_INFO => {
@@ -524,7 +513,8 @@ impl<T: EventSource> Worker<T> {
}
}
if needs_interrupt {
- self.interrupt.signal_used_queue(self.event_queue.vector);
+ self.event_queue
+ .trigger_interrupt(&self.guest_memory, &self.interrupt);
}
}
@@ -684,6 +674,7 @@ where
/// Creates a new virtio touch device which supports single touch only.
pub fn new_single_touch<T>(
+ idx: u32,
source: T,
width: u32,
height: u32,
@@ -695,7 +686,7 @@ where
Ok(Input {
kill_evt: None,
worker_thread: None,
- config: defaults::new_single_touch_config(width, height),
+ config: defaults::new_single_touch_config(idx, width, height),
source: Some(SocketEventSource::new(source)),
virtio_features,
})
@@ -703,6 +694,7 @@ where
/// Creates a new virtio touch device which supports multi touch.
pub fn new_multi_touch<T>(
+ idx: u32,
source: T,
width: u32,
height: u32,
@@ -714,7 +706,7 @@ where
Ok(Input {
kill_evt: None,
worker_thread: None,
- config: defaults::new_multi_touch_config(width, height),
+ config: defaults::new_multi_touch_config(idx, width, height),
source: Some(SocketEventSource::new(source)),
virtio_features,
})
@@ -723,6 +715,7 @@ where
/// Creates a new virtio trackpad device which supports (single) touch, primary and secondary
/// buttons as well as X and Y axis.
pub fn new_trackpad<T>(
+ idx: u32,
source: T,
width: u32,
height: u32,
@@ -734,49 +727,61 @@ where
Ok(Input {
kill_evt: None,
worker_thread: None,
- config: defaults::new_trackpad_config(width, height),
+ config: defaults::new_trackpad_config(idx, width, height),
source: Some(SocketEventSource::new(source)),
virtio_features,
})
}
/// Creates a new virtio mouse which supports primary, secondary, wheel and REL events.
-pub fn new_mouse<T>(source: T, virtio_features: u64) -> Result<Input<SocketEventSource<T>>>
+pub fn new_mouse<T>(
+ idx: u32,
+ source: T,
+ virtio_features: u64,
+) -> Result<Input<SocketEventSource<T>>>
where
T: Read + Write + AsRawDescriptor,
{
Ok(Input {
kill_evt: None,
worker_thread: None,
- config: defaults::new_mouse_config(),
+ config: defaults::new_mouse_config(idx),
source: Some(SocketEventSource::new(source)),
virtio_features,
})
}
/// Creates a new virtio keyboard, which supports the same events as an en-us physical keyboard.
-pub fn new_keyboard<T>(source: T, virtio_features: u64) -> Result<Input<SocketEventSource<T>>>
+pub fn new_keyboard<T>(
+ idx: u32,
+ source: T,
+ virtio_features: u64,
+) -> Result<Input<SocketEventSource<T>>>
where
T: Read + Write + AsRawDescriptor,
{
Ok(Input {
kill_evt: None,
worker_thread: None,
- config: defaults::new_keyboard_config(),
+ config: defaults::new_keyboard_config(idx),
source: Some(SocketEventSource::new(source)),
virtio_features,
})
}
/// Creates a new virtio device for switches.
-pub fn new_switches<T>(source: T, virtio_features: u64) -> Result<Input<SocketEventSource<T>>>
+pub fn new_switches<T>(
+ idx: u32,
+ source: T,
+ virtio_features: u64,
+) -> Result<Input<SocketEventSource<T>>>
where
T: Read + Write + AsRawDescriptor,
{
Ok(Input {
kill_evt: None,
worker_thread: None,
- config: defaults::new_switches_config(),
+ config: defaults::new_switches_config(idx),
source: Some(SocketEventSource::new(source)),
virtio_features,
})
diff --git a/devices/src/virtio/interrupt.rs b/devices/src/virtio/interrupt.rs
index 5b52dc505..3ed6ccc7e 100644
--- a/devices/src/virtio/interrupt.rs
+++ b/devices/src/virtio/interrupt.rs
@@ -3,8 +3,11 @@
// found in the LICENSE file.
use super::{INTERRUPT_STATUS_CONFIG_CHANGED, INTERRUPT_STATUS_USED_RING, VIRTIO_MSI_NO_VECTOR};
+use crate::irq_event::IrqLevelEvent;
use crate::pci::MsixConfig;
use base::Event;
+use std::cell::RefCell;
+use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use sync::Mutex;
@@ -31,8 +34,7 @@ pub trait SignalableInterrupt {
pub struct Interrupt {
interrupt_status: Arc<AtomicUsize>,
- interrupt_evt: Event,
- interrupt_resample_evt: Event,
+ interrupt_evt: IrqLevelEvent,
msix_config: Option<Arc<Mutex<MsixConfig>>>,
config_msix_vector: u16,
}
@@ -62,7 +64,7 @@ impl SignalableInterrupt for Interrupt {
== 0
{
// Write to irqfd to inject INTx interrupt
- self.interrupt_evt.write(1).unwrap();
+ self.interrupt_evt.trigger().unwrap();
}
}
@@ -71,36 +73,81 @@ impl SignalableInterrupt for Interrupt {
}
fn get_resample_evt(&self) -> Option<&Event> {
- Some(&self.interrupt_resample_evt)
+ Some(self.interrupt_evt.get_resample())
}
fn do_interrupt_resample(&self) {
if self.interrupt_status.load(Ordering::SeqCst) != 0 {
- self.interrupt_evt.write(1).unwrap();
+ self.interrupt_evt.trigger().unwrap();
}
}
}
+impl<I: SignalableInterrupt> SignalableInterrupt for Arc<Mutex<I>> {
+ fn signal(&self, vector: u16, interrupt_status_mask: u32) {
+ self.lock().signal(vector, interrupt_status_mask);
+ }
+
+ fn signal_used_queue(&self, vector: u16) {
+ self.lock().signal_used_queue(vector);
+ }
+
+ fn signal_config_changed(&self) {
+ self.lock().signal_config_changed();
+ }
+
+ fn get_resample_evt(&self) -> Option<&Event> {
+ // Cannot get resample event from a borrowed item.
+ None
+ }
+
+ fn do_interrupt_resample(&self) {}
+}
+
+impl<I: SignalableInterrupt> SignalableInterrupt for Rc<RefCell<I>> {
+ fn signal(&self, vector: u16, interrupt_status_mask: u32) {
+ self.borrow().signal(vector, interrupt_status_mask);
+ }
+
+ fn signal_used_queue(&self, vector: u16) {
+ self.borrow().signal_used_queue(vector);
+ }
+
+ fn signal_config_changed(&self) {
+ self.borrow().signal_config_changed();
+ }
+
+ fn get_resample_evt(&self) -> Option<&Event> {
+ // Cannot get resample event from a borrowed item.
+ None
+ }
+
+ fn do_interrupt_resample(&self) {}
+}
+
impl Interrupt {
pub fn new(
interrupt_status: Arc<AtomicUsize>,
- interrupt_evt: Event,
- interrupt_resample_evt: Event,
+ interrupt_evt: IrqLevelEvent,
msix_config: Option<Arc<Mutex<MsixConfig>>>,
config_msix_vector: u16,
) -> Interrupt {
Interrupt {
interrupt_status,
interrupt_evt,
- interrupt_resample_evt,
msix_config,
config_msix_vector,
}
}
+ /// Get a reference to the interrupt event.
+ pub fn get_interrupt_evt(&self) -> &Event {
+ self.interrupt_evt.get_trigger()
+ }
+
/// Handle interrupt resampling event, reading the value from the event and doing the resample.
pub fn interrupt_resample(&self) {
- let _ = self.interrupt_resample_evt.read();
+ self.interrupt_evt.clear_resample();
self.do_interrupt_resample();
}
diff --git a/devices/src/virtio/iommu.rs b/devices/src/virtio/iommu.rs
new file mode 100644
index 000000000..16b31821a
--- /dev/null
+++ b/devices/src/virtio/iommu.rs
@@ -0,0 +1,976 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::RefCell;
+use std::collections::BTreeMap;
+use std::fs::File;
+use std::io::{self, Write};
+use std::mem::size_of;
+use std::ops::RangeInclusive;
+use std::rc::Rc;
+use std::sync::Arc;
+use std::{result, thread};
+
+use acpi_tables::sdt::SDT;
+use anyhow::Context;
+use base::{
+ error, pagesize, warn, AsRawDescriptor, Error as SysError, Event, RawDescriptor,
+ Result as SysResult, Tube, TubeError,
+};
+use cros_async::{AsyncError, AsyncTube, EventAsync, Executor};
+use data_model::{DataInit, Le64};
+use futures::{select, FutureExt};
+use remain::sorted;
+use sync::Mutex;
+use thiserror::Error;
+use vm_control::{
+ VirtioIOMMURequest, VirtioIOMMUResponse, VirtioIOMMUVfioCommand, VirtioIOMMUVfioResult,
+};
+use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
+
+use crate::pci::PciAddress;
+use crate::virtio::{
+ async_utils, copy_config, DescriptorChain, DescriptorError, Interrupt, Queue, Reader,
+ SignalableInterrupt, VirtioDevice, Writer, TYPE_IOMMU,
+};
+use crate::VfioContainer;
+
+pub mod protocol;
+use crate::virtio::iommu::protocol::*;
+pub mod ipc_memory_mapper;
+use crate::virtio::iommu::ipc_memory_mapper::*;
+pub mod memory_mapper;
+pub mod memory_util;
+pub mod vfio_wrapper;
+use crate::virtio::iommu::memory_mapper::{Error as MemoryMapperError, *};
+
+use self::vfio_wrapper::VfioWrapper;
+
+const QUEUE_SIZE: u16 = 256;
+const NUM_QUEUES: usize = 2;
+const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
+
+// Size of struct virtio_iommu_probe_property
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+const IOMMU_PROBE_SIZE: usize = size_of::<virtio_iommu_probe_resv_mem>();
+
+const VIRTIO_IOMMU_VIOT_NODE_PCI_RANGE: u8 = 1;
+const VIRTIO_IOMMU_VIOT_NODE_VIRTIO_IOMMU_PCI: u8 = 3;
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuViotHeader {
+ node_count: u16,
+ node_offset: u16,
+ reserved: [u8; 8],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuViotHeader {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuViotVirtioPciNode {
+ type_: u8,
+ reserved: [u8; 1],
+ length: u16,
+ segment: u16,
+ bdf: u16,
+ reserved2: [u8; 8],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuViotVirtioPciNode {}
+
+#[derive(Copy, Clone, Debug, Default)]
+#[repr(C, packed)]
+struct VirtioIommuViotPciRangeNode {
+ type_: u8,
+ reserved: [u8; 1],
+ length: u16,
+ endpoint_start: u32,
+ segment_start: u16,
+ segment_end: u16,
+ bdf_start: u16,
+ bdf_end: u16,
+ output_node: u16,
+ reserved2: [u8; 2],
+ reserved3: [u8; 4],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioIommuViotPciRangeNode {}
+
+type Result<T> = result::Result<T, IommuError>;
+
+#[sorted]
+#[derive(Error, Debug)]
+pub enum IommuError {
+ #[error("async executor error: {0}")]
+ AsyncExec(AsyncError),
+ #[error("failed to create reader: {0}")]
+ CreateReader(DescriptorError),
+ #[error("failed to create wait context: {0}")]
+ CreateWaitContext(SysError),
+ #[error("failed to create writer: {0}")]
+ CreateWriter(DescriptorError),
+ #[error("failed getting host address: {0}")]
+ GetHostAddress(GuestMemoryError),
+ #[error("failed to read from guest address: {0}")]
+ GuestMemoryRead(io::Error),
+ #[error("failed to write to guest address: {0}")]
+ GuestMemoryWrite(io::Error),
+ #[error("memory mapper failed: {0}")]
+ MemoryMapper(MemoryMapperError),
+ #[error("Failed to read descriptor asynchronously: {0}")]
+ ReadAsyncDesc(AsyncError),
+ #[error("failed to read from virtio queue Event: {0}")]
+ ReadQueueEvent(SysError),
+ #[error("tube error: {0}")]
+ Tube(TubeError),
+ #[error("unexpected descriptor error")]
+ UnexpectedDescriptor,
+ #[error("failed to receive virtio-iommu control request: {0}")]
+ VirtioIOMMUReqError(TubeError),
+ #[error("failed to send virtio-iommu control response: {0}")]
+ VirtioIOMMUResponseError(TubeError),
+ #[error("failed to wait for events: {0}")]
+ WaitError(SysError),
+ #[error("write buffer length too small")]
+ WriteBufferTooSmall,
+}
+
+struct Worker {
+ mem: GuestMemory,
+ page_mask: u64,
+ // Hot-pluggable PCI endpoints ranges
+ // RangeInclusive: (start endpoint PCI address .. =end endpoint PCI address)
+ hp_endpoints_ranges: Vec<RangeInclusive<u32>>,
+ // All PCI endpoints that attach to certain IOMMU domain
+ // key: endpoint PCI address
+ // value: attached domain ID
+ endpoint_map: BTreeMap<u32, u32>,
+ // All attached domains
+ // key: domain ID
+ // value: reference counter and MemoryMapperTrait
+ domain_map: BTreeMap<u32, (u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>)>,
+}
+
+impl Worker {
+ // Remove the endpoint from the endpoint_map and
+ // decrement the reference counter (or remove the entry if the ref count is 1)
+ // from domain_map
+ fn detach_endpoint(&mut self, endpoint: u32) {
+ // The endpoint has attached to an IOMMU domain
+ if let Some(attached_domain) = self.endpoint_map.get(&endpoint) {
+ // Remove the entry or update the domain reference count
+ if let Some(dm_val) = self.domain_map.get(attached_domain) {
+ match dm_val.0 {
+ 0 => unreachable!(),
+ 1 => self.domain_map.remove(attached_domain),
+ _ => {
+ let new_refs = dm_val.0 - 1;
+ let vfio = dm_val.1.clone();
+ self.domain_map.insert(*attached_domain, (new_refs, vfio))
+ }
+ };
+ }
+ }
+
+ self.endpoint_map.remove(&endpoint);
+ }
+
+ // Notes: if a VFIO group contains multiple devices, it could violate the follow
+ // requirement from the virtio IOMMU spec: If the VIRTIO_IOMMU_F_BYPASS feature
+ // is negotiated, all accesses from unattached endpoints are allowed and translated
+ // by the IOMMU using the identity function. If the feature is not negotiated, any
+ // memory access from an unattached endpoint fails.
+ //
+ // This happens after the virtio-iommu device receives a VIRTIO_IOMMU_T_ATTACH
+ // request for the first endpoint in a VFIO group, any not yet attached endpoints
+ // in the VFIO group will be able to access the domain.
+ //
+ // This violation is benign for current virtualization use cases. Since device
+ // topology in the guest matches topology in the host, the guest doesn't expect
+ // the device in the same VFIO group are isolated from each other in the first place.
+ fn process_attach_request(
+ &mut self,
+ reader: &mut Reader,
+ tail: &mut virtio_iommu_req_tail,
+ endpoints: &Rc<RefCell<BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>>>,
+ ) -> Result<usize> {
+ let req: virtio_iommu_req_attach =
+ reader.read_obj().map_err(IommuError::GuestMemoryRead)?;
+
+ // If the reserved field of an ATTACH request is not zero,
+ // the device MUST reject the request and set status to
+ // VIRTIO_IOMMU_S_INVAL.
+ if req.reserved.iter().any(|&x| x != 0) {
+ tail.status = VIRTIO_IOMMU_S_INVAL;
+ return Ok(0);
+ }
+
+ // If the endpoint identified by endpoint doesn’t exist,
+ // the device MUST reject the request and set status to
+ // VIRTIO_IOMMU_S_NOENT.
+ let domain: u32 = req.domain.into();
+ let endpoint: u32 = req.endpoint.into();
+ if !endpoints.borrow().contains_key(&endpoint) {
+ tail.status = VIRTIO_IOMMU_S_NOENT;
+ return Ok(0);
+ }
+
+ // If the endpoint identified by endpoint is already attached
+ // to another domain, then the device SHOULD first detach it
+ // from that domain and attach it to the one identified by domain.
+ if self.endpoint_map.contains_key(&endpoint) {
+ self.detach_endpoint(endpoint);
+ }
+
+ if let Some(vfio_container) = endpoints.borrow_mut().get(&endpoint) {
+ let new_ref = match self.domain_map.get(&domain) {
+ None => 1,
+ Some(val) => val.0 + 1,
+ };
+
+ self.endpoint_map.insert(endpoint, domain);
+ self.domain_map
+ .insert(domain, (new_ref, vfio_container.clone()));
+ }
+
+ Ok(0)
+ }
+
+ fn process_dma_map_request(
+ &mut self,
+ reader: &mut Reader,
+ tail: &mut virtio_iommu_req_tail,
+ ) -> Result<usize> {
+ let req: virtio_iommu_req_map = reader.read_obj().map_err(IommuError::GuestMemoryRead)?;
+
+ // If virt_start, phys_start or (virt_end + 1) is not aligned
+ // on the page granularity, the device SHOULD reject the
+ // request and set status to VIRTIO_IOMMU_S_RANGE
+ if self.page_mask & u64::from(req.phys_start) != 0
+ || self.page_mask & u64::from(req.virt_start) != 0
+ || self.page_mask & (u64::from(req.virt_end) + 1) != 0
+ {
+ tail.status = VIRTIO_IOMMU_S_RANGE;
+ return Ok(0);
+ }
+
+ // If the device doesn’t recognize a flags bit, it MUST reject
+ // the request and set status to VIRTIO_IOMMU_S_INVAL.
+ if u32::from(req.flags) & !VIRTIO_IOMMU_MAP_F_MASK != 0 {
+ tail.status = VIRTIO_IOMMU_S_INVAL;
+ return Ok(0);
+ }
+
+ let domain: u32 = req.domain.into();
+ if !self.domain_map.contains_key(&domain) {
+ // If domain does not exist, the device SHOULD reject
+ // the request and set status to VIRTIO_IOMMU_S_NOENT.
+ tail.status = VIRTIO_IOMMU_S_NOENT;
+ return Ok(0);
+ }
+
+ // The device MUST NOT allow writes to a range mapped
+ // without the VIRTIO_IOMMU_MAP_F_WRITE flag.
+ let write_en = u32::from(req.flags) & VIRTIO_IOMMU_MAP_F_WRITE != 0;
+
+ if let Some(mapper) = self.domain_map.get(&domain) {
+ let size = u64::from(req.virt_end) - u64::from(req.virt_start) + 1u64;
+
+ let vfio_map_result = mapper.1.lock().add_map(MappingInfo {
+ iova: req.virt_start.into(),
+ gpa: GuestAddress(req.phys_start.into()),
+ size,
+ perm: match write_en {
+ true => Permission::RW,
+ false => Permission::Read,
+ },
+ });
+
+ match vfio_map_result {
+ Ok(()) => (),
+ Err(e) => match e {
+ MemoryMapperError::IovaRegionOverlap => {
+ // If a mapping already exists in the requested range,
+ // the device SHOULD reject the request and set status
+ // to VIRTIO_IOMMU_S_INVAL.
+ tail.status = VIRTIO_IOMMU_S_INVAL;
+ return Ok(0);
+ }
+ _ => return Err(IommuError::MemoryMapper(e)),
+ },
+ }
+ }
+
+ Ok(0)
+ }
+
+ fn process_dma_unmap_request(
+ &mut self,
+ reader: &mut Reader,
+ tail: &mut virtio_iommu_req_tail,
+ ) -> Result<usize> {
+ let req: virtio_iommu_req_unmap = reader.read_obj().map_err(IommuError::GuestMemoryRead)?;
+
+ let domain: u32 = req.domain.into();
+ if let Some(mapper) = self.domain_map.get(&domain) {
+ let size = u64::from(req.virt_end) - u64::from(req.virt_start) + 1;
+ mapper
+ .1
+ .lock()
+ .remove_map(u64::from(req.virt_start), size)
+ .map_err(IommuError::MemoryMapper)?;
+ } else {
+ // If domain does not exist, the device SHOULD set the
+ // request status to VIRTIO_IOMMU_S_NOENT
+ tail.status = VIRTIO_IOMMU_S_NOENT;
+ }
+
+ Ok(0)
+ }
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ fn process_probe_request(
+ &mut self,
+ reader: &mut Reader,
+ writer: &mut Writer,
+ tail: &mut virtio_iommu_req_tail,
+ endpoints: &Rc<RefCell<BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>>>,
+ ) -> Result<usize> {
+ let req: virtio_iommu_req_probe = reader.read_obj().map_err(IommuError::GuestMemoryRead)?;
+ let endpoint: u32 = req.endpoint.into();
+
+ // If the endpoint identified by endpoint doesn’t exist,
+ // then the device SHOULD reject the request and set status
+ // to VIRTIO_IOMMU_S_NOENT.
+ if !endpoints.borrow().contains_key(&endpoint) {
+ tail.status = VIRTIO_IOMMU_S_NOENT;
+ }
+
+ let properties_size = writer.available_bytes() - size_of::<virtio_iommu_req_tail>();
+
+ // It's OK if properties_size is larger than probe_size
+ // We are good even if properties_size is 0
+ if properties_size < IOMMU_PROBE_SIZE {
+ // If the properties list is smaller than probe_size, the device
+ // SHOULD NOT write any property. It SHOULD reject the request
+ // and set status to VIRTIO_IOMMU_S_INVAL.
+ tail.status = VIRTIO_IOMMU_S_INVAL;
+ } else if tail.status == VIRTIO_IOMMU_S_OK {
+ const VIRTIO_IOMMU_PROBE_T_RESV_MEM: u16 = 1;
+ const VIRTIO_IOMMU_RESV_MEM_T_MSI: u8 = 1;
+ const PROBE_PROPERTY_SIZE: u16 = 4;
+ const X86_MSI_IOVA_START: u64 = 0xfee0_0000;
+ const X86_MSI_IOVA_END: u64 = 0xfeef_ffff;
+
+ let properties = virtio_iommu_probe_resv_mem {
+ head: virtio_iommu_probe_property {
+ type_: VIRTIO_IOMMU_PROBE_T_RESV_MEM.into(),
+ length: (IOMMU_PROBE_SIZE as u16 - PROBE_PROPERTY_SIZE).into(),
+ },
+ subtype: VIRTIO_IOMMU_RESV_MEM_T_MSI,
+ start: X86_MSI_IOVA_START.into(),
+ end: X86_MSI_IOVA_END.into(),
+ ..Default::default()
+ };
+ writer
+ .write_all(properties.as_slice())
+ .map_err(IommuError::GuestMemoryWrite)?;
+ }
+
+ // If the device doesn’t fill all probe_size bytes with properties,
+ // it SHOULD fill the remaining bytes of properties with zeroes.
+ let remaining_bytes = writer.available_bytes() - size_of::<virtio_iommu_req_tail>();
+
+ if remaining_bytes > 0 {
+ let buffer: Vec<u8> = vec![0; remaining_bytes];
+ writer
+ .write_all(buffer.as_slice())
+ .map_err(IommuError::GuestMemoryWrite)?;
+ }
+
+ Ok(properties_size)
+ }
+
+ fn execute_request(
+ &mut self,
+ avail_desc: &DescriptorChain,
+ endpoints: &Rc<RefCell<BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>>>,
+ ) -> Result<usize> {
+ let mut reader =
+ Reader::new(self.mem.clone(), avail_desc.clone()).map_err(IommuError::CreateReader)?;
+ let mut writer =
+ Writer::new(self.mem.clone(), avail_desc.clone()).map_err(IommuError::CreateWriter)?;
+
+ // at least we need space to write VirtioIommuReqTail
+ if writer.available_bytes() < size_of::<virtio_iommu_req_tail>() {
+ return Err(IommuError::WriteBufferTooSmall);
+ }
+
+ let req_head: virtio_iommu_req_head =
+ reader.read_obj().map_err(IommuError::GuestMemoryRead)?;
+
+ let mut tail = virtio_iommu_req_tail {
+ status: VIRTIO_IOMMU_S_OK,
+ ..Default::default()
+ };
+
+ let reply_len = match req_head.type_ {
+ VIRTIO_IOMMU_T_ATTACH => {
+ self.process_attach_request(&mut reader, &mut tail, endpoints)?
+ }
+ VIRTIO_IOMMU_T_DETACH => {
+ // A few reasons why we don't support VIRTIO_IOMMU_T_DETACH for now:
+ //
+ // 1. Linux virtio IOMMU front-end driver doesn't implement VIRTIO_IOMMU_T_DETACH request
+ // 2. Seems it's not possible to dynamically attach and detach a IOMMU domain if the
+ // virtio IOMMU device is running on top of VFIO
+ // 3. Even if VIRTIO_IOMMU_T_DETACH is implemented in front-end driver, it could violate
+ // the following virtio IOMMU spec: Detach an endpoint from a domain. when this request
+ // completes, the endpoint cannot access any mapping from that domain anymore.
+ //
+ // This is because VFIO doesn't support detaching a single device. When the virtio-iommu
+ // device receives a VIRTIO_IOMMU_T_DETACH request, it can either to:
+ // - detach a group: any other endpoints in the group lose access to the domain.
+ // - do not detach the group at all: this breaks the above mentioned spec.
+ tail.status = VIRTIO_IOMMU_S_UNSUPP;
+ 0
+ }
+ VIRTIO_IOMMU_T_MAP => self.process_dma_map_request(&mut reader, &mut tail)?,
+ VIRTIO_IOMMU_T_UNMAP => self.process_dma_unmap_request(&mut reader, &mut tail)?,
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ VIRTIO_IOMMU_T_PROBE => {
+ self.process_probe_request(&mut reader, &mut writer, &mut tail, endpoints)?
+ }
+ _ => return Err(IommuError::UnexpectedDescriptor),
+ };
+
+ writer
+ .write_all(tail.as_slice())
+ .map_err(IommuError::GuestMemoryWrite)?;
+ Ok((reply_len as usize) + size_of::<virtio_iommu_req_tail>())
+ }
+
+ async fn request_queue<I: SignalableInterrupt>(
+ &mut self,
+ mut queue: Queue,
+ mut queue_event: EventAsync,
+ interrupt: &I,
+ endpoints: &Rc<RefCell<BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>>>,
+ ) -> Result<()> {
+ loop {
+ let avail_desc = queue
+ .next_async(&self.mem, &mut queue_event)
+ .await
+ .map_err(IommuError::ReadAsyncDesc)?;
+ let desc_index = avail_desc.index;
+
+ let len = match self.execute_request(&avail_desc, endpoints) {
+ Ok(len) => len as u32,
+ Err(e) => {
+ error!("execute_request failed: {}", e);
+
+ // If a request type is not recognized, the device SHOULD NOT write
+ // the buffer and SHOULD set the used length to zero
+ 0
+ }
+ };
+
+ queue.add_used(&self.mem, desc_index, len as u32);
+ queue.trigger_interrupt(&self.mem, interrupt);
+ }
+ }
+
+ fn handle_add_vfio_device(
+ mem: &GuestMemory,
+ endpoint_addr: u32,
+ container_fd: File,
+ endpoints: &Rc<RefCell<BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>>>,
+ hp_endpoints_ranges: &Rc<Vec<RangeInclusive<u32>>>,
+ ) -> VirtioIOMMUVfioResult {
+ let exists = |endpoint_addr: u32| -> bool {
+ for endpoints_range in hp_endpoints_ranges.iter() {
+ if endpoints_range.contains(&endpoint_addr) {
+ return true;
+ }
+ }
+ false
+ };
+
+ if !exists(endpoint_addr) {
+ return VirtioIOMMUVfioResult::NotInPCIRanges;
+ }
+
+ let vfio_container = match VfioContainer::new_from_container(container_fd) {
+ Ok(vfio_container) => vfio_container,
+ Err(e) => {
+ error!("failed to verify the new container: {}", e);
+ return VirtioIOMMUVfioResult::NoAvailableContainer;
+ }
+ };
+ endpoints.borrow_mut().insert(
+ endpoint_addr,
+ Arc::new(Mutex::new(Box::new(VfioWrapper::new(
+ Arc::new(Mutex::new(vfio_container)),
+ mem.clone(),
+ )))),
+ );
+ VirtioIOMMUVfioResult::Ok
+ }
+
+ fn handle_del_vfio_device(
+ pci_address: u32,
+ endpoints: &Rc<RefCell<BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>>>,
+ ) -> VirtioIOMMUVfioResult {
+ if endpoints.borrow_mut().remove(&pci_address).is_none() {
+ error!("There is no vfio container of {}", pci_address);
+ return VirtioIOMMUVfioResult::NoSuchDevice;
+ }
+ VirtioIOMMUVfioResult::Ok
+ }
+
+ fn handle_vfio(
+ mem: &GuestMemory,
+ vfio_cmd: VirtioIOMMUVfioCommand,
+ endpoints: &Rc<RefCell<BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>>>,
+ hp_endpoints_ranges: &Rc<Vec<RangeInclusive<u32>>>,
+ ) -> VirtioIOMMUResponse {
+ use VirtioIOMMUVfioCommand::*;
+ let vfio_result = match vfio_cmd {
+ VfioDeviceAdd {
+ endpoint_addr,
+ container,
+ } => Self::handle_add_vfio_device(
+ mem,
+ endpoint_addr,
+ container,
+ endpoints,
+ hp_endpoints_ranges,
+ ),
+ VfioDeviceDel { endpoint_addr } => {
+ Self::handle_del_vfio_device(endpoint_addr, endpoints)
+ }
+ };
+ VirtioIOMMUResponse::VfioResponse(vfio_result)
+ }
+
+ // Async task that handles messages from the host
+ async fn handle_command_tube(
+ mem: &GuestMemory,
+ command_tube: AsyncTube,
+ endpoints: &Rc<RefCell<BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>>>,
+ hp_endpoints_ranges: &Rc<Vec<RangeInclusive<u32>>>,
+ ) -> Result<()> {
+ loop {
+ match command_tube.next::<VirtioIOMMURequest>().await {
+ Ok(command) => {
+ let response: VirtioIOMMUResponse = match command {
+ VirtioIOMMURequest::VfioCommand(vfio_cmd) => {
+ Self::handle_vfio(mem, vfio_cmd, endpoints, hp_endpoints_ranges)
+ }
+ };
+ if let Err(e) = command_tube.send(response).await {
+ error!("{}", IommuError::VirtioIOMMUResponseError(e));
+ }
+ }
+ Err(e) => {
+ return Err(IommuError::VirtioIOMMUReqError(e));
+ }
+ }
+ }
+ }
+
+ fn run(
+ &mut self,
+ iommu_device_tube: Tube,
+ mut queues: Vec<Queue>,
+ queue_evts: Vec<Event>,
+ kill_evt: Event,
+ interrupt: Interrupt,
+ endpoints: BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>,
+ translate_response_senders: Option<BTreeMap<u32, Tube>>,
+ translate_request_rx: Option<Tube>,
+ ) -> Result<()> {
+ let ex = Executor::new().expect("Failed to create an executor");
+
+ let mut evts_async: Vec<EventAsync> = queue_evts
+ .into_iter()
+ .map(|e| EventAsync::new(e.0, &ex).expect("Failed to create async event for queue"))
+ .collect();
+ let interrupt = Rc::new(RefCell::new(interrupt));
+ let interrupt_ref = &*interrupt.borrow();
+
+ let (req_queue, req_evt) = (queues.remove(0), evts_async.remove(0));
+
+ let hp_endpoints_ranges = Rc::new(self.hp_endpoints_ranges.clone());
+ let mem = Rc::new(self.mem.clone());
+ // contains all pass-through endpoints that attach to this IOMMU device
+ // key: endpoint PCI address
+ // value: reference counter and MemoryMapperTrait
+ let endpoints: Rc<RefCell<BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>>> =
+ Rc::new(RefCell::new(endpoints));
+
+ let f_resample = async_utils::handle_irq_resample(&ex, interrupt.clone());
+ let f_kill = async_utils::await_and_exit(&ex, kill_evt);
+
+ let request_tube = translate_request_rx
+ .map(|t| AsyncTube::new(&ex, t).expect("Failed to create async tube for rx"));
+ let response_tubes = translate_response_senders.map(|m| {
+ m.into_iter()
+ .map(|x| {
+ (
+ x.0,
+ AsyncTube::new(&ex, x.1).expect("Failed to create async tube"),
+ )
+ })
+ .collect()
+ });
+
+ let f_handle_translate_request =
+ handle_translate_request(&endpoints, request_tube, response_tubes);
+ let f_request = self.request_queue(req_queue, req_evt, interrupt_ref, &endpoints);
+
+ let command_tube = AsyncTube::new(&ex, iommu_device_tube).unwrap();
+ // Future to handle command messages from host, such as passing vfio containers.
+ let f_cmd = Self::handle_command_tube(&mem, command_tube, &endpoints, &hp_endpoints_ranges);
+
+ let done = async {
+ select! {
+ res = f_request.fuse() => res.context("error in handling request queue"),
+ res = f_resample.fuse() => res.context("error in handle_irq_resample"),
+ res = f_kill.fuse() => res.context("error in await_and_exit"),
+ res = f_handle_translate_request.fuse() => res.context("error in handle_translate_request"),
+ res = f_cmd.fuse() => res.context("error in handling host request"),
+ }
+ };
+ match ex.run_until(done) {
+ Ok(Ok(())) => {}
+ Ok(Err(e)) => error!("Error in worker: {}", e),
+ Err(e) => return Err(IommuError::AsyncExec(e)),
+ }
+
+ Ok(())
+ }
+}
+
+async fn handle_translate_request(
+ endpoints: &Rc<RefCell<BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>>>,
+ request_tube: Option<AsyncTube>,
+ response_tubes: Option<BTreeMap<u32, AsyncTube>>,
+) -> Result<()> {
+ let request_tube = match request_tube {
+ Some(r) => r,
+ None => {
+ let () = futures::future::pending().await;
+ return Ok(());
+ }
+ };
+ let response_tubes = response_tubes.unwrap();
+ loop {
+ let TranslateRequest {
+ endpoint_id,
+ iova,
+ size,
+ } = request_tube.next().await.map_err(IommuError::Tube)?;
+ if let Some(mapper) = endpoints.borrow_mut().get(&endpoint_id) {
+ response_tubes
+ .get(&endpoint_id)
+ .unwrap()
+ .send(
+ mapper
+ .lock()
+ .translate(iova, size)
+ .map_err(|e| {
+ error!("Failed to handle TranslateRequest: {}", e);
+ e
+ })
+ .ok(),
+ )
+ .await
+ .map_err(IommuError::Tube)?;
+ } else {
+ error!("endpoint_id {} not found", endpoint_id)
+ }
+ }
+}
+
+/// Virtio device for IOMMU memory management.
+pub struct Iommu {
+ kill_evt: Option<Event>,
+ worker_thread: Option<thread::JoinHandle<Worker>>,
+ config: virtio_iommu_config,
+ avail_features: u64,
+ // Attached endpoints
+ // key: endpoint PCI address
+ // value: reference counter and MemoryMapperTrait
+ endpoints: BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>,
+ // Hot-pluggable PCI endpoints ranges
+ // RangeInclusive: (start endpoint PCI address .. =end endpoint PCI address)
+ hp_endpoints_ranges: Vec<RangeInclusive<u32>>,
+ translate_response_senders: Option<BTreeMap<u32, Tube>>,
+ translate_request_rx: Option<Tube>,
+ iommu_device_tube: Option<Tube>,
+}
+
+impl Iommu {
+ /// Create a new virtio IOMMU device.
+ pub fn new(
+ base_features: u64,
+ endpoints: BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>,
+ phys_max_addr: u64,
+ hp_endpoints_ranges: Vec<RangeInclusive<u32>>,
+ translate_response_senders: Option<BTreeMap<u32, Tube>>,
+ translate_request_rx: Option<Tube>,
+ iommu_device_tube: Option<Tube>,
+ ) -> SysResult<Iommu> {
+ let mut page_size_mask = !0_u64;
+ for (_, container) in endpoints.iter() {
+ page_size_mask &= container
+ .lock()
+ .get_mask()
+ .map_err(|_e| SysError::new(libc::EIO))?;
+ }
+
+ if page_size_mask == 0 {
+ // In case no endpoints bounded to vIOMMU during system booting,
+ // assume IOVA page size is the same as page_size
+ page_size_mask = (pagesize() as u64) - 1;
+ }
+
+ let input_range = virtio_iommu_range_64 {
+ start: Le64::from(0),
+ end: phys_max_addr.into(),
+ };
+
+ let config = virtio_iommu_config {
+ page_size_mask: page_size_mask.into(),
+ input_range,
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ probe_size: (IOMMU_PROBE_SIZE as u32).into(),
+ ..Default::default()
+ };
+
+ let mut avail_features: u64 = base_features;
+ avail_features |= 1 << VIRTIO_IOMMU_F_MAP_UNMAP | 1 << VIRTIO_IOMMU_F_INPUT_RANGE;
+
+ if cfg!(any(target_arch = "x86", target_arch = "x86_64")) {
+ avail_features |= 1 << VIRTIO_IOMMU_F_PROBE;
+ }
+
+ Ok(Iommu {
+ kill_evt: None,
+ worker_thread: None,
+ config,
+ avail_features,
+ endpoints,
+ hp_endpoints_ranges,
+ translate_response_senders,
+ translate_request_rx,
+ iommu_device_tube,
+ })
+ }
+}
+
+impl Drop for Iommu {
+ fn drop(&mut self) {
+ if let Some(kill_evt) = self.kill_evt.take() {
+ let _ = kill_evt.write(1);
+ }
+
+ if let Some(worker_thread) = self.worker_thread.take() {
+ let _ = worker_thread.join();
+ }
+ }
+}
+
+impl VirtioDevice for Iommu {
+ fn keep_rds(&self) -> Vec<RawDescriptor> {
+ let mut rds = Vec::new();
+
+ for (_, mapper) in self.endpoints.iter() {
+ rds.append(&mut mapper.lock().as_raw_descriptors());
+ }
+ if let Some(senders) = &self.translate_response_senders {
+ for (_, tube) in senders.iter() {
+ rds.push(tube.as_raw_descriptor());
+ }
+ }
+ if let Some(rx) = &self.translate_request_rx {
+ rds.push(rx.as_raw_descriptor());
+ }
+
+ if let Some(iommu_device_tube) = &self.iommu_device_tube {
+ rds.push(iommu_device_tube.as_raw_descriptor());
+ }
+
+ rds
+ }
+
+ fn device_type(&self) -> u32 {
+ TYPE_IOMMU
+ }
+
+ fn queue_max_sizes(&self) -> &[u16] {
+ QUEUE_SIZES
+ }
+
+ fn features(&self) -> u64 {
+ self.avail_features
+ }
+
+ fn read_config(&self, offset: u64, data: &mut [u8]) {
+ let mut config: Vec<u8> = Vec::new();
+ config.extend_from_slice(self.config.as_slice());
+ copy_config(data, 0, config.as_slice(), offset);
+ }
+
+ fn activate(
+ &mut self,
+ mem: GuestMemory,
+ interrupt: Interrupt,
+ queues: Vec<Queue>,
+ queue_evts: Vec<Event>,
+ ) {
+ if queues.len() != QUEUE_SIZES.len() || queue_evts.len() != QUEUE_SIZES.len() {
+ return;
+ }
+
+ let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
+ Ok(v) => v,
+ Err(e) => {
+ error!("failed to create kill Event pair: {}", e);
+ return;
+ }
+ };
+ self.kill_evt = Some(self_kill_evt);
+
+ // The least significant bit of page_size_masks defines the page
+ // granularity of IOMMU mappings
+ let page_mask = (1u64 << u64::from(self.config.page_size_mask).trailing_zeros()) - 1;
+ let eps = self.endpoints.clone();
+ let hp_endpoints_ranges = self.hp_endpoints_ranges.to_owned();
+
+ let translate_response_senders = self.translate_response_senders.take();
+ let translate_request_rx = self.translate_request_rx.take();
+
+ match self.iommu_device_tube.take() {
+ Some(iommu_device_tube) => {
+ let worker_result = thread::Builder::new()
+ .name("virtio_iommu".to_string())
+ .spawn(move || {
+ let mut worker = Worker {
+ mem,
+ page_mask,
+ hp_endpoints_ranges,
+ endpoint_map: BTreeMap::new(),
+ domain_map: BTreeMap::new(),
+ };
+ let result = worker.run(
+ iommu_device_tube,
+ queues,
+ queue_evts,
+ kill_evt,
+ interrupt,
+ eps,
+ translate_response_senders,
+ translate_request_rx,
+ );
+ if let Err(e) = result {
+ error!("virtio-iommu worker thread exited with error: {}", e);
+ }
+ worker
+ });
+
+ match worker_result {
+ Err(e) => error!("failed to spawn virtio_iommu worker thread: {}", e),
+ Ok(join_handle) => self.worker_thread = Some(join_handle),
+ }
+ }
+ None => {
+ error!("failed to start virtio-iommu worker: No control tube");
+ return;
+ }
+ }
+ }
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ fn generate_acpi(
+ &mut self,
+ pci_address: &Option<PciAddress>,
+ mut sdts: Vec<SDT>,
+ ) -> Option<Vec<SDT>> {
+ const OEM_REVISION: u32 = 1;
+ const VIOT_REVISION: u8 = 0;
+
+ for sdt in sdts.iter() {
+ // there should only be one VIOT table
+ if sdt.is_signature(b"VIOT") {
+ warn!("vIOMMU: duplicate VIOT table detected");
+ return None;
+ }
+ }
+
+ let mut viot = SDT::new(
+ *b"VIOT",
+ acpi_tables::HEADER_LEN,
+ VIOT_REVISION,
+ *b"CROSVM",
+ *b"CROSVMDT",
+ OEM_REVISION,
+ );
+ viot.append(VirtioIommuViotHeader {
+ // # of PCI range nodes + 1 virtio-pci node
+ node_count: (self.endpoints.len() + self.hp_endpoints_ranges.len() + 1) as u16,
+ node_offset: (viot.len() + std::mem::size_of::<VirtioIommuViotHeader>()) as u16,
+ ..Default::default()
+ });
+
+ let bdf = pci_address
+ .or_else(|| {
+ error!("vIOMMU device has no PCI address");
+ None
+ })?
+ .to_u32() as u16;
+ let iommu_offset = viot.len();
+
+ viot.append(VirtioIommuViotVirtioPciNode {
+ type_: VIRTIO_IOMMU_VIOT_NODE_VIRTIO_IOMMU_PCI,
+ length: size_of::<VirtioIommuViotVirtioPciNode>() as u16,
+ bdf,
+ ..Default::default()
+ });
+
+ for (endpoint, _) in self.endpoints.iter() {
+ viot.append(VirtioIommuViotPciRangeNode {
+ type_: VIRTIO_IOMMU_VIOT_NODE_PCI_RANGE,
+ length: size_of::<VirtioIommuViotPciRangeNode>() as u16,
+ endpoint_start: *endpoint,
+ bdf_start: *endpoint as u16,
+ bdf_end: *endpoint as u16,
+ output_node: iommu_offset as u16,
+ ..Default::default()
+ });
+ }
+
+ for endpoints_range in self.hp_endpoints_ranges.iter() {
+ let (endpoint_start, endpoint_end) = endpoints_range.clone().into_inner();
+ viot.append(VirtioIommuViotPciRangeNode {
+ type_: VIRTIO_IOMMU_VIOT_NODE_PCI_RANGE,
+ length: size_of::<VirtioIommuViotPciRangeNode>() as u16,
+ endpoint_start,
+ bdf_start: endpoint_start as u16,
+ bdf_end: endpoint_end as u16,
+ output_node: iommu_offset as u16,
+ ..Default::default()
+ });
+ }
+
+ sdts.push(viot);
+ Some(sdts)
+ }
+}
diff --git a/devices/src/virtio/iommu/ipc_memory_mapper.rs b/devices/src/virtio/iommu/ipc_memory_mapper.rs
new file mode 100644
index 000000000..eae82966d
--- /dev/null
+++ b/devices/src/virtio/iommu/ipc_memory_mapper.rs
@@ -0,0 +1,145 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Provide utility to communicate with an iommu in another process
+
+use std::ops::Deref;
+use std::result;
+
+use base::{AsRawDescriptor, AsRawDescriptors, RawDescriptor, Tube};
+use serde::{Deserialize, Serialize};
+
+use crate::virtio::memory_mapper::{Error, MemRegion, Translate};
+
+pub type Result<T> = result::Result<T, Error>;
+
+#[derive(Serialize, Deserialize)]
+pub struct TranslateRequest {
+ pub endpoint_id: u32,
+ pub iova: u64,
+ pub size: u64,
+}
+
+/// Sends an addr translation request to another process using `Tube`, and
+/// gets the translated addr from another `Tube`
+pub struct IpcMemoryMapper {
+ request_tx: Tube,
+ response_rx: Tube,
+ endpoint_id: u32,
+}
+
+impl IpcMemoryMapper {
+ /// Returns a new `IpcMemoryMapper` instance.
+ ///
+ /// # Arguments
+ ///
+ /// * `request_tx` - A tube to send `TranslateRequest` to another process.
+ /// * `response_rx` - A tube to receive `Option<Vec<MemRegion>>`
+ /// * `endpoint_id` - For the remote iommu to identify the device/ipc mapper.
+ pub fn new(request_tx: Tube, response_rx: Tube, endpoint_id: u32) -> Self {
+ Self {
+ request_tx,
+ response_rx,
+ endpoint_id,
+ }
+ }
+}
+
+impl Translate for IpcMemoryMapper {
+ fn translate(&self, iova: u64, size: u64) -> Result<Vec<MemRegion>> {
+ let req = TranslateRequest {
+ endpoint_id: self.endpoint_id,
+ iova,
+ size,
+ };
+ self.request_tx.send(&req).map_err(Error::Tube)?;
+ let res: Option<Vec<MemRegion>> = self.response_rx.recv().map_err(Error::Tube)?;
+ res.ok_or(Error::InvalidIOVA(iova, size))
+ }
+}
+
+impl AsRawDescriptors for IpcMemoryMapper {
+ fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
+ vec![
+ self.request_tx.as_raw_descriptor(),
+ self.response_rx.as_raw_descriptor(),
+ ]
+ }
+}
+
+impl Translate for std::sync::MutexGuard<'_, IpcMemoryMapper> {
+ fn translate(&self, iova: u64, size: u64) -> Result<Vec<MemRegion>> {
+ self.deref().translate(iova, size)
+ }
+}
+
+pub struct CreateIpcMapperRet {
+ pub mapper: IpcMemoryMapper,
+ pub response_tx: Tube,
+}
+
+/// Returns a new `IpcMemoryMapper` instance and a response_tx for the iommu
+/// to respond to `TranslateRequest`s.
+///
+/// # Arguments
+///
+/// * `endpoint_id` - For the remote iommu to identify the device/ipc mapper.
+/// * `request_tx` - A tube to send `TranslateRequest` to a remote iommu. This
+/// should be cloned and shared between different ipc mappers
+/// with different `endpoint_id`s.
+pub fn create_ipc_mapper(endpoint_id: u32, request_tx: Tube) -> CreateIpcMapperRet {
+ let (response_tx, response_rx) = Tube::pair().expect("failed to create tube pair");
+ CreateIpcMapperRet {
+ mapper: IpcMemoryMapper::new(request_tx, response_rx, endpoint_id),
+ response_tx,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::virtio::memory_mapper::Permission;
+ use std::thread;
+ use vm_memory::GuestAddress;
+
+ #[test]
+ fn test() {
+ let (request_tx, request_rx) = Tube::pair().expect("failed to create tube pair");
+ let CreateIpcMapperRet {
+ mapper,
+ response_tx,
+ } = create_ipc_mapper(3, request_tx);
+ let user_handle = thread::spawn(move || {
+ assert!(mapper
+ .translate(0x555, 1)
+ .unwrap()
+ .iter()
+ .zip(&vec![MemRegion {
+ gpa: GuestAddress(0x777),
+ len: 1,
+ perm: Permission::RW,
+ },])
+ .all(|(a, b)| a == b));
+ });
+ let iommu_handle = thread::spawn(move || {
+ let TranslateRequest {
+ endpoint_id,
+ iova,
+ size,
+ } = request_rx.recv().unwrap();
+ assert_eq!(endpoint_id, 3);
+ assert_eq!(iova, 0x555);
+ assert_eq!(size, 1);
+ response_tx
+ .send(&Some(vec![MemRegion {
+ gpa: GuestAddress(0x777),
+ len: 1,
+ perm: Permission::RW,
+ }]))
+ .unwrap();
+ });
+ iommu_handle.join().unwrap();
+ user_handle.join().unwrap();
+ }
+}
diff --git a/devices/src/virtio/iommu/memory_mapper.rs b/devices/src/virtio/iommu/memory_mapper.rs
new file mode 100644
index 000000000..411dba9ed
--- /dev/null
+++ b/devices/src/virtio/iommu/memory_mapper.rs
@@ -0,0 +1,729 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! MemoryMapper trait and basic impl for virtio-iommu implementation
+//!
+//! All the addr/range ends in this file are exclusive.
+
+use std::collections::BTreeMap;
+use std::result;
+
+use base::{error, AsRawDescriptors, RawDescriptor, TubeError};
+use remain::sorted;
+use serde::{Deserialize, Serialize};
+use thiserror::Error;
+use vm_memory::{GuestAddress, GuestMemoryError};
+
+use crate::vfio::VfioError;
+use crate::vfio_wrapper::VfioWrapper;
+
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum Permission {
+ Read = 1,
+ Write = 2,
+ RW = 3,
+}
+
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub struct MemRegion {
+ pub gpa: GuestAddress,
+ pub len: u64,
+ pub perm: Permission,
+}
+
+#[sorted]
+#[derive(Error, Debug)]
+pub enum Error {
+ #[error("address not aligned")]
+ AddrNotAligned,
+ #[error("address start ({0} is greater than or equal to end {1}")]
+ AddrStartGeEnd(u64, u64),
+ #[error("failed getting host address: {0}")]
+ GetHostAddress(GuestMemoryError),
+ #[error("integer overflow")]
+ IntegerOverflow,
+ #[error("invalid iova: {0}, length: {1}")]
+ InvalidIOVA(u64, u64),
+ #[error("iommu dma error")]
+ IommuDma,
+ #[error("iova partial overlap")]
+ IovaPartialOverlap,
+ #[error("iova region overlap")]
+ IovaRegionOverlap,
+ #[error("size is zero")]
+ SizeIsZero,
+ #[error("tube error: {0}")]
+ Tube(TubeError),
+ #[error("unimplemented")]
+ Unimplemented,
+ #[error{"vfio error: {0}"}]
+ Vfio(VfioError),
+}
+
+pub type Result<T> = result::Result<T, Error>;
+
+/// Manages the mapping from a guest IO virtual address space to the guest physical address space
+#[derive(Debug)]
+pub struct MappingInfo {
+ pub iova: u64,
+ pub gpa: GuestAddress,
+ pub size: u64,
+ pub perm: Permission,
+}
+
+impl MappingInfo {
+ #[allow(dead_code)]
+ fn new(iova: u64, gpa: GuestAddress, size: u64, perm: Permission) -> Result<Self> {
+ if size == 0 {
+ return Err(Error::SizeIsZero);
+ }
+ iova.checked_add(size).ok_or(Error::IntegerOverflow)?;
+ gpa.checked_add(size).ok_or(Error::IntegerOverflow)?;
+ Ok(Self {
+ iova,
+ gpa,
+ size,
+ perm,
+ })
+ }
+}
+
+// A basic iommu. It is designed as a building block for virtio-iommu.
+pub struct BasicMemoryMapper {
+ maps: BTreeMap<u64, MappingInfo>, // key = MappingInfo.iova
+ mask: u64,
+}
+
+/// A generic interface for vfio and other iommu backends
+pub trait MemoryMapper: Send {
+ fn add_map(&mut self, new_map: MappingInfo) -> Result<()>;
+ fn remove_map(&mut self, iova_start: u64, size: u64) -> Result<()>;
+ fn get_mask(&self) -> Result<u64>;
+
+ /// Trait for generic MemoryMapper abstraction, that is, all reside on MemoryMapper and want to
+ /// be converted back to its original type. Each must provide as_XXX_wrapper() +
+ /// as_XXX_wrapper_mut() + into_XXX_wrapper(), default impl methods return None.
+ fn as_vfio_wrapper(&self) -> Option<&VfioWrapper> {
+ None
+ }
+ fn as_vfio_wrapper_mut(&mut self) -> Option<&mut VfioWrapper> {
+ None
+ }
+ fn into_vfio_wrapper(self: Box<Self>) -> Option<Box<VfioWrapper>> {
+ None
+ }
+}
+
+pub trait Translate {
+ /// Multiple MemRegions should be returned when the gpa is discontiguous or perms are different.
+ fn translate(&self, iova: u64, size: u64) -> Result<Vec<MemRegion>>;
+}
+
+pub trait MemoryMapperTrait: MemoryMapper + Translate + AsRawDescriptors {}
+impl<T: MemoryMapper + Translate + AsRawDescriptors> MemoryMapperTrait for T {}
+
+impl BasicMemoryMapper {
+ pub fn new(mask: u64) -> BasicMemoryMapper {
+ BasicMemoryMapper {
+ maps: BTreeMap::new(),
+ mask,
+ }
+ }
+
+ #[cfg(test)]
+ pub fn len(&self) -> usize {
+ self.maps.len()
+ }
+}
+
+impl MemoryMapper for BasicMemoryMapper {
+ fn add_map(&mut self, new_map: MappingInfo) -> Result<()> {
+ if new_map.size == 0 {
+ return Err(Error::SizeIsZero);
+ }
+ let new_iova_end = new_map
+ .iova
+ .checked_add(new_map.size)
+ .ok_or(Error::IntegerOverflow)?;
+ new_map
+ .gpa
+ .checked_add(new_map.size)
+ .ok_or(Error::IntegerOverflow)?;
+ let mut iter = self.maps.range(..new_iova_end);
+ if let Some((_, map)) = iter.next_back() {
+ if map.iova + map.size > new_map.iova {
+ return Err(Error::IovaRegionOverlap);
+ }
+ }
+ self.maps.insert(new_map.iova, new_map);
+ Ok(())
+ }
+
+ fn remove_map(&mut self, iova_start: u64, size: u64) -> Result<()> {
+ // From the virtio-iommu spec
+ //
+ // If a mapping affected by the range is not covered in its entirety by the
+ // range (the UNMAP request would split the mapping), then the device SHOULD
+ // set the request \field{status} to VIRTIO_IOMMU_S_RANGE, and SHOULD NOT
+ // remove any mapping.
+ //
+ // Therefore, this func checks for partial overlap first before removing the maps.
+ if size == 0 {
+ return Err(Error::SizeIsZero);
+ }
+ let iova_end = iova_start.checked_add(size).ok_or(Error::IntegerOverflow)?;
+
+ let mut to_be_removed = Vec::new();
+ for (key, map) in self.maps.range(..iova_end).rev() {
+ let map_iova_end = map.iova + map.size;
+ if map_iova_end <= iova_start {
+ // no overlap
+ break;
+ }
+ if iova_start <= map.iova && map_iova_end <= iova_end {
+ to_be_removed.push(*key);
+ } else {
+ return Err(Error::IovaPartialOverlap);
+ }
+ }
+ for key in to_be_removed {
+ self.maps.remove(&key).expect("map should contain key");
+ }
+ Ok(())
+ }
+
+ fn get_mask(&self) -> Result<u64> {
+ Ok(self.mask)
+ }
+}
+
+impl Translate for BasicMemoryMapper {
+ /// Regions of contiguous iovas and gpas, and identical permission are merged
+ fn translate(&self, iova: u64, size: u64) -> Result<Vec<MemRegion>> {
+ if size == 0 {
+ return Err(Error::SizeIsZero);
+ }
+ let iova_end = iova.checked_add(size).ok_or(Error::IntegerOverflow)?;
+ let mut iter = self.maps.range(..iova_end);
+ let mut last_iova = iova_end;
+ let mut regions: Vec<MemRegion> = Vec::new();
+ while let Some((_, map)) = iter.next_back() {
+ if last_iova > map.iova + map.size {
+ break;
+ }
+ let mut new_region = true;
+
+ // This is the last region to be inserted / first to be returned when iova >= map.iova
+ let region_len = last_iova - std::cmp::max::<u64>(map.iova, iova);
+ if let Some(last) = regions.last_mut() {
+ if map.gpa.unchecked_add(map.size) == last.gpa && map.perm == last.perm {
+ last.gpa = map.gpa;
+ last.len += region_len;
+ new_region = false;
+ }
+ }
+ if new_region {
+ // If this is the only region to be returned, region_len == size (arg of this
+ // function)
+ // iova_end = iova + size
+ // last_iova = iova_end
+ // region_len = last_iova - max(map.iova, iova)
+ // = iova + size - iova
+ // = size
+ regions.push(MemRegion {
+ gpa: map.gpa,
+ len: region_len,
+ perm: map.perm,
+ });
+ }
+ if iova >= map.iova {
+ regions.reverse();
+ // The gpa of the first region has to be offseted
+ regions[0].gpa = map
+ .gpa
+ .checked_add(iova - map.iova)
+ .ok_or(Error::IntegerOverflow)?;
+ return Ok(regions);
+ }
+ last_iova = map.iova;
+ }
+
+ Err(Error::InvalidIOVA(iova, size))
+ }
+}
+
+impl AsRawDescriptors for BasicMemoryMapper {
+ fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
+ Vec::new()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::fmt::Debug;
+
+ #[test]
+ fn test_mapping_info() {
+ // Overflow
+ MappingInfo::new(u64::MAX - 1, GuestAddress(1), 2, Permission::Read).unwrap_err();
+ MappingInfo::new(1, GuestAddress(u64::MAX - 1), 2, Permission::Read).unwrap_err();
+ MappingInfo::new(u64::MAX, GuestAddress(1), 2, Permission::Read).unwrap_err();
+ MappingInfo::new(1, GuestAddress(u64::MAX), 2, Permission::Read).unwrap_err();
+ MappingInfo::new(5, GuestAddress(5), u64::MAX, Permission::Read).unwrap_err();
+ // size = 0
+ MappingInfo::new(1, GuestAddress(5), 0, Permission::Read).unwrap_err();
+ }
+
+ #[test]
+ fn test_map_overlap() {
+ let mut mapper = BasicMemoryMapper::new(u64::MAX);
+ mapper
+ .add_map(MappingInfo::new(10, GuestAddress(1000), 10, Permission::RW).unwrap())
+ .unwrap();
+ mapper
+ .add_map(MappingInfo::new(14, GuestAddress(1000), 1, Permission::RW).unwrap())
+ .unwrap_err();
+ mapper
+ .add_map(MappingInfo::new(0, GuestAddress(1000), 12, Permission::RW).unwrap())
+ .unwrap_err();
+ mapper
+ .add_map(MappingInfo::new(16, GuestAddress(1000), 6, Permission::RW).unwrap())
+ .unwrap_err();
+ mapper
+ .add_map(MappingInfo::new(5, GuestAddress(1000), 20, Permission::RW).unwrap())
+ .unwrap_err();
+ }
+
+ #[test]
+ // This test is taken from the virtio_iommu spec with translate() calls added
+ fn test_map_unmap() {
+ // #1
+ {
+ let mut mapper = BasicMemoryMapper::new(u64::MAX);
+ mapper.remove_map(0, 4).unwrap();
+ }
+ // #2
+ {
+ let mut mapper = BasicMemoryMapper::new(u64::MAX);
+ mapper
+ .add_map(MappingInfo::new(0, GuestAddress(1000), 9, Permission::RW).unwrap())
+ .unwrap();
+ assert_eq!(
+ mapper.translate(0, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1000),
+ len: 1,
+ perm: Permission::RW
+ }
+ );
+ assert_eq!(
+ mapper.translate(8, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1008),
+ len: 1,
+ perm: Permission::RW
+ }
+ );
+ mapper.translate(9, 1).unwrap_err();
+ mapper.remove_map(0, 9).unwrap();
+ mapper.translate(0, 1).unwrap_err();
+ }
+ // #3
+ {
+ let mut mapper = BasicMemoryMapper::new(u64::MAX);
+ mapper
+ .add_map(MappingInfo::new(0, GuestAddress(1000), 4, Permission::RW).unwrap())
+ .unwrap();
+ mapper
+ .add_map(MappingInfo::new(5, GuestAddress(50), 4, Permission::RW).unwrap())
+ .unwrap();
+ assert_eq!(
+ mapper.translate(0, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1000),
+ len: 1,
+ perm: Permission::RW
+ }
+ );
+ assert_eq!(
+ mapper.translate(6, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(51),
+ len: 1,
+ perm: Permission::RW
+ }
+ );
+ mapper.remove_map(0, 9).unwrap();
+ mapper.translate(0, 1).unwrap_err();
+ mapper.translate(6, 1).unwrap_err();
+ }
+ // #4
+ {
+ let mut mapper = BasicMemoryMapper::new(u64::MAX);
+ mapper
+ .add_map(MappingInfo::new(0, GuestAddress(1000), 9, Permission::RW).unwrap())
+ .unwrap();
+ mapper.remove_map(0, 4).unwrap_err();
+ assert_eq!(
+ mapper.translate(5, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1005),
+ len: 1,
+ perm: Permission::RW
+ }
+ );
+ }
+ // #5
+ {
+ let mut mapper = BasicMemoryMapper::new(u64::MAX);
+ mapper
+ .add_map(MappingInfo::new(0, GuestAddress(1000), 4, Permission::RW).unwrap())
+ .unwrap();
+ mapper
+ .add_map(MappingInfo::new(5, GuestAddress(50), 4, Permission::RW).unwrap())
+ .unwrap();
+ assert_eq!(
+ mapper.translate(0, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1000),
+ len: 1,
+ perm: Permission::RW
+ }
+ );
+ assert_eq!(
+ mapper.translate(5, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(50),
+ len: 1,
+ perm: Permission::RW
+ }
+ );
+ mapper.remove_map(0, 4).unwrap();
+ mapper.translate(0, 1).unwrap_err();
+ mapper.translate(4, 1).unwrap_err();
+ assert_eq!(
+ mapper.translate(5, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(50),
+ len: 1,
+ perm: Permission::RW
+ }
+ );
+ }
+ // #6
+ {
+ let mut mapper = BasicMemoryMapper::new(u64::MAX);
+ mapper
+ .add_map(MappingInfo::new(0, GuestAddress(1000), 4, Permission::RW).unwrap())
+ .unwrap();
+ assert_eq!(
+ mapper.translate(0, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1000),
+ len: 1,
+ perm: Permission::RW
+ }
+ );
+ mapper.translate(9, 1).unwrap_err();
+ mapper.remove_map(0, 9).unwrap();
+ mapper.translate(0, 1).unwrap_err();
+ mapper.translate(9, 1).unwrap_err();
+ }
+ // #7
+ {
+ let mut mapper = BasicMemoryMapper::new(u64::MAX);
+ mapper
+ .add_map(MappingInfo::new(0, GuestAddress(1000), 4, Permission::Read).unwrap())
+ .unwrap();
+ mapper
+ .add_map(MappingInfo::new(10, GuestAddress(50), 4, Permission::RW).unwrap())
+ .unwrap();
+ assert_eq!(
+ mapper.translate(0, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1000),
+ len: 1,
+ perm: Permission::Read
+ }
+ );
+ assert_eq!(
+ mapper.translate(3, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1003),
+ len: 1,
+ perm: Permission::Read
+ }
+ );
+ mapper.translate(4, 1).unwrap_err();
+ assert_eq!(
+ mapper.translate(10, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(50),
+ len: 1,
+ perm: Permission::RW
+ }
+ );
+ assert_eq!(
+ mapper.translate(13, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(53),
+ len: 1,
+ perm: Permission::RW
+ }
+ );
+ mapper.remove_map(0, 14).unwrap();
+ mapper.translate(0, 1).unwrap_err();
+ mapper.translate(3, 1).unwrap_err();
+ mapper.translate(4, 1).unwrap_err();
+ mapper.translate(10, 1).unwrap_err();
+ mapper.translate(13, 1).unwrap_err();
+ }
+ }
+ #[test]
+ fn test_remove_map() {
+ let mut mapper = BasicMemoryMapper::new(u64::MAX);
+ mapper
+ .add_map(MappingInfo::new(1, GuestAddress(1000), 4, Permission::Read).unwrap())
+ .unwrap();
+ mapper
+ .add_map(MappingInfo::new(5, GuestAddress(50), 4, Permission::RW).unwrap())
+ .unwrap();
+ mapper
+ .add_map(MappingInfo::new(9, GuestAddress(50), 4, Permission::RW).unwrap())
+ .unwrap();
+ assert_eq!(mapper.len(), 3);
+ mapper.remove_map(0, 6).unwrap_err();
+ assert_eq!(mapper.len(), 3);
+ mapper.remove_map(1, 5).unwrap_err();
+ assert_eq!(mapper.len(), 3);
+ mapper.remove_map(1, 9).unwrap_err();
+ assert_eq!(mapper.len(), 3);
+ mapper.remove_map(6, 4).unwrap_err();
+ assert_eq!(mapper.len(), 3);
+ mapper.remove_map(6, 14).unwrap_err();
+ assert_eq!(mapper.len(), 3);
+ mapper.remove_map(5, 4).unwrap();
+ assert_eq!(mapper.len(), 2);
+ mapper.remove_map(1, 9).unwrap_err();
+ assert_eq!(mapper.len(), 2);
+ mapper.remove_map(0, 15).unwrap();
+ assert_eq!(mapper.len(), 0);
+ }
+
+ fn assert_vec_eq<T: std::cmp::PartialEq + Debug>(a: Vec<T>, b: Vec<T>) {
+ assert_eq!(a.len(), b.len());
+ for (x, y) in a.into_iter().zip(b.into_iter()) {
+ assert_eq!(x, y);
+ }
+ }
+
+ #[test]
+ fn test_translate_len() {
+ let mut mapper = BasicMemoryMapper::new(u64::MAX);
+ // [1, 5) -> [1000, 1004)
+ mapper
+ .add_map(MappingInfo::new(1, GuestAddress(1000), 4, Permission::Read).unwrap())
+ .unwrap();
+ mapper.translate(1, 0).unwrap_err();
+ assert_eq!(
+ mapper.translate(1, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1000),
+ len: 1,
+ perm: Permission::Read
+ }
+ );
+ assert_eq!(
+ mapper.translate(1, 2).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1000),
+ len: 2,
+ perm: Permission::Read
+ }
+ );
+ assert_eq!(
+ mapper.translate(1, 3).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1000),
+ len: 3,
+ perm: Permission::Read
+ }
+ );
+ assert_eq!(
+ mapper.translate(2, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1001),
+ len: 1,
+ perm: Permission::Read
+ }
+ );
+ assert_eq!(
+ mapper.translate(2, 2).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1001),
+ len: 2,
+ perm: Permission::Read
+ }
+ );
+ mapper.translate(1, 5).unwrap_err();
+ // [1, 9) -> [1000, 1008)
+ mapper
+ .add_map(MappingInfo::new(5, GuestAddress(1004), 4, Permission::Read).unwrap())
+ .unwrap();
+ // Spanned across 2 maps
+ assert_eq!(
+ mapper.translate(2, 5).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1001),
+ len: 5,
+ perm: Permission::Read
+ }
+ );
+ assert_eq!(
+ mapper.translate(2, 6).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1001),
+ len: 6,
+ perm: Permission::Read
+ }
+ );
+ assert_eq!(
+ mapper.translate(2, 7).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1001),
+ len: 7,
+ perm: Permission::Read
+ }
+ );
+ mapper.translate(2, 8).unwrap_err();
+ mapper.translate(3, 10).unwrap_err();
+ // [1, 9) -> [1000, 1008), [11, 17) -> [1010, 1016)
+ mapper
+ .add_map(MappingInfo::new(11, GuestAddress(1010), 6, Permission::Read).unwrap())
+ .unwrap();
+ // Discontiguous iova
+ mapper.translate(3, 10).unwrap_err();
+ // [1, 17) -> [1000, 1016)
+ mapper
+ .add_map(MappingInfo::new(9, GuestAddress(1008), 2, Permission::Read).unwrap())
+ .unwrap();
+ // Spanned across 4 maps
+ assert_eq!(
+ mapper.translate(3, 10).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1002),
+ len: 10,
+ perm: Permission::Read
+ }
+ );
+ assert_eq!(
+ mapper.translate(1, 16).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(1000),
+ len: 16,
+ perm: Permission::Read
+ }
+ );
+ mapper.translate(1, 17).unwrap_err();
+ mapper.translate(0, 16).unwrap_err();
+ // [0, 1) -> [5, 6), [1, 17) -> [1000, 1016)
+ mapper
+ .add_map(MappingInfo::new(0, GuestAddress(5), 1, Permission::Read).unwrap())
+ .unwrap();
+ assert_eq!(
+ mapper.translate(0, 1).unwrap()[0],
+ MemRegion {
+ gpa: GuestAddress(5),
+ len: 1,
+ perm: Permission::Read
+ }
+ );
+ // Discontiguous gpa
+ assert_vec_eq(
+ mapper.translate(0, 2).unwrap(),
+ vec![
+ MemRegion {
+ gpa: GuestAddress(5),
+ len: 1,
+ perm: Permission::Read,
+ },
+ MemRegion {
+ gpa: GuestAddress(1000),
+ len: 1,
+ perm: Permission::Read,
+ },
+ ],
+ );
+ assert_vec_eq(
+ mapper.translate(0, 16).unwrap(),
+ vec![
+ MemRegion {
+ gpa: GuestAddress(5),
+ len: 1,
+ perm: Permission::Read,
+ },
+ MemRegion {
+ gpa: GuestAddress(1000),
+ len: 15,
+ perm: Permission::Read,
+ },
+ ],
+ );
+ // [0, 1) -> [5, 6), [1, 17) -> [1000, 1016), [17, 18) -> [1016, 1017) <RW>
+ mapper
+ .add_map(MappingInfo::new(17, GuestAddress(1016), 2, Permission::RW).unwrap())
+ .unwrap();
+ // Contiguous iova and gpa, but different perm
+ assert_vec_eq(
+ mapper.translate(1, 17).unwrap(),
+ vec![
+ MemRegion {
+ gpa: GuestAddress(1000),
+ len: 16,
+ perm: Permission::Read,
+ },
+ MemRegion {
+ gpa: GuestAddress(1016),
+ len: 1,
+ perm: Permission::RW,
+ },
+ ],
+ );
+ // Contiguous iova and gpa, but different perm
+ assert_vec_eq(
+ mapper.translate(2, 16).unwrap(),
+ vec![
+ MemRegion {
+ gpa: GuestAddress(1001),
+ len: 15,
+ perm: Permission::Read,
+ },
+ MemRegion {
+ gpa: GuestAddress(1016),
+ len: 1,
+ perm: Permission::RW,
+ },
+ ],
+ );
+ assert_vec_eq(
+ mapper.translate(2, 17).unwrap(),
+ vec![
+ MemRegion {
+ gpa: GuestAddress(1001),
+ len: 15,
+ perm: Permission::Read,
+ },
+ MemRegion {
+ gpa: GuestAddress(1016),
+ len: 2,
+ perm: Permission::RW,
+ },
+ ],
+ );
+ mapper.translate(2, 500).unwrap_err();
+ mapper.translate(500, 5).unwrap_err();
+ }
+}
diff --git a/devices/src/virtio/iommu/memory_util.rs b/devices/src/virtio/iommu/memory_util.rs
new file mode 100644
index 000000000..c0df37d8c
--- /dev/null
+++ b/devices/src/virtio/iommu/memory_util.rs
@@ -0,0 +1,137 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Utilities to access GuestMemory with IO virtual addresses and iommu
+
+use std::convert::TryInto;
+use std::sync::Arc;
+use sync::Mutex;
+
+use anyhow::{bail, Context};
+use data_model::DataInit;
+use vm_memory::{GuestAddress, GuestMemory};
+
+use crate::virtio::iommu::IpcMemoryMapper;
+use crate::virtio::memory_mapper::{Permission, Translate};
+
+/// A wrapper that works with gpa, or iova and an iommu.
+pub fn is_valid_wrapper<T: Translate>(
+ mem: &GuestMemory,
+ iommu: &Option<T>,
+ addr: GuestAddress,
+ size: u64,
+) -> anyhow::Result<bool> {
+ if let Some(iommu) = iommu {
+ is_valid(mem, iommu, addr.offset(), size)
+ } else {
+ Ok(addr
+ .checked_add(size as u64)
+ .map_or(false, |v| mem.address_in_range(v)))
+ }
+}
+
+/// Translates `iova` into gpa regions (or 1 gpa region when it is contiguous), and check if the
+/// gpa regions are all valid in `mem`.
+pub fn is_valid<T: Translate>(
+ mem: &GuestMemory,
+ iommu: &T,
+ iova: u64,
+ size: u64,
+) -> anyhow::Result<bool> {
+ match iommu.translate(iova, size) {
+ Ok(regions) => {
+ for r in regions {
+ if !mem.address_in_range(r.gpa) || mem.checked_offset(r.gpa, r.len).is_none() {
+ return Ok(false);
+ }
+ }
+ }
+ Err(e) => bail!("failed to translate iova to gpa: {}", e),
+ }
+ Ok(true)
+}
+
+/// A wrapper that works with gpa, or iova and an iommu.
+pub fn read_obj_from_addr_wrapper<T: DataInit>(
+ mem: &GuestMemory,
+ iommu: &Option<Arc<Mutex<IpcMemoryMapper>>>,
+ addr: GuestAddress,
+) -> anyhow::Result<T> {
+ if let Some(iommu) = iommu {
+ read_obj_from_addr::<T>(mem, &iommu.lock(), addr.offset())
+ } else {
+ mem.read_obj_from_addr::<T>(addr)
+ .context("read_obj_from_addr failed")
+ }
+}
+
+/// A version of `GuestMemory::read_obj_from_addr` that works with iova and iommu.
+pub fn read_obj_from_addr<T: DataInit>(
+ mem: &GuestMemory,
+ iommu: &IpcMemoryMapper,
+ iova: u64,
+) -> anyhow::Result<T> {
+ let regions = iommu
+ .translate(
+ iova,
+ std::mem::size_of::<T>()
+ .try_into()
+ .context("u64 doesn't fit in usize")?,
+ )
+ .context("failed to translate iova to gpa")?;
+ let mut buf = vec![0u8; std::mem::size_of::<T>()];
+ let mut addr: usize = 0;
+ for r in regions {
+ if (r.perm as u8 & Permission::Read as u8) == 0 {
+ bail!("gpa is not readable");
+ }
+ mem.read_at_addr(&mut buf[addr..(addr + r.len as usize)], r.gpa)
+ .context("failed to read from gpa")?;
+ addr += r.len as usize;
+ }
+ Ok(*T::from_slice(&buf).context("failed to construct obj")?)
+}
+
+/// A wrapper that works with gpa, or iova and an iommu.
+pub fn write_obj_at_addr_wrapper<T: DataInit>(
+ mem: &GuestMemory,
+ iommu: &Option<Arc<Mutex<IpcMemoryMapper>>>,
+ val: T,
+ addr: GuestAddress,
+) -> anyhow::Result<()> {
+ if let Some(iommu) = iommu {
+ write_obj_at_addr(mem, &iommu.lock(), val, addr.offset())
+ } else {
+ mem.write_obj_at_addr(val, addr)
+ .context("write_obj_at_addr failed")
+ }
+}
+
+/// A version of `GuestMemory::write_obj_at_addr` that works with iova and iommu.
+pub fn write_obj_at_addr<T: DataInit>(
+ mem: &GuestMemory,
+ iommu: &IpcMemoryMapper,
+ val: T,
+ iova: u64,
+) -> anyhow::Result<()> {
+ let regions = iommu
+ .translate(
+ iova,
+ std::mem::size_of::<T>()
+ .try_into()
+ .context("u64 doesn't fit in usize")?,
+ )
+ .context("failed to translate iova to gpa")?;
+ let buf = val.as_slice();
+ let mut addr: usize = 0;
+ for r in regions {
+ if (r.perm as u8 & Permission::Read as u8) == 0 {
+ bail!("gpa is not writable");
+ }
+ mem.write_at_addr(&buf[addr..(addr + (r.len as usize))], r.gpa)
+ .context("failed to write to gpa")?;
+ addr += r.len as usize;
+ }
+ Ok(())
+}
diff --git a/devices/src/virtio/iommu/protocol.rs b/devices/src/virtio/iommu/protocol.rs
new file mode 100644
index 000000000..93356f07d
--- /dev/null
+++ b/devices/src/virtio/iommu/protocol.rs
@@ -0,0 +1,174 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! This file was generated by the following commands and modified manually.
+//!
+//! ```shell
+//! $ bindgen virtio_iommu.h \
+//! --allowlist-type "virtio_iommu.*" \
+//! --allowlist-var "VIRTIO_IOMMU_.*" \
+//! --with-derive-default \
+//! --no-layout-tests \
+//! --no-prepend-enum-name > protocol.rs
+//! $ sed -i "s/__u/u/g" protocol.rs
+//! $ sed -i "s/__le/Le/g" protocol.rs
+//!
+//! The main points of the manual modifications are as follows:
+//! * Removed `head` and `tail` from each command struct. Instead, we process
+//! them as separate payloads.
+//! * Added implementations of DataInit for each struct.
+//! * Use of `packed` because removing `head` and `tail` introduces paddings
+//! * Remove `IncompleteArrayField`
+//! * Convert padding of [u8; 64usize] to [u64; 8usize]. According to the rust
+//! doc, "Arrays of sizes from 0 to 32 (inclusive) implement the Default trait
+//! if the element type allows it."
+
+#![allow(non_camel_case_types)]
+
+use data_model::{DataInit, Le16, Le32, Le64};
+
+pub const VIRTIO_IOMMU_F_INPUT_RANGE: u32 = 0;
+pub const VIRTIO_IOMMU_F_DOMAIN_RANGE: u32 = 1;
+pub const VIRTIO_IOMMU_F_MAP_UNMAP: u32 = 2;
+pub const VIRTIO_IOMMU_F_BYPASS: u32 = 3;
+pub const VIRTIO_IOMMU_F_PROBE: u32 = 4;
+pub const VIRTIO_IOMMU_F_MMIO: u32 = 5;
+pub const VIRTIO_IOMMU_T_ATTACH: u8 = 1;
+pub const VIRTIO_IOMMU_T_DETACH: u8 = 2;
+pub const VIRTIO_IOMMU_T_MAP: u8 = 3;
+pub const VIRTIO_IOMMU_T_UNMAP: u8 = 4;
+pub const VIRTIO_IOMMU_T_PROBE: u8 = 5;
+pub const VIRTIO_IOMMU_S_OK: u8 = 0;
+pub const VIRTIO_IOMMU_S_IOERR: u8 = 1;
+pub const VIRTIO_IOMMU_S_UNSUPP: u8 = 2;
+pub const VIRTIO_IOMMU_S_DEVERR: u8 = 3;
+pub const VIRTIO_IOMMU_S_INVAL: u8 = 4;
+pub const VIRTIO_IOMMU_S_RANGE: u8 = 5;
+pub const VIRTIO_IOMMU_S_NOENT: u8 = 6;
+pub const VIRTIO_IOMMU_S_FAULT: u8 = 7;
+pub const VIRTIO_IOMMU_S_NOMEM: u8 = 8;
+pub const VIRTIO_IOMMU_MAP_F_READ: u32 = 1;
+pub const VIRTIO_IOMMU_MAP_F_WRITE: u32 = 2;
+pub const VIRTIO_IOMMU_MAP_F_MMIO: u32 = 4;
+pub const VIRTIO_IOMMU_MAP_F_MASK: u32 = 7;
+pub const VIRTIO_IOMMU_PROBE_T_NONE: u32 = 0;
+pub const VIRTIO_IOMMU_PROBE_T_RESV_MEM: u32 = 1;
+pub const VIRTIO_IOMMU_PROBE_T_MASK: u32 = 4095;
+pub const VIRTIO_IOMMU_RESV_MEM_T_RESERVED: u32 = 0;
+pub const VIRTIO_IOMMU_RESV_MEM_T_MSI: u32 = 1;
+pub const VIRTIO_IOMMU_FAULT_R_UNKNOWN: u32 = 0;
+pub const VIRTIO_IOMMU_FAULT_R_DOMAIN: u32 = 1;
+pub const VIRTIO_IOMMU_FAULT_R_MAPPING: u32 = 2;
+pub const VIRTIO_IOMMU_FAULT_F_READ: u32 = 1;
+pub const VIRTIO_IOMMU_FAULT_F_WRITE: u32 = 2;
+pub const VIRTIO_IOMMU_FAULT_F_EXEC: u32 = 4;
+pub const VIRTIO_IOMMU_FAULT_F_ADDRESS: u32 = 256;
+
+#[repr(C, packed)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_iommu_range_64 {
+ pub start: Le64,
+ pub end: Le64,
+}
+unsafe impl DataInit for virtio_iommu_range_64 {}
+#[repr(C, packed)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_iommu_range_32 {
+ pub start: Le32,
+ pub end: Le32,
+}
+unsafe impl DataInit for virtio_iommu_range_32 {}
+#[repr(C, packed)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_iommu_config {
+ pub page_size_mask: Le64,
+ pub input_range: virtio_iommu_range_64,
+ pub domain_range: virtio_iommu_range_32,
+ pub probe_size: Le32,
+}
+unsafe impl DataInit for virtio_iommu_config {}
+#[repr(C, packed)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_iommu_req_head {
+ pub type_: u8,
+ pub reserved: [u8; 3usize],
+}
+unsafe impl DataInit for virtio_iommu_req_head {}
+#[repr(C, packed)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_iommu_req_tail {
+ pub status: u8,
+ pub reserved: [u8; 3usize],
+}
+unsafe impl DataInit for virtio_iommu_req_tail {}
+#[repr(C, packed)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_iommu_req_attach {
+ pub domain: Le32,
+ pub endpoint: Le32,
+ pub reserved: [u8; 8usize],
+}
+unsafe impl DataInit for virtio_iommu_req_attach {}
+#[repr(C, packed)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_iommu_req_detach {
+ pub domain: Le32,
+ pub endpoint: Le32,
+ pub reserved: [u8; 8usize],
+}
+unsafe impl DataInit for virtio_iommu_req_detach {}
+#[repr(C, packed)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_iommu_req_map {
+ pub domain: Le32,
+ pub virt_start: Le64,
+ pub virt_end: Le64,
+ pub phys_start: Le64,
+ pub flags: Le32,
+}
+unsafe impl DataInit for virtio_iommu_req_map {}
+#[repr(C, packed)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_iommu_req_unmap {
+ pub domain: Le32,
+ pub virt_start: Le64,
+ pub virt_end: Le64,
+ pub reserved: [u8; 4usize],
+}
+unsafe impl DataInit for virtio_iommu_req_unmap {}
+#[repr(C, packed)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_iommu_probe_property {
+ pub type_: Le16,
+ pub length: Le16,
+}
+unsafe impl DataInit for virtio_iommu_probe_property {}
+#[repr(C, packed)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_iommu_probe_resv_mem {
+ pub head: virtio_iommu_probe_property,
+ pub subtype: u8,
+ pub reserved: [u8; 3usize],
+ pub start: Le64,
+ pub end: Le64,
+}
+unsafe impl DataInit for virtio_iommu_probe_resv_mem {}
+#[repr(C, packed)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_iommu_req_probe {
+ pub endpoint: Le32,
+ pub reserved: [u64; 8usize],
+}
+unsafe impl DataInit for virtio_iommu_req_probe {}
+#[repr(C, packed)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_iommu_fault {
+ pub reason: u8,
+ pub reserved: [u8; 3usize],
+ pub flags: Le32,
+ pub endpoint: Le32,
+ pub reserved2: [u8; 4usize],
+ pub address: Le64,
+}
+unsafe impl DataInit for virtio_iommu_fault {}
diff --git a/devices/src/virtio/iommu/vfio_wrapper.rs b/devices/src/virtio/iommu/vfio_wrapper.rs
new file mode 100644
index 000000000..a25ab66f8
--- /dev/null
+++ b/devices/src/virtio/iommu/vfio_wrapper.rs
@@ -0,0 +1,99 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Wraps VfioContainer for virtio-iommu implementation
+
+use std::sync::Arc;
+
+use base::{AsRawDescriptor, AsRawDescriptors, RawDescriptor};
+use sync::Mutex;
+use vm_memory::{GuestAddress, GuestMemory};
+
+use crate::virtio::iommu::memory_mapper::{
+ Error as MemoryMapperError, MappingInfo, MemRegion, MemoryMapper, Permission, Translate,
+};
+use crate::VfioContainer;
+
+pub struct VfioWrapper {
+ container: Arc<Mutex<VfioContainer>>,
+ mem: GuestMemory,
+}
+
+impl VfioWrapper {
+ pub fn new(container: Arc<Mutex<VfioContainer>>, mem: GuestMemory) -> Self {
+ Self { container, mem }
+ }
+
+ pub fn as_vfio_container(&self) -> Arc<Mutex<VfioContainer>> {
+ self.container.clone()
+ }
+}
+
+impl MemoryMapper for VfioWrapper {
+ fn add_map(&mut self, mut map: MappingInfo) -> Result<(), MemoryMapperError> {
+ map.gpa = GuestAddress(
+ self.mem
+ .get_host_address_range(map.gpa, map.size as usize)
+ .map_err(MemoryMapperError::GetHostAddress)? as u64,
+ );
+ // Safe because both guest and host address are guaranteed by
+ // get_host_address_range() to be valid.
+ unsafe {
+ self.container.lock().vfio_dma_map(
+ map.iova,
+ map.size,
+ map.gpa.offset(),
+ (map.perm as u8 & Permission::Write as u8) != 0,
+ )
+ }
+ .map_err(|e| {
+ match base::Error::last() {
+ err if err.errno() == libc::EEXIST => {
+ // A mapping already exists in the requested range,
+ return MemoryMapperError::IovaRegionOverlap;
+ }
+ _ => MemoryMapperError::Vfio(e),
+ }
+ })
+ }
+
+ fn remove_map(&mut self, iova_start: u64, size: u64) -> Result<(), MemoryMapperError> {
+ iova_start
+ .checked_add(size)
+ .ok_or(MemoryMapperError::IntegerOverflow)?;
+ self.container
+ .lock()
+ .vfio_dma_unmap(iova_start, size)
+ .map_err(MemoryMapperError::Vfio)
+ }
+
+ fn get_mask(&self) -> Result<u64, MemoryMapperError> {
+ self.container
+ .lock()
+ .vfio_get_iommu_page_size_mask()
+ .map_err(MemoryMapperError::Vfio)
+ }
+
+ fn as_vfio_wrapper(&self) -> Option<&VfioWrapper> {
+ Some(self)
+ }
+ fn as_vfio_wrapper_mut(&mut self) -> Option<&mut VfioWrapper> {
+ Some(self)
+ }
+ fn into_vfio_wrapper(self: Box<Self>) -> Option<Box<VfioWrapper>> {
+ Some(self)
+ }
+}
+
+impl Translate for VfioWrapper {
+ fn translate(&self, _iova: u64, _size: u64) -> Result<Vec<MemRegion>, MemoryMapperError> {
+ Err(MemoryMapperError::Unimplemented)
+ }
+}
+
+impl AsRawDescriptors for VfioWrapper {
+ fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
+ vec![self.container.lock().as_raw_descriptor()]
+ }
+}
diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs
index 26966815b..8ec8bfb49 100644
--- a/devices/src/virtio/mod.rs
+++ b/devices/src/virtio/mod.rs
@@ -4,14 +4,12 @@
//! Implements virtio devices, queues, and transport mechanisms.
+mod async_utils;
mod balloon;
-mod block;
-mod block_async;
-mod console;
mod descriptor_utils;
mod input;
mod interrupt;
-mod net;
+mod iommu;
mod p9;
mod pmem;
mod queue;
@@ -23,11 +21,14 @@ mod video;
mod virtio_device;
mod virtio_pci_common_config;
mod virtio_pci_device;
-mod wl;
+pub mod wl;
+pub mod block;
+pub mod console;
pub mod fs;
#[cfg(feature = "gpu")]
pub mod gpu;
+pub mod net;
pub mod resource_bridge;
#[cfg(feature = "audio")]
pub mod snd;
@@ -35,7 +36,6 @@ pub mod vhost;
pub use self::balloon::*;
pub use self::block::*;
-pub use self::block_async::*;
pub use self::console::*;
pub use self::descriptor_utils::Error as DescriptorError;
pub use self::descriptor_utils::*;
@@ -43,11 +43,14 @@ pub use self::descriptor_utils::*;
pub use self::gpu::*;
pub use self::input::*;
pub use self::interrupt::*;
+pub use self::iommu::*;
pub use self::net::*;
pub use self::p9::*;
pub use self::pmem::*;
pub use self::queue::*;
pub use self::rng::*;
+#[cfg(feature = "audio")]
+pub use self::snd::*;
#[cfg(feature = "tpm")]
pub use self::tpm::*;
#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
@@ -56,10 +59,12 @@ pub use self::virtio_device::*;
pub use self::virtio_pci_device::*;
pub use self::wl::*;
-use crate::ProtectionType;
use std::cmp;
use std::convert::TryFrom;
+use hypervisor::ProtectionType;
+use virtio_sys::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
+
const DEVICE_RESET: u32 = 0x0;
const DEVICE_ACKNOWLEDGE: u32 = 0x01;
const DEVICE_DRIVER: u32 = 0x02;
@@ -83,17 +88,21 @@ const TYPE_INPUT: u32 = 18;
const TYPE_VSOCK: u32 = 19;
const TYPE_CRYPTO: u32 = 20;
const TYPE_IOMMU: u32 = 23;
+const TYPE_SOUND: u32 = 25;
const TYPE_FS: u32 = 26;
const TYPE_PMEM: u32 = 27;
+const TYPE_MAC80211_HWSIM: u32 = 29;
const TYPE_VIDEO_ENC: u32 = 30;
const TYPE_VIDEO_DEC: u32 = 31;
// Additional types invented by crosvm
const MAX_VIRTIO_DEVICE_ID: u32 = 63;
const TYPE_WL: u32 = MAX_VIRTIO_DEVICE_ID;
const TYPE_TPM: u32 = MAX_VIRTIO_DEVICE_ID - 1;
+// TODO(abhishekbh): Fix this after this device is accepted upstream.
+const TYPE_VHOST_USER: u32 = MAX_VIRTIO_DEVICE_ID - 2;
pub const VIRTIO_F_VERSION_1: u32 = 32;
-const VIRTIO_F_ACCESS_PLATFORM: u32 = 33;
+pub const VIRTIO_F_ACCESS_PLATFORM: u32 = 33;
const INTERRUPT_STATUS_USED_RING: u32 = 0x1;
const INTERRUPT_STATUS_CONFIG_CHANGED: u32 = 0x2;
@@ -122,6 +131,8 @@ pub fn type_to_str(type_: u32) -> Option<&'static str> {
TYPE_VSOCK => "vsock",
TYPE_CRYPTO => "crypto",
TYPE_IOMMU => "iommu",
+ TYPE_VHOST_USER => "vhost-user",
+ TYPE_SOUND => "snd",
TYPE_FS => "fs",
TYPE_PMEM => "pmem",
TYPE_WL => "wl",
@@ -158,9 +169,9 @@ pub fn copy_config(dst: &mut [u8], dst_offset: u64, src: &[u8], src_offset: u64)
/// Returns the set of reserved base features common to all virtio devices.
pub fn base_features(protected_vm: ProtectionType) -> u64 {
- let mut features: u64 = 1 << VIRTIO_F_VERSION_1;
+ let mut features: u64 = 1 << VIRTIO_F_VERSION_1 | 1 << VIRTIO_RING_F_EVENT_IDX;
- if protected_vm == ProtectionType::Protected {
+ if protected_vm != ProtectionType::Unprotected {
features |= 1 << VIRTIO_F_ACCESS_PLATFORM;
}
diff --git a/devices/src/virtio/net.rs b/devices/src/virtio/net.rs
index d1f35dca5..1ab972457 100644
--- a/devices/src/virtio/net.rs
+++ b/devices/src/virtio/net.rs
@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fmt::{self, Display};
use std::io::{self, Write};
use std::mem;
use std::net::Ipv4Addr;
@@ -15,6 +14,8 @@ use base::Error as SysError;
use base::{error, warn, AsRawDescriptor, Event, EventType, PollToken, RawDescriptor, WaitContext};
use data_model::{DataInit, Le16, Le64};
use net_util::{Error as TapError, MacAddress, TapT};
+use remain::sorted;
+use thiserror::Error as ThisError;
use virtio_sys::virtio_net;
use virtio_sys::virtio_net::{
virtio_net_hdr_v1, VIRTIO_NET_CTRL_GUEST_OFFLOADS, VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET,
@@ -29,79 +30,71 @@ use super::{
const QUEUE_SIZE: u16 = 256;
-#[derive(Debug)]
+#[sorted]
+#[derive(ThisError, Debug)]
pub enum NetError {
+ /// Cloning kill event failed.
+ #[error("failed to clone kill event: {0}")]
+ CloneKillEvent(SysError),
/// Creating kill event failed.
+ #[error("failed to create kill event: {0}")]
CreateKillEvent(SysError),
/// Creating WaitContext failed.
+ #[error("failed to create wait context: {0}")]
CreateWaitContext(SysError),
- /// Cloning kill event failed.
- CloneKillEvent(SysError),
/// Descriptor chain was invalid.
+ #[error("failed to valildate descriptor chain: {0}")]
DescriptorChain(DescriptorError),
- /// Removing read event from the tap fd events failed.
- WaitContextDisableTap(SysError),
- /// Adding read event to the tap fd events failed.
- WaitContextEnableTap(SysError),
- /// Error while waiting for events.
- WaitError(SysError),
/// Error reading data from control queue.
+ #[error("failed to read control message data: {0}")]
ReadCtrlData(io::Error),
/// Error reading header from control queue.
+ #[error("failed to read control message header: {0}")]
ReadCtrlHeader(io::Error),
/// There are no more available descriptors to receive into.
+ #[error("no rx descriptors available")]
RxDescriptorsExhausted,
+ /// Enabling tap interface failed.
+ #[error("failed to enable tap interface: {0}")]
+ TapEnable(TapError),
+ /// Couldn't get the MTU from the tap device.
+ #[error("failed to get tap interface MTU: {0}")]
+ TapGetMtu(TapError),
/// Open tap device failed.
+ #[error("failed to open tap device: {0}")]
TapOpen(TapError),
/// Setting tap IP failed.
+ #[error("failed to set tap IP: {0}")]
TapSetIp(TapError),
- /// Setting tap netmask failed.
- TapSetNetmask(TapError),
/// Setting tap mac address failed.
+ #[error("failed to set tap mac address: {0}")]
TapSetMacAddress(TapError),
- /// Setting tap interface offload flags failed.
- TapSetOffload(TapError),
+ /// Setting tap netmask failed.
+ #[error("failed to set tap netmask: {0}")]
+ TapSetNetmask(TapError),
/// Setting vnet header size failed.
+ #[error("failed to set vnet header size: {0}")]
TapSetVnetHdrSize(TapError),
- /// Enabling tap interface failed.
- TapEnable(TapError),
/// Validating tap interface failed.
+ #[error("failed to validate tap interface: {0}")]
TapValidate(String),
+ /// Removing read event from the tap fd events failed.
+ #[error("failed to disable EPOLLIN on tap fd: {0}")]
+ WaitContextDisableTap(SysError),
+ /// Adding read event to the tap fd events failed.
+ #[error("failed to enable EPOLLIN on tap fd: {0}")]
+ WaitContextEnableTap(SysError),
+ /// Error while waiting for events.
+ #[error("error while waiting for events: {0}")]
+ WaitError(SysError),
/// Failed writing an ack in response to a control message.
+ #[error("failed to write control message ack: {0}")]
WriteAck(io::Error),
/// Writing to a buffer in the guest failed.
+ #[error("failed to write to guest buffer: {0}")]
WriteBuffer(io::Error),
}
-impl Display for NetError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::NetError::*;
-
- match self {
- CreateKillEvent(e) => write!(f, "failed to create kill event: {}", e),
- CreateWaitContext(e) => write!(f, "failed to create wait context: {}", e),
- CloneKillEvent(e) => write!(f, "failed to clone kill event: {}", e),
- DescriptorChain(e) => write!(f, "failed to valildate descriptor chain: {}", e),
- WaitContextDisableTap(e) => write!(f, "failed to disable EPOLLIN on tap fd: {}", e),
- WaitContextEnableTap(e) => write!(f, "failed to enable EPOLLIN on tap fd: {}", e),
- WaitError(e) => write!(f, "error while waiting for events: {}", e),
- ReadCtrlData(e) => write!(f, "failed to read control message data: {}", e),
- ReadCtrlHeader(e) => write!(f, "failed to read control message header: {}", e),
- RxDescriptorsExhausted => write!(f, "no rx descriptors available"),
- TapOpen(e) => write!(f, "failed to open tap device: {}", e),
- TapSetIp(e) => write!(f, "failed to set tap IP: {}", e),
- TapSetNetmask(e) => write!(f, "failed to set tap netmask: {}", e),
- TapSetMacAddress(e) => write!(f, "failed to set tap mac address: {}", e),
- TapSetOffload(e) => write!(f, "failed to set tap interface offload flags: {}", e),
- TapSetVnetHdrSize(e) => write!(f, "failed to set vnet header size: {}", e),
- TapEnable(e) => write!(f, "failed to enable tap interface: {}", e),
- TapValidate(s) => write!(f, "failed to validate tap interface: {}", s),
- WriteAck(e) => write!(f, "failed to write control message ack: {}", e),
- WriteBuffer(e) => write!(f, "failed to write to guest buffer: {}", e),
- }
- }
-}
-
#[repr(C, packed)]
#[derive(Debug, Clone, Copy)]
pub struct virtio_net_ctrl_hdr {
@@ -112,7 +105,8 @@ pub struct virtio_net_ctrl_hdr {
// Safe because it only has data and has no implicit padding.
unsafe impl DataInit for virtio_net_ctrl_hdr {}
-fn virtio_features_to_tap_offload(features: u64) -> c_uint {
+/// Converts virtio-net feature bits to tap's offload bits.
+pub fn virtio_features_to_tap_offload(features: u64) -> c_uint {
let mut tap_offloads: c_uint = 0;
if features & (1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM) != 0 {
tap_offloads |= net_sys::TUN_F_CSUM;
@@ -135,7 +129,7 @@ fn virtio_features_to_tap_offload(features: u64) -> c_uint {
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
-pub(crate) struct VirtioNetConfig {
+pub struct VirtioNetConfig {
mac: [u8; 6],
status: Le16,
max_vq_pairs: Le16,
@@ -145,6 +139,195 @@ pub(crate) struct VirtioNetConfig {
// Safe because it only has data and has no implicit padding.
unsafe impl DataInit for VirtioNetConfig {}
+pub fn process_rx<I: SignalableInterrupt, T: TapT>(
+ interrupt: &I,
+ rx_queue: &mut Queue,
+ mem: &GuestMemory,
+ mut tap: &mut T,
+) -> result::Result<(), NetError> {
+ let mut needs_interrupt = false;
+ let mut exhausted_queue = false;
+
+ // Read as many frames as possible.
+ loop {
+ let desc_chain = match rx_queue.peek(mem) {
+ Some(desc) => desc,
+ None => {
+ exhausted_queue = true;
+ break;
+ }
+ };
+
+ let index = desc_chain.index;
+ let bytes_written = match Writer::new(mem.clone(), desc_chain) {
+ Ok(mut writer) => {
+ match writer.write_from(&mut tap, writer.available_bytes()) {
+ Ok(_) => {}
+ Err(ref e) if e.kind() == io::ErrorKind::WriteZero => {
+ warn!("net: rx: buffer is too small to hold frame");
+ break;
+ }
+ Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
+ // No more to read from the tap.
+ break;
+ }
+ Err(e) => {
+ warn!("net: rx: failed to write slice: {}", e);
+ return Err(NetError::WriteBuffer(e));
+ }
+ };
+
+ writer.bytes_written() as u32
+ }
+ Err(e) => {
+ error!("net: failed to create Writer: {}", e);
+ 0
+ }
+ };
+
+ if bytes_written > 0 {
+ rx_queue.pop_peeked(mem);
+ rx_queue.add_used(mem, index, bytes_written);
+ needs_interrupt = true;
+ }
+ }
+
+ if needs_interrupt {
+ rx_queue.trigger_interrupt(mem, interrupt);
+ }
+
+ if exhausted_queue {
+ Err(NetError::RxDescriptorsExhausted)
+ } else {
+ Ok(())
+ }
+}
+
+pub fn process_tx<I: SignalableInterrupt, T: TapT>(
+ interrupt: &I,
+ tx_queue: &mut Queue,
+ mem: &GuestMemory,
+ mut tap: &mut T,
+) {
+ while let Some(desc_chain) = tx_queue.pop(mem) {
+ let index = desc_chain.index;
+
+ match Reader::new(mem.clone(), desc_chain) {
+ Ok(mut reader) => {
+ let expected_count = reader.available_bytes();
+ match reader.read_to(&mut tap, expected_count) {
+ Ok(count) => {
+ // Tap writes must be done in one call. If the entire frame was not
+ // written, it's an error.
+ if count != expected_count {
+ error!(
+ "net: tx: wrote only {} bytes of {} byte frame",
+ count, expected_count
+ );
+ }
+ }
+ Err(e) => error!("net: tx: failed to write frame to tap: {}", e),
+ }
+ }
+ Err(e) => error!("net: failed to create Reader: {}", e),
+ }
+
+ tx_queue.add_used(mem, index, 0);
+ }
+
+ tx_queue.trigger_interrupt(mem, interrupt);
+}
+
+pub fn process_ctrl<I: SignalableInterrupt, T: TapT>(
+ interrupt: &I,
+ ctrl_queue: &mut Queue,
+ mem: &GuestMemory,
+ tap: &mut T,
+ acked_features: u64,
+ vq_pairs: u16,
+) -> Result<(), NetError> {
+ while let Some(desc_chain) = ctrl_queue.pop(mem) {
+ let index = desc_chain.index;
+
+ let mut reader =
+ Reader::new(mem.clone(), desc_chain.clone()).map_err(NetError::DescriptorChain)?;
+ let mut writer = Writer::new(mem.clone(), desc_chain).map_err(NetError::DescriptorChain)?;
+ let ctrl_hdr: virtio_net_ctrl_hdr = reader.read_obj().map_err(NetError::ReadCtrlHeader)?;
+
+ let mut write_error = || {
+ writer
+ .write_all(&[VIRTIO_NET_ERR as u8])
+ .map_err(NetError::WriteAck)?;
+ ctrl_queue.add_used(mem, index, writer.bytes_written() as u32);
+ Ok(())
+ };
+
+ match ctrl_hdr.class as c_uint {
+ VIRTIO_NET_CTRL_GUEST_OFFLOADS => {
+ if ctrl_hdr.cmd != VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET as u8 {
+ error!(
+ "invalid cmd for VIRTIO_NET_CTRL_GUEST_OFFLOADS: {}",
+ ctrl_hdr.cmd
+ );
+ write_error()?;
+ continue;
+ }
+ let offloads: Le64 = reader.read_obj().map_err(NetError::ReadCtrlData)?;
+ let tap_offloads = virtio_features_to_tap_offload(offloads.into());
+ if let Err(e) = tap.set_offload(tap_offloads) {
+ error!("Failed to set tap itnerface offload flags: {}", e);
+ write_error()?;
+ continue;
+ }
+
+ let ack = VIRTIO_NET_OK as u8;
+ writer.write_all(&[ack]).map_err(NetError::WriteAck)?;
+ }
+ VIRTIO_NET_CTRL_MQ => {
+ if ctrl_hdr.cmd == VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET as u8 {
+ let pairs: Le16 = reader.read_obj().map_err(NetError::ReadCtrlData)?;
+ // Simple handle it now
+ if acked_features & 1 << virtio_net::VIRTIO_NET_F_MQ == 0
+ || pairs.to_native() != vq_pairs
+ {
+ error!("Invalid VQ_PAIRS_SET cmd, driver request pairs: {}, device vq pairs: {}",
+ pairs.to_native(), vq_pairs);
+ write_error()?;
+ continue;
+ }
+ let ack = VIRTIO_NET_OK as u8;
+ writer.write_all(&[ack]).map_err(NetError::WriteAck)?;
+ }
+ }
+ _ => warn!(
+ "unimplemented class for VIRTIO_NET_CTRL_GUEST_OFFLOADS: {}",
+ ctrl_hdr.class
+ ),
+ }
+
+ ctrl_queue.add_used(mem, index, writer.bytes_written() as u32);
+ }
+
+ ctrl_queue.trigger_interrupt(mem, interrupt);
+ Ok(())
+}
+
+#[derive(PollToken, Debug, Clone)]
+pub enum Token {
+ // A frame is available for reading from the tap device to receive in the guest.
+ RxTap,
+ // The guest has made a buffer available to receive a frame into.
+ RxQueue,
+ // The transmit queue has a frame that is ready to send from the guest.
+ TxQueue,
+ // The control queue has a message.
+ CtrlQueue,
+ // Check if any interrupts need to be re-asserted.
+ InterruptResample,
+ // crosvm has requested the device to shut down.
+ Kill,
+}
+
struct Worker<T: TapT> {
interrupt: Arc<Interrupt>,
mem: GuestMemory,
@@ -162,92 +345,21 @@ where
T: TapT,
{
fn process_rx(&mut self) -> result::Result<(), NetError> {
- let mut needs_interrupt = false;
- let mut exhausted_queue = false;
-
- // Read as many frames as possible.
- loop {
- let desc_chain = match self.rx_queue.peek(&self.mem) {
- Some(desc) => desc,
- None => {
- exhausted_queue = true;
- break;
- }
- };
-
- let index = desc_chain.index;
- let bytes_written = match Writer::new(self.mem.clone(), desc_chain) {
- Ok(mut writer) => {
- match writer.write_from(&mut self.tap, writer.available_bytes()) {
- Ok(_) => {}
- Err(ref e) if e.kind() == io::ErrorKind::WriteZero => {
- warn!("net: rx: buffer is too small to hold frame");
- break;
- }
- Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
- // No more to read from the tap.
- break;
- }
- Err(e) => {
- warn!("net: rx: failed to write slice: {}", e);
- return Err(NetError::WriteBuffer(e));
- }
- };
-
- writer.bytes_written() as u32
- }
- Err(e) => {
- error!("net: failed to create Writer: {}", e);
- 0
- }
- };
-
- if bytes_written > 0 {
- self.rx_queue.pop_peeked(&self.mem);
- self.rx_queue.add_used(&self.mem, index, bytes_written);
- needs_interrupt = true;
- }
- }
-
- if needs_interrupt {
- self.interrupt.signal_used_queue(self.rx_queue.vector);
- }
-
- if exhausted_queue {
- Err(NetError::RxDescriptorsExhausted)
- } else {
- Ok(())
- }
+ process_rx(
+ self.interrupt.as_ref(),
+ &mut self.rx_queue,
+ &self.mem,
+ &mut self.tap,
+ )
}
fn process_tx(&mut self) {
- while let Some(desc_chain) = self.tx_queue.pop(&self.mem) {
- let index = desc_chain.index;
-
- match Reader::new(self.mem.clone(), desc_chain) {
- Ok(mut reader) => {
- let expected_count = reader.available_bytes();
- match reader.read_to(&mut self.tap, expected_count) {
- Ok(count) => {
- // Tap writes must be done in one call. If the entire frame was not
- // written, it's an error.
- if count != expected_count {
- error!(
- "net: tx: wrote only {} bytes of {} byte frame",
- count, expected_count
- );
- }
- }
- Err(e) => error!("net: tx: failed to write frame to tap: {}", e),
- }
- }
- Err(e) => error!("net: failed to create Reader: {}", e),
- }
-
- self.tx_queue.add_used(&self.mem, index, 0);
- }
-
- self.interrupt.signal_used_queue(self.tx_queue.vector);
+ process_tx(
+ self.interrupt.as_ref(),
+ &mut self.tx_queue,
+ &self.mem,
+ &mut self.tap,
+ )
}
fn process_ctrl(&mut self) -> Result<(), NetError> {
@@ -256,65 +368,14 @@ where
None => return Ok(()),
};
- while let Some(desc_chain) = ctrl_queue.pop(&self.mem) {
- let index = desc_chain.index;
-
- let mut reader = Reader::new(self.mem.clone(), desc_chain.clone())
- .map_err(NetError::DescriptorChain)?;
- let mut writer =
- Writer::new(self.mem.clone(), desc_chain).map_err(NetError::DescriptorChain)?;
- let ctrl_hdr: virtio_net_ctrl_hdr =
- reader.read_obj().map_err(NetError::ReadCtrlHeader)?;
-
- match ctrl_hdr.class as c_uint {
- VIRTIO_NET_CTRL_GUEST_OFFLOADS => {
- if ctrl_hdr.cmd != VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET as u8 {
- error!(
- "invalid cmd for VIRTIO_NET_CTRL_GUEST_OFFLOADS: {}",
- ctrl_hdr.cmd
- );
- let ack = VIRTIO_NET_ERR as u8;
- writer.write_all(&[ack]).map_err(NetError::WriteAck)?;
- ctrl_queue.add_used(&self.mem, index, 0);
- continue;
- }
- let offloads: Le64 = reader.read_obj().map_err(NetError::ReadCtrlData)?;
- let tap_offloads = virtio_features_to_tap_offload(offloads.into());
- self.tap
- .set_offload(tap_offloads)
- .map_err(NetError::TapSetOffload)?;
- let ack = VIRTIO_NET_OK as u8;
- writer.write_all(&[ack]).map_err(NetError::WriteAck)?;
- }
- VIRTIO_NET_CTRL_MQ => {
- if ctrl_hdr.cmd == VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET as u8 {
- let pairs: Le16 = reader.read_obj().map_err(NetError::ReadCtrlData)?;
- // Simple handle it now
- if self.acked_features & 1 << virtio_net::VIRTIO_NET_F_MQ == 0
- || pairs.to_native() != self.vq_pairs
- {
- error!("Invalid VQ_PAIRS_SET cmd, driver request pairs: {}, device vq pairs: {}",
- pairs.to_native(), self.vq_pairs);
- let ack = VIRTIO_NET_ERR as u8;
- writer.write_all(&[ack]).map_err(NetError::WriteAck)?;
- ctrl_queue.add_used(&self.mem, index, 0);
- continue;
- }
- let ack = VIRTIO_NET_OK as u8;
- writer.write_all(&[ack]).map_err(NetError::WriteAck)?;
- }
- }
- _ => warn!(
- "unimplemented class for VIRTIO_NET_CTRL_GUEST_OFFLOADS: {}",
- ctrl_hdr.class
- ),
- }
-
- ctrl_queue.add_used(&self.mem, index, 0);
- }
-
- self.interrupt.signal_used_queue(ctrl_queue.vector);
- Ok(())
+ process_ctrl(
+ self.interrupt.as_ref(),
+ ctrl_queue,
+ &self.mem,
+ &mut self.tap,
+ self.acked_features,
+ self.vq_pairs,
+ )
}
fn run(
@@ -323,22 +384,6 @@ where
tx_queue_evt: Event,
ctrl_queue_evt: Option<Event>,
) -> Result<(), NetError> {
- #[derive(PollToken)]
- enum Token {
- // A frame is available for reading from the tap device to receive in the guest.
- RxTap,
- // The guest has made a buffer available to receive a frame into.
- RxQueue,
- // The transmit queue has a frame that is ready to send from the guest.
- TxQueue,
- // The control queue has a message.
- CtrlQueue,
- // Check if any interrupts need to be re-asserted.
- InterruptResample,
- // crosvm has requested the device to shut down.
- Kill,
- }
-
let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
(&self.tap, Token::RxTap),
(&rx_queue_evt, Token::RxQueue),
@@ -408,7 +453,9 @@ where
}
}
Token::InterruptResample => {
- self.interrupt.interrupt_resample();
+ // We can unwrap safely because interrupt must have the event.
+ let _ = self.interrupt.get_resample_evt().unwrap().read();
+ self.interrupt.do_interrupt_resample();
}
Token::Kill => {
let _ = self.kill_evt.read();
@@ -421,6 +468,17 @@ where
}
}
+pub fn build_config(vq_pairs: u16, mtu: u16) -> VirtioNetConfig {
+ VirtioNetConfig {
+ max_vq_pairs: Le16::from(vq_pairs),
+ mtu: Le16::from(mtu),
+ // Other field has meaningful value when the corresponding feature
+ // is enabled, but all these features aren't supported now.
+ // So set them to default.
+ ..Default::default()
+ }
+}
+
pub struct Net<T: TapT> {
queue_sizes: Box<[u16]>,
workers_kill_evt: Vec<Event>,
@@ -429,6 +487,7 @@ pub struct Net<T: TapT> {
taps: Vec<T>,
avail_features: u64,
acked_features: u64,
+ mtu: u16,
}
impl<T> Net<T>
@@ -456,16 +515,31 @@ where
Net::from(base_features, tap, vq_pairs)
}
+ /// Try to open the already-configured TAP interface `name` and to create a network device from
+ /// it.
+ pub fn new_from_name(
+ base_features: u64,
+ name: &[u8],
+ vq_pairs: u16,
+ ) -> Result<Net<T>, NetError> {
+ let multi_queue = vq_pairs > 1;
+ let tap: T = T::new_with_name(name, true, multi_queue).map_err(NetError::TapOpen)?;
+
+ Net::from(base_features, tap, vq_pairs)
+ }
+
/// Creates a new virtio network device from a tap device that has already been
/// configured.
pub fn from(base_features: u64, tap: T, vq_pairs: u16) -> Result<Net<T>, NetError> {
let taps = tap.into_mq_taps(vq_pairs).map_err(NetError::TapOpen)?;
+ let mut mtu = u16::MAX;
// This would also validate a tap created by Self::new(), but that's a good thing as it
// would ensure that any changes in the creation procedure are matched in the validation.
// Plus we still need to set the offload and vnet_hdr_size values.
for tap in &taps {
validate_and_configure_tap(tap, vq_pairs)?;
+ mtu = std::cmp::min(mtu, tap.mtu().map_err(NetError::TapGetMtu)?);
}
let mut avail_features = base_features
@@ -476,7 +550,8 @@ where
| 1 << virtio_net::VIRTIO_NET_F_GUEST_TSO4
| 1 << virtio_net::VIRTIO_NET_F_GUEST_UFO
| 1 << virtio_net::VIRTIO_NET_F_HOST_TSO4
- | 1 << virtio_net::VIRTIO_NET_F_HOST_UFO;
+ | 1 << virtio_net::VIRTIO_NET_F_HOST_UFO
+ | 1 << virtio_net::VIRTIO_NET_F_MTU;
if vq_pairs > 1 {
avail_features |= 1 << virtio_net::VIRTIO_NET_F_MQ;
@@ -499,33 +574,22 @@ where
taps,
avail_features,
acked_features: 0u64,
+ mtu,
})
}
-
- fn build_config(&self) -> VirtioNetConfig {
- let vq_pairs = self.queue_sizes.len() as u16 / 2;
-
- VirtioNetConfig {
- max_vq_pairs: Le16::from(vq_pairs),
- // Other field has meaningful value when the corresponding feature
- // is enabled, but all these features aren't supported now.
- // So set them to default.
- ..Default::default()
- }
- }
}
// Ensure that the tap interface has the correct flags and sets the offload and VNET header size
// to the appropriate values.
-fn validate_and_configure_tap<T: TapT>(tap: &T, vq_pairs: u16) -> Result<(), NetError> {
+pub fn validate_and_configure_tap<T: TapT>(tap: &T, vq_pairs: u16) -> Result<(), NetError> {
let flags = tap.if_flags();
let mut required_flags = vec![
- (net_sys::IFF_TAP, "IFF_TAP"),
- (net_sys::IFF_NO_PI, "IFF_NO_PI"),
- (net_sys::IFF_VNET_HDR, "IFF_VNET_HDR"),
+ (libc::IFF_TAP, "IFF_TAP"),
+ (libc::IFF_NO_PI, "IFF_NO_PI"),
+ (libc::IFF_VNET_HDR, "IFF_VNET_HDR"),
];
if vq_pairs > 1 {
- required_flags.push((net_sys::IFF_MULTI_QUEUE, "IFF_MULTI_QUEUE"));
+ required_flags.push((libc::IFF_MULTI_QUEUE, "IFF_MULTI_QUEUE"));
}
let missing_flags = required_flags
.iter()
@@ -635,7 +699,8 @@ where
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
- let config_space = self.build_config();
+ let vq_pairs = self.queue_sizes.len() / 2;
+ let config_space = build_config(vq_pairs as u16, self.mtu);
copy_config(data, 0, config_space.as_slice(), offset);
}
diff --git a/devices/src/virtio/p9.rs b/devices/src/virtio/p9.rs
index 670645d88..0623bfe96 100644
--- a/devices/src/virtio/p9.rs
+++ b/devices/src/virtio/p9.rs
@@ -2,13 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fmt::{self, Display};
use std::io::{self, Write};
use std::mem;
use std::result;
use std::thread;
use base::{error, warn, Error as SysError, Event, PollToken, RawDescriptor, WaitContext};
+use remain::sorted;
+use thiserror::Error;
use vm_memory::GuestMemory;
use super::{
@@ -23,56 +24,39 @@ const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
const VIRTIO_9P_MOUNT_TAG: u8 = 0;
/// Errors that occur during operation of a virtio 9P device.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum P9Error {
- /// The tag for the 9P device was too large to fit in the config space.
- TagTooLong(usize),
- /// Creating WaitContext failed.
- CreateWaitContext(SysError),
/// Failed to create a 9p server.
+ #[error("failed to create 9p server: {0}")]
CreateServer(io::Error),
- /// Error while polling for events.
- WaitError(SysError),
- /// Error while reading from the virtio queue's Event.
- ReadQueueEvent(SysError),
+ /// Creating WaitContext failed.
+ #[error("failed to create WaitContext: {0}")]
+ CreateWaitContext(SysError),
+ /// An internal I/O error occurred.
+ #[error("P9 internal server error: {0}")]
+ Internal(io::Error),
+ /// A DescriptorChain contains invalid data.
+ #[error("DescriptorChain contains invalid data: {0}")]
+ InvalidDescriptorChain(DescriptorError),
/// A request is missing readable descriptors.
+ #[error("request does not have any readable descriptors")]
NoReadableDescriptors,
/// A request is missing writable descriptors.
+ #[error("request does not have any writable descriptors")]
NoWritableDescriptors,
+ /// Error while reading from the virtio queue's Event.
+ #[error("failed to read from virtio queue Event: {0}")]
+ ReadQueueEvent(SysError),
/// Failed to signal the virio used queue.
+ #[error("failed to signal used queue: {0}")]
SignalUsedQueue(SysError),
- /// A DescriptorChain contains invalid data.
- InvalidDescriptorChain(DescriptorError),
- /// An internal I/O error occurred.
- Internal(io::Error),
-}
-
-impl std::error::Error for P9Error {}
-
-impl Display for P9Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::P9Error::*;
-
- match self {
- TagTooLong(len) => write!(
- f,
- "P9 device tag is too long: len = {}, max = {}",
- len,
- ::std::u16::MAX
- ),
- CreateWaitContext(err) => write!(f, "failed to create WaitContext: {}", err),
- WaitError(err) => write!(f, "failed to wait for events: {}", err),
- CreateServer(err) => write!(f, "failed to create 9p server: {}", err),
- ReadQueueEvent(err) => write!(f, "failed to read from virtio queue Event: {}", err),
- NoReadableDescriptors => write!(f, "request does not have any readable descriptors"),
- NoWritableDescriptors => write!(f, "request does not have any writable descriptors"),
- SignalUsedQueue(err) => write!(f, "failed to signal used queue: {}", err),
- InvalidDescriptorChain(err) => {
- write!(f, "DescriptorChain contains invalid data: {}", err)
- }
- Internal(err) => write!(f, "P9 internal server error: {}", err),
- }
- }
+ /// The tag for the 9P device was too large to fit in the config space.
+ #[error("P9 device tag is too long: len = {0}, max = {}", ::std::u16::MAX)]
+ TagTooLong(usize),
+ /// Error while polling for events.
+ #[error("failed to wait for events: {0}")]
+ WaitError(SysError),
}
pub type P9Result<T> = result::Result<T, P9Error>;
@@ -99,8 +83,7 @@ impl Worker {
self.queue
.add_used(&self.mem, avail_desc.index, writer.bytes_written() as u32);
}
-
- self.interrupt.signal_used_queue(self.queue.vector);
+ self.queue.trigger_interrupt(&self.mem, &self.interrupt);
Ok(())
}
diff --git a/devices/src/virtio/pmem.rs b/devices/src/virtio/pmem.rs
index 9a3ca39cb..0301bd06c 100644
--- a/devices/src/virtio/pmem.rs
+++ b/devices/src/virtio/pmem.rs
@@ -2,19 +2,24 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fmt::{self, Display};
+use std::cell::RefCell;
use std::fs::File;
use std::io;
+use std::rc::Rc;
use std::thread;
-use base::{error, AsRawDescriptor, Event, PollToken, RawDescriptor, Tube, WaitContext};
+use base::{error, AsRawDescriptor, Event, RawDescriptor, Tube};
use base::{Error as SysError, Result as SysResult};
+use cros_async::{select3, EventAsync, Executor};
use data_model::{DataInit, Le32, Le64};
+use futures::pin_mut;
+use remain::sorted;
+use thiserror::Error;
use vm_control::{MemSlot, VmMsyncRequest, VmMsyncResponse};
use vm_memory::{GuestAddress, GuestMemory};
use super::{
- copy_config, DescriptorChain, DescriptorError, Interrupt, Queue, Reader, SignalableInterrupt,
+ async_utils, copy_config, DescriptorChain, DescriptorError, Interrupt, Queue, Reader,
VirtioDevice, Writer, TYPE_PMEM,
};
@@ -53,173 +58,161 @@ struct virtio_pmem_req {
// Safe because it only has data and has no implicit padding.
unsafe impl DataInit for virtio_pmem_req {}
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
enum Error {
/// Invalid virtio descriptor chain.
+ #[error("virtio descriptor error: {0}")]
Descriptor(DescriptorError),
/// Failed to read from virtqueue.
+ #[error("failed to read from virtqueue: {0}")]
ReadQueue(io::Error),
/// Failed to write to virtqueue.
+ #[error("failed to write to virtqueue: {0}")]
WriteQueue(io::Error),
}
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- Descriptor(e) => write!(f, "virtio descriptor error: {}", e),
- ReadQueue(e) => write!(f, "failed to read from virtqueue: {}", e),
- WriteQueue(e) => write!(f, "failed to write to virtqueue: {}", e),
- }
- }
-}
-
-impl ::std::error::Error for Error {}
-
type Result<T> = ::std::result::Result<T, Error>;
-struct Worker {
- interrupt: Interrupt,
- queue: Queue,
- memory: GuestMemory,
- pmem_device_tube: Tube,
- mapping_arena_slot: MemSlot,
+fn execute_request(
+ request: virtio_pmem_req,
+ pmem_device_tube: &Tube,
+ mapping_arena_slot: u32,
mapping_size: usize,
-}
-
-impl Worker {
- fn execute_request(&self, request: virtio_pmem_req) -> u32 {
- match request.type_.to_native() {
- VIRTIO_PMEM_REQ_TYPE_FLUSH => {
- let request = VmMsyncRequest::MsyncArena {
- slot: self.mapping_arena_slot,
- offset: 0, // The pmem backing file is always at offset 0 in the arena.
- size: self.mapping_size,
- };
+) -> u32 {
+ match request.type_.to_native() {
+ VIRTIO_PMEM_REQ_TYPE_FLUSH => {
+ let request = VmMsyncRequest::MsyncArena {
+ slot: mapping_arena_slot,
+ offset: 0, // The pmem backing file is always at offset 0 in the arena.
+ size: mapping_size,
+ };
- if let Err(e) = self.pmem_device_tube.send(&request) {
- error!("failed to send request: {}", e);
- return VIRTIO_PMEM_RESP_TYPE_EIO;
- }
+ if let Err(e) = pmem_device_tube.send(&request) {
+ error!("failed to send request: {}", e);
+ return VIRTIO_PMEM_RESP_TYPE_EIO;
+ }
- match self.pmem_device_tube.recv() {
- Ok(response) => match response {
- VmMsyncResponse::Ok => VIRTIO_PMEM_RESP_TYPE_OK,
- VmMsyncResponse::Err(e) => {
- error!("failed flushing disk image: {}", e);
- VIRTIO_PMEM_RESP_TYPE_EIO
- }
- },
- Err(e) => {
- error!("failed to receive data: {}", e);
+ match pmem_device_tube.recv() {
+ Ok(response) => match response {
+ VmMsyncResponse::Ok => VIRTIO_PMEM_RESP_TYPE_OK,
+ VmMsyncResponse::Err(e) => {
+ error!("failed flushing disk image: {}", e);
VIRTIO_PMEM_RESP_TYPE_EIO
}
+ },
+ Err(e) => {
+ error!("failed to receive data: {}", e);
+ VIRTIO_PMEM_RESP_TYPE_EIO
}
}
- _ => {
- error!("unknown request type: {}", request.type_.to_native());
- VIRTIO_PMEM_RESP_TYPE_EIO
- }
+ }
+ _ => {
+ error!("unknown request type: {}", request.type_.to_native());
+ VIRTIO_PMEM_RESP_TYPE_EIO
}
}
+}
- fn handle_request(&self, avail_desc: DescriptorChain) -> Result<usize> {
- let mut reader =
- Reader::new(self.memory.clone(), avail_desc.clone()).map_err(Error::Descriptor)?;
- let mut writer = Writer::new(self.memory.clone(), avail_desc).map_err(Error::Descriptor)?;
-
- let status_code = reader
- .read_obj()
- .map(|request| self.execute_request(request))
- .map_err(Error::ReadQueue)?;
-
- let response = virtio_pmem_resp {
- status_code: status_code.into(),
- };
-
- writer.write_obj(response).map_err(Error::WriteQueue)?;
+fn handle_request(
+ mem: &GuestMemory,
+ avail_desc: DescriptorChain,
+ pmem_device_tube: &Tube,
+ mapping_arena_slot: u32,
+ mapping_size: usize,
+) -> Result<usize> {
+ let mut reader = Reader::new(mem.clone(), avail_desc.clone()).map_err(Error::Descriptor)?;
+ let mut writer = Writer::new(mem.clone(), avail_desc).map_err(Error::Descriptor)?;
- Ok(writer.bytes_written())
- }
+ let status_code = reader
+ .read_obj()
+ .map(|request| execute_request(request, pmem_device_tube, mapping_arena_slot, mapping_size))
+ .map_err(Error::ReadQueue)?;
- fn process_queue(&mut self) -> bool {
- let mut needs_interrupt = false;
- while let Some(avail_desc) = self.queue.pop(&self.memory) {
- let avail_desc_index = avail_desc.index;
+ let response = virtio_pmem_resp {
+ status_code: status_code.into(),
+ };
- let bytes_written = match self.handle_request(avail_desc) {
- Ok(count) => count,
- Err(e) => {
- error!("pmem: unable to handle request: {}", e);
- 0
- }
- };
- self.queue
- .add_used(&self.memory, avail_desc_index, bytes_written as u32);
- needs_interrupt = true;
- }
+ writer.write_obj(response).map_err(Error::WriteQueue)?;
- needs_interrupt
- }
-
- fn run(&mut self, queue_evt: Event, kill_evt: Event) {
- #[derive(PollToken)]
- enum Token {
- QueueAvailable,
- InterruptResample,
- Kill,
- }
+ Ok(writer.bytes_written())
+}
- let wait_ctx: WaitContext<Token> = match WaitContext::build_with(&[
- (&queue_evt, Token::QueueAvailable),
- (&kill_evt, Token::Kill),
- ]) {
- Ok(pc) => pc,
+async fn handle_queue(
+ mem: &GuestMemory,
+ mut queue: Queue,
+ mut queue_event: EventAsync,
+ interrupt: Rc<RefCell<Interrupt>>,
+ pmem_device_tube: Tube,
+ mapping_arena_slot: u32,
+ mapping_size: usize,
+) {
+ loop {
+ let avail_desc = match queue.next_async(mem, &mut queue_event).await {
Err(e) => {
- error!("failed creating WaitContext: {}", e);
+ error!("Failed to read descriptor {}", e);
return;
}
+ Ok(d) => d,
};
- if let Some(resample_evt) = self.interrupt.get_resample_evt() {
- if wait_ctx
- .add(resample_evt, Token::InterruptResample)
- .is_err()
- {
- error!("failed adding resample event to WaitContext.");
- return;
+ let index = avail_desc.index;
+ let written = match handle_request(
+ mem,
+ avail_desc,
+ &pmem_device_tube,
+ mapping_arena_slot,
+ mapping_size,
+ ) {
+ Ok(n) => n,
+ Err(e) => {
+ error!("pmem: failed to handle request: {}", e);
+ 0
}
- }
-
- 'wait: loop {
- let events = match wait_ctx.wait() {
- Ok(v) => v,
- Err(e) => {
- error!("failed polling for events: {}", e);
- break;
- }
- };
+ };
+ queue.add_used(mem, index, written as u32);
+ queue.trigger_interrupt(mem, &*interrupt.borrow());
+ }
+}
- let mut needs_interrupt = false;
- for event in events.iter().filter(|e| e.is_readable) {
- match event.token {
- Token::QueueAvailable => {
- if let Err(e) = queue_evt.read() {
- error!("failed reading queue Event: {}", e);
- break 'wait;
- }
- needs_interrupt |= self.process_queue();
- }
- Token::InterruptResample => {
- self.interrupt.interrupt_resample();
- }
- Token::Kill => break 'wait,
- }
- }
- if needs_interrupt {
- self.interrupt.signal_used_queue(self.queue.vector);
- }
- }
+fn run_worker(
+ queue_evt: Event,
+ queue: Queue,
+ pmem_device_tube: Tube,
+ interrupt: Interrupt,
+ kill_evt: Event,
+ mem: GuestMemory,
+ mapping_arena_slot: u32,
+ mapping_size: usize,
+) {
+ // Wrap the interrupt in a `RefCell` so it can be shared between async functions.
+ let interrupt = Rc::new(RefCell::new(interrupt));
+
+ let ex = Executor::new().unwrap();
+
+ let queue_evt = EventAsync::new(queue_evt.0, &ex).expect("failed to set up the queue event");
+
+ // Process requests from the virtio queue.
+ let queue_fut = handle_queue(
+ &mem,
+ queue,
+ queue_evt,
+ interrupt.clone(),
+ pmem_device_tube,
+ mapping_arena_slot,
+ mapping_size,
+ );
+ pin_mut!(queue_fut);
+
+ // Process any requests to resample the irq value.
+ let resample = async_utils::handle_irq_resample(&ex, interrupt);
+ pin_mut!(resample);
+
+ // Exit if the kill event is triggered.
+ let kill = async_utils::await_and_exit(&ex, kill_evt);
+ pin_mut!(kill);
+
+ if let Err(e) = ex.run_until(select3(queue_fut, resample, kill)) {
+ error!("error happened in executor: {}", e);
}
}
@@ -338,15 +331,16 @@ impl VirtioDevice for Pmem {
let worker_result = thread::Builder::new()
.name("virtio_pmem".to_string())
.spawn(move || {
- let mut worker = Worker {
- interrupt,
- memory,
+ run_worker(
+ queue_event,
queue,
pmem_device_tube,
+ interrupt,
+ kill_event,
+ memory,
mapping_arena_slot,
mapping_size,
- };
- worker.run(queue_event, kill_event);
+ )
});
match worker_result {
diff --git a/devices/src/virtio/queue.rs b/devices/src/virtio/queue.rs
index 537abaeee..a436f748f 100644
--- a/devices/src/virtio/queue.rs
+++ b/devices/src/virtio/queue.rs
@@ -3,15 +3,25 @@
// found in the LICENSE file.
use std::cmp::min;
+use std::convert::TryInto;
use std::num::Wrapping;
use std::sync::atomic::{fence, Ordering};
+use std::sync::Arc;
+use sync::Mutex;
+use anyhow::{bail, Context};
use base::error;
use cros_async::{AsyncError, EventAsync};
+use data_model::{DataInit, Le16, Le32, Le64};
use virtio_sys::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
use vm_memory::{GuestAddress, GuestMemory};
use super::{SignalableInterrupt, VIRTIO_MSI_NO_VECTOR};
+use crate::virtio::ipc_memory_mapper::IpcMemoryMapper;
+use crate::virtio::memory_mapper::{MemRegion, Permission, Translate};
+use crate::virtio::memory_util::{
+ is_valid_wrapper, read_obj_from_addr_wrapper, write_obj_at_addr_wrapper,
+};
const VIRTQ_DESC_F_NEXT: u16 = 0x1;
const VIRTQ_DESC_F_WRITE: u16 = 0x2;
@@ -64,7 +74,8 @@ pub struct DescriptorChain {
/// Index into the descriptor table
pub index: u16,
- /// Guest physical address of device specific data
+ /// Guest physical address of device specific data, or IO virtual address
+ /// if iommu is used
pub addr: GuestAddress,
/// Length of device specific data
@@ -76,7 +87,21 @@ pub struct DescriptorChain {
/// Index into the descriptor table of the next descriptor if flags has
/// the next bit set
pub next: u16,
+
+ /// Translates `addr` to guest physical address
+ iommu: Option<Arc<Mutex<IpcMemoryMapper>>>,
+}
+
+#[derive(Copy, Clone, Debug)]
+#[repr(C)]
+pub struct Desc {
+ pub addr: Le64,
+ pub len: Le32,
+ pub flags: Le16,
+ pub next: Le16,
}
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for Desc {}
impl DescriptorChain {
pub(crate) fn checked_new(
@@ -85,51 +110,74 @@ impl DescriptorChain {
queue_size: u16,
index: u16,
required_flags: u16,
- ) -> Option<DescriptorChain> {
+ iommu: Option<Arc<Mutex<IpcMemoryMapper>>>,
+ ) -> anyhow::Result<DescriptorChain> {
if index >= queue_size {
- return None;
+ bail!("index ({}) >= queue_size ({})", index, queue_size);
}
- let desc_head = mem.checked_offset(desc_table, (index as u64) * 16)?;
- // These reads can't fail unless Guest memory is hopelessly broken.
- let addr = GuestAddress(mem.read_obj_from_addr::<u64>(desc_head).unwrap() as u64);
- mem.checked_offset(desc_head, 16)?;
- let len: u32 = mem.read_obj_from_addr(desc_head.unchecked_add(8)).unwrap();
- let flags: u16 = mem.read_obj_from_addr(desc_head.unchecked_add(12)).unwrap();
- let next: u16 = mem.read_obj_from_addr(desc_head.unchecked_add(14)).unwrap();
+ let desc_head = desc_table
+ .checked_add((index as u64) * 16)
+ .context("integer overflow")?;
+ let desc: Desc = read_obj_from_addr_wrapper::<Desc>(mem, &iommu, desc_head)
+ .context("failed to read desc")?;
let chain = DescriptorChain {
mem: mem.clone(),
desc_table,
queue_size,
ttl: queue_size,
index,
- addr,
- len,
- flags,
- next,
+ addr: GuestAddress(desc.addr.into()),
+ len: desc.len.into(),
+ flags: desc.flags.into(),
+ next: desc.next.into(),
+ iommu,
};
if chain.is_valid() && chain.flags & required_flags == required_flags {
- Some(chain)
+ Ok(chain)
} else {
- None
+ bail!("chain is invalid")
}
}
- #[allow(clippy::if_same_then_else, clippy::needless_bool)]
- fn is_valid(&self) -> bool {
- if self.len > 0
- && self
- .mem
- .checked_offset(self.addr, self.len as u64 - 1u64)
- .is_none()
- {
- false
- } else if self.has_next() && self.next >= self.queue_size {
- false
+ /// Get the mem region(s), regardless if the `DescriptorChain`s contain gpa (guest physical
+ /// address), or iova (io virtual address) and iommu.
+ pub fn get_mem_regions(&self) -> anyhow::Result<Vec<MemRegion>> {
+ if let Some(iommu) = &self.iommu {
+ iommu
+ .lock()
+ .translate(self.addr.offset(), self.len as u64)
+ .context("failed to get mem regions")
} else {
- true
+ Ok(vec![MemRegion {
+ gpa: self.addr,
+ len: self.len.try_into().expect("u32 doesn't fit in usize"),
+ perm: Permission::RW,
+ }])
+ }
+ }
+
+ fn is_valid(&self) -> bool {
+ if self.len > 0 {
+ match self.get_mem_regions() {
+ Ok(regions) => {
+ if regions.iter().any(|r| {
+ self.mem
+ .checked_offset(r.gpa, r.len as u64 - 1u64)
+ .is_none()
+ }) {
+ return false;
+ }
+ }
+ Err(e) => {
+ error!("{:#}", e);
+ return false;
+ }
+ }
}
+
+ !self.has_next() || self.next < self.queue_size
}
/// Gets if this descriptor chain has another descriptor chain linked after it.
@@ -161,17 +209,24 @@ impl DescriptorChain {
if self.has_next() {
// Once we see a write-only descriptor, all subsequent descriptors must be write-only.
let required_flags = self.flags & VIRTQ_DESC_F_WRITE;
- DescriptorChain::checked_new(
+ let iommu = self.iommu.as_ref().map(Arc::clone);
+ match DescriptorChain::checked_new(
&self.mem,
self.desc_table,
self.queue_size,
self.next,
required_flags,
- )
- .map(|mut c| {
- c.ttl = self.ttl - 1;
- c
- })
+ iommu,
+ ) {
+ Ok(mut c) => {
+ c.ttl = self.ttl - 1;
+ Some(c)
+ }
+ Err(e) => {
+ error!("{:#}", e);
+ None
+ }
+ }
} else {
None
}
@@ -221,8 +276,8 @@ pub struct Queue {
/// Guest physical address of the used ring
pub used_ring: GuestAddress,
- next_avail: Wrapping<u16>,
- next_used: Wrapping<u16>,
+ pub next_avail: Wrapping<u16>,
+ pub next_used: Wrapping<u16>,
// Device feature bits accepted by the driver
features: u64,
@@ -232,6 +287,8 @@ pub struct Queue {
// processing requests. This is the count of how many are in flight(could be several contexts
// handling requests in parallel). When this count is zero, notifications are re-enabled.
notification_disable_count: usize,
+
+ iommu: Option<Arc<Mutex<IpcMemoryMapper>>>,
}
impl Queue {
@@ -250,6 +307,7 @@ impl Queue {
features: 0,
last_used: Wrapping(0),
notification_disable_count: 0,
+ iommu: None,
}
}
@@ -283,44 +341,38 @@ impl Queue {
let used_ring_size = 6 + 8 * queue_size;
if !self.ready {
error!("attempt to use virtio queue that is not marked ready");
- false
+ return false;
} else if self.size > self.max_size || self.size == 0 || (self.size & (self.size - 1)) != 0
{
error!("virtio queue with invalid size: {}", self.size);
- false
- } else if desc_table
- .checked_add(desc_table_size as u64)
- .map_or(true, |v| !mem.address_in_range(v))
- {
- error!(
- "virtio queue descriptor table goes out of bounds: start:0x{:08x} size:0x{:08x}",
- desc_table.offset(),
- desc_table_size
- );
- false
- } else if avail_ring
- .checked_add(avail_ring_size as u64)
- .map_or(true, |v| !mem.address_in_range(v))
- {
- error!(
- "virtio queue available ring goes out of bounds: start:0x{:08x} size:0x{:08x}",
- avail_ring.offset(),
- avail_ring_size
- );
- false
- } else if used_ring
- .checked_add(used_ring_size as u64)
- .map_or(true, |v| !mem.address_in_range(v))
- {
- error!(
- "virtio queue used ring goes out of bounds: start:0x{:08x} size:0x{:08x}",
- used_ring.offset(),
- used_ring_size
- );
- false
- } else {
- true
+ return false;
+ }
+
+ let iommu = self.iommu.as_ref().map(|i| i.lock());
+ for (addr, size, name) in [
+ (desc_table, desc_table_size, "descriptor table"),
+ (avail_ring, avail_ring_size, "available ring"),
+ (used_ring, used_ring_size, "used ring"),
+ ] {
+ match is_valid_wrapper(mem, &iommu, addr, size as u64) {
+ Ok(valid) => {
+ if !valid {
+ error!(
+ "virtio queue {} goes out of bounds: start:0x{:08x} size:0x{:08x}",
+ name,
+ addr.offset(),
+ size,
+ );
+ return false;
+ }
+ }
+ Err(e) => {
+ error!("is_valid failed: {:#}", e);
+ return false;
+ }
+ }
}
+ true
}
// Get the index of the first available descriptor chain in the available ring
@@ -329,11 +381,11 @@ impl Queue {
// All available ring entries between `self.next_avail` and `get_avail_index()` are available
// to be processed by the device.
fn get_avail_index(&self, mem: &GuestMemory) -> Wrapping<u16> {
- let avail_index_addr = self.avail_ring.unchecked_add(2);
- let avail_index: u16 = mem.read_obj_from_addr(avail_index_addr).unwrap();
+ fence(Ordering::SeqCst);
- // Make sure following reads (e.g. desc_idx) don't pass the avail_index read.
- fence(Ordering::Acquire);
+ let avail_index_addr = self.avail_ring.unchecked_add(2);
+ let avail_index: u16 =
+ read_obj_from_addr_wrapper(mem, &self.iommu, avail_index_addr).unwrap();
Wrapping(avail_index)
}
@@ -345,19 +397,23 @@ impl Queue {
//
// This value is only used if the `VIRTIO_F_EVENT_IDX` feature has been negotiated.
fn set_avail_event(&mut self, mem: &GuestMemory, avail_index: Wrapping<u16>) {
+ fence(Ordering::SeqCst);
+
let avail_event_addr = self
.used_ring
.unchecked_add(4 + 8 * u64::from(self.actual_size()));
- mem.write_obj_at_addr(avail_index.0, avail_event_addr)
- .unwrap();
+ write_obj_at_addr_wrapper(mem, &self.iommu, avail_index.0, avail_event_addr).unwrap();
}
// Query the value of a single-bit flag in the available ring.
//
// Returns `true` if `flag` is currently set (by the driver) in the available ring flags.
- #[allow(dead_code)]
fn get_avail_flag(&self, mem: &GuestMemory, flag: u16) -> bool {
- let avail_flags: u16 = mem.read_obj_from_addr(self.avail_ring).unwrap();
+ fence(Ordering::SeqCst);
+
+ let avail_flags: u16 =
+ read_obj_from_addr_wrapper(mem, &self.iommu, self.avail_ring).unwrap();
+
avail_flags & flag == flag
}
@@ -369,10 +425,14 @@ impl Queue {
//
// This value is only valid if the `VIRTIO_F_EVENT_IDX` feature has been negotiated.
fn get_used_event(&self, mem: &GuestMemory) -> Wrapping<u16> {
+ fence(Ordering::SeqCst);
+
let used_event_addr = self
.avail_ring
.unchecked_add(4 + 2 * u64::from(self.actual_size()));
- let used_event: u16 = mem.read_obj_from_addr(used_event_addr).unwrap();
+ let used_event: u16 =
+ read_obj_from_addr_wrapper(mem, &self.iommu, used_event_addr).unwrap();
+
Wrapping(used_event)
}
@@ -381,25 +441,26 @@ impl Queue {
// This indicates to the driver that all entries up to (but not including) `used_index` have
// been used by the device and may be processed by the driver.
fn set_used_index(&mut self, mem: &GuestMemory, used_index: Wrapping<u16>) {
- // This fence ensures all descriptor writes are visible before the index update.
- fence(Ordering::Release);
+ fence(Ordering::SeqCst);
let used_index_addr = self.used_ring.unchecked_add(2);
- mem.write_obj_at_addr(used_index.0, used_index_addr)
- .unwrap();
+ write_obj_at_addr_wrapper(mem, &self.iommu, used_index.0, used_index_addr).unwrap();
}
// Set a single-bit flag in the used ring.
//
// Changes the bit specified by the mask in `flag` to `value`.
fn set_used_flag(&mut self, mem: &GuestMemory, flag: u16, value: bool) {
- let mut used_flags: u16 = mem.read_obj_from_addr(self.used_ring).unwrap();
+ fence(Ordering::SeqCst);
+
+ let mut used_flags: u16 =
+ read_obj_from_addr_wrapper(mem, &self.iommu, self.used_ring).unwrap();
if value {
used_flags |= flag;
} else {
used_flags &= !flag;
}
- mem.write_obj_at_addr(used_flags, self.used_ring).unwrap();
+ write_obj_at_addr_wrapper(mem, &self.iommu, used_flags, self.used_ring).unwrap();
}
/// Get the first available descriptor chain without removing it from the queue.
@@ -418,12 +479,19 @@ impl Queue {
}
let desc_idx_addr_offset = 4 + (u64::from(self.next_avail.0 % queue_size) * 2);
- let desc_idx_addr = mem.checked_offset(self.avail_ring, desc_idx_addr_offset)?;
+ let desc_idx_addr = self.avail_ring.checked_add(desc_idx_addr_offset)?;
// This index is checked below in checked_new.
- let descriptor_index: u16 = mem.read_obj_from_addr(desc_idx_addr).unwrap();
-
- DescriptorChain::checked_new(mem, self.desc_table, queue_size, descriptor_index, 0)
+ let descriptor_index: u16 =
+ read_obj_from_addr_wrapper(mem, &self.iommu, desc_idx_addr).unwrap();
+
+ let iommu = self.iommu.as_ref().map(Arc::clone);
+ DescriptorChain::checked_new(mem, self.desc_table, queue_size, descriptor_index, 0, iommu)
+ .map_err(|e| {
+ error!("{:#}", e);
+ e
+ })
+ .ok()
}
/// Remove the first available descriptor chain from the queue.
@@ -480,19 +548,14 @@ impl Queue {
let used_elem = used_ring.unchecked_add((4 + next_used * 8) as u64);
// These writes can't fail as we are guaranteed to be within the descriptor ring.
- mem.write_obj_at_addr(desc_index as u32, used_elem).unwrap();
- mem.write_obj_at_addr(len as u32, used_elem.unchecked_add(4))
+ write_obj_at_addr_wrapper(mem, &self.iommu, desc_index as u32, used_elem).unwrap();
+ write_obj_at_addr_wrapper(mem, &self.iommu, len as u32, used_elem.unchecked_add(4))
.unwrap();
self.next_used += Wrapping(1);
self.set_used_index(mem, self.next_used);
}
- /// Updates the index at which the driver should signal the device next.
- pub fn update_int_required(&mut self, mem: &GuestMemory) {
- self.set_avail_event(mem, self.get_avail_index(mem));
- }
-
/// Enable / Disable guest notify device that requests are available on
/// the descriptor chain.
pub fn set_notify(&mut self, mem: &GuestMemory, enable: bool) {
@@ -502,9 +565,9 @@ impl Queue {
self.notification_disable_count += 1;
}
- if self.features & ((1u64) << VIRTIO_RING_F_EVENT_IDX) != 0 {
- self.update_int_required(mem);
- } else {
+ // We should only set VIRTQ_USED_F_NO_NOTIFY when the VIRTIO_RING_F_EVENT_IDX feature has
+ // not been negotiated.
+ if self.features & ((1u64) << VIRTIO_RING_F_EVENT_IDX) == 0 {
self.set_used_flag(
mem,
VIRTQ_USED_F_NO_NOTIFY,
@@ -523,13 +586,7 @@ impl Queue {
// so no need to inject new interrupt.
self.next_used - used_event - Wrapping(1) < self.next_used - self.last_used
} else {
- // TODO(b/172975852): This branch should check the flag that requests interrupt
- // supression:
- // ```
- // !self.get_avail_flag(mem, VIRTQ_AVAIL_F_NO_INTERRUPT)
- // ```
- // Re-enable the flag check once the missing interrupt issue is debugged.
- true
+ !self.get_avail_flag(mem, VIRTQ_AVAIL_F_NO_INTERRUPT)
}
}
@@ -554,14 +611,17 @@ impl Queue {
pub fn ack_features(&mut self, features: u64) {
self.features |= features;
}
+
+ pub fn set_iommu(&mut self, iommu: Arc<Mutex<IpcMemoryMapper>>) {
+ self.iommu = Some(iommu);
+ }
}
#[cfg(test)]
mod tests {
use super::super::Interrupt;
use super::*;
- use base::Event;
- use data_model::{DataInit, Le16, Le32, Le64};
+ use crate::IrqLevelEvent;
use std::convert::TryInto;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
@@ -576,17 +636,6 @@ mod tests {
#[derive(Copy, Clone, Debug)]
#[repr(C)]
- struct Desc {
- addr: Le64,
- len: Le32,
- flags: Le16,
- next: Le16,
- }
- // Safe as this only runs in test
- unsafe impl DataInit for Desc {}
-
- #[derive(Copy, Clone, Debug)]
- #[repr(C)]
struct Avail {
flags: Le16,
idx: Le16,
@@ -669,18 +718,18 @@ mod tests {
fn queue_event_id_guest_fast() {
let mut queue = Queue::new(QUEUE_SIZE.try_into().unwrap());
let memory_start_addr = GuestAddress(0x0);
- let mem = GuestMemory::new(&vec![(memory_start_addr, GUEST_MEMORY_SIZE)]).unwrap();
+ let mem = GuestMemory::new(&[(memory_start_addr, GUEST_MEMORY_SIZE)]).unwrap();
setup_vq(&mut queue, &mem);
let interrupt = Interrupt::new(
Arc::new(AtomicUsize::new(0)),
- Event::new().unwrap(),
- Event::new().unwrap(),
+ IrqLevelEvent::new().unwrap(),
None,
10,
);
// Calculating the offset of used_event within Avail structure
+ #[allow(deref_nullptr)]
let used_event_offset: u64 =
unsafe { &(*(::std::ptr::null::<Avail>())).used_event as *const _ as u64 };
let used_event_address = GuestAddress(AVAIL_OFFSET + used_event_offset);
@@ -745,18 +794,18 @@ mod tests {
fn queue_event_id_guest_slow() {
let mut queue = Queue::new(QUEUE_SIZE.try_into().unwrap());
let memory_start_addr = GuestAddress(0x0);
- let mem = GuestMemory::new(&vec![(memory_start_addr, GUEST_MEMORY_SIZE)]).unwrap();
+ let mem = GuestMemory::new(&[(memory_start_addr, GUEST_MEMORY_SIZE)]).unwrap();
setup_vq(&mut queue, &mem);
let interrupt = Interrupt::new(
Arc::new(AtomicUsize::new(0)),
- Event::new().unwrap(),
- Event::new().unwrap(),
+ IrqLevelEvent::new().unwrap(),
None,
10,
);
// Calculating the offset of used_event within Avail structure
+ #[allow(deref_nullptr)]
let used_event_offset: u64 =
unsafe { &(*(::std::ptr::null::<Avail>())).used_event as *const _ as u64 };
let used_event_address = GuestAddress(AVAIL_OFFSET + used_event_offset);
diff --git a/devices/src/virtio/resource_bridge.rs b/devices/src/virtio/resource_bridge.rs
index a89843764..dfc1601a1 100644
--- a/devices/src/virtio/resource_bridge.rs
+++ b/devices/src/virtio/resource_bridge.rs
@@ -8,7 +8,9 @@
use std::fmt;
use std::fs::File;
+use remain::sorted;
use serde::{Deserialize, Serialize};
+use thiserror::Error;
use base::{with_as_descriptor, Tube, TubeError};
@@ -48,11 +50,15 @@ pub enum ResourceResponse {
Invalid,
}
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum ResourceBridgeError {
+ #[error("attempt to send non-existent gpu resource for {0}")]
InvalidResource(ResourceRequest),
- SendFailure(ResourceRequest, TubeError),
+ #[error("error receiving resource bridge response for {0}: {1}")]
RecieveFailure(ResourceRequest, TubeError),
+ #[error("failed to send a resource bridge request for {0}: {1}")]
+ SendFailure(ResourceRequest, TubeError),
}
impl fmt::Display for ResourceRequest {
@@ -64,28 +70,6 @@ impl fmt::Display for ResourceRequest {
}
}
-impl fmt::Display for ResourceBridgeError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- ResourceBridgeError::InvalidResource(req) => {
- write!(f, "attempt to send non-existent gpu resource for {}", req)
- }
- ResourceBridgeError::SendFailure(req, e) => write!(
- f,
- "failed to send a resource bridge request for {}: {}",
- req, e
- ),
- ResourceBridgeError::RecieveFailure(req, e) => write!(
- f,
- "error receiving resource bridge response for {}: {}",
- req, e
- ),
- }
- }
-}
-
-impl std::error::Error for ResourceBridgeError {}
-
pub fn get_resource_info(
tube: &Tube,
request: ResourceRequest,
diff --git a/devices/src/virtio/rng.rs b/devices/src/virtio/rng.rs
index 65d671e6a..edc1c99d9 100644
--- a/devices/src/virtio/rng.rs
+++ b/devices/src/virtio/rng.rs
@@ -2,12 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fmt::{self, Display};
use std::fs::File;
use std::io;
use std::thread;
use base::{error, warn, AsRawDescriptor, Event, PollToken, RawDescriptor, WaitContext};
+use remain::sorted;
+use thiserror::Error;
use vm_memory::GuestMemory;
use super::{Interrupt, Queue, SignalableInterrupt, VirtioDevice, Writer, TYPE_RNG};
@@ -15,23 +16,15 @@ use super::{Interrupt, Queue, SignalableInterrupt, VirtioDevice, Writer, TYPE_RN
const QUEUE_SIZE: u16 = 256;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum RngError {
/// Can't access /dev/urandom
+ #[error("failed to access /dev/urandom: {0}")]
AccessingRandomDev(io::Error),
}
pub type Result<T> = std::result::Result<T, RngError>;
-impl Display for RngError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::RngError::*;
-
- match self {
- AccessingRandomDev(e) => write!(f, "failed to access /dev/urandom: {}", e),
- }
- }
-}
-
struct Worker {
interrupt: Interrupt,
queue: Queue,
@@ -119,7 +112,7 @@ impl Worker {
}
}
if needs_interrupt {
- self.interrupt.signal_used_queue(self.queue.vector);
+ self.queue.trigger_interrupt(&self.mem, &self.interrupt);
}
}
}
diff --git a/devices/src/virtio/snd/common.rs b/devices/src/virtio/snd/common.rs
new file mode 100644
index 000000000..0f734e9c2
--- /dev/null
+++ b/devices/src/virtio/snd/common.rs
@@ -0,0 +1,112 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use audio_streams::SampleFormat;
+use remain::sorted;
+use thiserror::Error as ThisError;
+
+use crate::virtio::snd::constants::*;
+
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum Error {
+ #[error("Unsupported frame rate: {0}")]
+ UnsupportedFrameRate(u32),
+ #[error("Unsupported virtio frame rate: {0}")]
+ UnsupportedVirtioFrameRate(u8),
+ #[error("Unsupported virtio pcm format: {0}")]
+ UnsupportedVirtioPcmFormat(u8),
+}
+
+type Result<T> = std::result::Result<T, Error>;
+
+/// Converts VIRTIO_SND_PCM_RATE_* enum to frame rate
+pub fn from_virtio_frame_rate(virtio_frame_rate: u8) -> Result<u32> {
+ Ok(match virtio_frame_rate {
+ VIRTIO_SND_PCM_RATE_5512 => 5512u32,
+ VIRTIO_SND_PCM_RATE_8000 => 8000u32,
+ VIRTIO_SND_PCM_RATE_11025 => 11025u32,
+ VIRTIO_SND_PCM_RATE_16000 => 16000u32,
+ VIRTIO_SND_PCM_RATE_22050 => 22050u32,
+ VIRTIO_SND_PCM_RATE_32000 => 32000u32,
+ VIRTIO_SND_PCM_RATE_44100 => 44100u32,
+ VIRTIO_SND_PCM_RATE_48000 => 48000u32,
+ VIRTIO_SND_PCM_RATE_64000 => 64000u32,
+ VIRTIO_SND_PCM_RATE_88200 => 88200u32,
+ VIRTIO_SND_PCM_RATE_96000 => 96000u32,
+ VIRTIO_SND_PCM_RATE_176400 => 176400u32,
+ VIRTIO_SND_PCM_RATE_192000 => 192000u32,
+ VIRTIO_SND_PCM_RATE_384000 => 384000u32,
+ _ => {
+ return Err(Error::UnsupportedVirtioFrameRate(virtio_frame_rate));
+ }
+ })
+}
+
+/// Converts VIRTIO_SND_PCM_FMT_* enum to SampleFormat
+pub fn from_virtio_sample_format(virtio_pcm_format: u8) -> Result<SampleFormat> {
+ Ok(match virtio_pcm_format {
+ VIRTIO_SND_PCM_FMT_U8 => SampleFormat::U8,
+ VIRTIO_SND_PCM_FMT_S16 => SampleFormat::S16LE,
+ VIRTIO_SND_PCM_FMT_S24 => SampleFormat::S24LE,
+ VIRTIO_SND_PCM_FMT_S32 => SampleFormat::S32LE,
+ _ => {
+ return Err(Error::UnsupportedVirtioPcmFormat(virtio_pcm_format));
+ }
+ })
+}
+
+/// Converts SampleFormat to VIRTIO_SND_PCM_FMT_*
+pub fn from_sample_format(format: SampleFormat) -> u8 {
+ match format {
+ SampleFormat::U8 => VIRTIO_SND_PCM_FMT_U8,
+ SampleFormat::S16LE => VIRTIO_SND_PCM_FMT_S16,
+ SampleFormat::S24LE => VIRTIO_SND_PCM_FMT_S24,
+ SampleFormat::S32LE => VIRTIO_SND_PCM_FMT_S32,
+ }
+}
+
+/// Converts frame rate to VIRTIO_SND_PCM_RATE_* enum
+pub fn virtio_frame_rate(frame_rate: u32) -> Result<u8> {
+ Ok(match frame_rate {
+ 5512u32 => VIRTIO_SND_PCM_RATE_5512,
+ 8000u32 => VIRTIO_SND_PCM_RATE_8000,
+ 11025u32 => VIRTIO_SND_PCM_RATE_11025,
+ 16000u32 => VIRTIO_SND_PCM_RATE_16000,
+ 22050u32 => VIRTIO_SND_PCM_RATE_22050,
+ 32000u32 => VIRTIO_SND_PCM_RATE_32000,
+ 44100u32 => VIRTIO_SND_PCM_RATE_44100,
+ 48000u32 => VIRTIO_SND_PCM_RATE_48000,
+ 64000u32 => VIRTIO_SND_PCM_RATE_64000,
+ 88200u32 => VIRTIO_SND_PCM_RATE_88200,
+ 96000u32 => VIRTIO_SND_PCM_RATE_96000,
+ 176400u32 => VIRTIO_SND_PCM_RATE_176400,
+ 192000u32 => VIRTIO_SND_PCM_RATE_192000,
+ 384000u32 => VIRTIO_SND_PCM_RATE_384000,
+ _ => {
+ return Err(Error::UnsupportedFrameRate(frame_rate));
+ }
+ })
+}
+
+/// Get the name of VIRTIO_SND_R_PCM_* enums
+pub fn get_virtio_snd_r_pcm_cmd_name(cmd_code: u32) -> &'static str {
+ match cmd_code {
+ 0 => "Uninitialized",
+ VIRTIO_SND_R_PCM_SET_PARAMS => "VIRTIO_SND_R_PCM_SET_PARAMS",
+ VIRTIO_SND_R_PCM_PREPARE => "VIRTIO_SND_R_PCM_PREPARE",
+ VIRTIO_SND_R_PCM_START => "VIRTIO_SND_R_PCM_START",
+ VIRTIO_SND_R_PCM_STOP => "VIRTIO_SND_R_PCM_STOP",
+ VIRTIO_SND_R_PCM_RELEASE => "VIRTIO_SND_R_PCM_RELEASE",
+ _ => unreachable!(),
+ }
+}
+
+pub fn get_virtio_direction_name(dir: u8) -> &'static str {
+ match dir {
+ VIRTIO_SND_D_OUTPUT => "VIRTIO_SND_D_OUTPUT",
+ VIRTIO_SND_D_INPUT => "VIRTIO_SND_D_INPUT",
+ _ => unreachable!(),
+ }
+}
diff --git a/devices/src/virtio/snd/constants.rs b/devices/src/virtio/snd/constants.rs
index 28a73a083..48e598c59 100644
--- a/devices/src/virtio/snd/constants.rs
+++ b/devices/src/virtio/snd/constants.rs
@@ -1,21 +1,41 @@
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-pub const JACK_INFO: u32 = 1;
-pub const JACK_REMAP: u32 = 2;
+pub const VIRTIO_SND_R_JACK_INFO: u32 = 1;
+pub const VIRTIO_SND_R_JACK_REMAP: u32 = 2;
-pub const STREAM_INFO: u32 = 0x0100;
-pub const STREAM_SET_PARAMS: u32 = 0x0100 + 1;
-pub const STREAM_PREPARE: u32 = 0x0100 + 2;
-pub const STREAM_RELEASE: u32 = 0x0100 + 3;
-pub const STREAM_START: u32 = 0x0100 + 4;
-pub const STREAM_STOP: u32 = 0x0100 + 5;
+/* PCM control request types */
+pub const VIRTIO_SND_R_PCM_INFO: u32 = 0x0100;
+pub const VIRTIO_SND_R_PCM_SET_PARAMS: u32 = 0x0101;
+pub const VIRTIO_SND_R_PCM_PREPARE: u32 = 0x0102;
+pub const VIRTIO_SND_R_PCM_RELEASE: u32 = 0x0103;
+pub const VIRTIO_SND_R_PCM_START: u32 = 0x0104;
+pub const VIRTIO_SND_R_PCM_STOP: u32 = 0x0105;
+
+/* channel map control request types */
+pub const VIRTIO_SND_R_CHMAP_INFO: u32 = 0x0200;
-pub const CHANNEL_MAP_INFO: u32 = 0x0200;
+/* jack event types */
+pub const VIRTIO_SND_EVT_JACK_CONNECTED: u32 = 0x1000;
+pub const VIRTIO_SND_EVT_JACK_DISCONNECTED: u32 = 0x1001;
+/* PCM event types */
+pub const VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED: u32 = 0x1100;
+pub const VIRTIO_SND_EVT_PCM_XRUN: u32 = 0x1101;
+
+/* common status codes */
+pub const VIRTIO_SND_S_OK: u32 = 0x8000;
+pub const VIRTIO_SND_S_BAD_MSG: u32 = 0x8001;
+pub const VIRTIO_SND_S_NOT_SUPP: u32 = 0x8002;
+pub const VIRTIO_SND_S_IO_ERR: u32 = 0x8003;
+
+/* stream direction */
pub const VIRTIO_SND_D_OUTPUT: u8 = 0;
pub const VIRTIO_SND_D_INPUT: u8 = 1;
+/* supported jack features */
+pub const VIRTIO_SND_JACK_F_REMAP: u32 = 0;
+
/* supported PCM stream features */
pub const VIRTIO_SND_PCM_F_SHMEM_HOST: u8 = 0;
pub const VIRTIO_SND_PCM_F_SHMEM_GUEST: u8 = 1;
@@ -45,11 +65,13 @@ pub const VIRTIO_SND_PCM_FMT_S32: u8 = 17;
pub const VIRTIO_SND_PCM_FMT_U32: u8 = 18;
pub const VIRTIO_SND_PCM_FMT_FLOAT: u8 = 19;
pub const VIRTIO_SND_PCM_FMT_FLOAT64: u8 = 20;
+/* digital formats (width / physical width) */
pub const VIRTIO_SND_PCM_FMT_DSD_U8: u8 = 21;
pub const VIRTIO_SND_PCM_FMT_DSD_U16: u8 = 22;
pub const VIRTIO_SND_PCM_FMT_DSD_U32: u8 = 23;
pub const VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME: u8 = 24;
+/* supported PCM frame rates */
pub const VIRTIO_SND_PCM_RATE_5512: u8 = 0;
pub const VIRTIO_SND_PCM_RATE_8000: u8 = 1;
pub const VIRTIO_SND_PCM_RATE_11025: u8 = 2;
@@ -65,75 +87,43 @@ pub const VIRTIO_SND_PCM_RATE_176400: u8 = 11;
pub const VIRTIO_SND_PCM_RATE_192000: u8 = 12;
pub const VIRTIO_SND_PCM_RATE_384000: u8 = 13;
-// From https://github.com/oasis-tcs/virtio-spec/blob/master/virtio-sound.tex
-/* jack control request types */
-pub const VIRTIO_SND_R_JACK_INFO: u32 = 1;
-pub const VIRTIO_SND_R_JACK_REMAP: u32 = 2;
-
-/* PCM control request types */
-pub const VIRTIO_SND_R_PCM_INFO: u32 = 0x0100;
-pub const VIRTIO_SND_R_PCM_SET_PARAMS: u32 = 0x0101;
-pub const VIRTIO_SND_R_PCM_PREPARE: u32 = 0x0102;
-pub const VIRTIO_SND_R_PCM_RELEASE: u32 = 0x0103;
-pub const VIRTIO_SND_R_PCM_START: u32 = 0x0104;
-pub const VIRTIO_SND_R_PCM_STOP: u32 = 0x0105;
-
-/* channel map control request types */
-pub const VIRTIO_SND_R_CHMAP_INFO: u32 = 0x0200;
-
-/* jack event types */
-pub const VIRTIO_SND_EVT_JACK_CONNECTED: u32 = 0x1000;
-pub const VIRTIO_SND_EVT_JACK_DISCONNECTED: u32 = 0x1001;
-
-/* PCM event types */
-pub const VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED: u32 = 0x1100;
-pub const VIRTIO_SND_EVT_PCM_XRUN: u32 = 0x1101;
-
-/* common status codes */
-pub const VIRTIO_SND_S_OK: u32 = 0x8000;
-pub const VIRTIO_SND_S_BAD_MSG: u32 = 0x8001;
-pub const VIRTIO_SND_S_NOT_SUPP: u32 = 0x8002;
-pub const VIRTIO_SND_S_IO_ERR: u32 = 0x8003;
-
-pub const VIRTIO_SND_JACK_F_REMAP: u32 = 0;
-
/* standard channel position definition */
-pub const VIRTIO_SND_CHMAP_NONE: u32 = 0; /* undefined */
-pub const VIRTIO_SND_CHMAP_NA: u32 = 1; /* silent */
-pub const VIRTIO_SND_CHMAP_MONO: u32 = 2; /* mono stream */
-pub const VIRTIO_SND_CHMAP_FL: u32 = 3; /* front left */
-pub const VIRTIO_SND_CHMAP_FR: u32 = 4; /* front right */
-pub const VIRTIO_SND_CHMAP_RL: u32 = 5; /* rear left */
-pub const VIRTIO_SND_CHMAP_RR: u32 = 6; /* rear right */
-pub const VIRTIO_SND_CHMAP_FC: u32 = 7; /* front center */
-pub const VIRTIO_SND_CHMAP_LFE: u32 = 8; /* low frequency (LFE) */
-pub const VIRTIO_SND_CHMAP_SL: u32 = 9; /* side left */
-pub const VIRTIO_SND_CHMAP_SR: u32 = 10; /* side right */
-pub const VIRTIO_SND_CHMAP_RC: u32 = 11; /* rear center */
-pub const VIRTIO_SND_CHMAP_FLC: u32 = 12; /* front left center */
-pub const VIRTIO_SND_CHMAP_FRC: u32 = 13; /* front right center */
-pub const VIRTIO_SND_CHMAP_RLC: u32 = 14; /* rear left center */
-pub const VIRTIO_SND_CHMAP_RRC: u32 = 15; /* rear right center */
-pub const VIRTIO_SND_CHMAP_FLW: u32 = 16; /* front left wide */
-pub const VIRTIO_SND_CHMAP_FRW: u32 = 17; /* front right wide */
-pub const VIRTIO_SND_CHMAP_FLH: u32 = 18; /* front left high */
-pub const VIRTIO_SND_CHMAP_FCH: u32 = 19; /* front center high */
-pub const VIRTIO_SND_CHMAP_FRH: u32 = 20; /* front right high */
-pub const VIRTIO_SND_CHMAP_TC: u32 = 21; /* top center */
-pub const VIRTIO_SND_CHMAP_TFL: u32 = 22; /* top front left */
-pub const VIRTIO_SND_CHMAP_TFR: u32 = 23; /* top front right */
-pub const VIRTIO_SND_CHMAP_TFC: u32 = 24; /* top front center */
-pub const VIRTIO_SND_CHMAP_TRL: u32 = 25; /* top rear left */
-pub const VIRTIO_SND_CHMAP_TRR: u32 = 26; /* top rear right */
-pub const VIRTIO_SND_CHMAP_TRC: u32 = 27; /* top rear center */
-pub const VIRTIO_SND_CHMAP_TFLC: u32 = 28; /* top front left center */
-pub const VIRTIO_SND_CHMAP_TFRC: u32 = 29; /* top front right center */
-pub const VIRTIO_SND_CHMAP_TSL: u32 = 34; /* top side left */
-pub const VIRTIO_SND_CHMAP_TSR: u32 = 35; /* top side right */
-pub const VIRTIO_SND_CHMAP_LLFE: u32 = 36; /* left LFE */
-pub const VIRTIO_SND_CHMAP_RLFE: u32 = 37; /* right LFE */
-pub const VIRTIO_SND_CHMAP_BC: u32 = 38; /* bottom center */
-pub const VIRTIO_SND_CHMAP_BLC: u32 = 39; /* bottom left center */
-pub const VIRTIO_SND_CHMAP_BRC: u32 = 40; /* bottom right center */
+pub const VIRTIO_SND_CHMAP_NONE: u8 = 0; /* undefined */
+pub const VIRTIO_SND_CHMAP_NA: u8 = 1; /* silent */
+pub const VIRTIO_SND_CHMAP_MONO: u8 = 2; /* mono stream */
+pub const VIRTIO_SND_CHMAP_FL: u8 = 3; /* front left */
+pub const VIRTIO_SND_CHMAP_FR: u8 = 4; /* front right */
+pub const VIRTIO_SND_CHMAP_RL: u8 = 5; /* rear left */
+pub const VIRTIO_SND_CHMAP_RR: u8 = 6; /* rear right */
+pub const VIRTIO_SND_CHMAP_FC: u8 = 7; /* front center */
+pub const VIRTIO_SND_CHMAP_LFE: u8 = 8; /* low frequency (LFE) */
+pub const VIRTIO_SND_CHMAP_SL: u8 = 9; /* side left */
+pub const VIRTIO_SND_CHMAP_SR: u8 = 10; /* side right */
+pub const VIRTIO_SND_CHMAP_RC: u8 = 11; /* rear center */
+pub const VIRTIO_SND_CHMAP_FLC: u8 = 12; /* front left center */
+pub const VIRTIO_SND_CHMAP_FRC: u8 = 13; /* front right center */
+pub const VIRTIO_SND_CHMAP_RLC: u8 = 14; /* rear left center */
+pub const VIRTIO_SND_CHMAP_RRC: u8 = 15; /* rear right center */
+pub const VIRTIO_SND_CHMAP_FLW: u8 = 16; /* front left wide */
+pub const VIRTIO_SND_CHMAP_FRW: u8 = 17; /* front right wide */
+pub const VIRTIO_SND_CHMAP_FLH: u8 = 18; /* front left high */
+pub const VIRTIO_SND_CHMAP_FCH: u8 = 19; /* front center high */
+pub const VIRTIO_SND_CHMAP_FRH: u8 = 20; /* front right high */
+pub const VIRTIO_SND_CHMAP_TC: u8 = 21; /* top center */
+pub const VIRTIO_SND_CHMAP_TFL: u8 = 22; /* top front left */
+pub const VIRTIO_SND_CHMAP_TFR: u8 = 23; /* top front right */
+pub const VIRTIO_SND_CHMAP_TFC: u8 = 24; /* top front center */
+pub const VIRTIO_SND_CHMAP_TRL: u8 = 25; /* top rear left */
+pub const VIRTIO_SND_CHMAP_TRR: u8 = 26; /* top rear right */
+pub const VIRTIO_SND_CHMAP_TRC: u8 = 27; /* top rear center */
+pub const VIRTIO_SND_CHMAP_TFLC: u8 = 28; /* top front left center */
+pub const VIRTIO_SND_CHMAP_TFRC: u8 = 29; /* top front right center */
+pub const VIRTIO_SND_CHMAP_TSL: u8 = 34; /* top side left */
+pub const VIRTIO_SND_CHMAP_TSR: u8 = 35; /* top side right */
+pub const VIRTIO_SND_CHMAP_LLFE: u8 = 36; /* left LFE */
+pub const VIRTIO_SND_CHMAP_RLFE: u8 = 37; /* right LFE */
+pub const VIRTIO_SND_CHMAP_BC: u8 = 38; /* bottom center */
+pub const VIRTIO_SND_CHMAP_BLC: u8 = 39; /* bottom left center */
+pub const VIRTIO_SND_CHMAP_BRC: u8 = 40; /* bottom right center */
pub const VIRTIO_SND_CHMAP_MAX_SIZE: usize = 18;
diff --git a/devices/src/virtio/snd/cras_backend/async_funcs.rs b/devices/src/virtio/snd/cras_backend/async_funcs.rs
new file mode 100644
index 000000000..cb0c6a65b
--- /dev/null
+++ b/devices/src/virtio/snd/cras_backend/async_funcs.rs
@@ -0,0 +1,767 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use futures::{
+ channel::{mpsc, oneshot},
+ SinkExt, StreamExt,
+};
+use std::io::{self, Read, Write};
+use std::rc::Rc;
+
+use audio_streams::{capture::AsyncCaptureBuffer, AsyncPlaybackBuffer};
+use base::{debug, error};
+use cros_async::{sync::Mutex as AsyncMutex, EventAsync, Executor};
+use data_model::{DataInit, Le32};
+use vm_memory::GuestMemory;
+
+use crate::virtio::cras_backend::{Parameters, PcmResponse};
+use crate::virtio::snd::common::*;
+use crate::virtio::snd::constants::*;
+use crate::virtio::snd::layout::*;
+use crate::virtio::{DescriptorChain, Queue, Reader, SignalableInterrupt, Writer};
+
+use super::{DirectionalStream, Error, SndData, StreamInfo, WorkerStatus};
+
+// Returns true if the operation is successful. Returns error if there is
+// a runtime/internal error
+async fn process_pcm_ctrl(
+ ex: &Executor,
+ mem: &GuestMemory,
+ tx_send: &mpsc::UnboundedSender<PcmResponse>,
+ rx_send: &mpsc::UnboundedSender<PcmResponse>,
+ streams: &Rc<AsyncMutex<Vec<AsyncMutex<StreamInfo<'_>>>>>,
+ params: &Parameters,
+ cmd_code: u32,
+ writer: &mut Writer,
+ stream_id: usize,
+) -> Result<(), Error> {
+ let streams = streams.read_lock().await;
+ let mut stream = match streams.get(stream_id) {
+ Some(stream_info) => stream_info.lock().await,
+ None => {
+ error!(
+ "Stream id={} not found for {}. Error code: VIRTIO_SND_S_BAD_MSG",
+ stream_id,
+ get_virtio_snd_r_pcm_cmd_name(cmd_code)
+ );
+ return writer
+ .write_obj(VIRTIO_SND_S_BAD_MSG)
+ .map_err(Error::WriteResponse);
+ }
+ };
+
+ debug!(
+ "{} for stream id={}",
+ get_virtio_snd_r_pcm_cmd_name(cmd_code),
+ stream_id
+ );
+
+ let result = match cmd_code {
+ VIRTIO_SND_R_PCM_PREPARE => {
+ stream
+ .prepare(ex, mem.clone(), tx_send, rx_send, params)
+ .await
+ }
+ VIRTIO_SND_R_PCM_START => stream.start().await,
+ VIRTIO_SND_R_PCM_STOP => stream.stop().await,
+ VIRTIO_SND_R_PCM_RELEASE => stream.release().await,
+ _ => unreachable!(),
+ };
+ match result {
+ Ok(_) => {
+ return writer
+ .write_obj(VIRTIO_SND_S_OK)
+ .map_err(Error::WriteResponse);
+ }
+ Err(Error::OperationNotSupported) => {
+ error!(
+ "{} for stream id={} failed. Error code: VIRTIO_SND_S_NOT_SUPP.",
+ get_virtio_snd_r_pcm_cmd_name(cmd_code),
+ stream_id
+ );
+
+ return writer
+ .write_obj(VIRTIO_SND_S_NOT_SUPP)
+ .map_err(Error::WriteResponse);
+ }
+ Err(e) => {
+ // Runtime/internal error would be more appropriate, but there's
+ // no such error type
+ error!(
+ "{} for stream id={} failed. Error code: VIRTIO_SND_S_IO_ERR. Actual error: {}",
+ get_virtio_snd_r_pcm_cmd_name(cmd_code),
+ stream_id,
+ e
+ );
+ return writer
+ .write_obj(VIRTIO_SND_S_IO_ERR)
+ .map_err(Error::WriteResponse);
+ }
+ };
+}
+
+async fn write_data<'a>(
+ mut dst_buf: AsyncPlaybackBuffer<'a>,
+ reader: Option<Reader>,
+ period_bytes: usize,
+) -> Result<(), Error> {
+ let transferred = match reader {
+ Some(mut reader) => dst_buf.copy_from(&mut reader),
+ None => dst_buf.copy_from(&mut io::repeat(0).take(period_bytes as u64)),
+ }
+ .map_err(Error::Io)?;
+ if transferred as usize != period_bytes {
+ error!(
+ "Bytes written {} != period_bytes {}",
+ transferred, period_bytes
+ );
+ Err(Error::InvalidBufferSize)
+ } else {
+ dst_buf.commit().await;
+ Ok(())
+ }
+}
+
+async fn read_data<'a>(
+ mut src_buf: AsyncCaptureBuffer<'a>,
+ writer: Option<&mut Writer>,
+ period_bytes: usize,
+) -> Result<(), Error> {
+ let transferred = match writer {
+ Some(writer) => src_buf.copy_to(writer),
+ None => src_buf.copy_to(&mut io::sink()),
+ }
+ .map_err(Error::Io)?;
+ if transferred as usize != period_bytes {
+ error!(
+ "Bytes written {} != period_bytes {}",
+ transferred, period_bytes
+ );
+ Err(Error::InvalidBufferSize)
+ } else {
+ src_buf.commit().await;
+ Ok(())
+ }
+}
+
+impl From<Result<(), Error>> for virtio_snd_pcm_status {
+ fn from(res: Result<(), Error>) -> Self {
+ let status = match res {
+ Ok(()) => VIRTIO_SND_S_OK,
+ Err(e) => {
+ error!("PCM I/O message failed: {}", e);
+ VIRTIO_SND_S_IO_ERR
+ }
+ };
+
+ // TODO(woodychow): Extend audio_streams API, and fetch latency_bytes from
+ // `next_playback_buffer` or `next_capture_buffer`"
+ Self {
+ status: Le32::from(status),
+ latency_bytes: Le32::from(0),
+ }
+ }
+}
+
+// Drain all DescriptorChain in desc_receiver during WorkerStatus::Quit process.
+async fn drain_desc_receiver(
+ desc_receiver: &mut mpsc::UnboundedReceiver<DescriptorChain>,
+ mem: &GuestMemory,
+ sender: &mut mpsc::UnboundedSender<PcmResponse>,
+) -> Result<(), Error> {
+ let mut o_desc_chain = desc_receiver.next().await;
+ while let Some(desc_chain) = o_desc_chain {
+ // From the virtio-snd spec:
+ // The device MUST complete all pending I/O messages for the specified stream ID.
+ let desc_index = desc_chain.index;
+ let writer = Writer::new(mem.clone(), desc_chain).map_err(Error::DescriptorChain)?;
+ let status = virtio_snd_pcm_status {
+ status: Le32::from(VIRTIO_SND_S_OK),
+ latency_bytes: Le32::from(0),
+ };
+ // Fetch next DescriptorChain to see if the current one is the last one.
+ o_desc_chain = desc_receiver.next().await;
+ let (done, future) = if o_desc_chain.is_none() {
+ let (done, future) = oneshot::channel();
+ (Some(done), Some(future))
+ } else {
+ (None, None)
+ };
+ sender
+ .send(PcmResponse {
+ desc_index,
+ status,
+ writer,
+ done,
+ })
+ .await
+ .map_err(Error::MpscSend)?;
+
+ if let Some(f) = future {
+ // From the virtio-snd spec:
+ // The device MUST NOT complete the control request (VIRTIO_SND_R_PCM_RELEASE)
+ // while there are pending I/O messages for the specified stream ID.
+ f.await.map_err(Error::DoneNotTriggered)?;
+ };
+ }
+ Ok(())
+}
+
+/// Start a pcm worker that receives descriptors containing PCM frames (audio data) from the tx/rx
+/// queue, and forward them to CRAS. One pcm worker per stream.
+///
+/// This worker is started when VIRTIO_SND_R_PCM_PREPARE is called, and returned before
+/// VIRTIO_SND_R_PCM_RELEASE is completed for the stream.
+pub async fn start_pcm_worker(
+ ex: Executor,
+ dstream: DirectionalStream,
+ mut desc_receiver: mpsc::UnboundedReceiver<DescriptorChain>,
+ status_mutex: Rc<AsyncMutex<WorkerStatus>>,
+ mem: GuestMemory,
+ mut sender: mpsc::UnboundedSender<PcmResponse>,
+ period_bytes: usize,
+) -> Result<(), Error> {
+ match dstream {
+ DirectionalStream::Output(mut stream) => {
+ loop {
+ let dst_buf = stream
+ .next_playback_buffer(&ex)
+ .await
+ .map_err(Error::FetchBuffer)?;
+ let worker_status = status_mutex.lock().await;
+ match *worker_status {
+ WorkerStatus::Quit => {
+ drain_desc_receiver(&mut desc_receiver, &mem, &mut sender).await?;
+ write_data(dst_buf, None, period_bytes).await?;
+ break Ok(());
+ }
+ WorkerStatus::Pause => {
+ write_data(dst_buf, None, period_bytes).await?;
+ }
+ WorkerStatus::Running => match desc_receiver.try_next() {
+ Err(e) => {
+ error!("Underrun. No new DescriptorChain while running: {}", e);
+ write_data(dst_buf, None, period_bytes).await?;
+ }
+ Ok(None) => {
+ error!("Unreachable. status should be Quit when the channel is closed");
+ write_data(dst_buf, None, period_bytes).await?;
+ return Err(Error::InvalidPCMWorkerState);
+ }
+ Ok(Some(desc_chain)) => {
+ let desc_index = desc_chain.index;
+ let mut reader = Reader::new(mem.clone(), desc_chain.clone())
+ .map_err(Error::DescriptorChain)?;
+ // stream_id was already read in handle_pcm_queue
+ reader.consume(std::mem::size_of::<virtio_snd_pcm_xfer>());
+ let writer = Writer::new(mem.clone(), desc_chain)
+ .map_err(Error::DescriptorChain)?;
+
+ sender
+ .send(PcmResponse {
+ desc_index,
+ status: write_data(dst_buf, Some(reader), period_bytes)
+ .await
+ .into(),
+ writer,
+ done: None,
+ })
+ .await
+ .map_err(Error::MpscSend)?;
+ }
+ },
+ }
+ }
+ }
+ DirectionalStream::Input(mut stream) => {
+ loop {
+ let src_buf = stream
+ .next_capture_buffer(&ex)
+ .await
+ .map_err(Error::FetchBuffer)?;
+
+ let worker_status = status_mutex.lock().await;
+ match *worker_status {
+ WorkerStatus::Quit => {
+ drain_desc_receiver(&mut desc_receiver, &mem, &mut sender).await?;
+ read_data(src_buf, None, period_bytes).await?;
+ break Ok(());
+ }
+ WorkerStatus::Pause => {
+ read_data(src_buf, None, period_bytes).await?;
+ }
+ WorkerStatus::Running => match desc_receiver.try_next() {
+ Err(e) => {
+ error!("Overrun. No new DescriptorChain while running: {}", e);
+ read_data(src_buf, None, period_bytes).await?;
+ }
+ Ok(None) => {
+ error!("Unreachable. status should be Quit when the channel is closed");
+ read_data(src_buf, None, period_bytes).await?;
+ return Err(Error::InvalidPCMWorkerState);
+ }
+ Ok(Some(desc_chain)) => {
+ let desc_index = desc_chain.index;
+ let mut reader = Reader::new(mem.clone(), desc_chain.clone())
+ .map_err(Error::DescriptorChain)?;
+ // stream_id was already read in handle_pcm_queue
+ reader.consume(std::mem::size_of::<virtio_snd_pcm_xfer>());
+ let mut writer = Writer::new(mem.clone(), desc_chain)
+ .map_err(Error::DescriptorChain)?;
+
+ sender
+ .send(PcmResponse {
+ desc_index,
+ status: read_data(src_buf, Some(&mut writer), period_bytes)
+ .await
+ .into(),
+ writer,
+ done: None,
+ })
+ .await
+ .map_err(Error::MpscSend)?;
+ }
+ },
+ }
+ }
+ }
+ }
+}
+
+// Defer pcm message response to the pcm response worker
+async fn defer_pcm_response_to_worker(
+ desc_chain: DescriptorChain,
+ mem: &GuestMemory,
+ status: virtio_snd_pcm_status,
+ response_sender: &mut mpsc::UnboundedSender<PcmResponse>,
+) -> Result<(), Error> {
+ let desc_index = desc_chain.index;
+ let writer = Writer::new(mem.clone(), desc_chain).map_err(Error::DescriptorChain)?;
+ response_sender
+ .send(PcmResponse {
+ desc_index,
+ status,
+ writer,
+ done: None,
+ })
+ .await
+ .map_err(Error::MpscSend)
+}
+
+fn send_pcm_response_with_writer<I: SignalableInterrupt>(
+ mut writer: Writer,
+ desc_index: u16,
+ mem: &GuestMemory,
+ queue: &mut Queue,
+ interrupt: &I,
+ status: virtio_snd_pcm_status,
+) -> Result<(), Error> {
+ // For rx queue only. Fast forward the unused audio data buffer.
+ if writer.available_bytes() > std::mem::size_of::<virtio_snd_pcm_status>() {
+ writer
+ .consume_bytes(writer.available_bytes() - std::mem::size_of::<virtio_snd_pcm_status>());
+ }
+ writer.write_obj(status).map_err(Error::WriteResponse)?;
+ queue.add_used(mem, desc_index, writer.bytes_written() as u32);
+ queue.trigger_interrupt(mem, interrupt);
+ Ok(())
+}
+
+pub async fn send_pcm_response_worker<I: SignalableInterrupt>(
+ mem: &GuestMemory,
+ queue: &Rc<AsyncMutex<Queue>>,
+ interrupt: &I,
+ recv: &mut mpsc::UnboundedReceiver<PcmResponse>,
+) -> Result<(), Error> {
+ loop {
+ if let Some(r) = recv.next().await {
+ send_pcm_response_with_writer(
+ r.writer,
+ r.desc_index,
+ &mem,
+ &mut *queue.lock().await,
+ interrupt,
+ r.status,
+ )?;
+
+ // Resume pcm_worker
+ if let Some(done) = r.done {
+ done.send(()).map_err(Error::OneshotSend)?;
+ }
+ } else {
+ debug!("PcmResponse channel is closed.");
+ break;
+ }
+ }
+ Ok(())
+}
+
+/// Handle messages from the tx or the rx queue. One invocation is needed for
+/// each queue.
+pub async fn handle_pcm_queue<'a>(
+ mem: &GuestMemory,
+ streams: &Rc<AsyncMutex<Vec<AsyncMutex<StreamInfo<'a>>>>>,
+ mut response_sender: mpsc::UnboundedSender<PcmResponse>,
+ queue: &Rc<AsyncMutex<Queue>>,
+ queue_event: EventAsync,
+) -> Result<(), Error> {
+ loop {
+ // Manual queue.next_async() to avoid holding the mutex
+ let next_async = async {
+ loop {
+ // Check if there are more descriptors available.
+ if let Some(chain) = queue.lock().await.pop(mem) {
+ return Ok(chain);
+ }
+ queue_event.next_val().await?;
+ }
+ };
+
+ let desc_chain = next_async.await.map_err(Error::Async)?;
+ let mut reader =
+ Reader::new(mem.clone(), desc_chain.clone()).map_err(Error::DescriptorChain)?;
+
+ let pcm_xfer: virtio_snd_pcm_xfer = reader.read_obj().map_err(Error::ReadMessage)?;
+ let stream_id: usize = u32::from(pcm_xfer.stream_id) as usize;
+
+ let streams = streams.read_lock().await;
+ let stream_info = match streams.get(stream_id) {
+ Some(stream_info) => stream_info.read_lock().await,
+ None => {
+ error!(
+ "stream_id ({}) >= num_streams ({})",
+ stream_id,
+ streams.len()
+ );
+ defer_pcm_response_to_worker(
+ desc_chain,
+ mem,
+ virtio_snd_pcm_status {
+ status: Le32::from(VIRTIO_SND_S_IO_ERR),
+ latency_bytes: Le32::from(0),
+ },
+ &mut response_sender,
+ )
+ .await?;
+ continue;
+ }
+ };
+
+ match stream_info.sender.as_ref() {
+ Some(mut s) => {
+ s.send(desc_chain).await.map_err(Error::MpscSend)?;
+ }
+ None => {
+ error!(
+ "stream {} is not ready. state: {}",
+ stream_id,
+ get_virtio_snd_r_pcm_cmd_name(stream_info.state)
+ );
+ defer_pcm_response_to_worker(
+ desc_chain,
+ mem,
+ virtio_snd_pcm_status {
+ status: Le32::from(VIRTIO_SND_S_IO_ERR),
+ latency_bytes: Le32::from(0),
+ },
+ &mut response_sender,
+ )
+ .await?;
+ }
+ };
+ }
+}
+
+/// Handle all the control messages from the ctrl queue.
+pub async fn handle_ctrl_queue<I: SignalableInterrupt>(
+ ex: &Executor,
+ mem: &GuestMemory,
+ streams: &Rc<AsyncMutex<Vec<AsyncMutex<StreamInfo<'_>>>>>,
+ snd_data: &SndData,
+ mut queue: Queue,
+ mut queue_event: EventAsync,
+ interrupt: &I,
+ tx_send: mpsc::UnboundedSender<PcmResponse>,
+ rx_send: mpsc::UnboundedSender<PcmResponse>,
+ params: &Parameters,
+) -> Result<(), Error> {
+ loop {
+ let desc_chain = queue
+ .next_async(mem, &mut queue_event)
+ .await
+ .map_err(Error::Async)?;
+
+ let index = desc_chain.index;
+
+ let mut reader =
+ Reader::new(mem.clone(), desc_chain.clone()).map_err(Error::DescriptorChain)?;
+ let mut writer = Writer::new(mem.clone(), desc_chain).map_err(Error::DescriptorChain)?;
+ // Don't advance the reader
+ let code = reader
+ .clone()
+ .read_obj::<virtio_snd_hdr>()
+ .map_err(Error::ReadMessage)?
+ .code
+ .into();
+
+ let handle_ctrl_msg = async {
+ return match code {
+ VIRTIO_SND_R_JACK_INFO => {
+ let query_info: virtio_snd_query_info =
+ reader.read_obj().map_err(Error::ReadMessage)?;
+ let start_id: usize = u32::from(query_info.start_id) as usize;
+ let count: usize = u32::from(query_info.count) as usize;
+ if start_id + count > snd_data.jack_info.len() {
+ error!(
+ "start_id({}) + count({}) must be smaller than the number of jacks ({})",
+ start_id,
+ count,
+ snd_data.jack_info.len()
+ );
+ return writer
+ .write_obj(VIRTIO_SND_S_BAD_MSG)
+ .map_err(Error::WriteResponse);
+ }
+ // The response consists of the virtio_snd_hdr structure (contains the request
+ // status code), followed by the device-writable information structures of the
+ // item. Each information structure begins with the following common header
+ writer
+ .write_obj(VIRTIO_SND_S_OK)
+ .map_err(Error::WriteResponse)?;
+ for i in start_id..(start_id + count) {
+ writer
+ .write_all(snd_data.jack_info[i].as_slice())
+ .map_err(Error::WriteResponse)?;
+ }
+ Ok(())
+ }
+ VIRTIO_SND_R_PCM_INFO => {
+ let query_info: virtio_snd_query_info =
+ reader.read_obj().map_err(Error::ReadMessage)?;
+ let start_id: usize = u32::from(query_info.start_id) as usize;
+ let count: usize = u32::from(query_info.count) as usize;
+ if start_id + count > snd_data.pcm_info.len() {
+ error!(
+ "start_id({}) + count({}) must be smaller than the number of streams ({})",
+ start_id,
+ count,
+ snd_data.pcm_info.len()
+ );
+ return writer
+ .write_obj(VIRTIO_SND_S_BAD_MSG)
+ .map_err(Error::WriteResponse);
+ }
+ // The response consists of the virtio_snd_hdr structure (contains the request
+ // status code), followed by the device-writable information structures of the
+ // item. Each information structure begins with the following common header
+ writer
+ .write_obj(VIRTIO_SND_S_OK)
+ .map_err(Error::WriteResponse)?;
+ for i in start_id..(start_id + count) {
+ writer
+ .write_all(snd_data.pcm_info[i].as_slice())
+ .map_err(Error::WriteResponse)?;
+ }
+ Ok(())
+ }
+ VIRTIO_SND_R_CHMAP_INFO => {
+ let query_info: virtio_snd_query_info =
+ reader.read_obj().map_err(Error::ReadMessage)?;
+ let start_id: usize = u32::from(query_info.start_id) as usize;
+ let count: usize = u32::from(query_info.count) as usize;
+ if start_id + count > snd_data.chmap_info.len() {
+ error!(
+ "start_id({}) + count({}) must be smaller than the number of chmaps ({})",
+ start_id,
+ count,
+ snd_data.pcm_info.len()
+ );
+ return writer
+ .write_obj(VIRTIO_SND_S_BAD_MSG)
+ .map_err(Error::WriteResponse);
+ }
+ // The response consists of the virtio_snd_hdr structure (contains the request
+ // status code), followed by the device-writable information structures of the
+ // item. Each information structure begins with the following common header
+ writer
+ .write_obj(VIRTIO_SND_S_OK)
+ .map_err(Error::WriteResponse)?;
+ for i in start_id..(start_id + count) {
+ writer
+ .write_all(snd_data.chmap_info[i].as_slice())
+ .map_err(Error::WriteResponse)?;
+ }
+ Ok(())
+ }
+ VIRTIO_SND_R_JACK_REMAP => {
+ unreachable!("remap is unsupported");
+ }
+ VIRTIO_SND_R_PCM_SET_PARAMS => {
+ // Raise VIRTIO_SND_S_BAD_MSG or IO error?
+ let set_params: virtio_snd_pcm_set_params =
+ reader.read_obj().map_err(Error::ReadMessage)?;
+ let stream_id: usize = u32::from(set_params.hdr.stream_id) as usize;
+ let buffer_bytes: u32 = set_params.buffer_bytes.into();
+ let period_bytes: u32 = set_params.period_bytes.into();
+
+ let dir = match snd_data.pcm_info.get(stream_id) {
+ Some(pcm_info) => {
+ if set_params.channels < pcm_info.channels_min
+ || set_params.channels > pcm_info.channels_max
+ {
+ error!(
+ "Number of channels ({}) must be between {} and {}",
+ set_params.channels,
+ pcm_info.channels_min,
+ pcm_info.channels_max
+ );
+ return writer
+ .write_obj(VIRTIO_SND_S_NOT_SUPP)
+ .map_err(Error::WriteResponse);
+ }
+ if (u64::from(pcm_info.formats) & (1 << set_params.format)) == 0 {
+ error!("PCM format {} is not supported.", set_params.format);
+ return writer
+ .write_obj(VIRTIO_SND_S_NOT_SUPP)
+ .map_err(Error::WriteResponse);
+ }
+ if (u64::from(pcm_info.rates) & (1 << set_params.rate)) == 0 {
+ error!("PCM frame rate {} is not supported.", set_params.rate);
+ return writer
+ .write_obj(VIRTIO_SND_S_NOT_SUPP)
+ .map_err(Error::WriteResponse);
+ }
+
+ pcm_info.direction
+ }
+ None => {
+ error!(
+ "stream_id {} < streams {}",
+ stream_id,
+ snd_data.pcm_info.len()
+ );
+ return writer
+ .write_obj(VIRTIO_SND_S_BAD_MSG)
+ .map_err(Error::WriteResponse);
+ }
+ };
+
+ if set_params.features != 0 {
+ error!("No feature is supported");
+ return writer
+ .write_obj(VIRTIO_SND_S_NOT_SUPP)
+ .map_err(Error::WriteResponse);
+ }
+
+ if buffer_bytes % period_bytes != 0 {
+ error!(
+ "buffer_bytes({}) must be dividable by period_bytes({})",
+ buffer_bytes, period_bytes
+ );
+ return writer
+ .write_obj(VIRTIO_SND_S_BAD_MSG)
+ .map_err(Error::WriteResponse);
+ }
+
+ let streams = streams.read_lock().await;
+ let mut stream_info = match streams.get(stream_id) {
+ Some(stream_info) => stream_info.lock().await,
+ None => {
+ error!("stream_id {} < streams {}", stream_id, streams.len());
+ return writer
+ .write_obj(VIRTIO_SND_S_BAD_MSG)
+ .map_err(Error::WriteResponse);
+ }
+ };
+
+ if stream_info.state != 0
+ && stream_info.state != VIRTIO_SND_R_PCM_SET_PARAMS
+ && stream_info.state != VIRTIO_SND_R_PCM_PREPARE
+ && stream_info.state != VIRTIO_SND_R_PCM_RELEASE
+ {
+ error!(
+ "Invalid PCM state transition from {} to {}",
+ get_virtio_snd_r_pcm_cmd_name(stream_info.state),
+ get_virtio_snd_r_pcm_cmd_name(VIRTIO_SND_R_PCM_SET_PARAMS)
+ );
+ return writer
+ .write_obj(VIRTIO_SND_S_NOT_SUPP)
+ .map_err(Error::WriteResponse);
+ }
+
+ // Only required for PREPARE -> SET_PARAMS
+ stream_info.release_worker().await?;
+
+ stream_info.channels = set_params.channels;
+ stream_info.format = from_virtio_sample_format(set_params.format).unwrap();
+ stream_info.frame_rate = from_virtio_frame_rate(set_params.rate).unwrap();
+ stream_info.buffer_bytes = buffer_bytes as usize;
+ stream_info.period_bytes = period_bytes as usize;
+ stream_info.direction = dir;
+ stream_info.state = VIRTIO_SND_R_PCM_SET_PARAMS;
+
+ debug!(
+ "VIRTIO_SND_R_PCM_SET_PARAMS for stream id={}. Stream info: {:#?}",
+ stream_id, *stream_info
+ );
+
+ writer
+ .write_obj(VIRTIO_SND_S_OK)
+ .map_err(Error::WriteResponse)
+ }
+ VIRTIO_SND_R_PCM_PREPARE
+ | VIRTIO_SND_R_PCM_START
+ | VIRTIO_SND_R_PCM_STOP
+ | VIRTIO_SND_R_PCM_RELEASE => {
+ let hdr: virtio_snd_pcm_hdr = reader.read_obj().map_err(Error::ReadMessage)?;
+ let stream_id: usize = u32::from(hdr.stream_id) as usize;
+ process_pcm_ctrl(
+ ex,
+ &mem.clone(),
+ &tx_send,
+ &rx_send,
+ streams,
+ params,
+ code,
+ &mut writer,
+ stream_id,
+ )
+ .await
+ .and(Ok(()))?;
+ Ok(())
+ }
+ c => {
+ error!("Unrecognized code: {}", c);
+ return writer
+ .write_obj(VIRTIO_SND_S_BAD_MSG)
+ .map_err(Error::WriteResponse);
+ }
+ };
+ };
+
+ handle_ctrl_msg.await?;
+ queue.add_used(mem, index, writer.bytes_written() as u32);
+ queue.trigger_interrupt(&mem, interrupt);
+ }
+}
+
+/// Send events to the audio driver.
+pub async fn handle_event_queue<I: SignalableInterrupt>(
+ mem: &GuestMemory,
+ mut queue: Queue,
+ mut queue_event: EventAsync,
+ interrupt: &I,
+) -> Result<(), Error> {
+ loop {
+ let desc_chain = queue
+ .next_async(mem, &mut queue_event)
+ .await
+ .map_err(Error::Async)?;
+
+ // TODO(woodychow): Poll and forward events from cras asynchronously (API to be added)
+ let index = desc_chain.index;
+ queue.add_used(mem, index, 0);
+ queue.trigger_interrupt(&mem, interrupt);
+ }
+}
diff --git a/devices/src/virtio/snd/cras_backend/mod.rs b/devices/src/virtio/snd/cras_backend/mod.rs
new file mode 100644
index 000000000..3efdb4f31
--- /dev/null
+++ b/devices/src/virtio/snd/cras_backend/mod.rs
@@ -0,0 +1,843 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// virtio-sound spec: https://github.com/oasis-tcs/virtio-spec/blob/master/virtio-sound.tex
+
+use std::cell::RefCell;
+use std::fmt;
+use std::io;
+use std::num::ParseIntError;
+use std::rc::Rc;
+use std::str::{FromStr, ParseBoolError};
+use std::thread;
+
+use anyhow::Context;
+use audio_streams::{SampleFormat, StreamSource};
+use base::{
+ error, set_rt_prio_limit, set_rt_round_robin, warn, Error as SysError, Event, RawDescriptor,
+};
+use cros_async::sync::Mutex as AsyncMutex;
+use cros_async::{AsyncError, EventAsync, Executor};
+use data_model::DataInit;
+use futures::channel::{
+ mpsc,
+ oneshot::{self, Canceled},
+};
+use futures::{pin_mut, select, Future, FutureExt, TryFutureExt};
+use libcras::{BoxError, CrasClient, CrasClientType, CrasSocketType};
+use thiserror::Error as ThisError;
+use vm_memory::GuestMemory;
+
+use crate::virtio::snd::common::*;
+use crate::virtio::snd::constants::*;
+use crate::virtio::snd::layout::*;
+use crate::virtio::{
+ async_utils, copy_config, DescriptorChain, DescriptorError, Interrupt, Queue, VirtioDevice,
+ Writer, TYPE_SOUND,
+};
+
+pub mod async_funcs;
+use crate::virtio::snd::cras_backend::async_funcs::*;
+
+// control + event + tx + rx queue
+pub const MAX_QUEUE_NUM: usize = 4;
+pub const MAX_VRING_LEN: u16 = 1024;
+const AUDIO_THREAD_RTPRIO: u16 = 10; // Matches other cros audio clients.
+
+#[derive(ThisError, Debug)]
+pub enum Error {
+ /// next_async failed.
+ #[error("Failed to read descriptor asynchronously: {0}")]
+ Async(AsyncError),
+ /// Creating stream failed.
+ #[error("Failed to create stream: {0}")]
+ CreateStream(BoxError),
+ /// Creating kill event failed.
+ #[error("Failed to create kill event: {0}")]
+ CreateKillEvent(SysError),
+ /// Creating WaitContext failed.
+ #[error("Failed to create wait context: {0}")]
+ CreateWaitContext(SysError),
+ /// Cloning kill event failed.
+ #[error("Failed to clone kill event: {0}")]
+ CloneKillEvent(SysError),
+ /// Descriptor chain was invalid.
+ #[error("Failed to valildate descriptor chain: {0}")]
+ DescriptorChain(DescriptorError),
+ // Future error.
+ #[error("Unexpected error. Done was not triggered before dropped: {0}")]
+ DoneNotTriggered(Canceled),
+ /// Error reading message from queue.
+ #[error("Failed to read message: {0}")]
+ ReadMessage(io::Error),
+ /// Failed writing a response to a control message.
+ #[error("Failed to write message response: {0}")]
+ WriteResponse(io::Error),
+ /// Libcras error.
+ #[error("Error in libcras: {0}")]
+ Libcras(libcras::Error),
+ // Mpsc read error.
+ #[error("Error in mpsc: {0}")]
+ MpscSend(futures::channel::mpsc::SendError),
+ // Oneshot send error.
+ #[error("Error in oneshot send")]
+ OneshotSend(()),
+ /// Stream not found.
+ #[error("stream id ({0}) < num_streams ({1})")]
+ StreamNotFound(usize, usize),
+ /// Fetch buffer error
+ #[error("Failed to get buffer from CRAS: {0}")]
+ FetchBuffer(BoxError),
+ /// Invalid buffer size
+ #[error("Invalid buffer size")]
+ InvalidBufferSize,
+ /// IoError
+ #[error("I/O failed: {0}")]
+ Io(io::Error),
+ /// Operation not supported.
+ #[error("Operation not supported")]
+ OperationNotSupported,
+ /// Writing to a buffer in the guest failed.
+ #[error("failed to write to buffer: {0}")]
+ WriteBuffer(io::Error),
+ /// Failed to parse parameters.
+ #[error("Invalid cras snd parameter: {0}")]
+ UnknownParameter(String),
+ /// Unknown cras snd parameter value.
+ #[error("Invalid cras snd parameter value ({0}): {1}")]
+ InvalidParameterValue(String, String),
+ /// Failed to parse bool value.
+ #[error("Invalid bool value: {0}")]
+ InvalidBoolValue(ParseBoolError),
+ /// Failed to parse int value.
+ #[error("Invalid int value: {0}")]
+ InvalidIntValue(ParseIntError),
+ // Invalid PCM worker state.
+ #[error("Invalid PCM worker state")]
+ InvalidPCMWorkerState,
+}
+
+/// Holds the parameters for a cras sound device
+#[derive(Debug, Clone)]
+pub struct Parameters {
+ pub capture: bool,
+ pub client_type: CrasClientType,
+ pub socket_type: CrasSocketType,
+ pub num_output_streams: u32,
+ pub num_input_streams: u32,
+}
+
+impl Default for Parameters {
+ fn default() -> Self {
+ Parameters {
+ capture: false,
+ client_type: CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
+ socket_type: CrasSocketType::Unified,
+ num_output_streams: 1,
+ num_input_streams: 1,
+ }
+ }
+}
+
+impl FromStr for Parameters {
+ type Err = Error;
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ let mut params: Parameters = Default::default();
+ let opts = s
+ .split(',')
+ .map(|frag| frag.split('='))
+ .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
+
+ for (k, v) in opts {
+ match k {
+ "capture" => {
+ params.capture = v.parse::<bool>().map_err(Error::InvalidBoolValue)?;
+ }
+ "client_type" => {
+ params.client_type = v.parse().map_err(|e: libcras::CrasSysError| {
+ Error::InvalidParameterValue(v.to_string(), e.to_string())
+ })?;
+ }
+ "socket_type" => {
+ params.socket_type = v.parse().map_err(|e: libcras::Error| {
+ Error::InvalidParameterValue(v.to_string(), e.to_string())
+ })?;
+ }
+ "num_output_streams" => {
+ params.num_output_streams = v.parse::<u32>().map_err(Error::InvalidIntValue)?;
+ }
+ "num_input_streams" => {
+ params.num_input_streams = v.parse::<u32>().map_err(Error::InvalidIntValue)?;
+ }
+ _ => {
+ return Err(Error::UnknownParameter(k.to_string()));
+ }
+ }
+ }
+
+ Ok(params)
+ }
+}
+
+pub enum DirectionalStream {
+ Input(Box<dyn audio_streams::capture::AsyncCaptureBufferStream>),
+ Output(Box<dyn audio_streams::AsyncPlaybackBufferStream>),
+}
+
+#[derive(Copy, Clone, std::cmp::PartialEq)]
+pub enum WorkerStatus {
+ Pause = 0,
+ Running = 1,
+ Quit = 2,
+}
+
+pub struct StreamInfo<'a> {
+ client: Option<CrasClient<'a>>,
+ channels: u8,
+ format: SampleFormat,
+ frame_rate: u32,
+ buffer_bytes: usize,
+ period_bytes: usize,
+ direction: u8, // VIRTIO_SND_D_*
+ state: u32, // VIRTIO_SND_R_PCM_SET_PARAMS -> VIRTIO_SND_R_PCM_STOP, or 0 (uninitialized)
+
+ // Worker related
+ status_mutex: Rc<AsyncMutex<WorkerStatus>>,
+ sender: Option<mpsc::UnboundedSender<DescriptorChain>>,
+ worker_future: Option<Box<dyn Future<Output = Result<(), Error>> + Unpin>>,
+}
+
+impl fmt::Debug for StreamInfo<'_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("StreamInfo")
+ .field("channels", &self.channels)
+ .field("format", &self.format)
+ .field("frame_rate", &self.frame_rate)
+ .field("buffer_bytes", &self.buffer_bytes)
+ .field("period_bytes", &self.period_bytes)
+ .field("direction", &get_virtio_direction_name(self.direction))
+ .field("state", &get_virtio_snd_r_pcm_cmd_name(self.state))
+ .finish()
+ }
+}
+
+impl Default for StreamInfo<'_> {
+ fn default() -> Self {
+ StreamInfo {
+ client: None,
+ channels: 0,
+ format: SampleFormat::U8,
+ frame_rate: 0,
+ buffer_bytes: 0,
+ period_bytes: 0,
+ direction: 0,
+ state: 0,
+ status_mutex: Rc::new(AsyncMutex::new(WorkerStatus::Pause)),
+ sender: None,
+ worker_future: None,
+ }
+ }
+}
+
+// Stores constant data
+pub struct SndData {
+ jack_info: Vec<virtio_snd_jack_info>,
+ pcm_info: Vec<virtio_snd_pcm_info>,
+ chmap_info: Vec<virtio_snd_chmap_info>,
+}
+
+impl SndData {
+ pub fn pcm_info_len(&self) -> usize {
+ self.pcm_info.len()
+ }
+}
+
+const SUPPORTED_FORMATS: u64 = 1 << VIRTIO_SND_PCM_FMT_U8
+ | 1 << VIRTIO_SND_PCM_FMT_S16
+ | 1 << VIRTIO_SND_PCM_FMT_S24
+ | 1 << VIRTIO_SND_PCM_FMT_S32;
+const SUPPORTED_FRAME_RATES: u64 = 1 << VIRTIO_SND_PCM_RATE_8000
+ | 1 << VIRTIO_SND_PCM_RATE_11025
+ | 1 << VIRTIO_SND_PCM_RATE_16000
+ | 1 << VIRTIO_SND_PCM_RATE_22050
+ | 1 << VIRTIO_SND_PCM_RATE_32000
+ | 1 << VIRTIO_SND_PCM_RATE_44100
+ | 1 << VIRTIO_SND_PCM_RATE_48000;
+
+// Response from pcm_worker to pcm_queue
+pub struct PcmResponse {
+ desc_index: u16,
+ status: virtio_snd_pcm_status, // response to the pcm message
+ writer: Writer,
+ done: Option<oneshot::Sender<()>>, // when pcm response is written to the queue
+}
+
+impl<'a> StreamInfo<'a> {
+ async fn prepare(
+ &mut self,
+ ex: &Executor,
+ mem: GuestMemory,
+ tx_send: &mpsc::UnboundedSender<PcmResponse>,
+ rx_send: &mpsc::UnboundedSender<PcmResponse>,
+ params: &Parameters,
+ ) -> Result<(), Error> {
+ if self.state != VIRTIO_SND_R_PCM_SET_PARAMS
+ && self.state != VIRTIO_SND_R_PCM_PREPARE
+ && self.state != VIRTIO_SND_R_PCM_RELEASE
+ {
+ error!(
+ "Invalid PCM state transition from {} to {}",
+ get_virtio_snd_r_pcm_cmd_name(self.state),
+ get_virtio_snd_r_pcm_cmd_name(VIRTIO_SND_R_PCM_PREPARE)
+ );
+ return Err(Error::OperationNotSupported);
+ }
+ if self.state == VIRTIO_SND_R_PCM_PREPARE {
+ self.release_worker().await?;
+ }
+ let frame_size = self.channels as usize * self.format.sample_bytes();
+ if self.period_bytes % frame_size != 0 {
+ error!("period_bytes must be divisible by frame size");
+ return Err(Error::OperationNotSupported);
+ }
+ if self.client.is_none() {
+ let mut client = CrasClient::with_type(params.socket_type).map_err(Error::Libcras)?;
+ if params.capture {
+ client.enable_cras_capture();
+ }
+ client.set_client_type(params.client_type);
+ self.client = Some(client);
+ }
+ // (*)
+ // `buffer_size` in `audio_streams` API indicates the buffer size in bytes that the stream
+ // consumes (or transmits) each time (next_playback/capture_buffer).
+ // `period_bytes` in virtio-snd device (or ALSA) indicates the device transmits (or
+ // consumes) for each PCM message.
+ // Therefore, `buffer_size` in `audio_streams` == `period_bytes` in virtio-snd.
+ let (stream, pcm_sender) = match self.direction {
+ VIRTIO_SND_D_OUTPUT => (
+ DirectionalStream::Output(
+ self.client
+ .as_mut()
+ .unwrap()
+ .new_async_playback_stream(
+ self.channels as usize,
+ self.format,
+ self.frame_rate,
+ // See (*)
+ self.period_bytes / frame_size,
+ ex,
+ )
+ .map_err(Error::CreateStream)?
+ .1,
+ ),
+ tx_send.clone(),
+ ),
+ VIRTIO_SND_D_INPUT => {
+ (
+ DirectionalStream::Input(
+ self.client
+ .as_mut()
+ .unwrap()
+ .new_async_capture_stream(
+ self.channels as usize,
+ self.format,
+ self.frame_rate,
+ // See (*)
+ self.period_bytes / frame_size,
+ &[],
+ ex,
+ )
+ .map_err(Error::CreateStream)?
+ .1,
+ ),
+ rx_send.clone(),
+ )
+ }
+ _ => unreachable!(),
+ };
+
+ let (sender, receiver) = mpsc::unbounded();
+ self.sender = Some(sender);
+ self.state = VIRTIO_SND_R_PCM_PREPARE;
+
+ self.status_mutex = Rc::new(AsyncMutex::new(WorkerStatus::Pause));
+ let f = start_pcm_worker(
+ ex.clone(),
+ stream,
+ receiver,
+ self.status_mutex.clone(),
+ mem,
+ pcm_sender,
+ self.period_bytes,
+ );
+ self.worker_future = Some(Box::new(ex.spawn_local(f).into_future()));
+ Ok(())
+ }
+
+ async fn start(&mut self) -> Result<(), Error> {
+ if self.state != VIRTIO_SND_R_PCM_PREPARE && self.state != VIRTIO_SND_R_PCM_STOP {
+ error!(
+ "Invalid PCM state transition from {} to {}",
+ get_virtio_snd_r_pcm_cmd_name(self.state),
+ get_virtio_snd_r_pcm_cmd_name(VIRTIO_SND_R_PCM_START)
+ );
+ return Err(Error::OperationNotSupported);
+ }
+ self.state = VIRTIO_SND_R_PCM_START;
+ *self.status_mutex.lock().await = WorkerStatus::Running;
+ Ok(())
+ }
+
+ async fn stop(&mut self) -> Result<(), Error> {
+ if self.state != VIRTIO_SND_R_PCM_START {
+ error!(
+ "Invalid PCM state transition from {} to {}",
+ get_virtio_snd_r_pcm_cmd_name(self.state),
+ get_virtio_snd_r_pcm_cmd_name(VIRTIO_SND_R_PCM_STOP)
+ );
+ return Err(Error::OperationNotSupported);
+ }
+ self.state = VIRTIO_SND_R_PCM_STOP;
+ *self.status_mutex.lock().await = WorkerStatus::Pause;
+ Ok(())
+ }
+
+ async fn release(&mut self) -> Result<(), Error> {
+ if self.state != VIRTIO_SND_R_PCM_PREPARE && self.state != VIRTIO_SND_R_PCM_STOP {
+ error!(
+ "Invalid PCM state transition from {} to {}",
+ get_virtio_snd_r_pcm_cmd_name(self.state),
+ get_virtio_snd_r_pcm_cmd_name(VIRTIO_SND_R_PCM_RELEASE)
+ );
+ return Err(Error::OperationNotSupported);
+ }
+ self.state = VIRTIO_SND_R_PCM_RELEASE;
+ self.release_worker().await?;
+ self.client = None;
+ Ok(())
+ }
+
+ async fn release_worker(&mut self) -> Result<(), Error> {
+ *self.status_mutex.lock().await = WorkerStatus::Quit;
+ match self.sender.take() {
+ Some(s) => s.close_channel(),
+ None => (),
+ }
+ match self.worker_future.take() {
+ Some(f) => f.await?,
+ None => (),
+ }
+ Ok(())
+ }
+}
+
+pub struct VirtioSndCras {
+ cfg: virtio_snd_config,
+ avail_features: u64,
+ acked_features: u64,
+ queue_sizes: Box<[u16]>,
+ worker_threads: Vec<thread::JoinHandle<()>>,
+ kill_evt: Option<Event>,
+ params: Parameters,
+}
+
+impl VirtioSndCras {
+ pub fn new(base_features: u64, params: Parameters) -> Result<VirtioSndCras, Error> {
+ let cfg = hardcoded_virtio_snd_config(&params);
+
+ let avail_features = base_features;
+
+ Ok(VirtioSndCras {
+ cfg,
+ avail_features,
+ acked_features: 0,
+ queue_sizes: vec![MAX_VRING_LEN; MAX_QUEUE_NUM].into_boxed_slice(),
+ worker_threads: Vec::new(),
+ kill_evt: None,
+ params,
+ })
+ }
+}
+
+// To be used with hardcoded_snd_data
+pub fn hardcoded_virtio_snd_config(params: &Parameters) -> virtio_snd_config {
+ virtio_snd_config {
+ jacks: 0.into(),
+ streams: (params.num_output_streams + params.num_input_streams).into(),
+ chmaps: 4.into(),
+ }
+}
+
+// To be used with hardcoded_virtio_snd_config
+pub fn hardcoded_snd_data(params: &Parameters) -> SndData {
+ let jack_info: Vec<virtio_snd_jack_info> = Vec::new();
+ let mut pcm_info: Vec<virtio_snd_pcm_info> = Vec::new();
+ let mut chmap_info: Vec<virtio_snd_chmap_info> = Vec::new();
+
+ for _ in 0..params.num_output_streams {
+ pcm_info.push(virtio_snd_pcm_info {
+ hdr: virtio_snd_info {
+ hda_fn_nid: 0.into(),
+ },
+ features: 0.into(), /* 1 << VIRTIO_SND_PCM_F_XXX */
+ formats: SUPPORTED_FORMATS.into(),
+ rates: SUPPORTED_FRAME_RATES.into(),
+ direction: VIRTIO_SND_D_OUTPUT,
+ channels_min: 1,
+ channels_max: 6,
+ padding: [0; 5],
+ });
+ }
+ for _ in 0..params.num_input_streams {
+ pcm_info.push(virtio_snd_pcm_info {
+ hdr: virtio_snd_info {
+ hda_fn_nid: 0.into(),
+ },
+ features: 0.into(), /* 1 << VIRTIO_SND_PCM_F_XXX */
+ formats: SUPPORTED_FORMATS.into(),
+ rates: SUPPORTED_FRAME_RATES.into(),
+ direction: VIRTIO_SND_D_INPUT,
+ channels_min: 1,
+ channels_max: 2,
+ padding: [0; 5],
+ });
+ }
+
+ // Use stereo channel map.
+ let mut positions = [VIRTIO_SND_CHMAP_NONE; VIRTIO_SND_CHMAP_MAX_SIZE];
+ positions[0] = VIRTIO_SND_CHMAP_FL;
+ positions[1] = VIRTIO_SND_CHMAP_FR;
+
+ chmap_info.push(virtio_snd_chmap_info {
+ hdr: virtio_snd_info {
+ hda_fn_nid: 0.into(),
+ },
+ direction: VIRTIO_SND_D_OUTPUT,
+ channels: 2,
+ positions,
+ });
+ chmap_info.push(virtio_snd_chmap_info {
+ hdr: virtio_snd_info {
+ hda_fn_nid: 0.into(),
+ },
+ direction: VIRTIO_SND_D_INPUT,
+ channels: 2,
+ positions,
+ });
+ positions[2] = VIRTIO_SND_CHMAP_RL;
+ positions[3] = VIRTIO_SND_CHMAP_RR;
+ chmap_info.push(virtio_snd_chmap_info {
+ hdr: virtio_snd_info {
+ hda_fn_nid: 0.into(),
+ },
+ direction: VIRTIO_SND_D_OUTPUT,
+ channels: 4,
+ positions,
+ });
+ positions[2] = VIRTIO_SND_CHMAP_FC;
+ positions[3] = VIRTIO_SND_CHMAP_LFE;
+ positions[4] = VIRTIO_SND_CHMAP_RL;
+ positions[5] = VIRTIO_SND_CHMAP_RR;
+ chmap_info.push(virtio_snd_chmap_info {
+ hdr: virtio_snd_info {
+ hda_fn_nid: 0.into(),
+ },
+ direction: VIRTIO_SND_D_OUTPUT,
+ channels: 6,
+ positions,
+ });
+
+ SndData {
+ jack_info,
+ pcm_info,
+ chmap_info,
+ }
+}
+
+impl VirtioDevice for VirtioSndCras {
+ fn keep_rds(&self) -> Vec<RawDescriptor> {
+ Vec::new()
+ }
+
+ fn device_type(&self) -> u32 {
+ TYPE_SOUND
+ }
+
+ fn queue_max_sizes(&self) -> &[u16] {
+ &self.queue_sizes
+ }
+
+ fn features(&self) -> u64 {
+ self.avail_features
+ }
+
+ fn ack_features(&mut self, mut v: u64) {
+ // 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!("virtio_fs got unknown feature ack: {:x}", v);
+
+ // Don't count these features as acked.
+ v &= !unrequested_features;
+ }
+ self.acked_features |= v;
+ }
+
+ fn read_config(&self, offset: u64, data: &mut [u8]) {
+ copy_config(data, 0, self.cfg.as_slice(), offset)
+ }
+
+ fn activate(
+ &mut self,
+ guest_mem: GuestMemory,
+ interrupt: Interrupt,
+ queues: Vec<Queue>,
+ queue_evts: Vec<Event>,
+ ) {
+ if queues.len() != self.queue_sizes.len() || queue_evts.len() != self.queue_sizes.len() {
+ error!(
+ "snd: expected {} queues, got {}",
+ self.queue_sizes.len(),
+ queues.len()
+ );
+ }
+
+ let (self_kill_evt, kill_evt) =
+ match Event::new().and_then(|evt| Ok((evt.try_clone()?, evt))) {
+ Ok(v) => v,
+ Err(e) => {
+ error!("failed to create kill Event pair: {}", e);
+ return;
+ }
+ };
+ self.kill_evt = Some(self_kill_evt);
+
+ let params = self.params.clone();
+
+ let worker_result = thread::Builder::new()
+ .name("virtio_snd w".to_string())
+ .spawn(move || {
+ if let Err(e) = set_rt_prio_limit(u64::from(AUDIO_THREAD_RTPRIO))
+ .and_then(|_| set_rt_round_robin(i32::from(AUDIO_THREAD_RTPRIO)))
+ {
+ warn!("Failed to set audio thread to real time: {}", e);
+ }
+
+ if let Err(err_string) = run_worker(
+ interrupt,
+ queues,
+ guest_mem,
+ hardcoded_snd_data(&params),
+ queue_evts,
+ kill_evt,
+ params,
+ ) {
+ error!("{}", err_string);
+ }
+ });
+
+ match worker_result {
+ Err(e) => {
+ error!("failed to spawn virtio_snd worker: {}", e);
+ return;
+ }
+ Ok(join_handle) => self.worker_threads.push(join_handle),
+ }
+ }
+
+ fn reset(&mut self) -> bool {
+ 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);
+ }
+
+ true
+ }
+}
+
+impl Drop for VirtioSndCras {
+ fn drop(&mut self) {
+ self.reset();
+ }
+}
+
+fn run_worker(
+ interrupt: Interrupt,
+ mut queues: Vec<Queue>,
+ mem: GuestMemory,
+ snd_data: SndData,
+ queue_evts: Vec<Event>,
+ kill_evt: Event,
+ params: Parameters,
+) -> Result<(), String> {
+ let ex = Executor::new().expect("Failed to create an executor");
+
+ let mut streams: Vec<AsyncMutex<StreamInfo>> = Vec::new();
+ streams.resize_with(snd_data.pcm_info.len(), Default::default);
+ let streams = Rc::new(AsyncMutex::new(streams));
+
+ let interrupt = Rc::new(RefCell::new(interrupt));
+ let interrupt_ref = &*interrupt.borrow();
+
+ let ctrl_queue = queues.remove(0);
+ let _event_queue = queues.remove(0);
+ let tx_queue = Rc::new(AsyncMutex::new(queues.remove(0)));
+ let rx_queue = Rc::new(AsyncMutex::new(queues.remove(0)));
+
+ let mut evts_async: Vec<EventAsync> = queue_evts
+ .into_iter()
+ .map(|e| EventAsync::new(e.0, &ex).expect("Failed to create async event for queue"))
+ .collect();
+
+ let ctrl_queue_evt = evts_async.remove(0);
+ let _event_queue_evt = evts_async.remove(0);
+ let tx_queue_evt = evts_async.remove(0);
+ let rx_queue_evt = evts_async.remove(0);
+
+ let (tx_send, mut tx_recv) = mpsc::unbounded();
+ let (rx_send, mut rx_recv) = mpsc::unbounded();
+ let tx_send2 = tx_send.clone();
+ let rx_send2 = rx_send.clone();
+
+ let f_ctrl = handle_ctrl_queue(
+ &ex,
+ &mem,
+ &streams,
+ &snd_data,
+ ctrl_queue,
+ ctrl_queue_evt,
+ interrupt_ref,
+ tx_send,
+ rx_send,
+ &params,
+ );
+
+ // TODO(woodychow): Enable this when libcras sends jack connect/disconnect evts
+ // let f_event = handle_event_queue(
+ // &mem,
+ // snd_state,
+ // event_queue,
+ // event_queue_evt,
+ // interrupt,
+ // );
+
+ let f_tx = handle_pcm_queue(&mem, &streams, tx_send2, &tx_queue, tx_queue_evt);
+
+ let f_tx_response = send_pcm_response_worker(&mem, &tx_queue, interrupt_ref, &mut tx_recv);
+
+ let f_rx = handle_pcm_queue(&mem, &streams, rx_send2, &rx_queue, rx_queue_evt);
+
+ let f_rx_response = send_pcm_response_worker(&mem, &rx_queue, interrupt_ref, &mut rx_recv);
+
+ let f_resample = async_utils::handle_irq_resample(&ex, interrupt.clone());
+
+ // Exit if the kill event is triggered.
+ let f_kill = async_utils::await_and_exit(&ex, kill_evt);
+
+ pin_mut!(
+ f_ctrl,
+ f_tx,
+ f_tx_response,
+ f_rx,
+ f_rx_response,
+ f_resample,
+ f_kill
+ );
+
+ let done = async {
+ select! {
+ res = f_ctrl.fuse() => res.context("error in handling ctrl queue"),
+ res = f_tx.fuse() => res.context("error in handling tx queue"),
+ res = f_tx_response.fuse() => res.context("error in handling tx response"),
+ res = f_rx.fuse() => res.context("error in handling rx queue"),
+ res = f_rx_response.fuse() => res.context("error in handling rx response"),
+ res = f_resample.fuse() => res.context("error in handle_irq_resample"),
+ res = f_kill.fuse() => res.context("error in await_and_exit"),
+ }
+ };
+ match ex.run_until(done) {
+ Ok(Ok(())) => {}
+ Ok(Err(e)) => error!("Error in worker: {}", e),
+ Err(e) => error!("Error happened in executor: {}", e),
+ }
+
+ Ok(())
+}
+#[cfg(test)]
+mod tests {
+ use super::*;
+ #[test]
+ fn parameters_fromstr() {
+ fn check_success(
+ s: &str,
+ capture: bool,
+ client_type: CrasClientType,
+ socket_type: CrasSocketType,
+ num_output_streams: u32,
+ num_input_streams: u32,
+ ) {
+ let params = s.parse::<Parameters>().expect("parse should have succeded");
+ assert_eq!(params.capture, capture);
+ assert_eq!(params.client_type, client_type);
+ assert_eq!(params.socket_type, socket_type);
+ assert_eq!(params.num_output_streams, num_output_streams);
+ assert_eq!(params.num_input_streams, num_input_streams);
+ }
+ fn check_failure(s: &str) {
+ s.parse::<Parameters>()
+ .expect_err("parse should have failed");
+ }
+
+ check_success(
+ "capture=false",
+ false,
+ CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
+ CrasSocketType::Unified,
+ 1,
+ 1,
+ );
+ check_success(
+ "capture=true,client_type=crosvm",
+ true,
+ CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
+ CrasSocketType::Unified,
+ 1,
+ 1,
+ );
+ check_success(
+ "capture=true,client_type=arcvm",
+ true,
+ CrasClientType::CRAS_CLIENT_TYPE_ARCVM,
+ CrasSocketType::Unified,
+ 1,
+ 1,
+ );
+ check_failure("capture=true,client_type=none");
+ check_success(
+ "socket_type=legacy",
+ false,
+ CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
+ CrasSocketType::Legacy,
+ 1,
+ 1,
+ );
+ check_success(
+ "socket_type=unified",
+ false,
+ CrasClientType::CRAS_CLIENT_TYPE_CROSVM,
+ CrasSocketType::Unified,
+ 1,
+ 1,
+ );
+ check_success(
+ "capture=true,client_type=arcvm,num_output_streams=2,num_input_streams=3",
+ true,
+ CrasClientType::CRAS_CLIENT_TYPE_ARCVM,
+ CrasSocketType::Unified,
+ 2,
+ 3,
+ );
+ }
+}
diff --git a/devices/src/virtio/snd/layout.rs b/devices/src/virtio/snd/layout.rs
index 8881af0b4..a217bff3d 100644
--- a/devices/src/virtio/snd/layout.rs
+++ b/devices/src/virtio/snd/layout.rs
@@ -41,7 +41,7 @@ pub struct virtio_snd_event {
// Safe because it only has data and has no implicit padding.
unsafe impl DataInit for virtio_snd_event {}
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct virtio_snd_query_info {
pub hdr: virtio_snd_hdr,
@@ -76,7 +76,7 @@ pub struct virtio_snd_pcm_info {
// Safe because it only has data and has no implicit padding.
unsafe impl DataInit for virtio_snd_pcm_info {}
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct virtio_snd_pcm_hdr {
pub hdr: virtio_snd_hdr,
@@ -85,7 +85,7 @@ pub struct virtio_snd_pcm_hdr {
// Safe because it only has data and has no implicit padding.
unsafe impl DataInit for virtio_snd_pcm_hdr {}
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct virtio_snd_pcm_set_params {
pub hdr: virtio_snd_pcm_hdr,
@@ -117,7 +117,7 @@ pub struct virtio_snd_pcm_status {
// Safe because it only has data and has no implicit padding.
unsafe impl DataInit for virtio_snd_pcm_status {}
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct virtio_snd_jack_info {
pub hdr: virtio_snd_info,
@@ -130,7 +130,7 @@ pub struct virtio_snd_jack_info {
// Safe because it only has data and has no implicit padding.
unsafe impl DataInit for virtio_snd_jack_info {}
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct virtio_snd_jack_remap {
pub hdr: virtio_snd_jack_hdr, /* .code = VIRTIO_SND_R_JACK_REMAP */
@@ -140,7 +140,7 @@ pub struct virtio_snd_jack_remap {
// Safe because it only has data and has no implicit padding.
unsafe impl DataInit for virtio_snd_jack_remap {}
-#[derive(Copy, Clone)]
+#[derive(Copy, Clone, Default)]
#[repr(C)]
pub struct virtio_snd_chmap_info {
pub hdr: virtio_snd_info,
diff --git a/devices/src/virtio/snd/mod.rs b/devices/src/virtio/snd/mod.rs
index 5a772aed1..c558576f9 100644
--- a/devices/src/virtio/snd/mod.rs
+++ b/devices/src/virtio/snd/mod.rs
@@ -2,8 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+pub mod common;
pub mod constants;
-
pub mod layout;
+#[cfg(feature = "audio_cras")]
+pub mod cras_backend;
+
pub mod vios_backend;
+
+pub use vios_backend::new_sound;
+pub use vios_backend::SoundError;
diff --git a/devices/src/virtio/snd/vios_backend/mod.rs b/devices/src/virtio/snd/vios_backend/mod.rs
index 602ec4dcd..d55c4c5d3 100644
--- a/devices/src/virtio/snd/vios_backend/mod.rs
+++ b/devices/src/virtio/snd/vios_backend/mod.rs
@@ -9,3 +9,219 @@ mod shm_vios;
pub use self::shm_streams::*;
pub use self::shm_vios::*;
+
+pub mod streams;
+
+mod worker;
+
+use std::thread;
+
+use crate::virtio::{copy_config, DescriptorError, Interrupt, Queue, VirtioDevice, TYPE_SOUND};
+use base::{error, Error as BaseError, Event, RawDescriptor};
+use data_model::{DataInit, Le32};
+use remain::sorted;
+use sync::Mutex;
+use vm_memory::GuestMemory;
+
+use std::path::Path;
+use std::sync::mpsc::{RecvError, SendError};
+use std::sync::Arc;
+
+use super::layout::*;
+use streams::StreamMsg;
+use worker::*;
+
+use std::io::Error as IoError;
+use thiserror::Error as ThisError;
+
+const QUEUE_SIZES: &[u16] = &[64, 64, 64, 64];
+
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum SoundError {
+ #[error("The driver sent an invalid message")]
+ BadDriverMsg,
+ #[error("Failed to get event notifier from VioS client: {0}")]
+ ClientEventNotifier(Error),
+ #[error("Failed to create VioS client: {0}")]
+ ClientNew(Error),
+ #[error("Failed to create event pair: {0}")]
+ CreateEvent(BaseError),
+ #[error("Failed to create Reader from descriptor chain: {0}")]
+ CreateReader(DescriptorError),
+ #[error("Failed to create thread: {0}")]
+ CreateThread(IoError),
+ #[error("Failed to create Writer from descriptor chain: {0}")]
+ CreateWriter(DescriptorError),
+ #[error("Error with queue descriptor: {0}")]
+ Descriptor(DescriptorError),
+ #[error("Attempted a {0} operation while on the wrong state: {1}, this is a bug")]
+ ImpossibleState(&'static str, &'static str),
+ #[error("Error consuming queue event: {0}")]
+ QueueEvt(BaseError),
+ #[error("Failed to read/write from/to queue: {0}")]
+ QueueIO(IoError),
+ #[error("Failed to receive message: {0}")]
+ StreamThreadRecv(RecvError),
+ #[error("Failed to send message: {0}")]
+ StreamThreadSend(SendError<StreamMsg>),
+ #[error("Error creating WaitContext: {0}")]
+ WaitCtx(BaseError),
+}
+
+pub type Result<T> = std::result::Result<T, SoundError>;
+
+pub struct Sound {
+ config: virtio_snd_config,
+ virtio_features: u64,
+ worker_thread: Option<thread::JoinHandle<bool>>,
+ kill_evt: Option<Event>,
+ vios_client: Arc<VioSClient>,
+}
+
+impl VirtioDevice for Sound {
+ fn keep_rds(&self) -> Vec<RawDescriptor> {
+ self.vios_client.keep_fds()
+ }
+
+ fn device_type(&self) -> u32 {
+ TYPE_SOUND
+ }
+
+ fn queue_max_sizes(&self) -> &[u16] {
+ QUEUE_SIZES
+ }
+
+ fn read_config(&self, offset: u64, data: &mut [u8]) {
+ copy_config(data, 0, self.config.as_slice(), offset);
+ }
+
+ fn write_config(&mut self, _offset: u64, _data: &[u8]) {
+ error!("virtio-snd: driver attempted a config write which is not allowed by the spec");
+ }
+
+ fn features(&self) -> u64 {
+ self.virtio_features
+ }
+
+ fn activate(
+ &mut self,
+ mem: GuestMemory,
+ interrupt: Interrupt,
+ mut queues: Vec<Queue>,
+ mut queue_evts: Vec<Event>,
+ ) {
+ if self.worker_thread.is_some() {
+ error!("virtio-snd: Device is already active");
+ return;
+ }
+ if queues.len() != 4 || queue_evts.len() != 4 {
+ error!(
+ "virtio-snd: device activated with wrong number of queues: {}, {}",
+ queues.len(),
+ queue_evts.len()
+ );
+ return;
+ }
+ let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
+ Ok(v) => v,
+ Err(e) => {
+ error!("virtio-snd: failed to create kill Event pair: {}", e);
+ return;
+ }
+ };
+ self.kill_evt = Some(self_kill_evt);
+ let control_queue = queues.remove(0);
+ let control_queue_evt = queue_evts.remove(0);
+ let event_queue = queues.remove(0);
+ let event_queue_evt = queue_evts.remove(0);
+ let tx_queue = queues.remove(0);
+ let tx_queue_evt = queue_evts.remove(0);
+ let rx_queue = queues.remove(0);
+ let rx_queue_evt = queue_evts.remove(0);
+
+ let vios_client = self.vios_client.clone();
+ if let Err(e) = vios_client.start_bg_thread() {
+ error!("Failed to start vios background thread: {}", e);
+ }
+
+ let thread_result = thread::Builder::new()
+ .name(String::from("virtio_snd"))
+ .spawn(move || {
+ match Worker::try_new(
+ vios_client,
+ Arc::new(interrupt),
+ mem,
+ Arc::new(Mutex::new(control_queue)),
+ control_queue_evt,
+ event_queue,
+ event_queue_evt,
+ Arc::new(Mutex::new(tx_queue)),
+ tx_queue_evt,
+ Arc::new(Mutex::new(rx_queue)),
+ rx_queue_evt,
+ ) {
+ Ok(mut worker) => match worker.control_loop(kill_evt) {
+ Ok(_) => true,
+ Err(e) => {
+ error!("virtio-snd: Error in worker loop: {}", e);
+ false
+ }
+ },
+ Err(e) => {
+ error!("virtio-snd: Failed to create worker: {}", e);
+ false
+ }
+ }
+ });
+ match thread_result {
+ Err(e) => {
+ error!("failed to spawn virtio_snd worker thread: {}", e);
+ }
+ Ok(join_handle) => {
+ self.worker_thread = Some(join_handle);
+ }
+ }
+ }
+
+ fn reset(&mut self) -> bool {
+ let mut ret = true;
+ if let Some(kill_evt) = self.kill_evt.take() {
+ if let Err(e) = kill_evt.write(1) {
+ error!("virtio-snd: failed to notify the kill event: {}", e);
+ ret = false;
+ }
+ } else if let Some(worker_thread) = self.worker_thread.take() {
+ match worker_thread.join() {
+ Err(e) => {
+ error!("virtio-snd: Worker thread panicked: {:?}", e);
+ ret = false;
+ }
+ Ok(worker_status) => {
+ ret = worker_status;
+ }
+ }
+ }
+ if let Err(e) = self.vios_client.stop_bg_thread() {
+ error!("virtio-snd: Failed to stop vios background thread: {}", e);
+ ret = false;
+ }
+ ret
+ }
+}
+
+/// Creates a new virtio sound device connected to a VioS backend
+pub fn new_sound<P: AsRef<Path>>(path: P, virtio_features: u64) -> Result<Sound> {
+ let vios_client = Arc::new(VioSClient::try_new(path).map_err(SoundError::ClientNew)?);
+ Ok(Sound {
+ config: virtio_snd_config {
+ jacks: Le32::from(vios_client.num_jacks()),
+ streams: Le32::from(vios_client.num_streams()),
+ chmaps: Le32::from(vios_client.num_chmaps()),
+ },
+ virtio_features,
+ worker_thread: None,
+ kill_evt: None,
+ vios_client,
+ })
+}
diff --git a/devices/src/virtio/snd/vios_backend/shm_streams.rs b/devices/src/virtio/snd/vios_backend/shm_streams.rs
index 5b5e0f641..274a68567 100644
--- a/devices/src/virtio/snd/vios_backend/shm_streams.rs
+++ b/devices/src/virtio/snd/vios_backend/shm_streams.rs
@@ -9,109 +9,88 @@
use super::shm_vios::{VioSClient, VioSStreamParams};
+use crate::virtio::snd::common::*;
use crate::virtio::snd::constants::*;
-use audio_streams::shm_streams::{BufferSet, ServerRequest, ShmStream, ShmStreamSource};
+use audio_streams::shm_streams::{
+ BufferSet, ServerRequest, SharedMemory as AudioSharedMemory, ShmStream, ShmStreamSource,
+};
use audio_streams::{BoxError, SampleFormat, StreamDirection, StreamEffect};
-use base::{error, SharedMemory, SharedMemoryUnix};
+use base::{
+ error, Error as SysError, MemoryMapping, MemoryMappingBuilder, SharedMemory, SharedMemoryUnix,
+};
+use data_model::VolatileMemory;
+use sync::Mutex;
use std::fs::File;
-use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+use std::os::unix::io::{FromRawFd, RawFd};
use std::path::Path;
use std::sync::Arc;
use std::time::{Duration, Instant};
-use sys_util::{Error as SysError, SharedMemory as SysSharedMemory};
-
use super::shm_vios::{Error, Result};
// This is the error type used in audio_streams::shm_streams. Unfortunately, it's not declared
// public there so it needs to be re-declared here. It also prevents the usage of anyhow::Error.
type GenericResult<T> = std::result::Result<T, BoxError>;
+enum StreamState {
+ Available,
+ Acquired,
+ Active,
+}
+
+struct StreamDesc {
+ state: Arc<Mutex<StreamState>>,
+ direction: StreamDirection,
+}
+
/// Adapter that provides the ShmStreamSource trait around the VioS backend.
pub struct VioSShmStreamSource {
vios_client: Arc<VioSClient>,
+ stream_descs: Vec<StreamDesc>,
}
impl VioSShmStreamSource {
/// Creates a new stream source given the path to the audio server's socket.
pub fn new<P: AsRef<Path>>(server: P) -> Result<VioSShmStreamSource> {
+ let vios_client = Arc::new(VioSClient::try_new(server)?);
+ let mut stream_descs: Vec<StreamDesc> = Vec::new();
+ let mut idx = 0u32;
+ while let Some(info) = vios_client.stream_info(idx) {
+ stream_descs.push(StreamDesc {
+ state: Arc::new(Mutex::new(StreamState::Active)),
+ direction: if info.direction == VIRTIO_SND_D_OUTPUT {
+ StreamDirection::Playback
+ } else {
+ StreamDirection::Capture
+ },
+ });
+ idx += 1;
+ }
Ok(Self {
- vios_client: Arc::new(VioSClient::try_new(server)?),
+ vios_client,
+ stream_descs,
})
}
}
-fn from_sample_format(format: SampleFormat) -> u8 {
- match format {
- SampleFormat::U8 => VIRTIO_SND_PCM_FMT_U8,
- SampleFormat::S16LE => VIRTIO_SND_PCM_FMT_U16,
- SampleFormat::S24LE => VIRTIO_SND_PCM_FMT_U24,
- SampleFormat::S32LE => VIRTIO_SND_PCM_FMT_U32,
- }
-}
-
-fn virtio_frame_rate(frame_rate: u32) -> GenericResult<u8> {
- Ok(match frame_rate {
- 5512u32 => VIRTIO_SND_PCM_RATE_5512,
- 8000u32 => VIRTIO_SND_PCM_RATE_8000,
- 11025u32 => VIRTIO_SND_PCM_RATE_11025,
- 16000u32 => VIRTIO_SND_PCM_RATE_16000,
- 22050u32 => VIRTIO_SND_PCM_RATE_22050,
- 32000u32 => VIRTIO_SND_PCM_RATE_32000,
- 44100u32 => VIRTIO_SND_PCM_RATE_44100,
- 48000u32 => VIRTIO_SND_PCM_RATE_48000,
- 64000u32 => VIRTIO_SND_PCM_RATE_64000,
- 88200u32 => VIRTIO_SND_PCM_RATE_88200,
- 96000u32 => VIRTIO_SND_PCM_RATE_96000,
- 176400u32 => VIRTIO_SND_PCM_RATE_176400,
- 192000u32 => VIRTIO_SND_PCM_RATE_192000,
- 384000u32 => VIRTIO_SND_PCM_RATE_384000,
- _ => {
- return Err(Box::new(Error::UnsupportedFrameRate(frame_rate)));
- }
- })
-}
-
-impl ShmStreamSource for VioSShmStreamSource {
- /// Creates a new stream
- #[allow(clippy::too_many_arguments)]
- fn new_stream(
+impl VioSShmStreamSource {
+ fn new_stream_inner(
&mut self,
+ stream_id: u32,
direction: StreamDirection,
num_channels: usize,
format: SampleFormat,
frame_rate: u32,
buffer_size: usize,
_effects: &[StreamEffect],
- client_shm: &SysSharedMemory,
+ client_shm: &dyn AudioSharedMemory<Error = base::Error>,
_buffer_offsets: [u64; 2],
) -> GenericResult<Box<dyn ShmStream>> {
- self.vios_client.ensure_bg_thread_started()?;
- let virtio_dir = match direction {
- StreamDirection::Playback => VIRTIO_SND_D_OUTPUT,
- StreamDirection::Capture => VIRTIO_SND_D_INPUT,
- };
let frame_size = num_channels * format.sample_bytes();
let period_bytes = (frame_size * buffer_size) as u32;
- let stream_id = self
- .vios_client
- .get_unused_stream_id(virtio_dir)
- .ok_or(Box::new(Error::NoStreamsAvailable))?;
- // Create the stream object before any errors can be returned to guarantee the stream will
- // be released in all cases
- let stream_box = VioSndShmStream::new(
- buffer_size,
- num_channels,
- format,
- frame_rate,
- stream_id,
- direction,
- self.vios_client.clone(),
- client_shm,
- );
self.vios_client.prepare_stream(stream_id)?;
let params = VioSStreamParams {
buffer_bytes: 2 * period_bytes,
@@ -123,7 +102,68 @@ impl ShmStreamSource for VioSShmStreamSource {
};
self.vios_client.set_stream_parameters(stream_id, params)?;
self.vios_client.start_stream(stream_id)?;
- stream_box
+ VioSndShmStream::new(
+ buffer_size,
+ num_channels,
+ format,
+ frame_rate,
+ stream_id,
+ direction,
+ self.vios_client.clone(),
+ client_shm,
+ self.stream_descs[stream_id as usize].state.clone(),
+ )
+ }
+
+ fn get_unused_stream_id(&self, direction: StreamDirection) -> Option<u32> {
+ self.stream_descs
+ .iter()
+ .position(|s| match &*s.state.lock() {
+ StreamState::Available => s.direction == direction,
+ _ => false,
+ })
+ .map(|idx| idx as u32)
+ }
+}
+
+impl ShmStreamSource<base::Error> for VioSShmStreamSource {
+ /// Creates a new stream
+ #[allow(clippy::too_many_arguments)]
+ fn new_stream(
+ &mut self,
+ direction: StreamDirection,
+ num_channels: usize,
+ format: SampleFormat,
+ frame_rate: u32,
+ buffer_size: usize,
+ effects: &[StreamEffect],
+ client_shm: &dyn AudioSharedMemory<Error = base::Error>,
+ buffer_offsets: [u64; 2],
+ ) -> GenericResult<Box<dyn ShmStream>> {
+ self.vios_client.start_bg_thread()?;
+ let stream_id = self
+ .get_unused_stream_id(direction)
+ .ok_or(Box::new(Error::NoStreamsAvailable))?;
+ let stream = self
+ .new_stream_inner(
+ stream_id,
+ direction,
+ num_channels,
+ format,
+ frame_rate,
+ buffer_size,
+ effects,
+ client_shm,
+ buffer_offsets,
+ )
+ .map_err(|e| {
+ // Attempt to release the stream so that it can be used later. This is a best effort
+ // attempt, so we ignore any error it may return.
+ let _ = self.vios_client.release_stream(stream_id);
+ e
+ })?;
+ *self.stream_descs[stream_id as usize].state.lock() = StreamState::Acquired;
+ Ok(stream)
}
/// Get a list of file descriptors used by the implementation.
@@ -149,6 +189,7 @@ pub struct VioSndShmStream {
direction: StreamDirection,
vios_client: Arc<VioSClient>,
client_shm: SharedMemory,
+ state: Arc<Mutex<StreamState>>,
}
impl VioSndShmStream {
@@ -161,7 +202,8 @@ impl VioSndShmStream {
stream_id: u32,
direction: StreamDirection,
vios_client: Arc<VioSClient>,
- client_shm: &SysSharedMemory,
+ client_shm: &dyn AudioSharedMemory<Error = base::Error>,
+ state: Arc<Mutex<StreamState>>,
) -> GenericResult<Box<dyn ShmStream>> {
let interval = Duration::from_millis(buffer_size as u64 * 1000 / frame_rate as u64);
@@ -177,8 +219,7 @@ impl VioSndShmStream {
// safe because we checked the result of libc::fcntl()
File::from_raw_fd(dup_fd)
};
- let client_shm_clone =
- SharedMemory::from_file(file).map_err(|e| Error::BaseMmapError(e))?;
+ let client_shm_clone = SharedMemory::from_file(file).map_err(Error::BaseMmapError)?;
Ok(Box::new(Self {
num_channels,
@@ -192,6 +233,7 @@ impl VioSndShmStream {
direction,
vios_client,
client_shm: client_shm_clone,
+ state,
}))
}
}
@@ -234,20 +276,54 @@ impl BufferSet for VioSndShmStream {
fn callback(&mut self, offset: usize, frames: usize) -> GenericResult<()> {
match self.direction {
StreamDirection::Playback => {
- self.vios_client.inject_audio_data(
+ let requested_size = frames * self.frame_size;
+ let shm_ref = &mut self.client_shm;
+ let (_, res) = self.vios_client.inject_audio_data::<Result<()>, _>(
self.stream_id,
- &mut self.client_shm,
- offset,
- frames * self.frame_size,
+ requested_size,
+ |slice| {
+ if requested_size != slice.size() {
+ error!(
+ "Buffer size is different than the requested size: {} vs {}",
+ requested_size,
+ slice.size()
+ );
+ }
+ let size = std::cmp::min(requested_size, slice.size());
+ let (src_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;
+ let src_slice = src_mmap
+ .get_slice(mmap_offset, size)
+ .map_err(Error::VolatileMemoryError)?;
+ src_slice.copy_to_volatile_slice(slice);
+ Ok(())
+ },
)?;
+ res?;
}
StreamDirection::Capture => {
- self.vios_client.request_audio_data(
+ let requested_size = frames * self.frame_size;
+ let shm_ref = &mut self.client_shm;
+ let (_, res) = self.vios_client.request_audio_data::<Result<()>, _>(
self.stream_id,
- &mut self.client_shm,
- offset,
- frames * self.frame_size,
+ requested_size,
+ |slice| {
+ if requested_size != slice.size() {
+ error!(
+ "Buffer size is different than the requested size: {} vs {}",
+ requested_size,
+ slice.size()
+ );
+ }
+ let size = std::cmp::min(requested_size, slice.size());
+ let (dst_mmap, mmap_offset) = mmap_buffer(shm_ref, offset, size)?;
+ let dst_slice = dst_mmap
+ .get_slice(mmap_offset, size)
+ .map_err(Error::VolatileMemoryError)?;
+ slice.copy_to_volatile_slice(dst_slice);
+ Ok(())
+ },
)?;
+ res?;
}
}
Ok(())
@@ -268,5 +344,28 @@ impl Drop for VioSndShmStream {
{
error!("Failed to stop and release stream {}: {}", stream_id, e);
}
+ *self.state.lock() = StreamState::Available;
}
}
+
+/// Memory map a shared memory object to access an audio buffer. The buffer may not be located at an
+/// offset aligned to page size, so the offset within the mapped region is returned along with the
+/// MemoryMapping struct.
+fn mmap_buffer(
+ src: &mut SharedMemory,
+ offset: usize,
+ size: usize,
+) -> Result<(MemoryMapping, usize)> {
+ // If the buffer is not aligned to page size a bigger region needs to be mapped.
+ let aligned_offset = offset & !(base::pagesize() - 1);
+ let offset_from_mapping_start = offset - aligned_offset;
+ let extended_size = size + offset_from_mapping_start;
+
+ let mmap = MemoryMappingBuilder::new(extended_size)
+ .offset(aligned_offset as u64)
+ .from_shared_memory(src)
+ .build()
+ .map_err(Error::GuestMmapError)?;
+
+ Ok((mmap, offset_from_mapping_start))
+}
diff --git a/devices/src/virtio/snd/vios_backend/shm_vios.rs b/devices/src/virtio/snd/vios_backend/shm_vios.rs
index 02ffa1ce5..349781f59 100644
--- a/devices/src/virtio/snd/vios_backend/shm_vios.rs
+++ b/devices/src/virtio/snd/vios_backend/shm_vios.rs
@@ -6,15 +6,15 @@ use crate::virtio::snd::constants::*;
use crate::virtio::snd::layout::*;
use base::{
- error, net::UnixSeqpacket, Error as BaseError, Event, FromRawDescriptor, IntoRawDescriptor,
+ error, AsRawDescriptor, Error as BaseError, Event, FromRawDescriptor, IntoRawDescriptor,
MemoryMapping, MemoryMappingBuilder, MmapError, PollToken, SafeDescriptor, ScmSocket,
- SharedMemory, WaitContext,
+ UnixSeqpacket, WaitContext,
};
-use data_model::{DataInit, VolatileMemory, VolatileMemoryError};
+use data_model::{DataInit, VolatileMemory, VolatileMemoryError, VolatileSlice};
-use std::collections::HashMap;
+use std::collections::{HashMap, VecDeque};
use std::fs::File;
-use std::io::{Error as IOError, ErrorKind as IOErrorKind, Seek, SeekFrom};
+use std::io::{Error as IOError, ErrorKind as IOErrorKind, IoSliceMut, Seek, SeekFrom};
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::path::Path;
use std::sync::mpsc::{channel, Receiver, RecvError, Sender};
@@ -23,62 +23,66 @@ use std::thread::JoinHandle;
use sync::Mutex;
+use remain::sorted;
use thiserror::Error as ThisError;
pub type Result<T> = std::result::Result<T, Error>;
+#[sorted]
#[derive(ThisError, Debug)]
pub enum Error {
- #[error("Failed to connect to VioS server: {0:?}")]
- ServerConnectionError(IOError),
- #[error("Failed to communicate with VioS server: {0:?}")]
- ServerError(BaseError),
- #[error("Failed to communicate with VioS server: {0:?}")]
- ServerIOError(IOError),
- #[error("Failed to get size of tx shared memory: {0}")]
- FileSizeError(IOError),
+ #[error("Error memory mapping client_shm: {0}")]
+ BaseMmapError(BaseError),
+ #[error("Sender was dropped without sending buffer status, the recv thread may have exited")]
+ BufferStatusSenderLost(RecvError),
+ #[error("Command failed with status {0}")]
+ CommandFailed(u32),
#[error("Error duplicating file descriptor: {0}")]
DupError(BaseError),
- #[error("Error accessing VioS server's shared memory: {0}")]
- ServerMmapError(MmapError),
+ #[error("Failed to create Recv event: {0}")]
+ EventCreateError(BaseError),
+ #[error("Failed to dup Recv event: {0}")]
+ EventDupError(BaseError),
+ #[error("Failed to signal event: {0}")]
+ EventWriteError(BaseError),
+ #[error("Failed to get size of tx shared memory: {0}")]
+ FileSizeError(IOError),
#[error("Error accessing guest's shared memory: {0}")]
GuestMmapError(MmapError),
- #[error("Error memory mapping client_shm: {0}")]
- BaseMmapError(BaseError),
- #[error("Error accessing volatile memory: {0}")]
- VolatileMemoryError(VolatileMemoryError),
- #[error("{0}")]
- ProtocolError(ProtocolErrorKind),
- #[error("No PCM streams available")]
- NoStreamsAvailable,
+ #[error("No jack with id {0}")]
+ InvalidJackId(u32),
#[error("No stream with id {0}")]
InvalidStreamId(u32),
- #[error("Stream is unexpected state: {0:?}")]
- UnexpectedState(StreamState),
- #[error("Invalid operation for stream direction: {0}")]
- WrongDirection(u8),
+ #[error("IO buffer operation failed: status = {0}")]
+ IOBufferError(u32),
+ #[error("No PCM streams available")]
+ NoStreamsAvailable,
#[error("Insuficient space for the new buffer in the queue's buffer area")]
OutOfSpace,
- #[error("Unsupported frame rate: {0}")]
- UnsupportedFrameRate(u32),
#[error("Platform not supported")]
PlatformNotSupported,
- #[error("Command failed with status {0}")]
- CommandFailed(u32),
- #[error("IO buffer operation failed: status = {0}")]
- IOBufferError(u32),
+ #[error("{0}")]
+ ProtocolError(ProtocolErrorKind),
+ #[error("Failed to connect to VioS server: {0:?}")]
+ ServerConnectionError(IOError),
+ #[error("Failed to communicate with VioS server: {0:?}")]
+ ServerError(BaseError),
+ #[error("Failed to communicate with VioS server: {0:?}")]
+ ServerIOError(IOError),
+ #[error("Error accessing VioS server's shared memory: {0}")]
+ ServerMmapError(MmapError),
#[error("Failed to duplicate UnixSeqpacket: {0}")]
UnixSeqpacketDupError(IOError),
- #[error("Sender was dropped without sending buffer status, the recv thread may have exited")]
- BufferStatusSenderLost(RecvError),
- #[error("Failed to create Recv event: {0}")]
- EventCreateError(BaseError),
- #[error("Failed to dup Recv event: {0}")]
- EventDupError(BaseError),
+ #[error("Unsupported frame rate: {0}")]
+ UnsupportedFrameRate(u32),
+ #[error("Error accessing volatile memory: {0}")]
+ VolatileMemoryError(VolatileMemoryError),
#[error("Failed to create Recv thread's WaitContext: {0}")]
WaitContextCreateError(BaseError),
#[error("Error waiting for events")]
WaitError(BaseError),
+ #[error("Invalid operation for stream direction: {0}")]
+ WrongDirection(u8),
}
#[derive(ThisError, Debug)]
@@ -99,32 +103,42 @@ pub enum ProtocolErrorKind {
/// notifications. It's thread safe, it can be encapsulated in an Arc smart pointer and shared
/// between threads.
pub struct VioSClient {
- config: VioSConfig,
// These mutexes should almost never be held simultaneously. If at some point they have to the
// locking order should match the order in which they are declared here.
- streams: Mutex<Vec<VioSStreamInfo>>,
+ config: VioSConfig,
+ jacks: Vec<virtio_snd_jack_info>,
+ streams: Vec<virtio_snd_pcm_info>,
+ chmaps: Vec<virtio_snd_chmap_info>,
+ // The control socket is used from multiple threads to send and wait for a reply, which needs
+ // to happen atomically, hence the need for a mutex instead of just sharing clones of the
+ // socket.
control_socket: Mutex<UnixSeqpacket>,
- event_socket: Mutex<UnixSeqpacket>,
- tx: Mutex<IoBufferQueue>,
- rx: Mutex<IoBufferQueue>,
- rx_subscribers: Arc<Mutex<HashMap<usize, Sender<(u32, usize)>>>>,
- recv_running: Arc<Mutex<bool>>,
- recv_event: Mutex<Event>,
+ event_socket: UnixSeqpacket,
+ // These are thread safe and don't require locking
+ tx: IoBufferQueue,
+ rx: IoBufferQueue,
+ // This is accessed by the recv_thread and whatever thread processes the events
+ events: Arc<Mutex<VecDeque<virtio_snd_event>>>,
+ event_notifier: Event,
+ // These are accessed by the recv_thread and the stream threads
+ tx_subscribers: Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>>,
+ rx_subscribers: Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>>,
+ recv_thread_state: Arc<Mutex<ThreadFlags>>,
+ recv_event: Event,
recv_thread: Mutex<Option<JoinHandle<Result<()>>>>,
}
impl VioSClient {
/// Create a new client given the path to the audio server's socket.
pub fn try_new<P: AsRef<Path>>(server: P) -> Result<VioSClient> {
- let client_socket =
- UnixSeqpacket::connect(server).map_err(|e| Error::ServerConnectionError(e))?;
+ let client_socket = UnixSeqpacket::connect(server).map_err(Error::ServerConnectionError)?;
let mut config: VioSConfig = Default::default();
let mut fds: Vec<RawFd> = Vec::new();
const NUM_FDS: usize = 5;
fds.resize(NUM_FDS, 0);
let (recv_size, fd_count) = client_socket
- .recv_with_fds(config.as_mut_slice(), &mut fds)
- .map_err(|e| Error::ServerError(e))?;
+ .recv_with_fds(IoSliceMut::new(config.as_mut_slice()), &mut fds)
+ .map_err(Error::ServerError)?;
// Resize the vector to the actual number of file descriptors received and wrap them in
// SafeDescriptors to prevent leaks
@@ -181,349 +195,472 @@ impl VioSClient {
));
}
- let rx_subscribers: Arc<Mutex<HashMap<usize, Sender<(u32, usize)>>>> =
+ let tx_subscribers: Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>> =
+ Arc::new(Mutex::new(HashMap::new()));
+ let rx_subscribers: Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>> =
Arc::new(Mutex::new(HashMap::new()));
- let recv_running = Arc::new(Mutex::new(true));
- let recv_event = Event::new().map_err(|e| Error::EventCreateError(e))?;
+ let recv_thread_state = Arc::new(Mutex::new(ThreadFlags {
+ running: true,
+ reporting_events: false,
+ }));
+ let recv_event = Event::new().map_err(Error::EventCreateError)?;
let mut client = VioSClient {
config,
- streams: Mutex::new(Vec::new()),
+ jacks: Vec::new(),
+ streams: Vec::new(),
+ chmaps: Vec::new(),
control_socket: Mutex::new(client_socket),
- event_socket: Mutex::new(event_socket),
- tx: Mutex::new(IoBufferQueue::new(tx_socket, tx_shm_file)?),
- rx: Mutex::new(IoBufferQueue::new(rx_socket, rx_shm_file)?),
+ event_socket,
+ tx: IoBufferQueue::new(tx_socket, tx_shm_file)?,
+ rx: IoBufferQueue::new(rx_socket, rx_shm_file)?,
+ events: Arc::new(Mutex::new(VecDeque::new())),
+ event_notifier: Event::new().map_err(Error::EventCreateError)?,
+ tx_subscribers,
rx_subscribers,
- recv_running,
- recv_event: Mutex::new(recv_event),
+ recv_thread_state,
+ recv_event,
recv_thread: Mutex::new(None),
};
- client.request_and_cache_streams_info()?;
+ client.request_and_cache_info()?;
Ok(client)
}
- pub fn ensure_bg_thread_started(&self) -> Result<()> {
+ /// Get the number of jacks
+ pub fn num_jacks(&self) -> u32 {
+ self.config.jacks
+ }
+
+ /// Get the number of pcm streams
+ pub fn num_streams(&self) -> u32 {
+ self.config.streams
+ }
+
+ /// Get the number of channel maps
+ pub fn num_chmaps(&self) -> u32 {
+ self.config.chmaps
+ }
+
+ /// Get the configuration information on a jack
+ pub fn jack_info(&self, idx: u32) -> Option<virtio_snd_jack_info> {
+ self.jacks.get(idx as usize).copied()
+ }
+
+ /// Get the configuration information on a pcm stream
+ pub fn stream_info(&self, idx: u32) -> Option<virtio_snd_pcm_info> {
+ self.streams.get(idx as usize).cloned()
+ }
+
+ /// Get the configuration information on a channel map
+ pub fn chmap_info(&self, idx: u32) -> Option<virtio_snd_chmap_info> {
+ self.chmaps.get(idx as usize).copied()
+ }
+
+ /// Starts the background thread that receives release messages from the server. If the thread
+ /// was already started this function does nothing.
+ /// This thread must be started prior to attempting any stream IO operation or the calling
+ /// thread would block.
+ pub fn start_bg_thread(&self) -> Result<()> {
if self.recv_thread.lock().is_some() {
return Ok(());
}
+ let recv_event = self.recv_event.try_clone().map_err(Error::EventDupError)?;
+ let tx_socket = self.tx.try_clone_socket()?;
+ let rx_socket = self.rx.try_clone_socket()?;
let event_socket = self
- .recv_event
- .lock()
- .try_clone()
- .map_err(|e| Error::EventDupError(e))?;
- let rx_socket = self
- .rx
- .lock()
- .socket
+ .event_socket
.try_clone()
- .map_err(|e| Error::UnixSeqpacketDupError(e))?;
+ .map_err(Error::UnixSeqpacketDupError)?;
let mut opt = self.recv_thread.lock();
// The lock on recv_thread was released above to avoid holding more than one lock at a time
- // while duplicating the fds. So we have to check again the condition.
+ // while duplicating the fds. So we have to check the condition again.
if opt.is_none() {
*opt = Some(spawn_recv_thread(
+ self.tx_subscribers.clone(),
self.rx_subscribers.clone(),
- event_socket,
- self.recv_running.clone(),
+ self.event_notifier
+ .try_clone()
+ .map_err(Error::EventDupError)?,
+ self.events.clone(),
+ recv_event,
+ self.recv_thread_state.clone(),
+ tx_socket,
rx_socket,
+ event_socket,
));
}
Ok(())
}
- /// Gets an unused stream id of the specified direction. `direction` must be one of
- /// VIRTIO_SND_D_INPUT OR VIRTIO_SND_D_OUTPUT.
- pub fn get_unused_stream_id(&self, direction: u8) -> Option<u32> {
- self.streams
- .lock()
- .iter()
- .filter(|s| s.state == StreamState::Available && s.direction == direction as u8)
- .map(|s| s.id)
- .next()
+ /// Stops the background thread.
+ pub fn stop_bg_thread(&self) -> Result<()> {
+ if self.recv_thread.lock().is_none() {
+ return Ok(());
+ }
+ self.recv_thread_state.lock().running = false;
+ self.recv_event
+ .write(1u64)
+ .map_err(Error::EventWriteError)?;
+ if let Some(handle) = self.recv_thread.lock().take() {
+ return match handle.join() {
+ Ok(r) => r,
+ Err(e) => {
+ error!("Recv thread panicked: {:?}", e);
+ Ok(())
+ }
+ };
+ }
+ Ok(())
+ }
+
+ /// Gets an Event object that will trigger every time an event is received from the server
+ pub fn get_event_notifier(&self) -> Result<Event> {
+ // Let the background thread know that there is at least one consumer of events
+ self.recv_thread_state.lock().reporting_events = true;
+ self.event_notifier
+ .try_clone()
+ .map_err(Error::EventDupError)
+ }
+
+ /// Retrieves one event. Callers should have received a notification through the event notifier
+ /// before calling this function.
+ pub fn pop_event(&self) -> Option<virtio_snd_event> {
+ self.events.lock().pop_front()
+ }
+
+ /// Remap a jack. This should only be called if the jack announces support for the operation
+ /// through the features field in the corresponding virtio_snd_jack_info struct.
+ pub fn remap_jack(&self, jack_id: u32, association: u32, sequence: u32) -> Result<()> {
+ if jack_id >= self.config.jacks {
+ return Err(Error::InvalidJackId(jack_id));
+ }
+ let msg = virtio_snd_jack_remap {
+ hdr: virtio_snd_jack_hdr {
+ hdr: virtio_snd_hdr {
+ code: VIRTIO_SND_R_JACK_REMAP.into(),
+ },
+ jack_id: jack_id.into(),
+ },
+ association: association.into(),
+ sequence: sequence.into(),
+ };
+ let control_socket_lock = self.control_socket.lock();
+ send_cmd(&*control_socket_lock, msg)
}
/// Configures a stream with the given parameters.
pub fn set_stream_parameters(&self, stream_id: u32, params: VioSStreamParams) -> Result<()> {
- self.validate_stream_id(
- stream_id,
- &[StreamState::Available, StreamState::Acquired],
- None,
- )?;
+ self.streams
+ .get(stream_id as usize)
+ .ok_or(Error::InvalidStreamId(stream_id))?;
let raw_params: virtio_snd_pcm_set_params = (stream_id, params).into();
- self.send_cmd(raw_params)?;
- self.streams.lock()[stream_id as usize].state = StreamState::Acquired;
- Ok(())
+ let control_socket_lock = self.control_socket.lock();
+ send_cmd(&*control_socket_lock, raw_params)
+ }
+
+ /// Configures a stream with the given parameters.
+ pub fn set_stream_parameters_raw(&self, raw_params: virtio_snd_pcm_set_params) -> Result<()> {
+ let stream_id = raw_params.hdr.stream_id.to_native();
+ self.streams
+ .get(stream_id as usize)
+ .ok_or(Error::InvalidStreamId(stream_id))?;
+ let control_socket_lock = self.control_socket.lock();
+ send_cmd(&*control_socket_lock, raw_params)
}
/// Send the PREPARE_STREAM command to the server.
pub fn prepare_stream(&self, stream_id: u32) -> Result<()> {
- self.common_stream_op(
- stream_id,
- &[StreamState::Available, StreamState::Acquired],
- StreamState::Acquired,
- STREAM_PREPARE,
- )
+ self.common_stream_op(stream_id, VIRTIO_SND_R_PCM_PREPARE)
}
/// Send the RELEASE_STREAM command to the server.
pub fn release_stream(&self, stream_id: u32) -> Result<()> {
- self.common_stream_op(
- stream_id,
- &[StreamState::Acquired],
- StreamState::Available,
- STREAM_RELEASE,
- )
+ self.common_stream_op(stream_id, VIRTIO_SND_R_PCM_RELEASE)
}
/// Send the START_STREAM command to the server.
pub fn start_stream(&self, stream_id: u32) -> Result<()> {
- self.common_stream_op(
- stream_id,
- &[StreamState::Acquired],
- StreamState::Active,
- STREAM_START,
- )
+ self.common_stream_op(stream_id, VIRTIO_SND_R_PCM_START)
}
/// Send the STOP_STREAM command to the server.
pub fn stop_stream(&self, stream_id: u32) -> Result<()> {
- self.common_stream_op(
- stream_id,
- &[StreamState::Active],
- StreamState::Acquired,
- STREAM_STOP,
- )
+ self.common_stream_op(stream_id, VIRTIO_SND_R_PCM_STOP)
}
- /// Send audio frames to the server. The audio data is taken from a shared memory resource.
- pub fn inject_audio_data(
+ /// Send audio frames to the server. Blocks the calling thread until the server acknowledges
+ /// the data.
+ pub fn inject_audio_data<R, Cb: FnOnce(VolatileSlice) -> R>(
&self,
stream_id: u32,
- buffer: &mut SharedMemory,
- src_offset: usize,
size: usize,
- ) -> Result<()> {
- self.validate_stream_id(stream_id, &[StreamState::Active], Some(VIRTIO_SND_D_OUTPUT))?;
- let mut tx_lock = self.tx.lock();
- let tx = &mut *tx_lock;
- let dst_offset = tx.push_buffer(buffer, src_offset, size)?;
- let msg = IoTransferMsg::new(stream_id, dst_offset, size);
- seq_socket_send(&tx.socket, msg)
+ callback: Cb,
+ ) -> Result<(u32, R)> {
+ if self
+ .streams
+ .get(stream_id as usize)
+ .ok_or(Error::InvalidStreamId(stream_id))?
+ .direction
+ != VIRTIO_SND_D_OUTPUT
+ {
+ return Err(Error::WrongDirection(VIRTIO_SND_D_OUTPUT));
+ }
+ self.streams
+ .get(stream_id as usize)
+ .ok_or(Error::InvalidStreamId(stream_id))?;
+ let dst_offset = self.tx.allocate_buffer(size)?;
+ let buffer_slice = self.tx.buffer_at(dst_offset, size)?;
+ let ret = callback(buffer_slice);
+ // Register to receive the status before sending the buffer to the server
+ let (sender, receiver): (Sender<BufferReleaseMsg>, Receiver<BufferReleaseMsg>) = channel();
+ self.tx_subscribers.lock().insert(dst_offset, sender);
+ self.tx.send_buffer(stream_id, dst_offset, size)?;
+ let (_, latency) = await_status(receiver)?;
+ Ok((latency, ret))
}
- pub fn request_audio_data(
+ /// Request audio frames from the server. It blocks until the data is available.
+ pub fn request_audio_data<R, Cb: FnOnce(&VolatileSlice) -> R>(
&self,
stream_id: u32,
- buffer: &mut SharedMemory,
- dst_offset: usize,
size: usize,
- ) -> Result<usize> {
- self.validate_stream_id(stream_id, &[StreamState::Active], Some(VIRTIO_SND_D_INPUT))?;
- let (src_offset, status_promise) = {
- let mut rx_lock = self.rx.lock();
- let rx = &mut *rx_lock;
- let src_offset = rx.allocate_buffer(size)?;
- // Register to receive the status before sending the buffer to the server
- let (sender, receiver): (Sender<(u32, usize)>, Receiver<(u32, usize)>) = channel();
- // It's OK to acquire rx_subscriber's lock after rx_lock
- self.rx_subscribers.lock().insert(src_offset, sender);
- let msg = IoTransferMsg::new(stream_id, src_offset, size);
- seq_socket_send(&rx.socket, msg)?;
- (src_offset, receiver)
- };
- // Make sure no mutexes are held while awaiting for the buffer to be written to
- let recv_size = await_status(status_promise)?;
+ callback: Cb,
+ ) -> Result<(u32, R)> {
+ if self
+ .streams
+ .get(stream_id as usize)
+ .ok_or(Error::InvalidStreamId(stream_id))?
+ .direction
+ != VIRTIO_SND_D_INPUT
{
- let mut rx_lock = self.rx.lock();
- rx_lock
- .pop_buffer(buffer, dst_offset, recv_size, src_offset)
- .map(|()| recv_size)
+ return Err(Error::WrongDirection(VIRTIO_SND_D_INPUT));
}
+ let src_offset = self.rx.allocate_buffer(size)?;
+ // Register to receive the status before sending the buffer to the server
+ let (sender, receiver): (Sender<BufferReleaseMsg>, Receiver<BufferReleaseMsg>) = channel();
+ self.rx_subscribers.lock().insert(src_offset, sender);
+ self.rx.send_buffer(stream_id, src_offset, size)?;
+ // Make sure no mutexes are held while awaiting for the buffer to be written to
+ let (recv_size, latency) = await_status(receiver)?;
+ let buffer_slice = self.rx.buffer_at(src_offset, recv_size)?;
+ Ok((latency, callback(&buffer_slice)))
}
/// Get a list of file descriptors used by the implementation.
pub fn keep_fds(&self) -> Vec<RawFd> {
let control_fd = self.control_socket.lock().as_raw_fd();
- let event_fd = self.event_socket.lock().as_raw_fd();
- let (tx_socket_fd, tx_shm_fd) = {
- let lock = self.tx.lock();
- (lock.socket.as_raw_fd(), lock.file.as_raw_fd())
- };
- let (rx_socket_fd, rx_shm_fd) = {
- let lock = self.rx.lock();
- (lock.socket.as_raw_fd(), lock.file.as_raw_fd())
- };
- vec![
- control_fd,
- event_fd,
- tx_socket_fd,
- tx_shm_fd,
- rx_socket_fd,
- rx_shm_fd,
- ]
+ let event_fd = self.event_socket.as_raw_fd();
+ let recv_event = self.recv_event.as_raw_descriptor();
+ let event_notifier = self.event_notifier.as_raw_descriptor();
+ let mut ret = vec![control_fd, event_fd, recv_event, event_notifier];
+ ret.append(&mut self.tx.keep_fds());
+ ret.append(&mut self.rx.keep_fds());
+ ret
}
- fn send_cmd<T: DataInit>(&self, data: T) -> Result<()> {
- let mut control_socket_lock = self.control_socket.lock();
- seq_socket_send(&mut *control_socket_lock, data)?;
- recv_cmd_status(&mut *control_socket_lock)
- }
-
- fn validate_stream_id(
- &self,
- stream_id: u32,
- permitted_states: &[StreamState],
- direction: Option<u8>,
- ) -> Result<()> {
- let streams_lock = self.streams.lock();
- let stream_idx = stream_id as usize;
- if stream_idx >= streams_lock.len() {
- return Err(Error::InvalidStreamId(stream_id));
- }
- if !permitted_states.contains(&streams_lock[stream_idx].state) {
- return Err(Error::UnexpectedState(streams_lock[stream_idx].state));
- }
- match direction {
- None => Ok(()),
- Some(d) => {
- if d == streams_lock[stream_idx].direction {
- Ok(())
- } else {
- Err(Error::WrongDirection(streams_lock[stream_idx].direction))
- }
- }
- }
- }
-
- fn common_stream_op(
- &self,
- stream_id: u32,
- expected_states: &[StreamState],
- new_state: StreamState,
- op: u32,
- ) -> Result<()> {
- self.validate_stream_id(stream_id, expected_states, None)?;
+ fn common_stream_op(&self, stream_id: u32, op: u32) -> Result<()> {
+ self.streams
+ .get(stream_id as usize)
+ .ok_or(Error::InvalidStreamId(stream_id))?;
let msg = virtio_snd_pcm_hdr {
hdr: virtio_snd_hdr { code: op.into() },
stream_id: stream_id.into(),
};
- self.send_cmd(msg)?;
- self.streams.lock()[stream_id as usize].state = new_state;
+ let control_socket_lock = self.control_socket.lock();
+ send_cmd(&*control_socket_lock, msg)
+ }
+
+ fn request_and_cache_info(&mut self) -> Result<()> {
+ self.request_and_cache_jacks_info()?;
+ self.request_and_cache_streams_info()?;
+ self.request_and_cache_chmaps_info()?;
Ok(())
}
- fn request_and_cache_streams_info(&mut self) -> Result<()> {
- let num_streams = self.config.streams as usize;
- let info_size = std::mem::size_of::<virtio_snd_pcm_info>();
+ fn request_info<T: DataInit + Default + Copy + Clone>(
+ &self,
+ req_code: u32,
+ count: usize,
+ ) -> Result<Vec<T>> {
+ let info_size = std::mem::size_of::<T>();
+ let status_size = std::mem::size_of::<virtio_snd_hdr>();
let req = virtio_snd_query_info {
hdr: virtio_snd_hdr {
- code: STREAM_INFO.into(),
+ code: req_code.into(),
},
start_id: 0u32.into(),
- count: (num_streams as u32).into(),
+ count: (count as u32).into(),
size: (std::mem::size_of::<virtio_snd_query_info>() as u32).into(),
};
- self.send_cmd(req)?;
let control_socket_lock = self.control_socket.lock();
- let info_vec = control_socket_lock
+ seq_socket_send(&*control_socket_lock, req)?;
+ let reply = control_socket_lock
.recv_as_vec()
- .map_err(|e| Error::ServerIOError(e))?;
- if info_vec.len() != num_streams * info_size {
+ .map_err(Error::ServerIOError)?;
+ let mut status: virtio_snd_hdr = Default::default();
+ status
+ .as_mut_slice()
+ .copy_from_slice(&reply[0..status_size]);
+ if status.code.to_native() != VIRTIO_SND_S_OK {
+ return Err(Error::CommandFailed(status.code.to_native()));
+ }
+ if reply.len() != status_size + count * info_size {
return Err(Error::ProtocolError(
- ProtocolErrorKind::UnexpectedMessageSize(num_streams * info_size, info_vec.len()),
+ ProtocolErrorKind::UnexpectedMessageSize(count * info_size, reply.len()),
));
}
- self.streams = Mutex::new(
- info_vec
- .chunks(info_size)
- .enumerate()
- .map(|(id, info_buffer)| {
- // unwrap is safe because we checked the size of the vector
- let virtio_stream_info = virtio_snd_pcm_info::from_slice(&info_buffer).unwrap();
- VioSStreamInfo::new(id as u32, &virtio_stream_info)
- })
- .collect(),
- );
+ Ok(reply[status_size..]
+ .chunks(info_size)
+ .map(|info_buffer| {
+ let mut info: T = Default::default();
+ // Need to use copy_from_slice instead of T::from_slice because the info_buffer may
+ // not be aligned correctly
+ info.as_mut_slice().copy_from_slice(info_buffer);
+ info
+ })
+ .collect())
+ }
+
+ fn request_and_cache_jacks_info(&mut self) -> Result<()> {
+ let num_jacks = self.config.jacks as usize;
+ if num_jacks == 0 {
+ return Ok(());
+ }
+ self.jacks = self.request_info(VIRTIO_SND_R_JACK_INFO, num_jacks)?;
+ Ok(())
+ }
+
+ fn request_and_cache_streams_info(&mut self) -> Result<()> {
+ let num_streams = self.config.streams as usize;
+ if num_streams == 0 {
+ return Ok(());
+ }
+ self.streams = self.request_info(VIRTIO_SND_R_PCM_INFO, num_streams)?;
+ Ok(())
+ }
+
+ fn request_and_cache_chmaps_info(&mut self) -> Result<()> {
+ let num_chmaps = self.config.chmaps as usize;
+ if num_chmaps == 0 {
+ return Ok(());
+ }
+ self.chmaps = self.request_info(VIRTIO_SND_R_CHMAP_INFO, num_chmaps)?;
Ok(())
}
}
impl Drop for VioSClient {
fn drop(&mut self) {
- // Stop the recv thread
- *self.recv_running.lock() = false;
- if let Err(e) = self.recv_event.lock().write(1u64) {
- error!("Failed to notify recv thread: {:?}", e);
- }
- if let Some(handle) = self.recv_thread.lock().take() {
- match handle.join() {
- Ok(r) => {
- if let Err(e) = r {
- error!("Error detected on Recv Thread: {}", e);
- }
- }
- Err(e) => error!("Recv thread panicked: {:?}", e),
- };
+ if let Err(e) = self.stop_bg_thread() {
+ error!("Error stopping Recv thread: {}", e);
}
}
}
+#[derive(Clone, Copy)]
+struct ThreadFlags {
+ running: bool,
+ reporting_events: bool,
+}
+
#[derive(PollToken)]
enum Token {
Notification,
+ TxBufferMsg,
RxBufferMsg,
+ EventMsg,
+}
+
+fn recv_buffer_status_msg(
+ socket: &UnixSeqpacket,
+ subscribers: &Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>>,
+) -> Result<()> {
+ let mut msg: IoStatusMsg = Default::default();
+ let size = socket
+ .recv(msg.as_mut_slice())
+ .map_err(Error::ServerIOError)?;
+ if size != std::mem::size_of::<IoStatusMsg>() {
+ return Err(Error::ProtocolError(
+ ProtocolErrorKind::UnexpectedMessageSize(std::mem::size_of::<IoStatusMsg>(), size),
+ ));
+ }
+ let mut status = msg.status.status.into();
+ if status == u32::MAX {
+ // Anyone waiting for this would continue to wait for as long as status is
+ // u32::MAX
+ status -= 1;
+ }
+ let latency = msg.status.latency_bytes.into();
+ let offset = msg.buffer_offset as usize;
+ let consumed_len = msg.consumed_len as usize;
+ let promise_opt = subscribers.lock().remove(&offset);
+ match promise_opt {
+ None => error!(
+ "Received an unexpected buffer status message: {}. This is a BUG!!",
+ offset
+ ),
+ Some(sender) => {
+ if let Err(e) = sender.send(BufferReleaseMsg {
+ status,
+ latency,
+ consumed_len,
+ }) {
+ error!("Failed to notify waiting thread: {:?}", e);
+ }
+ }
+ }
+ Ok(())
+}
+
+fn recv_event(socket: &UnixSeqpacket) -> Result<virtio_snd_event> {
+ let mut msg: virtio_snd_event = Default::default();
+ let size = socket
+ .recv(msg.as_mut_slice())
+ .map_err(Error::ServerIOError)?;
+ if size != std::mem::size_of::<virtio_snd_event>() {
+ return Err(Error::ProtocolError(
+ ProtocolErrorKind::UnexpectedMessageSize(std::mem::size_of::<virtio_snd_event>(), size),
+ ));
+ }
+ Ok(msg)
}
fn spawn_recv_thread(
- rx_subscribers: Arc<Mutex<HashMap<usize, Sender<(u32, usize)>>>>,
+ tx_subscribers: Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>>,
+ rx_subscribers: Arc<Mutex<HashMap<usize, Sender<BufferReleaseMsg>>>>,
+ event_notifier: Event,
+ event_queue: Arc<Mutex<VecDeque<virtio_snd_event>>>,
event: Event,
- running: Arc<Mutex<bool>>,
+ state: Arc<Mutex<ThreadFlags>>,
+ tx_socket: UnixSeqpacket,
rx_socket: UnixSeqpacket,
+ event_socket: UnixSeqpacket,
) -> JoinHandle<Result<()>> {
std::thread::spawn(move || {
let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
+ (&tx_socket, Token::TxBufferMsg),
(&rx_socket, Token::RxBufferMsg),
+ (&event_socket, Token::EventMsg),
(&event, Token::Notification),
])
- .map_err(|e| Error::WaitContextCreateError(e))?;
- while *running.lock() {
- let events = wait_ctx.wait().map_err(|e| Error::WaitError(e))?;
+ .map_err(Error::WaitContextCreateError)?;
+ loop {
+ let state_cpy = *state.lock();
+ if !state_cpy.running {
+ break;
+ }
+ let events = wait_ctx.wait().map_err(Error::WaitError)?;
for evt in events {
match evt.token {
- Token::RxBufferMsg => {
- let mut msg: IoStatusMsg = Default::default();
- let size = rx_socket
- .recv(msg.as_mut_slice())
- .map_err(|e| Error::ServerIOError(e))?;
- if size != std::mem::size_of::<IoStatusMsg>() {
- return Err(Error::ProtocolError(
- ProtocolErrorKind::UnexpectedMessageSize(
- std::mem::size_of::<IoStatusMsg>(),
- size,
- ),
- ));
- }
- let mut status = msg.status.status.into();
- if status == u32::MAX {
- // Anyone waiting for this would continue to wait for as long as status is
- // u32::MAX
- status -= 1;
- }
- let offset = msg.buffer_offset as usize;
- let consumed_len = msg.consumed_len as usize;
- // Acquire and immediately release the mutex protecting the hashmap
- let promise_opt = rx_subscribers.lock().remove(&offset);
- match promise_opt {
- None => error!(
- "Received an unexpected buffer status message: {}. This is a BUG!!",
- offset
- ),
- Some(sender) => {
- if let Err(e) = sender.send((status, consumed_len)) {
- error!("Failed to notify waiting thread: {:?}", e);
- }
- }
- }
+ Token::TxBufferMsg => recv_buffer_status_msg(&tx_socket, &tx_subscribers)?,
+ Token::RxBufferMsg => recv_buffer_status_msg(&rx_socket, &rx_subscribers)?,
+ Token::EventMsg => {
+ let evt = recv_event(&event_socket)?;
+ let state_cpy = *state.lock();
+ if state_cpy.reporting_events {
+ event_queue.lock().push_back(evt);
+ event_notifier.write(1).map_err(Error::EventWriteError)?;
+ } // else just drop the events
}
Token::Notification => {
// Just consume the notification and check for termination on the next
@@ -539,12 +676,14 @@ fn spawn_recv_thread(
})
}
-fn await_status(promise: Receiver<(u32, usize)>) -> Result<usize> {
- let (status, consumed_len) = promise
- .recv()
- .map_err(|e| Error::BufferStatusSenderLost(e))?;
+fn await_status(promise: Receiver<BufferReleaseMsg>) -> Result<(usize, u32)> {
+ let BufferReleaseMsg {
+ status,
+ latency,
+ consumed_len,
+ } = promise.recv().map_err(Error::BufferStatusSenderLost)?;
if status == VIRTIO_SND_S_OK {
- Ok(consumed_len)
+ Ok((consumed_len, latency))
} else {
Err(Error::IOBufferError(status))
}
@@ -555,111 +694,62 @@ struct IoBufferQueue {
file: File,
mmap: MemoryMapping,
size: usize,
- next: usize,
+ next: Mutex<usize>,
}
impl IoBufferQueue {
fn new(socket: UnixSeqpacket, mut file: File) -> Result<IoBufferQueue> {
- let size = file
- .seek(SeekFrom::End(0))
- .map_err(|e| Error::FileSizeError(e))? as usize;
+ let size = file.seek(SeekFrom::End(0)).map_err(Error::FileSizeError)? as usize;
let mmap = MemoryMappingBuilder::new(size)
.from_file(&file)
.build()
- .map_err(|e| Error::ServerMmapError(e))?;
+ .map_err(Error::ServerMmapError)?;
Ok(IoBufferQueue {
socket,
file,
mmap,
size,
- next: 0,
+ next: Mutex::new(0),
})
}
- fn allocate_buffer(&mut self, size: usize) -> Result<usize> {
+ fn allocate_buffer(&self, size: usize) -> Result<usize> {
if size > self.size {
return Err(Error::OutOfSpace);
}
- let offset = if size > self.size - self.next {
+ let mut next_lock = self.next.lock();
+ let offset = if size > self.size - *next_lock {
// Can't fit the new buffer at the end of the area, so put it at the beginning
0
} else {
- self.next
+ *next_lock
};
- self.next = offset + size;
+ *next_lock = offset + size;
Ok(offset)
}
- fn push_buffer(&mut self, src: &mut SharedMemory, offset: usize, size: usize) -> Result<usize> {
- let shm_offset = self.allocate_buffer(size)?;
- let (src_mmap, mmap_offset) = mmap_buffer(src, offset, size)?;
- let src_slice = src_mmap
- .get_slice(mmap_offset, size)
- .map_err(|e| Error::VolatileMemoryError(e))?;
- let dst_slice = self
- .mmap
- .get_slice(shm_offset, size)
- .map_err(|e| Error::VolatileMemoryError(e))?;
- src_slice.copy_to_volatile_slice(dst_slice);
- Ok(shm_offset)
- }
-
- fn pop_buffer(
- &mut self,
- dst: &mut SharedMemory,
- dst_offset: usize,
- size: usize,
- src_offset: usize,
- ) -> Result<()> {
- let (dst_mmap, mmap_offset) = mmap_buffer(dst, dst_offset, size)?;
- let dst_slice = dst_mmap
- .get_slice(mmap_offset, size)
- .map_err(|e| Error::VolatileMemoryError(e))?;
- let src_slice = self
- .mmap
- .get_slice(src_offset, size)
- .map_err(|e| Error::VolatileMemoryError(e))?;
- src_slice.copy_to_volatile_slice(dst_slice);
- Ok(())
+ fn buffer_at(&self, offset: usize, len: usize) -> Result<VolatileSlice> {
+ self.mmap
+ .get_slice(offset, len)
+ .map_err(Error::VolatileMemoryError)
}
-}
-/// Description of a stream made available by the server.
-pub struct VioSStreamInfo {
- pub id: u32,
- pub hda_fn_nid: u32,
- pub features: u32,
- pub formats: u64,
- pub rates: u64,
- pub direction: u8,
- pub channels_min: u8,
- pub channels_max: u8,
- state: StreamState,
-}
+ fn try_clone_socket(&self) -> Result<UnixSeqpacket> {
+ self.socket
+ .try_clone()
+ .map_err(Error::UnixSeqpacketDupError)
+ }
-impl VioSStreamInfo {
- fn new(id: u32, info: &virtio_snd_pcm_info) -> VioSStreamInfo {
- VioSStreamInfo {
- id,
- hda_fn_nid: info.hdr.hda_fn_nid.to_native(),
- features: info.features.to_native(),
- formats: info.formats.to_native(),
- rates: info.rates.to_native(),
- direction: info.direction,
- channels_min: info.channels_min,
- channels_max: info.channels_max,
- state: StreamState::Available,
- }
+ fn send_buffer(&self, stream_id: u32, offset: usize, size: usize) -> Result<()> {
+ let msg = IoTransferMsg::new(stream_id, offset, size);
+ seq_socket_send(&self.socket, msg)
}
-}
-#[derive(PartialEq, Debug, Copy, Clone)]
-pub enum StreamState {
- Available,
- Acquired,
- Active,
+ fn keep_fds(&self) -> Vec<RawFd> {
+ vec![self.file.as_raw_fd(), self.socket.as_raw_fd()]
+ }
}
/// Groups the parameters used to configure a stream prior to using it.
@@ -672,53 +762,36 @@ pub struct VioSStreamParams {
pub rate: u8,
}
-impl Into<virtio_snd_pcm_set_params> for (u32, VioSStreamParams) {
- fn into(self) -> virtio_snd_pcm_set_params {
+impl From<(u32, VioSStreamParams)> for virtio_snd_pcm_set_params {
+ fn from(val: (u32, VioSStreamParams)) -> Self {
virtio_snd_pcm_set_params {
hdr: virtio_snd_pcm_hdr {
hdr: virtio_snd_hdr {
- code: STREAM_SET_PARAMS.into(),
+ code: VIRTIO_SND_R_PCM_SET_PARAMS.into(),
},
- stream_id: self.0.into(),
+ stream_id: val.0.into(),
},
- buffer_bytes: self.1.buffer_bytes.into(),
- period_bytes: self.1.period_bytes.into(),
- features: self.1.features.into(),
- channels: self.1.channels,
- format: self.1.format,
- rate: self.1.rate,
+ buffer_bytes: val.1.buffer_bytes.into(),
+ period_bytes: val.1.period_bytes.into(),
+ features: val.1.features.into(),
+ channels: val.1.channels,
+ format: val.1.format,
+ rate: val.1.rate,
padding: 0u8,
}
}
}
-/// Memory map a shared memory object to access an audio buffer. The buffer may not be located at an
-/// offset aligned to page size, so the offset within the mapped region is returned along with the
-/// MemoryMapping struct.
-fn mmap_buffer(
- src: &mut SharedMemory,
- offset: usize,
- size: usize,
-) -> Result<(MemoryMapping, usize)> {
- // If the buffer is not aligned to page size a bigger region needs to be mapped.
- let aligned_offset = offset & !(base::pagesize() - 1);
- let offset_from_mapping_start = offset - aligned_offset;
- let extended_size = size + offset_from_mapping_start;
-
- let mmap = MemoryMappingBuilder::new(extended_size)
- .offset(aligned_offset as u64)
- .from_shared_memory(src)
- .build()
- .map_err(|e| Error::GuestMmapError(e))?;
-
- Ok((mmap, offset_from_mapping_start))
+fn send_cmd<T: DataInit>(control_socket: &UnixSeqpacket, data: T) -> Result<()> {
+ seq_socket_send(control_socket, data)?;
+ recv_cmd_status(control_socket)
}
-fn recv_cmd_status(control_socket: &mut UnixSeqpacket) -> Result<()> {
+fn recv_cmd_status(control_socket: &UnixSeqpacket) -> Result<()> {
let mut status: virtio_snd_hdr = Default::default();
control_socket
.recv(status.as_mut_slice())
- .map_err(|e| Error::ServerIOError(e))?;
+ .map_err(Error::ServerIOError)?;
if status.code.to_native() == VIRTIO_SND_S_OK {
Ok(())
} else {
@@ -742,7 +815,7 @@ fn seq_socket_send<T: DataInit>(socket: &UnixSeqpacket, data: T) -> Result<()> {
Ok(())
}
-const VIOS_VERSION: u32 = 1;
+const VIOS_VERSION: u32 = 2;
#[repr(C)]
#[derive(Copy, Clone, Default)]
@@ -755,6 +828,12 @@ struct VioSConfig {
// Safe because it only has data and has no implicit padding.
unsafe impl DataInit for VioSConfig {}
+struct BufferReleaseMsg {
+ status: u32,
+ latency: u32,
+ consumed_len: usize,
+}
+
#[repr(C)]
#[derive(Copy, Clone)]
struct IoTransferMsg {
diff --git a/devices/src/virtio/snd/vios_backend/streams.rs b/devices/src/virtio/snd/vios_backend/streams.rs
new file mode 100644
index 000000000..cf5166a63
--- /dev/null
+++ b/devices/src/virtio/snd/vios_backend/streams.rs
@@ -0,0 +1,494 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::collections::VecDeque;
+use std::ops::Deref;
+use std::sync::mpsc::{channel, Receiver, Sender};
+use std::sync::Arc;
+use std::thread;
+use std::time::{Duration, Instant};
+
+use crate::virtio::{DescriptorChain, Interrupt, Queue, Reader, Writer};
+use base::{error, set_rt_prio_limit, set_rt_round_robin, warn};
+use data_model::Le32;
+use sync::Mutex;
+use vm_memory::GuestMemory;
+
+use super::Error as VioSError;
+use super::*;
+use super::{Result, SoundError};
+use crate::virtio::snd::common::from_virtio_frame_rate;
+use crate::virtio::snd::constants::*;
+use crate::virtio::snd::layout::*;
+
+/// Messages that the worker can send to the stream (thread).
+pub enum StreamMsg {
+ SetParams(DescriptorChain, virtio_snd_pcm_set_params),
+ Prepare(DescriptorChain),
+ Start(DescriptorChain),
+ Stop(DescriptorChain),
+ Release(DescriptorChain),
+ Buffer(DescriptorChain),
+ Break,
+}
+
+enum StreamState {
+ New,
+ ParamsSet,
+ Prepared,
+ Started,
+ Stopped,
+ Released,
+}
+
+pub struct Stream {
+ stream_id: u32,
+ receiver: Receiver<StreamMsg>,
+ vios_client: Arc<VioSClient>,
+ guest_memory: GuestMemory,
+ control_queue: Arc<Mutex<Queue>>,
+ io_queue: Arc<Mutex<Queue>>,
+ interrupt: Arc<Interrupt>,
+ capture: bool,
+ current_state: StreamState,
+ period: Duration,
+ start_time: Instant,
+ next_buffer: Duration,
+ buffer_queue: VecDeque<DescriptorChain>,
+}
+
+impl Stream {
+ /// Start a new stream thread and return its handler.
+ pub fn try_new(
+ stream_id: u32,
+ vios_client: Arc<VioSClient>,
+ guest_memory: GuestMemory,
+ interrupt: Arc<Interrupt>,
+ control_queue: Arc<Mutex<Queue>>,
+ io_queue: Arc<Mutex<Queue>>,
+ capture: bool,
+ ) -> Result<StreamProxy> {
+ let (sender, receiver): (Sender<StreamMsg>, Receiver<StreamMsg>) = channel();
+ let thread = thread::Builder::new()
+ .name(format!("virtio_snd stream {}", stream_id))
+ .spawn(move || {
+ try_set_real_time_priority();
+
+ let mut stream = Stream {
+ stream_id,
+ receiver,
+ vios_client,
+ guest_memory,
+ control_queue,
+ io_queue,
+ interrupt,
+ capture,
+ current_state: StreamState::New,
+ period: Duration::from_millis(0),
+ start_time: Instant::now(),
+ next_buffer: Duration::from_millis(0),
+ buffer_queue: VecDeque::new(),
+ };
+
+ if let Err(e) = stream.stream_loop() {
+ error!("virtio-snd: Error in stream {}: {}", stream_id, e);
+ }
+ })
+ .map_err(SoundError::CreateThread)?;
+ Ok(StreamProxy {
+ sender,
+ thread: Some(thread),
+ })
+ }
+
+ fn stream_loop(&mut self) -> Result<()> {
+ loop {
+ if !self.recv_msg()? {
+ break;
+ }
+ self.maybe_process_queued_buffers()?;
+ }
+ Ok(())
+ }
+
+ fn recv_msg(&mut self) -> Result<bool> {
+ let msg = self.receiver.recv().map_err(SoundError::StreamThreadRecv)?;
+ let (code, desc, next_state) = match msg {
+ StreamMsg::SetParams(desc, params) => {
+ let code = match self.vios_client.set_stream_parameters_raw(params) {
+ Ok(()) => {
+ let frame_rate = from_virtio_frame_rate(params.rate).unwrap_or(0) as u64;
+ self.period = Duration::from_millis(
+ (params.period_bytes.to_native() as u64 * 1000u64)
+ / frame_rate
+ / params.channels as u64
+ / bytes_per_sample(params.format) as u64,
+ );
+ VIRTIO_SND_S_OK
+ }
+ Err(e) => {
+ error!(
+ "virtio-snd: Error setting parameters for stream {}: {}",
+ self.stream_id, e
+ );
+ vios_error_to_status_code(e)
+ }
+ };
+ (code, desc, StreamState::ParamsSet)
+ }
+ StreamMsg::Prepare(desc) => {
+ let code = match self.vios_client.prepare_stream(self.stream_id) {
+ Ok(()) => VIRTIO_SND_S_OK,
+ Err(e) => {
+ error!(
+ "virtio-snd: Failed to prepare stream {}: {}",
+ self.stream_id, e
+ );
+ vios_error_to_status_code(e)
+ }
+ };
+ (code, desc, StreamState::Prepared)
+ }
+ StreamMsg::Start(desc) => {
+ let code = match self.vios_client.start_stream(self.stream_id) {
+ Ok(()) => VIRTIO_SND_S_OK,
+ Err(e) => {
+ error!(
+ "virtio-snd: Failed to start stream {}: {}",
+ self.stream_id, e
+ );
+ vios_error_to_status_code(e)
+ }
+ };
+ self.start_time = Instant::now();
+ self.next_buffer = Duration::from_millis(0);
+ (code, desc, StreamState::Started)
+ }
+ StreamMsg::Stop(desc) => {
+ let code = match self.vios_client.stop_stream(self.stream_id) {
+ Ok(()) => VIRTIO_SND_S_OK,
+ Err(e) => {
+ error!(
+ "virtio-snd: Failed to stop stream {}: {}",
+ self.stream_id, e
+ );
+ vios_error_to_status_code(e)
+ }
+ };
+ (code, desc, StreamState::Stopped)
+ }
+ StreamMsg::Release(desc) => {
+ let code = match self.vios_client.release_stream(self.stream_id) {
+ Ok(()) => VIRTIO_SND_S_OK,
+ Err(e) => {
+ error!(
+ "virtio-snd: Failed to release stream {}: {}",
+ self.stream_id, e
+ );
+ vios_error_to_status_code(e)
+ }
+ };
+ (code, desc, StreamState::Released)
+ }
+ StreamMsg::Buffer(d) => {
+ // Buffers may arrive while in several states:
+ // - Prepared: Buffer should be queued and played when start cmd arrives
+ // - Started: Buffer should be processed immediately
+ // - Stopped: Buffer should be returned to the guest immediately
+ // Because we may need to wait to process the buffer, we always queue it and
+ // decide what to do with queued buffers after every message.
+ self.buffer_queue.push_back(d);
+ // return here to avoid replying on control queue below
+ return Ok(true);
+ }
+ StreamMsg::Break => {
+ return Ok(false);
+ }
+ };
+ reply_control_op_status(
+ code,
+ desc,
+ &self.guest_memory,
+ &self.control_queue,
+ self.interrupt.deref(),
+ )?;
+ self.current_state = next_state;
+ Ok(true)
+ }
+
+ fn maybe_process_queued_buffers(&mut self) -> Result<()> {
+ match self.current_state {
+ StreamState::Started => {
+ while let Some(desc) = self.buffer_queue.pop_front() {
+ let mut reader = Reader::new(self.guest_memory.clone(), desc.clone())
+ .map_err(SoundError::CreateReader)?;
+ // Ignore the first buffer, it was already read by the time this thread
+ // receives the descriptor
+ reader.consume(std::mem::size_of::<virtio_snd_pcm_xfer>());
+ let desc_index = desc.index;
+ let mut writer = Writer::new(self.guest_memory.clone(), desc)
+ .map_err(SoundError::CreateWriter)?;
+ let io_res = if self.capture {
+ let buffer_size =
+ writer.available_bytes() - std::mem::size_of::<virtio_snd_pcm_status>();
+ self.vios_client
+ .request_audio_data(self.stream_id, buffer_size, |vslice| {
+ writer.write_from_volatile_slice(*vslice)
+ })
+ } else {
+ self.vios_client.inject_audio_data(
+ self.stream_id,
+ reader.available_bytes(),
+ |vslice| reader.read_to_volatile_slice(vslice),
+ )
+ };
+ let (code, latency) = match io_res {
+ Ok((latency, _)) => (VIRTIO_SND_S_OK, latency),
+ Err(e) => {
+ error!(
+ "virtio-snd: Failed IO operation in stream {}: {}",
+ self.stream_id, e
+ );
+ (VIRTIO_SND_S_IO_ERR, 0)
+ }
+ };
+ if let Err(e) = writer.write_obj(virtio_snd_pcm_status {
+ status: Le32::from(code),
+ latency_bytes: Le32::from(latency),
+ }) {
+ error!(
+ "virtio-snd: Failed to write pcm status from stream {} thread: {}",
+ self.stream_id, e
+ );
+ }
+
+ self.next_buffer += self.period;
+ let elapsed = self.start_time.elapsed();
+ if elapsed < self.next_buffer {
+ // Completing an IO request can be considered an elapsed period
+ // notification by the driver, so we must wait the right amount of time to
+ // release the buffer if the sound server client returned too soon.
+ std::thread::sleep(self.next_buffer - elapsed);
+ }
+ {
+ let mut io_queue_lock = self.io_queue.lock();
+ io_queue_lock.add_used(
+ &self.guest_memory,
+ desc_index,
+ writer.bytes_written() as u32,
+ );
+ io_queue_lock.trigger_interrupt(&self.guest_memory, self.interrupt.deref());
+ }
+ }
+ }
+ StreamState::Stopped | StreamState::Released => {
+ // For some reason playback buffers can arrive after stop and release (maybe because
+ // buffer-ready notifications arrive over eventfds and those are processed in
+ // random order?). The spec requires the device to not confirm the release of a
+ // stream until all IO buffers have been released, but that's impossible to
+ // guarantee if a buffer arrives after release is requested. Luckily it seems to
+ // work fine if the buffer is released after the release command is completed.
+ while let Some(desc) = self.buffer_queue.pop_front() {
+ reply_pcm_buffer_status(
+ VIRTIO_SND_S_OK,
+ 0,
+ desc,
+ &self.guest_memory,
+ &self.io_queue,
+ self.interrupt.deref(),
+ )?;
+ }
+ }
+ StreamState::Prepared => {} // Do nothing, any buffers will be processed after start
+ _ => {
+ if self.buffer_queue.len() > 0 {
+ warn!("virtio-snd: Buffers received while in unexpected state");
+ }
+ }
+ }
+ Ok(())
+ }
+}
+
+impl Drop for Stream {
+ fn drop(&mut self) {
+ // Try to stop and release the stream in case it was playing, these operations will fail if the
+ // stream is already released, just ignore that failure
+ let _ = self.vios_client.stop_stream(self.stream_id);
+ let _ = self.vios_client.release_stream(self.stream_id);
+
+ // Also release any pending buffer
+ while let Some(desc) = self.buffer_queue.pop_front() {
+ if let Err(e) = reply_pcm_buffer_status(
+ VIRTIO_SND_S_IO_ERR,
+ 0,
+ desc,
+ &self.guest_memory,
+ &self.io_queue,
+ self.interrupt.deref(),
+ ) {
+ error!(
+ "virtio-snd: Failed to reply buffer on stream {}: {}",
+ self.stream_id, e
+ );
+ }
+ }
+ }
+}
+
+/// Basically a proxy to the thread handling a particular stream.
+pub struct StreamProxy {
+ sender: Sender<StreamMsg>,
+ thread: Option<thread::JoinHandle<()>>,
+}
+
+impl StreamProxy {
+ /// Access the underlying sender to clone it or send messages
+ pub fn msg_sender(&self) -> &Sender<StreamMsg> {
+ &self.sender
+ }
+
+ /// Send a message to the stream thread on the other side of this sender
+ pub fn send_msg(sender: &Sender<StreamMsg>, msg: StreamMsg) -> Result<()> {
+ sender.send(msg).map_err(SoundError::StreamThreadSend)
+ }
+
+ /// Convenience function to send a message to this stream's thread
+ pub fn send(&self, msg: StreamMsg) -> Result<()> {
+ Self::send_msg(&self.sender, msg)
+ }
+
+ fn stop_thread(&mut self) {
+ if let Err(e) = self.send(StreamMsg::Break) {
+ error!(
+ "virtio-snd: Failed to send Break msg to stream thread: {}",
+ e
+ );
+ }
+ if let Some(th) = self.thread.take() {
+ if let Err(e) = th.join() {
+ error!("virtio-snd: Panic detected on stream thread: {:?}", e);
+ }
+ }
+ }
+}
+
+impl Drop for StreamProxy {
+ fn drop(&mut self) {
+ self.stop_thread();
+ }
+}
+
+/// Attempts to set the current thread's priority to a value hight enough to handle audio IO. This
+/// may fail due to insuficient permissions.
+pub fn try_set_real_time_priority() {
+ const AUDIO_THREAD_RTPRIO: u16 = 10; // Matches other cros audio clients.
+ if let Err(e) = set_rt_prio_limit(u64::from(AUDIO_THREAD_RTPRIO))
+ .and_then(|_| set_rt_round_robin(i32::from(AUDIO_THREAD_RTPRIO)))
+ {
+ warn!("Failed to set audio stream thread to real time: {}", e);
+ }
+}
+
+/// Gets the appropriate virtio-snd error to return to the driver from a VioSError.
+pub fn vios_error_to_status_code(e: VioSError) -> u32 {
+ match e {
+ VioSError::ServerIOError(_) => VIRTIO_SND_S_IO_ERR,
+ _ => VIRTIO_SND_S_NOT_SUPP,
+ }
+}
+
+/// Encapsulates sending the virtio_snd_hdr struct back to the driver.
+pub fn reply_control_op_status(
+ code: u32,
+ desc: DescriptorChain,
+ guest_memory: &GuestMemory,
+ queue: &Arc<Mutex<Queue>>,
+ interrupt: &Interrupt,
+) -> Result<()> {
+ let desc_index = desc.index;
+ let mut writer = Writer::new(guest_memory.clone(), desc).map_err(SoundError::Descriptor)?;
+ writer
+ .write_obj(virtio_snd_hdr {
+ code: Le32::from(code),
+ })
+ .map_err(SoundError::QueueIO)?;
+ {
+ let mut queue_lock = queue.lock();
+ queue_lock.add_used(guest_memory, desc_index, writer.bytes_written() as u32);
+ queue_lock.trigger_interrupt(guest_memory, interrupt);
+ }
+ Ok(())
+}
+
+/// Encapsulates sending the virtio_snd_pcm_status struct back to the driver.
+pub fn reply_pcm_buffer_status(
+ status: u32,
+ latency_bytes: u32,
+ desc: DescriptorChain,
+ guest_memory: &GuestMemory,
+ queue: &Arc<Mutex<Queue>>,
+ interrupt: &Interrupt,
+) -> Result<()> {
+ let desc_index = desc.index;
+ let mut writer = Writer::new(guest_memory.clone(), desc).map_err(SoundError::Descriptor)?;
+ if writer.available_bytes() > std::mem::size_of::<virtio_snd_pcm_status>() {
+ writer
+ .consume_bytes(writer.available_bytes() - std::mem::size_of::<virtio_snd_pcm_status>());
+ }
+ writer
+ .write_obj(virtio_snd_pcm_status {
+ status: Le32::from(status),
+ latency_bytes: Le32::from(latency_bytes),
+ })
+ .map_err(SoundError::QueueIO)?;
+ {
+ let mut queue_lock = queue.lock();
+ queue_lock.add_used(guest_memory, desc_index, writer.bytes_written() as u32);
+ queue_lock.trigger_interrupt(guest_memory, interrupt);
+ }
+ Ok(())
+}
+
+fn bytes_per_sample(format: u8) -> usize {
+ match format {
+ VIRTIO_SND_PCM_FMT_IMA_ADPCM => 1usize,
+ VIRTIO_SND_PCM_FMT_MU_LAW => 1usize,
+ VIRTIO_SND_PCM_FMT_A_LAW => 1usize,
+ VIRTIO_SND_PCM_FMT_S8 => 1usize,
+ VIRTIO_SND_PCM_FMT_U8 => 1usize,
+ VIRTIO_SND_PCM_FMT_S16 => 2usize,
+ VIRTIO_SND_PCM_FMT_U16 => 2usize,
+ VIRTIO_SND_PCM_FMT_S32 => 4usize,
+ VIRTIO_SND_PCM_FMT_U32 => 4usize,
+ VIRTIO_SND_PCM_FMT_FLOAT => 4usize,
+ VIRTIO_SND_PCM_FMT_FLOAT64 => 8usize,
+ // VIRTIO_SND_PCM_FMT_DSD_U8
+ // VIRTIO_SND_PCM_FMT_DSD_U16
+ // VIRTIO_SND_PCM_FMT_DSD_U32
+ // VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME
+ // VIRTIO_SND_PCM_FMT_S18_3
+ // VIRTIO_SND_PCM_FMT_U18_3
+ // VIRTIO_SND_PCM_FMT_S20_3
+ // VIRTIO_SND_PCM_FMT_U20_3
+ // VIRTIO_SND_PCM_FMT_S24_3
+ // VIRTIO_SND_PCM_FMT_U24_3
+ // VIRTIO_SND_PCM_FMT_S20
+ // VIRTIO_SND_PCM_FMT_U20
+ // VIRTIO_SND_PCM_FMT_S24
+ // VIRTIO_SND_PCM_FMT_U24
+ _ => {
+ // Some of these formats are not consistently stored in a particular size (24bits is
+ // sometimes stored in a 32bit word) while others are of variable size.
+ // The size per sample estimated here is designed to greatly underestimate the time it
+ // takes to play a buffer and depend instead on timings provided by the sound server if
+ // it supports these formats.
+ warn!(
+ "Unknown sample size for format {}, depending on sound server timing instead.",
+ format
+ );
+ 1000usize
+ }
+ }
+}
diff --git a/devices/src/virtio/snd/vios_backend/worker.rs b/devices/src/virtio/snd/vios_backend/worker.rs
new file mode 100644
index 000000000..7e9ec50f5
--- /dev/null
+++ b/devices/src/virtio/snd/vios_backend/worker.rs
@@ -0,0 +1,607 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io::Read;
+use std::ops::Deref;
+use std::sync::{mpsc::Sender, Arc};
+use std::thread;
+
+use crate::virtio::{DescriptorChain, Interrupt, Queue, Reader, SignalableInterrupt, Writer};
+use base::{error, warn, Event, PollToken, WaitContext};
+use data_model::{DataInit, Le32};
+use sync::Mutex;
+use vm_memory::GuestMemory;
+
+use super::super::constants::*;
+use super::super::layout::*;
+use super::streams::*;
+use super::*;
+use super::{Result, SoundError};
+
+pub struct Worker {
+ // Lock order: Must never hold more than one queue lock at the same time.
+ interrupt: Arc<Interrupt>,
+ control_queue: Arc<Mutex<Queue>>,
+ control_queue_evt: Event,
+ event_queue: Queue,
+ event_queue_evt: Event,
+ guest_memory: GuestMemory,
+ vios_client: Arc<VioSClient>,
+ streams: Vec<StreamProxy>,
+ io_thread: Option<thread::JoinHandle<Result<()>>>,
+ io_kill: Event,
+}
+
+impl Worker {
+ /// Creates a new virtio-snd worker.
+ pub fn try_new(
+ vios_client: Arc<VioSClient>,
+ interrupt: Arc<Interrupt>,
+ guest_memory: GuestMemory,
+ control_queue: Arc<Mutex<Queue>>,
+ control_queue_evt: Event,
+ event_queue: Queue,
+ event_queue_evt: Event,
+ tx_queue: Arc<Mutex<Queue>>,
+ tx_queue_evt: Event,
+ rx_queue: Arc<Mutex<Queue>>,
+ rx_queue_evt: Event,
+ ) -> Result<Worker> {
+ let mut streams: Vec<StreamProxy> = Vec::with_capacity(vios_client.num_streams() as usize);
+ {
+ for stream_id in 0..vios_client.num_streams() {
+ let capture = vios_client
+ .stream_info(stream_id)
+ .map(|i| i.direction == VIRTIO_SND_D_INPUT)
+ .unwrap_or(false);
+ let io_queue = if capture { &rx_queue } else { &tx_queue };
+ streams.push(Stream::try_new(
+ stream_id,
+ vios_client.clone(),
+ guest_memory.clone(),
+ interrupt.clone(),
+ control_queue.clone(),
+ io_queue.clone(),
+ capture,
+ )?);
+ }
+ }
+ let (self_kill_io, kill_io) = Event::new()
+ .and_then(|e| Ok((e.try_clone()?, e)))
+ .map_err(SoundError::CreateEvent)?;
+
+ let interrupt_clone = interrupt.clone();
+ let guest_memory_clone = guest_memory.clone();
+ let senders: Vec<Sender<StreamMsg>> =
+ streams.iter().map(|sp| sp.msg_sender().clone()).collect();
+ let io_thread = thread::Builder::new()
+ .name(String::from("virtio_snd_io"))
+ .spawn(move || {
+ try_set_real_time_priority();
+
+ io_loop(
+ interrupt_clone,
+ guest_memory_clone,
+ tx_queue,
+ tx_queue_evt,
+ rx_queue,
+ rx_queue_evt,
+ senders,
+ kill_io,
+ )
+ })
+ .map_err(SoundError::CreateThread)?;
+ Ok(Worker {
+ interrupt,
+ control_queue,
+ control_queue_evt,
+ event_queue,
+ event_queue_evt,
+ guest_memory,
+ vios_client,
+ streams,
+ io_thread: Some(io_thread),
+ io_kill: self_kill_io,
+ })
+ }
+
+ /// Emulates the virtio-snd device. It won't return until something is written to the kill_evt
+ /// event or an unrecoverable error occurs.
+ pub fn control_loop(&mut self, kill_evt: Event) -> Result<()> {
+ let event_notifier = self
+ .vios_client
+ .get_event_notifier()
+ .map_err(SoundError::ClientEventNotifier)?;
+ #[derive(PollToken)]
+ enum Token {
+ ControlQAvailable,
+ EventQAvailable,
+ InterruptResample,
+ EventTriggered,
+ Kill,
+ }
+ let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
+ (&self.control_queue_evt, Token::ControlQAvailable),
+ (&self.event_queue_evt, Token::EventQAvailable),
+ (&event_notifier, Token::EventTriggered),
+ (&kill_evt, Token::Kill),
+ ])
+ .map_err(SoundError::WaitCtx)?;
+
+ if let Some(resample_evt) = self.interrupt.get_resample_evt() {
+ wait_ctx
+ .add(resample_evt, Token::InterruptResample)
+ .map_err(SoundError::WaitCtx)?;
+ }
+ 'wait: loop {
+ let wait_events = wait_ctx.wait().map_err(SoundError::WaitCtx)?;
+
+ for wait_evt in wait_events.iter().filter(|e| e.is_readable) {
+ match wait_evt.token {
+ Token::ControlQAvailable => {
+ self.control_queue_evt
+ .read()
+ .map_err(SoundError::QueueEvt)?;
+ self.process_controlq_buffers()?;
+ }
+ Token::EventQAvailable => {
+ // Just read from the event object to make sure the producer of such events
+ // never blocks. The buffers will only be used when actual virtio-snd
+ // events are triggered.
+ self.event_queue_evt.read().map_err(SoundError::QueueEvt)?;
+ }
+ Token::EventTriggered => {
+ event_notifier.read().map_err(SoundError::QueueEvt)?;
+ self.process_event_triggered()?;
+ }
+ Token::InterruptResample => {
+ self.interrupt.interrupt_resample();
+ }
+ Token::Kill => {
+ let _ = kill_evt.read();
+ break 'wait;
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn stop_io_thread(&mut self) {
+ if let Err(e) = self.io_kill.write(1) {
+ error!(
+ "virtio-snd: Failed to send Break msg to stream thread: {}",
+ e
+ );
+ }
+ if let Some(th) = self.io_thread.take() {
+ match th.join() {
+ Err(e) => {
+ error!("virtio-snd: Panic detected on stream thread: {:?}", e);
+ }
+ Ok(r) => {
+ if let Err(e) = r {
+ error!("virtio-snd: IO thread exited with and error: {}", e);
+ }
+ }
+ }
+ }
+ }
+
+ // Pops and handles all available ontrol queue buffers. Logs minor errors, but returns an
+ // Err if it encounters an unrecoverable error.
+ fn process_controlq_buffers(&mut self) -> Result<()> {
+ while let Some(avail_desc) = lock_pop_unlock(&self.control_queue, &self.guest_memory) {
+ let mut reader = Reader::new(self.guest_memory.clone(), avail_desc.clone())
+ .map_err(SoundError::Descriptor)?;
+ let available_bytes = reader.available_bytes();
+ if available_bytes < std::mem::size_of::<virtio_snd_hdr>() {
+ error!(
+ "virtio-snd: Message received on control queue is too small: {}",
+ available_bytes
+ );
+ return reply_control_op_status(
+ VIRTIO_SND_S_BAD_MSG,
+ avail_desc,
+ &self.guest_memory,
+ &self.control_queue,
+ self.interrupt.deref(),
+ );
+ }
+ let mut read_buf = vec![0u8; available_bytes];
+ reader
+ .read_exact(&mut read_buf)
+ .map_err(SoundError::QueueIO)?;
+ let mut code: Le32 = Default::default();
+ // need to copy because the buffer may not be properly aligned
+ code.as_mut_slice()
+ .copy_from_slice(&read_buf[..std::mem::size_of::<Le32>()]);
+ let request_type = code.to_native();
+ match request_type {
+ VIRTIO_SND_R_JACK_INFO => {
+ let (code, info_vec) = {
+ match self.parse_info_query(&read_buf) {
+ None => (VIRTIO_SND_S_BAD_MSG, Vec::new()),
+ Some((start_id, count)) => {
+ let end_id = start_id.saturating_add(count);
+ if end_id > self.vios_client.num_jacks() {
+ error!(
+ "virtio-snd: Requested info on invalid jacks ids: {}..{}",
+ start_id,
+ end_id - 1
+ );
+ (VIRTIO_SND_S_NOT_SUPP, Vec::new())
+ } else {
+ (
+ VIRTIO_SND_S_OK,
+ // Safe to unwrap because we just ensured all the ids are valid
+ (start_id..end_id)
+ .map(|id| self.vios_client.jack_info(id).unwrap())
+ .collect(),
+ )
+ }
+ }
+ }
+ };
+ self.send_info_reply(avail_desc, code, info_vec)?;
+ }
+ VIRTIO_SND_R_JACK_REMAP => {
+ let code = if read_buf.len() != std::mem::size_of::<virtio_snd_jack_remap>() {
+ error!(
+ "virtio-snd: The driver sent the wrong number bytes for a jack_remap struct: {}",
+ read_buf.len()
+ );
+ VIRTIO_SND_S_BAD_MSG
+ } else {
+ let mut request: virtio_snd_jack_remap = Default::default();
+ request.as_mut_slice().copy_from_slice(&read_buf);
+ let jack_id = request.hdr.jack_id.to_native();
+ let association = request.association.to_native();
+ let sequence = request.sequence.to_native();
+ if let Err(e) = self.vios_client.remap_jack(jack_id, association, sequence)
+ {
+ error!("virtio-snd: Failed to remap jack: {}", e);
+ vios_error_to_status_code(e)
+ } else {
+ VIRTIO_SND_S_OK
+ }
+ };
+ let desc_index = avail_desc.index;
+ let mut writer = Writer::new(self.guest_memory.clone(), avail_desc)
+ .map_err(SoundError::Descriptor)?;
+ writer
+ .write_obj(virtio_snd_hdr {
+ code: Le32::from(code),
+ })
+ .map_err(SoundError::QueueIO)?;
+ {
+ let mut queue_lock = self.control_queue.lock();
+ queue_lock.add_used(
+ &self.guest_memory,
+ desc_index,
+ writer.bytes_written() as u32,
+ );
+ queue_lock.trigger_interrupt(&self.guest_memory, self.interrupt.deref());
+ }
+ }
+ VIRTIO_SND_R_CHMAP_INFO => {
+ let (code, info_vec) = {
+ match self.parse_info_query(&read_buf) {
+ None => (VIRTIO_SND_S_BAD_MSG, Vec::new()),
+ Some((start_id, count)) => {
+ let end_id = start_id.saturating_add(count);
+ if end_id > self.vios_client.num_chmaps() {
+ error!(
+ "virtio-snd: Requested info on invalid chmaps ids: {}..{}",
+ start_id,
+ end_id - 1
+ );
+ (VIRTIO_SND_S_NOT_SUPP, Vec::new())
+ } else {
+ (
+ VIRTIO_SND_S_OK,
+ // Safe to unwrap because we just ensured all the ids are valid
+ (start_id..end_id)
+ .map(|id| self.vios_client.chmap_info(id).unwrap())
+ .collect(),
+ )
+ }
+ }
+ }
+ };
+ self.send_info_reply(avail_desc, code, info_vec)?;
+ }
+ VIRTIO_SND_R_PCM_INFO => {
+ let (code, info_vec) = {
+ match self.parse_info_query(&read_buf) {
+ None => (VIRTIO_SND_S_BAD_MSG, Vec::new()),
+ Some((start_id, count)) => {
+ let end_id = start_id.saturating_add(count);
+ if end_id > self.vios_client.num_streams() {
+ error!(
+ "virtio-snd: Requested info on invalid stream ids: {}..{}",
+ start_id,
+ end_id - 1
+ );
+ (VIRTIO_SND_S_NOT_SUPP, Vec::new())
+ } else {
+ (
+ VIRTIO_SND_S_OK,
+ // Safe to unwrap because we just ensured all the ids are valid
+ (start_id..end_id)
+ .map(|id| self.vios_client.stream_info(id).unwrap())
+ .collect(),
+ )
+ }
+ }
+ }
+ };
+ self.send_info_reply(avail_desc, code, info_vec)?;
+ }
+ VIRTIO_SND_R_PCM_SET_PARAMS => self.process_set_params(avail_desc, &read_buf)?,
+ VIRTIO_SND_R_PCM_PREPARE => {
+ self.try_parse_pcm_hdr_and_send_msg(&read_buf, StreamMsg::Prepare(avail_desc))?
+ }
+ VIRTIO_SND_R_PCM_RELEASE => {
+ self.try_parse_pcm_hdr_and_send_msg(&read_buf, StreamMsg::Release(avail_desc))?
+ }
+ VIRTIO_SND_R_PCM_START => {
+ self.try_parse_pcm_hdr_and_send_msg(&read_buf, StreamMsg::Start(avail_desc))?
+ }
+ VIRTIO_SND_R_PCM_STOP => {
+ self.try_parse_pcm_hdr_and_send_msg(&read_buf, StreamMsg::Stop(avail_desc))?
+ }
+ _ => {
+ error!(
+ "virtio-snd: Unknown control queue mesage code: {}",
+ request_type
+ );
+ reply_control_op_status(
+ VIRTIO_SND_S_NOT_SUPP,
+ avail_desc,
+ &self.guest_memory,
+ &self.control_queue,
+ self.interrupt.deref(),
+ )?;
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn process_event_triggered(&mut self) -> Result<()> {
+ while let Some(evt) = self.vios_client.pop_event() {
+ if let Some(desc) = self.event_queue.pop(&self.guest_memory) {
+ let desc_index = desc.index;
+ let mut writer =
+ Writer::new(self.guest_memory.clone(), desc).map_err(SoundError::Descriptor)?;
+ writer.write_obj(evt).map_err(SoundError::QueueIO)?;
+ self.event_queue.add_used(
+ &self.guest_memory,
+ desc_index,
+ writer.bytes_written() as u32,
+ );
+ {
+ self.event_queue
+ .trigger_interrupt(&self.guest_memory, self.interrupt.deref());
+ }
+ } else {
+ warn!("virtio-snd: Dropping event because there are no buffers in virtqueue");
+ }
+ }
+ Ok(())
+ }
+
+ fn parse_info_query(&mut self, read_buf: &[u8]) -> Option<(u32, u32)> {
+ if read_buf.len() != std::mem::size_of::<virtio_snd_query_info>() {
+ error!(
+ "virtio-snd: The driver sent the wrong number bytes for a pcm_info struct: {}",
+ read_buf.len()
+ );
+ return None;
+ }
+ let mut query: virtio_snd_query_info = Default::default();
+ query.as_mut_slice().copy_from_slice(read_buf);
+ let start_id = query.start_id.to_native();
+ let count = query.count.to_native();
+ Some((start_id, count))
+ }
+
+ // Returns Err if it encounters an unrecoverable error, Ok otherwise
+ fn process_set_params(&mut self, desc: DescriptorChain, read_buf: &[u8]) -> Result<()> {
+ if read_buf.len() != std::mem::size_of::<virtio_snd_pcm_set_params>() {
+ error!(
+ "virtio-snd: The driver sent a buffer of the wrong size for a set_params struct: {}",
+ read_buf.len()
+ );
+ return reply_control_op_status(
+ VIRTIO_SND_S_BAD_MSG,
+ desc,
+ &self.guest_memory,
+ &self.control_queue,
+ self.interrupt.deref(),
+ );
+ }
+ let mut params: virtio_snd_pcm_set_params = Default::default();
+ params.as_mut_slice().copy_from_slice(read_buf);
+ let stream_id = params.hdr.stream_id.to_native();
+ if stream_id < self.vios_client.num_streams() {
+ self.streams[stream_id as usize].send(StreamMsg::SetParams(desc, params))
+ } else {
+ error!(
+ "virtio-snd: Driver requested operation on invalid stream: {}",
+ stream_id
+ );
+ reply_control_op_status(
+ VIRTIO_SND_S_BAD_MSG,
+ desc,
+ &self.guest_memory,
+ &self.control_queue,
+ self.interrupt.deref(),
+ )
+ }
+ }
+
+ // Returns Err if it encounters an unrecoverable error, Ok otherwise
+ fn try_parse_pcm_hdr_and_send_msg(&mut self, read_buf: &[u8], msg: StreamMsg) -> Result<()> {
+ if read_buf.len() != std::mem::size_of::<virtio_snd_pcm_hdr>() {
+ error!(
+ "virtio-snd: The driver sent a buffer too small to contain a header: {}",
+ read_buf.len()
+ );
+ return reply_control_op_status(
+ VIRTIO_SND_S_BAD_MSG,
+ match msg {
+ StreamMsg::Prepare(d)
+ | StreamMsg::Start(d)
+ | StreamMsg::Stop(d)
+ | StreamMsg::Release(d) => d,
+ _ => panic!("virtio-snd: Can't handle message. This is a BUG!!"),
+ },
+ &self.guest_memory,
+ &self.control_queue,
+ self.interrupt.deref(),
+ );
+ }
+ let mut pcm_hdr: virtio_snd_pcm_hdr = Default::default();
+ pcm_hdr.as_mut_slice().copy_from_slice(read_buf);
+ let stream_id = pcm_hdr.stream_id.to_native();
+ if stream_id < self.vios_client.num_streams() {
+ self.streams[stream_id as usize].send(msg)
+ } else {
+ error!(
+ "virtio-snd: Driver requested operation on invalid stream: {}",
+ stream_id
+ );
+ reply_control_op_status(
+ VIRTIO_SND_S_BAD_MSG,
+ match msg {
+ StreamMsg::Prepare(d)
+ | StreamMsg::Start(d)
+ | StreamMsg::Stop(d)
+ | StreamMsg::Release(d) => d,
+ _ => panic!("virtio-snd: Can't handle message. This is a BUG!!"),
+ },
+ &self.guest_memory,
+ &self.control_queue,
+ self.interrupt.deref(),
+ )
+ }
+ }
+
+ fn send_info_reply<T: DataInit>(
+ &mut self,
+ desc: DescriptorChain,
+ code: u32,
+ info_vec: Vec<T>,
+ ) -> Result<()> {
+ let desc_index = desc.index;
+ let mut writer =
+ Writer::new(self.guest_memory.clone(), desc).map_err(SoundError::Descriptor)?;
+ writer
+ .write_obj(virtio_snd_hdr {
+ code: Le32::from(code),
+ })
+ .map_err(SoundError::QueueIO)?;
+ for info in info_vec {
+ writer.write_obj(info).map_err(SoundError::QueueIO)?;
+ }
+ {
+ let mut queue_lock = self.control_queue.lock();
+ queue_lock.add_used(
+ &self.guest_memory,
+ desc_index,
+ writer.bytes_written() as u32,
+ );
+ queue_lock.trigger_interrupt(&self.guest_memory, self.interrupt.deref());
+ }
+ Ok(())
+ }
+}
+
+impl Drop for Worker {
+ fn drop(&mut self) {
+ self.stop_io_thread();
+ }
+}
+
+fn io_loop(
+ interrupt: Arc<Interrupt>,
+ guest_memory: GuestMemory,
+ tx_queue: Arc<Mutex<Queue>>,
+ tx_queue_evt: Event,
+ rx_queue: Arc<Mutex<Queue>>,
+ rx_queue_evt: Event,
+ senders: Vec<Sender<StreamMsg>>,
+ kill_evt: Event,
+) -> Result<()> {
+ #[derive(PollToken)]
+ enum Token {
+ TxQAvailable,
+ RxQAvailable,
+ Kill,
+ }
+ let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
+ (&tx_queue_evt, Token::TxQAvailable),
+ (&rx_queue_evt, Token::RxQAvailable),
+ (&kill_evt, Token::Kill),
+ ])
+ .map_err(SoundError::WaitCtx)?;
+
+ 'wait: loop {
+ let wait_events = wait_ctx.wait().map_err(SoundError::WaitCtx)?;
+ for wait_evt in wait_events.iter().filter(|e| e.is_readable) {
+ let queue = match wait_evt.token {
+ Token::TxQAvailable => {
+ tx_queue_evt.read().map_err(SoundError::QueueEvt)?;
+ &tx_queue
+ }
+ Token::RxQAvailable => {
+ rx_queue_evt.read().map_err(SoundError::QueueEvt)?;
+ &rx_queue
+ }
+ Token::Kill => {
+ let _ = kill_evt.read();
+ break 'wait;
+ }
+ };
+ while let Some(avail_desc) = lock_pop_unlock(queue, &guest_memory) {
+ let mut reader = Reader::new(guest_memory.clone(), avail_desc.clone())
+ .map_err(SoundError::Descriptor)?;
+ let xfer: virtio_snd_pcm_xfer = reader.read_obj().map_err(SoundError::QueueIO)?;
+ let stream_id = xfer.stream_id.to_native();
+ if stream_id as usize >= senders.len() {
+ error!(
+ "virtio-snd: Driver sent buffer for invalid stream: {}",
+ stream_id
+ );
+ reply_pcm_buffer_status(
+ VIRTIO_SND_S_IO_ERR,
+ 0,
+ avail_desc,
+ &guest_memory,
+ queue,
+ interrupt.deref(),
+ )?;
+ } else {
+ StreamProxy::send_msg(
+ &senders[stream_id as usize],
+ StreamMsg::Buffer(avail_desc),
+ )?;
+ }
+ }
+ }
+ }
+ Ok(())
+}
+
+// If queue.lock().pop() is used directly in the condition of a 'while' loop the lock is held over
+// the entire loop block. Encapsulating it in this fuction guarantees that the lock is dropped
+// immediately after pop() is called, which allows the code to remain somewhat simpler.
+fn lock_pop_unlock(
+ queue: &Arc<Mutex<Queue>>,
+ guest_memory: &GuestMemory,
+) -> Option<DescriptorChain> {
+ queue.lock().pop(guest_memory)
+}
diff --git a/devices/src/virtio/tpm.rs b/devices/src/virtio/tpm.rs
index e9e07c90a..a3b4ffec7 100644
--- a/devices/src/virtio/tpm.rs
+++ b/devices/src/virtio/tpm.rs
@@ -2,15 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::env;
-use std::fmt::{self, Display};
-use std::fs;
use std::io::{self, Read, Write};
use std::ops::BitOrAssign;
-use std::path::PathBuf;
+use std::sync::Arc;
use std::thread;
use base::{error, Event, PollToken, RawDescriptor, WaitContext};
+use remain::sorted;
+use sync::Mutex;
+use thiserror::Error;
use vm_memory::GuestMemory;
use super::{
@@ -35,17 +35,17 @@ struct Worker {
mem: GuestMemory,
queue_evt: Event,
kill_evt: Event,
- device: Device,
+ backend: Arc<Mutex<dyn TpmBackend>>,
}
-struct Device {
- simulator: tpm2::Simulator,
+pub trait TpmBackend: Send {
+ fn execute_command<'a>(&'a mut self, command: &[u8]) -> &'a [u8];
}
-impl Device {
- fn perform_work(&mut self, mem: &GuestMemory, desc: DescriptorChain) -> Result<u32> {
- let mut reader = Reader::new(mem.clone(), desc.clone()).map_err(Error::Descriptor)?;
- let mut writer = Writer::new(mem.clone(), desc).map_err(Error::Descriptor)?;
+impl Worker {
+ fn perform_work(&mut self, desc: DescriptorChain) -> Result<u32> {
+ let mut reader = Reader::new(self.mem.clone(), desc.clone()).map_err(Error::Descriptor)?;
+ let mut writer = Writer::new(self.mem.clone(), desc).map_err(Error::Descriptor)?;
let available_bytes = reader.available_bytes();
if available_bytes > TPM_BUFSIZE {
@@ -57,7 +57,9 @@ impl Device {
let mut command = vec![0u8; available_bytes];
reader.read_exact(&mut command).map_err(Error::Read)?;
- let response = self.simulator.execute_command(&command);
+ let mut backend = self.backend.lock();
+
+ let response = backend.execute_command(&command);
if response.len() > TPM_BUFSIZE {
return Err(Error::ResponseTooLong {
@@ -73,31 +75,29 @@ impl Device {
});
}
- writer.write_all(&response).map_err(Error::Write)?;
+ writer.write_all(response).map_err(Error::Write)?;
Ok(writer.bytes_written() as u32)
}
-}
-impl Worker {
fn process_queue(&mut self) -> NeedsInterrupt {
- let avail_desc = match self.queue.pop(&self.mem) {
- Some(avail_desc) => avail_desc,
- None => return NeedsInterrupt::No,
- };
-
- let index = avail_desc.index;
+ let mut needs_interrupt = NeedsInterrupt::No;
+ while let Some(avail_desc) = self.queue.pop(&self.mem) {
+ let index = avail_desc.index;
+
+ let len = match self.perform_work(avail_desc) {
+ Ok(len) => len,
+ Err(err) => {
+ error!("{}", err);
+ 0
+ }
+ };
- let len = match self.device.perform_work(&self.mem, avail_desc) {
- Ok(len) => len,
- Err(err) => {
- error!("{}", err);
- 0
- }
- };
+ self.queue.add_used(&self.mem, index, len);
+ needs_interrupt = NeedsInterrupt::Yes;
+ }
- self.queue.add_used(&self.mem, index, len);
- NeedsInterrupt::Yes
+ needs_interrupt
}
fn run(mut self) {
@@ -154,7 +154,7 @@ impl Worker {
}
}
if needs_interrupt == NeedsInterrupt::Yes {
- self.interrupt.signal_used_queue(self.queue.vector);
+ self.queue.trigger_interrupt(&self.mem, &self.interrupt);
}
}
}
@@ -162,15 +162,15 @@ impl Worker {
/// Virtio vTPM device.
pub struct Tpm {
- storage: PathBuf,
+ backend: Arc<Mutex<dyn TpmBackend>>,
kill_evt: Option<Event>,
worker_thread: Option<thread::JoinHandle<()>>,
}
impl Tpm {
- pub fn new(storage: PathBuf) -> Tpm {
+ pub fn new(backend: Arc<Mutex<dyn TpmBackend>>) -> Tpm {
Tpm {
- storage,
+ backend,
kill_evt: None,
worker_thread: None,
}
@@ -215,15 +215,7 @@ impl VirtioDevice for Tpm {
let queue = queues.remove(0);
let queue_evt = queue_evts.remove(0);
- if let Err(err) = fs::create_dir_all(&self.storage) {
- error!("vtpm failed to create directory for simulator: {}", err);
- return;
- }
- if let Err(err) = env::set_current_dir(&self.storage) {
- error!("vtpm failed to change into simulator directory: {}", err);
- return;
- }
- let simulator = tpm2::Simulator::singleton_in_current_directory();
+ let backend = self.backend.clone();
let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
Ok(v) => v,
@@ -240,7 +232,7 @@ impl VirtioDevice for Tpm {
mem,
queue_evt,
kill_evt,
- device: Device { simulator },
+ backend,
};
let worker_result = thread::Builder::new()
@@ -274,38 +266,22 @@ impl BitOrAssign for NeedsInterrupt {
type Result<T> = std::result::Result<T, Error>;
+#[sorted]
+#[derive(Error, Debug)]
enum Error {
+ #[error("vtpm response buffer is too small: {size} < {required} bytes")]
+ BufferTooSmall { size: usize, required: usize },
+ #[error("vtpm command is too long: {size} > {} bytes", TPM_BUFSIZE)]
CommandTooLong { size: usize },
+ #[error("virtio descriptor error: {0}")]
Descriptor(DescriptorError),
+ #[error("vtpm failed to read from guest memory: {0}")]
Read(io::Error),
+ #[error(
+ "vtpm simulator generated a response that is unexpectedly long: {size} > {} bytes",
+ TPM_BUFSIZE
+ )]
ResponseTooLong { size: usize },
- BufferTooSmall { size: usize, required: usize },
+ #[error("vtpm failed to write to guest memory: {0}")]
Write(io::Error),
}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- CommandTooLong { size } => write!(
- f,
- "vtpm command is too long: {} > {} bytes",
- size, TPM_BUFSIZE
- ),
- Descriptor(e) => write!(f, "virtio descriptor error: {}", e),
- Read(e) => write!(f, "vtpm failed to read from guest memory: {}", e),
- ResponseTooLong { size } => write!(
- f,
- "vtpm simulator generated a response that is unexpectedly long: {} > {} bytes",
- size, TPM_BUFSIZE
- ),
- BufferTooSmall { size, required } => write!(
- f,
- "vtpm response buffer is too small: {} < {} bytes",
- size, required
- ),
- Write(e) => write!(f, "vtpm failed to write to guest memory: {}", e),
- }
- }
-}
diff --git a/devices/src/virtio/vhost/mod.rs b/devices/src/virtio/vhost/mod.rs
index 14bd8cad8..c76249b06 100644
--- a/devices/src/virtio/vhost/mod.rs
+++ b/devices/src/virtio/vhost/mod.rs
@@ -4,17 +4,16 @@
//! Implements vhost-based virtio devices.
-use std::fmt::{self, Display};
-
use base::{Error as SysError, TubeError};
use net_util::Error as TapError;
use remain::sorted;
+use thiserror::Error;
use vhost::Error as VhostError;
mod control_socket;
mod net;
pub mod user;
-mod vsock;
+pub mod vsock;
mod worker;
pub use self::control_socket::*;
@@ -22,100 +21,89 @@ pub use self::net::Net;
pub use self::vsock::Vsock;
#[sorted]
-#[derive(Debug)]
+#[derive(Error, Debug)]
pub enum Error {
/// Cloning kill event failed.
+ #[error("failed to clone kill event: {0}")]
CloneKillEvent(SysError),
/// Creating kill event failed.
+ #[error("failed to create kill event: {0}")]
CreateKillEvent(SysError),
/// Creating tube failed.
+ #[error("failed to create tube: {0}")]
CreateTube(TubeError),
/// Creating wait context failed.
+ #[error("failed to create poll context: {0}")]
CreateWaitContext(SysError),
/// Enabling tap interface failed.
+ #[error("failed to enable tap interface: {0}")]
TapEnable(TapError),
/// Open tap device failed.
+ #[error("failed to open tap device: {0}")]
TapOpen(TapError),
/// Setting tap IP failed.
+ #[error("failed to set tap IP: {0}")]
TapSetIp(TapError),
/// Setting tap mac address failed.
+ #[error("failed to set tap mac address: {0}")]
TapSetMacAddress(TapError),
/// Setting tap netmask failed.
+ #[error("failed to set tap netmask: {0}")]
TapSetNetmask(TapError),
/// Setting tap interface offload flags failed.
+ #[error("failed to set tap interface offload flags: {0}")]
TapSetOffload(TapError),
/// Setting vnet header size failed.
+ #[error("failed to set vnet header size: {0}")]
TapSetVnetHdrSize(TapError),
/// Get features failed.
+ #[error("failed to get features: {0}")]
VhostGetFeatures(VhostError),
/// Failed to create vhost event.
+ #[error("failed to create vhost event: {0}")]
VhostIrqCreate(SysError),
/// Failed to read vhost event.
+ #[error("failed to read vhost event: {0}")]
VhostIrqRead(SysError),
/// Net set backend failed.
+ #[error("net set backend failed: {0}")]
VhostNetSetBackend(VhostError),
/// Failed to open vhost device.
+ #[error("failed to open vhost device: {0}")]
VhostOpen(VhostError),
/// Set features failed.
+ #[error("failed to set features: {0}")]
VhostSetFeatures(VhostError),
/// Set mem table failed.
+ #[error("failed to set mem table: {0}")]
VhostSetMemTable(VhostError),
/// Set owner failed.
+ #[error("failed to set owner: {0}")]
VhostSetOwner(VhostError),
/// Set vring addr failed.
+ #[error("failed to set vring addr: {0}")]
VhostSetVringAddr(VhostError),
/// Set vring base failed.
+ #[error("failed to set vring base: {0}")]
VhostSetVringBase(VhostError),
/// Set vring call failed.
+ #[error("failed to set vring call: {0}")]
VhostSetVringCall(VhostError),
/// Set vring kick failed.
+ #[error("failed to set vring kick: {0}")]
VhostSetVringKick(VhostError),
/// Set vring num failed.
+ #[error("failed to set vring num: {0}")]
VhostSetVringNum(VhostError),
/// Failed to set CID for guest.
+ #[error("failed to set CID for guest: {0}")]
VhostVsockSetCid(VhostError),
/// Failed to start vhost-vsock driver.
+ #[error("failed to start vhost-vsock driver: {0}")]
VhostVsockStart(VhostError),
/// Error while waiting for events.
+ #[error("failed waiting for events: {0}")]
WaitError(SysError),
}
pub type Result<T> = std::result::Result<T, Error>;
-
-impl Display for Error {
- #[remain::check]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- #[sorted]
- match self {
- CloneKillEvent(e) => write!(f, "failed to clone kill event: {}", e),
- CreateKillEvent(e) => write!(f, "failed to create kill event: {}", e),
- CreateTube(e) => write!(f, "failed to create tube: {}", e),
- CreateWaitContext(e) => write!(f, "failed to create poll context: {}", e),
- TapEnable(e) => write!(f, "failed to enable tap interface: {}", e),
- TapOpen(e) => write!(f, "failed to open tap device: {}", e),
- TapSetIp(e) => write!(f, "failed to set tap IP: {}", e),
- TapSetMacAddress(e) => write!(f, "failed to set tap mac address: {}", e),
- TapSetNetmask(e) => write!(f, "failed to set tap netmask: {}", e),
- TapSetOffload(e) => write!(f, "failed to set tap interface offload flags: {}", e),
- TapSetVnetHdrSize(e) => write!(f, "failed to set vnet header size: {}", e),
- VhostGetFeatures(e) => write!(f, "failed to get features: {}", e),
- VhostIrqCreate(e) => write!(f, "failed to create vhost event: {}", e),
- VhostIrqRead(e) => write!(f, "failed to read vhost event: {}", e),
- VhostNetSetBackend(e) => write!(f, "net set backend failed: {}", e),
- VhostOpen(e) => write!(f, "failed to open vhost device: {}", e),
- VhostSetFeatures(e) => write!(f, "failed to set features: {}", e),
- VhostSetMemTable(e) => write!(f, "failed to set mem table: {}", e),
- VhostSetOwner(e) => write!(f, "failed to set owner: {}", e),
- VhostSetVringAddr(e) => write!(f, "failed to set vring addr: {}", e),
- VhostSetVringBase(e) => write!(f, "failed to set vring base: {}", e),
- VhostSetVringCall(e) => write!(f, "failed to set vring call: {}", e),
- VhostSetVringKick(e) => write!(f, "failed to set vring kick: {}", e),
- VhostSetVringNum(e) => write!(f, "failed to set vring num: {}", e),
- VhostVsockSetCid(e) => write!(f, "failed to set CID for guest: {}", e),
- VhostVsockStart(e) => write!(f, "failed to start vhost-vsock driver: {}", e),
- WaitError(e) => write!(f, "failed waiting for events: {}", e),
- }
- }
-}
diff --git a/devices/src/virtio/vhost/net.rs b/devices/src/virtio/vhost/net.rs
index d68febe19..9fbdde554 100644
--- a/devices/src/virtio/vhost/net.rs
+++ b/devices/src/virtio/vhost/net.rs
@@ -4,7 +4,7 @@
use std::mem;
use std::net::Ipv4Addr;
-use std::path::PathBuf;
+use std::path::Path;
use std::thread;
use net_util::{MacAddress, TapT};
@@ -45,12 +45,11 @@ where
/// Create a new virtio network device with the given IP address and
/// netmask.
pub fn new(
- vhost_net_device_path: &PathBuf,
+ vhost_net_device_path: &Path,
base_features: u64,
ip_addr: Ipv4Addr,
netmask: Ipv4Addr,
mac_addr: MacAddress,
- mem: &GuestMemory,
) -> Result<Net<T, U>> {
let kill_evt = Event::new().map_err(Error::CreateKillEvent)?;
@@ -72,7 +71,7 @@ where
.map_err(Error::TapSetVnetHdrSize)?;
tap.enable().map_err(Error::TapEnable)?;
- let vhost_net_handle = U::new(vhost_net_device_path, mem).map_err(Error::VhostOpen)?;
+ let vhost_net_handle = U::new(vhost_net_device_path).map_err(Error::VhostOpen)?;
let avail_features = base_features
| 1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM
@@ -190,7 +189,7 @@ where
fn activate(
&mut self,
- _: GuestMemory,
+ mem: GuestMemory,
interrupt: Interrupt,
queues: Vec<Queue>,
queue_evts: Vec<Event>,
@@ -210,26 +209,30 @@ where
} else {
None
};
+ let mut worker = Worker::new(
+ queues,
+ vhost_net_handle,
+ vhost_interrupt,
+ interrupt,
+ acked_features,
+ kill_evt,
+ socket,
+ );
+ let activate_vqs = |handle: &U| -> Result<()> {
+ for idx in 0..NUM_QUEUES {
+ handle
+ .set_backend(idx, Some(&tap))
+ .map_err(Error::VhostNetSetBackend)?;
+ }
+ Ok(())
+ };
+ let result = worker.init(mem, queue_evts, QUEUE_SIZES, activate_vqs);
+ if let Err(e) = result {
+ error!("net worker thread exited with error: {}", e);
+ }
let worker_result = thread::Builder::new()
.name("vhost_net".to_string())
.spawn(move || {
- let mut worker = Worker::new(
- queues,
- vhost_net_handle,
- vhost_interrupt,
- interrupt,
- acked_features,
- kill_evt,
- socket,
- );
- let activate_vqs = |handle: &U| -> Result<()> {
- for idx in 0..NUM_QUEUES {
- handle
- .set_backend(idx, Some(&tap))
- .map_err(Error::VhostNetSetBackend)?;
- }
- Ok(())
- };
let cleanup_vqs = |handle: &U| -> Result<()> {
for idx in 0..NUM_QUEUES {
handle
@@ -238,8 +241,7 @@ where
}
Ok(())
};
- let result =
- worker.run(queue_evts, QUEUE_SIZES, activate_vqs, cleanup_vqs);
+ let result = worker.run(cleanup_vqs);
if let Err(e) = result {
error!("net worker thread exited with error: {}", e);
}
@@ -353,8 +355,10 @@ pub mod tests {
use super::*;
use crate::virtio::base_features;
use crate::virtio::VIRTIO_MSI_NO_VECTOR;
- use crate::ProtectionType;
+ use crate::IrqLevelEvent;
+ use hypervisor::ProtectionType;
use net_util::fakes::FakeTap;
+ use std::path::PathBuf;
use std::result;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
@@ -368,7 +372,6 @@ pub mod tests {
}
fn create_net_common() -> Net<FakeTap, FakeNet<FakeTap>> {
- let guest_memory = create_guest_memory().unwrap();
let features = base_features(ProtectionType::Unprotected);
Net::<FakeTap, FakeNet<FakeTap>>::new(
&PathBuf::from(""),
@@ -376,7 +379,6 @@ pub mod tests {
Ipv4Addr::new(127, 0, 0, 1),
Ipv4Addr::new(255, 255, 255, 0),
"de:21:e8:47:6b:6a".parse().unwrap(),
- &guest_memory,
)
.unwrap()
}
@@ -416,8 +418,7 @@ pub mod tests {
guest_memory,
Interrupt::new(
Arc::new(AtomicUsize::new(0)),
- Event::new().unwrap(),
- Event::new().unwrap(),
+ IrqLevelEvent::new().unwrap(),
None,
VIRTIO_MSI_NO_VECTOR,
),
diff --git a/devices/src/virtio/vhost/user/README.md b/devices/src/virtio/vhost/user/README.md
new file mode 100644
index 000000000..e8bc78d4b
--- /dev/null
+++ b/devices/src/virtio/vhost/user/README.md
@@ -0,0 +1,30 @@
+# Vhost-user devices
+
+This directory contains the implementation of [vhost-user] devices and [virtio vhost-user] devices.
+
+## Code Locations
+
+- [`vmm`](./vmm/) - Implements vhost-user vmm device; i.e. vhost-user master.
+- [`device`](./device/) - Implements vhost-user device backend; i.e. vhost-user slave.
+- [`proxy.rs`](./proxy.rs) - Implements [virtio vhost-user] device, which works like a proxy
+ forwarding vhost-user messages to the guest via virtqueues.
+
+## Usage
+
+### Vhost-user
+
+First, start a vhost-user device with the `crosvm device` command. Here we use the block device as
+an example, but the basic usage is same for all of devices.
+
+```bash
+$ crosvm device block --socket /path/to/socket --file /path/to/block.img
+```
+
+Then start a VM with a vhost-user block device by specifying the same socket path .
+
+```bash
+$ crosvm run -r rootfs.img --vhost-user-blk /path/to/socket <crosvm arguments>
+```
+
+[vhost-user]: https://qemu.readthedocs.io/en/latest/interop/vhost-user.html
+[virtio vhost-user]: https://wiki.qemu.org/Features/VirtioVhostUser
diff --git a/devices/src/virtio/vhost/user/device/block.rs b/devices/src/virtio/vhost/user/device/block.rs
new file mode 100644
index 000000000..262e65610
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/block.rs
@@ -0,0 +1,303 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::RefCell;
+use std::fs::OpenOptions;
+use std::rc::Rc;
+use std::sync::{atomic::AtomicU64, atomic::Ordering, Arc};
+
+use anyhow::{anyhow, bail, Context};
+use argh::FromArgs;
+use futures::future::{AbortHandle, Abortable};
+use sync::Mutex;
+use vmm_vhost::message::*;
+
+use base::{warn, Event, Timer};
+use cros_async::{sync::Mutex as AsyncMutex, EventAsync, Executor, TimerAsync};
+use data_model::DataInit;
+use disk::create_async_disk_file;
+use hypervisor::ProtectionType;
+use vm_memory::GuestMemory;
+
+use crate::virtio::block::asynchronous::{flush_disk, handle_queue};
+use crate::virtio::block::*;
+use crate::virtio::vhost::user::device::{
+ handler::{DeviceRequestHandler, Doorbell, VhostUserBackend},
+ vvu::pci::VvuPciDevice,
+};
+use crate::virtio::{self, base_features, block::sys::*, copy_config};
+
+const QUEUE_SIZE: u16 = 256;
+const NUM_QUEUES: u16 = 16;
+
+pub(crate) struct BlockBackend {
+ ex: Executor,
+ disk_state: Rc<AsyncMutex<DiskState>>,
+ disk_size: Arc<AtomicU64>,
+ block_size: u32,
+ seg_max: u32,
+ avail_features: u64,
+ acked_features: u64,
+ acked_protocol_features: VhostUserProtocolFeatures,
+ flush_timer: Rc<RefCell<TimerAsync>>,
+ flush_timer_armed: Rc<RefCell<bool>>,
+ workers: [Option<AbortHandle>; Self::MAX_QUEUE_NUM],
+}
+
+impl BlockBackend {
+ /// Creates a new block backend.
+ ///
+ /// * `ex`: executor used to run this device task.
+ /// * `filename`: Name of the disk image file.
+ /// * `options`: Vector of file options.
+ /// - `read-only`
+ pub(crate) fn new(ex: &Executor, filename: &str, options: Vec<&str>) -> anyhow::Result<Self> {
+ let read_only = options.contains(&"read-only");
+ let sparse = false;
+ let block_size = 512;
+ let f = OpenOptions::new()
+ .read(true)
+ .write(!read_only)
+ .create(false)
+ .open(filename)
+ .context("Failed to open disk file")?;
+ let disk_image = create_async_disk_file(f).context("Failed to create async file")?;
+
+ let base_features = base_features(ProtectionType::Unprotected);
+
+ if block_size % SECTOR_SIZE as u32 != 0 {
+ bail!(
+ "Block size {} is not a multiple of {}.",
+ block_size,
+ SECTOR_SIZE,
+ );
+ }
+ let disk_size = disk_image.get_len()?;
+ if disk_size % block_size as u64 != 0 {
+ warn!(
+ "Disk size {} is not a multiple of block size {}; \
+ the remainder will not be visible to the guest.",
+ disk_size, block_size,
+ );
+ }
+
+ let avail_features = build_avail_features(base_features, read_only, sparse, true)
+ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+
+ let seg_max = get_seg_max(QUEUE_SIZE);
+
+ let async_image = disk_image.to_async_disk(ex)?;
+
+ let disk_size = Arc::new(AtomicU64::new(disk_size));
+
+ let disk_state = Rc::new(AsyncMutex::new(DiskState::new(
+ async_image,
+ Arc::clone(&disk_size),
+ read_only,
+ sparse,
+ None, // id: Option<BlockId>,
+ )));
+
+ let timer = Timer::new().context("Failed to create a timer")?;
+ let flush_timer_write = Rc::new(RefCell::new(
+ TimerAsync::new(
+ // Call try_clone() to share the same underlying FD with the `flush_disk` task.
+ timer.0.try_clone().context("Failed to clone flush_timer")?,
+ ex,
+ )
+ .context("Failed to create an async timer")?,
+ ));
+ // Create a separate TimerAsync with the same backing kernel timer. This allows the
+ // `flush_disk` task to borrow its copy waiting for events while the queue handlers can
+ // still borrow their copy momentarily to set timeouts.
+ // Call try_clone() to share the same underlying FD with the `flush_disk` task.
+ let flush_timer_read = timer
+ .0
+ .try_clone()
+ .context("Failed to clone flush_timer")
+ .and_then(|t| TimerAsync::new(t, ex).context("Failed to create an async timer"))?;
+ let flush_timer_armed = Rc::new(RefCell::new(false));
+ ex.spawn_local(flush_disk(
+ Rc::clone(&disk_state),
+ flush_timer_read,
+ Rc::clone(&flush_timer_armed),
+ ))
+ .detach();
+
+ Ok(BlockBackend {
+ ex: ex.clone(),
+ disk_state,
+ disk_size,
+ block_size,
+ seg_max,
+ avail_features,
+ acked_features: 0,
+ acked_protocol_features: VhostUserProtocolFeatures::empty(),
+ flush_timer: flush_timer_write,
+ flush_timer_armed,
+ workers: Default::default(),
+ })
+ }
+}
+
+impl VhostUserBackend for BlockBackend {
+ const MAX_QUEUE_NUM: usize = NUM_QUEUES as usize;
+ const MAX_VRING_LEN: u16 = QUEUE_SIZE;
+
+ type Error = anyhow::Error;
+
+ fn features(&self) -> u64 {
+ self.avail_features
+ }
+
+ fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
+ let unrequested_features = value & !self.avail_features;
+ if unrequested_features != 0 {
+ bail!("invalid features are given: {:#x}", unrequested_features);
+ }
+
+ self.acked_features |= value;
+
+ Ok(())
+ }
+
+ fn acked_features(&self) -> u64 {
+ self.acked_features
+ }
+
+ fn protocol_features(&self) -> VhostUserProtocolFeatures {
+ VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::MQ
+ }
+
+ fn ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()> {
+ let features = VhostUserProtocolFeatures::from_bits(features)
+ .ok_or_else(|| anyhow!("invalid protocol features are given: {:#x}", features))?;
+ let supported = self.protocol_features();
+ self.acked_protocol_features = features & supported;
+ Ok(())
+ }
+
+ fn acked_protocol_features(&self) -> u64 {
+ self.acked_protocol_features.bits()
+ }
+
+ fn read_config(&self, offset: u64, data: &mut [u8]) {
+ let config_space = {
+ let disk_size = self.disk_size.load(Ordering::Relaxed);
+ build_config_space(disk_size, self.seg_max, self.block_size, NUM_QUEUES)
+ };
+ copy_config(data, 0, config_space.as_slice(), offset);
+ }
+
+ fn reset(&mut self) {
+ panic!("Unsupported call to reset");
+ }
+
+ fn start_queue(
+ &mut self,
+ idx: usize,
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: Event,
+ ) -> anyhow::Result<()> {
+ if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
+ warn!("Starting new queue handler without stopping old handler");
+ handle.abort();
+ }
+
+ // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
+ queue.ack_features(self.acked_features);
+
+ let kick_evt = EventAsync::new(kick_evt.0, &self.ex)
+ .context("failed to create EventAsync for kick_evt")?;
+ let (handle, registration) = AbortHandle::new_pair();
+
+ let disk_state = Rc::clone(&self.disk_state);
+ let timer = Rc::clone(&self.flush_timer);
+ let timer_armed = Rc::clone(&self.flush_timer_armed);
+ self.ex
+ .spawn_local(Abortable::new(
+ handle_queue(
+ self.ex.clone(),
+ mem,
+ disk_state,
+ Rc::new(RefCell::new(queue)),
+ kick_evt,
+ doorbell,
+ timer,
+ timer_armed,
+ ),
+ registration,
+ ))
+ .detach();
+
+ self.workers[idx] = Some(handle);
+ Ok(())
+ }
+
+ fn stop_queue(&mut self, idx: usize) {
+ if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
+ handle.abort();
+ }
+ }
+}
+
+#[derive(FromArgs)]
+#[argh(description = "")]
+struct Options {
+ #[argh(
+ option,
+ description = "path and options of the disk file.",
+ arg_name = "PATH<:read-only>"
+ )]
+ file: String,
+ #[argh(option, description = "path to a vhost-user socket", arg_name = "PATH")]
+ socket: Option<String>,
+ #[argh(
+ option,
+ description = "VFIO-PCI device name (e.g. '0000:00:07.0')",
+ arg_name = "STRING"
+ )]
+ vfio: Option<String>,
+}
+
+/// Starts a vhost-user block device.
+/// Returns an error if the given `args` is invalid or the device fails to run.
+pub fn run_block_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
+ let opts = match Options::from_args(&[program_name], args) {
+ Ok(opts) => opts,
+ Err(e) => {
+ if e.status.is_err() {
+ bail!(e.output);
+ } else {
+ println!("{}", e.output);
+ }
+ return Ok(());
+ }
+ };
+
+ if !(opts.socket.is_some() ^ opts.vfio.is_some()) {
+ bail!("Exactly one of `--socket` or `--vfio` is required");
+ }
+
+ let ex = Executor::new().context("failed to create executor")?;
+
+ let mut fileopts = opts.file.split(":").collect::<Vec<_>>();
+ let filename = fileopts.remove(0);
+
+ let block = BlockBackend::new(&ex, filename, fileopts)?;
+ let handler = DeviceRequestHandler::new(block);
+ match (opts.socket, opts.vfio) {
+ (Some(socket), None) => {
+ // run_until() returns an Result<Result<..>> which the ? operator lets us flatten.
+ ex.run_until(handler.run(socket, &ex))?
+ }
+ (None, Some(device_name)) => {
+ let device = VvuPciDevice::new(device_name.as_str(), BlockBackend::MAX_QUEUE_NUM)?;
+ ex.run_until(handler.run_vvu(device, &ex))?
+ }
+ _ => unreachable!("Must be checked above"),
+ }
+}
diff --git a/devices/src/virtio/vhost/user/device/console.rs b/devices/src/virtio/vhost/user/device/console.rs
new file mode 100644
index 000000000..50d1f11a5
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/console.rs
@@ -0,0 +1,352 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::collections::VecDeque;
+use std::io::{self, stdin};
+use std::ops::DerefMut;
+use std::path::PathBuf;
+use std::sync::Arc;
+
+use anyhow::{anyhow, bail, Context};
+use base::{error, warn, Event, FileSync, RawDescriptor, Terminal};
+use cros_async::{EventAsync, Executor};
+use data_model::DataInit;
+
+use argh::FromArgs;
+use futures::future::{AbortHandle, Abortable};
+use hypervisor::ProtectionType;
+use sync::Mutex;
+use vm_memory::GuestMemory;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+use crate::serial_device::{SerialDevice, SerialHardware, SerialParameters, SerialType};
+use crate::virtio::console::{
+ handle_input, process_transmit_queue, spawn_input_thread, virtio_console_config, ConsoleError,
+};
+use crate::virtio::vhost::user::device::handler::{
+ DeviceRequestHandler, Doorbell, VhostUserBackend,
+};
+use crate::virtio::vhost::user::device::vvu::pci::VvuPciDevice;
+use crate::virtio::{self, copy_config};
+
+async fn run_tx_queue(
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: EventAsync,
+ mut output: Box<dyn io::Write>,
+) {
+ loop {
+ if let Err(e) = kick_evt.next_val().await {
+ error!("Failed to read kick event for tx queue: {}", e);
+ break;
+ }
+ process_transmit_queue(&mem, &doorbell, &mut queue, &mut output);
+ }
+}
+
+async fn run_rx_queue(
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: EventAsync,
+ in_buffer: Arc<Mutex<VecDeque<u8>>>,
+ in_avail_evt: EventAsync,
+) {
+ loop {
+ if let Err(e) = in_avail_evt.next_val().await {
+ error!("Failed reading in_avail_evt: {}", e);
+ break;
+ }
+ match handle_input(&mem, &doorbell, in_buffer.lock().deref_mut(), &mut queue) {
+ Ok(()) => {}
+ Err(ConsoleError::RxDescriptorsExhausted) => {
+ if let Err(e) = kick_evt.next_val().await {
+ error!("Failed to read kick event for rx queue: {}", e);
+ break;
+ }
+ }
+ }
+ }
+}
+
+struct ConsoleDevice {
+ input: Option<Box<dyn io::Read + Send>>,
+ output: Option<Box<dyn io::Write + Send>>,
+ avail_features: u64,
+}
+
+impl SerialDevice for ConsoleDevice {
+ fn new(
+ protected_vm: ProtectionType,
+ _evt: Event,
+ input: Option<Box<dyn io::Read + Send>>,
+ output: Option<Box<dyn io::Write + Send>>,
+ _sync: Option<Box<dyn FileSync + Send>>,
+ _out_timestamp: bool,
+ _keep_rds: Vec<RawDescriptor>,
+ ) -> ConsoleDevice {
+ let avail_features =
+ virtio::base_features(protected_vm) | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ ConsoleDevice {
+ input,
+ output,
+ avail_features,
+ }
+ }
+}
+
+struct ConsoleBackend {
+ ex: Executor,
+ device: ConsoleDevice,
+ acked_features: u64,
+ acked_protocol_features: VhostUserProtocolFeatures,
+ workers: [Option<AbortHandle>; Self::MAX_QUEUE_NUM],
+}
+
+impl ConsoleBackend {
+ fn new(ex: &Executor, device: ConsoleDevice) -> Self {
+ Self {
+ ex: ex.clone(),
+ device,
+ acked_features: 0,
+ acked_protocol_features: VhostUserProtocolFeatures::empty(),
+ workers: Default::default(),
+ }
+ }
+}
+
+impl VhostUserBackend for ConsoleBackend {
+ const MAX_QUEUE_NUM: usize = 2; /* transmit and receive queues */
+ const MAX_VRING_LEN: u16 = 256;
+
+ type Error = anyhow::Error;
+
+ fn features(&self) -> u64 {
+ self.device.avail_features
+ }
+
+ fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
+ let unrequested_features = value & !self.device.avail_features;
+ if unrequested_features != 0 {
+ bail!("invalid features are given: {:#x}", unrequested_features);
+ }
+
+ self.acked_features |= value;
+
+ Ok(())
+ }
+
+ fn acked_features(&self) -> u64 {
+ self.acked_features
+ }
+
+ fn protocol_features(&self) -> VhostUserProtocolFeatures {
+ VhostUserProtocolFeatures::CONFIG
+ }
+
+ fn ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()> {
+ let features = VhostUserProtocolFeatures::from_bits(features)
+ .ok_or_else(|| anyhow!("invalid protocol features are given: {:#x}", features))?;
+ let supported = self.protocol_features();
+ self.acked_protocol_features = features & supported;
+ Ok(())
+ }
+
+ fn acked_protocol_features(&self) -> u64 {
+ self.acked_protocol_features.bits()
+ }
+
+ fn read_config(&self, offset: u64, data: &mut [u8]) {
+ let config = virtio_console_config {
+ max_nr_ports: 1.into(),
+ ..Default::default()
+ };
+ copy_config(data, 0, config.as_slice(), offset);
+ }
+
+ fn reset(&mut self) {
+ for handle in self.workers.iter_mut().filter_map(Option::take) {
+ handle.abort();
+ }
+ }
+
+ fn start_queue(
+ &mut self,
+ idx: usize,
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: Event,
+ ) -> anyhow::Result<()> {
+ if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
+ warn!("Starting new queue handler without stopping old handler");
+ handle.abort();
+ }
+
+ // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
+ queue.ack_features(self.acked_features);
+
+ let kick_evt = EventAsync::new(kick_evt.0, &self.ex)
+ .context("Failed to create EventAsync for kick_evt")?;
+ let (handle, registration) = AbortHandle::new_pair();
+ match idx {
+ // ReceiveQueue
+ 0 => {
+ // See explanation in devices/src/virtio/console.rs
+ // We need a multithreaded input polling because io::Read only provides
+ // a blocking interface which we cannot use in an async function.
+ let in_avail_evt = match Event::new() {
+ Ok(evt) => evt,
+ Err(e) => {
+ bail!("Failed creating Event: {}", e);
+ }
+ };
+
+ let input_unpacked = self
+ .device
+ .input
+ .take()
+ .ok_or_else(|| anyhow!("input source unavailable"))?;
+ let in_buffer = spawn_input_thread(input_unpacked, &in_avail_evt)
+ .take()
+ .ok_or_else(|| anyhow!("input channel unavailable"))?;
+
+ // Create the async 'in' event so we can await on it.
+ let in_avail_async_evt = EventAsync::new(in_avail_evt.0, &self.ex)
+ .context("Failed to create EventAsync for in_avail_evt")?;
+
+ self.ex
+ .spawn_local(Abortable::new(
+ run_rx_queue(
+ queue,
+ mem,
+ doorbell,
+ kick_evt,
+ in_buffer,
+ in_avail_async_evt,
+ ),
+ registration,
+ ))
+ .detach();
+ }
+ // TransmitQueue
+ 1 => {
+ // Take ownership of output writer.
+ // Safe because output should always be initialized to something
+ let output_unwrapped: Box<dyn io::Write + Send> = self
+ .device
+ .output
+ .take()
+ .ok_or_else(|| anyhow!("no output available"))?;
+ self.ex
+ .spawn_local(Abortable::new(
+ run_tx_queue(queue, mem, doorbell, kick_evt, output_unwrapped),
+ registration,
+ ))
+ .detach();
+ }
+ _ => bail!("attempted to start unknown queue: {}", idx),
+ }
+
+ self.workers[idx] = Some(handle);
+ Ok(())
+ }
+
+ fn stop_queue(&mut self, idx: usize) {
+ if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
+ handle.abort();
+ }
+ }
+}
+
+#[derive(FromArgs)]
+#[argh(description = "")]
+struct Options {
+ #[argh(option, description = "path to a vhost-user socket", arg_name = "PATH")]
+ socket: Option<String>,
+ #[argh(
+ option,
+ description = "VFIO-PCI device name (e.g. '0000:00:07.0')",
+ arg_name = "STRING"
+ )]
+ vfio: Option<String>,
+ #[argh(option, description = "path to a file", arg_name = "OUTFILE")]
+ output_file: Option<PathBuf>,
+ #[argh(option, description = "path to a file", arg_name = "INFILE")]
+ input_file: Option<PathBuf>,
+}
+
+/// Starts a vhost-user console device.
+/// Returns an error if the given `args` is invalid or the device fails to run.
+pub fn run_console_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
+ let opts = match Options::from_args(&[program_name], args) {
+ Ok(opts) => opts,
+ Err(e) => {
+ if e.status.is_err() {
+ bail!(e.output);
+ } else {
+ println!("{}", e.output);
+ }
+ return Ok(());
+ }
+ };
+
+ let type_ = match opts.output_file {
+ Some(_) => SerialType::File,
+ None => SerialType::Stdout,
+ };
+
+ let params = SerialParameters {
+ type_,
+ hardware: SerialHardware::VirtioConsole,
+ // Required only if type_ is SerialType::File or SerialType::UnixSocket
+ path: opts.output_file,
+ input: opts.input_file,
+ num: 1,
+ console: true,
+ earlycon: false,
+ // We do not support stdin-less mode
+ stdin: true,
+ out_timestamp: false,
+ };
+
+ let console = match params.create_serial_device::<ConsoleDevice>(
+ ProtectionType::Unprotected,
+ // We need to pass an event as per Serial Device API but we don't really use it anyway.
+ &Event::new()?,
+ // Same for keep_rds, we don't really use this.
+ &mut Vec::new(),
+ ) {
+ Ok(c) => c,
+ Err(e) => bail!(e),
+ };
+ let ex = Executor::new().context("Failed to create executor")?;
+ let backend = ConsoleBackend::new(&ex, console);
+ let handler = DeviceRequestHandler::new(backend);
+
+ // Set stdin() in raw mode so we can send over individual keystrokes unbuffered
+ stdin()
+ .set_raw_mode()
+ .context("Failed to set terminal raw mode")?;
+
+ let res = match (opts.socket, opts.vfio) {
+ (Some(socket), None) => {
+ // run_until() returns an Result<Result<..>> which the ? operator lets us flatten.
+ ex.run_until(handler.run(socket, &ex))?
+ }
+ (None, Some(vfio)) => {
+ let device = VvuPciDevice::new(&vfio, ConsoleBackend::MAX_QUEUE_NUM)?;
+ ex.run_until(handler.run_vvu(device, &ex))?
+ }
+ _ => Err(anyhow!("exactly one of `--socket` or `--vfio` is required")),
+ };
+
+ // Restore terminal capabilities back to what they were before
+ stdin()
+ .set_canon_mode()
+ .context("Failed to restore canonical mode for terminal")?;
+
+ res
+}
diff --git a/devices/src/virtio/vhost/user/device/cras_snd.rs b/devices/src/virtio/vhost/user/device/cras_snd.rs
new file mode 100644
index 000000000..57df22605
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/cras_snd.rs
@@ -0,0 +1,289 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::os::unix::net::UnixListener;
+use std::rc::Rc;
+use std::sync::Arc;
+
+use anyhow::{anyhow, bail, Context};
+use argh::FromArgs;
+use base::{warn, Event, UnlinkUnixListener};
+use cros_async::{sync::Mutex as AsyncMutex, EventAsync, Executor};
+use data_model::DataInit;
+use futures::channel::mpsc;
+use futures::future::{AbortHandle, Abortable};
+use hypervisor::ProtectionType;
+use once_cell::sync::OnceCell;
+use sync::Mutex;
+use vm_memory::GuestMemory;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+use crate::virtio::snd::cras_backend::{
+ async_funcs::{handle_ctrl_queue, handle_pcm_queue, send_pcm_response_worker},
+ hardcoded_snd_data, hardcoded_virtio_snd_config, Parameters, PcmResponse, SndData, StreamInfo,
+ MAX_QUEUE_NUM, MAX_VRING_LEN,
+};
+use crate::virtio::snd::layout::virtio_snd_config;
+use crate::virtio::vhost::user::device::handler::{
+ DeviceRequestHandler, Doorbell, VhostUserBackend,
+};
+use crate::virtio::{self, copy_config};
+
+static SND_EXECUTOR: OnceCell<Executor> = OnceCell::new();
+
+// Async workers:
+// 0 - ctrl
+// 1 - event
+// 2 - tx
+// 3 - rx
+const PCM_RESPONSE_WORKER_IDX_OFFSET: usize = 2;
+struct CrasSndBackend {
+ cfg: virtio_snd_config,
+ avail_features: u64,
+ acked_features: u64,
+ acked_protocol_features: VhostUserProtocolFeatures,
+ workers: [Option<AbortHandle>; MAX_QUEUE_NUM],
+ response_workers: [Option<AbortHandle>; 2], // tx and rx
+ snd_data: Rc<SndData>,
+ streams: Rc<AsyncMutex<Vec<AsyncMutex<StreamInfo<'static>>>>>,
+ params: Parameters,
+ tx_send: mpsc::UnboundedSender<PcmResponse>,
+ rx_send: mpsc::UnboundedSender<PcmResponse>,
+ tx_recv: Option<mpsc::UnboundedReceiver<PcmResponse>>,
+ rx_recv: Option<mpsc::UnboundedReceiver<PcmResponse>>,
+}
+
+impl CrasSndBackend {
+ pub fn new(params: Parameters) -> anyhow::Result<Self> {
+ let cfg = hardcoded_virtio_snd_config(&params);
+ let avail_features = virtio::base_features(ProtectionType::Unprotected)
+ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+
+ let snd_data = hardcoded_snd_data(&params);
+
+ let mut streams: Vec<AsyncMutex<StreamInfo>> = Vec::new();
+ streams.resize_with(snd_data.pcm_info_len(), Default::default);
+ let streams = Rc::new(AsyncMutex::new(streams));
+
+ let (tx_send, tx_recv) = mpsc::unbounded();
+ let (rx_send, rx_recv) = mpsc::unbounded();
+
+ Ok(CrasSndBackend {
+ cfg,
+ avail_features,
+ acked_features: 0,
+ acked_protocol_features: VhostUserProtocolFeatures::empty(),
+ workers: Default::default(),
+ response_workers: Default::default(),
+ snd_data: Rc::new(snd_data),
+ streams,
+ params,
+ tx_send,
+ rx_send,
+ tx_recv: Some(tx_recv),
+ rx_recv: Some(rx_recv),
+ })
+ }
+}
+
+impl VhostUserBackend for CrasSndBackend {
+ const MAX_QUEUE_NUM: usize = MAX_QUEUE_NUM;
+ const MAX_VRING_LEN: u16 = MAX_VRING_LEN;
+
+ type Error = anyhow::Error;
+
+ fn features(&self) -> u64 {
+ self.avail_features
+ }
+
+ fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
+ let unrequested_features = value & !self.avail_features;
+ if unrequested_features != 0 {
+ bail!("invalid features are given: {:#x}", unrequested_features);
+ }
+
+ self.acked_features |= value;
+
+ Ok(())
+ }
+
+ fn acked_features(&self) -> u64 {
+ self.acked_features
+ }
+
+ fn protocol_features(&self) -> VhostUserProtocolFeatures {
+ VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::MQ
+ }
+
+ fn ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()> {
+ let features = VhostUserProtocolFeatures::from_bits(features)
+ .ok_or_else(|| anyhow!("invalid protocol features are given: {:#x}", features))?;
+ let supported = self.protocol_features();
+ self.acked_protocol_features = features & supported;
+ Ok(())
+ }
+
+ fn acked_protocol_features(&self) -> u64 {
+ self.acked_protocol_features.bits()
+ }
+
+ fn read_config(&self, offset: u64, data: &mut [u8]) {
+ copy_config(data, 0, self.cfg.as_slice(), offset)
+ }
+
+ fn reset(&mut self) {
+ for handle in self.workers.iter_mut().filter_map(Option::take) {
+ handle.abort();
+ }
+ }
+
+ fn start_queue(
+ &mut self,
+ idx: usize,
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: Event,
+ ) -> anyhow::Result<()> {
+ if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
+ warn!("Starting new queue handler without stopping old handler");
+ handle.abort();
+ }
+
+ // Safe because the executor is initialized in main() below.
+ let ex = SND_EXECUTOR.get().expect("Executor not initialized");
+
+ // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
+ queue.ack_features(self.acked_features);
+
+ let kick_evt =
+ EventAsync::new(kick_evt.0, ex).context("failed to create EventAsync for kick_evt")?;
+ let (handle, registration) = AbortHandle::new_pair();
+ match idx {
+ 0 => {
+ // ctrl queue
+ let streams = self.streams.clone();
+ let snd_data = self.snd_data.clone();
+ let tx_send = self.tx_send.clone();
+ let rx_send = self.rx_send.clone();
+ let params = self.params.clone();
+ ex.spawn_local(Abortable::new(
+ async move {
+ handle_ctrl_queue(
+ &ex, &mem, &streams, &*snd_data, queue, kick_evt, &doorbell, tx_send,
+ rx_send, &params,
+ )
+ .await
+ },
+ registration,
+ ))
+ .detach();
+ }
+ 1 => {} // TODO(woodychow): Add event queue support
+ 2 | 3 => {
+ let (send, recv) = if idx == 2 {
+ (self.tx_send.clone(), self.tx_recv.take())
+ } else {
+ (self.rx_send.clone(), self.rx_recv.take())
+ };
+ let mut recv = recv.ok_or_else(|| anyhow!("queue restart is not supported"))?;
+ let queue = Rc::new(AsyncMutex::new(queue));
+ let queue2 = Rc::clone(&queue);
+ let mem = Rc::new(mem);
+ let mem2 = Rc::clone(&mem);
+ let streams = Rc::clone(&self.streams);
+ ex.spawn_local(Abortable::new(
+ async move { handle_pcm_queue(&*mem, &streams, send, &queue, kick_evt).await },
+ registration,
+ ))
+ .detach();
+
+ let (handle2, registration2) = AbortHandle::new_pair();
+
+ ex.spawn_local(Abortable::new(
+ async move {
+ send_pcm_response_worker(&*mem2, &queue2, &doorbell, &mut recv).await
+ },
+ registration2,
+ ))
+ .detach();
+
+ self.response_workers[idx - PCM_RESPONSE_WORKER_IDX_OFFSET] = Some(handle2);
+ }
+ _ => bail!("attempted to start unknown queue: {}", idx),
+ }
+
+ self.workers[idx] = Some(handle);
+ Ok(())
+ }
+
+ fn stop_queue(&mut self, idx: usize) {
+ if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
+ handle.abort();
+ }
+ if idx == 2 || idx == 3 {
+ if let Some(handle) = self
+ .response_workers
+ .get_mut(idx - PCM_RESPONSE_WORKER_IDX_OFFSET)
+ .and_then(Option::take)
+ {
+ handle.abort();
+ }
+ }
+ }
+}
+
+#[derive(FromArgs)]
+#[argh(description = "")]
+struct Options {
+ #[argh(option, description = "path to a socket", arg_name = "PATH")]
+ socket: String,
+ #[argh(
+ option,
+ description = "comma separated key=value pairs for setting up cras snd devices.
+Possible key values:
+capture - Enable audio capture. Default to false.
+client_type - Set specific client type for cras backend.
+num_output_streams - Set number of output PCM streams.
+num_input_streams - Set number of input PCM streams.
+Example: [capture=true,client=crosvm,socket=unified,num_output_streams=1,num_input_streams=1]",
+ arg_name = "CONFIG"
+ )]
+ config: Option<String>,
+}
+
+/// Starts a vhost-user snd device with the cras backend.
+/// Returns an error if the given `args` is invalid or the device fails to run.
+pub fn run_cras_snd_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
+ let opts = match Options::from_args(&[program_name], args) {
+ Ok(opts) => opts,
+ Err(e) => {
+ if e.status.is_err() {
+ bail!(e.output);
+ } else {
+ println!("{}", e.output);
+ }
+ return Ok(());
+ }
+ };
+ let params = opts
+ .config
+ .unwrap_or("".to_string())
+ .parse::<Parameters>()?;
+
+ let snd_device = CrasSndBackend::new(params)?;
+
+ // Create and bind unix socket
+ let listener = UnixListener::bind(opts.socket).map(UnlinkUnixListener)?;
+
+ let handler = DeviceRequestHandler::new(snd_device);
+
+ // Child, we can continue by spawning the executor and set up the device
+ let ex = Executor::new().context("Failed to create executor")?;
+
+ let _ = SND_EXECUTOR.set(ex.clone());
+
+ // run_until() returns an Result<Result<..>> which the ? operator lets us flatten.
+ ex.run_until(handler.run_with_listener(listener, &ex))?
+}
diff --git a/devices/src/virtio/vhost/user/device/fs.rs b/devices/src/virtio/vhost/user/device/fs.rs
new file mode 100644
index 000000000..8b996c2ab
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/fs.rs
@@ -0,0 +1,334 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io;
+use std::os::unix::io::AsRawFd;
+use std::os::unix::net::UnixListener;
+use std::path::{Path, PathBuf};
+use std::sync::Arc;
+
+use anyhow::{anyhow, bail, Context};
+use argh::FromArgs;
+use base::{error, get_max_open_files, warn, Event, RawDescriptor, Tube, UnlinkUnixListener};
+use cros_async::{EventAsync, Executor};
+use data_model::{DataInit, Le32};
+use fuse::Server;
+use futures::future::{AbortHandle, Abortable};
+use hypervisor::ProtectionType;
+use minijail::{self, Minijail};
+use sync::Mutex;
+use vm_memory::GuestMemory;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+use crate::virtio;
+use crate::virtio::copy_config;
+use crate::virtio::fs::passthrough::PassthroughFs;
+use crate::virtio::fs::{process_fs_queue, virtio_fs_config, FS_MAX_TAG_LEN};
+use crate::virtio::vhost::user::device::handler::{
+ DeviceRequestHandler, Doorbell, VhostUserBackend,
+};
+
+async fn handle_fs_queue(
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: EventAsync,
+ server: Arc<fuse::Server<PassthroughFs>>,
+ tube: Arc<Mutex<Tube>>,
+) {
+ // Slot is always going to be 0 because we do not support DAX
+ let slot: u32 = 0;
+
+ loop {
+ if let Err(e) = kick_evt.next_val().await {
+ error!("Failed to read kick event for fs queue: {}", e);
+ break;
+ }
+ if let Err(e) = process_fs_queue(&mem, &doorbell, &mut queue, &server, &tube, slot) {
+ error!("Process FS queue failed: {}", e);
+ break;
+ }
+ }
+}
+
+fn default_uidmap() -> String {
+ let euid = unsafe { libc::geteuid() };
+ format!("{} {} 1", euid, euid)
+}
+
+fn default_gidmap() -> String {
+ let egid = unsafe { libc::getegid() };
+ format!("{} {} 1", egid, egid)
+}
+
+fn jail_and_fork(
+ mut keep_rds: Vec<RawDescriptor>,
+ dir_path: PathBuf,
+ uid_map: Option<String>,
+ gid_map: Option<String>,
+) -> anyhow::Result<i32> {
+ // Create new minijail sandbox
+ let mut j = Minijail::new()?;
+
+ j.namespace_pids();
+ j.namespace_user();
+ j.namespace_user_disable_setgroups();
+ j.uidmap(&uid_map.unwrap_or_else(default_uidmap))?;
+ j.gidmap(&gid_map.unwrap_or_else(default_gidmap))?;
+ j.run_as_init();
+
+ j.namespace_vfs();
+ j.namespace_net();
+ j.no_new_privs();
+
+ // Only pivot_root if we are not re-using the current root directory.
+ if dir_path != Path::new("/") {
+ // It's safe to call `namespace_vfs` multiple times.
+ j.namespace_vfs();
+ j.enter_pivot_root(&dir_path)?;
+ }
+ j.set_remount_mode(libc::MS_SLAVE);
+
+ let limit = get_max_open_files().context("failed to get max open files")?;
+ j.set_rlimit(libc::RLIMIT_NOFILE as i32, limit, limit)?;
+
+ // Make sure there are no duplicates in keep_rds
+ keep_rds.dedup();
+
+ // fork on the jail here
+ let pid = unsafe { j.fork(Some(&keep_rds))? };
+
+ if pid > 0 {
+ unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGTERM) };
+ }
+
+ if pid < 0 {
+ bail!("Fork error! {}", std::io::Error::last_os_error());
+ }
+
+ Ok(pid)
+}
+
+struct FsBackend {
+ ex: Executor,
+ server: Arc<fuse::Server<PassthroughFs>>,
+ tag: [u8; FS_MAX_TAG_LEN],
+ avail_features: u64,
+ acked_features: u64,
+ acked_protocol_features: VhostUserProtocolFeatures,
+ workers: [Option<AbortHandle>; Self::MAX_QUEUE_NUM],
+ keep_rds: Vec<RawDescriptor>,
+}
+
+impl FsBackend {
+ pub fn new(ex: &Executor, tag: &str) -> anyhow::Result<Self> {
+ if tag.len() > FS_MAX_TAG_LEN {
+ bail!(
+ "fs tag is too long: {} (max supported: {})",
+ tag.len(),
+ FS_MAX_TAG_LEN
+ );
+ }
+ let mut fs_tag = [0u8; FS_MAX_TAG_LEN];
+ fs_tag[..tag.len()].copy_from_slice(tag.as_bytes());
+
+ let avail_features = virtio::base_features(ProtectionType::Unprotected)
+ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+
+ // Use default passthroughfs config
+ let fs = PassthroughFs::new(Default::default())?;
+
+ let mut keep_rds: Vec<RawDescriptor> = [0, 1, 2].to_vec();
+ keep_rds.append(&mut fs.keep_rds());
+
+ let server = Arc::new(Server::new(fs));
+
+ Ok(FsBackend {
+ ex: ex.clone(),
+ server,
+ tag: fs_tag,
+ avail_features,
+ acked_features: 0,
+ acked_protocol_features: VhostUserProtocolFeatures::empty(),
+ workers: Default::default(),
+ keep_rds,
+ })
+ }
+}
+
+impl VhostUserBackend for FsBackend {
+ const MAX_QUEUE_NUM: usize = 2; /* worker queue and high priority queue */
+ const MAX_VRING_LEN: u16 = 1024;
+
+ type Error = anyhow::Error;
+
+ fn features(&self) -> u64 {
+ self.avail_features
+ }
+
+ fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
+ let unrequested_features = value & !self.avail_features;
+ if unrequested_features != 0 {
+ bail!("invalid features are given: {:#x}", unrequested_features);
+ }
+
+ self.acked_features |= value;
+
+ Ok(())
+ }
+
+ fn acked_features(&self) -> u64 {
+ self.acked_features
+ }
+
+ fn protocol_features(&self) -> VhostUserProtocolFeatures {
+ VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::MQ
+ }
+
+ fn ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()> {
+ let features = VhostUserProtocolFeatures::from_bits(features)
+ .ok_or_else(|| anyhow!("invalid protocol features are given: {:#x}", features))?;
+ let supported = self.protocol_features();
+ self.acked_protocol_features = features & supported;
+ Ok(())
+ }
+
+ fn acked_protocol_features(&self) -> u64 {
+ self.acked_protocol_features.bits()
+ }
+
+ fn read_config(&self, offset: u64, data: &mut [u8]) {
+ let config = virtio_fs_config {
+ tag: self.tag,
+ num_request_queues: Le32::from(1),
+ };
+ copy_config(data, 0, config.as_slice(), offset);
+ }
+
+ fn reset(&mut self) {
+ for handle in self.workers.iter_mut().filter_map(Option::take) {
+ handle.abort();
+ }
+ }
+
+ fn start_queue(
+ &mut self,
+ idx: usize,
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: Event,
+ ) -> anyhow::Result<()> {
+ if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
+ warn!("Starting new queue handler without stopping old handler");
+ handle.abort();
+ }
+
+ // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
+ queue.ack_features(self.acked_features);
+
+ let kick_evt = EventAsync::new(kick_evt.0, &self.ex)
+ .context("failed to create EventAsync for kick_evt")?;
+ let (handle, registration) = AbortHandle::new_pair();
+ let (_, fs_device_tube) = Tube::pair()?;
+
+ self.ex
+ .spawn_local(Abortable::new(
+ handle_fs_queue(
+ queue,
+ mem,
+ doorbell,
+ kick_evt,
+ self.server.clone(),
+ Arc::new(Mutex::new(fs_device_tube)),
+ ),
+ registration,
+ ))
+ .detach();
+
+ self.workers[idx] = Some(handle);
+ Ok(())
+ }
+
+ fn stop_queue(&mut self, idx: usize) {
+ if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
+ handle.abort();
+ }
+ }
+}
+
+#[derive(FromArgs)]
+#[argh(description = "")]
+struct Options {
+ #[argh(option, description = "path to a socket", arg_name = "PATH")]
+ socket: String,
+ #[argh(option, description = "the virtio-fs tag", arg_name = "TAG")]
+ tag: String,
+ #[argh(option, description = "path to a directory to share", arg_name = "DIR")]
+ shared_dir: PathBuf,
+ #[argh(option, description = "uid map to use", arg_name = "UIDMAP")]
+ uid_map: Option<String>,
+ #[argh(option, description = "gid map to use", arg_name = "GIDMAP")]
+ gid_map: Option<String>,
+}
+
+/// Starts a vhost-user fs device.
+/// Returns an error if the given `args` is invalid or the device fails to run.
+pub fn run_fs_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
+ let opts = match Options::from_args(&[program_name], args) {
+ Ok(opts) => opts,
+ Err(e) => {
+ if e.status.is_err() {
+ bail!(e.output);
+ } else {
+ println!("{}", e.output);
+ }
+ return Ok(());
+ }
+ };
+
+ base::syslog::init().context("Failed to initialize syslog")?;
+
+ let ex = Executor::new().context("Failed to create executor")?;
+ let fs_device = FsBackend::new(&ex, &opts.tag)?;
+
+ // Create and bind unix socket
+ let listener = UnixListener::bind(opts.socket).map(UnlinkUnixListener)?;
+ let mut keep_rds = fs_device.keep_rds.clone();
+ keep_rds.push(listener.as_raw_fd());
+ base::syslog::push_descriptors(&mut keep_rds);
+
+ let handler = DeviceRequestHandler::new(fs_device);
+
+ let pid = jail_and_fork(keep_rds, opts.shared_dir, opts.uid_map, opts.gid_map)?;
+
+ // Parent, nothing to do but wait and then exit
+ if pid != 0 {
+ unsafe { libc::waitpid(pid, std::ptr::null_mut(), 0) };
+ return Ok(());
+ }
+
+ // We need to set the no setuid fixup secure bit so that we don't drop capabilities when
+ // changing the thread uid/gid. Without this, creating new entries can fail in some corner
+ // cases.
+ const SECBIT_NO_SETUID_FIXUP: i32 = 1 << 2;
+ // TODO(crbug.com/1199487): Remove this once libc provides the wrapper for all targets.
+ #[cfg(target_os = "linux")]
+ {
+ // Safe because this doesn't modify any memory and we check the return value.
+ let mut securebits = unsafe { libc::prctl(libc::PR_GET_SECUREBITS) };
+ if securebits < 0 {
+ bail!(io::Error::last_os_error());
+ }
+ securebits |= SECBIT_NO_SETUID_FIXUP;
+ // Safe because this doesn't modify any memory and we check the return value.
+ let ret = unsafe { libc::prctl(libc::PR_SET_SECUREBITS, securebits) };
+ if ret < 0 {
+ bail!(io::Error::last_os_error());
+ }
+ }
+
+ // run_until() returns an Result<Result<..>> which the ? operator lets us flatten.
+ ex.run_until(handler.run_with_listener(listener, &ex))?
+}
diff --git a/devices/src/virtio/vhost/user/device/gpu.rs b/devices/src/virtio/vhost/user/device/gpu.rs
new file mode 100644
index 000000000..a6e5888d8
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/gpu.rs
@@ -0,0 +1,460 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{cell::RefCell, collections::BTreeMap, fs::File, path::PathBuf, rc::Rc, sync::Arc};
+
+use anyhow::{anyhow, bail, Context};
+use argh::FromArgs;
+use async_task::Task;
+use base::{
+ clone_descriptor, error, warn, Event, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor,
+ Tube, UnixSeqpacketListener, UnlinkUnixSeqpacketListener,
+};
+use cros_async::{AsyncTube, AsyncWrapper, EventAsync, Executor, IoSourceExt};
+use hypervisor::ProtectionType;
+use sync::Mutex;
+use vm_memory::GuestMemory;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+use crate::virtio::{
+ self, gpu,
+ vhost::user::device::handler::{DeviceRequestHandler, Doorbell, VhostUserBackend},
+ vhost::user::device::wl::parse_wayland_sock,
+ DescriptorChain, Gpu, GpuDisplayParameters, GpuParameters, Queue, QueueReader, VirtioDevice,
+};
+
+#[derive(Clone)]
+struct SharedReader {
+ queue: Arc<Mutex<Queue>>,
+ doorbell: Arc<Mutex<Doorbell>>,
+}
+
+impl gpu::QueueReader for SharedReader {
+ fn pop(&self, mem: &GuestMemory) -> Option<DescriptorChain> {
+ self.queue.lock().pop(mem)
+ }
+
+ fn add_used(&self, mem: &GuestMemory, desc_index: u16, len: u32) {
+ self.queue.lock().add_used(mem, desc_index, len)
+ }
+
+ fn signal_used(&self, mem: &GuestMemory) {
+ self.queue.lock().trigger_interrupt(mem, &self.doorbell);
+ }
+}
+
+async fn run_ctrl_queue(
+ reader: SharedReader,
+ mem: GuestMemory,
+ kick_evt: EventAsync,
+ state: Rc<RefCell<gpu::Frontend>>,
+) {
+ loop {
+ if let Err(e) = kick_evt.next_val().await {
+ error!("Failed to read kick event for ctrl queue: {}", e);
+ }
+
+ let mut state = state.borrow_mut();
+ let needs_interrupt = state.process_queue(&mem, &reader);
+
+ if needs_interrupt {
+ reader.signal_used(&mem);
+ }
+ }
+}
+
+async fn run_display(
+ display: Box<dyn IoSourceExt<AsyncWrapper<SafeDescriptor>>>,
+ state: Rc<RefCell<gpu::Frontend>>,
+) {
+ loop {
+ if let Err(e) = display.wait_readable().await {
+ error!(
+ "Failed to wait for display context to become readable: {}",
+ e
+ );
+ break;
+ }
+
+ if state.borrow_mut().process_display() {
+ break;
+ }
+ }
+}
+
+async fn run_resource_bridge(tube: Box<dyn IoSourceExt<Tube>>, state: Rc<RefCell<gpu::Frontend>>) {
+ loop {
+ if let Err(e) = tube.wait_readable().await {
+ error!(
+ "Failed to wait for resource bridge tube to become readable: {}",
+ e
+ );
+ break;
+ }
+
+ if let Err(e) = state.borrow_mut().process_resource_bridge(tube.as_source()) {
+ error!("Failed to process resource bridge: {:#}", e);
+ break;
+ }
+ }
+}
+
+fn cancel_task<R: 'static>(ex: &Executor, task: Task<R>) {
+ ex.spawn_local(task.cancel()).detach()
+}
+
+struct GpuBackend {
+ ex: Executor,
+ gpu: Rc<RefCell<Gpu>>,
+ resource_bridges: Arc<Mutex<Vec<Tube>>>,
+ acked_protocol_features: u64,
+ state: Option<Rc<RefCell<gpu::Frontend>>>,
+ fence_state: Arc<Mutex<gpu::FenceState>>,
+ display_worker: Option<Task<()>>,
+ workers: [Option<Task<()>>; Self::MAX_QUEUE_NUM],
+}
+
+impl VhostUserBackend for GpuBackend {
+ const MAX_QUEUE_NUM: usize = gpu::QUEUE_SIZES.len();
+ const MAX_VRING_LEN: u16 = gpu::QUEUE_SIZES[0];
+
+ type Error = anyhow::Error;
+
+ fn features(&self) -> u64 {
+ self.gpu.borrow().features() | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
+ }
+
+ fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
+ self.gpu.borrow_mut().ack_features(value);
+ Ok(())
+ }
+
+ fn acked_features(&self) -> u64 {
+ self.features()
+ }
+
+ fn protocol_features(&self) -> VhostUserProtocolFeatures {
+ VhostUserProtocolFeatures::CONFIG
+ | VhostUserProtocolFeatures::SLAVE_REQ
+ | VhostUserProtocolFeatures::MQ
+ }
+
+ fn ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()> {
+ let unrequested_features = features & !self.protocol_features().bits();
+ if unrequested_features != 0 {
+ bail!("Unexpected protocol features: {:#x}", unrequested_features);
+ }
+
+ self.acked_protocol_features |= features;
+ Ok(())
+ }
+
+ fn acked_protocol_features(&self) -> u64 {
+ self.acked_protocol_features
+ }
+
+ fn read_config(&self, offset: u64, dst: &mut [u8]) {
+ self.gpu.borrow().read_config(offset, dst)
+ }
+
+ fn write_config(&self, offset: u64, data: &[u8]) {
+ self.gpu.borrow_mut().write_config(offset, data)
+ }
+
+ fn set_device_request_channel(&mut self, channel: File) -> anyhow::Result<()> {
+ let tube = AsyncTube::new(&self.ex, unsafe {
+ Tube::from_raw_descriptor(channel.into_raw_descriptor())
+ })
+ .context("failed to create AsyncTube")?;
+
+ // We need a PciAddress in order to initialize the pci bar but this isn't part of the
+ // vhost-user protocol. Instead we expect this to be the first message the crosvm main
+ // process sends us on the device tube.
+ let gpu = Rc::clone(&self.gpu);
+ self.ex
+ .spawn_local(async move {
+ let response = match tube.next().await {
+ Ok(addr) => gpu.borrow_mut().get_device_bars(addr),
+ Err(e) => {
+ error!("Failed to get `PciAddr` from tube: {}", e);
+ return;
+ }
+ };
+
+ if let Err(e) = tube.send(response).await {
+ error!("Failed to send `PciBarConfiguration`: {}", e);
+ }
+
+ let device_tube: Tube = match tube.next().await {
+ Ok(tube) => tube,
+ Err(e) => {
+ error!("Failed to get device tube: {}", e);
+ return;
+ }
+ };
+
+ gpu.borrow_mut().set_device_tube(device_tube);
+ })
+ .detach();
+
+ Ok(())
+ }
+
+ fn start_queue(
+ &mut self,
+ idx: usize,
+ queue: Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: Event,
+ ) -> anyhow::Result<()> {
+ if let Some(task) = self.workers.get_mut(idx).and_then(Option::take) {
+ warn!("Starting new queue handler without stopping old handler");
+ cancel_task(&self.ex, task);
+ }
+
+ match idx {
+ // ctrl queue.
+ 0 => {}
+ // We don't currently handle the cursor queue.
+ 1 => return Ok(()),
+ _ => bail!("attempted to start unknown queue: {}", idx),
+ }
+
+ let kick_evt = EventAsync::new(kick_evt.0, &self.ex)
+ .context("failed to create EventAsync for kick_evt")?;
+
+ let reader = SharedReader {
+ queue: Arc::new(Mutex::new(queue)),
+ doorbell,
+ };
+
+ let state = if let Some(s) = self.state.as_ref() {
+ s.clone()
+ } else {
+ let fence_handler =
+ gpu::create_fence_handler(mem.clone(), reader.clone(), self.fence_state.clone());
+ let state = Rc::new(RefCell::new(
+ self.gpu
+ .borrow_mut()
+ .initialize_frontend(self.fence_state.clone(), fence_handler)
+ .ok_or_else(|| anyhow!("failed to initialize gpu frontend"))?,
+ ));
+ self.state = Some(state.clone());
+ state
+ };
+
+ // Start handling the resource bridges if we haven't already.
+ for bridge in self.resource_bridges.lock().drain(..) {
+ let tube = self
+ .ex
+ .async_from(bridge)
+ .context("failed to create async tube")?;
+ self.ex
+ .spawn_local(run_resource_bridge(tube, state.clone()))
+ .detach();
+ }
+
+ // Start handling the display, if we haven't already.
+ if self.display_worker.is_none() {
+ let display = clone_descriptor(&*state.borrow_mut().display().borrow())
+ .map(|fd| {
+ // Safe because we just created this fd.
+ AsyncWrapper::new(unsafe { SafeDescriptor::from_raw_descriptor(fd) })
+ })
+ .context("failed to clone inner WaitContext for gpu display")
+ .and_then(|ctx| {
+ self.ex
+ .async_from(ctx)
+ .context("failed to create async WaitContext")
+ })?;
+
+ let task = self.ex.spawn_local(run_display(display, state.clone()));
+ self.display_worker = Some(task);
+ }
+
+ let task = self
+ .ex
+ .spawn_local(run_ctrl_queue(reader, mem, kick_evt, state));
+ self.workers[idx] = Some(task);
+ Ok(())
+ }
+
+ fn stop_queue(&mut self, idx: usize) {
+ if let Some(task) = self.workers.get_mut(idx).and_then(Option::take) {
+ cancel_task(&self.ex, task)
+ }
+ }
+
+ fn reset(&mut self) {
+ if let Some(task) = self.display_worker.take() {
+ cancel_task(&self.ex, task)
+ }
+
+ for task in self.workers.iter_mut().filter_map(Option::take) {
+ cancel_task(&self.ex, task)
+ }
+ }
+}
+
+fn gpu_parameters_from_str(input: &str) -> Result<GpuParameters, String> {
+ serde_json::from_str(input).map_err(|e| e.to_string())
+}
+
+#[derive(FromArgs)]
+#[argh(description = "run gpu device")]
+struct Options {
+ #[argh(
+ option,
+ description = "path to bind a listening vhost-user socket",
+ arg_name = "PATH"
+ )]
+ socket: String,
+ #[argh(
+ option,
+ description = "path to one or more Wayland sockets. The unnamed socket is \
+ used for displaying virtual screens while the named ones are used for IPC",
+ from_str_fn(parse_wayland_sock),
+ arg_name = "PATH[,name=NAME]"
+ )]
+ wayland_sock: Vec<(String, PathBuf)>,
+ #[argh(
+ option,
+ description = "path to one or more bridge sockets for communicating with \
+ other graphics devices (wayland, video, etc)",
+ arg_name = "PATH"
+ )]
+ resource_bridge: Vec<String>,
+ #[argh(option, description = " X11 display name to use", arg_name = "DISPLAY")]
+ x_display: Option<String>,
+ #[argh(
+ option,
+ from_str_fn(gpu_parameters_from_str),
+ default = "Default::default()",
+ description = "a JSON object of virtio-gpu parameters",
+ arg_name = "JSON"
+ )]
+ params: GpuParameters,
+}
+
+pub fn run_gpu_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
+ let Options {
+ x_display,
+ params: mut gpu_parameters,
+ resource_bridge,
+ socket,
+ wayland_sock,
+ } = match Options::from_args(&[program_name], args) {
+ Ok(opts) => opts,
+ Err(e) => {
+ if e.status.is_err() {
+ bail!(e.output);
+ } else {
+ println!("{}", e.output);
+ }
+ return Ok(());
+ }
+ };
+
+ base::syslog::init().context("failed to initialize syslog")?;
+
+ let wayland_paths: BTreeMap<_, _> = wayland_sock.into_iter().collect();
+
+ let resource_bridge_listeners = resource_bridge
+ .into_iter()
+ .map(|p| {
+ UnixSeqpacketListener::bind(&p)
+ .map(UnlinkUnixSeqpacketListener)
+ .with_context(|| format!("failed to bind socket at path {}", p))
+ })
+ .collect::<anyhow::Result<Vec<_>>>()?;
+
+ if gpu_parameters.displays.is_empty() {
+ gpu_parameters
+ .displays
+ .push(GpuDisplayParameters::default());
+ }
+
+ let ex = Executor::new().context("failed to create executor")?;
+
+ // We don't know the order in which other devices are going to connect to the resource bridges
+ // so start listening for all of them on separate threads. Any devices that connect after the
+ // gpu device starts its queues will not have its resource bridges processed. In practice this
+ // should be fine since the devices that use the resource bridge always try to connect to the
+ // gpu device before handling messages from the VM.
+ let resource_bridges = Arc::new(Mutex::new(Vec::with_capacity(
+ resource_bridge_listeners.len(),
+ )));
+ for listener in resource_bridge_listeners {
+ let resource_bridges = Arc::clone(&resource_bridges);
+ ex.spawn_blocking(move || match listener.accept() {
+ Ok(stream) => resource_bridges.lock().push(Tube::new(stream)),
+ Err(e) => {
+ let path = listener
+ .path()
+ .unwrap_or_else(|_| PathBuf::from("{unknown}"));
+ error!(
+ "Failed to accept resource bridge connection for socket {}: {}",
+ path.display(),
+ e
+ );
+ }
+ })
+ .detach();
+ }
+
+ let exit_evt = Event::new().context("failed to create Event")?;
+
+ // Initialized later.
+ let gpu_device_tube = None;
+
+ let mut display_backends = vec![
+ virtio::DisplayBackend::X(x_display),
+ virtio::DisplayBackend::Stub,
+ ];
+ if let Some(p) = wayland_paths.get("") {
+ display_backends.insert(0, virtio::DisplayBackend::Wayland(Some(p.to_owned())));
+ }
+
+ // These are only used when there is an input device.
+ let event_devices = Vec::new();
+
+ // This is only used in single-process mode, even for the regular gpu device.
+ let map_request = Arc::new(Mutex::new(None));
+
+ // The regular gpu device sets this to true when sandboxing is enabled. Assume that we
+ // are always sandboxed.
+ let external_blob = true;
+ let base_features = virtio::base_features(ProtectionType::Unprotected);
+ let channels = wayland_paths;
+
+ let gpu = Rc::new(RefCell::new(Gpu::new(
+ exit_evt,
+ gpu_device_tube,
+ Vec::new(), // resource_bridges, handled separately by us
+ display_backends,
+ &gpu_parameters,
+ None,
+ event_devices,
+ map_request,
+ external_blob,
+ base_features,
+ channels,
+ )));
+
+ let backend = GpuBackend {
+ ex: ex.clone(),
+ gpu,
+ resource_bridges,
+ acked_protocol_features: 0,
+ state: None,
+ fence_state: Default::default(),
+ display_worker: None,
+ workers: Default::default(),
+ };
+
+ let handler = DeviceRequestHandler::new(backend);
+ // run_until() returns an Result<Result<..>> which the ? operator lets us flatten.
+ ex.run_until(handler.run(socket, &ex))?
+}
diff --git a/devices/src/virtio/vhost/user/device/handler.rs b/devices/src/virtio/vhost/user/device/handler.rs
new file mode 100644
index 000000000..b5fd5403e
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/handler.rs
@@ -0,0 +1,1063 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Library for implementing vhost-user device executables.
+//!
+//! This crate provides
+//! * `VhostUserBackend` trait, which is a collection of methods to handle vhost-user requests, and
+//! * `DeviceRequestHandler` struct, which makes a connection to a VMM and starts an event loop.
+//!
+//! They are expected to be used as follows:
+//!
+//! 1. Define a struct and implement `VhostUserBackend` for it.
+//! 2. Create a `DeviceRequestHandler` with the backend struct.
+//! 3. Drive the `DeviceRequestHandler::run` async fn with an executor.
+//!
+//! ```ignore
+//! struct MyBackend {
+//! /* fields */
+//! }
+//!
+//! impl VhostUserBackend for MyBackend {
+//! /* implement methods */
+//! }
+//!
+//! fn main() -> Result<(), Box<dyn Error>> {
+//! let backend = MyBackend { /* initialize fields */ };
+//! let handler = DeviceRequestHandler::new(backend);
+//! let socket = std::path::Path("/path/to/socket");
+//! let ex = cros_async::Executor::new()?;
+//!
+//! if let Err(e) = ex.run_until(handler.run(socket, &ex)) {
+//! eprintln!("error happened: {}", e);
+//! }
+//! Ok(())
+//! }
+//! ```
+//!
+// Implementation note:
+// This code lets us take advantage of the vmm_vhost low level implementation of the vhost user
+// protocol. DeviceRequestHandler implements the VhostUserSlaveReqHandlerMut trait from vmm_vhost,
+// and includes some common code for setting up guest memory and managing partially configured
+// vrings. DeviceRequestHandler::run watches the vhost-user socket and then calls handle_request()
+// when it becomes readable. handle_request() reads and parses the message and then calls one of the
+// VhostUserSlaveReqHandlerMut trait methods. These dispatch back to the supplied VhostUserBackend
+// implementation (this is what our devices implement).
+
+use base::AsRawDescriptor;
+use std::convert::{From, TryFrom};
+use std::fs::File;
+use std::num::Wrapping;
+use std::os::unix::io::AsRawFd;
+use std::os::unix::net::UnixListener;
+use std::path::Path;
+use std::sync::Arc;
+
+use anyhow::{anyhow, bail, Context, Result};
+use base::{
+ clear_fd_flags, error, info, Event, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor,
+ SharedMemory, SharedMemoryUnix, UnlinkUnixListener,
+};
+use cros_async::{AsyncWrapper, Executor};
+use sync::Mutex;
+use vm_memory::{GuestAddress, GuestMemory, MemoryRegion};
+use vmm_vhost::{
+ connection::vfio::{Endpoint as VfioEndpoint, Listener as VfioListener},
+ message::{
+ VhostUserConfigFlags, VhostUserInflight, VhostUserMemoryRegion, VhostUserProtocolFeatures,
+ VhostUserSingleMemoryRegion, VhostUserVirtioFeatures, VhostUserVringAddrFlags,
+ VhostUserVringState,
+ },
+ Protocol, SlaveListener, SlaveReqHandler,
+};
+
+use vmm_vhost::{Error as VhostError, Result as VhostResult, VhostUserSlaveReqHandlerMut};
+
+use crate::vfio::{VfioDevice, VfioRegionAddr};
+use crate::virtio::vhost::user::device::vvu::{
+ device::VvuDevice,
+ doorbell::DoorbellRegion,
+ pci::{VvuPciCaps, VvuPciDevice},
+};
+use crate::virtio::{Queue, SignalableInterrupt};
+
+/// An event to deliver an interrupt to the guest.
+///
+/// Unlike `devices::Interrupt`, this doesn't support interrupt status and signal resampling.
+// TODO(b/187487351): To avoid sending unnecessary events, we might want to support interrupt
+// status. For this purpose, we need a mechanism to share interrupt status between the vmm and the
+// device process.
+pub struct CallEvent(Event);
+
+impl SignalableInterrupt for CallEvent {
+ fn signal(&self, _vector: u16, _interrupt_status_mask: u32) {
+ self.0.write(1).unwrap();
+ }
+
+ fn signal_config_changed(&self) {} // TODO(dgreid)
+
+ fn get_resample_evt(&self) -> Option<&Event> {
+ None
+ }
+
+ fn do_interrupt_resample(&self) {}
+}
+
+impl From<File> for CallEvent {
+ fn from(file: File) -> Self {
+ // Safe because we own the file.
+ CallEvent(unsafe { Event::from_raw_descriptor(file.into_raw_descriptor()) })
+ }
+}
+
+/// Keeps a mapping from the vmm's virtual addresses to guest addresses.
+/// used to translate messages from the vmm to guest offsets.
+#[derive(Default)]
+pub struct MappingInfo {
+ pub vmm_addr: u64,
+ pub guest_phys: u64,
+ pub size: u64,
+}
+
+pub fn vmm_va_to_gpa(maps: &[MappingInfo], vmm_va: u64) -> VhostResult<GuestAddress> {
+ for map in maps {
+ if vmm_va >= map.vmm_addr && vmm_va < map.vmm_addr + map.size {
+ return Ok(GuestAddress(vmm_va - map.vmm_addr + map.guest_phys));
+ }
+ }
+ Err(VhostError::InvalidMessage)
+}
+
+pub fn create_guest_memory(
+ contexts: &[VhostUserMemoryRegion],
+ files: Vec<File>,
+) -> VhostResult<(GuestMemory, Vec<MappingInfo>)> {
+ let mut regions = Vec::with_capacity(files.len());
+ for (region, file) in contexts.iter().zip(files.into_iter()) {
+ let region = MemoryRegion::new_from_shm(
+ region.memory_size,
+ GuestAddress(region.guest_phys_addr),
+ region.mmap_offset,
+ Arc::new(SharedMemory::from_safe_descriptor(SafeDescriptor::from(file)).unwrap()),
+ )
+ .map_err(|e| {
+ error!("failed to create a memory region: {}", e);
+ VhostError::InvalidOperation
+ })?;
+ regions.push(region);
+ }
+ let guest_mem = GuestMemory::from_regions(regions).map_err(|e| {
+ error!("failed to create guest memory: {}", e);
+ VhostError::InvalidOperation
+ })?;
+
+ let vmm_maps = contexts
+ .iter()
+ .map(|region| MappingInfo {
+ vmm_addr: region.user_addr,
+ guest_phys: region.guest_phys_addr,
+ size: region.memory_size,
+ })
+ .collect();
+ Ok((guest_mem, vmm_maps))
+}
+
+pub fn create_vvu_guest_memory(
+ vfio_dev: &VfioDevice,
+ shared_mem_addr: &VfioRegionAddr,
+ contexts: &[VhostUserMemoryRegion],
+) -> VhostResult<(GuestMemory, Vec<MappingInfo>)> {
+ let file_offset = vfio_dev.get_offset_for_addr(shared_mem_addr).map_err(|e| {
+ error!("failed to get underlying file: {}", e);
+ VhostError::InvalidOperation
+ })?;
+
+ let mut vmm_maps = Vec::with_capacity(contexts.len());
+ let mut regions = Vec::with_capacity(contexts.len());
+ let page_size = base::pagesize() as u64;
+ for region in contexts {
+ let offset = file_offset + region.mmap_offset;
+ assert_eq!(offset % page_size, 0);
+
+ vmm_maps.push(MappingInfo {
+ vmm_addr: region.user_addr as u64,
+ guest_phys: region.guest_phys_addr as u64,
+ size: region.memory_size,
+ });
+
+ let cloned_file = vfio_dev.dev_file().try_clone().map_err(|e| {
+ error!("failed to clone vfio device file: {}", e);
+ VhostError::InvalidOperation
+ })?;
+ let region = MemoryRegion::new_from_file(
+ region.memory_size,
+ GuestAddress(region.guest_phys_addr),
+ file_offset + region.mmap_offset,
+ Arc::new(cloned_file),
+ )
+ .map_err(|e| {
+ error!("failed to create a memory region: {}", e);
+ VhostError::InvalidOperation
+ })?;
+ regions.push(region);
+ }
+
+ let guest_mem = GuestMemory::from_regions(regions).map_err(|e| {
+ error!("failed to create guest memory: {}", e);
+ VhostError::InvalidOperation
+ })?;
+
+ Ok((guest_mem, vmm_maps))
+}
+
+/// Trait for vhost-user backend.
+pub trait VhostUserBackend
+where
+ Self: Sized,
+ Self::Error: std::fmt::Display,
+{
+ const MAX_QUEUE_NUM: usize;
+ const MAX_VRING_LEN: u16;
+
+ /// Error type specific to this backend.
+ type Error;
+
+ /// The set of feature bits that this backend supports.
+ fn features(&self) -> u64;
+
+ /// Acknowledges that this set of features should be enabled.
+ fn ack_features(&mut self, value: u64) -> std::result::Result<(), Self::Error>;
+
+ /// Returns the set of enabled features.
+ fn acked_features(&self) -> u64;
+
+ /// The set of protocol feature bits that this backend supports.
+ fn protocol_features(&self) -> VhostUserProtocolFeatures;
+
+ /// Acknowledges that this set of protocol features should be enabled.
+ fn ack_protocol_features(&mut self, _value: u64) -> std::result::Result<(), Self::Error>;
+
+ /// Returns the set of enabled protocol features.
+ fn acked_protocol_features(&self) -> u64;
+
+ /// Reads this device configuration space at `offset`.
+ fn read_config(&self, offset: u64, dst: &mut [u8]);
+
+ /// writes `data` to this device's configuration space at `offset`.
+ fn write_config(&self, _offset: u64, _data: &[u8]) {}
+
+ /// Sets the channel for device-specific communication.
+ fn set_device_request_channel(
+ &mut self,
+ _channel: File,
+ ) -> std::result::Result<(), Self::Error> {
+ Ok(())
+ }
+
+ /// Indicates that the backend should start processing requests for virtio queue number `idx`.
+ /// This method must not block the current thread so device backends should either spawn an
+ /// async task or another thread to handle messages from the Queue.
+ fn start_queue(
+ &mut self,
+ idx: usize,
+ queue: Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: Event,
+ ) -> std::result::Result<(), Self::Error>;
+
+ /// Indicates that the backend should stop processing requests for virtio queue number `idx`.
+ fn stop_queue(&mut self, idx: usize);
+
+ /// Resets the vhost-user backend.
+ fn reset(&mut self);
+}
+
+pub enum Doorbell {
+ Call(CallEvent),
+ Vfio(DoorbellRegion),
+}
+
+impl SignalableInterrupt for Doorbell {
+ fn signal(&self, vector: u16, interrupt_status_mask: u32) {
+ match &self {
+ Self::Call(evt) => evt.signal(vector, interrupt_status_mask),
+ Self::Vfio(evt) => evt.signal(vector, interrupt_status_mask),
+ }
+ }
+
+ fn signal_config_changed(&self) {
+ match &self {
+ Self::Call(evt) => evt.signal_config_changed(),
+ Self::Vfio(evt) => evt.signal_config_changed(),
+ }
+ }
+
+ fn get_resample_evt(&self) -> Option<&Event> {
+ match &self {
+ Self::Call(evt) => evt.get_resample_evt(),
+ Self::Vfio(evt) => evt.get_resample_evt(),
+ }
+ }
+
+ fn do_interrupt_resample(&self) {
+ match &self {
+ Self::Call(evt) => evt.do_interrupt_resample(),
+ Self::Vfio(evt) => evt.do_interrupt_resample(),
+ }
+ }
+}
+
+/// A virtio ring entry.
+struct Vring {
+ queue: Queue,
+ doorbell: Option<Arc<Mutex<Doorbell>>>,
+ enabled: bool,
+}
+
+impl Vring {
+ fn new(max_size: u16) -> Self {
+ Self {
+ queue: Queue::new(max_size),
+ doorbell: None,
+ enabled: false,
+ }
+ }
+
+ fn reset(&mut self) {
+ self.queue.reset();
+ self.doorbell = None;
+ self.enabled = false;
+ }
+}
+
+pub(super) enum HandlerType {
+ VhostUser,
+ Vvu {
+ vfio_dev: Arc<VfioDevice>,
+ caps: VvuPciCaps,
+ notification_evts: Vec<Event>,
+ },
+}
+
+impl Default for HandlerType {
+ fn default() -> Self {
+ Self::VhostUser
+ }
+}
+
+/// Structure to have an event loop for interaction between a VMM and `VhostUserBackend`.
+pub struct DeviceRequestHandler<B>
+where
+ B: 'static + VhostUserBackend,
+{
+ vrings: Vec<Vring>,
+ owned: bool,
+ vmm_maps: Option<Vec<MappingInfo>>,
+ mem: Option<GuestMemory>,
+ backend: B,
+
+ handler_type: HandlerType,
+}
+
+impl<B> DeviceRequestHandler<B>
+where
+ B: 'static + VhostUserBackend,
+{
+ /// Creates the vhost-user handler instance for `backend`.
+ pub fn new(backend: B) -> Self {
+ let mut vrings = Vec::with_capacity(B::MAX_QUEUE_NUM);
+ for _ in 0..B::MAX_QUEUE_NUM {
+ vrings.push(Vring::new(B::MAX_VRING_LEN as u16));
+ }
+
+ DeviceRequestHandler {
+ vrings,
+ owned: false,
+ vmm_maps: None,
+ mem: None,
+ backend,
+ handler_type: Default::default(), // For vvu, this field will be overwritten later.
+ }
+ }
+
+ /// Creates a listening socket at `socket` and handles incoming messages from the VMM, which are
+ /// dispatched to the device backend via the `VhostUserBackend` trait methods.
+ pub async fn run<P: AsRef<Path>>(self, socket: P, ex: &Executor) -> Result<()> {
+ let listener = UnixListener::bind(socket)
+ .map(UnlinkUnixListener)
+ .context("failed to create a UNIX domain socket listener")?;
+ return self.run_with_listener(listener, ex).await;
+ }
+
+ /// Attaches to an already bound socket via `listener` and handles incoming messages from the
+ /// VMM, which are dispatched to the device backend via the `VhostUserBackend` trait methods.
+ pub async fn run_with_listener(
+ self,
+ listener: UnlinkUnixListener,
+ ex: &Executor,
+ ) -> Result<()> {
+ let (socket, _) = ex
+ .spawn_blocking(move || {
+ listener
+ .accept()
+ .context("failed to accept an incoming connection")
+ })
+ .await?;
+ let mut req_handler =
+ SlaveReqHandler::from_stream(socket, Arc::new(std::sync::Mutex::new(self)));
+ let h = SafeDescriptor::try_from(&req_handler as &dyn AsRawDescriptor)
+ .map(AsyncWrapper::new)
+ .expect("failed to get safe descriptor for handler");
+ let handler_source = ex
+ .async_from(h)
+ .context("failed to create an async source")?;
+
+ loop {
+ handler_source
+ .wait_readable()
+ .await
+ .context("failed to wait for the handler socket to become readable")?;
+ match req_handler.handle_request() {
+ Ok(()) => (),
+ Err(VhostError::ClientExit) => {
+ info!("vhost-user connection closed");
+ // Exit as the client closed the connection.
+ return Ok(());
+ }
+ Err(e) => {
+ bail!("failed to handle a vhost-user request: {}", e);
+ }
+ };
+ }
+ }
+
+ /// Starts listening virtio-vhost-user device with VFIO to handle incoming vhost-user messages
+ /// forwarded by it.
+ pub async fn run_vvu(mut self, mut device: VvuPciDevice, ex: &Executor) -> Result<()> {
+ self.handler_type = HandlerType::Vvu {
+ vfio_dev: Arc::clone(&device.vfio_dev),
+ caps: device.caps.clone(),
+ notification_evts: std::mem::take(&mut device.notification_evts),
+ };
+ let driver = VvuDevice::new(device);
+
+ let mut listener = VfioListener::new(driver)
+ .map_err(|e| anyhow!("failed to create a VFIO listener: {}", e))
+ .and_then(|l| {
+ SlaveListener::<VfioEndpoint<_, _>, _>::new(
+ l,
+ Arc::new(std::sync::Mutex::new(self)),
+ )
+ .map_err(|e| anyhow!("failed to create SlaveListener: {}", e))
+ })?;
+
+ let mut req_handler = listener
+ .accept()
+ .map_err(|e| anyhow!("failed to accept VFIO connection: {}", e))?
+ .expect("vvu proxy is unavailable via VFIO");
+
+ let h = SafeDescriptor::try_from(&req_handler as &dyn AsRawDescriptor)
+ .map(AsyncWrapper::new)
+ .expect("failed to get safe descriptor for handler");
+ let handler_source = ex
+ .async_from(h)
+ .context("failed to create asyn handler source")?;
+
+ let done = async move {
+ loop {
+ // Wait for requests from the sibling.
+ // `read_u64()` returns the number of requests arrived.
+ let count = handler_source
+ .read_u64()
+ .await
+ .context("failed to wait for handler source")?;
+ for _ in 0..count {
+ req_handler
+ .handle_request()
+ .context("failed to handle request")?;
+ }
+ }
+ };
+ match ex.run_until(done) {
+ Ok(Ok(())) => Ok(()),
+ Ok(Err(e)) => Err(e),
+ Err(e) => Err(e).context("executor error"),
+ }
+ }
+}
+
+impl<B: VhostUserBackend> VhostUserSlaveReqHandlerMut for DeviceRequestHandler<B> {
+ fn protocol(&self) -> Protocol {
+ match self.handler_type {
+ HandlerType::VhostUser => Protocol::Regular,
+ HandlerType::Vvu { .. } => Protocol::Virtio,
+ }
+ }
+
+ fn set_owner(&mut self) -> VhostResult<()> {
+ if self.owned {
+ return Err(VhostError::InvalidOperation);
+ }
+ self.owned = true;
+ Ok(())
+ }
+
+ fn reset_owner(&mut self) -> VhostResult<()> {
+ self.owned = false;
+ self.backend.reset();
+ Ok(())
+ }
+
+ fn get_features(&mut self) -> VhostResult<u64> {
+ let features = self.backend.features();
+ Ok(features)
+ }
+
+ fn set_features(&mut self, features: u64) -> VhostResult<()> {
+ if !self.owned {
+ return Err(VhostError::InvalidOperation);
+ }
+
+ if (features & !(self.backend.features())) != 0 {
+ return Err(VhostError::InvalidParam);
+ }
+
+ if let Err(e) = self.backend.ack_features(features) {
+ error!("failed to acknowledge features 0x{:x}: {}", features, e);
+ return Err(VhostError::InvalidOperation);
+ }
+
+ // If VHOST_USER_F_PROTOCOL_FEATURES has not been negotiated, the ring is initialized in an
+ // enabled state.
+ // If VHOST_USER_F_PROTOCOL_FEATURES has been negotiated, the ring is initialized in a
+ // disabled state.
+ // Client must not pass data to/from the backend until ring is enabled by
+ // VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has been disabled by
+ // VHOST_USER_SET_VRING_ENABLE with parameter 0.
+ let acked_features = self.backend.acked_features();
+ let vring_enabled = VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() & acked_features != 0;
+ for v in &mut self.vrings {
+ v.enabled = vring_enabled;
+ }
+
+ Ok(())
+ }
+
+ fn get_protocol_features(&mut self) -> VhostResult<VhostUserProtocolFeatures> {
+ Ok(self.backend.protocol_features())
+ }
+
+ fn set_protocol_features(&mut self, features: u64) -> VhostResult<()> {
+ if let Err(e) = self.backend.ack_protocol_features(features) {
+ error!("failed to set protocol features 0x{:x}: {}", features, e);
+ return Err(VhostError::InvalidOperation);
+ }
+ Ok(())
+ }
+
+ fn set_mem_table(
+ &mut self,
+ contexts: &[VhostUserMemoryRegion],
+ files: Vec<File>,
+ ) -> VhostResult<()> {
+ let (guest_mem, vmm_maps) = match &self.handler_type {
+ HandlerType::VhostUser => {
+ if files.len() != contexts.len() {
+ return Err(VhostError::InvalidParam);
+ }
+ create_guest_memory(contexts, files)?
+ }
+ HandlerType::Vvu {
+ vfio_dev: device,
+ caps,
+ ..
+ } => {
+ // virtio-vhost-user doesn't pass FDs.
+ if !files.is_empty() {
+ return Err(VhostError::InvalidParam);
+ }
+ create_vvu_guest_memory(device.as_ref(), caps.shared_mem_cfg_addr(), contexts)?
+ }
+ };
+
+ self.mem = Some(guest_mem);
+ self.vmm_maps = Some(vmm_maps);
+ Ok(())
+ }
+
+ fn get_queue_num(&mut self) -> VhostResult<u64> {
+ Ok(self.vrings.len() as u64)
+ }
+
+ fn set_vring_num(&mut self, index: u32, num: u32) -> VhostResult<()> {
+ if index as usize >= self.vrings.len() || num == 0 || num > B::MAX_VRING_LEN.into() {
+ return Err(VhostError::InvalidParam);
+ }
+ self.vrings[index as usize].queue.size = num as u16;
+
+ Ok(())
+ }
+
+ fn set_vring_addr(
+ &mut self,
+ index: u32,
+ _flags: VhostUserVringAddrFlags,
+ descriptor: u64,
+ used: u64,
+ available: u64,
+ _log: u64,
+ ) -> VhostResult<()> {
+ if index as usize >= self.vrings.len() {
+ return Err(VhostError::InvalidParam);
+ }
+
+ let vmm_maps = self.vmm_maps.as_ref().ok_or(VhostError::InvalidParam)?;
+ let vring = &mut self.vrings[index as usize];
+ vring.queue.desc_table = vmm_va_to_gpa(vmm_maps, descriptor)?;
+ vring.queue.avail_ring = vmm_va_to_gpa(vmm_maps, available)?;
+ vring.queue.used_ring = vmm_va_to_gpa(vmm_maps, used)?;
+
+ Ok(())
+ }
+
+ fn set_vring_base(&mut self, index: u32, base: u32) -> VhostResult<()> {
+ if index as usize >= self.vrings.len() || base >= B::MAX_VRING_LEN.into() {
+ return Err(VhostError::InvalidParam);
+ }
+
+ let vring = &mut self.vrings[index as usize];
+ vring.queue.next_avail = Wrapping(base as u16);
+ vring.queue.next_used = Wrapping(base as u16);
+
+ Ok(())
+ }
+
+ fn get_vring_base(&mut self, index: u32) -> VhostResult<VhostUserVringState> {
+ if index as usize >= self.vrings.len() {
+ return Err(VhostError::InvalidParam);
+ }
+
+ // Quotation from vhost-user spec:
+ // Client must start ring upon receiving a kick (that is, detecting
+ // that file descriptor is readable) on the descriptor specified by
+ // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
+ // VHOST_USER_GET_VRING_BASE.
+ self.backend.stop_queue(index as usize);
+
+ let vring = &mut self.vrings[index as usize];
+ vring.reset();
+
+ Ok(VhostUserVringState::new(
+ index,
+ vring.queue.next_avail.0 as u32,
+ ))
+ }
+
+ fn set_vring_kick(&mut self, index: u8, file: Option<File>) -> VhostResult<()> {
+ if index as usize >= self.vrings.len() {
+ return Err(VhostError::InvalidParam);
+ }
+
+ let vring = &mut self.vrings[index as usize];
+ if vring.queue.ready {
+ error!("kick fd cannot replaced after queue is started");
+ return Err(VhostError::InvalidOperation);
+ }
+
+ let kick_evt = match &self.handler_type {
+ HandlerType::VhostUser => {
+ let file = file.ok_or(VhostError::InvalidParam)?;
+ // Remove O_NONBLOCK from kick_fd. Otherwise, uring_executor will fails when we read
+ // values via `next_val()` later.
+ if let Err(e) = clear_fd_flags(file.as_raw_fd(), libc::O_NONBLOCK) {
+ error!("failed to remove O_NONBLOCK for kick fd: {}", e);
+ return Err(VhostError::InvalidParam);
+ }
+
+ // Safe because we own the file.
+ unsafe { Event::from_raw_descriptor(file.into_raw_descriptor()) }
+ }
+ HandlerType::Vvu {
+ notification_evts, ..
+ } => {
+ if file.is_some() {
+ return Err(VhostError::InvalidParam);
+ }
+ notification_evts[index as usize].try_clone().map_err(|e| {
+ error!("failed to clone notification_evts[{}]: {}", index, e);
+ VhostError::InvalidOperation
+ })?
+ }
+ };
+
+ let vring = &mut self.vrings[index as usize];
+ vring.queue.ready = true;
+
+ let queue = vring.queue.clone();
+ let doorbell = vring
+ .doorbell
+ .as_ref()
+ .ok_or(VhostError::InvalidOperation)?;
+ let mem = self
+ .mem
+ .as_ref()
+ .cloned()
+ .ok_or(VhostError::InvalidOperation)?;
+
+ if let Err(e) =
+ self.backend
+ .start_queue(index as usize, queue, mem, Arc::clone(doorbell), kick_evt)
+ {
+ error!("Failed to start queue {}: {}", index, e);
+ return Err(VhostError::SlaveInternalError);
+ }
+
+ Ok(())
+ }
+
+ fn set_vring_call(&mut self, index: u8, file: Option<File>) -> VhostResult<()> {
+ if index as usize >= self.vrings.len() {
+ return Err(VhostError::InvalidParam);
+ }
+
+ let doorbell = match &self.handler_type {
+ HandlerType::VhostUser => {
+ let file = file.ok_or(VhostError::InvalidParam)?;
+ Doorbell::Call(CallEvent::try_from(file).map_err(|_| {
+ error!("failed to convert callfd to CallSignal");
+ VhostError::InvalidParam
+ })?)
+ }
+ HandlerType::Vvu {
+ vfio_dev: device,
+ caps,
+ ..
+ } => {
+ let base = caps.doorbell_base_addr();
+ let addr = VfioRegionAddr {
+ index: base.index,
+ addr: base.addr + (index as u64 * caps.doorbell_off_multiplier() as u64),
+ };
+ Doorbell::Vfio(DoorbellRegion {
+ vfio: Arc::clone(device),
+ index,
+ addr,
+ })
+ }
+ };
+
+ match &self.vrings[index as usize].doorbell {
+ None => {
+ self.vrings[index as usize].doorbell = Some(Arc::new(Mutex::new(doorbell)));
+ }
+ Some(cell) => {
+ let mut evt = cell.lock();
+ *evt = doorbell;
+ }
+ }
+
+ Ok(())
+ }
+
+ fn set_vring_err(&mut self, _index: u8, _fd: Option<File>) -> VhostResult<()> {
+ // TODO
+ Ok(())
+ }
+
+ fn set_vring_enable(&mut self, index: u32, enable: bool) -> VhostResult<()> {
+ if index as usize >= self.vrings.len() {
+ return Err(VhostError::InvalidParam);
+ }
+
+ // This request should be handled only when VHOST_USER_F_PROTOCOL_FEATURES
+ // has been negotiated.
+ if self.backend.acked_features() & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 {
+ return Err(VhostError::InvalidOperation);
+ }
+
+ // Slave must not pass data to/from the backend until ring is
+ // enabled by VHOST_USER_SET_VRING_ENABLE with parameter 1,
+ // or after it has been disabled by VHOST_USER_SET_VRING_ENABLE
+ // with parameter 0.
+ self.vrings[index as usize].enabled = enable;
+
+ Ok(())
+ }
+
+ fn get_config(
+ &mut self,
+ offset: u32,
+ size: u32,
+ _flags: VhostUserConfigFlags,
+ ) -> VhostResult<Vec<u8>> {
+ if offset >= size {
+ return Err(VhostError::InvalidParam);
+ }
+
+ let mut data = vec![0; size as usize];
+ self.backend.read_config(u64::from(offset), &mut data);
+ Ok(data)
+ }
+
+ fn set_config(
+ &mut self,
+ offset: u32,
+ buf: &[u8],
+ _flags: VhostUserConfigFlags,
+ ) -> VhostResult<()> {
+ self.backend.write_config(u64::from(offset), buf);
+ Ok(())
+ }
+
+ fn set_slave_req_fd(&mut self, fd: File) {
+ if let Err(e) = self.backend.set_device_request_channel(fd) {
+ error!("failed to set device request channel: {}", e);
+ }
+ }
+
+ fn get_inflight_fd(
+ &mut self,
+ _inflight: &VhostUserInflight,
+ ) -> VhostResult<(VhostUserInflight, File)> {
+ unimplemented!("get_inflight_fd");
+ }
+
+ fn set_inflight_fd(&mut self, _inflight: &VhostUserInflight, _file: File) -> VhostResult<()> {
+ unimplemented!("set_inflight_fd");
+ }
+
+ fn get_max_mem_slots(&mut self) -> VhostResult<u64> {
+ //TODO
+ Ok(0)
+ }
+
+ fn add_mem_region(
+ &mut self,
+ _region: &VhostUserSingleMemoryRegion,
+ _fd: File,
+ ) -> VhostResult<()> {
+ //TODO
+ Ok(())
+ }
+
+ fn remove_mem_region(&mut self, _region: &VhostUserSingleMemoryRegion) -> VhostResult<()> {
+ //TODO
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use std::sync::mpsc::channel;
+ use std::sync::Barrier;
+
+ use anyhow::{anyhow, bail};
+ use data_model::DataInit;
+ use tempfile::{Builder, TempDir};
+
+ use crate::virtio::vhost::user::vmm::VhostUserHandler;
+
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
+ #[repr(C)]
+ struct FakeConfig {
+ x: u32,
+ y: u64,
+ }
+
+ unsafe impl DataInit for FakeConfig {}
+
+ const FAKE_CONFIG_DATA: FakeConfig = FakeConfig { x: 1, y: 2 };
+
+ struct FakeBackend {
+ avail_features: u64,
+ acked_features: u64,
+ acked_protocol_features: VhostUserProtocolFeatures,
+ }
+
+ impl FakeBackend {
+ fn new() -> Self {
+ Self {
+ avail_features: VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits(),
+ acked_features: 0,
+ acked_protocol_features: VhostUserProtocolFeatures::empty(),
+ }
+ }
+ }
+
+ impl VhostUserBackend for FakeBackend {
+ const MAX_QUEUE_NUM: usize = 16;
+ const MAX_VRING_LEN: u16 = 256;
+
+ type Error = anyhow::Error;
+
+ fn features(&self) -> u64 {
+ self.avail_features
+ }
+
+ fn ack_features(&mut self, value: u64) -> std::result::Result<(), Self::Error> {
+ let unrequested_features = value & !self.avail_features;
+ if unrequested_features != 0 {
+ bail!(
+ "invalid protocol features are given: 0x{:x}",
+ unrequested_features
+ );
+ }
+ self.acked_features |= value;
+ Ok(())
+ }
+
+ fn acked_features(&self) -> u64 {
+ self.acked_features
+ }
+
+ fn protocol_features(&self) -> VhostUserProtocolFeatures {
+ VhostUserProtocolFeatures::CONFIG
+ }
+
+ fn ack_protocol_features(&mut self, features: u64) -> std::result::Result<(), Self::Error> {
+ let features = VhostUserProtocolFeatures::from_bits(features).ok_or(anyhow!(
+ "invalid protocol features are given: 0x{:x}",
+ features
+ ))?;
+ let supported = self.protocol_features();
+ self.acked_protocol_features = features & supported;
+ Ok(())
+ }
+
+ fn acked_protocol_features(&self) -> u64 {
+ self.acked_protocol_features.bits()
+ }
+
+ fn read_config(&self, offset: u64, dst: &mut [u8]) {
+ dst.copy_from_slice(&FAKE_CONFIG_DATA.as_slice()[offset as usize..]);
+ }
+
+ fn reset(&mut self) {}
+
+ fn start_queue(
+ &mut self,
+ _idx: usize,
+ _queue: Queue,
+ _mem: GuestMemory,
+ _doorbell: Arc<Mutex<Doorbell>>,
+ _kick_evt: Event,
+ ) -> std::result::Result<(), Self::Error> {
+ Ok(())
+ }
+
+ fn stop_queue(&mut self, _idx: usize) {}
+ }
+
+ fn temp_dir() -> TempDir {
+ Builder::new().prefix("/tmp/vhost_test").tempdir().unwrap()
+ }
+
+ #[test]
+ fn test_vhost_user_activate() {
+ use vmm_vhost::{
+ connection::socket::{Endpoint as SocketEndpoint, Listener as SocketListener},
+ SlaveListener,
+ };
+
+ const QUEUES_NUM: usize = 2;
+
+ let dir = temp_dir();
+ let mut path = dir.path().to_owned();
+ path.push("sock");
+ let listener = SocketListener::new(&path, true).unwrap();
+
+ let vmm_bar = Arc::new(Barrier::new(2));
+ let dev_bar = vmm_bar.clone();
+
+ let (tx, rx) = channel();
+
+ std::thread::spawn(move || {
+ // VMM side
+ rx.recv().unwrap(); // Ensure the device is ready.
+
+ let allow_features = VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let init_features = VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let allow_protocol_features = VhostUserProtocolFeatures::CONFIG;
+ let mut vmm_handler = VhostUserHandler::new_from_path(
+ &path,
+ QUEUES_NUM as u64,
+ allow_features,
+ init_features,
+ allow_protocol_features,
+ )
+ .unwrap();
+
+ println!("read_config");
+ let mut buf = vec![0; std::mem::size_of::<FakeConfig>()];
+ vmm_handler.read_config::<FakeConfig>(0, &mut buf).unwrap();
+ // Check if the obtained config data is correct.
+ let config = FakeConfig::from_slice(&buf).unwrap();
+ assert_eq!(*config, FAKE_CONFIG_DATA);
+
+ println!("set_mem_table");
+ let mem = GuestMemory::new(&[(GuestAddress(0x0), 0x10000)]).unwrap();
+ vmm_handler.set_mem_table(&mem).unwrap();
+
+ for idx in 0..QUEUES_NUM {
+ println!("activate_mem_table: queue_index={}", idx);
+ let queue = Queue::new(0x10);
+ let queue_evt = Event::new().unwrap();
+ let irqfd = Event::new().unwrap();
+
+ vmm_handler
+ .activate_vring(&mem, idx, &queue, &queue_evt, &irqfd)
+ .unwrap();
+ }
+
+ // The VMM side is supposed to stop before the device side.
+ drop(vmm_handler);
+
+ vmm_bar.wait();
+ });
+
+ // Device side
+ let handler = Arc::new(std::sync::Mutex::new(DeviceRequestHandler::new(
+ FakeBackend::new(),
+ )));
+ let mut listener = SlaveListener::<SocketEndpoint<_>, _>::new(listener, handler).unwrap();
+
+ // Notify listener is ready.
+ tx.send(()).unwrap();
+
+ let mut listener = listener.accept().unwrap().unwrap();
+
+ // VhostUserHandler::new()
+ listener.handle_request().expect("set_owner");
+ listener.handle_request().expect("get_features");
+ listener.handle_request().expect("set_features");
+ listener.handle_request().expect("get_protocol_features");
+ listener.handle_request().expect("set_protocol_features");
+
+ // VhostUserHandler::read_config()
+ listener.handle_request().expect("get_config");
+
+ // VhostUserHandler::set_mem_table()
+ listener.handle_request().expect("set_mem_table");
+
+ for _ in 0..QUEUES_NUM {
+ // VhostUserHandler::activate_vring()
+ listener.handle_request().expect("set_vring_num");
+ listener.handle_request().expect("set_vring_addr");
+ listener.handle_request().expect("set_vring_base");
+ listener.handle_request().expect("set_vring_call");
+ listener.handle_request().expect("set_vring_kick");
+ listener.handle_request().expect("set_vring_enable");
+ }
+
+ dev_bar.wait();
+
+ match listener.handle_request() {
+ Err(VhostError::ClientExit) => (),
+ r => panic!("Err(ClientExit) was expected but {:?}", r),
+ }
+ }
+}
diff --git a/devices/src/virtio/vhost/user/device/mod.rs b/devices/src/virtio/vhost/user/device/mod.rs
new file mode 100644
index 000000000..78c5d8a14
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/mod.rs
@@ -0,0 +1,29 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod block;
+mod console;
+#[cfg(feature = "audio_cras")]
+mod cras_snd;
+mod fs;
+#[cfg(feature = "gpu")]
+mod gpu;
+mod handler;
+mod net;
+mod vsock;
+mod vvu;
+mod wl;
+
+pub use block::run_block_device;
+pub use console::run_console_device;
+#[cfg(feature = "audio_cras")]
+pub use cras_snd::run_cras_snd_device;
+pub use fs::run_fs_device;
+#[cfg(feature = "gpu")]
+pub use gpu::run_gpu_device;
+
+#[cfg(any(all(windows, feature = "slirp"), not(windows)))]
+pub use net::run_net_device;
+pub use vsock::run_vsock_device;
+pub use wl::run_wl_device;
diff --git a/devices/src/virtio/vhost/user/device/net.rs b/devices/src/virtio/vhost/user/device/net.rs
new file mode 100644
index 000000000..2b7a64f61
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/net.rs
@@ -0,0 +1,181 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#[cfg_attr(windows, path = "windows/net.rs")]
+#[cfg_attr(not(windows), path = "unix/net.rs")]
+mod net;
+
+// Only Windows exposes public symbols, but the module level use is used on both platforms.
+#[allow(unused_imports)]
+pub use net::*;
+
+use std::sync::Arc;
+
+use anyhow::{anyhow, bail, Context};
+use base::{error, Event};
+use cros_async::{EventAsync, Executor, IntoAsync};
+use data_model::DataInit;
+use futures::future::AbortHandle;
+use net_util::TapT;
+use once_cell::sync::OnceCell;
+use sync::Mutex;
+use vm_memory::GuestMemory;
+use vmm_vhost::message::VhostUserProtocolFeatures;
+
+use crate::virtio;
+use crate::virtio::net::{build_config, process_ctrl, process_tx, virtio_features_to_tap_offload};
+use crate::virtio::vhost::user::device::handler::{Doorbell, VhostUserBackend};
+
+thread_local! {
+ pub(crate) static NET_EXECUTOR: OnceCell<Executor> = OnceCell::new();
+}
+
+// TODO(b/188947559): Come up with better way to include these constants. Compiler errors happen
+// if they are kept in the trait.
+const MAX_QUEUE_NUM: usize = 3; /* rx, tx, ctrl */
+const MAX_VRING_LEN: u16 = 256;
+
+async fn run_tx_queue<T: TapT>(
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ mut tap: T,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: EventAsync,
+) {
+ loop {
+ if let Err(e) = kick_evt.next_val().await {
+ error!("Failed to read kick event for tx queue: {}", e);
+ break;
+ }
+
+ process_tx(&doorbell, &mut queue, &mem, &mut tap);
+ }
+}
+
+async fn run_ctrl_queue<T: TapT>(
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ mut tap: T,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: EventAsync,
+ acked_features: u64,
+ vq_pairs: u16,
+) {
+ loop {
+ if let Err(e) = kick_evt.next_val().await {
+ error!("Failed to read kick event for tx queue: {}", e);
+ break;
+ }
+
+ if let Err(e) = process_ctrl(
+ &doorbell,
+ &mut queue,
+ &mem,
+ &mut tap,
+ acked_features,
+ vq_pairs,
+ ) {
+ error!("Failed to process ctrl queue: {}", e);
+ break;
+ }
+ }
+}
+
+pub(crate) struct NetBackend<T: TapT + IntoAsync> {
+ tap: T,
+ avail_features: u64,
+ acked_features: u64,
+ acked_protocol_features: VhostUserProtocolFeatures,
+ workers: [Option<AbortHandle>; MAX_QUEUE_NUM],
+ mtu: u16,
+ #[cfg(all(windows, feature = "slirp"))]
+ slirp_kill_event: Event,
+}
+
+impl<T: 'static> NetBackend<T>
+where
+ T: TapT + IntoAsync,
+{
+ fn max_vq_pairs() -> usize {
+ Self::MAX_QUEUE_NUM / 2
+ }
+}
+
+impl<T: 'static> VhostUserBackend for NetBackend<T>
+where
+ T: TapT + IntoAsync,
+{
+ const MAX_QUEUE_NUM: usize = MAX_QUEUE_NUM; /* rx, tx, ctrl */
+ const MAX_VRING_LEN: u16 = MAX_VRING_LEN;
+
+ type Error = anyhow::Error;
+
+ fn features(&self) -> u64 {
+ self.avail_features
+ }
+
+ fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
+ let unrequested_features = value & !self.avail_features;
+ if unrequested_features != 0 {
+ bail!("invalid features are given: {:#x}", unrequested_features);
+ }
+
+ self.acked_features |= value;
+
+ self.tap
+ .set_offload(virtio_features_to_tap_offload(self.acked_features))
+ .context("failed to set tap offload to match features")?;
+
+ Ok(())
+ }
+
+ fn acked_features(&self) -> u64 {
+ self.acked_features
+ }
+
+ fn protocol_features(&self) -> VhostUserProtocolFeatures {
+ VhostUserProtocolFeatures::CONFIG
+ }
+
+ fn ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()> {
+ let features = VhostUserProtocolFeatures::from_bits(features)
+ .ok_or_else(|| anyhow!("invalid protocol features are given: {:#x}", features))?;
+ let supported = self.protocol_features();
+ self.acked_protocol_features = features & supported;
+ Ok(())
+ }
+
+ fn acked_protocol_features(&self) -> u64 {
+ self.acked_protocol_features.bits()
+ }
+
+ fn read_config(&self, offset: u64, data: &mut [u8]) {
+ let config_space = build_config(Self::max_vq_pairs() as u16, self.mtu);
+ virtio::copy_config(data, 0, config_space.as_slice(), offset);
+ }
+
+ fn reset(&mut self) {}
+
+ fn start_queue(
+ &mut self,
+ idx: usize,
+ queue: virtio::Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: Event,
+ ) -> anyhow::Result<()> {
+ net::start_queue(self, idx, queue, mem, doorbell, kick_evt)
+ }
+
+ fn stop_queue(&mut self, idx: usize) {
+ if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
+ handle.abort();
+ }
+ }
+}
+
+/// Starts a vhost-user net device.
+pub fn run_net_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
+ start_device(program_name, args)
+}
diff --git a/devices/src/virtio/vhost/user/device/unix/net.rs b/devices/src/virtio/vhost/user/device/unix/net.rs
new file mode 100644
index 000000000..aca8573c9
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/unix/net.rs
@@ -0,0 +1,373 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::net::Ipv4Addr;
+use std::str::FromStr;
+use std::sync::Arc;
+use std::thread;
+
+use anyhow::{anyhow, bail, Context};
+use argh::FromArgs;
+use base::validate_raw_descriptor;
+use base::{error, warn, Event, RawDescriptor};
+use cros_async::{EventAsync, Executor, IntoAsync, IoSourceExt};
+use futures::future::{AbortHandle, Abortable};
+use hypervisor::ProtectionType;
+use net_util::TapT;
+use net_util::{MacAddress, Tap};
+use sync::Mutex;
+use virtio_sys::virtio_net;
+use vm_memory::GuestMemory;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+use super::{run_ctrl_queue, run_tx_queue, NetBackend, NET_EXECUTOR};
+use crate::virtio;
+use crate::virtio::net::validate_and_configure_tap;
+use crate::virtio::net::{process_rx, NetError};
+use crate::virtio::vhost::user::device::handler::{
+ DeviceRequestHandler, Doorbell, VhostUserBackend,
+};
+use virtio::vhost::user::device::vvu::pci::VvuPciDevice;
+
+struct TapConfig {
+ host_ip: Ipv4Addr,
+ netmask: Ipv4Addr,
+ mac: MacAddress,
+}
+
+impl FromStr for TapConfig {
+ type Err = anyhow::Error;
+
+ fn from_str(arg: &str) -> Result<Self, Self::Err> {
+ let args: Vec<&str> = arg.split(',').collect();
+ if args.len() != 3 {
+ bail!("TAP config must consist of 3 parts but {}", args.len());
+ }
+
+ let host_ip: Ipv4Addr = args[0]
+ .parse()
+ .map_err(|e| anyhow!("invalid IP address: {}", e))?;
+ let netmask: Ipv4Addr = args[1]
+ .parse()
+ .map_err(|e| anyhow!("invalid net mask: {}", e))?;
+ let mac: MacAddress = args[2]
+ .parse()
+ .map_err(|e| anyhow!("invalid MAC address: {}", e))?;
+
+ Ok(Self {
+ host_ip,
+ netmask,
+ mac,
+ })
+ }
+}
+
+impl<T: 'static> NetBackend<T>
+where
+ T: TapT + IntoAsync,
+{
+ fn new_from_config(config: &TapConfig) -> anyhow::Result<Self> {
+ // Create a tap device.
+ let tap = T::new(true /* vnet_hdr */, false /* multi_queue */)
+ .context("failed to create tap device")?;
+ tap.set_ip_addr(config.host_ip)
+ .context("failed to set IP address")?;
+ tap.set_netmask(config.netmask)
+ .context("failed to set netmask")?;
+ tap.set_mac_address(config.mac)
+ .context("failed to set MAC address")?;
+
+ Self::new(tap)
+ }
+
+ pub fn new_from_tap_fd(tap_fd: RawDescriptor) -> anyhow::Result<Self> {
+ let tap_fd = validate_raw_descriptor(tap_fd).context("failed to validate tap fd")?;
+ // Safe because we ensure that we get a unique handle to the fd.
+ let tap = unsafe { T::from_raw_descriptor(tap_fd).context("failed to create tap device")? };
+
+ Self::new(tap)
+ }
+
+ fn new(tap: T) -> anyhow::Result<Self> {
+ let vq_pairs = Self::max_vq_pairs();
+ validate_and_configure_tap(&tap, vq_pairs as u16)
+ .context("failed to validate and configure tap")?;
+
+ let avail_features = virtio::base_features(ProtectionType::Unprotected)
+ | 1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM
+ | 1 << virtio_net::VIRTIO_NET_F_CSUM
+ | 1 << virtio_net::VIRTIO_NET_F_CTRL_VQ
+ | 1 << virtio_net::VIRTIO_NET_F_CTRL_GUEST_OFFLOADS
+ | 1 << virtio_net::VIRTIO_NET_F_GUEST_TSO4
+ | 1 << virtio_net::VIRTIO_NET_F_GUEST_UFO
+ | 1 << virtio_net::VIRTIO_NET_F_HOST_TSO4
+ | 1 << virtio_net::VIRTIO_NET_F_HOST_UFO
+ | 1 << virtio_net::VIRTIO_NET_F_MTU
+ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+
+ let mtu = tap.mtu()?;
+
+ Ok(Self {
+ tap,
+ avail_features,
+ acked_features: 0,
+ acked_protocol_features: VhostUserProtocolFeatures::empty(),
+ workers: Default::default(),
+ mtu,
+ })
+ }
+}
+
+async fn run_rx_queue<T: TapT>(
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ mut tap: Box<dyn IoSourceExt<T>>,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: EventAsync,
+) {
+ loop {
+ if let Err(e) = tap.wait_readable().await {
+ error!("Failed to wait for tap device to become readable: {}", e);
+ break;
+ }
+ match process_rx(&doorbell, &mut queue, &mem, tap.as_source_mut()) {
+ Ok(()) => {}
+ Err(NetError::RxDescriptorsExhausted) => {
+ if let Err(e) = kick_evt.next_val().await {
+ error!("Failed to read kick event for rx queue: {}", e);
+ break;
+ }
+ }
+ Err(e) => {
+ error!("Failed to process rx queue: {}", e);
+ break;
+ }
+ }
+ }
+}
+
+/// Platform specific impl of VhostUserBackend::start_queue.
+pub(super) fn start_queue<T: 'static + IntoAsync + TapT>(
+ backend: &mut NetBackend<T>,
+ idx: usize,
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: Event,
+) -> anyhow::Result<()> {
+ if let Some(handle) = backend.workers.get_mut(idx).and_then(Option::take) {
+ warn!("Starting new queue handler without stopping old handler");
+ handle.abort();
+ }
+
+ // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
+ queue.ack_features(backend.acked_features);
+
+ NET_EXECUTOR.with(|ex| {
+ // Safe because the executor is initialized in main() below.
+ let ex = ex.get().expect("Executor not initialized");
+
+ let kick_evt =
+ EventAsync::new(kick_evt.0, ex).context("failed to create EventAsync for kick_evt")?;
+ let tap = backend
+ .tap
+ .try_clone()
+ .context("failed to clone tap device")?;
+ let (handle, registration) = AbortHandle::new_pair();
+ match idx {
+ 0 => {
+ let tap = ex
+ .async_from(tap)
+ .context("failed to create async tap device")?;
+
+ ex.spawn_local(Abortable::new(
+ run_rx_queue(queue, mem, tap, doorbell, kick_evt),
+ registration,
+ ))
+ .detach();
+ }
+ 1 => {
+ ex.spawn_local(Abortable::new(
+ run_tx_queue(queue, mem, tap, doorbell, kick_evt),
+ registration,
+ ))
+ .detach();
+ }
+ 2 => {
+ ex.spawn_local(Abortable::new(
+ run_ctrl_queue(
+ queue,
+ mem,
+ tap,
+ doorbell,
+ kick_evt,
+ backend.acked_features,
+ 1, /* vq_pairs */
+ ),
+ registration,
+ ))
+ .detach();
+ }
+ _ => bail!("attempted to start unknown queue: {}", idx),
+ }
+
+ backend.workers[idx] = Some(handle);
+ Ok(())
+ })
+}
+
+#[derive(FromArgs)]
+#[argh(description = "")]
+struct Options {
+ #[argh(
+ option,
+ description = "TAP device config. (e.g. \
+ \"/path/to/sock,10.0.2.2,255.255.255.0,12:34:56:78:9a:bc\")",
+ arg_name = "SOCKET_PATH,IP_ADDR,NET_MASK,MAC_ADDR"
+ )]
+ device: Vec<String>,
+ #[argh(
+ option,
+ description = "TAP FD with a socket path",
+ arg_name = "SOCKET_PATH,TAP_FD"
+ )]
+ tap_fd: Vec<String>,
+ #[argh(
+ option,
+ description = "TAP device config for virtio-vhost-user. \
+ (e.g. \"0000:00:07.0,10.0.2.2,255.255.255.0,12:34:56:78:9a:bc\")",
+ arg_name = "DEVICE,IP_ADDR,NET_MASK,MAC_ADDR"
+ )]
+ vvu_device: Vec<String>,
+ #[argh(
+ option,
+ description = "TAP FD with a vfio device name for virtio-vhost-user",
+ arg_name = "DEVICE,TAP_FD"
+ )]
+ vvu_tap_fd: Vec<String>,
+}
+
+enum Connection {
+ Socket(String),
+ Vfio(String),
+}
+
+fn new_backend_from_device_arg(arg: &str) -> anyhow::Result<(String, NetBackend<Tap>)> {
+ let pos = match arg.find(',') {
+ Some(p) => p,
+ None => {
+ bail!("device must take comma-separated argument");
+ }
+ };
+ let conn = &arg[0..pos];
+ let cfg = &arg[pos + 1..]
+ .parse::<TapConfig>()
+ .context("failed to parse tap config")?;
+ let backend = NetBackend::<Tap>::new_from_config(cfg).context("failed to create NetBackend")?;
+ Ok((conn.to_string(), backend))
+}
+
+fn new_backend_from_tapfd_arg(arg: &str) -> anyhow::Result<(String, NetBackend<Tap>)> {
+ let pos = match arg.find(',') {
+ Some(p) => p,
+ None => {
+ bail!("'tap-fd' flag must take comma-separated argument");
+ }
+ };
+ let conn = &arg[0..pos];
+ let tap_fd = &arg[pos + 1..]
+ .parse::<i32>()
+ .context("failed to parse tap-fd")?;
+ let backend =
+ NetBackend::<Tap>::new_from_tap_fd(*tap_fd).context("failed to create NetBackend")?;
+
+ Ok((conn.to_string(), backend))
+}
+
+/// Starts a vhost-user net device.
+/// Returns an error if the given `args` is invalid or the device fails to run.
+pub(crate) fn start_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
+ let opts = match Options::from_args(&[program_name], args) {
+ Ok(opts) => opts,
+ Err(e) => {
+ if e.status.is_err() {
+ bail!(e.output);
+ } else {
+ println!("{}", e.output);
+ }
+ return Ok(());
+ }
+ };
+
+ let num_devices =
+ opts.device.len() + opts.tap_fd.len() + opts.vvu_device.len() + opts.vvu_tap_fd.len();
+
+ if num_devices == 0 {
+ bail!("no device option was passed");
+ }
+
+ let mut devices: Vec<(Connection, NetBackend<Tap>)> = Vec::with_capacity(num_devices);
+
+ // vhost-user
+ for arg in opts.device.iter() {
+ devices.push(
+ new_backend_from_device_arg(arg)
+ .map(|(s, backend)| (Connection::Socket(s), backend))?,
+ );
+ }
+ for arg in opts.tap_fd.iter() {
+ devices.push(
+ new_backend_from_tapfd_arg(arg).map(|(s, backend)| (Connection::Socket(s), backend))?,
+ );
+ }
+
+ // virtio-vhost-user
+ for arg in opts.vvu_device.iter() {
+ devices.push(
+ new_backend_from_device_arg(arg).map(|(s, backend)| (Connection::Vfio(s), backend))?,
+ );
+ }
+ for arg in opts.vvu_tap_fd.iter() {
+ devices.push(
+ new_backend_from_tapfd_arg(arg).map(|(s, backend)| (Connection::Vfio(s), backend))?,
+ );
+ }
+
+ let mut threads = Vec::with_capacity(num_devices);
+
+ for (conn, backend) in devices {
+ match conn {
+ Connection::Socket(socket) => {
+ let ex = Executor::new().context("failed to create executor")?;
+ let handler = DeviceRequestHandler::new(backend);
+ threads.push(thread::spawn(move || {
+ NET_EXECUTOR.with(|thread_ex| {
+ let _ = thread_ex.set(ex.clone());
+ });
+ ex.run_until(handler.run(&socket, &ex))?
+ }));
+ }
+ Connection::Vfio(device_name) => {
+ let device =
+ VvuPciDevice::new(device_name.as_str(), NetBackend::<Tap>::MAX_QUEUE_NUM)?;
+ let handler = DeviceRequestHandler::new(backend);
+ let ex = Executor::new().context("failed to create executor")?;
+ threads.push(thread::spawn(move || {
+ NET_EXECUTOR.with(|thread_ex| {
+ let _ = thread_ex.set(ex.clone());
+ });
+ ex.run_until(handler.run_vvu(device, &ex))?
+ }));
+ }
+ };
+ }
+
+ for t in threads {
+ match t.join() {
+ Ok(r) => r?,
+ Err(e) => bail!("thread panicked: {:?}", e),
+ }
+ }
+ Ok(())
+}
diff --git a/devices/src/virtio/vhost/user/device/vsock.rs b/devices/src/virtio/vhost/user/device/vsock.rs
new file mode 100644
index 000000000..1d06a22f7
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/vsock.rs
@@ -0,0 +1,665 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::os::unix::fs::OpenOptionsExt;
+use std::{
+ convert::{self, TryFrom, TryInto},
+ fs::{File, OpenOptions},
+ mem::size_of,
+ num::Wrapping,
+ os::unix::net::UnixListener,
+ path::Path,
+ str,
+ sync::{Arc, Mutex as StdMutex},
+};
+
+use anyhow::{bail, Context};
+use argh::FromArgs;
+use base::{
+ clear_fd_flags, error, info, AsRawDescriptor, Event, FromRawDescriptor, IntoRawDescriptor,
+ SafeDescriptor, UnlinkUnixListener,
+};
+use cros_async::{AsyncWrapper, EventAsync, Executor};
+use data_model::{DataInit, Le64};
+use hypervisor::ProtectionType;
+use sync::Mutex;
+use vhost::{self, Vhost, Vsock};
+use vm_memory::GuestMemory;
+use vmm_vhost::{
+ connection::vfio::{Endpoint as VfioEndpoint, Listener as VfioListener},
+ message::{
+ VhostUserConfigFlags, VhostUserInflight, VhostUserMemoryRegion, VhostUserProtocolFeatures,
+ VhostUserSingleMemoryRegion, VhostUserVirtioFeatures, VhostUserVringAddrFlags,
+ VhostUserVringState,
+ },
+ Error, Result, SlaveReqHandler, VhostUserSlaveReqHandlerMut,
+};
+use vmm_vhost::{Protocol, SlaveListener};
+
+use crate::{
+ vfio::VfioRegionAddr,
+ virtio::{
+ base_features,
+ vhost::{
+ user::device::{
+ handler::{
+ create_guest_memory, create_vvu_guest_memory, vmm_va_to_gpa, HandlerType,
+ MappingInfo,
+ },
+ vvu::{doorbell::DoorbellRegion, pci::VvuPciDevice, VvuDevice},
+ },
+ vsock,
+ },
+ Queue, SignalableInterrupt,
+ },
+};
+
+const MAX_VRING_LEN: u16 = vsock::QUEUE_SIZE;
+const NUM_QUEUES: usize = vsock::QUEUE_SIZES.len();
+const EVENT_QUEUE: usize = NUM_QUEUES - 1;
+
+struct VsockBackend {
+ ex: Executor,
+ handle: Vsock,
+ cid: u64,
+ features: u64,
+ handler_type: HandlerType,
+ protocol_features: VhostUserProtocolFeatures,
+ mem: Option<GuestMemory>,
+ vmm_maps: Option<Vec<MappingInfo>>,
+ queues: [Queue; NUM_QUEUES],
+ // Only used for vvu device mode.
+ call_evts: [Option<Arc<Mutex<DoorbellRegion>>>; NUM_QUEUES],
+}
+
+impl VsockBackend {
+ fn new<P: AsRef<Path>>(
+ ex: &Executor,
+ cid: u64,
+ vhost_socket: P,
+ handler_type: HandlerType,
+ ) -> anyhow::Result<VsockBackend> {
+ let handle = Vsock::new(
+ OpenOptions::new()
+ .read(true)
+ .write(true)
+ .custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK)
+ .open(vhost_socket)
+ .context("failed to open `Vsock` socket")?,
+ );
+
+ let features = handle.get_features().context("failed to get features")?;
+ let protocol_features = VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIG;
+ Ok(VsockBackend {
+ ex: ex.clone(),
+ handle,
+ cid,
+ features,
+ handler_type,
+ protocol_features,
+ mem: None,
+ vmm_maps: None,
+ queues: [
+ Queue::new(MAX_VRING_LEN),
+ Queue::new(MAX_VRING_LEN),
+ Queue::new(MAX_VRING_LEN),
+ ],
+ call_evts: Default::default(),
+ })
+ }
+}
+
+fn convert_vhost_error(err: vhost::Error) -> Error {
+ use vhost::Error::*;
+ match err {
+ IoctlError(e) => Error::ReqHandlerError(e),
+ _ => Error::SlaveInternalError,
+ }
+}
+
+impl VhostUserSlaveReqHandlerMut for VsockBackend {
+ fn protocol(&self) -> Protocol {
+ match self.handler_type {
+ HandlerType::VhostUser => Protocol::Regular,
+ HandlerType::Vvu { .. } => Protocol::Virtio,
+ }
+ }
+
+ fn set_owner(&mut self) -> Result<()> {
+ self.handle.set_owner().map_err(convert_vhost_error)
+ }
+
+ fn reset_owner(&mut self) -> Result<()> {
+ self.handle.reset_owner().map_err(convert_vhost_error)
+ }
+
+ fn get_features(&mut self) -> Result<u64> {
+ let features = base_features(ProtectionType::Unprotected)
+ | self.features
+ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ Ok(features)
+ }
+
+ fn set_features(&mut self, features: u64) -> Result<()> {
+ self.handle
+ .set_features(features & self.features)
+ .map_err(convert_vhost_error)
+ }
+
+ fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures> {
+ Ok(self.protocol_features)
+ }
+
+ fn set_protocol_features(&mut self, features: u64) -> Result<()> {
+ let unrequested_features = features & !self.protocol_features.bits();
+ if unrequested_features != 0 {
+ Err(Error::InvalidParam)
+ } else {
+ Ok(())
+ }
+ }
+
+ fn set_mem_table(
+ &mut self,
+ contexts: &[VhostUserMemoryRegion],
+ files: Vec<File>,
+ ) -> Result<()> {
+ let (guest_mem, vmm_maps) = match &self.handler_type {
+ HandlerType::VhostUser => create_guest_memory(contexts, files)?,
+ HandlerType::Vvu { vfio_dev, caps, .. } => {
+ // virtio-vhost-user doesn't pass FDs.
+ if !files.is_empty() {
+ return Err(Error::InvalidParam);
+ }
+ create_vvu_guest_memory(vfio_dev.as_ref(), caps.shared_mem_cfg_addr(), contexts)?
+ }
+ };
+
+ self.handle
+ .set_mem_table(&guest_mem)
+ .map_err(convert_vhost_error)?;
+
+ self.mem = Some(guest_mem);
+ self.vmm_maps = Some(vmm_maps);
+
+ Ok(())
+ }
+
+ fn get_queue_num(&mut self) -> Result<u64> {
+ Ok(NUM_QUEUES as u64)
+ }
+
+ fn set_vring_num(&mut self, index: u32, num: u32) -> Result<()> {
+ if index >= NUM_QUEUES as u32 || num == 0 || num > vsock::QUEUE_SIZE.into() {
+ return Err(Error::InvalidParam);
+ }
+
+ // We checked these values already.
+ let index = index as usize;
+ let num = num as u16;
+ self.queues[index].size = num;
+
+ // The last vq is an event-only vq that is not handled by the kernel.
+ if index == EVENT_QUEUE {
+ return Ok(());
+ }
+
+ self.handle
+ .set_vring_num(index, num)
+ .map_err(convert_vhost_error)
+ }
+
+ fn set_vring_addr(
+ &mut self,
+ index: u32,
+ flags: VhostUserVringAddrFlags,
+ descriptor: u64,
+ used: u64,
+ available: u64,
+ log: u64,
+ ) -> Result<()> {
+ if index >= NUM_QUEUES as u32 {
+ return Err(Error::InvalidParam);
+ }
+
+ let index = index as usize;
+
+ let mem = self.mem.as_ref().ok_or(Error::InvalidParam)?;
+ let maps = self.vmm_maps.as_ref().ok_or(Error::InvalidParam)?;
+
+ let mut queue = &mut self.queues[index];
+ queue.desc_table = vmm_va_to_gpa(maps, descriptor)?;
+ queue.avail_ring = vmm_va_to_gpa(maps, available)?;
+ queue.used_ring = vmm_va_to_gpa(maps, used)?;
+ let log_addr = if flags.contains(VhostUserVringAddrFlags::VHOST_VRING_F_LOG) {
+ vmm_va_to_gpa(maps, log).map(Some)?
+ } else {
+ None
+ };
+
+ if index == EVENT_QUEUE {
+ return Ok(());
+ }
+
+ self.handle
+ .set_vring_addr(
+ mem,
+ queue.max_size,
+ queue.actual_size(),
+ index,
+ flags.bits(),
+ queue.desc_table,
+ queue.used_ring,
+ queue.avail_ring,
+ log_addr,
+ )
+ .map_err(convert_vhost_error)
+ }
+
+ fn set_vring_base(&mut self, index: u32, base: u32) -> Result<()> {
+ if index >= NUM_QUEUES as u32 || base >= vsock::QUEUE_SIZE.into() {
+ return Err(Error::InvalidParam);
+ }
+
+ let index = index as usize;
+ let base = base as u16;
+
+ let mut queue = &mut self.queues[index];
+ queue.next_avail = Wrapping(base);
+ queue.next_used = Wrapping(base);
+
+ if index == EVENT_QUEUE {
+ return Ok(());
+ }
+
+ self.handle
+ .set_vring_base(index, base)
+ .map_err(convert_vhost_error)
+ }
+
+ fn get_vring_base(&mut self, index: u32) -> Result<VhostUserVringState> {
+ if index >= NUM_QUEUES as u32 {
+ return Err(Error::InvalidParam);
+ }
+
+ let index = index as usize;
+ let next_avail = if index == EVENT_QUEUE {
+ self.queues[index].next_avail.0
+ } else {
+ self.handle
+ .get_vring_base(index)
+ .map_err(convert_vhost_error)?
+ };
+
+ Ok(VhostUserVringState::new(index as u32, next_avail.into()))
+ }
+
+ fn set_vring_kick(&mut self, index: u8, fd: Option<File>) -> Result<()> {
+ if index >= NUM_QUEUES as u8 {
+ return Err(Error::InvalidParam);
+ }
+
+ let index = usize::from(index);
+ let event = match &self.handler_type {
+ HandlerType::Vvu {
+ notification_evts, ..
+ } => {
+ if fd.is_some() {
+ return Err(Error::InvalidParam);
+ }
+ let queue = &mut self.queues[index];
+ if queue.ready {
+ error!("kick fd cannot replaced after queue is started");
+ return Err(Error::InvalidOperation);
+ }
+
+ notification_evts[index].try_clone().map_err(|e| {
+ error!("failed to clone notification_evts[{}]: {}", index, e);
+ Error::InvalidOperation
+ })?
+ }
+ HandlerType::VhostUser => {
+ let file = fd.ok_or(Error::InvalidParam)?;
+
+ // Safe because the descriptor is uniquely owned by `file`.
+ let event = unsafe { Event::from_raw_descriptor(file.into_raw_descriptor()) };
+
+ // Remove O_NONBLOCK from the kick fd.
+ if let Err(e) = clear_fd_flags(event.as_raw_descriptor(), libc::O_NONBLOCK) {
+ error!("failed to remove O_NONBLOCK for kick fd: {}", e);
+ return Err(Error::InvalidParam);
+ }
+
+ event
+ }
+ };
+
+ if index != EVENT_QUEUE {
+ self.handle
+ .set_vring_kick(index, &event)
+ .map_err(convert_vhost_error)?;
+ }
+
+ Ok(())
+ }
+
+ fn set_vring_call(&mut self, index: u8, fd: Option<File>) -> Result<()> {
+ if index >= NUM_QUEUES as u8 {
+ return Err(Error::InvalidParam);
+ }
+
+ let index = usize::from(index);
+ let event = match &self.handler_type {
+ HandlerType::Vvu { vfio_dev, caps, .. } => {
+ let vfio = Arc::clone(vfio_dev);
+ let base = caps.doorbell_base_addr();
+ let addr = VfioRegionAddr {
+ index: base.index,
+ addr: base.addr + (index as u64 * caps.doorbell_off_multiplier() as u64),
+ };
+
+ let doorbell = DoorbellRegion {
+ vfio,
+ index: index as u8,
+ addr,
+ };
+ let call_evt = match self.call_evts[index].as_ref() {
+ None => {
+ let evt = Arc::new(Mutex::new(doorbell));
+ self.call_evts[index] = Some(evt.clone());
+ evt
+ }
+ Some(evt) => {
+ *evt.lock() = doorbell;
+ evt.clone()
+ }
+ };
+
+ let kernel_evt = Event::new().map_err(|_| Error::SlaveInternalError)?;
+ let task_evt = EventAsync::new(
+ kernel_evt.try_clone().expect("failed to clone event").0,
+ &self.ex,
+ )
+ .map_err(|_| Error::SlaveInternalError)?;
+ self.ex
+ .spawn_local(async move {
+ loop {
+ let _ = task_evt
+ .next_val()
+ .await
+ .expect("failed to wait for event fd");
+ call_evt.signal_used_queue(index as u16);
+ }
+ })
+ .detach();
+ kernel_evt
+ }
+ HandlerType::VhostUser => {
+ let file = fd.ok_or(Error::InvalidParam)?;
+ // Safe because the descriptor is uniquely owned by `file`.
+ unsafe { Event::from_raw_descriptor(file.into_raw_descriptor()) }
+ }
+ };
+ if index != EVENT_QUEUE {
+ self.handle
+ .set_vring_call(index, &event)
+ .map_err(convert_vhost_error)?;
+ }
+
+ Ok(())
+ }
+
+ fn set_vring_err(&mut self, index: u8, fd: Option<File>) -> Result<()> {
+ if index >= NUM_QUEUES as u8 {
+ return Err(Error::InvalidParam);
+ }
+
+ let index = usize::from(index);
+ let file = fd.ok_or(Error::InvalidParam)?;
+
+ // Safe because the descriptor is uniquely owned by `file`.
+ let event = unsafe { Event::from_raw_descriptor(file.into_raw_descriptor()) };
+
+ if index == EVENT_QUEUE {
+ return Ok(());
+ }
+
+ self.handle
+ .set_vring_err(index, &event)
+ .map_err(convert_vhost_error)
+ }
+
+ fn set_vring_enable(&mut self, index: u32, enable: bool) -> Result<()> {
+ if index >= NUM_QUEUES as u32 {
+ return Err(Error::InvalidParam);
+ }
+
+ self.queues[index as usize].ready = enable;
+
+ if index == (EVENT_QUEUE) as u32 {
+ return Ok(());
+ }
+
+ if self.queues[..EVENT_QUEUE].iter().all(|q| q.ready) {
+ // All queues are ready. Start the device.
+ self.handle.set_cid(self.cid).map_err(convert_vhost_error)?;
+ self.handle.start().map_err(convert_vhost_error)
+ } else if !enable {
+ // If we just disabled a vring then stop the device.
+ self.handle.stop().map_err(convert_vhost_error)
+ } else {
+ Ok(())
+ }
+ }
+
+ fn get_config(
+ &mut self,
+ offset: u32,
+ size: u32,
+ _flags: VhostUserConfigFlags,
+ ) -> Result<Vec<u8>> {
+ let start: usize = offset.try_into().map_err(|_| Error::InvalidParam)?;
+ let end: usize = offset
+ .checked_add(size)
+ .and_then(|e| e.try_into().ok())
+ .ok_or(Error::InvalidParam)?;
+
+ if start >= size_of::<Le64>() || end > size_of::<Le64>() {
+ return Err(Error::InvalidParam);
+ }
+
+ Ok(Le64::from(self.cid).as_slice()[start..end].to_vec())
+ }
+
+ fn set_config(
+ &mut self,
+ _offset: u32,
+ _buf: &[u8],
+ _flags: VhostUserConfigFlags,
+ ) -> Result<()> {
+ Err(Error::InvalidOperation)
+ }
+
+ fn set_slave_req_fd(&mut self, _vu_req: File) {}
+
+ fn get_inflight_fd(
+ &mut self,
+ _inflight: &VhostUserInflight,
+ ) -> Result<(VhostUserInflight, File)> {
+ Err(Error::InvalidOperation)
+ }
+
+ fn set_inflight_fd(&mut self, _inflight: &VhostUserInflight, _file: File) -> Result<()> {
+ Err(Error::InvalidOperation)
+ }
+
+ fn get_max_mem_slots(&mut self) -> Result<u64> {
+ Err(Error::InvalidOperation)
+ }
+
+ fn add_mem_region(&mut self, _region: &VhostUserSingleMemoryRegion, _fd: File) -> Result<()> {
+ Err(Error::InvalidOperation)
+ }
+
+ fn remove_mem_region(&mut self, _region: &VhostUserSingleMemoryRegion) -> Result<()> {
+ Err(Error::InvalidOperation)
+ }
+}
+
+async fn run_device<P: AsRef<Path>>(
+ ex: &Executor,
+ socket: P,
+ backend: Arc<StdMutex<VsockBackend>>,
+) -> anyhow::Result<()> {
+ let listener = UnixListener::bind(socket)
+ .map(UnlinkUnixListener)
+ .context("failed to bind socket")?;
+ let (socket, _) = ex
+ .spawn_blocking(move || listener.accept())
+ .await
+ .context("failed to accept socket connection")?;
+
+ let mut req_handler = SlaveReqHandler::from_stream(socket, backend);
+ let h = SafeDescriptor::try_from(&req_handler as &dyn AsRawDescriptor)
+ .map(AsyncWrapper::new)
+ .expect("failed to get safe descriptor for handler");
+ let handler_source = ex.async_from(h).context("failed to create async handler")?;
+
+ loop {
+ handler_source
+ .wait_readable()
+ .await
+ .context("failed to wait for vhost socket to become readable")?;
+ match req_handler.handle_request() {
+ Ok(()) => (),
+ Err(Error::Disconnect) => {
+ info!("vhost-user connection closed");
+ // Exit as the client closed the connection.
+ return Ok(());
+ }
+ Err(e) => {
+ bail!("failed to handle a vhost-user request: {}", e);
+ }
+ };
+ }
+}
+
+#[derive(FromArgs)]
+#[argh(description = "")]
+struct Options {
+ #[argh(
+ option,
+ description = "path to bind a listening vhost-user socket",
+ arg_name = "PATH"
+ )]
+ socket: Option<String>,
+ #[argh(option, description = "name of vfio pci device", arg_name = "STRING")]
+ vfio: Option<String>,
+ #[argh(
+ option,
+ description = "the vsock context id for this device",
+ arg_name = "INT"
+ )]
+ cid: u64,
+ #[argh(
+ option,
+ description = "path to the vhost-vsock control socket",
+ default = "String::from(\"/dev/vhost-vsock\")",
+ arg_name = "PATH"
+ )]
+ vhost_socket: String,
+}
+
+fn run_vvu_device<P: AsRef<Path>>(
+ ex: &Executor,
+ cid: u64,
+ vhost_socket: P,
+ device_name: &str,
+) -> anyhow::Result<()> {
+ let mut device =
+ VvuPciDevice::new(device_name, NUM_QUEUES).context("failed to create `VvuPciDevice`")?;
+ let backend = VsockBackend::new(
+ ex,
+ cid,
+ vhost_socket,
+ HandlerType::Vvu {
+ vfio_dev: Arc::clone(&device.vfio_dev),
+ caps: device.caps.clone(),
+ notification_evts: std::mem::take(&mut device.notification_evts),
+ },
+ )
+ .map(StdMutex::new)
+ .map(Arc::new)
+ .context("failed to create `VsockBackend`")?;
+ let driver = VvuDevice::new(device);
+
+ let mut listener = VfioListener::new(driver)
+ .context("failed to create `VfioListener`")
+ .and_then(|l| {
+ SlaveListener::<VfioEndpoint<_, _>, _>::new(l, backend)
+ .context("failed to create `SlaveListener`")
+ })?;
+ let mut req_handler = listener
+ .accept()
+ .context("failed to accept vfio connection")?
+ .expect("no incoming connection detected");
+ let h = SafeDescriptor::try_from(&req_handler as &dyn AsRawDescriptor)
+ .map(AsyncWrapper::new)
+ .expect("failed to get safe descriptor for handler");
+ let handler_source = ex
+ .async_from(h)
+ .context("failed to create async handler source")?;
+
+ let done = async move {
+ loop {
+ let count = handler_source
+ .read_u64()
+ .await
+ .context("failed to wait for handler source")?;
+ for _ in 0..count {
+ req_handler
+ .handle_request()
+ .context("failed to handle request")?;
+ }
+ }
+ };
+ match ex.run_until(done) {
+ Ok(Ok(())) => Ok(()),
+ Ok(Err(e)) => Err(e),
+ Err(e) => Err(e).context("executor error"),
+ }
+}
+
+/// Returns an error if the given `args` is invalid or the device fails to run.
+pub fn run_vsock_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
+ let opts = match Options::from_args(&[program_name], args) {
+ Ok(opts) => opts,
+ Err(e) => {
+ if e.status.is_err() {
+ bail!(e.output);
+ } else {
+ println!("{}", e.output);
+ }
+ return Ok(());
+ }
+ };
+
+ let ex = Executor::new().context("failed to create executor")?;
+
+ match (opts.socket, opts.vfio) {
+ (Some(socket), None) => {
+ let backend =
+ VsockBackend::new(&ex, opts.cid, opts.vhost_socket, HandlerType::VhostUser)
+ .map(StdMutex::new)
+ .map(Arc::new)?;
+
+ // TODO: Replace the `and_then` with `Result::flatten` once it is stabilized.
+ ex.run_until(run_device(&ex, socket, backend))
+ .context("failed to run vsock device")
+ .and_then(convert::identity)
+ }
+ (None, Some(device_name)) => run_vvu_device(&ex, opts.cid, opts.vhost_socket, &device_name),
+ _ => bail!("Exactly one of `--socket` or `--vfio` is required"),
+ }
+}
diff --git a/devices/src/virtio/vhost/user/device/vvu.rs b/devices/src/virtio/vhost/user/device/vvu.rs
new file mode 100644
index 000000000..0e0d0be62
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/vvu.rs
@@ -0,0 +1,13 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Implement a userspace driver for the virtio vhost-user device.
+
+mod bus;
+pub mod device;
+pub mod doorbell;
+pub mod pci;
+mod queue;
+
+pub use device::*;
diff --git a/devices/src/virtio/vhost/user/device/vvu/bus.rs b/devices/src/virtio/vhost/user/device/vvu/bus.rs
new file mode 100644
index 000000000..6cc667c6b
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/vvu/bus.rs
@@ -0,0 +1,62 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Provides utilities to bind and open a VFIO PCI device.
+
+use std::fs::write;
+use std::sync::Arc;
+
+use anyhow::{anyhow, Context, Result};
+use sync::Mutex;
+
+use crate::pci::PciAddress;
+use crate::vfio::{VfioContainer, VfioDevice};
+
+/// Opens the PCI device as `VfioDevice`.
+pub fn open_vfio_device(pci_address: PciAddress) -> Result<VfioDevice> {
+ // Convert the PCI address to "DDDD:bb:dd.f" format.
+ let addr_str = pci_address.to_string();
+
+ // Unbind virtio-pci from the device.
+ write("/sys/bus/pci/drivers/virtio-pci/unbind", &addr_str)
+ .context("failed to unbind as virtio-pci device")?;
+
+ // Set "vfio-pci" for the driver so the device can be bound to our vfio driver.
+ write(
+ format!("/sys/bus/pci/devices/{}/driver_override", &addr_str),
+ "vfio-pci\n",
+ )
+ .context("failed to override driver")?;
+
+ // Bind the device to the driver.
+ write("/sys/bus/pci/drivers/vfio-pci/bind", &addr_str).context("failed to bind VFIO device")?;
+
+ // Write the empty string to driver_override to return the device to standard matching rules
+ // binding. Note that this operation won't unbind the current driver or load a new driver.
+ write(
+ format!("/sys/bus/pci/devices/{}/driver_override", &addr_str),
+ "",
+ )
+ .context("failed to clear driver_override")?;
+
+ let vfio_path = format!("/sys/bus/pci/devices/{}", &addr_str);
+ let mut last_err = None;
+ // We can't create the container until udev updates the permissions on
+ // /dev/vfio/$group_id. There's no easy way to wait for that to happen, so
+ // just poll. In practice, this should take <100ms.
+ for _ in 1..50 {
+ let vfio_container = Arc::new(Mutex::new(VfioContainer::new()?));
+ match VfioDevice::new(&vfio_path, vfio_container) {
+ Ok(vfio_dev) => return Ok(vfio_dev),
+ Err(e) => {
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ last_err = Some(e);
+ }
+ }
+ }
+ Err(anyhow!(
+ "failed to create VFIO device: {}",
+ last_err.unwrap()
+ ))
+}
diff --git a/devices/src/virtio/vhost/user/device/vvu/device.rs b/devices/src/virtio/vhost/user/device/vvu/device.rs
new file mode 100644
index 000000000..e28f5f8e9
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/vvu/device.rs
@@ -0,0 +1,254 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Implement a struct that works as a `vmm_vhost`'s backend.
+
+use std::io::{IoSlice, IoSliceMut};
+use std::mem;
+use std::os::unix::io::RawFd;
+use std::sync::mpsc::{channel, Receiver, Sender};
+use std::sync::Arc;
+use std::thread;
+
+use anyhow::{anyhow, bail, Context, Result};
+use base::{error, Event};
+use cros_async::{EventAsync, Executor};
+use futures::{pin_mut, select, FutureExt};
+use sync::Mutex;
+use vmm_vhost::connection::vfio::{Device as VfioDeviceTrait, RecvIntoBufsError};
+
+use crate::vfio::VfioDevice;
+use crate::virtio::vhost::user::device::vvu::{
+ pci::{QueueNotifier, QueueType, VvuPciDevice},
+ queue::UserQueue,
+};
+
+async fn process_rxq(
+ evt: EventAsync,
+ mut rxq: UserQueue,
+ rxq_sender: Sender<Vec<u8>>,
+ rxq_evt: Event,
+) -> Result<()> {
+ loop {
+ if let Err(e) = evt.next_val().await {
+ error!("Failed to read the next queue event: {}", e);
+ continue;
+ }
+
+ while let Some(slice) = rxq.read_data()? {
+ let mut buf = vec![0; slice.size()];
+ slice.copy_to(&mut buf);
+ rxq_sender.send(buf)?;
+ rxq_evt.write(1).context("process_rxq")?;
+ }
+ }
+}
+
+async fn process_txq(evt: EventAsync, txq: Arc<Mutex<UserQueue>>) -> Result<()> {
+ loop {
+ if let Err(e) = evt.next_val().await {
+ error!("Failed to read the next queue event: {}", e);
+ continue;
+ }
+
+ txq.lock().ack_used()?;
+ }
+}
+
+fn run_worker(
+ ex: Executor,
+ rx_queue: UserQueue,
+ rx_irq: Event,
+ rx_sender: Sender<Vec<u8>>,
+ rx_evt: Event,
+ tx_queue: Arc<Mutex<UserQueue>>,
+ tx_irq: Event,
+) -> Result<()> {
+ let rx_irq = EventAsync::new(rx_irq.0, &ex).context("failed to create async event")?;
+ let rxq = process_rxq(rx_irq, rx_queue, rx_sender, rx_evt);
+ pin_mut!(rxq);
+
+ let tx_irq = EventAsync::new(tx_irq.0, &ex).context("failed to create async event")?;
+ let txq = process_txq(tx_irq, Arc::clone(&tx_queue));
+ pin_mut!(txq);
+
+ let done = async {
+ select! {
+ res = rxq.fuse() => res.context("failed to handle rxq"),
+ res = txq.fuse() => res.context("failed to handle txq"),
+ }
+ };
+
+ match ex.run_until(done) {
+ Ok(_) => Ok(()),
+ Err(e) => {
+ bail!("failed to process virtio-vhost-user queues: {}", e);
+ }
+ }
+}
+
+enum DeviceState {
+ Initialized {
+ // TODO(keiichiw): Update `VfioDeviceTrait::start()` to take `VvuPciDevice` so that we can
+ // drop this field.
+ device: VvuPciDevice,
+ },
+ Running {
+ vfio: Arc<VfioDevice>,
+
+ rxq_notifier: Arc<Mutex<QueueNotifier>>,
+ rxq_receiver: Receiver<Vec<u8>>,
+ /// Store data that was provided by rxq_receiver but not consumed yet.
+ rxq_buf: Vec<u8>,
+
+ txq: Arc<Mutex<UserQueue>>,
+ txq_notifier: Arc<Mutex<QueueNotifier>>,
+ },
+}
+
+pub struct VvuDevice {
+ state: DeviceState,
+ rxq_evt: Event,
+}
+
+impl VvuDevice {
+ pub fn new(device: VvuPciDevice) -> Self {
+ Self {
+ state: DeviceState::Initialized { device },
+ rxq_evt: Event::new().expect("failed to create VvuDevice's rxq_evt"),
+ }
+ }
+}
+
+impl VfioDeviceTrait for VvuDevice {
+ fn event(&self) -> &Event {
+ &self.rxq_evt
+ }
+
+ fn start(&mut self) -> Result<()> {
+ let device = match &mut self.state {
+ DeviceState::Initialized { device } => device,
+ DeviceState::Running { .. } => {
+ bail!("VvuDevice has already started");
+ }
+ };
+ let ex = Executor::new().expect("Failed to create an executor");
+
+ let mut irqs = mem::take(&mut device.irqs);
+ let mut queues = mem::take(&mut device.queues);
+ let mut queue_notifiers = mem::take(&mut device.queue_notifiers);
+ let vfio = Arc::clone(&device.vfio_dev);
+
+ let rxq = queues.remove(0);
+ let rxq_irq = irqs.remove(0);
+ let rxq_notifier = Arc::new(Mutex::new(queue_notifiers.remove(0)));
+ // TODO: Can we use async channel instead so we don't need `rxq_evt`?
+ let (rxq_sender, rxq_receiver) = channel();
+ let rxq_evt = self.rxq_evt.try_clone().expect("rxq_evt clone");
+
+ let txq = Arc::new(Mutex::new(queues.remove(0)));
+ let txq_cloned = Arc::clone(&txq);
+ let txq_irq = irqs.remove(0);
+ let txq_notifier = Arc::new(Mutex::new(queue_notifiers.remove(0)));
+
+ let old_state = std::mem::replace(
+ &mut self.state,
+ DeviceState::Running {
+ vfio,
+ rxq_notifier,
+ rxq_receiver,
+ rxq_buf: vec![],
+ txq,
+ txq_notifier,
+ },
+ );
+
+ let device = match old_state {
+ DeviceState::Initialized { device } => device,
+ _ => unreachable!(),
+ };
+
+ thread::Builder::new()
+ .name("virtio-vhost-user driver".to_string())
+ .spawn(move || {
+ device.start().expect("failed to start device");
+ if let Err(e) =
+ run_worker(ex, rxq, rxq_irq, rxq_sender, rxq_evt, txq_cloned, txq_irq)
+ {
+ error!("worker thread exited with error: {}", e);
+ }
+ })?;
+
+ Ok(())
+ }
+
+ fn send_bufs(&mut self, iovs: &[IoSlice], fds: Option<&[RawFd]>) -> Result<usize> {
+ if fds.is_some() {
+ bail!("cannot send FDs");
+ }
+
+ let (txq, txq_notifier, vfio) = match &mut self.state {
+ DeviceState::Initialized { .. } => {
+ bail!("VvuDevice hasn't started yet");
+ }
+ DeviceState::Running {
+ vfio,
+ txq,
+ txq_notifier,
+ ..
+ } => (txq, txq_notifier, vfio),
+ };
+
+ let size = iovs.iter().map(|v| v.len()).sum();
+ let data: Vec<u8> = iovs.iter().flat_map(|v| v.to_vec()).collect();
+
+ txq.lock().write(&data).context("Failed to send data")?;
+ txq_notifier.lock().notify(vfio, QueueType::Tx as u16);
+
+ Ok(size)
+ }
+
+ fn recv_into_bufs(&mut self, bufs: &mut [IoSliceMut]) -> Result<usize, RecvIntoBufsError> {
+ let (rxq_receiver, rxq_notifier, rxq_buf, vfio) = match &mut self.state {
+ DeviceState::Initialized { .. } => {
+ return Err(RecvIntoBufsError::Fatal(anyhow!(
+ "VvuDevice hasn't started yet"
+ )));
+ }
+ DeviceState::Running {
+ rxq_receiver,
+ rxq_notifier,
+ rxq_buf,
+ vfio,
+ ..
+ } => (rxq_receiver, rxq_notifier, rxq_buf, vfio),
+ };
+
+ let mut size = 0;
+ for buf in bufs {
+ let len = buf.len();
+
+ while rxq_buf.len() < len {
+ let mut data = rxq_receiver
+ .recv()
+ .context("failed to receive data")
+ .map_err(RecvIntoBufsError::Fatal)?;
+ rxq_buf.append(&mut data);
+ }
+
+ buf.clone_from_slice(&rxq_buf[..len]);
+
+ rxq_buf.drain(0..len);
+ size += len;
+
+ rxq_notifier.lock().notify(vfio, QueueType::Rx as u16);
+ }
+
+ if size == 0 {
+ // TODO(b/216407443): We should change `self.state` and exit gracefully.
+ return Err(RecvIntoBufsError::Disconnect);
+ }
+ Ok(size)
+ }
+}
diff --git a/devices/src/virtio/vhost/user/device/vvu/doorbell.rs b/devices/src/virtio/vhost/user/device/vvu/doorbell.rs
new file mode 100644
index 000000000..0aad0b9ab
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/vvu/doorbell.rs
@@ -0,0 +1,33 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::sync::Arc;
+
+use base::Event;
+
+use crate::vfio::{VfioDevice, VfioRegionAddr};
+use crate::virtio::SignalableInterrupt;
+
+/// Doorbell region in the VVU device's additional BAR.
+/// Writing to this area will sends a signal to the sibling VM's vhost-user device.
+pub struct DoorbellRegion {
+ pub vfio: Arc<VfioDevice>,
+ pub index: u8,
+ pub addr: VfioRegionAddr,
+}
+
+impl SignalableInterrupt for DoorbellRegion {
+ fn signal(&self, _vector: u16, _interrupt_status_mask: u32) {
+ // Write `1` to the doorbell, which will be forwarded to the sibling's call FD.
+ self.vfio.region_write_to_addr(&1, &self.addr, 0);
+ }
+
+ fn signal_config_changed(&self) {}
+
+ fn get_resample_evt(&self) -> Option<&Event> {
+ None
+ }
+
+ fn do_interrupt_resample(&self) {}
+}
diff --git a/devices/src/virtio/vhost/user/device/vvu/pci.rs b/devices/src/virtio/vhost/user/device/vvu/pci.rs
new file mode 100644
index 000000000..9cc70a04a
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/vvu/pci.rs
@@ -0,0 +1,516 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Implement a userspace PCI device driver for the virtio vhost-user device.
+
+use std::str::FromStr;
+use std::sync::Arc;
+use std::time::{Duration, Instant};
+
+use anyhow::{anyhow, bail, Context, Result};
+use base::{info, Event};
+use data_model::DataInit;
+use memoffset::offset_of;
+use resources::Alloc;
+use vfio_sys::*;
+use virtio_sys::vhost::VIRTIO_F_VERSION_1;
+
+use crate::pci::{MsixCap, PciAddress, PciCapabilityID, CAPABILITY_LIST_HEAD_OFFSET};
+use crate::vfio::{VfioDevice, VfioPciConfig, VfioRegionAddr};
+use crate::virtio::vhost::user::device::vvu::{
+ bus::open_vfio_device,
+ queue::{DescTableAddrs, IovaAllocator, UserQueue},
+};
+use crate::virtio::{PciCapabilityType, VirtioPciCap};
+
+const VIRTIO_CONFIG_STATUS_RESET: u8 = 0;
+
+fn get_pci_cap_addr(cap: &VirtioPciCap) -> Result<VfioRegionAddr> {
+ const PCI_MAX_RESOURCE: u8 = 6;
+
+ if cap.bar >= PCI_MAX_RESOURCE {
+ bail!("invalid bar: {:?} >= {}", cap.bar, PCI_MAX_RESOURCE);
+ }
+
+ if u32::from(cap.offset)
+ .checked_add(u32::from(cap.length))
+ .is_none()
+ {
+ bail!("overflow: {:?} + {:?}", cap.offset, cap.length);
+ }
+
+ Ok(VfioRegionAddr {
+ index: cap.bar.into(),
+ addr: u32::from(cap.offset) as u64,
+ })
+}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+/// VirtIO spec: 4.1.4.3 Common configuration structure layout
+struct virtio_pci_common_cfg {
+ // For the whole device.
+ device_feature_select: u32,
+ device_feature: u32,
+ guest_feature_select: u32,
+ guest_feature: u32,
+ msix_config: u16,
+ num_queues: u16,
+ device_status: u8,
+ config_generation: u8,
+
+ // For a specific virtqueue.
+ queue_select: u16,
+ queue_size: u16,
+ queue_msix_vector: u16,
+ queue_enable: u16,
+ queue_notify_off: u16,
+ queue_desc_lo: u32,
+ queue_desc_hi: u32,
+ queue_avail_lo: u32,
+ queue_avail_hi: u32,
+ queue_used_lo: u32,
+ queue_used_hi: u32,
+}
+
+unsafe impl DataInit for virtio_pci_common_cfg {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+struct virtio_pci_notification_cfg {
+ notification_select: u16,
+ notification_msix_vector: u16,
+}
+
+unsafe impl DataInit for virtio_pci_notification_cfg {}
+
+#[derive(Clone)]
+pub struct VvuPciCaps {
+ msix_table_size: u16,
+ common_cfg_addr: VfioRegionAddr,
+ notify_off_multiplier: u32,
+ notify_base_addr: VfioRegionAddr,
+ dev_cfg_addr: VfioRegionAddr,
+ isr_addr: VfioRegionAddr,
+ doorbell_off_multiplier: u32,
+ doorbell_base_addr: VfioRegionAddr,
+ notify_cfg_addr: VfioRegionAddr,
+ shared_mem_cfg_addr: VfioRegionAddr,
+}
+
+impl VvuPciCaps {
+ pub fn new(config: &VfioPciConfig) -> Result<Self> {
+ // Safe because zero is valid for every field in `VvuPciCaps`.
+ let mut caps: Self = unsafe { std::mem::zeroed() };
+
+ // Read PCI capability config one by one and set up each of them.
+ let mut pos: u8 = config.read_config(CAPABILITY_LIST_HEAD_OFFSET as u32);
+ while pos != 0 {
+ let cfg: [u8; 2] = config.read_config(pos.into());
+ let (cap_id, cap_next) = (cfg[0], cfg[1]);
+
+ if cap_id == PciCapabilityID::Msix as u8 {
+ let cap = config.read_config::<MsixCap>(pos.into());
+ // According to PCI 3.0 specification section 6.8.2.3 ("Message Control for MSI-X"),
+ // MSI-X Table Size N, which is encoded as N-1.
+ caps.msix_table_size = cap.msg_ctl().get_table_size() + 1;
+ }
+
+ if cap_id != PciCapabilityID::VendorSpecific as u8 {
+ pos = cap_next;
+ continue;
+ }
+
+ let cap: VirtioPciCap = config.read_config(pos.into());
+
+ let cfg = PciCapabilityType::n(cap.cfg_type)
+ .ok_or_else(|| anyhow!("invalid cfg_type: {}", cap.cfg_type))?;
+ match cfg {
+ PciCapabilityType::CommonConfig => {
+ caps.common_cfg_addr = get_pci_cap_addr(&cap)?;
+ }
+ PciCapabilityType::NotifyConfig => {
+ caps.notify_off_multiplier =
+ config.read_config(pos as u32 + std::mem::size_of::<VirtioPciCap>() as u32);
+ caps.notify_base_addr = get_pci_cap_addr(&cap)?;
+ }
+ PciCapabilityType::IsrConfig => {
+ caps.isr_addr = get_pci_cap_addr(&cap)?;
+ }
+ PciCapabilityType::DeviceConfig => {
+ caps.dev_cfg_addr = get_pci_cap_addr(&cap)?;
+ }
+ PciCapabilityType::PciConfig => {
+ // do nothing
+ }
+ PciCapabilityType::DoorbellConfig => {
+ caps.doorbell_off_multiplier =
+ config.read_config(pos as u32 + std::mem::size_of::<VirtioPciCap>() as u32);
+ caps.doorbell_base_addr = get_pci_cap_addr(&cap)?;
+ }
+ PciCapabilityType::NotificationConfig => {
+ caps.notify_cfg_addr = get_pci_cap_addr(&cap)?;
+ }
+ PciCapabilityType::SharedMemoryConfig => {
+ caps.shared_mem_cfg_addr = get_pci_cap_addr(&cap)?;
+ }
+ }
+
+ pos = cap.cap_next;
+ }
+
+ Ok(caps)
+ }
+
+ pub fn doorbell_off_multiplier(&self) -> u32 {
+ self.doorbell_off_multiplier
+ }
+
+ pub fn doorbell_base_addr(&self) -> &VfioRegionAddr {
+ &self.doorbell_base_addr
+ }
+
+ pub fn shared_mem_cfg_addr(&self) -> &VfioRegionAddr {
+ &self.shared_mem_cfg_addr
+ }
+}
+
+macro_rules! write_common_cfg_field {
+ ($device:expr, $field:ident, $val:expr) => {
+ $device.vfio_dev.region_write_to_addr(
+ &$val,
+ &$device.caps.common_cfg_addr,
+ offset_of!(virtio_pci_common_cfg, $field) as u64,
+ )
+ };
+}
+
+macro_rules! read_common_cfg_field {
+ ($device:expr, $field:ident) => {
+ $device.vfio_dev.region_read_from_addr(
+ &$device.caps.common_cfg_addr,
+ offset_of!(virtio_pci_common_cfg, $field) as u64,
+ )
+ };
+}
+
+macro_rules! write_notify_cfg_field {
+ ($device:expr, $field:ident, $val:expr) => {
+ $device.vfio_dev.region_write_to_addr(
+ &$val,
+ &$device.caps.notify_cfg_addr,
+ offset_of!(virtio_pci_notification_cfg, $field) as u64,
+ )
+ };
+}
+
+macro_rules! read_notify_cfg_field {
+ ($device:expr, $field:ident) => {
+ $device.vfio_dev.region_read_from_addr(
+ &$device.caps.notify_cfg_addr,
+ offset_of!(virtio_pci_notification_cfg, $field) as u64,
+ )
+ };
+}
+
+/// A wrapper of VVU's notification resource which works as an interrupt for a virtqueue.
+pub struct QueueNotifier(VfioRegionAddr);
+
+impl QueueNotifier {
+ pub fn notify(&self, vfio_dev: &VfioDevice, index: u16) {
+ vfio_dev.region_write_to_addr(&index, &self.0, 0);
+ }
+}
+
+pub struct VvuPciDevice {
+ pub vfio_dev: Arc<VfioDevice>,
+ pub caps: VvuPciCaps,
+ pub queues: Vec<UserQueue>,
+ pub queue_notifiers: Vec<QueueNotifier>,
+ pub irqs: Vec<Event>,
+ pub notification_evts: Vec<Event>,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum QueueType {
+ Rx = 0, // the integer represents the queue index.
+ Tx = 1,
+}
+
+impl VvuPciDevice {
+ /// Creates a driver for virtio-vhost-user PCI device.
+ ///
+ /// # Arguments
+ ///
+ /// * `pci_id` - PCI device ID such as `"0000:00:05.0"`.
+ /// * `device_vq_num` - number of virtqueues that the device backend (e.g. block) may use.
+ pub fn new(pci_id: &str, device_vq_num: usize) -> Result<Self> {
+ let pci_address = PciAddress::from_str(pci_id).context("failed to parse PCI address")?;
+ let vfio_dev = Arc::new(open_vfio_device(pci_address)?);
+ let config = VfioPciConfig::new(vfio_dev.clone());
+ let caps = VvuPciCaps::new(&config)?;
+ vfio_dev
+ .check_device_info()
+ .context("failed to check VFIO device information")?;
+
+ let page_mask = vfio_dev
+ .vfio_get_iommu_page_size_mask()
+ .context("failed to get iommu page size mask")?;
+ if page_mask & (base::pagesize() as u64) == 0 {
+ bail!("Unsupported iommu page mask {:x}", page_mask);
+ }
+
+ let mut pci_dev = Self {
+ vfio_dev,
+ caps,
+ queues: vec![],
+ queue_notifiers: vec![],
+ irqs: vec![],
+ notification_evts: vec![],
+ };
+
+ config.set_bus_master();
+ pci_dev.init(device_vq_num)?;
+
+ Ok(pci_dev)
+ }
+
+ fn set_status(&self, status: u8) {
+ let new_status = if status == VIRTIO_CONFIG_STATUS_RESET {
+ VIRTIO_CONFIG_STATUS_RESET
+ } else {
+ let cur_status: u8 = read_common_cfg_field!(self, device_status);
+ status | cur_status
+ };
+
+ write_common_cfg_field!(self, device_status, new_status);
+ }
+
+ fn get_device_feature(&self) -> u64 {
+ write_common_cfg_field!(self, device_feature_select, 0);
+ let lower: u32 = read_common_cfg_field!(self, device_feature);
+ write_common_cfg_field!(self, device_feature_select, 1);
+ let upper: u32 = read_common_cfg_field!(self, device_feature);
+
+ lower as u64 | ((upper as u64) << 32)
+ }
+
+ fn set_device_feature(&self, features: u64) {
+ let lower: u32 = (features & (u32::MAX as u64)) as u32;
+ let upper: u32 = (features >> 32) as u32;
+ write_common_cfg_field!(self, device_feature_select, 0);
+ write_common_cfg_field!(self, device_feature, lower);
+ write_common_cfg_field!(self, device_feature_select, 1);
+ write_common_cfg_field!(self, device_feature, upper);
+ }
+
+ /// Creates the VVU's virtqueue (i.e. rxq or txq).
+ fn create_queue(&self, typ: QueueType) -> Result<(UserQueue, QueueNotifier)> {
+ write_common_cfg_field!(self, queue_select, typ as u16);
+
+ let queue_size: u16 = read_common_cfg_field!(self, queue_size);
+ if queue_size == 0 {
+ bail!("queue_size for {:?} queue is 0", typ);
+ }
+
+ let device_writable = match typ {
+ QueueType::Rx => true,
+ QueueType::Tx => false,
+ };
+ let queue = UserQueue::new(queue_size, device_writable, typ as u8, self)?;
+ let DescTableAddrs { desc, avail, used } = queue.desc_table_addrs()?;
+
+ let desc_lo = (desc & 0xffffffff) as u32;
+ let desc_hi = (desc >> 32) as u32;
+ write_common_cfg_field!(self, queue_desc_lo, desc_lo);
+ write_common_cfg_field!(self, queue_desc_hi, desc_hi);
+
+ let avail_lo = (avail & 0xffffffff) as u32;
+ let avail_hi = (avail >> 32) as u32;
+ write_common_cfg_field!(self, queue_avail_lo, avail_lo);
+ write_common_cfg_field!(self, queue_avail_hi, avail_hi);
+
+ let used_lo = (used & 0xffffffff) as u32;
+ let used_hi = (used >> 32) as u32;
+ write_common_cfg_field!(self, queue_used_lo, used_lo);
+ write_common_cfg_field!(self, queue_used_hi, used_hi);
+
+ let notify_off: u16 = read_common_cfg_field!(self, queue_notify_off);
+ let mut notify_addr = self.caps.notify_base_addr.clone();
+ notify_addr.addr += notify_off as u64 * self.caps.notify_off_multiplier as u64;
+ let notifier = QueueNotifier(notify_addr);
+
+ write_common_cfg_field!(self, queue_enable, 1_u16);
+
+ Ok((queue, notifier))
+ }
+
+ /// Creates the VVU's rxq and txq.
+ fn create_queues(&self) -> Result<(Vec<UserQueue>, Vec<QueueNotifier>)> {
+ let (rxq, rxq_notifier) = self.create_queue(QueueType::Rx)?;
+ rxq_notifier.notify(&self.vfio_dev, QueueType::Rx as u16);
+
+ let (txq, txq_notifier) = self.create_queue(QueueType::Tx)?;
+ txq_notifier.notify(&self.vfio_dev, QueueType::Tx as u16);
+
+ Ok((vec![rxq, txq], vec![rxq_notifier, txq_notifier]))
+ }
+
+ /// Creates two sets of interrupts events; ones for the VVU virtqueues (i.e. rxq and txq) and
+ /// ones for the device virtqueues.
+ ///
+ /// # Arguments
+ /// * `device_vq_num` - the number of queues for the device.
+ fn create_irqs(&self, device_vq_num: usize) -> Result<(Vec<Event>, Vec<Event>)> {
+ const VIRTIO_MSI_NO_VECTOR: u16 = 0xffff;
+
+ // Sets msix_config
+ write_common_cfg_field!(self, msix_config, 0u16);
+ let v: u16 = read_common_cfg_field!(self, msix_config);
+ if v == VIRTIO_MSI_NO_VECTOR {
+ bail!("failed to set config vector: {}", v);
+ }
+
+ // Creates events for the interrupts of vvu's rxq and txq.
+ let vvu_irqs = vec![
+ Event::new().context("failed to create event")?,
+ Event::new().context("failed to create event")?,
+ ];
+
+ // Create events for the device virtqueue interrupts.
+ let mut notification_evts = Vec::with_capacity(device_vq_num);
+ for _ in 0..device_vq_num {
+ notification_evts.push(Event::new().context("failed to create event")?);
+ }
+
+ let msix_num = 2 + device_vq_num;
+ if msix_num > usize::from(self.caps.msix_table_size) {
+ bail!(
+ "{} MSI-X vector is required but only {} are available.",
+ msix_num,
+ self.caps.msix_table_size
+ );
+ }
+
+ let mut msix_vec = Vec::with_capacity(msix_num);
+ msix_vec.push(Some(&vvu_irqs[0]));
+ msix_vec.push(Some(&vvu_irqs[1]));
+ msix_vec.extend(notification_evts.iter().take(device_vq_num).map(Some));
+
+ self.vfio_dev
+ .irq_enable(&msix_vec, VFIO_PCI_MSIX_IRQ_INDEX, 0)
+ .map_err(|e| anyhow!("failed to enable irq: {}", e))?;
+
+ // Registers VVU virtqueue's irqs by writing `queue_msix_vector`.
+ for index in 0..self.queues.len() {
+ write_common_cfg_field!(self, queue_select, index as u16);
+ write_common_cfg_field!(self, queue_msix_vector, index as u16);
+ let v: u16 = read_common_cfg_field!(self, queue_msix_vector);
+ if v == VIRTIO_MSI_NO_VECTOR {
+ bail!("failed to set vector {} to {}-th vvu virtqueue", v, index);
+ }
+ }
+
+ // Registers the device virtqueus's irqs by writing `notification_msix_vector`.
+ for i in 0..device_vq_num as u16 {
+ let msix_vector = self.queues.len() as u16 + i;
+
+ write_notify_cfg_field!(self, notification_select, i);
+ let select: u16 = read_notify_cfg_field!(self, notification_select);
+ if select != i {
+ bail!("failed to select {}-th notification", i);
+ }
+
+ write_notify_cfg_field!(self, notification_msix_vector, msix_vector);
+ let vector: u16 = read_notify_cfg_field!(self, notification_msix_vector);
+ if msix_vector != vector {
+ bail!(
+ "failed to set vector {} to {}-th notification",
+ msix_vector,
+ i
+ );
+ }
+ }
+
+ Ok((vvu_irqs, notification_evts))
+ }
+
+ fn init(&mut self, device_vq_num: usize) -> Result<()> {
+ self.set_status(VIRTIO_CONFIG_STATUS_RESET as u8);
+ // Wait until reset is done with timeout.
+ let deadline = Instant::now() + Duration::from_secs(1);
+ loop {
+ let cur_status: u8 = read_common_cfg_field!(self, device_status);
+ if cur_status == 0 {
+ break;
+ }
+ if Instant::now() < deadline {
+ std::thread::sleep(Duration::from_millis(10));
+ } else {
+ bail!("device initialization didn't finish within the time limit");
+ }
+ }
+
+ self.set_status(
+ (virtio_sys::vhost::VIRTIO_CONFIG_S_ACKNOWLEDGE
+ | virtio_sys::vhost::VIRTIO_CONFIG_S_DRIVER) as u8,
+ );
+
+ // TODO(b/207364742): Support VIRTIO_RING_F_EVENT_IDX.
+ let required_features = 1u64 << VIRTIO_F_VERSION_1;
+ self.set_device_feature(required_features);
+ let enabled_features = self.get_device_feature();
+ if (required_features & enabled_features) != required_features {
+ bail!(
+ "required feature set is 0x{:x} but 0x{:x} is enabled",
+ required_features,
+ enabled_features
+ );
+ };
+ self.set_status(virtio_sys::vhost::VIRTIO_CONFIG_S_FEATURES_OK as u8);
+
+ // Initialize Virtqueues
+ let (queues, queue_notifiers) = self.create_queues()?;
+ self.queues = queues;
+ self.queue_notifiers = queue_notifiers;
+
+ let (irqs, notification_evts) = self.create_irqs(device_vq_num)?;
+ self.irqs = irqs;
+ self.notification_evts = notification_evts;
+
+ self.set_status(virtio_sys::vhost::VIRTIO_CONFIG_S_DRIVER_OK as u8);
+
+ Ok(())
+ }
+
+ pub fn start(&self) -> Result<()> {
+ const STATUS_OFFSET: u64 = 0;
+ const VIRTIO_VHOST_USER_STATUS_SLAVE_UP: usize = 0;
+ let mut status: u32 = self
+ .vfio_dev
+ .region_read_from_addr(&self.caps.dev_cfg_addr, STATUS_OFFSET);
+
+ status |= 1u32 << VIRTIO_VHOST_USER_STATUS_SLAVE_UP;
+
+ self.vfio_dev
+ .region_write_to_addr(&status, &self.caps.dev_cfg_addr, STATUS_OFFSET);
+
+ info!("vvu device started");
+ Ok(())
+ }
+}
+
+impl IovaAllocator for VvuPciDevice {
+ fn alloc_iova(&self, size: u64, tag: u8) -> Result<u64> {
+ self.vfio_dev
+ .alloc_iova(size, base::pagesize() as u64, Alloc::VvuQueue(tag))
+ .context("failed to find an iova region to map the gpa region to")
+ }
+
+ unsafe fn map_iova(&self, iova: u64, size: u64, addr: *const u8) -> Result<()> {
+ self.vfio_dev
+ .vfio_dma_map(iova, size, addr as u64, true)
+ .context("failed to map iova")
+ }
+}
diff --git a/devices/src/virtio/vhost/user/device/vvu/queue.rs b/devices/src/virtio/vhost/user/device/vvu/queue.rs
new file mode 100644
index 000000000..86ede3ec3
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/vvu/queue.rs
@@ -0,0 +1,586 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Implement the driver side of virtio queue handling.
+//! The virtqueue struct is expected to be used in userspace VFIO virtio drivers.
+
+use std::mem;
+use std::num::Wrapping;
+use std::sync::atomic::{fence, Ordering};
+
+use anyhow::{anyhow, bail, Context, Result};
+use data_model::{DataInit, Le16, Le32, Le64, VolatileSlice};
+use virtio_sys::virtio_ring::VRING_DESC_F_WRITE;
+use vm_memory::{GuestAddress as IOVA, GuestMemory as QueueMemory};
+
+use crate::virtio::Desc;
+
+#[derive(Copy, Clone, Debug)]
+#[repr(C)]
+struct UsedElem {
+ id: Le32,
+ len: Le32,
+}
+// Safe as there are no implicit offset.
+unsafe impl DataInit for UsedElem {}
+
+const BUF_SIZE: u64 = 1024;
+
+pub struct DescTableAddrs {
+ pub desc: u64,
+ pub avail: u64,
+ pub used: u64,
+}
+
+struct MemLayout {
+ /// Address of the descriptor table in UserQueue.mem.
+ desc_table: IOVA,
+
+ /// Address of the available ring in UserQueue.mem.
+ avail_ring: IOVA,
+
+ /// Address of the used ring in UserQueue.mem.
+ used_ring: IOVA,
+
+ /// Address of the start of buffers in UserQueue.mem.
+ buffer_addr: IOVA,
+}
+
+/// Represents a virtqueue that is allocated in the guest userspace and manipulated from a VFIO
+/// driver.
+///
+/// This struct is similar to `devices::virtio::Queue` which is designed for the virtio devices, but
+/// this struct is defined for the virtio drivers.
+///
+/// # Memory Layout
+///
+/// `mem` is the memory allocated in the guest userspace for the virtqueue, which is mapped into
+/// the vvu device via VFIO. The GuestAddresses of `mem` are the IOVAs that should be used when
+/// communicating with the vvu device. All accesses to the shared memory from the device backend
+/// must be done through the GuestMemory read/write functions.
+///
+/// The layout `mem` is defined in the following table and stored in `mem_layout`.
+///
+/// | | Alignment | Size |
+/// |-----------------------------------------------------------------|
+/// | Descriptor Table | 16 | 16 ∗ (Queue Size) |
+/// | Available Ring | 2 | 6 + 2 ∗ (Queue Size) |
+/// | Used Ring | 4 | 6 + 8 ∗ (Queue Size) |
+/// | Buffers | (Buffer Size) | (Buffer Size) * (Queue Size) |
+/// -------------------------------------------------------------------
+///
+/// TODO(b/207364742): Once we support `VIRTIO_F_EVENT_IDX`, the additional 2 bytes for the
+/// `used_event` field will be added.
+/// TODO(b/215153367): Use `crate::virtio::Queue` as an underlying data structure so that we can use
+/// `descriptor_utils::{Reader, Writer}` instead of having our own read/write methods.
+/// One of the biggest blockers is that `virtio::Queue` is designed for device-side's virtqueue,
+/// where readable/writable areas are inverted from our use case.
+pub struct UserQueue {
+ /// The queue size.
+ size: Wrapping<u16>,
+
+ /// The underlying memory.
+ mem: QueueMemory,
+
+ /// Virtqueue layout on `mem`.
+ mem_layout: MemLayout,
+
+ avail_idx: Wrapping<u16>,
+
+ used_count: Wrapping<u16>,
+ free_count: Wrapping<u16>,
+
+ /// Whether buffers are device-writable or readable.
+ /// If true, every descriptor has the VIRTQ_DESC_F_WRITE flag.
+ /// TODO(b/215153358, b/215153367): Since VIRTQ_DESC_F_WRITE is a per-descriptor flag, this
+ /// design is specific to the current vvu specification draft, where a device-writable queue
+ /// and a device-readable queue are separated.
+ /// Ideally, we should update the vvu spec to use both device-{readable, writable} buffers in
+ /// one virtqueue. Also, it's better to use `crate::virtio::DescriptorChain` for descirptors as
+ /// a part of b/215153367.
+ device_writable: bool,
+}
+
+/// Interface used by UserQueue to interact with the IOMMU.
+pub trait IovaAllocator {
+ /// Allocates an IO virtual address region of the requested size.
+ fn alloc_iova(&self, size: u64, tag: u8) -> Result<u64>;
+ /// Maps the given address at the given IOVA.
+ ///
+ /// # Safety
+ ///
+ /// `addr` must reference a region of at least length `size`. Memory passed
+ /// to this function may be mutated at any time, so `addr` must not be memory
+ /// that is directly managed by rust.
+ unsafe fn map_iova(&self, iova: u64, size: u64, addr: *const u8) -> Result<()>;
+}
+
+impl UserQueue {
+ /// Creats a `UserQueue` instance.
+ pub fn new<I>(queue_size: u16, device_writable: bool, tag: u8, iova_alloc: &I) -> Result<Self>
+ where
+ I: IovaAllocator,
+ {
+ let (mem, size, mem_layout) = Self::init_memory(queue_size, tag, iova_alloc)?;
+
+ let mut queue = Self {
+ mem,
+ size: Wrapping(size),
+ mem_layout,
+ avail_idx: Wrapping(0),
+ used_count: Wrapping(0),
+ free_count: Wrapping(size),
+ device_writable,
+ };
+
+ queue.init_descriptor_table()?;
+
+ Ok(queue)
+ }
+
+ /// Allocates memory region and returns addresses on the regions for (`desc_table`, `avail_ring`, `used_ring`, `buffer``).
+ fn init_memory<I>(
+ max_queue_size: u16,
+ tag: u8,
+ iova_alloc: &I,
+ ) -> Result<(QueueMemory, u16, MemLayout)>
+ where
+ I: IovaAllocator,
+ {
+ // Since vhost-user negotiation finishes within ~20 messages, queue size 32 is enough.
+ const MAX_QUEUE_SIZE: u16 = 256;
+
+ let queue_size = std::cmp::min(MAX_QUEUE_SIZE, max_queue_size);
+ if queue_size == 0 || !queue_size.is_power_of_two() {
+ bail!(
+ "queue_size must be a positive power of 2 number but {}",
+ queue_size
+ );
+ }
+
+ fn align(n: u64, m: u64) -> u64 {
+ ((n + m - 1) / m) * m
+ }
+
+ let desc_table = IOVA(0);
+ let desc_size = 16u64 * u64::from(queue_size);
+ let desc_end = desc_table.0 + desc_size;
+
+ let avail_ring = IOVA(align(desc_end, 2));
+ let avail_size = 6 + 2 * u64::from(queue_size);
+ let avail_end = avail_ring.0 + avail_size;
+
+ let used_ring = IOVA(align(avail_end, 4));
+ let used_size = 6 + 8 * u64::from(queue_size);
+ let used_end = used_ring.0 + used_size;
+
+ let buffer_addr = IOVA(align(used_end, BUF_SIZE));
+ let buffer_size = BUF_SIZE * u64::from(queue_size);
+
+ let mem_size = align(buffer_addr.0 + buffer_size, base::pagesize() as u64);
+ let iova_start = iova_alloc
+ .alloc_iova(mem_size, tag)
+ .context("failed to allocate queue iova")?;
+
+ let mem = QueueMemory::new(&[(IOVA(iova_start), mem_size)])
+ .map_err(|e| anyhow!("failed to create QueueMemory for virtqueue: {}", e))?;
+
+ let host_addr = mem
+ .get_host_address_range(IOVA(iova_start), mem_size as usize)
+ .context("failed to get host address")?;
+ // Safe because the region being mapped is managed via the GuestMemory interface.
+ unsafe {
+ iova_alloc
+ .map_iova(iova_start, mem_size, host_addr)
+ .context("failed to map queue")?;
+ }
+
+ let mem_layout = MemLayout {
+ desc_table: desc_table.unchecked_add(iova_start),
+ avail_ring: avail_ring.unchecked_add(iova_start),
+ used_ring: used_ring.unchecked_add(iova_start),
+ buffer_addr: buffer_addr.unchecked_add(iova_start),
+ };
+
+ Ok((mem, queue_size, mem_layout))
+ }
+
+ /// Initialize the descriptor table.
+ fn init_descriptor_table(&mut self) -> Result<()> {
+ let flags = if self.device_writable {
+ Le16::from(VRING_DESC_F_WRITE as u16)
+ } else {
+ Le16::from(0)
+ };
+ let len = Le32::from(BUF_SIZE as u32);
+ let next = Le16::from(0);
+
+ // Register pre-allocated buffers to the descriptor area.
+ for i in 0..self.size.0 {
+ let idx = Wrapping(i);
+ let iova = self.buffer_address(idx)?.offset();
+ let desc = Desc {
+ addr: iova.into(),
+ len,
+ flags,
+ next,
+ };
+ self.write_desc_entry(idx, desc)
+ .map_err(|e| anyhow!("failed to write {}-th desc: {}", idx, e))?;
+
+ fence(Ordering::SeqCst);
+ self.mem
+ .write_obj_at_addr(
+ idx.0,
+ self.mem_layout
+ .avail_ring
+ .unchecked_add(u64::from(4 + 2 * i)),
+ )
+ .context("failed to write avail ring")?;
+ }
+
+ // If all of `self`'s buffers are device-writable, expose them to the device.
+ if self.device_writable {
+ for _ in 0..self.size.0 {
+ // TODO(keiichiw): avail_idx should be incremented in update_avail_index
+ self.avail_idx += Wrapping(1);
+ self.update_avail_index()?;
+ }
+ }
+
+ Ok(())
+ }
+
+ pub fn desc_table_addrs(&self) -> Result<DescTableAddrs> {
+ Ok(DescTableAddrs {
+ desc: self.mem_layout.desc_table.offset(),
+ avail: self.mem_layout.avail_ring.offset(),
+ used: self.mem_layout.used_ring.offset(),
+ })
+ }
+
+ /// Returns the IOVA of the buffer for the given `index`.
+ fn buffer_address(&self, index: Wrapping<u16>) -> Result<IOVA> {
+ let offset = u64::from((index % self.size).0) * BUF_SIZE;
+ self.mem_layout
+ .buffer_addr
+ .checked_add(offset)
+ .ok_or(anyhow!("overflow txq"))
+ }
+
+ /// Writes the given descriptor table entry.
+ fn write_desc_entry(&self, index: Wrapping<u16>, desc: Desc) -> Result<()> {
+ let addr = self
+ .mem_layout
+ .desc_table
+ .unchecked_add(u64::from((index % self.size).0) * mem::size_of::<Desc>() as u64);
+ fence(Ordering::SeqCst);
+ self.mem
+ .write_obj_at_addr(desc, addr)
+ .context("failed to write desc")
+ }
+
+ /// Puts an index into the avail ring for use by the host.
+ fn update_avail_index(&self) -> Result<()> {
+ fence(Ordering::SeqCst);
+ self.mem
+ .write_obj_at_addr(
+ self.avail_idx.0,
+ self.mem_layout.avail_ring.unchecked_add(2),
+ )
+ .context("failed to write avail.idx")?;
+ Ok(())
+ }
+
+ /// Reads the Used ring's index.
+ fn read_used_idx(&self) -> Result<Wrapping<u16>> {
+ let used_index_addr = self.mem_layout.used_ring.unchecked_add(2);
+ fence(Ordering::SeqCst);
+ let used_index: u16 = self.mem.read_obj_from_addr(used_index_addr).unwrap();
+ Ok(Wrapping(used_index))
+ }
+
+ /// Reads the Used ring's element for the given index.
+ fn read_used_elem(&self, idx: Wrapping<u16>) -> Result<UsedElem> {
+ let offset = 4 + (idx % self.size).0 as usize * mem::size_of::<UsedElem>();
+ let addr = self
+ .mem_layout
+ .used_ring
+ .checked_add(offset as u64)
+ .context("overflow")?;
+ fence(Ordering::SeqCst);
+ self.mem
+ .read_obj_from_addr(addr)
+ .context("failed to read used")
+ }
+
+ /// Reads data in the virtqueue.
+ /// Returns `Ok(None)` if no data are available.
+ ///
+ /// TODO: Use `descriptor_utils::Reader`.
+ pub fn read_data(&mut self) -> Result<Option<VolatileSlice>> {
+ if !self.device_writable {
+ bail!("driver cannot read device-readable descriptors");
+ }
+
+ let idx = self.read_used_idx()?;
+ let cur = self.used_count;
+ if cur == idx {
+ return Ok(None);
+ }
+
+ let elem = self.read_used_elem(cur)?;
+
+ let id = Wrapping(u32::from(elem.id) as u16);
+ let len = u32::from(elem.len) as usize;
+
+ let addr = self.buffer_address(id)?;
+
+ fence(Ordering::SeqCst);
+ let s = self
+ .mem
+ .get_slice_at_addr(addr, len)
+ .context("failed to read data")?;
+
+ self.used_count += Wrapping(1);
+ self.avail_idx += Wrapping(1);
+ self.update_avail_index()?;
+ Ok(Some(s))
+ }
+
+ /// Writes data into virtqueue's buffer and returns its address.
+ ///
+ /// TODO: Use `descriptor_utils::Writer`.
+ fn write_to_buffer(&self, index: Wrapping<u16>, data: &[u8]) -> Result<IOVA> {
+ if data.len() as u64 > BUF_SIZE {
+ bail!(
+ "data size {} is larger than the buffer size {}",
+ data.len(),
+ BUF_SIZE
+ );
+ }
+
+ let addr = self.buffer_address(index)?;
+ fence(Ordering::SeqCst);
+ let written = self
+ .mem
+ .write_at_addr(data, addr)
+ .context("failed to write data")?;
+ if written < data.len() {
+ bail!(
+ "no enough memory: written {}, but data length is {}",
+ written,
+ data.len()
+ );
+ }
+ Ok(addr)
+ }
+
+ /// Acknowledges buffers that the device used.
+ pub fn ack_used(&mut self) -> Result<()> {
+ let used_idx = self.read_used_idx()?;
+ let num_used = used_idx - self.used_count;
+
+ self.used_count += num_used;
+ self.free_count += num_used;
+
+ Ok(())
+ }
+
+ /// Writes the given data to the virtqueue.
+ pub fn write(&mut self, data: &[u8]) -> Result<()> {
+ if self.device_writable {
+ bail!("driver cannot write to device-writable descriptors");
+ }
+
+ self.ack_used()?;
+
+ if self.free_count == Wrapping(0) {
+ // TODO: wait until the device processes buffers.
+ bail!("no avail descriptor is left");
+ }
+
+ let addr = self
+ .write_to_buffer(self.avail_idx, data)
+ .context("failed to write data to virtqueue")?;
+
+ let desc = Desc {
+ addr: Le64::from(addr.offset()),
+ len: Le32::from(data.len() as u32),
+ flags: Le16::from(0),
+ next: Le16::from(0),
+ };
+ self.write_desc_entry(self.avail_idx, desc)?;
+ self.free_count -= Wrapping(1);
+
+ self.avail_idx += Wrapping(1);
+ self.update_avail_index()?;
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use std::cell::RefCell;
+ use std::io::Read;
+ use std::io::Write;
+
+ use crate::virtio::{Queue as DeviceQueue, Reader, Writer};
+
+ // An allocator that just allocates 0 as an IOVA.
+ struct SimpleIovaAllocator(RefCell<bool>);
+
+ impl IovaAllocator for SimpleIovaAllocator {
+ fn alloc_iova(&self, _size: u64, _tag: u8) -> Result<u64> {
+ if *self.0.borrow() {
+ bail!("exhaused");
+ }
+ *self.0.borrow_mut() = true;
+ Ok(0)
+ }
+
+ unsafe fn map_iova(&self, _iova: u64, _size: u64, _addr: *const u8) -> Result<()> {
+ if !*self.0.borrow() {
+ bail!("not allocated");
+ }
+ Ok(())
+ }
+ }
+
+ fn setup_vq(queue: &mut DeviceQueue, addrs: DescTableAddrs) {
+ queue.desc_table = IOVA(addrs.desc);
+ queue.avail_ring = IOVA(addrs.avail);
+ queue.used_ring = IOVA(addrs.used);
+ queue.ready = true;
+ }
+
+ fn device_write(mem: &QueueMemory, q: &mut DeviceQueue, data: &[u8]) -> usize {
+ let desc_chain = q.pop(mem).unwrap();
+ let index = desc_chain.index;
+
+ let mut writer = Writer::new(mem.clone(), desc_chain).unwrap();
+ let written = writer.write(data).unwrap();
+ q.add_used(mem, index, written as u32);
+ written
+ }
+
+ fn device_read(mem: &QueueMemory, q: &mut DeviceQueue, len: usize) -> Vec<u8> {
+ let desc_chain = q.pop(mem).unwrap();
+ let desc_index = desc_chain.index;
+ let mut reader = Reader::new(mem.clone(), desc_chain).unwrap();
+ let mut buf = vec![0; len];
+ reader.read_exact(&mut buf).unwrap();
+ q.add_used(mem, desc_index, len as u32);
+ buf
+ }
+
+ fn driver_read(q: &mut UserQueue) -> Vec<u8> {
+ let data = q.read_data().unwrap().unwrap();
+ let mut buf = vec![0; data.size()];
+ data.copy_to(&mut buf);
+
+ buf
+ }
+
+ fn driver_write(q: &mut UserQueue, data: &[u8]) {
+ q.write(data).unwrap()
+ }
+
+ // Send an array from the driver to the device `count` times.
+ fn drv_to_dev(queue_size: u16, count: u32) {
+ let iova_alloc = SimpleIovaAllocator(RefCell::new(false));
+ let mut drv_queue =
+ UserQueue::new(queue_size, false /* device_writable */, 0, &iova_alloc).unwrap();
+ let mut dev_queue = DeviceQueue::new(queue_size);
+ setup_vq(&mut dev_queue, drv_queue.desc_table_addrs().unwrap());
+
+ for i in 0..count {
+ let input = vec![(i + 1) as u8; 5];
+ driver_write(&mut drv_queue, &input);
+
+ let buf = device_read(&drv_queue.mem, &mut dev_queue, input.len());
+ assert_eq!(input, buf);
+ assert!(dev_queue.peek(&drv_queue.mem).is_none());
+ }
+ }
+
+ #[test]
+ fn test_driver_write() {
+ let queue_size = 256;
+ let iteration = 20;
+ drv_to_dev(queue_size, iteration);
+ }
+
+ #[test]
+ fn test_driver_write_small_queue() {
+ // Test with a small queue.
+ let queue_size = 8;
+ let iteration = 20;
+ drv_to_dev(queue_size, iteration);
+ }
+
+ // This test loops (65536 + 20) times. To avoid running it on slow emulated CI environments,
+ // specify target architecture.
+ // TODO(keiichiw): Change the test to mutate queues' internal state to avoid the actual loop.
+ #[test]
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ fn test_driver_write_wrapping() {
+ // Test the index can be wrapped around when the iteration count exceeds 16bits.
+ let queue_size = 256;
+
+ let iteration = u32::from(u16::MAX) + 20;
+ drv_to_dev(queue_size, iteration);
+ }
+
+ // Send an array from the device to the driver `count` times.
+ fn dev_to_drv(queue_size: u16, count: u32) {
+ let iova_alloc = SimpleIovaAllocator(RefCell::new(false));
+ let mut drv_queue =
+ UserQueue::new(queue_size, true /* device_writable */, 0, &iova_alloc).unwrap();
+ let mut dev_queue = DeviceQueue::new(queue_size);
+ setup_vq(&mut dev_queue, drv_queue.desc_table_addrs().unwrap());
+
+ for i in 0..count {
+ let input = [i as u8; 5];
+
+ // Device writes data to driver
+ let written = device_write(&drv_queue.mem, &mut dev_queue, &input);
+ assert_eq!(written, input.len());
+
+ // Driver reads data
+ let buf = driver_read(&mut drv_queue);
+ assert_eq!(buf, input);
+ }
+ }
+
+ #[test]
+ fn test_driver_read() {
+ let queue_size = 256;
+ let iteration = 20;
+ dev_to_drv(queue_size, iteration);
+ }
+
+ #[test]
+ fn test_driver_read_small_queue() {
+ // Test with a small queue.
+ let queue_size = 8;
+ let iteration = 20;
+ dev_to_drv(queue_size, iteration);
+ }
+
+ // This test loops (65536 + 20) times. To avoid running it on slow emulated CI environments,
+ // specify target architecture.
+ // TODO(keiichiw): Change the test to mutate queues' internal state to avoid the actual loop.
+ #[test]
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ fn test_driver_read_wrapping() {
+ // Test the index can be wrapped around when the iteration count exceeds 16bits.
+ let queue_size = 256;
+ let iteration = u32::from(u16::MAX) + 20;
+ dev_to_drv(queue_size, iteration);
+ }
+}
diff --git a/devices/src/virtio/vhost/user/device/windows/net.rs b/devices/src/virtio/vhost/user/device/windows/net.rs
new file mode 100644
index 000000000..67b5395cb
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/windows/net.rs
@@ -0,0 +1,288 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use anyhow::{bail, Context};
+use argh::FromArgs;
+use base::named_pipes::{OverlappedWrapper, PipeConnection};
+use base::{error, warn, Event, RawDescriptor};
+use cros_async::{EventAsync, Executor, IntoAsync, IoSourceExt};
+use futures::future::{AbortHandle, Abortable};
+use hypervisor::ProtectionType;
+#[cfg(feature = "slirp")]
+use net_util::Slirp;
+use net_util::TapT;
+#[cfg(feature = "slirp")]
+use serde::{Deserialize, Serialize};
+use std::sync::Arc;
+use sync::Mutex;
+use virtio_sys::virtio_net;
+use vm_memory::GuestMemory;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+use super::{run_ctrl_queue, run_tx_queue, NetBackend, NET_EXECUTOR};
+use crate::virtio;
+#[cfg(feature = "slirp")]
+use crate::virtio::net::MAX_BUFFER_SIZE;
+use crate::virtio::net::{process_rx, NetError};
+use crate::virtio::vhost::user::device::handler::read_from_tube_transporter;
+use crate::virtio::vhost::user::device::handler::{DeviceRequestHandler, Doorbell};
+use crate::virtio::{base_features, SignalableInterrupt};
+use broker_ipc::{common_child_setup, CommonChildStartupArgs};
+use tube_transporter::TubeToken;
+
+impl<T: 'static> NetBackend<T>
+where
+ T: TapT + IntoAsync,
+{
+ #[cfg(feature = "slirp")]
+ pub fn new_slirp(
+ guest_pipe: PipeConnection,
+ slirp_kill_event: Event,
+ ) -> anyhow::Result<NetBackend<Slirp>> {
+ let avail_features = base_features(ProtectionType::Unprotected)
+ | 1 << virtio_net::VIRTIO_NET_F_CTRL_VQ
+ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let slirp = Slirp::new_for_multi_process(guest_pipe).map_err(NetError::SlirpCreateError)?;
+
+ Ok(NetBackend::<Slirp> {
+ tap: slirp,
+ avail_features,
+ acked_features: 0,
+ acked_protocol_features: VhostUserProtocolFeatures::empty(),
+ workers: Default::default(),
+ mtu: 1500,
+ slirp_kill_event,
+ })
+ }
+}
+
+async fn run_rx_queue<T: TapT>(
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ mut tap: Box<dyn IoSourceExt<T>>,
+ call_evt: Arc<Mutex<Doorbell>>,
+ kick_evt: EventAsync,
+ read_notifier: EventAsync,
+ mut overlapped_wrapper: OverlappedWrapper,
+) {
+ let mut rx_buf = [0u8; MAX_BUFFER_SIZE];
+ let mut rx_count = 0;
+ let mut deferred_rx = false;
+ tap.as_source_mut()
+ .read_overlapped(&mut rx_buf, &mut overlapped_wrapper)
+ .expect("read_overlapped failed");
+ loop {
+ // If we already have a packet from deferred RX, we don't need to wait for the slirp device.
+ if !deferred_rx {
+ if let Err(e) = read_notifier.next_val().await {
+ error!("Failed to wait for tap device to become readable: {}", e);
+ break;
+ }
+ }
+
+ let needs_interrupt = process_rx(
+ &call_evt,
+ &mut queue,
+ &mem,
+ tap.as_source_mut(),
+ &mut rx_buf,
+ &mut deferred_rx,
+ &mut rx_count,
+ &mut overlapped_wrapper,
+ );
+ if needs_interrupt {
+ call_evt.lock().signal_used_queue(queue.vector);
+ }
+
+ // There aren't any RX descriptors available for us to write packets to. Wait for the guest
+ // to consume some packets and make more descriptors available to us.
+ if deferred_rx {
+ if let Err(e) = kick_evt.next_val().await {
+ error!("Failed to read kick event for rx queue: {}", e);
+ break;
+ }
+ }
+ }
+}
+
+/// Platform specific impl of VhostUserBackend::start_queue.
+pub(super) fn start_queue<T: 'static + IntoAsync + TapT>(
+ backend: &mut NetBackend<T>,
+ idx: usize,
+ mut queue: virtio::Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: Event,
+) -> anyhow::Result<()> {
+ if let Some(handle) = backend.workers.get_mut(idx).and_then(Option::take) {
+ warn!("Starting new queue handler without stopping old handler");
+ handle.abort();
+ }
+
+ // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
+ queue.ack_features(backend.acked_features);
+
+ let overlapped_wrapper =
+ OverlappedWrapper::new(true).expect("Failed to create overlapped wrapper");
+
+ super::NET_EXECUTOR.with(|ex| {
+ // Safe because the executor is initialized in main() below.
+ let ex = ex.get().expect("Executor not initialized");
+
+ let kick_evt =
+ EventAsync::new(kick_evt.0, ex).context("failed to create EventAsync for kick_evt")?;
+ let tap = backend
+ .tap
+ .try_clone()
+ .context("failed to clone tap device")?;
+ let (handle, registration) = AbortHandle::new_pair();
+ match idx {
+ 0 => {
+ let tap = ex
+ .async_from(tap)
+ .context("failed to create async tap device")?;
+ let read_notifier = overlapped_wrapper
+ .get_h_event_ref()
+ .unwrap()
+ .try_clone()
+ .unwrap();
+ let read_notifier = EventAsync::new_without_reset(read_notifier, &ex)
+ .context("failed to create async read notifier")?;
+
+ ex.spawn_local(Abortable::new(
+ run_rx_queue(
+ queue,
+ mem,
+ tap,
+ doorbell,
+ kick_evt,
+ read_notifier,
+ overlapped_wrapper,
+ ),
+ registration,
+ ))
+ .detach();
+ }
+ 1 => {
+ ex.spawn_local(Abortable::new(
+ run_tx_queue(queue, mem, tap, doorbell, kick_evt),
+ registration,
+ ))
+ .detach();
+ }
+ 2 => {
+ ex.spawn_local(Abortable::new(
+ run_ctrl_queue(
+ queue,
+ mem,
+ tap,
+ doorbell,
+ kick_evt,
+ backend.acked_features,
+ 1, /* vq_pairs */
+ ),
+ registration,
+ ))
+ .detach();
+ }
+ _ => bail!("attempted to start unknown queue: {}", idx),
+ }
+
+ backend.workers[idx] = Some(handle);
+ Ok(())
+ })
+}
+
+#[cfg(feature = "slirp")]
+impl<T> Drop for NetBackend<T>
+where
+ T: TapT + IntoAsync,
+{
+ fn drop(&mut self) {
+ let _ = self.slirp_kill_event.write(1);
+ }
+}
+
+/// Config arguments passed through the bootstrap Tube from the broker to the Net backend
+/// process.
+#[cfg(feature = "slirp")]
+#[derive(Serialize, Deserialize, Debug)]
+pub struct NetBackendConfig {
+ pub guest_pipe: PipeConnection,
+ pub slirp_kill_event: Event,
+}
+
+#[derive(FromArgs)]
+#[argh(description = "")]
+struct Options {
+ #[argh(
+ option,
+ description = "pipe handle end for Tube Transporter",
+ arg_name = "HANDLE"
+ )]
+ bootstrap: usize,
+}
+
+#[cfg(all(windows, not(feature = "slirp")))]
+compile_error!("vhost-user net device requires slirp feature on Windows.");
+
+#[cfg(feature = "slirp")]
+pub fn run_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
+ let opts = match Options::from_args(&[program_name], args) {
+ Ok(opts) => opts,
+ Err(e) => {
+ if e.status.is_err() {
+ bail!(e.output);
+ } else {
+ println!("{}", e.output);
+ }
+ return Ok(());
+ }
+ };
+
+ // Get the Tubes from the TubeTransporter. Then get the "Config" from the bootstrap_tube
+ // which will contain slirp settings.
+ let raw_transport_tube = opts.bootstrap as RawDescriptor;
+
+ let mut tubes = read_from_tube_transporter(raw_transport_tube).unwrap();
+
+ let vhost_user_tube = tubes.get_tube(TubeToken::VhostUser).unwrap();
+ let bootstrap_tube = tubes.get_tube(TubeToken::Bootstrap).unwrap();
+
+ let startup_args: CommonChildStartupArgs =
+ bootstrap_tube.recv::<CommonChildStartupArgs>().unwrap();
+ common_child_setup(startup_args).unwrap();
+
+ let net_backend_config = bootstrap_tube.recv::<NetBackendConfig>().unwrap();
+
+ let exit_event = bootstrap_tube.recv::<Event>()?;
+
+ // We only have one net device for now.
+ let dev = NetBackend::<net_util::Slirp>::new_slirp(
+ net_backend_config.guest_pipe,
+ net_backend_config.slirp_kill_event,
+ )
+ .unwrap();
+
+ let handler = DeviceRequestHandler::new(dev);
+
+ let ex = Executor::new().context("failed to create executor")?;
+
+ NET_EXECUTOR.with(|net_ex| {
+ let _ = net_ex.set(ex.clone());
+ });
+
+ if sandbox::is_sandbox_target() {
+ sandbox::TargetServices::get()
+ .expect("failed to get target services")
+ .unwrap()
+ .lower_token();
+ }
+
+ if let Err(e) = ex.run_until(handler.run(vhost_user_tube, exit_event, &ex)) {
+ bail!("error occurred: {}", e);
+ }
+
+ Ok(())
+}
diff --git a/devices/src/virtio/vhost/user/device/wl.rs b/devices/src/virtio/vhost/user/device/wl.rs
new file mode 100644
index 000000000..6b7fc32fc
--- /dev/null
+++ b/devices/src/virtio/vhost/user/device/wl.rs
@@ -0,0 +1,367 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::RefCell;
+use std::collections::BTreeMap;
+use std::path::PathBuf;
+use std::rc::Rc;
+use std::sync::Arc;
+use std::thread;
+use std::time::{Duration, Instant};
+
+use anyhow::{anyhow, bail, Context};
+use argh::FromArgs;
+use base::{
+ clone_descriptor, error, warn, Event, FromRawDescriptor, SafeDescriptor, Tube, UnixSeqpacket,
+ UnixSeqpacketListener, UnlinkUnixSeqpacketListener,
+};
+use cros_async::{AsyncWrapper, EventAsync, Executor, IoSourceExt};
+use futures::future::{AbortHandle, Abortable};
+use hypervisor::ProtectionType;
+use sync::Mutex;
+use vm_memory::GuestMemory;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+use crate::virtio::vhost::user::device::handler::{
+ DeviceRequestHandler, Doorbell, VhostUserBackend,
+};
+use crate::virtio::{base_features, wl, Queue};
+
+async fn run_out_queue(
+ mut queue: Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: EventAsync,
+ wlstate: Rc<RefCell<wl::WlState>>,
+) {
+ loop {
+ if let Err(e) = kick_evt.next_val().await {
+ error!("Failed to read kick event for out queue: {}", e);
+ break;
+ }
+
+ wl::process_out_queue(&doorbell, &mut queue, &mem, &mut wlstate.borrow_mut());
+ }
+}
+
+async fn run_in_queue(
+ mut queue: Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: EventAsync,
+ wlstate: Rc<RefCell<wl::WlState>>,
+ wlstate_ctx: Box<dyn IoSourceExt<AsyncWrapper<SafeDescriptor>>>,
+) {
+ loop {
+ if let Err(e) = wlstate_ctx.wait_readable().await {
+ error!(
+ "Failed to wait for inner WaitContext to become readable: {}",
+ e
+ );
+ break;
+ }
+
+ if let Err(wl::DescriptorsExhausted) =
+ wl::process_in_queue(&doorbell, &mut queue, &mem, &mut wlstate.borrow_mut())
+ {
+ if let Err(e) = kick_evt.next_val().await {
+ error!("Failed to read kick event for in queue: {}", e);
+ break;
+ }
+ }
+ }
+}
+
+struct WlBackend {
+ ex: Executor,
+ wayland_paths: Option<BTreeMap<String, PathBuf>>,
+ vm_socket: Option<Tube>,
+ resource_bridge: Option<Tube>,
+ use_transition_flags: bool,
+ use_send_vfd_v2: bool,
+ features: u64,
+ acked_features: u64,
+ wlstate: Option<Rc<RefCell<wl::WlState>>>,
+ workers: [Option<AbortHandle>; Self::MAX_QUEUE_NUM],
+}
+
+impl WlBackend {
+ fn new(
+ ex: &Executor,
+ wayland_paths: BTreeMap<String, PathBuf>,
+ vm_socket: Tube,
+ resource_bridge: Option<Tube>,
+ ) -> WlBackend {
+ let features = base_features(ProtectionType::Unprotected)
+ | 1 << wl::VIRTIO_WL_F_TRANS_FLAGS
+ | 1 << wl::VIRTIO_WL_F_SEND_FENCES
+ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ WlBackend {
+ ex: ex.clone(),
+ wayland_paths: Some(wayland_paths),
+ vm_socket: Some(vm_socket),
+ resource_bridge,
+ use_transition_flags: false,
+ use_send_vfd_v2: false,
+ features,
+ acked_features: 0,
+ wlstate: None,
+ workers: Default::default(),
+ }
+ }
+}
+
+impl VhostUserBackend for WlBackend {
+ const MAX_QUEUE_NUM: usize = wl::QUEUE_SIZES.len();
+ const MAX_VRING_LEN: u16 = wl::QUEUE_SIZE;
+
+ type Error = anyhow::Error;
+
+ fn features(&self) -> u64 {
+ self.features
+ }
+
+ fn ack_features(&mut self, value: u64) -> anyhow::Result<()> {
+ let unrequested_features = value & !self.features();
+ if unrequested_features != 0 {
+ bail!("invalid features are given: {:#x}", unrequested_features);
+ }
+
+ self.acked_features |= value;
+
+ if value & (1 << wl::VIRTIO_WL_F_TRANS_FLAGS) != 0 {
+ self.use_transition_flags = true;
+ }
+ if value & (1 << wl::VIRTIO_WL_F_SEND_FENCES) != 0 {
+ self.use_send_vfd_v2 = true;
+ }
+
+ Ok(())
+ }
+
+ fn acked_features(&self) -> u64 {
+ self.acked_features
+ }
+
+ fn protocol_features(&self) -> VhostUserProtocolFeatures {
+ VhostUserProtocolFeatures::empty()
+ }
+
+ fn ack_protocol_features(&mut self, features: u64) -> anyhow::Result<()> {
+ if features != 0 {
+ Err(anyhow!("Unexpected protocol features: {:#x}", features))
+ } else {
+ Ok(())
+ }
+ }
+
+ fn acked_protocol_features(&self) -> u64 {
+ VhostUserProtocolFeatures::empty().bits()
+ }
+
+ fn read_config(&self, _offset: u64, _dst: &mut [u8]) {}
+
+ fn start_queue(
+ &mut self,
+ idx: usize,
+ mut queue: Queue,
+ mem: GuestMemory,
+ doorbell: Arc<Mutex<Doorbell>>,
+ kick_evt: Event,
+ ) -> anyhow::Result<()> {
+ if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
+ warn!("Starting new queue handler without stopping old handler");
+ handle.abort();
+ }
+
+ // Enable any virtqueue features that were negotiated (like VIRTIO_RING_F_EVENT_IDX).
+ queue.ack_features(self.acked_features);
+
+ let kick_evt = EventAsync::new(kick_evt.0, &self.ex)
+ .context("failed to create EventAsync for kick_evt")?;
+
+ // We use this de-structuring let binding to separate borrows so that the compiler doesn't
+ // think we're borrowing all of `self` in the closure below.
+ let WlBackend {
+ ref mut wayland_paths,
+ ref mut vm_socket,
+ ref mut resource_bridge,
+ ref use_transition_flags,
+ ref use_send_vfd_v2,
+ ..
+ } = self;
+ let wlstate = self
+ .wlstate
+ .get_or_insert_with(|| {
+ Rc::new(RefCell::new(wl::WlState::new(
+ wayland_paths.take().expect("WlState already initialized"),
+ vm_socket.take().expect("WlState already initialized"),
+ *use_transition_flags,
+ *use_send_vfd_v2,
+ resource_bridge.take(),
+ )))
+ })
+ .clone();
+ let (handle, registration) = AbortHandle::new_pair();
+ match idx {
+ 0 => {
+ let wlstate_ctx = clone_descriptor(wlstate.borrow().wait_ctx())
+ .map(|fd| {
+ // Safe because we just created this fd.
+ AsyncWrapper::new(unsafe { SafeDescriptor::from_raw_descriptor(fd) })
+ })
+ .context("failed to clone inner WaitContext for WlState")
+ .and_then(|ctx| {
+ self.ex
+ .async_from(ctx)
+ .context("failed to create async WaitContext")
+ })?;
+
+ self.ex
+ .spawn_local(Abortable::new(
+ run_in_queue(queue, mem, doorbell, kick_evt, wlstate, wlstate_ctx),
+ registration,
+ ))
+ .detach();
+ }
+ 1 => {
+ self.ex
+ .spawn_local(Abortable::new(
+ run_out_queue(queue, mem, doorbell, kick_evt, wlstate),
+ registration,
+ ))
+ .detach();
+ }
+ _ => bail!("attempted to start unknown queue: {}", idx),
+ }
+ self.workers[idx] = Some(handle);
+ Ok(())
+ }
+
+ fn stop_queue(&mut self, idx: usize) {
+ if let Some(handle) = self.workers.get_mut(idx).and_then(Option::take) {
+ handle.abort();
+ }
+ }
+ fn reset(&mut self) {
+ for handle in self.workers.iter_mut().filter_map(Option::take) {
+ handle.abort();
+ }
+ }
+}
+
+pub(crate) fn parse_wayland_sock(value: &str) -> Result<(String, PathBuf), String> {
+ let mut components = value.split(',');
+ let path = PathBuf::from(match components.next() {
+ None => return Err("missing socket path".to_string()),
+ Some(c) => c,
+ });
+ let mut name = "";
+ for c in components {
+ let mut kv = c.splitn(2, '=');
+ let (kind, value) = match (kv.next(), kv.next()) {
+ (Some(kind), Some(value)) => (kind, value),
+ _ => return Err(format!("option must be of the form `kind=value`: {}", c)),
+ };
+ match kind {
+ "name" => name = value,
+ _ => return Err(format!("unrecognized option: {}", kind)),
+ }
+ }
+
+ Ok((name.to_string(), path))
+}
+
+#[derive(FromArgs)]
+#[argh(description = "")]
+struct Options {
+ #[argh(
+ option,
+ description = "path to bind a listening vhost-user socket",
+ arg_name = "PATH"
+ )]
+ socket: String,
+ #[argh(
+ option,
+ description = "path to a socket for wayland-specific messages",
+ arg_name = "PATH"
+ )]
+ vm_socket: String,
+ #[argh(
+ option,
+ description = "path to one or more Wayland sockets. The unnamed socket is used for\
+ displaying virtual screens while the named ones are used for IPC",
+ from_str_fn(parse_wayland_sock),
+ arg_name = "PATH[,name=NAME]"
+ )]
+ wayland_sock: Vec<(String, PathBuf)>,
+ #[argh(
+ option,
+ description = "path to the GPU resource bridge",
+ arg_name = "PATH"
+ )]
+ resource_bridge: Option<String>,
+}
+
+/// Starts a vhost-user wayland device.
+/// Returns an error if the given `args` is invalid or the device fails to run.
+pub fn run_wl_device(program_name: &str, args: &[&str]) -> anyhow::Result<()> {
+ let Options {
+ vm_socket,
+ wayland_sock,
+ socket,
+ resource_bridge,
+ } = match Options::from_args(&[program_name], args) {
+ Ok(opts) => opts,
+ Err(e) => {
+ if e.status.is_err() {
+ bail!(e.output);
+ } else {
+ println!("{}", e.output);
+ }
+ return Ok(());
+ }
+ };
+
+ let wayland_paths: BTreeMap<_, _> = wayland_sock.into_iter().collect();
+
+ let resource_bridge = resource_bridge
+ .map(|p| -> anyhow::Result<Tube> {
+ let deadline = Instant::now() + Duration::from_secs(5);
+ loop {
+ match UnixSeqpacket::connect(&p) {
+ Ok(s) => return Ok(Tube::new(s)),
+ Err(e) => {
+ if Instant::now() < deadline {
+ thread::sleep(Duration::from_millis(50));
+ } else {
+ return Err(anyhow::Error::new(e));
+ }
+ }
+ }
+ }
+ })
+ .transpose()
+ .context("failed to connect to resource bridge socket")?;
+
+ let ex = Executor::new().context("failed to create executor")?;
+
+ // We can safely `unwrap()` this because it is a required option.
+ let vm_listener = UnixSeqpacketListener::bind(vm_socket)
+ .map(UnlinkUnixSeqpacketListener)
+ .context("failed to create listening socket")?;
+ let vm_socket = vm_listener
+ .accept()
+ .map(Tube::new)
+ .context("failed to accept vm socket connection")?;
+ let handler = DeviceRequestHandler::new(WlBackend::new(
+ &ex,
+ wayland_paths,
+ vm_socket,
+ resource_bridge,
+ ));
+
+ // run_until() returns an Result<Result<..>> which the ? operator lets us flatten.
+ ex.run_until(handler.run(socket, &ex))?
+}
diff --git a/devices/src/virtio/vhost/user/mod.rs b/devices/src/virtio/vhost/user/mod.rs
index b408e1c18..154ffafaf 100644
--- a/devices/src/virtio/vhost/user/mod.rs
+++ b/devices/src/virtio/vhost/user/mod.rs
@@ -2,100 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-mod block;
-mod fs;
-mod handler;
-mod net;
-mod worker;
+pub mod device;
+pub mod proxy;
+pub mod vmm;
-pub use self::block::*;
-pub use self::fs::*;
-pub use self::net::*;
-
-use remain::sorted;
-use thiserror::Error as ThisError;
-use vm_memory::GuestMemoryError;
-use vmm_vhost::Error as VhostError;
-
-#[sorted]
-#[derive(ThisError, Debug)]
-pub enum Error {
- /// Failed to copy config to a buffer.
- #[error("failed to copy config to a buffer: {0}")]
- CopyConfig(std::io::Error),
- /// Failed to create `base::Event`.
- #[error("failed to create Event: {0}")]
- CreateEvent(base::Error),
- /// Failed to get config.
- #[error("failed to get config: {0}")]
- GetConfig(VhostError),
- /// Failed to get features.
- #[error("failed to get features: {0}")]
- GetFeatures(VhostError),
- /// Failed to get host address.
- #[error("failed to get host address: {0}")]
- GetHostAddress(GuestMemoryError),
- /// Failed to get protocol features.
- #[error("failed to get protocol features: {0}")]
- GetProtocolFeatures(VhostError),
- /// Failed to get number of queues.
- #[error("failed to get number of queues: {0}")]
- GetQueueNum(VhostError),
- /// Failed to get vring base offset.
- #[error("failed to get vring base offset: {0}")]
- GetVringBase(VhostError),
- /// Invalid config offset is given.
- #[error("invalid config offset is given: {data_len} + {offset} > {config_len}")]
- InvalidConfigOffset {
- data_len: u64,
- offset: u64,
- config_len: u64,
- },
- /// MSI-X config is unavailable.
- #[error("MSI-X config is unavailable")]
- MsixConfigUnavailable,
- /// MSI-X irqfd is unavailable.
- #[error("MSI-X irqfd is unavailable")]
- MsixIrqfdUnavailable,
- /// Failed to reset owner.
- #[error("failed to reset owner: {0}")]
- ResetOwner(VhostError),
- /// Failed to set features.
- #[error("failed to set features: {0}")]
- SetFeatures(VhostError),
- /// Failed to set memory map regions.
- #[error("failed to set memory map regions: {0}")]
- SetMemTable(VhostError),
- /// Failed to set owner.
- #[error("failed to set owner: {0}")]
- SetOwner(VhostError),
- /// Failed to set protocol features.
- #[error("failed to set protocol features: {0}")]
- SetProtocolFeatures(VhostError),
- /// Failed to set vring address.
- #[error("failed to set vring address: {0}")]
- SetVringAddr(VhostError),
- /// Failed to set vring base offset.
- #[error("failed to set vring base offset: {0}")]
- SetVringBase(VhostError),
- /// Failed to set eventfd to signal used vring buffers.
- #[error("failed to set eventfd to signal used vring buffers: {0}")]
- SetVringCall(VhostError),
- /// Failed to enable or disable vring.
- #[error("failed to enable or disable vring: {0}")]
- SetVringEnable(VhostError),
- /// Failed to set eventfd for adding buffers to vring.
- #[error("failed to set eventfd for adding buffers to vring: {0}")]
- SetVringKick(VhostError),
- /// Failed to set the size of the queue.
- #[error("failed to set the size of the queue: {0}")]
- SetVringNum(VhostError),
- /// Failed to connect socket.
- #[error("failed to connect socket: {0}")]
- SocketConnect(std::io::Error),
- /// The tag for the Fs device was too long to fit in the config space.
- #[error("tag is too long: {len} > {max}")]
- TagTooLong { len: usize, max: usize },
-}
-
-pub type Result<T> = std::result::Result<T, Error>;
+pub use self::device::*;
+pub use self::proxy::*;
diff --git a/devices/src/virtio/vhost/user/proxy.rs b/devices/src/virtio/vhost/user/proxy.rs
new file mode 100644
index 000000000..a156eeddf
--- /dev/null
+++ b/devices/src/virtio/vhost/user/proxy.rs
@@ -0,0 +1,1420 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! This module implements the "Vhost User" Virtio device as specified here -
+//! <https://stefanha.github.io/virtio/vhost-user-slave.html#x1-2830007>. The
+//! device implements the Virtio-Vhost-User protocol. It acts as a proxy between
+//! the Vhost-user Master (referred to as the `Vhost-user sibling` in this
+//! module) running in a sibling VM's VMM and Virtio-Vhost-User Slave
+//! implementation (referred to as `device backend` in this module) in the
+//! device VM.
+
+use std::fs::File;
+use std::io::Write;
+use std::os::unix::net::UnixListener;
+use std::thread;
+
+use base::{
+ error, AsRawDescriptor, Event, EventType, FromRawDescriptor, IntoRawDescriptor, PollToken,
+ RawDescriptor, SafeDescriptor, Tube, TubeError, WaitContext,
+};
+use data_model::{DataInit, Le32};
+use libc::{recv, MSG_DONTWAIT, MSG_PEEK};
+use resources::Alloc;
+use uuid::Uuid;
+use vm_control::{VmMemoryDestination, VmMemoryRequest, VmMemoryResponse, VmMemorySource};
+use vm_memory::GuestMemory;
+use vmm_vhost::{
+ connection::socket::Endpoint as SocketEndpoint,
+ connection::EndpointExt,
+ message::{
+ MasterReq, VhostUserMemory, VhostUserMemoryRegion, VhostUserMsgHeader,
+ VhostUserMsgValidator, VhostUserU64,
+ },
+ Protocol, SlaveReqHelper,
+};
+
+use crate::virtio::descriptor_utils::Error as DescriptorUtilsError;
+use crate::virtio::{
+ copy_config, DescriptorChain, Interrupt, PciCapabilityType, Queue, Reader, SignalableInterrupt,
+ VirtioDevice, VirtioPciCap, Writer, TYPE_VHOST_USER,
+};
+use crate::PciAddress;
+use crate::{
+ pci::{
+ PciBarConfiguration, PciBarIndex, PciBarPrefetchable, PciBarRegionType, PciCapability,
+ PciCapabilityID,
+ },
+ virtio::{VIRTIO_F_ACCESS_PLATFORM, VIRTIO_MSI_NO_VECTOR},
+};
+
+use remain::sorted;
+use thiserror::Error as ThisError;
+use vmm_vhost::Error as VhostError;
+
+// Note: There are two sets of queues that will be mentioned here. 1st set is
+// for this Virtio PCI device itself. 2nd set is the actual device backends
+// which are set up via Virtio Vhost User (a protocol whose messages are
+// forwarded by this device) such as block, net, etc..
+//
+// The queue configuration about any device backends this proxy may support.
+const MAX_VHOST_DEVICE_QUEUES: usize = 16;
+
+// Proxy device i.e. this device's configuration.
+const NUM_PROXY_DEVICE_QUEUES: usize = 2;
+const PROXY_DEVICE_QUEUE_SIZE: u16 = 256;
+const PROXY_DEVICE_QUEUE_SIZES: &[u16] = &[PROXY_DEVICE_QUEUE_SIZE; NUM_PROXY_DEVICE_QUEUES];
+const CONFIG_UUID_SIZE: usize = 16;
+// Defined in the specification here -
+// https://stefanha.github.io/virtio/vhost-user-slave.html#x1-2870004.
+const VIRTIO_VHOST_USER_STATUS_SLAVE_UP: u8 = 0;
+
+const BAR_INDEX: u8 = 2;
+
+// Bar configuration.
+// All offsets are from the starting of bar `BAR_INDEX`.
+const DOORBELL_OFFSET: u64 = 0;
+// TODO(abhishekbh): Copied from lspci in qemu with VVU support.
+const DOORBELL_SIZE: u64 = 0x2000;
+const NOTIFICATIONS_OFFSET: u64 = DOORBELL_OFFSET + DOORBELL_SIZE;
+const NOTIFICATIONS_SIZE: u64 = 0x1000;
+const SHARED_MEMORY_OFFSET: u64 = NOTIFICATIONS_OFFSET + NOTIFICATIONS_SIZE;
+// TODO(abhishekbh): Copied from qemu with VVU support. This should be same as
+// VirtioVhostUser.device_bar_size, but it's significantly lower than the
+// memory allocated to a sibling VM. Figure out how these two are related.
+const SHARED_MEMORY_SIZE: u64 = 0x1000;
+
+// Notifications region related constants.
+const NOTIFICATIONS_VRING_SELECT_OFFSET: u64 = 0;
+const NOTIFICATIONS_MSIX_VECTOR_SELECT_OFFSET: u64 = 2;
+
+// Capabilities related configuration.
+//
+// Values written in the Doorbell must be within 32 bit i.e. a write to offset 0
+// to 3 represents a Vring 0 related event, a write to offset 4 to 7 represents
+// a Vring 1 related event.
+const DOORBELL_OFFSET_MULTIPLIER: u32 = 4;
+
+// Vhost-user sibling message types that require extra processing by this proxy. All
+// other messages are passed through to the device backend.
+const SIBLING_ACTION_MESSAGE_TYPES: &[MasterReq] = &[
+ MasterReq::SET_MEM_TABLE,
+ MasterReq::SET_LOG_BASE,
+ MasterReq::SET_LOG_FD,
+ MasterReq::SET_VRING_KICK,
+ MasterReq::SET_VRING_CALL,
+ MasterReq::SET_VRING_ERR,
+ MasterReq::SET_SLAVE_REQ_FD,
+ MasterReq::SET_INFLIGHT_FD,
+];
+
+// TODO(abhishekbh): Migrate to anyhow::Error.
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum Error {
+ /// Failed to accept connection on a socket.
+ #[error("failed to accept connection on a socket: {0}")]
+ AcceptConnection(std::io::Error),
+ /// Bar not allocated.
+ #[error("bar not allocated: {0}")]
+ BarNotAllocated(PciBarIndex),
+ /// Call event not set for a vring.
+ #[error("call event not set for {}-th vring: {0}")]
+ CallEventNotSet(usize),
+ /// Failed to create a listener.
+ #[error("failed to create a listener: {0}")]
+ CreateListener(std::io::Error),
+ /// Failed to create a wait context object.
+ #[error("failed to create a wait context object: {0}")]
+ CreateWaitContext(base::Error),
+ /// Failed to create a Writer object.
+ #[error("failed to create a Writer")]
+ CreateWriter,
+ /// Failed to send ACK in response to Vhost-user sibling message.
+ #[error("Failed to send Ack: {0}")]
+ FailedAck(VhostError),
+ /// Failed to accept sibling connection.
+ #[error("failed to accept sibling connection: {0}")]
+ FailedToAcceptSiblingConnection(std::io::Error),
+ /// Failed to read kick event for a vring.
+ #[error("Failed to read kick event for {}-th vring: {1} {0}")]
+ FailedToReadKickEvt(base::Error, usize),
+ /// Failed to receive doorbell data.
+ #[error("failed to receive doorbell data: {0}")]
+ FailedToReceiveDoorbellData(TubeError),
+ /// Failed to write call event.
+ #[error("failed to write call event for {}=th ring: {1} {0}")]
+ FailedToWriteCallEvent(base::Error, usize),
+ /// Invalid PCI bar index.
+ #[error("invalid bar index: {0}")]
+ InvalidBar(PciBarIndex),
+ /// Invalid Vhost-user sibling message.
+ #[error("invalid Vhost-user sibling message")]
+ InvalidSiblingMessage,
+ /// Kick data not set for a vring.
+ #[error("kick data not set for {}-th vring: {0}")]
+ KickDataNotSet(usize),
+ /// Failed to send a memory mapping request.
+ #[error("memory mapping request failed")]
+ MemoryMappingRequestFailure,
+ /// MSI vector not set for a vring.
+ #[error("MSI vector not set for {}-th vring: {0}")]
+ MsiVectorNotSet(usize),
+ /// Failed to parse vring kick / call file descriptors.
+ #[error("failed to parse vring kick / call file descriptors: {0}")]
+ ParseVringFdRequest(VhostError),
+ /// Failed to read payload of a Vhost-user sibling header.
+ #[error("failed to read Vhost-user sibling message header: {0}")]
+ ReadSiblingHeader(VhostError),
+ /// Failed to read payload of a Vhost-user sibling message.
+ #[error("failed to read Vhost-user sibling message payload: {0}")]
+ ReadSiblingPayload(VhostError),
+ /// Rx buffer too small to accomodate data.
+ #[error("rx buffer too small")]
+ RxBufferTooSmall,
+ /// There are no more available descriptors to receive into.
+ #[error("no rx descriptors available")]
+ RxDescriptorsExhausted,
+ /// Sibling is disconnected.
+ #[error("sibling disconnected")]
+ SiblingDisconnected,
+ /// Failed to receive a memory mapping request from the main process.
+ #[error("receiving mapping request from tube failed: {0}")]
+ TubeReceiveFailure(TubeError),
+ /// Failed to send a memory mapping request to the main process.
+ #[error("sending mapping request to tube failed: {0}")]
+ TubeSendFailure(TubeError),
+ /// Adding |SET_VRING_KICK| related epoll event failed.
+ #[error("failed to add kick event to the epoll set: {0}")]
+ WaitContextAddKickEvent(base::Error),
+ /// Removing read event from the sibling VM socket events failed.
+ #[error("failed to disable EPOLLIN on sibling VM socket fd: {0}")]
+ WaitContextDisableSiblingVmSocket(base::Error),
+ /// Adding read event to the sibling VM socket events failed.
+ #[error("failed to enable EPOLLIN on sibling VM socket fd: {0}")]
+ WaitContextEnableSiblingVmSocket(base::Error),
+ /// Failed to wait for events.
+ #[error("failed to wait for events: {0}")]
+ WaitError(base::Error),
+ /// Writing to a buffer in the guest failed.
+ #[error("failed to write to guest buffer: {0}")]
+ WriteBuffer(std::io::Error),
+ /// Failed to create a Writer.
+ #[error("failed to create a Writer: {0}")]
+ WriterCreation(DescriptorUtilsError),
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+// Device configuration as per section 5.7.4.
+#[derive(Debug, Clone, Copy)]
+#[repr(C)]
+struct VirtioVhostUserConfig {
+ status: Le32,
+ max_vhost_queues: Le32,
+ uuid: [u8; CONFIG_UUID_SIZE],
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VirtioVhostUserConfig {}
+
+impl Default for VirtioVhostUserConfig {
+ fn default() -> Self {
+ VirtioVhostUserConfig {
+ status: Le32::from(0),
+ max_vhost_queues: Le32::from(MAX_VHOST_DEVICE_QUEUES as u32),
+ uuid: [0; CONFIG_UUID_SIZE],
+ }
+ }
+}
+
+impl VirtioVhostUserConfig {
+ fn is_slave_up(&self) -> bool {
+ self.check_status_bit(VIRTIO_VHOST_USER_STATUS_SLAVE_UP)
+ }
+
+ fn check_status_bit(&self, bit: u8) -> bool {
+ let status = self.status.to_native();
+ status & (1 << bit) > 0
+ }
+}
+
+// Checks if the message requires any extra processing by this proxy.
+fn is_action_request(hdr: &VhostUserMsgHeader<MasterReq>) -> bool {
+ SIBLING_ACTION_MESSAGE_TYPES
+ .iter()
+ .any(|&h| h == hdr.get_code())
+}
+
+// Checks if |files| are sent by the Vhost-user sibling only for specific messages.
+fn check_attached_files(
+ hdr: &VhostUserMsgHeader<MasterReq>,
+ files: &Option<Vec<File>>,
+) -> Result<()> {
+ match hdr.get_code() {
+ MasterReq::SET_MEM_TABLE
+ | MasterReq::SET_VRING_CALL
+ | MasterReq::SET_VRING_KICK
+ | MasterReq::SET_VRING_ERR
+ | MasterReq::SET_LOG_BASE
+ | MasterReq::SET_LOG_FD
+ | MasterReq::SET_SLAVE_REQ_FD
+ | MasterReq::SET_INFLIGHT_FD
+ | MasterReq::ADD_MEM_REG => {
+ // These messages are always associated with an fd.
+ if files.is_some() {
+ Ok(())
+ } else {
+ Err(Error::InvalidSiblingMessage)
+ }
+ }
+ _ if files.is_some() => Err(Error::InvalidSiblingMessage),
+ _ => Ok(()),
+ }
+}
+
+// Check if `hdr` is valid.
+fn is_header_valid(hdr: &VhostUserMsgHeader<MasterReq>) -> bool {
+ if hdr.is_reply() || hdr.get_version() != 0x1 {
+ return false;
+ }
+ true
+}
+
+// Payload sent by the sibling in a |SET_VRING_KICK| message.
+#[derive(Default)]
+struct KickData {
+ // Fd sent by the sibling. This is monitored and when it's written to an interrupt is injected
+ // into the guest.
+ kick_evt: Option<Event>,
+
+ // The interrupt to be injected to the guest in response to an event to |kick_evt|.
+ msi_vector: Option<u16>,
+}
+
+// Vring related data sent through |SET_VRING_KICK| and |SET_VRING_CALL|.
+#[derive(Default)]
+struct Vring {
+ kick_data: KickData,
+ call_evt: Option<Event>,
+}
+
+// Processes messages from the Vhost-user sibling and sends it to the device backend and
+// vice-versa.
+struct Worker {
+ mem: GuestMemory,
+ interrupt: Interrupt,
+ rx_queue: Queue,
+ tx_queue: Queue,
+
+ // To communicate with the main process.
+ main_process_tube: Tube,
+
+ // The bar representing the doorbell, notification and shared memory regions.
+ pci_bar: Alloc,
+
+ // Offset at which to allocate the next shared memory region, corresponding
+ // to the |SET_MEM_TABLE| sibling message.
+ mem_offset: usize,
+
+ // Vring related data sent through |SET_VRING_KICK| and |SET_VRING_CALL|
+ // messages.
+ vrings: [Vring; MAX_VHOST_DEVICE_QUEUES],
+
+ // Helps with communication and parsing messages from the sibling.
+ slave_req_helper: SlaveReqHelper<SocketEndpoint<MasterReq>>,
+}
+
+#[derive(PollToken, Debug, Clone)]
+enum Token {
+ // Data is available on the Vhost-user sibling socket.
+ SiblingSocket,
+ // The device backend has made a read buffer available.
+ RxQueue,
+ // The device backend has sent a buffer to the |Worker::tx_queue|.
+ TxQueue,
+ // The sibling writes a kick event for the |index|-th vring.
+ SiblingKick { index: usize },
+ // crosvm has requested the device to shut down.
+ Kill,
+ // Message from the main thread.
+ MainThread,
+}
+
+impl Worker {
+ // The entry point into `Worker`.
+ // - At this point the connection with the sibling is already established.
+ // - Process messages from the device over Virtio, from the sibling over a unix domain socket,
+ // from the main thread in this device over a tube and from the main crosvm process over a
+ // tube.
+ fn run(
+ &mut self,
+ rx_queue_evt: Event,
+ tx_queue_evt: Event,
+ main_thread_tube: Tube,
+ kill_evt: Event,
+ ) -> Result<()> {
+ // TODO(abhishekbh): Should interrupt.signal_config_changed be called here ?.
+ let mut wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
+ (&self.slave_req_helper, Token::SiblingSocket),
+ (&rx_queue_evt, Token::RxQueue),
+ (&tx_queue_evt, Token::TxQueue),
+ (&main_thread_tube, Token::MainThread),
+ (&kill_evt, Token::Kill),
+ ])
+ .map_err(Error::CreateWaitContext)?;
+
+ // Represents if |slave_req_helper.endpoint| is being monitored for data
+ // from the Vhost-user sibling.
+ let mut sibling_socket_polling_enabled = true;
+ 'wait: loop {
+ let events = wait_ctx.wait().map_err(Error::WaitError)?;
+ for event in events.iter().filter(|e| e.is_readable) {
+ match event.token {
+ Token::SiblingSocket => {
+ match self.process_rx(&mut wait_ctx) {
+ Ok(()) => {}
+ Err(Error::RxDescriptorsExhausted) => {
+ // If the driver has no Rx buffers left, then no
+ // point monitoring the Vhost-user sibling for data. There
+ // would be no way to send it to the device backend.
+ wait_ctx
+ .modify(
+ &self.slave_req_helper,
+ EventType::None,
+ Token::SiblingSocket,
+ )
+ .map_err(Error::WaitContextDisableSiblingVmSocket)?;
+ sibling_socket_polling_enabled = false;
+ }
+ // TODO(b/216407443): Handle sibling disconnection. The sibling sends
+ // 0-length data the proxy device forwards it to the guest so that the
+ // VVU backend can get notified that the connection is closed.
+ Err(e) => return Err(e),
+ }
+ }
+ Token::RxQueue => {
+ if let Err(e) = rx_queue_evt.read() {
+ error!("error reading rx queue Event: {}", e);
+ break 'wait;
+ }
+
+ // Rx buffers are available, now we should monitor the
+ // Vhost-user sibling connection for data.
+ if !sibling_socket_polling_enabled {
+ wait_ctx
+ .modify(
+ &self.slave_req_helper,
+ EventType::Read,
+ Token::SiblingSocket,
+ )
+ .map_err(Error::WaitContextEnableSiblingVmSocket)?;
+ sibling_socket_polling_enabled = true;
+ }
+ }
+ Token::TxQueue => {
+ if let Err(e) = tx_queue_evt.read() {
+ error!("error reading tx queue event: {}", e);
+ break 'wait;
+ }
+ self.process_tx();
+ }
+ Token::SiblingKick { index } => {
+ if let Err(e) = self.process_sibling_kick(index) {
+ error!(
+ "error processing sibling kick for {}-th vring: {}",
+ index, e
+ );
+ break 'wait;
+ }
+ }
+ Token::MainThread => {
+ if let Err(e) = self.process_doorbell_message(&main_thread_tube) {
+ error!("error processing doorbell message: {}", e);
+ break 'wait;
+ }
+ }
+ Token::Kill => {
+ let _ = kill_evt.read();
+ break 'wait;
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+
+ // Processes data from the Vhost-user sibling and forward to the driver via Rx buffers.
+ fn process_rx(&mut self, wait_ctx: &mut WaitContext<Token>) -> Result<()> {
+ // Keep looping until -
+ // - No more Rx buffers are available on the Rx queue. OR
+ // - No more data is available on the Vhost-user sibling socket (checked via a
+ // peek).
+ //
+ // If a Rx buffer is available and if data is present on the Vhost
+ // master socket then -
+ // - Parse the Vhost-user sibling message. If it's not an action type message
+ // then copy the message as is to the Rx buffer and forward it to the
+ // device backend.
+ //
+ // Peek if any data is left on the Vhost-user sibling socket. If no, then
+ // nothing to forwad to the device backend.
+ while self.is_sibling_data_available()? {
+ let desc = self
+ .rx_queue
+ .peek(&self.mem)
+ .ok_or(Error::RxDescriptorsExhausted)?;
+ // To successfully receive attached file descriptors, we need to
+ // receive messages and corresponding attached file descriptors in
+ // this way:
+ // - receive messsage header and optional attached files.
+ // - receive optional message body and payload according size field
+ // in message header.
+ // - forward it to the device backend.
+ let (hdr, files) = self
+ .slave_req_helper
+ .as_mut()
+ .recv_header()
+ .map_err(Error::ReadSiblingHeader)?;
+ check_attached_files(&hdr, &files)?;
+ let buf = self.get_sibling_msg_data(&hdr)?;
+
+ let index = desc.index;
+ let bytes_written = {
+ if is_action_request(&hdr) {
+ // TODO(abhishekbh): Implement action messages.
+ let res = match hdr.get_code() {
+ MasterReq::SET_MEM_TABLE => {
+ // Map the sibling memory in this process and forward the
+ // sibling memory info to the slave. Only if the mapping
+ // succeeds send info along to the slave, else send a failed
+ // Ack back to the master.
+ self.set_mem_table(&hdr, &buf, files)
+ }
+ MasterReq::SET_VRING_CALL => self.set_vring_call(&hdr, &buf, files),
+ MasterReq::SET_VRING_KICK => {
+ self.set_vring_kick(wait_ctx, &hdr, &buf, files)
+ }
+ _ => {
+ unimplemented!("unimplemented action message:{:?}", hdr.get_code());
+ }
+ };
+
+ // If the "action" in response to the action messages
+ // failed then no bytes have been written to the virt
+ // queue. Else, the action is done. Now forward the
+ // message to the virt queue and return how many bytes
+ // were written.
+ match res {
+ Ok(()) => self.forward_msg_to_device(desc, &hdr, &buf),
+ Err(e) => Err(e),
+ }
+ } else {
+ // If no special processing required. Forward this message as is
+ // to the device backend.
+ self.forward_msg_to_device(desc, &hdr, &buf)
+ }
+ };
+
+ // If some bytes were written to the virt queue, now it's time
+ // to add a used buffer and notify the guest. Else if there was
+ // an error of any sort, we notify the sibling by sending an ACK
+ // with failure.
+ match bytes_written {
+ Ok(bytes_written) => {
+ // The driver is able to deal with a descriptor with 0 bytes written.
+ self.rx_queue.pop_peeked(&self.mem);
+ self.rx_queue.add_used(&self.mem, index, bytes_written);
+ if !self.rx_queue.trigger_interrupt(&self.mem, &self.interrupt) {
+ // This interrupt should always be injected. We'd rather fail
+ // fast if there is an error.
+ panic!("failed to send interrupt");
+ }
+ }
+ Err(e) => {
+ error!("failed to forward message to the device: {}", e);
+ self.slave_req_helper
+ .send_ack_message(&hdr, false)
+ .map_err(Error::FailedAck)?;
+ }
+ }
+ }
+
+ Ok(())
+ }
+
+ // Returns the sibling connection status.
+ fn is_sibling_data_available(&self) -> Result<bool> {
+ // Peek if any data is left on the Vhost-user sibling socket. If no, then
+ // nothing to forwad to the device backend.
+ let mut peek_buf = [0; 1];
+ let raw_fd = self.slave_req_helper.as_raw_descriptor();
+ // Safe because `raw_fd` and `peek_buf` are owned by this struct.
+ let peek_ret = unsafe {
+ recv(
+ raw_fd,
+ peek_buf.as_mut_ptr() as *mut libc::c_void,
+ peek_buf.len(),
+ MSG_PEEK | MSG_DONTWAIT,
+ )
+ };
+
+ match peek_ret {
+ 0 => Err(Error::SiblingDisconnected),
+ ret if ret < 0 => match base::Error::last() {
+ // EAGAIN means that no data is available. Any other error means that the sibling
+ // has disconnected.
+ e if e.errno() == libc::EAGAIN => Ok(false),
+ _ => Err(Error::SiblingDisconnected),
+ },
+ _ => Ok(true),
+ }
+ }
+
+ // Returns any data attached to a Vhost-user sibling message.
+ fn get_sibling_msg_data(&mut self, hdr: &VhostUserMsgHeader<MasterReq>) -> Result<Vec<u8>> {
+ let buf = match hdr.get_size() {
+ 0 => vec![0u8; 0],
+ len => {
+ let rbuf = self
+ .slave_req_helper
+ .as_mut()
+ .recv_data(len as usize)
+ .map_err(Error::ReadSiblingPayload)?;
+ if rbuf.len() != len as usize {
+ self.slave_req_helper
+ .send_ack_message(hdr, false)
+ .map_err(Error::FailedAck)?;
+ return Err(Error::InvalidSiblingMessage);
+ }
+ rbuf
+ }
+ };
+ Ok(buf)
+ }
+
+ // Forwards |hdr, buf| to the device backend via |desc_chain| in the virtio
+ // queue. Returns the number of bytes written to the virt queue.
+ fn forward_msg_to_device(
+ &mut self,
+ desc_chain: DescriptorChain,
+ hdr: &VhostUserMsgHeader<MasterReq>,
+ buf: &[u8],
+ ) -> Result<u32> {
+ let bytes_written = match Writer::new(self.mem.clone(), desc_chain) {
+ Ok(mut writer) => {
+ if writer.available_bytes()
+ < buf.len() + std::mem::size_of::<VhostUserMsgHeader<MasterReq>>()
+ {
+ error!("rx buffer too small to accomodate server data");
+ return Err(Error::RxBufferTooSmall);
+ }
+ // Write header first then any data. Do these separately to prevent any reorders.
+ let mut written = writer.write(hdr.as_slice()).map_err(Error::WriteBuffer)?;
+ written += writer.write(buf).map_err(Error::WriteBuffer)?;
+ written as u32
+ }
+ Err(e) => {
+ error!("failed to create Writer: {}", e);
+ return Err(Error::CreateWriter);
+ }
+ };
+ Ok(bytes_written)
+ }
+
+ // Handles `SET_MEM_TABLE` message from sibling. Parses `hdr` into
+ // memory region information. For each memory region sent by the Vhost
+ // Master, it mmaps a region of memory in the main process. At the end of
+ // this function both this VMM and the sibling have two regions of
+ // virtual memory pointing to the same physical page. These regions will be
+ // accessed by the device VM and the silbing VM.
+ fn set_mem_table(
+ &mut self,
+ hdr: &VhostUserMsgHeader<MasterReq>,
+ payload: &[u8],
+ files: Option<Vec<File>>,
+ ) -> Result<()> {
+ if !is_header_valid(hdr) {
+ return Err(Error::InvalidSiblingMessage);
+ }
+
+ // `hdr` is followed by a `payload`. `payload` consists of metadata about the number of
+ // memory regions and then memory regions themeselves. The memory regions structs consist of
+ // metadata about actual device related memory passed from the sibling. Ensure that the size
+ // of the payload is consistent with this structure.
+ let payload_size = payload.len();
+ if payload_size < std::mem::size_of::<VhostUserMemory>() {
+ error!("payload size {} lesser than minimum required", payload_size);
+ return Err(Error::InvalidSiblingMessage);
+ }
+ let (msg_slice, regions_slice) = payload.split_at(std::mem::size_of::<VhostUserMemory>());
+ let msg = VhostUserMemory::from_slice(msg_slice).ok_or(Error::InvalidSiblingMessage)?;
+ if !msg.is_valid() {
+ return Err(Error::InvalidSiblingMessage);
+ }
+
+ let memory_region_metadata_size = std::mem::size_of::<VhostUserMemory>();
+ if payload_size
+ != memory_region_metadata_size
+ + msg.num_regions as usize * std::mem::size_of::<VhostUserMemoryRegion>()
+ {
+ return Err(Error::InvalidSiblingMessage);
+ }
+
+ let regions: Vec<&VhostUserMemoryRegion> = regions_slice
+ .chunks(std::mem::size_of::<VhostUserMemoryRegion>())
+ .map(VhostUserMemoryRegion::from_slice)
+ .collect::<Option<_>>()
+ .ok_or_else(|| {
+ error!("failed to construct VhostUserMemoryRegion array");
+ Error::InvalidSiblingMessage
+ })?;
+
+ if !regions.iter().all(|r| r.is_valid()) {
+ return Err(Error::InvalidSiblingMessage);
+ }
+
+ let files = files.ok_or(Error::InvalidSiblingMessage)?;
+ if files.len() != msg.num_regions as usize {
+ return Err(Error::InvalidSiblingMessage);
+ }
+
+ self.create_sibling_guest_memory(&regions, files)?;
+ Ok(())
+ }
+
+ // Mmaps sibling memory in this device's VMM's main process' address
+ // space.
+ pub fn create_sibling_guest_memory(
+ &mut self,
+ contexts: &[&VhostUserMemoryRegion],
+ files: Vec<File>,
+ ) -> Result<()> {
+ if contexts.len() != files.len() {
+ return Err(Error::InvalidSiblingMessage);
+ }
+
+ for (region, file) in contexts.iter().zip(files.into_iter()) {
+ let request = VmMemoryRequest::RegisterMemory {
+ source: VmMemorySource::Descriptor {
+ descriptor: SafeDescriptor::from(file),
+ offset: region.mmap_offset,
+ size: region.memory_size,
+ },
+ dest: VmMemoryDestination::ExistingAllocation {
+ allocation: self.pci_bar,
+ offset: self.mem_offset as u64,
+ },
+ read_only: false,
+ };
+ self.process_memory_mapping_request(&request)?;
+ self.mem_offset += region.memory_size as usize;
+ }
+ Ok(())
+ }
+
+ // Sends memory mapping request to the main process. If successful adds the
+ // mmaped info into |sibling_mem|, else returns error.
+ fn process_memory_mapping_request(&mut self, request: &VmMemoryRequest) -> Result<()> {
+ self.main_process_tube
+ .send(request)
+ .map_err(Error::TubeSendFailure)?;
+
+ let response = self
+ .main_process_tube
+ .recv()
+ .map_err(Error::TubeReceiveFailure)?;
+ match response {
+ VmMemoryResponse::RegisterMemory { .. } => Ok(()),
+ VmMemoryResponse::Err(e) => {
+ error!("memory mapping failed: {}", e);
+ Err(Error::MemoryMappingRequestFailure)
+ }
+ _ => Err(Error::MemoryMappingRequestFailure),
+ }
+ }
+
+ // Handles |SET_VRING_CALL|.
+ fn set_vring_call(
+ &mut self,
+ hdr: &VhostUserMsgHeader<MasterReq>,
+ payload: &[u8],
+ files: Option<Vec<File>>,
+ ) -> Result<()> {
+ if !is_header_valid(hdr) {
+ return Err(Error::InvalidSiblingMessage);
+ }
+
+ let payload_size = payload.len();
+ if payload_size != std::mem::size_of::<VhostUserU64>() {
+ error!("wrong payload size {}", payload_size);
+ return Err(Error::InvalidSiblingMessage);
+ }
+
+ let (index, file) = self
+ .slave_req_helper
+ .handle_vring_fd_request(payload, files)
+ .map_err(Error::ParseVringFdRequest)?;
+
+ if index as usize >= MAX_VHOST_DEVICE_QUEUES {
+ error!("illegal Vring index:{}", index);
+ return Err(Error::InvalidSiblingMessage);
+ }
+
+ let file = file.ok_or_else(|| {
+ error!("no file found for SET_VRING_CALL");
+ Error::InvalidSiblingMessage
+ })?;
+
+ // Safe because we own the file.
+ self.vrings[index as usize].call_evt =
+ unsafe { Some(Event::from_raw_descriptor(file.into_raw_descriptor())) };
+
+ Ok(())
+ }
+
+ // Handles |SET_VRING_KICK|. If successful it sets up an event handler for a
+ // write to the sent kick fd.
+ fn set_vring_kick(
+ &mut self,
+ wait_ctx: &mut WaitContext<Token>,
+ hdr: &VhostUserMsgHeader<MasterReq>,
+ payload: &[u8],
+ files: Option<Vec<File>>,
+ ) -> Result<()> {
+ if !is_header_valid(hdr) {
+ return Err(Error::InvalidSiblingMessage);
+ }
+
+ let payload_size = payload.len();
+ if payload_size != std::mem::size_of::<VhostUserU64>() {
+ error!("wrong payload size {}", payload_size);
+ return Err(Error::InvalidSiblingMessage);
+ }
+
+ let (index, file) = self
+ .slave_req_helper
+ .handle_vring_fd_request(payload, files)
+ .map_err(Error::ParseVringFdRequest)?;
+
+ if index as usize >= MAX_VHOST_DEVICE_QUEUES {
+ error!("illegal Vring index:{}", index);
+ return Err(Error::InvalidSiblingMessage);
+ }
+
+ let file = file.ok_or_else(|| {
+ error!("no file found for SET_VRING_KICK");
+ Error::InvalidSiblingMessage
+ })?;
+
+ // Safe because we own the file.
+ let kick_evt = unsafe { Event::from_raw_descriptor(file.into_raw_descriptor()) };
+ let kick_data = &mut self.vrings[index as usize].kick_data;
+
+ wait_ctx
+ .add(
+ &kick_evt,
+ Token::SiblingKick {
+ index: index as usize,
+ },
+ )
+ .map_err(Error::WaitContextAddKickEvent)?;
+ kick_data.kick_evt = Some(kick_evt);
+
+ Ok(())
+ }
+
+ // Processes data from the device backend (via virtio Tx queue) and forward it to
+ // the Vhost-user sibling over its socket connection.
+ fn process_tx(&mut self) {
+ while let Some(desc_chain) = self.tx_queue.pop(&self.mem) {
+ let index = desc_chain.index;
+ match Reader::new(self.mem.clone(), desc_chain) {
+ Ok(mut reader) => {
+ let expected_count = reader.available_bytes();
+ match reader.read_to(self.slave_req_helper.as_mut().as_mut(), expected_count) {
+ Ok(count) => {
+ // The |reader| guarantees that all the available data is read.
+ if count != expected_count {
+ error!("wrote only {} bytes of {}", count, expected_count);
+ }
+ }
+ Err(e) => error!("failed to write message to vhost-vmm: {}", e),
+ }
+ }
+ Err(e) => error!("failed to create Reader: {}", e),
+ }
+ self.tx_queue.add_used(&self.mem, index, 0);
+ if !self.tx_queue.trigger_interrupt(&self.mem, &self.interrupt) {
+ panic!("failed inject tx queue interrupt");
+ }
+ }
+ }
+
+ // Processes a sibling kick for the |index|-th vring and injects the corresponding interrupt
+ // into the guest.
+ fn process_sibling_kick(&mut self, index: usize) -> Result<()> {
+ // The sibling is indicating a used queue event on
+ // vring number |index|. Acknowledge the event and
+ // inject the related interrupt into the guest.
+ let kick_data = &self.vrings[index as usize].kick_data;
+ let kick_evt = kick_data
+ .kick_evt
+ .as_ref()
+ .ok_or(Error::KickDataNotSet(index))?;
+ kick_evt
+ .read()
+ .map_err(|e| Error::FailedToReadKickEvt(e, index))?;
+ match kick_data.msi_vector {
+ Some(msi_vector) => {
+ self.interrupt.signal_used_queue(msi_vector);
+ Ok(())
+ }
+ None => Err(Error::MsiVectorNotSet(index)),
+ }
+ }
+
+ // Processes a message sent, on `main_thread_tube`, in response to a doorbell write. It writes
+ // to the corresponding call event of the vring index sent over `main_thread_tube`.
+ fn process_doorbell_message(&mut self, main_thread_tube: &Tube) -> Result<()> {
+ // It's okay to call |expect| here as there's no way to indicate failure on
+ // a doorbell write operation. We'd rather fail early than have inconsistent
+ // state.
+ let index: usize = main_thread_tube
+ .recv()
+ .map_err(Error::FailedToReceiveDoorbellData)?;
+ let call_evt = self.vrings[index]
+ .call_evt
+ .as_ref()
+ .ok_or(Error::CallEventNotSet(index))?;
+ call_evt
+ .write(1)
+ .map_err(|e| Error::FailedToWriteCallEvent(e, index))?;
+ Ok(())
+ }
+}
+
+// Doorbell capability of the proxy device.
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct VirtioPciDoorbellCap {
+ cap: VirtioPciCap,
+ doorbell_off_multiplier: Le32,
+}
+// It is safe to implement DataInit; `VirtioPciCap` implements DataInit and for
+// Le32 any value is valid.
+unsafe impl DataInit for VirtioPciDoorbellCap {}
+
+impl PciCapability for VirtioPciDoorbellCap {
+ fn bytes(&self) -> &[u8] {
+ self.as_slice()
+ }
+
+ // TODO: What should this be.
+ fn id(&self) -> PciCapabilityID {
+ PciCapabilityID::VendorSpecific
+ }
+
+ // TODO: What should this be.
+ fn writable_bits(&self) -> Vec<u32> {
+ vec![0u32; 4]
+ }
+}
+
+impl VirtioPciDoorbellCap {
+ pub fn new(cap: VirtioPciCap, doorbell_off_multiplier: u32) -> Self {
+ VirtioPciDoorbellCap {
+ cap,
+ doorbell_off_multiplier: Le32::from(doorbell_off_multiplier),
+ }
+ }
+}
+
+// Used to store parameters passed in the |activate| function.
+struct ActivateParams {
+ mem: GuestMemory,
+ interrupt: Interrupt,
+ queues: Vec<Queue>,
+ queue_evts: Vec<Event>,
+}
+
+pub struct VirtioVhostUser {
+ base_features: u64,
+
+ // Represents the amount of memory to be mapped for a sibling VM. Each
+ // Virtio Vhost User Slave implementation requires access to the entire sibling
+ // memory.
+ //
+ // TODO(abhishekbh): Understand why shared memory region size and overall bar
+ // size differ in the QEMU implementation.
+ device_bar_size: u64,
+
+ // Bound socket waiting to accept a socket connection from the Vhost-user
+ // sibling.
+ listener: Option<UnixListener>,
+
+ // Device configuration.
+ config: VirtioVhostUserConfig,
+
+ kill_evt: Option<Event>,
+ worker_thread: Option<thread::JoinHandle<Result<Worker>>>,
+
+ // The bar representing the doorbell, notification and shared memory regions.
+ pci_bar: Option<Alloc>,
+ // The device backend queue index selected by the driver by writing to the
+ // Notifications region at offset `NOTIFICATIONS_MSIX_VECTOR_SELECT_OFFSET`
+ // in the bar. This points into `notification_msix_vectors`.
+ notification_select: Option<u16>,
+ // Stores msix vectors corresponding to each device backend queue.
+ notification_msix_vectors: [Option<u16>; MAX_VHOST_DEVICE_QUEUES],
+
+ // Cache for params stored in |activate|.
+ activate_params: Option<ActivateParams>,
+
+ // Is Vhost-user sibling connected.
+ sibling_connected: bool,
+
+ // To communicate with the main process.
+ main_process_tube: Option<Tube>,
+
+ // To communicate with the worker thread.
+ worker_thread_tube: Option<Tube>,
+
+ // PCI address that this device needs to be allocated if specified.
+ pci_address: Option<PciAddress>,
+}
+
+impl VirtioVhostUser {
+ pub fn new(
+ base_features: u64,
+ listener: UnixListener,
+ main_process_tube: Tube,
+ pci_address: Option<PciAddress>,
+ uuid: Option<Uuid>,
+ max_sibling_mem_size: u64,
+ ) -> Result<VirtioVhostUser> {
+ let device_bar_size = max_sibling_mem_size
+ .checked_next_power_of_two()
+ .expect("Sibling too large");
+
+ Ok(VirtioVhostUser {
+ base_features: base_features | 1 << VIRTIO_F_ACCESS_PLATFORM,
+ device_bar_size,
+ listener: Some(listener),
+ config: VirtioVhostUserConfig {
+ status: Le32::from(0),
+ max_vhost_queues: Le32::from(MAX_VHOST_DEVICE_QUEUES as u32),
+ uuid: *uuid.unwrap_or_default().as_bytes(),
+ },
+ kill_evt: None,
+ worker_thread: None,
+ pci_bar: None,
+ notification_select: None,
+ notification_msix_vectors: [None; MAX_VHOST_DEVICE_QUEUES],
+ activate_params: None,
+ sibling_connected: false,
+ main_process_tube: Some(main_process_tube),
+ worker_thread_tube: None,
+ pci_address,
+ })
+ }
+
+ fn check_bar_metadata(&self, bar_index: PciBarIndex) -> Result<()> {
+ if bar_index != BAR_INDEX as usize {
+ return Err(Error::InvalidBar(bar_index));
+ }
+
+ if self.pci_bar.is_none() {
+ return Err(Error::BarNotAllocated(bar_index));
+ }
+
+ Ok(())
+ }
+
+ // Handles writes to the DOORBELL region of the BAR as per the VVU spec.
+ fn write_bar_doorbell(&mut self, offset: u64) {
+ // The |offset| represents the Vring number who call event needs to be
+ // written to.
+ let vring = (offset / DOORBELL_OFFSET_MULTIPLIER as u64) as usize;
+ match &self.worker_thread_tube {
+ Some(worker_thread_tube) => {
+ if let Err(e) = worker_thread_tube.send(&vring) {
+ error!("failed to send doorbell write request: {}", e);
+ }
+ }
+ None => {
+ error!("worker thread tube not allocated");
+ }
+ }
+ }
+
+ // Implement writing to the notifications bar as per the VVU spec.
+ fn write_bar_notifications(&mut self, offset: u64, data: &[u8]) {
+ if data.len() < std::mem::size_of::<u16>() {
+ error!("data buffer is too small: {}", data.len());
+ return;
+ }
+
+ // The driver will first write to |NOTIFICATIONS_VRING_SELECT_OFFSET| to
+ // specify which index in `self.notification_msix_vectors` to write to.
+ // Then it writes the msix vector value by writing to
+ // `NOTIFICATIONS_MSIX_VECTOR_SELECT_OFFSET`.
+ let mut dst = [0u8; 2];
+ dst.copy_from_slice(&data[..2]);
+ let val = u16::from_le_bytes(dst);
+ match offset {
+ NOTIFICATIONS_VRING_SELECT_OFFSET => {
+ self.notification_select = Some(val);
+ }
+ NOTIFICATIONS_MSIX_VECTOR_SELECT_OFFSET => {
+ if let Some(notification_select) = self.notification_select {
+ if notification_select as usize >= self.notification_msix_vectors.len() {
+ error!("invalid notification select: {}", notification_select);
+ return;
+ }
+ self.notification_msix_vectors[notification_select as usize] =
+ if val == VIRTIO_MSI_NO_VECTOR {
+ None
+ } else {
+ Some(val)
+ };
+ } else {
+ error!("no notification select set");
+ }
+ }
+ _ => {
+ error!("invalid notification cfg offset: {}", offset);
+ }
+ }
+ }
+
+ // Implement reading from the notifications bar as per the VVU spec.
+ fn read_bar_notifications(&mut self, offset: u64, data: &mut [u8]) {
+ if data.len() < std::mem::size_of::<u16>() {
+ error!("data buffer is too small: {}", data.len());
+ return;
+ }
+
+ // The driver will first write to |NOTIFICATIONS_VRING_SELECT_OFFSET| to
+ // specify which index in |self.notification_msix_vectors| to read from.
+ // Then it reads the msix vector value by reading from
+ // |NOTIFICATIONS_MSIX_VECTOR_SELECT_OFFSET|.
+ // Return 0 if a vector value hasn't been set for the queue.
+ let mut val = 0;
+ if offset == NOTIFICATIONS_VRING_SELECT_OFFSET {
+ val = self.notification_select.unwrap_or(0);
+ } else if offset == NOTIFICATIONS_MSIX_VECTOR_SELECT_OFFSET {
+ if let Some(notification_select) = self.notification_select {
+ val = self.notification_msix_vectors[notification_select as usize].unwrap_or(0);
+ } else {
+ error!("no notification select set");
+ }
+ } else {
+ error!("invalid notification cfg offset: {}", offset);
+ }
+ let d = u16::to_le_bytes(val);
+ data[..2].copy_from_slice(&d);
+ }
+
+ // Initializes state and starts the worker thread which will process all messages to this device
+ // and send out messages in response.
+ fn start_worker(&mut self) {
+ // This function should never be called if this device hasn't been
+ // activated.
+ if self.activate_params.is_none() {
+ panic!("device not activated");
+ }
+
+ let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
+ Ok(v) => v,
+ Err(e) => {
+ error!("failed creating kill Event pair: {}", e);
+ return;
+ }
+ };
+ self.kill_evt = Some(self_kill_evt);
+
+ let listener = self.listener.take().expect("listener should be set");
+
+ let main_process_tube = self.main_process_tube.take().expect("tube missing");
+
+ // Safe because a PCI bar is guaranteed to be allocated at this point.
+ let pci_bar = self.pci_bar.expect("PCI bar unallocated");
+
+ // Initialize the Worker with the Msix vector values to be injected for
+ // each Vhost device queue.
+ let mut vrings: [Vring; MAX_VHOST_DEVICE_QUEUES] = Default::default();
+ for (i, vring) in vrings.iter_mut().enumerate() {
+ vring.kick_data = KickData {
+ kick_evt: None,
+ msi_vector: self.notification_msix_vectors[i],
+ };
+ }
+
+ // Create tube to communicate with the worker thread.
+ let (worker_thread_tube, main_thread_tube) =
+ Tube::pair().expect("failed to create tube pair");
+ self.worker_thread_tube = Some(worker_thread_tube);
+
+ // TODO(abhishekbh): Should interrupt.signal_config_changed be called ?
+ self.sibling_connected = true;
+ let mut activate_params = self.activate_params.take().unwrap();
+ // This thread will wait for the sibling to connect and the continuously parse messages from
+ // the sibling as well as the device (over Virtio).
+ let worker_result = thread::Builder::new()
+ .name("virtio_vhost_user".to_string())
+ .spawn(move || {
+ let rx_queue = activate_params.queues.remove(0);
+ let tx_queue = activate_params.queues.remove(0);
+
+ // Block until the connection with the sibling is established. We do this in a
+ // thread to avoid blocking the main thread.
+ let (socket, _) = listener
+ .accept()
+ .map_err(Error::FailedToAcceptSiblingConnection)?;
+
+ // Although this device is relates to Virtio Vhost User but it uses
+ // `slave_req_helper` to parse messages from a Vhost-user sibling.
+ // Thus, we need `slave_req_helper` in `Protocol::Regular` mode and not
+ // in `Protocol::Virtio' mode.
+ let slave_req_helper: SlaveReqHelper<SocketEndpoint<MasterReq>> =
+ SlaveReqHelper::new(SocketEndpoint::from(socket), Protocol::Regular);
+
+ let mut worker = Worker {
+ mem: activate_params.mem,
+ interrupt: activate_params.interrupt,
+ rx_queue,
+ tx_queue,
+ main_process_tube,
+ pci_bar,
+ mem_offset: SHARED_MEMORY_OFFSET as usize,
+ vrings,
+ slave_req_helper,
+ };
+ let rx_queue_evt = activate_params.queue_evts.remove(0);
+ let tx_queue_evt = activate_params.queue_evts.remove(0);
+ if let Err(e) = worker.run(rx_queue_evt, tx_queue_evt, main_thread_tube, kill_evt) {
+ // TODO(b/216407443): Handle sibling reconnect events.
+ error!("worker thread exited: {}", e);
+ }
+ Ok(worker)
+ });
+
+ match worker_result {
+ Err(e) => {
+ error!("failed to spawn virtio_vhost_user worker: {}", e);
+ }
+ Ok(join_handle) => {
+ self.worker_thread = Some(join_handle);
+ }
+ }
+ }
+}
+
+impl Drop for VirtioVhostUser {
+ fn drop(&mut self) {
+ if let Some(kill_evt) = self.kill_evt.take() {
+ match kill_evt.write(1) {
+ Ok(()) => {
+ if let Some(worker_thread) = self.worker_thread.take() {
+ // Ignore the result because there is nothing we can do about it.
+ let _ = worker_thread.join();
+ }
+ }
+ Err(e) => error!("failed to write kill event: {}", e),
+ }
+ }
+ }
+}
+
+impl VirtioDevice for VirtioVhostUser {
+ fn features(&self) -> u64 {
+ self.base_features
+ }
+
+ fn keep_rds(&self) -> Vec<RawDescriptor> {
+ let mut rds = Vec::new();
+
+ if let Some(rd) = &self.listener {
+ rds.push(rd.as_raw_descriptor());
+ }
+
+ if let Some(rd) = &self.kill_evt {
+ rds.push(rd.as_raw_descriptor());
+ }
+
+ if let Some(rd) = &self.main_process_tube {
+ rds.push(rd.as_raw_descriptor());
+ }
+
+ // `self.worker_thread_tube` is set after a fork / keep_rds is called in multiprocess mode.
+ // Hence, it's not required to be processed in this function.
+ rds
+ }
+
+ fn device_type(&self) -> u32 {
+ TYPE_VHOST_USER
+ }
+
+ fn queue_max_sizes(&self) -> &[u16] {
+ PROXY_DEVICE_QUEUE_SIZES
+ }
+
+ fn num_interrupts(&self) -> usize {
+ // The total interrupts include both this device's interrupts as well as
+ // the VVU device related interrupt.
+ NUM_PROXY_DEVICE_QUEUES + MAX_VHOST_DEVICE_QUEUES
+ }
+
+ fn read_config(&self, offset: u64, data: &mut [u8]) {
+ copy_config(
+ data,
+ 0, /* dst_offset */
+ self.config.as_slice(),
+ offset,
+ );
+ }
+
+ fn write_config(&mut self, offset: u64, data: &[u8]) {
+ copy_config(
+ self.config.as_mut_slice(),
+ offset,
+ data,
+ 0, /* src_offset */
+ );
+
+ // The driver has indicated that it's safe for the Vhost-user sibling to
+ // initiate a connection and send data over.
+ if self.config.is_slave_up() && !self.sibling_connected {
+ self.start_worker();
+ }
+ }
+
+ fn get_device_caps(&self) -> Vec<Box<dyn crate::pci::PciCapability>> {
+ // Allocate capabilities as per sections 5.7.7.5, 5.7.7.6, 5.7.7.7 of
+ // the link at the top of the file. The PCI bar is organized in the
+ // following format |Doorbell|Notification|Shared Memory|.
+ let mut doorbell_virtio_pci_cap = VirtioPciCap::new(
+ PciCapabilityType::DoorbellConfig,
+ BAR_INDEX,
+ DOORBELL_OFFSET as u32,
+ DOORBELL_SIZE as u32,
+ );
+ doorbell_virtio_pci_cap.set_cap_len(std::mem::size_of::<VirtioPciDoorbellCap>() as u8);
+ let doorbell = Box::new(VirtioPciDoorbellCap::new(
+ doorbell_virtio_pci_cap,
+ DOORBELL_OFFSET_MULTIPLIER,
+ ));
+
+ let notification = Box::new(VirtioPciCap::new(
+ PciCapabilityType::NotificationConfig,
+ BAR_INDEX,
+ NOTIFICATIONS_OFFSET as u32,
+ NOTIFICATIONS_SIZE as u32,
+ ));
+
+ let shared_memory = Box::new(VirtioPciCap::new(
+ PciCapabilityType::SharedMemoryConfig,
+ BAR_INDEX,
+ SHARED_MEMORY_OFFSET as u32,
+ SHARED_MEMORY_SIZE as u32,
+ ));
+
+ vec![doorbell, notification, shared_memory]
+ }
+
+ fn get_device_bars(&mut self, address: PciAddress) -> Vec<PciBarConfiguration> {
+ // A PCI bar corresponding to |Doorbell|Notification|Shared Memory| will
+ // be allocated and its address (64 bit) will be stored in BAR 2 and BAR
+ // 3. This is as per the VVU spec and qemu implementation.
+ self.pci_bar = Some(Alloc::PciBar {
+ bus: address.bus,
+ dev: address.dev,
+ func: address.func,
+ bar: BAR_INDEX,
+ });
+
+ vec![PciBarConfiguration::new(
+ BAR_INDEX as usize,
+ self.device_bar_size,
+ PciBarRegionType::Memory64BitRegion,
+ // NotPrefetchable so as to exit on every read / write event in the
+ // guest.
+ PciBarPrefetchable::NotPrefetchable,
+ )]
+ }
+
+ fn activate(
+ &mut self,
+ mem: GuestMemory,
+ interrupt: Interrupt,
+ queues: Vec<Queue>,
+ queue_evts: Vec<Event>,
+ ) {
+ if queues.len() != NUM_PROXY_DEVICE_QUEUES || queue_evts.len() != NUM_PROXY_DEVICE_QUEUES {
+ error!("bad queue length: {} {}", queues.len(), queue_evts.len());
+ return;
+ }
+
+ // Cache these to be used later in the `start_worker` function.
+ self.activate_params = Some(ActivateParams {
+ mem,
+ interrupt,
+ queues,
+ queue_evts,
+ });
+ }
+
+ fn read_bar(&mut self, bar_index: PciBarIndex, offset: u64, data: &mut [u8]) {
+ if let Err(e) = self.check_bar_metadata(bar_index) {
+ error!("invalid bar metadata: {}", e);
+ return;
+ }
+
+ if (NOTIFICATIONS_OFFSET..SHARED_MEMORY_OFFSET).contains(&offset) {
+ self.read_bar_notifications(offset - NOTIFICATIONS_OFFSET, data);
+ } else {
+ error!("addr is outside known region for reads");
+ }
+ }
+
+ fn write_bar(&mut self, bar_index: PciBarIndex, offset: u64, data: &[u8]) {
+ if let Err(e) = self.check_bar_metadata(bar_index) {
+ error!("invalid bar metadata: {}", e);
+ return;
+ }
+
+ if (DOORBELL_OFFSET..NOTIFICATIONS_OFFSET).contains(&offset) {
+ self.write_bar_doorbell(offset - DOORBELL_OFFSET);
+ } else if (NOTIFICATIONS_OFFSET..SHARED_MEMORY_OFFSET).contains(&offset) {
+ self.write_bar_notifications(offset - NOTIFICATIONS_OFFSET, data);
+ } else {
+ error!("addr is outside known region for writes");
+ }
+ }
+
+ fn reset(&mut self) -> bool {
+ if let Some(kill_evt) = self.kill_evt.take() {
+ if let Err(e) = kill_evt.write(1) {
+ error!("failed to notify the kill event: {}", e);
+ return false;
+ }
+ }
+
+ if let Some(worker_thread) = self.worker_thread.take() {
+ if let Err(e) = worker_thread.join() {
+ error!("failed to get back resources: {:?}", e);
+ return false;
+ }
+ }
+
+ // TODO(abhishekbh): Disconnect from sibling and reset
+ // `sibling_connected`.
+ false
+ }
+
+ fn pci_address(&self) -> Option<PciAddress> {
+ self.pci_address
+ }
+}
diff --git a/devices/src/virtio/vhost/user/block.rs b/devices/src/virtio/vhost/user/vmm/block.rs
index f3b6abfbe..bc575544c 100644
--- a/devices/src/virtio/vhost/user/block.rs
+++ b/devices/src/virtio/vhost/user/vmm/block.rs
@@ -9,16 +9,12 @@ use std::thread;
use std::u32;
use base::{error, Event, RawDescriptor};
-use cros_async::Executor;
use virtio_sys::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
use vm_memory::GuestMemory;
-use vmm_vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
-use vmm_vhost::vhost_user::Master;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
-use crate::virtio::vhost::user::handler::VhostUserHandler;
-use crate::virtio::vhost::user::worker::Worker;
-use crate::virtio::vhost::user::{Error, Result};
-use crate::virtio::{virtio_blk_config, Interrupt, Queue, VirtioDevice, TYPE_BLOCK};
+use crate::virtio::vhost::user::vmm::{handler::VhostUserHandler, worker::Worker, Error, Result};
+use crate::virtio::{block::common::virtio_blk_config, Interrupt, Queue, VirtioDevice, TYPE_BLOCK};
const VIRTIO_BLK_F_SEG_MAX: u32 = 2;
const VIRTIO_BLK_F_RO: u32 = 5;
@@ -39,8 +35,6 @@ pub struct Block {
impl Block {
pub fn new<P: AsRef<Path>>(base_features: u64, socket_path: P) -> Result<Block> {
let socket = UnixStream::connect(&socket_path).map_err(Error::SocketConnect)?;
- // TODO(b/181753022): Support multiple queues.
- let vhost_user_blk = Master::from_stream(socket, 1 /* queues_num */);
let allow_features = 1u64 << crate::virtio::VIRTIO_F_VERSION_1
| 1 << VIRTIO_BLK_F_SEG_MAX
@@ -50,12 +44,15 @@ impl Block {
| 1 << VIRTIO_BLK_F_DISCARD
| 1 << VIRTIO_BLK_F_WRITE_ZEROES
| 1 << VIRTIO_RING_F_EVENT_IDX
+ | base_features
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
let allow_protocol_features = VhostUserProtocolFeatures::CONFIG;
- let mut handler = VhostUserHandler::new(
- vhost_user_blk,
+ let mut handler = VhostUserHandler::new_from_stream(
+ socket,
+ // TODO(b/181753022): Support multiple queues.
+ 1, /* queues_num */
allow_features,
init_features,
allow_protocol_features,
@@ -145,14 +142,13 @@ impl VirtioDevice for Block {
let worker_result = thread::Builder::new()
.name("vhost_user_virtio_blk".to_string())
.spawn(move || {
- let ex = Executor::new().expect("failed to create an executor");
let mut worker = Worker {
queues,
mem,
kill_evt,
};
- if let Err(e) = worker.run(&ex, interrupt) {
+ if let Err(e) = worker.run(interrupt) {
error!("failed to start a worker: {}", e);
}
worker
@@ -161,7 +157,6 @@ impl VirtioDevice for Block {
match worker_result {
Err(e) => {
error!("failed to spawn vhost-user virtio_blk worker: {}", e);
- return;
}
Ok(join_handle) => {
self.worker_thread = Some(join_handle);
diff --git a/devices/src/virtio/vhost/user/vmm/console.rs b/devices/src/virtio/vhost/user/vmm/console.rs
new file mode 100644
index 000000000..c22023cf7
--- /dev/null
+++ b/devices/src/virtio/vhost/user/vmm/console.rs
@@ -0,0 +1,154 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::RefCell;
+use std::os::unix::net::UnixStream;
+use std::path::Path;
+use std::thread;
+
+use base::{error, Event, RawDescriptor};
+use vm_memory::GuestMemory;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+use crate::virtio::console::{virtio_console_config, QUEUE_SIZE};
+use crate::virtio::vhost::user::vmm::{handler::VhostUserHandler, worker::Worker, Error, Result};
+use crate::virtio::{Interrupt, Queue, VirtioDevice, TYPE_CONSOLE};
+
+pub struct Console {
+ kill_evt: Option<Event>,
+ worker_thread: Option<thread::JoinHandle<Worker>>,
+ handler: RefCell<VhostUserHandler>,
+ queue_sizes: Vec<u16>,
+}
+
+impl Console {
+ pub fn new<P: AsRef<Path>>(base_features: u64, socket_path: P) -> Result<Console> {
+ let socket = UnixStream::connect(&socket_path).map_err(Error::SocketConnect)?;
+
+ // VIRTIO_CONSOLE_F_MULTIPORT is not supported, so we just implement port 0 (receiveq,
+ // transmitq)
+ let queues_num = 2;
+
+ let allow_features = 1u64 << crate::virtio::VIRTIO_F_VERSION_1
+ | base_features
+ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let allow_protocol_features = VhostUserProtocolFeatures::CONFIG;
+
+ let mut handler = VhostUserHandler::new_from_stream(
+ socket,
+ queues_num,
+ allow_features,
+ init_features,
+ allow_protocol_features,
+ )?;
+ let queue_sizes = handler.queue_sizes(QUEUE_SIZE, queues_num as usize)?;
+
+ Ok(Console {
+ kill_evt: None,
+ worker_thread: None,
+ handler: RefCell::new(handler),
+ queue_sizes,
+ })
+ }
+}
+
+impl VirtioDevice for Console {
+ fn keep_rds(&self) -> Vec<RawDescriptor> {
+ Vec::new()
+ }
+
+ fn features(&self) -> u64 {
+ self.handler.borrow().avail_features
+ }
+
+ fn device_type(&self) -> u32 {
+ TYPE_CONSOLE
+ }
+
+ fn queue_max_sizes(&self) -> &[u16] {
+ self.queue_sizes.as_slice()
+ }
+
+ fn read_config(&self, offset: u64, data: &mut [u8]) {
+ if let Err(e) = self
+ .handler
+ .borrow_mut()
+ .read_config::<virtio_console_config>(offset, data)
+ {
+ error!("failed to read config: {}", e);
+ }
+ }
+
+ fn activate(
+ &mut self,
+ mem: GuestMemory,
+ interrupt: Interrupt,
+ queues: Vec<Queue>,
+ queue_evts: Vec<Event>,
+ ) {
+ if let Err(e) = self
+ .handler
+ .borrow_mut()
+ .activate(&mem, &interrupt, &queues, &queue_evts)
+ {
+ error!("failed to activate queues: {}", e);
+ return;
+ }
+ let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
+ Ok(v) => v,
+ Err(e) => {
+ error!("failed creating kill Event pair: {}", e);
+ return;
+ }
+ };
+ self.kill_evt = Some(self_kill_evt);
+
+ let worker_result = thread::Builder::new()
+ .name("vhost_user_virtio_console".to_string())
+ .spawn(move || {
+ let mut worker = Worker {
+ queues,
+ mem,
+ kill_evt,
+ };
+
+ if let Err(e) = worker.run(interrupt) {
+ error!("failed to start a worker: {}", e);
+ }
+ worker
+ });
+
+ match worker_result {
+ Err(e) => {
+ error!("failed to spawn vhost-user virtio_console worker: {}", e);
+ }
+ Ok(join_handle) => {
+ self.worker_thread = Some(join_handle);
+ }
+ }
+ }
+
+ fn reset(&mut self) -> bool {
+ if let Err(e) = self.handler.borrow_mut().reset(self.queue_sizes.len()) {
+ error!("Failed to reset console device: {}", e);
+ false
+ } else {
+ true
+ }
+ }
+}
+
+impl Drop for Console {
+ 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);
+ }
+
+ if let Some(worker_thread) = self.worker_thread.take() {
+ let _ = worker_thread.join();
+ }
+ }
+}
diff --git a/devices/src/virtio/vhost/user/fs.rs b/devices/src/virtio/vhost/user/vmm/fs.rs
index 2e212b97c..f7c935e60 100644
--- a/devices/src/virtio/vhost/user/fs.rs
+++ b/devices/src/virtio/vhost/user/vmm/fs.rs
@@ -8,17 +8,13 @@ use std::path::Path;
use std::thread;
use base::{error, Event, RawDescriptor};
-use cros_async::Executor;
use data_model::{DataInit, Le32};
use vm_memory::GuestMemory;
-use vmm_vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
-use vmm_vhost::vhost_user::{Error as VhostUserError, Master};
-use vmm_vhost::Error as VhostError;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+use vmm_vhost::Error as VhostUserError;
use crate::virtio::fs::{virtio_fs_config, FS_MAX_TAG_LEN, QUEUE_SIZE};
-use crate::virtio::vhost::user::handler::VhostUserHandler;
-use crate::virtio::vhost::user::worker::Worker;
-use crate::virtio::vhost::user::{Error, Result};
+use crate::virtio::vhost::user::vmm::{handler::VhostUserHandler, worker::Worker, Error, Result};
use crate::virtio::{copy_config, TYPE_FS};
use crate::virtio::{Interrupt, Queue, VirtioDevice};
@@ -51,16 +47,17 @@ impl Fs {
};
let socket = UnixStream::connect(&socket_path).map_err(Error::SocketConnect)?;
- let master = Master::from_stream(socket, default_queue_size as u64);
let allow_features = 1u64 << crate::virtio::VIRTIO_F_VERSION_1
+ | base_features
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
let allow_protocol_features =
VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIG;
- let mut handler = VhostUserHandler::new(
- master,
+ let mut handler = VhostUserHandler::new_from_stream(
+ socket,
+ default_queue_size as u64,
allow_features,
init_features,
allow_protocol_features,
@@ -109,9 +106,9 @@ impl VirtioDevice for Fs {
Ok(()) => {}
// copy local config when VhostUserProtocolFeatures::CONFIG is not supported by the
// device
- Err(Error::GetConfig(VhostError::VhostUserProtocol(
- VhostUserError::InvalidOperation,
- ))) => copy_config(data, 0, self.cfg.as_slice(), offset),
+ Err(Error::GetConfig(VhostUserError::InvalidOperation)) => {
+ copy_config(data, 0, self.cfg.as_slice(), offset)
+ }
Err(e) => error!("Failed to fetch device config: {}", e),
}
}
@@ -144,14 +141,13 @@ impl VirtioDevice for Fs {
let worker_result = thread::Builder::new()
.name("vhost_user_virtio_fs".to_string())
.spawn(move || {
- let ex = Executor::new().expect("failed to create an executor");
let mut worker = Worker {
queues,
mem,
kill_evt,
};
- if let Err(e) = worker.run(&ex, interrupt) {
+ if let Err(e) = worker.run(interrupt) {
error!("failed to start a worker: {}", e);
}
worker
@@ -160,7 +156,6 @@ impl VirtioDevice for Fs {
match worker_result {
Err(e) => {
error!("failed to spawn vhost-user virtio_fs worker: {}", e);
- return;
}
Ok(join_handle) => {
self.worker_thread = Some(join_handle);
diff --git a/devices/src/virtio/vhost/user/vmm/gpu.rs b/devices/src/virtio/vhost/user/vmm/gpu.rs
new file mode 100644
index 000000000..b90b92d52
--- /dev/null
+++ b/devices/src/virtio/vhost/user/vmm/gpu.rs
@@ -0,0 +1,259 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{cell::RefCell, path::Path, thread};
+
+use base::{error, Event, RawDescriptor, Tube};
+use vm_memory::GuestMemory;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+use crate::{
+ pci::{PciBarConfiguration, PciCapability},
+ virtio::{
+ gpu::QUEUE_SIZES,
+ vhost::user::vmm::{worker::Worker, Result, VhostUserHandler},
+ virtio_gpu_config, Interrupt, PciCapabilityType, Queue, VirtioDevice, VirtioPciShmCap,
+ GPU_BAR_NUM, GPU_BAR_OFFSET, GPU_BAR_SIZE, TYPE_GPU, VIRTIO_GPU_F_CONTEXT_INIT,
+ VIRTIO_GPU_F_CREATE_GUEST_HANDLE, VIRTIO_GPU_F_RESOURCE_BLOB, VIRTIO_GPU_F_RESOURCE_SYNC,
+ VIRTIO_GPU_F_RESOURCE_UUID, VIRTIO_GPU_F_VIRGL, VIRTIO_GPU_SHM_ID_HOST_VISIBLE,
+ },
+ PciAddress,
+};
+
+/// Current state of our Gpu.
+enum GpuState {
+ /// Created, address has not yet been assigned through `get_device_bars`.
+ Init {
+ /// VMM-side Tube to the GPU process from which we will send the PCI address, retrieve the
+ /// BAR configuration, and send the vhost-user control tube in `get_device_bars`.
+ host_gpu_tube: Tube,
+ /// Device-side control tube to be sent during `get_device_bars`.
+ device_control_tube: Tube,
+ },
+ /// Address has been set through `get_device_bars`.
+ Configured,
+}
+
+pub struct Gpu {
+ kill_evt: Option<Event>,
+ worker_thread: Option<thread::JoinHandle<Worker>>,
+ handler: RefCell<VhostUserHandler>,
+ state: GpuState,
+ queue_sizes: Vec<u16>,
+}
+
+impl Gpu {
+ /// Create a new GPU proxy instance for the VMM.
+ ///
+ /// `base_features` is the desired set of virtio features.
+ /// `socket_path` is the path to the socket of the GPU device.
+ /// `gpu_tubes` is a pair of (vmm side, device side) connected tubes that are used to perform
+ /// the initial configuration of the GPU device.
+ /// `device_control_tube` is the device-side tube to be passed to the GPU device so it can
+ /// perform `VmRequest`s.
+ pub fn new<P: AsRef<Path>>(
+ base_features: u64,
+ socket_path: P,
+ gpu_tubes: (Tube, Tube),
+ device_control_tube: Tube,
+ ) -> Result<Gpu> {
+ let default_queue_size = QUEUE_SIZES.len();
+
+ let allow_features = 1u64 << crate::virtio::VIRTIO_F_VERSION_1
+ | 1 << VIRTIO_GPU_F_VIRGL
+ | 1 << VIRTIO_GPU_F_RESOURCE_UUID
+ | 1 << VIRTIO_GPU_F_RESOURCE_BLOB
+ | 1 << VIRTIO_GPU_F_CONTEXT_INIT
+ | 1 << VIRTIO_GPU_F_RESOURCE_SYNC
+ | 1 << VIRTIO_GPU_F_CREATE_GUEST_HANDLE
+ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+
+ let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let allow_protocol_features =
+ VhostUserProtocolFeatures::CONFIG | VhostUserProtocolFeatures::SLAVE_REQ;
+
+ let mut handler = VhostUserHandler::new_from_path(
+ socket_path,
+ default_queue_size as u64,
+ allow_features,
+ init_features,
+ allow_protocol_features,
+ )?;
+ handler.set_device_request_channel(gpu_tubes.1)?;
+
+ Ok(Gpu {
+ kill_evt: None,
+ worker_thread: None,
+ handler: RefCell::new(handler),
+ state: GpuState::Init {
+ host_gpu_tube: gpu_tubes.0,
+ device_control_tube,
+ },
+ queue_sizes: QUEUE_SIZES[..].to_vec(),
+ })
+ }
+}
+
+impl VirtioDevice for Gpu {
+ fn keep_rds(&self) -> Vec<RawDescriptor> {
+ Vec::new()
+ }
+
+ fn device_type(&self) -> u32 {
+ TYPE_GPU
+ }
+
+ fn queue_max_sizes(&self) -> &[u16] {
+ &self.queue_sizes
+ }
+
+ fn features(&self) -> u64 {
+ self.handler.borrow().avail_features
+ }
+
+ fn ack_features(&mut self, features: u64) {
+ if let Err(e) = self.handler.borrow_mut().ack_features(features) {
+ error!("failed to enable features 0x{:x}: {}", features, e);
+ }
+ }
+
+ fn read_config(&self, offset: u64, data: &mut [u8]) {
+ if let Err(e) = self
+ .handler
+ .borrow_mut()
+ .read_config::<virtio_gpu_config>(offset, data)
+ {
+ error!("failed to read gpu config: {}", e);
+ }
+ }
+
+ fn write_config(&mut self, offset: u64, data: &[u8]) {
+ if let Err(e) = self
+ .handler
+ .borrow_mut()
+ .write_config::<virtio_gpu_config>(offset, data)
+ {
+ error!("failed to write gpu config: {}", e);
+ }
+ }
+
+ fn activate(
+ &mut self,
+ mem: GuestMemory,
+ interrupt: Interrupt,
+ queues: Vec<Queue>,
+ queue_evts: Vec<Event>,
+ ) {
+ if let Err(e) = self
+ .handler
+ .borrow_mut()
+ .activate(&mem, &interrupt, &queues, &queue_evts)
+ {
+ error!("failed to activate queues: {}", e);
+ return;
+ }
+
+ let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
+ Ok(v) => v,
+ Err(e) => {
+ error!("failed creating kill Event pair: {}", e);
+ return;
+ }
+ };
+ self.kill_evt = Some(self_kill_evt);
+
+ let worker_result = thread::Builder::new()
+ .name("vhost_user_gpu".to_string())
+ .spawn(move || {
+ let mut worker = Worker {
+ queues,
+ mem,
+ kill_evt,
+ };
+
+ if let Err(e) = worker.run(interrupt) {
+ error!("failed to start a worker: {}", e);
+ }
+ worker
+ });
+
+ match worker_result {
+ Err(e) => {
+ error!("failed to spawn vhost_user_gpu worker: {}", e);
+ }
+ Ok(join_handle) => {
+ self.worker_thread = Some(join_handle);
+ }
+ }
+ }
+
+ fn get_device_bars(&mut self, address: PciAddress) -> Vec<PciBarConfiguration> {
+ let (host_gpu_tube, device_control_tube) =
+ match std::mem::replace(&mut self.state, GpuState::Configured) {
+ GpuState::Init {
+ host_gpu_tube,
+ device_control_tube,
+ } => (host_gpu_tube, device_control_tube),
+ GpuState::Configured => {
+ panic!("get_device_bars shall not be called more than once!")
+ }
+ };
+
+ if let Err(e) = host_gpu_tube.send(&address) {
+ error!("failed to send `PciAddress` to gpu device: {}", e);
+ return Vec::new();
+ }
+
+ let res = match host_gpu_tube.recv() {
+ Ok(cfg) => cfg,
+ Err(e) => {
+ error!(
+ "failed to receive `PciBarConfiguration` from gpu device: {}",
+ e
+ );
+ return Vec::new();
+ }
+ };
+
+ if let Err(e) = host_gpu_tube.send(&device_control_tube) {
+ error!("failed to send device control tube to gpu device: {}", e);
+ return Vec::new();
+ }
+
+ res
+ }
+
+ fn get_device_caps(&self) -> Vec<Box<dyn PciCapability>> {
+ vec![Box::new(VirtioPciShmCap::new(
+ PciCapabilityType::SharedMemoryConfig,
+ GPU_BAR_NUM,
+ GPU_BAR_OFFSET,
+ GPU_BAR_SIZE,
+ VIRTIO_GPU_SHM_ID_HOST_VISIBLE,
+ ))]
+ }
+
+ fn reset(&mut self) -> bool {
+ if let Err(e) = self.handler.borrow_mut().reset(self.queue_sizes.len()) {
+ error!("Failed to reset gpu device: {}", e);
+ false
+ } else {
+ true
+ }
+ }
+}
+
+impl Drop for Gpu {
+ fn drop(&mut self) {
+ if let Some(kill_evt) = self.kill_evt.take() {
+ if let Some(worker_thread) = self.worker_thread.take() {
+ if let Err(e) = kill_evt.write(1) {
+ error!("failed to write to kill_evt: {}", e);
+ return;
+ }
+ let _ = worker_thread.join();
+ }
+ }
+ }
+}
diff --git a/devices/src/virtio/vhost/user/handler.rs b/devices/src/virtio/vhost/user/vmm/handler.rs
index f3797ae8e..402186823 100644
--- a/devices/src/virtio/vhost/user/handler.rs
+++ b/devices/src/virtio/vhost/user/vmm/handler.rs
@@ -3,36 +3,76 @@
// found in the LICENSE file.
use std::io::Write;
+use std::os::unix::net::UnixStream;
+use std::path::Path;
-use base::{AsRawDescriptor, Event};
+use base::{AsRawDescriptor, Event, Tube};
use vm_memory::GuestMemory;
-use vmm_vhost::vhost_user::message::{
- VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserVirtioFeatures,
- VHOST_USER_CONFIG_OFFSET,
+use vmm_vhost::message::{
+ MasterReq, VhostUserConfigFlags, VhostUserProtocolFeatures, VhostUserVirtioFeatures,
+};
+use vmm_vhost::{
+ connection::socket::Endpoint as SocketEndpoint, Master, VhostBackend, VhostUserMaster,
+ VhostUserMemoryRegionInfo, VringConfigData,
};
-use vmm_vhost::vhost_user::{Master, VhostUserMaster};
-use vmm_vhost::{VhostBackend, VhostUserMemoryRegionInfo, VringConfigData};
-use crate::virtio::vhost::user::{Error, Result};
+use crate::virtio::vhost::user::vmm::{Error, Result};
use crate::virtio::{Interrupt, Queue};
-fn set_features(vu: &mut Master, avail_features: u64, ack_features: u64) -> Result<u64> {
+type SocketMaster = Master<SocketEndpoint<MasterReq>>;
+
+fn set_features(vu: &mut SocketMaster, avail_features: u64, ack_features: u64) -> Result<u64> {
let features = avail_features & ack_features;
vu.set_features(features).map_err(Error::SetFeatures)?;
Ok(features)
}
pub struct VhostUserHandler {
- vu: Master,
+ vu: SocketMaster,
pub avail_features: u64,
acked_features: u64,
protocol_features: VhostUserProtocolFeatures,
}
impl VhostUserHandler {
+ /// Creates a `VhostUserHandler` instance attached to the provided UDS path
+ /// with features and protocol features initialized.
+ pub fn new_from_path<P: AsRef<Path>>(
+ path: P,
+ max_queue_num: u64,
+ allow_features: u64,
+ init_features: u64,
+ allow_protocol_features: VhostUserProtocolFeatures,
+ ) -> Result<Self> {
+ Self::new(
+ SocketMaster::connect(path, max_queue_num)
+ .map_err(Error::SocketConnectOnMasterCreate)?,
+ allow_features,
+ init_features,
+ allow_protocol_features,
+ )
+ }
+
+ /// Creates a `VhostUserHandler` instance attached to the provided
+ /// UnixStream with features and protocol features initialized.
+ pub fn new_from_stream(
+ sock: UnixStream,
+ max_queue_num: u64,
+ allow_features: u64,
+ init_features: u64,
+ allow_protocol_features: VhostUserProtocolFeatures,
+ ) -> Result<Self> {
+ Self::new(
+ SocketMaster::from_stream(sock, max_queue_num),
+ allow_features,
+ init_features,
+ allow_protocol_features,
+ )
+ }
+
/// Creates a `VhostUserHandler` instance with features and protocol features initialized.
- pub fn new(
- mut vu: Master,
+ fn new(
+ mut vu: SocketMaster,
allow_features: u64,
init_features: u64,
allow_protocol_features: VhostUserProtocolFeatures,
@@ -100,12 +140,7 @@ impl VhostUserHandler {
let buf = vec![0u8; config_len as usize];
let (_, config) = self
.vu
- .get_config(
- VHOST_USER_CONFIG_OFFSET,
- config_len as u32,
- VhostUserConfigFlags::WRITABLE,
- &buf,
- )
+ .get_config(0, config_len as u32, VhostUserConfigFlags::WRITABLE, &buf)
.map_err(Error::GetConfig)?;
data.write_all(
@@ -114,7 +149,33 @@ impl VhostUserHandler {
.map_err(Error::CopyConfig)
}
- fn set_mem_table(&mut self, mem: &GuestMemory) -> Result<()> {
+ /// Writes `data` into the device configuration space at `offset`.
+ pub fn write_config<T>(&mut self, offset: u64, data: &[u8]) -> Result<()> {
+ let config_len = std::mem::size_of::<T>() as u64;
+ let data_len = data.len() as u64;
+ offset
+ .checked_add(data_len)
+ .and_then(|l| if l <= config_len { Some(()) } else { None })
+ .ok_or(Error::InvalidConfigOffset {
+ data_len,
+ offset,
+ config_len,
+ })?;
+
+ self.vu
+ .set_config(offset as u32, VhostUserConfigFlags::empty(), data)
+ .map_err(Error::SetConfig)
+ }
+
+ /// Sets the channel for device-specific messages.
+ pub fn set_device_request_channel(&mut self, channel: Tube) -> Result<()> {
+ self.vu
+ .set_slave_request_fd(&channel)
+ .map_err(Error::SetDeviceRequestChannel)
+ }
+
+ /// Sets the memory map regions so it can translate the vring addresses.
+ pub fn set_mem_table(&mut self, mem: &GuestMemory) -> Result<()> {
let mut regions: Vec<VhostUserMemoryRegionInfo> = Vec::new();
mem.with_regions::<_, ()>(
|_idx, guest_phys_addr, memory_size, userspace_addr, mmap, mmap_offset| {
@@ -138,13 +199,14 @@ impl VhostUserHandler {
Ok(())
}
- fn activate_vring(
+ /// Activates a vring for the given `queue`.
+ pub fn activate_vring(
&mut self,
mem: &GuestMemory,
queue_index: usize,
queue: &Queue,
queue_evt: &Event,
- interrupt: &Interrupt,
+ irqfd: &Event,
) -> Result<()> {
self.vu
.set_vring_num(queue_index, queue.actual_size())
@@ -173,20 +235,11 @@ impl VhostUserHandler {
.set_vring_base(queue_index, 0)
.map_err(Error::SetVringBase)?;
- let msix_config_opt = interrupt
- .get_msix_config()
- .as_ref()
- .ok_or(Error::MsixConfigUnavailable)?;
- let msix_config = msix_config_opt.lock();
- let irqfd = msix_config
- .get_irqfd(queue.vector as usize)
- .ok_or(Error::MsixIrqfdUnavailable)?;
self.vu
- .set_vring_call(queue_index, &irqfd.0)
+ .set_vring_call(queue_index, irqfd)
.map_err(Error::SetVringCall)?;
-
self.vu
- .set_vring_kick(queue_index, &queue_evt.0)
+ .set_vring_kick(queue_index, queue_evt)
.map_err(Error::SetVringKick)?;
self.vu
.set_vring_enable(queue_index, true)
@@ -203,11 +256,20 @@ impl VhostUserHandler {
queues: &[Queue],
queue_evts: &[Event],
) -> Result<()> {
- self.set_mem_table(&mem)?;
+ self.set_mem_table(mem)?;
+
+ let msix_config_opt = interrupt
+ .get_msix_config()
+ .as_ref()
+ .ok_or(Error::MsixConfigUnavailable)?;
+ let msix_config = msix_config_opt.lock();
for (queue_index, queue) in queues.iter().enumerate() {
let queue_evt = &queue_evts[queue_index];
- self.activate_vring(&mem, queue_index, queue, queue_evt, &interrupt)?;
+ let irqfd = msix_config
+ .get_irqfd(queue.vector as usize)
+ .unwrap_or_else(|| interrupt.get_interrupt_evt());
+ self.activate_vring(mem, queue_index, queue, queue_evt, irqfd)?;
}
Ok(())
diff --git a/devices/src/virtio/vhost/user/vmm/mac80211_hwsim.rs b/devices/src/virtio/vhost/user/vmm/mac80211_hwsim.rs
new file mode 100644
index 000000000..30ec5f212
--- /dev/null
+++ b/devices/src/virtio/vhost/user/vmm/mac80211_hwsim.rs
@@ -0,0 +1,166 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::RefCell;
+use std::os::unix::net::UnixStream;
+use std::path::Path;
+use std::thread;
+use std::u32;
+
+use base::{error, Event, RawDescriptor};
+use remain::sorted;
+use thiserror::Error as ThisError;
+use vm_memory::GuestMemory;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+use crate::virtio::vhost::user::vmm::{handler::VhostUserHandler, worker::Worker, Error};
+use crate::virtio::{Interrupt, Queue, VirtioDevice, TYPE_MAC80211_HWSIM, VIRTIO_F_VERSION_1};
+
+use std::result::Result;
+
+#[sorted]
+#[derive(ThisError, Debug)]
+enum Mac80211HwsimError {
+ #[error("failed to activate queues: {0}")]
+ ActivateQueue(Error),
+ #[error("failed to kill event pair: {0}")]
+ CreateKillEventPair(base::Error),
+ #[error("failed to spawn mac80211_hwsim worker: {0}")]
+ SpawnWorker(std::io::Error),
+}
+
+const QUEUE_SIZE: u16 = 256;
+const QUEUE_COUNT: usize = 2;
+
+pub struct Mac80211Hwsim {
+ kill_evt: Option<Event>,
+ worker_thread: Option<thread::JoinHandle<Worker>>,
+ handler: RefCell<VhostUserHandler>,
+ queue_sizes: Vec<u16>,
+}
+
+impl Mac80211Hwsim {
+ pub fn new<P: AsRef<Path>>(base_features: u64, socket_path: P) -> Result<Mac80211Hwsim, Error> {
+ let socket = UnixStream::connect(&socket_path).map_err(Error::SocketConnect)?;
+
+ let allow_features = 1 << VIRTIO_F_VERSION_1
+ | base_features
+ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let allow_protocol_features = VhostUserProtocolFeatures::empty();
+
+ let mut handler = VhostUserHandler::new_from_stream(
+ socket,
+ QUEUE_COUNT as u64, /* # of queues */
+ allow_features,
+ init_features,
+ allow_protocol_features,
+ )?;
+ let queue_sizes = handler.queue_sizes(QUEUE_SIZE, QUEUE_COUNT)?;
+
+ Ok(Mac80211Hwsim {
+ kill_evt: None,
+ worker_thread: None,
+ handler: RefCell::new(handler),
+ queue_sizes,
+ })
+ }
+
+ fn activate_internal(
+ &mut self,
+ mem: GuestMemory,
+ interrupt: Interrupt,
+ queues: Vec<Queue>,
+ queue_evts: Vec<Event>,
+ ) -> Result<(), Mac80211HwsimError> {
+ self.handler
+ .borrow_mut()
+ .activate(&mem, &interrupt, &queues, &queue_evts)
+ .map_err(Mac80211HwsimError::ActivateQueue)?;
+
+ let (self_kill_evt, kill_evt) = Event::new()
+ .and_then(|e| Ok((e.try_clone()?, e)))
+ .map_err(Mac80211HwsimError::CreateKillEventPair)?;
+
+ self.kill_evt = Some(self_kill_evt);
+
+ let join_handle = thread::Builder::new()
+ .name("vhost_user_mac80211_hwsim".to_string())
+ .spawn(move || {
+ let mut worker = Worker {
+ queues,
+ mem,
+ kill_evt,
+ };
+ if let Err(e) = worker.run(interrupt) {
+ error!("failed to start a worker: {}", e);
+ }
+ worker
+ })
+ .map_err(Mac80211HwsimError::SpawnWorker)?;
+
+ self.worker_thread = Some(join_handle);
+
+ Ok(())
+ }
+}
+
+impl Drop for Mac80211Hwsim {
+ fn drop(&mut self) {
+ if let Some(kill_evt) = self.kill_evt.take() {
+ if let Some(worker_thread) = self.worker_thread.take() {
+ if let Err(e) = kill_evt.write(1) {
+ error!("failed to write to kill_evt: {}", e);
+ return;
+ }
+ let _ = worker_thread.join();
+ }
+ }
+ }
+}
+
+impl VirtioDevice for Mac80211Hwsim {
+ fn keep_rds(&self) -> Vec<RawDescriptor> {
+ Vec::new()
+ }
+
+ fn features(&self) -> u64 {
+ self.handler.borrow().avail_features
+ }
+
+ fn ack_features(&mut self, features: u64) {
+ if let Err(e) = self.handler.borrow_mut().ack_features(features) {
+ error!("failed to enable features 0x{:x}: {}", features, e);
+ }
+ }
+
+ fn device_type(&self) -> u32 {
+ TYPE_MAC80211_HWSIM
+ }
+
+ fn queue_max_sizes(&self) -> &[u16] {
+ self.queue_sizes.as_slice()
+ }
+
+ fn activate(
+ &mut self,
+ mem: GuestMemory,
+ interrupt: Interrupt,
+ queues: Vec<Queue>,
+ queue_evts: Vec<Event>,
+ ) {
+ if let Err(e) = self.activate_internal(mem, interrupt, queues, queue_evts) {
+ error!("Failed to activate mac80211_hwsim: {}", e);
+ }
+ }
+
+ fn reset(&mut self) -> bool {
+ if let Err(e) = self.handler.borrow_mut().reset(self.queue_sizes.len()) {
+ error!("Failed to reset mac80211_hwsim device: {}", e);
+ false
+ } else {
+ true
+ }
+ }
+}
diff --git a/devices/src/virtio/vhost/user/vmm/mod.rs b/devices/src/virtio/vhost/user/vmm/mod.rs
new file mode 100644
index 000000000..ac77e04ce
--- /dev/null
+++ b/devices/src/virtio/vhost/user/vmm/mod.rs
@@ -0,0 +1,127 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod block;
+mod console;
+mod fs;
+#[cfg(feature = "gpu")]
+mod gpu;
+mod handler;
+mod mac80211_hwsim;
+mod net;
+#[cfg(feature = "audio")]
+mod snd;
+mod vsock;
+mod wl;
+mod worker;
+
+pub use self::block::*;
+pub use self::console::*;
+pub use self::fs::*;
+#[cfg(feature = "gpu")]
+pub use self::gpu::*;
+pub use self::handler::VhostUserHandler;
+pub use self::mac80211_hwsim::*;
+pub use self::net::*;
+#[cfg(feature = "audio")]
+pub use self::snd::*;
+pub use self::vsock::*;
+pub use self::wl::*;
+
+use remain::sorted;
+use thiserror::Error as ThisError;
+use vm_memory::GuestMemoryError;
+use vmm_vhost::Error as VhostError;
+
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum Error {
+ /// Failed to copy config to a buffer.
+ #[error("failed to copy config to a buffer: {0}")]
+ CopyConfig(std::io::Error),
+ /// Failed to create `base::Event`.
+ #[error("failed to create Event: {0}")]
+ CreateEvent(base::Error),
+ /// Failed to get config.
+ #[error("failed to get config: {0}")]
+ GetConfig(VhostError),
+ /// Failed to get features.
+ #[error("failed to get features: {0}")]
+ GetFeatures(VhostError),
+ /// Failed to get host address.
+ #[error("failed to get host address: {0}")]
+ GetHostAddress(GuestMemoryError),
+ /// Failed to get protocol features.
+ #[error("failed to get protocol features: {0}")]
+ GetProtocolFeatures(VhostError),
+ /// Failed to get number of queues.
+ #[error("failed to get number of queues: {0}")]
+ GetQueueNum(VhostError),
+ /// Failed to get vring base offset.
+ #[error("failed to get vring base offset: {0}")]
+ GetVringBase(VhostError),
+ /// Invalid config offset is given.
+ #[error("invalid config offset is given: {data_len} + {offset} > {config_len}")]
+ InvalidConfigOffset {
+ data_len: u64,
+ offset: u64,
+ config_len: u64,
+ },
+ /// MSI-X config is unavailable.
+ #[error("MSI-X config is unavailable")]
+ MsixConfigUnavailable,
+ /// MSI-X irqfd is unavailable.
+ #[error("MSI-X irqfd is unavailable")]
+ MsixIrqfdUnavailable,
+ /// Failed to reset owner.
+ #[error("failed to reset owner: {0}")]
+ ResetOwner(VhostError),
+ /// Failed to set config.
+ #[error("failed to set config: {0}")]
+ SetConfig(VhostError),
+ /// Failed to set device request channel.
+ #[error("failed to set device request channel: {0}")]
+ SetDeviceRequestChannel(VhostError),
+ /// Failed to set features.
+ #[error("failed to set features: {0}")]
+ SetFeatures(VhostError),
+ /// Failed to set memory map regions.
+ #[error("failed to set memory map regions: {0}")]
+ SetMemTable(VhostError),
+ /// Failed to set owner.
+ #[error("failed to set owner: {0}")]
+ SetOwner(VhostError),
+ /// Failed to set protocol features.
+ #[error("failed to set protocol features: {0}")]
+ SetProtocolFeatures(VhostError),
+ /// Failed to set vring address.
+ #[error("failed to set vring address: {0}")]
+ SetVringAddr(VhostError),
+ /// Failed to set vring base offset.
+ #[error("failed to set vring base offset: {0}")]
+ SetVringBase(VhostError),
+ /// Failed to set eventfd to signal used vring buffers.
+ #[error("failed to set eventfd to signal used vring buffers: {0}")]
+ SetVringCall(VhostError),
+ /// Failed to enable or disable vring.
+ #[error("failed to enable or disable vring: {0}")]
+ SetVringEnable(VhostError),
+ /// Failed to set eventfd for adding buffers to vring.
+ #[error("failed to set eventfd for adding buffers to vring: {0}")]
+ SetVringKick(VhostError),
+ /// Failed to set the size of the queue.
+ #[error("failed to set the size of the queue: {0}")]
+ SetVringNum(VhostError),
+ /// Failed to connect socket.
+ #[error("failed to connect socket: {0}")]
+ SocketConnect(std::io::Error),
+ /// Failed to create Master from a UDS path.
+ #[error("failed to connect to device socket while creating instance: {0}")]
+ SocketConnectOnMasterCreate(VhostError),
+ /// The tag for the Fs device was too long to fit in the config space.
+ #[error("tag is too long: {len} > {max}")]
+ TagTooLong { len: usize, max: usize },
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
diff --git a/devices/src/virtio/vhost/user/net.rs b/devices/src/virtio/vhost/user/vmm/net.rs
index fe3deccf4..2b6da0c73 100644
--- a/devices/src/virtio/vhost/user/net.rs
+++ b/devices/src/virtio/vhost/user/vmm/net.rs
@@ -9,16 +9,12 @@ use std::thread;
use std::u32;
use base::{error, Event, RawDescriptor};
-use cros_async::Executor;
use virtio_sys::virtio_net;
use virtio_sys::virtio_ring::VIRTIO_RING_F_EVENT_IDX;
use vm_memory::GuestMemory;
-use vmm_vhost::vhost_user::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
-use vmm_vhost::vhost_user::Master;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
-use crate::virtio::vhost::user::handler::VhostUserHandler;
-use crate::virtio::vhost::user::worker::Worker;
-use crate::virtio::vhost::user::Error;
+use crate::virtio::vhost::user::vmm::{handler::VhostUserHandler, worker::Worker, Error};
use crate::virtio::{Interrupt, Queue, VirtioDevice, VirtioNetConfig, TYPE_NET};
type Result<T> = std::result::Result<T, Error>;
@@ -35,11 +31,11 @@ pub struct Net {
impl Net {
pub fn new<P: AsRef<Path>>(base_features: u64, socket_path: P) -> Result<Net> {
let socket = UnixStream::connect(&socket_path).map_err(Error::SocketConnect)?;
- let vhost_user_net = Master::from_stream(socket, 16 /* # of queues */);
- // TODO(b/182430355): Support VIRTIO_NET_F_CTRL_VQ and VIRTIO_NET_F_CTRL_GUEST_OFFLOADS.
let allow_features = 1 << crate::virtio::VIRTIO_F_VERSION_1
| 1 << virtio_net::VIRTIO_NET_F_CSUM
+ | 1 << virtio_net::VIRTIO_NET_F_CTRL_VQ
+ | 1 << virtio_net::VIRTIO_NET_F_CTRL_GUEST_OFFLOADS
| 1 << virtio_net::VIRTIO_NET_F_GUEST_CSUM
| 1 << virtio_net::VIRTIO_NET_F_GUEST_TSO4
| 1 << virtio_net::VIRTIO_NET_F_GUEST_UFO
@@ -47,18 +43,20 @@ impl Net {
| 1 << virtio_net::VIRTIO_NET_F_HOST_UFO
| 1 << virtio_net::VIRTIO_NET_F_MQ
| 1 << VIRTIO_RING_F_EVENT_IDX
+ | base_features
| VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
let allow_protocol_features =
VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIG;
- let mut handler = VhostUserHandler::new(
- vhost_user_net,
+ let mut handler = VhostUserHandler::new_from_stream(
+ socket,
+ 3, /* # of queues */
allow_features,
init_features,
allow_protocol_features,
)?;
- let queue_sizes = handler.queue_sizes(QUEUE_SIZE, 2 /* 1 rx + 1 tx */)?;
+ let queue_sizes = handler.queue_sizes(QUEUE_SIZE, 3 /* rx, tx, ctrl */)?;
Ok(Net {
kill_evt: None,
@@ -122,14 +120,6 @@ impl VirtioDevice for Net {
queues: Vec<Queue>,
queue_evts: Vec<Event>,
) {
- // TODO(b/182430355): Remove this check once ctrlq is supported.
- if queues.len() % 2 != 0 {
- error!(
- "The number of queues must be an even number but {}",
- queues.len()
- );
- }
-
if let Err(e) = self
.handler
.borrow_mut()
@@ -151,13 +141,12 @@ impl VirtioDevice for Net {
let worker_result = thread::Builder::new()
.name("vhost_user_virtio_net".to_string())
.spawn(move || {
- let ex = Executor::new().expect("failed to create an executor");
let mut worker = Worker {
queues,
mem,
kill_evt,
};
- if let Err(e) = worker.run(&ex, interrupt) {
+ if let Err(e) = worker.run(interrupt) {
error!("failed to start a worker: {}", e);
}
worker
@@ -166,7 +155,6 @@ impl VirtioDevice for Net {
match worker_result {
Err(e) => {
error!("failed to spawn virtio_net worker: {}", e);
- return;
}
Ok(join_handle) => {
self.worker_thread = Some(join_handle);
diff --git a/devices/src/virtio/vhost/user/vmm/snd.rs b/devices/src/virtio/vhost/user/vmm/snd.rs
new file mode 100644
index 000000000..c492a28ed
--- /dev/null
+++ b/devices/src/virtio/vhost/user/vmm/snd.rs
@@ -0,0 +1,153 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::RefCell;
+use std::os::unix::net::UnixStream;
+use std::path::Path;
+use std::thread;
+
+use base::{error, Event, RawDescriptor};
+use vm_memory::GuestMemory;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+use crate::virtio::snd::layout::virtio_snd_config;
+use crate::virtio::vhost::user::vmm::{handler::VhostUserHandler, worker::Worker, Error, Result};
+use crate::virtio::TYPE_SOUND;
+use crate::virtio::{Interrupt, Queue, VirtioDevice};
+
+// A vhost-user snd device.
+pub struct Snd {
+ kill_evt: Option<Event>,
+ worker_thread: Option<thread::JoinHandle<Worker>>,
+ handler: RefCell<VhostUserHandler>,
+ queue_sizes: Vec<u16>,
+}
+
+const QUEUE_SIZE: u16 = 1024;
+
+// control, event, tx, and rx queues
+const NUM_QUEUES: usize = 4;
+
+impl Snd {
+ // Create a vhost-user snd device.
+ pub fn new<P: AsRef<Path>>(base_features: u64, socket_path: P) -> Result<Snd> {
+ let socket = UnixStream::connect(&socket_path).map_err(Error::SocketConnect)?;
+
+ let allow_features = 1u64 << crate::virtio::VIRTIO_F_VERSION_1
+ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let allow_protocol_features =
+ VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIG;
+
+ let mut handler = VhostUserHandler::new_from_stream(
+ socket,
+ NUM_QUEUES as u64,
+ allow_features,
+ init_features,
+ allow_protocol_features,
+ )?;
+ let queue_sizes = handler.queue_sizes(QUEUE_SIZE, NUM_QUEUES)?;
+
+ Ok(Snd {
+ kill_evt: None,
+ worker_thread: None,
+ handler: RefCell::new(handler),
+ queue_sizes,
+ })
+ }
+}
+
+impl VirtioDevice for Snd {
+ fn keep_rds(&self) -> Vec<RawDescriptor> {
+ Vec::new()
+ }
+
+ fn device_type(&self) -> u32 {
+ TYPE_SOUND
+ }
+
+ fn queue_max_sizes(&self) -> &[u16] {
+ &self.queue_sizes
+ }
+
+ fn features(&self) -> u64 {
+ self.handler.borrow().avail_features
+ }
+
+ fn ack_features(&mut self, features: u64) {
+ if let Err(e) = self.handler.borrow_mut().ack_features(features) {
+ error!("failed to enable features 0x{:x}: {}", features, e);
+ }
+ }
+
+ fn read_config(&self, offset: u64, data: &mut [u8]) {
+ if let Err(e) = self
+ .handler
+ .borrow_mut()
+ .read_config::<virtio_snd_config>(offset, data)
+ {
+ error!("failed to read config: {}", e);
+ }
+ }
+
+ fn activate(
+ &mut self,
+ mem: GuestMemory,
+ interrupt: Interrupt,
+ queues: Vec<Queue>,
+ queue_evts: Vec<Event>,
+ ) {
+ if let Err(e) = self
+ .handler
+ .borrow_mut()
+ .activate(&mem, &interrupt, &queues, &queue_evts)
+ {
+ error!("failed to activate queues: {}", e);
+ return;
+ }
+
+ let (self_kill_evt, kill_evt) =
+ match Event::new().and_then(|evt| Ok((evt.try_clone()?, evt))) {
+ Ok(v) => v,
+ Err(e) => {
+ error!("failed creating kill Event pair: {}", e);
+ return;
+ }
+ };
+ self.kill_evt = Some(self_kill_evt);
+
+ let worker_result = thread::Builder::new()
+ .name("vhost_user_virtio_snd".to_string())
+ .spawn(move || {
+ let mut worker = Worker {
+ queues,
+ mem,
+ kill_evt,
+ };
+
+ if let Err(e) = worker.run(interrupt) {
+ error!("failed to start a worker: {}", e);
+ }
+ worker
+ });
+
+ match worker_result {
+ Err(e) => {
+ error!("failed to spawn vhost-user virtio_snd worker: {}", e);
+ }
+ Ok(join_handle) => {
+ self.worker_thread = Some(join_handle);
+ }
+ }
+ }
+
+ fn reset(&mut self) -> bool {
+ if let Err(e) = self.handler.borrow_mut().reset(self.queue_sizes.len()) {
+ error!("Failed to reset snd device: {}", e);
+ false
+ } else {
+ true
+ }
+ }
+}
diff --git a/devices/src/virtio/vhost/user/vmm/vsock.rs b/devices/src/virtio/vhost/user/vmm/vsock.rs
new file mode 100644
index 000000000..f8afe2626
--- /dev/null
+++ b/devices/src/virtio/vhost/user/vmm/vsock.rs
@@ -0,0 +1,160 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{cell::RefCell, os::unix::net::UnixStream, path::Path, thread};
+
+use base::{error, Event, RawDescriptor};
+use data_model::Le64;
+use vm_memory::GuestMemory;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+use crate::virtio::{
+ vhost::{
+ user::vmm::{handler::VhostUserHandler, worker::Worker, Error, Result},
+ vsock,
+ },
+ Interrupt, Queue, VirtioDevice, TYPE_VSOCK, VIRTIO_F_VERSION_1,
+};
+
+pub struct Vsock {
+ kill_evt: Option<Event>,
+ worker_thread: Option<thread::JoinHandle<Worker>>,
+ handler: RefCell<VhostUserHandler>,
+ queue_sizes: Vec<u16>,
+}
+
+impl Vsock {
+ pub fn new<P: AsRef<Path>>(base_features: u64, socket_path: P) -> Result<Vsock> {
+ let socket = UnixStream::connect(socket_path).map_err(Error::SocketConnect)?;
+
+ let init_features = VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let allow_features = init_features
+ | base_features
+ | 1 << VIRTIO_F_VERSION_1
+ | 1 << virtio_sys::vhost::VIRTIO_RING_F_INDIRECT_DESC
+ | 1 << virtio_sys::vhost::VIRTIO_RING_F_EVENT_IDX
+ | 1 << virtio_sys::vhost::VIRTIO_F_NOTIFY_ON_EMPTY
+ | 1 << virtio_sys::vhost::VHOST_F_LOG_ALL
+ | 1 << virtio_sys::vhost::VIRTIO_F_ANY_LAYOUT;
+ let allow_protocol_features =
+ VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIG;
+
+ let mut handler = VhostUserHandler::new_from_stream(
+ socket,
+ vsock::QUEUE_SIZES.len() as u64,
+ allow_features,
+ init_features,
+ allow_protocol_features,
+ )?;
+ let queue_sizes = handler.queue_sizes(vsock::QUEUE_SIZE, vsock::QUEUE_SIZES.len())?;
+
+ Ok(Vsock {
+ kill_evt: None,
+ worker_thread: None,
+ handler: RefCell::new(handler),
+ queue_sizes,
+ })
+ }
+}
+
+impl Drop for Vsock {
+ 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);
+ }
+
+ if let Some(worker_thread) = self.worker_thread.take() {
+ let _ = worker_thread.join();
+ }
+ }
+}
+
+impl VirtioDevice for Vsock {
+ fn keep_rds(&self) -> Vec<RawDescriptor> {
+ Vec::new()
+ }
+
+ fn features(&self) -> u64 {
+ self.handler.borrow().avail_features
+ }
+
+ fn ack_features(&mut self, features: u64) {
+ if let Err(e) = self.handler.borrow_mut().ack_features(features) {
+ error!("failed to enable features 0x{:x}: {}", features, e);
+ }
+ }
+
+ fn device_type(&self) -> u32 {
+ TYPE_VSOCK
+ }
+
+ fn queue_max_sizes(&self) -> &[u16] {
+ self.queue_sizes.as_slice()
+ }
+
+ fn read_config(&self, offset: u64, data: &mut [u8]) {
+ if let Err(e) = self.handler.borrow_mut().read_config::<Le64>(offset, data) {
+ error!("failed to read config: {}", e);
+ }
+ }
+
+ fn activate(
+ &mut self,
+ mem: GuestMemory,
+ interrupt: Interrupt,
+ queues: Vec<Queue>,
+ queue_evts: Vec<Event>,
+ ) {
+ if let Err(e) = self
+ .handler
+ .borrow_mut()
+ .activate(&mem, &interrupt, &queues, &queue_evts)
+ {
+ error!("failed to activate queues: {}", e);
+ return;
+ }
+
+ let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
+ Ok(v) => v,
+ Err(e) => {
+ error!("failed creating kill Event pair: {}", e);
+ return;
+ }
+ };
+ self.kill_evt = Some(self_kill_evt);
+
+ let worker_result = thread::Builder::new()
+ .name("vhost_user_vsock".to_string())
+ .spawn(move || {
+ let mut worker = Worker {
+ queues,
+ mem,
+ kill_evt,
+ };
+ if let Err(e) = worker.run(interrupt) {
+ error!("failed to start a worker: {}", e);
+ }
+ worker
+ });
+
+ match worker_result {
+ Err(e) => {
+ error!("failed to spawn virtio_vsock worker: {}", e);
+ }
+ Ok(join_handle) => {
+ self.worker_thread = Some(join_handle);
+ }
+ }
+ }
+
+ fn reset(&mut self) -> bool {
+ if let Err(e) = self.handler.borrow_mut().reset(self.queue_sizes.len()) {
+ error!("Failed to reset vsock device: {}", e);
+ false
+ } else {
+ true
+ }
+ }
+}
diff --git a/devices/src/virtio/vhost/user/vmm/wl.rs b/devices/src/virtio/vhost/user/vmm/wl.rs
new file mode 100644
index 000000000..689392b83
--- /dev/null
+++ b/devices/src/virtio/vhost/user/vmm/wl.rs
@@ -0,0 +1,154 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::RefCell;
+use std::path::Path;
+use std::thread;
+
+use base::{error, Event, RawDescriptor};
+use vm_memory::GuestMemory;
+use vmm_vhost::message::{VhostUserProtocolFeatures, VhostUserVirtioFeatures};
+
+use crate::virtio::vhost::user::vmm::{worker::Worker, Result, VhostUserHandler};
+use crate::virtio::wl::{
+ QUEUE_SIZE, QUEUE_SIZES, VIRTIO_WL_F_SEND_FENCES, VIRTIO_WL_F_TRANS_FLAGS,
+};
+use crate::virtio::{Interrupt, Queue, VirtioDevice, TYPE_WL};
+
+pub struct Wl {
+ kill_evt: Option<Event>,
+ worker_thread: Option<thread::JoinHandle<Worker>>,
+ handler: RefCell<VhostUserHandler>,
+ queue_sizes: Vec<u16>,
+}
+
+impl Wl {
+ pub fn new<P: AsRef<Path>>(base_features: u64, socket_path: P) -> Result<Wl> {
+ let default_queue_size = QUEUE_SIZES.len();
+
+ let allow_features = 1u64 << crate::virtio::VIRTIO_F_VERSION_1
+ | 1 << VIRTIO_WL_F_TRANS_FLAGS
+ | 1 << VIRTIO_WL_F_SEND_FENCES
+ | base_features
+ | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let init_features = base_features | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let allow_protocol_features =
+ VhostUserProtocolFeatures::MQ | VhostUserProtocolFeatures::CONFIG;
+
+ let mut handler = VhostUserHandler::new_from_path(
+ socket_path,
+ default_queue_size as u64,
+ allow_features,
+ init_features,
+ allow_protocol_features,
+ )?;
+ let queue_sizes = handler.queue_sizes(QUEUE_SIZE, default_queue_size)?;
+
+ Ok(Wl {
+ kill_evt: None,
+ worker_thread: None,
+ handler: RefCell::new(handler),
+ queue_sizes,
+ })
+ }
+}
+
+impl VirtioDevice for Wl {
+ fn keep_rds(&self) -> Vec<RawDescriptor> {
+ Vec::new()
+ }
+
+ fn device_type(&self) -> u32 {
+ TYPE_WL
+ }
+
+ fn queue_max_sizes(&self) -> &[u16] {
+ &self.queue_sizes
+ }
+
+ fn features(&self) -> u64 {
+ self.handler.borrow().avail_features
+ }
+
+ fn ack_features(&mut self, features: u64) {
+ if let Err(e) = self.handler.borrow_mut().ack_features(features) {
+ error!("failed to enable features 0x{:x}: {}", features, e);
+ }
+ }
+
+ fn read_config(&self, _offset: u64, _data: &mut [u8]) {}
+
+ fn activate(
+ &mut self,
+ mem: GuestMemory,
+ interrupt: Interrupt,
+ queues: Vec<Queue>,
+ queue_evts: Vec<Event>,
+ ) {
+ if let Err(e) = self
+ .handler
+ .borrow_mut()
+ .activate(&mem, &interrupt, &queues, &queue_evts)
+ {
+ error!("failed to activate queues: {}", e);
+ return;
+ }
+
+ let (self_kill_evt, kill_evt) = match Event::new().and_then(|e| Ok((e.try_clone()?, e))) {
+ Ok(v) => v,
+ Err(e) => {
+ error!("failed creating kill Event pair: {}", e);
+ return;
+ }
+ };
+ self.kill_evt = Some(self_kill_evt);
+
+ let worker_result = thread::Builder::new()
+ .name("vhost_user_wl".to_string())
+ .spawn(move || {
+ let mut worker = Worker {
+ queues,
+ mem,
+ kill_evt,
+ };
+
+ if let Err(e) = worker.run(interrupt) {
+ error!("failed to start a worker: {}", e);
+ }
+ worker
+ });
+
+ match worker_result {
+ Err(e) => {
+ error!("failed to spawn vhost_user_wl worker: {}", e);
+ }
+ Ok(join_handle) => {
+ self.worker_thread = Some(join_handle);
+ }
+ }
+ }
+
+ fn reset(&mut self) -> bool {
+ if let Err(e) = self.handler.borrow_mut().reset(self.queue_sizes.len()) {
+ error!("Failed to reset wl device: {}", e);
+ false
+ } else {
+ true
+ }
+ }
+}
+
+impl Drop for Wl {
+ fn drop(&mut self) {
+ if let Some(kill_evt) = self.kill_evt.take() {
+ if let Some(worker_thread) = self.worker_thread.take() {
+ if let Err(e) = kill_evt.write(1) {
+ error!("failed to write to kill_evt: {}", e);
+ return;
+ }
+ let _ = worker_thread.join();
+ }
+ }
+ }
+}
diff --git a/devices/src/virtio/vhost/user/vmm/worker.rs b/devices/src/virtio/vhost/user/vmm/worker.rs
new file mode 100644
index 000000000..0e66c09bb
--- /dev/null
+++ b/devices/src/virtio/vhost/user/vmm/worker.rs
@@ -0,0 +1,44 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use base::Event;
+use cros_async::{select2, Executor, SelectResult};
+use futures::pin_mut;
+use vm_memory::GuestMemory;
+
+use crate::virtio::{async_utils, Interrupt, Queue};
+
+pub struct Worker {
+ pub queues: Vec<Queue>,
+ pub mem: GuestMemory,
+ pub kill_evt: Event,
+}
+
+impl Worker {
+ // Runs asynchronous tasks.
+ pub fn run(&mut self, interrupt: Interrupt) -> Result<(), String> {
+ let ex = Executor::new().expect("failed to create an executor");
+
+ let interrupt = Rc::new(RefCell::new(interrupt));
+ let resample = async_utils::handle_irq_resample(&ex, interrupt);
+ pin_mut!(resample);
+
+ let kill_evt = self.kill_evt.try_clone().expect("failed to clone kill_evt");
+ let kill = async_utils::await_and_exit(&ex, kill_evt);
+ pin_mut!(kill);
+
+ match ex.run_until(select2(resample, kill)) {
+ Ok((resample_res, _)) => {
+ if let SelectResult::Finished(Err(e)) = resample_res {
+ return Err(format!("failed to resample a irq value: {:?}", e));
+ }
+ Ok(())
+ }
+ Err(e) => Err(e.to_string()),
+ }
+ }
+}
diff --git a/devices/src/virtio/vhost/user/worker.rs b/devices/src/virtio/vhost/user/worker.rs
deleted file mode 100644
index 0015a158c..000000000
--- a/devices/src/virtio/vhost/user/worker.rs
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2021 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use base::{error, Event};
-use cros_async::{select2, AsyncError, EventAsync, Executor, SelectResult};
-use futures::pin_mut;
-use thiserror::Error as ThisError;
-use vm_memory::GuestMemory;
-
-use crate::virtio::interrupt::SignalableInterrupt;
-use crate::virtio::{Interrupt, Queue};
-
-#[derive(ThisError, Debug)]
-enum Error {
- /// Failed to read the resample event.
- #[error("failed to read the resample event: {0}")]
- ReadResampleEvent(AsyncError),
-}
-
-pub struct Worker {
- pub queues: Vec<Queue>,
- pub mem: GuestMemory,
- pub kill_evt: Event,
-}
-
-impl Worker {
- // Processes any requests to resample the irq value.
- async fn handle_irq_resample(
- resample_evt: EventAsync,
- interrupt: Interrupt,
- ) -> Result<(), Error> {
- loop {
- let _ = resample_evt
- .next_val()
- .await
- .map_err(Error::ReadResampleEvent)?;
- interrupt.do_interrupt_resample();
- }
- }
-
- // Waits until the kill event is triggered.
- async fn wait_kill(kill_evt: EventAsync) {
- // Once this event is readable, exit. Exiting this future will cause the main loop to
- // break and the device process to exit.
- let _ = kill_evt.next_val().await;
- }
-
- // Runs asynchronous tasks.
- pub fn run(&mut self, ex: &Executor, interrupt: Interrupt) -> Result<(), String> {
- let resample_evt = interrupt
- .get_resample_evt()
- .expect("resample event required")
- .try_clone()
- .expect("failed to clone resample event");
- let async_resample_evt =
- EventAsync::new(resample_evt.0, ex).expect("failed to create async resample event");
- let resample = Self::handle_irq_resample(async_resample_evt, interrupt);
- pin_mut!(resample);
-
- let kill_evt = EventAsync::new(
- self.kill_evt
- .try_clone()
- .expect("failed to clone kill_evt")
- .0,
- &ex,
- )
- .expect("failed to create async kill event fd");
- let kill = Self::wait_kill(kill_evt);
- pin_mut!(kill);
-
- match ex.run_until(select2(resample, kill)) {
- Ok((resample_res, _)) => {
- if let SelectResult::Finished(Err(e)) = resample_res {
- return Err(format!("failed to resample a irq value: {:?}", e));
- }
- Ok(())
- }
- Err(e) => Err(e.to_string()),
- }
- }
-}
diff --git a/devices/src/virtio/vhost/vsock.rs b/devices/src/virtio/vhost/vsock.rs
index 719aea48b..6ae6c641f 100644
--- a/devices/src/virtio/vhost/vsock.rs
+++ b/devices/src/virtio/vhost/vsock.rs
@@ -2,11 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::{path::PathBuf, thread};
+use std::fs::{File, OpenOptions};
+use std::os::unix::prelude::{FromRawFd, OpenOptionsExt};
+use std::path::PathBuf;
+use std::thread;
+use anyhow::Context;
+use base::{error, validate_raw_descriptor, warn, AsRawDescriptor, Event, RawDescriptor};
use data_model::{DataInit, Le64};
-
-use base::{error, warn, AsRawDescriptor, Event, RawDescriptor};
+use serde::Deserialize;
use vhost::Vhost;
use vhost::Vsock as VhostVsockHandle;
use vm_memory::GuestMemory;
@@ -15,9 +19,31 @@ use super::worker::Worker;
use super::{Error, Result};
use crate::virtio::{copy_config, Interrupt, Queue, VirtioDevice, TYPE_VSOCK};
-const QUEUE_SIZE: u16 = 256;
+pub const QUEUE_SIZE: u16 = 256;
const NUM_QUEUES: usize = 3;
-const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
+pub const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE; NUM_QUEUES];
+static VHOST_VSOCK_DEFAULT_PATH: &str = "/dev/vhost-vsock";
+
+#[derive(Debug, Deserialize, PartialEq)]
+#[serde(deny_unknown_fields)]
+pub struct VhostVsockConfig {
+ #[serde(default)]
+ pub device: VhostVsockDeviceParameter,
+ pub cid: u64,
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum VhostVsockDeviceParameter {
+ Path(PathBuf),
+ Fd(RawDescriptor),
+}
+
+impl Default for VhostVsockDeviceParameter {
+ fn default() -> Self {
+ VhostVsockDeviceParameter::Path(PathBuf::from(VHOST_VSOCK_DEFAULT_PATH))
+ }
+}
pub struct Vsock {
worker_kill_evt: Option<Event>,
@@ -31,15 +57,25 @@ pub struct Vsock {
impl Vsock {
/// Create a new virtio-vsock device with the given VM cid.
- pub fn new(
- vhost_vsock_device_path: &PathBuf,
- base_features: u64,
- cid: u64,
- mem: &GuestMemory,
- ) -> Result<Vsock> {
+ pub fn new(base_features: u64, vhost_config: &VhostVsockConfig) -> anyhow::Result<Vsock> {
+ let device_file = match &vhost_config.device {
+ VhostVsockDeviceParameter::Fd(fd) => {
+ let fd = validate_raw_descriptor(*fd)
+ .context("failed to validate fd for virtual socket device")?;
+ // Safe because the `fd` is actually owned by this process and
+ // we have a unique handle to it.
+ unsafe { File::from_raw_fd(fd) }
+ }
+ VhostVsockDeviceParameter::Path(path) => OpenOptions::new()
+ .read(true)
+ .write(true)
+ .custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK)
+ .open(path)
+ .context("failed to open virtual socket device")?,
+ };
+
let kill_evt = Event::new().map_err(Error::CreateKillEvent)?;
- let handle =
- VhostVsockHandle::new(vhost_vsock_device_path, mem).map_err(Error::VhostOpen)?;
+ let handle = VhostVsockHandle::new(device_file);
let avail_features = base_features
| 1 << virtio_sys::vhost::VIRTIO_F_NOTIFY_ON_EMPTY
@@ -57,7 +93,7 @@ impl Vsock {
worker_kill_evt: Some(kill_evt.try_clone().map_err(Error::CloneKillEvent)?),
kill_evt: Some(kill_evt),
vhost_handle: Some(handle),
- cid,
+ cid: vhost_config.cid,
interrupts: Some(interrupts),
avail_features,
acked_features: 0,
@@ -147,7 +183,7 @@ impl VirtioDevice for Vsock {
fn activate(
&mut self,
- _: GuestMemory,
+ mem: GuestMemory,
interrupt: Interrupt,
queues: Vec<Queue>,
queue_evts: Vec<Event>,
@@ -162,29 +198,32 @@ impl VirtioDevice for Vsock {
if let Some(kill_evt) = self.worker_kill_evt.take() {
let acked_features = self.acked_features;
let cid = self.cid;
+ // The third vq is an event-only vq that is not handled by the vhost
+ // subsystem (but still needs to exist). Split it off here.
+ let vhost_queues = queues[..2].to_vec();
+ let mut worker = Worker::new(
+ vhost_queues,
+ vhost_handle,
+ interrupts,
+ interrupt,
+ acked_features,
+ kill_evt,
+ None,
+ );
+ let activate_vqs = |handle: &VhostVsockHandle| -> Result<()> {
+ handle.set_cid(cid).map_err(Error::VhostVsockSetCid)?;
+ handle.start().map_err(Error::VhostVsockStart)?;
+ Ok(())
+ };
+ let result = worker.init(mem, queue_evts, QUEUE_SIZES, activate_vqs);
+ if let Err(e) = result {
+ error!("vpipe worker thread exited with error: {:?}", e);
+ }
let worker_result = thread::Builder::new()
.name("vhost_vsock".to_string())
.spawn(move || {
- // The third vq is an event-only vq that is not handled by the vhost
- // subsystem (but still needs to exist). Split it off here.
- let vhost_queues = queues[..2].to_vec();
- let mut worker = Worker::new(
- vhost_queues,
- vhost_handle,
- interrupts,
- interrupt,
- acked_features,
- kill_evt,
- None,
- );
- let activate_vqs = |handle: &VhostVsockHandle| -> Result<()> {
- handle.set_cid(cid).map_err(Error::VhostVsockSetCid)?;
- handle.start().map_err(Error::VhostVsockStart)?;
- Ok(())
- };
let cleanup_vqs = |_handle: &VhostVsockHandle| -> Result<()> { Ok(()) };
- let result =
- worker.run(queue_evts, QUEUE_SIZES, activate_vqs, cleanup_vqs);
+ let result = worker.run(cleanup_vqs);
if let Err(e) = result {
error!("vsock worker thread exited with error: {:?}", e);
}
@@ -214,9 +253,12 @@ impl VirtioDevice for Vsock {
#[cfg(test)]
mod tests {
- use super::*;
-
use std::convert::TryInto;
+ use std::result::Result;
+
+ use serde_keyvalue::*;
+
+ use super::*;
#[test]
fn ack_features() {
@@ -289,4 +331,100 @@ mod tests {
let vsock = Vsock::new_for_testing(cid, features);
assert_eq!(features, vsock.features());
}
+
+ fn from_vsock_arg(options: &str) -> Result<VhostVsockConfig, ParseError> {
+ from_key_values(options)
+ }
+
+ #[test]
+ fn params_from_key_values() {
+ // Fd device
+ let params = from_vsock_arg("device=42,cid=56").unwrap();
+ assert_eq!(
+ params,
+ VhostVsockConfig {
+ device: VhostVsockDeviceParameter::Fd(42),
+ cid: 56,
+ }
+ );
+ // No key for fd device
+ let params = from_vsock_arg("42,cid=56").unwrap();
+ assert_eq!(
+ params,
+ VhostVsockConfig {
+ device: VhostVsockDeviceParameter::Fd(42),
+ cid: 56,
+ }
+ );
+ // Path device
+ let params = from_vsock_arg("device=/some/path,cid=56").unwrap();
+ assert_eq!(
+ params,
+ VhostVsockConfig {
+ device: VhostVsockDeviceParameter::Path("/some/path".into()),
+ cid: 56,
+ }
+ );
+ // No key for path device
+ let params = from_vsock_arg("/some/path,cid=56").unwrap();
+ assert_eq!(
+ params,
+ VhostVsockConfig {
+ device: VhostVsockDeviceParameter::Path("/some/path".into()),
+ cid: 56,
+ }
+ );
+ // Default device
+ let params = from_vsock_arg("cid=56").unwrap();
+ assert_eq!(
+ params,
+ VhostVsockConfig {
+ device: VhostVsockDeviceParameter::Path(VHOST_VSOCK_DEFAULT_PATH.into()),
+ cid: 56,
+ }
+ );
+
+ // No argument
+ assert_eq!(
+ from_vsock_arg("").unwrap_err(),
+ ParseError {
+ kind: ErrorKind::SerdeError("missing field `cid`".into()),
+ pos: 0
+ }
+ );
+ // Missing cid
+ assert_eq!(
+ from_vsock_arg("device=42").unwrap_err(),
+ ParseError {
+ kind: ErrorKind::SerdeError("missing field `cid`".into()),
+ pos: 0,
+ }
+ );
+ // Cid passed twice
+ assert_eq!(
+ from_vsock_arg("cid=42,cid=56").unwrap_err(),
+ ParseError {
+ kind: ErrorKind::SerdeError("duplicate field `cid`".into()),
+ pos: 0,
+ }
+ );
+ // Device passed twice
+ assert_eq!(
+ from_vsock_arg("cid=56,device=42,device=/some/path").unwrap_err(),
+ ParseError {
+ kind: ErrorKind::SerdeError("duplicate field `device`".into()),
+ pos: 0,
+ }
+ );
+ // Invalid argument
+ assert_eq!(
+ from_vsock_arg("invalid=foo").unwrap_err(),
+ ParseError {
+ kind: ErrorKind::SerdeError(
+ "unknown field `invalid`, expected `device` or `cid`".into()
+ ),
+ pos: 0,
+ }
+ );
+ }
}
diff --git a/devices/src/virtio/vhost/worker.rs b/devices/src/virtio/vhost/worker.rs
index 9d7822ebf..01309186f 100644
--- a/devices/src/virtio/vhost/worker.rs
+++ b/devices/src/virtio/vhost/worker.rs
@@ -6,6 +6,7 @@ use std::os::raw::c_ulonglong;
use base::{error, Error as SysError, Event, PollToken, Tube, WaitContext};
use vhost::Vhost;
+use vm_memory::GuestMemory;
use super::control_socket::{VhostDevRequest, VhostDevResponse};
use super::{Error, Result};
@@ -44,16 +45,15 @@ impl<T: Vhost> Worker<T> {
}
}
- pub fn run<F1, F2>(
+ pub fn init<F1>(
&mut self,
+ mem: GuestMemory,
queue_evts: Vec<Event>,
queue_sizes: &[u16],
activate_vqs: F1,
- cleanup_vqs: F2,
) -> Result<()>
where
F1: FnOnce(&T) -> Result<()>,
- F2: FnOnce(&T) -> Result<()>,
{
let avail_features = self
.vhost_handle
@@ -66,7 +66,7 @@ impl<T: Vhost> Worker<T> {
.map_err(Error::VhostSetFeatures)?;
self.vhost_handle
- .set_mem_table()
+ .set_mem_table(&mem)
.map_err(Error::VhostSetMemTable)?;
for (queue_index, queue) in self.queues.iter().enumerate() {
@@ -76,6 +76,7 @@ impl<T: Vhost> Worker<T> {
self.vhost_handle
.set_vring_addr(
+ &mem,
queue_sizes[queue_index],
queue.actual_size(),
queue_index,
@@ -96,7 +97,13 @@ impl<T: Vhost> Worker<T> {
}
activate_vqs(&self.vhost_handle)?;
+ Ok(())
+ }
+ pub fn run<F1>(&mut self, cleanup_vqs: F1) -> Result<()>
+ where
+ F1: FnOnce(&T) -> Result<()>,
+ {
#[derive(PollToken)]
enum Token {
VhostIrqi { index: usize },
diff --git a/devices/src/virtio/video/async_cmd_desc_map.rs b/devices/src/virtio/video/async_cmd_desc_map.rs
index 560b5b954..657ce62b0 100644
--- a/devices/src/virtio/video/async_cmd_desc_map.rs
+++ b/devices/src/virtio/video/async_cmd_desc_map.rs
@@ -45,10 +45,10 @@ impl AsyncCmdDescMap {
queue_type,
..
} if stream_id == target_stream_id
- && target_queue_type.as_ref().unwrap_or(&queue_type) == queue_type =>
+ && target_queue_type.as_ref().unwrap_or(queue_type) == queue_type =>
{
responses.push(AsyncCmdResponse::from_response(
- tag.clone(),
+ *tag,
CmdResponse::ResourceQueue {
timestamp: 0,
flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_ERR,
@@ -60,7 +60,7 @@ impl AsyncCmdDescMap {
// TODO(b/1518105): Use more appropriate error code if a new protocol supports
// one.
responses.push(AsyncCmdResponse::from_error(
- tag.clone(),
+ *tag,
VideoError::InvalidOperation,
));
}
@@ -68,12 +68,12 @@ impl AsyncCmdDescMap {
stream_id,
queue_type,
} if stream_id == target_stream_id
- && target_queue_type.as_ref().unwrap_or(&queue_type) == queue_type =>
+ && target_queue_type.as_ref().unwrap_or(queue_type) == queue_type =>
{
// TODO(b/1518105): Use more appropriate error code if a new protocol supports
// one.
responses.push(AsyncCmdResponse::from_error(
- tag.clone(),
+ *tag,
VideoError::InvalidOperation,
));
}
diff --git a/devices/src/virtio/video/command.rs b/devices/src/virtio/video/command.rs
index e32a93504..12ec37146 100644
--- a/devices/src/virtio/video/command.rs
+++ b/devices/src/virtio/video/command.rs
@@ -4,9 +4,10 @@
//! Data structures for commands of virtio video devices.
+use remain::sorted;
use std::convert::{TryFrom, TryInto};
-use std::fmt;
use std::io;
+use thiserror::Error as ThisError;
use base::error;
use data_model::Le32;
@@ -16,41 +17,27 @@ use crate::virtio::video::control::*;
use crate::virtio::video::format::*;
use crate::virtio::video::params::Params;
use crate::virtio::video::protocol::*;
+use crate::virtio::video::resource::{ResourceType, UnresolvedResourceEntry};
use crate::virtio::Reader;
/// An error indicating a failure while reading a request from the guest.
-#[derive(Debug)]
+#[sorted]
+#[derive(Debug, ThisError)]
pub enum ReadCmdError {
- /// Failure while reading an object.
- IoError(io::Error),
- /// Invalid arguement is passed,
+ /// Invalid argument is passed.
+ #[error("invalid argument passed to command")]
InvalidArgument,
/// The type of the command was invalid.
+ #[error("invalid command type: {0}")]
InvalidCmdType(u32),
+ /// Failed to read an object.
+ #[error("failed to read object: {0}")]
+ IoError(#[from] io::Error),
/// The type of the requested control was unsupported.
+ #[error("unsupported control type: {0}")]
UnsupportedCtrlType(u32),
}
-impl fmt::Display for ReadCmdError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::ReadCmdError::*;
- match self {
- IoError(e) => write!(f, "failed to read an object: {}", e),
- InvalidArgument => write!(f, "invalid arguement is passed in command"),
- InvalidCmdType(t) => write!(f, "invalid command type: {}", t),
- UnsupportedCtrlType(t) => write!(f, "unsupported control type: {}", t),
- }
- }
-}
-
-impl std::error::Error for ReadCmdError {}
-
-impl From<io::Error> for ReadCmdError {
- fn from(e: io::Error) -> ReadCmdError {
- ReadCmdError::IoError(e)
- }
-}
-
#[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)]
#[repr(u32)]
pub enum QueueType {
@@ -67,6 +54,8 @@ pub enum VideoCmd {
StreamCreate {
stream_id: u32,
coded_format: Format,
+ input_resource_type: ResourceType,
+ output_resource_type: ResourceType,
},
StreamDestroy {
stream_id: u32,
@@ -79,7 +68,10 @@ pub enum VideoCmd {
queue_type: QueueType,
resource_id: u32,
plane_offsets: Vec<u32>,
- uuid: u128,
+ /// The outer vector contains one entry per memory plane, whereas the inner vector contains
+ /// all the memory entries that make a single plane (i.e. one for virtio objects, one or
+ /// more for guest pages).
+ plane_entries: Vec<Vec<UnresolvedResourceEntry>>,
},
ResourceQueue {
stream_id: u32,
@@ -99,11 +91,15 @@ pub enum VideoCmd {
GetParams {
stream_id: u32,
queue_type: QueueType,
+ /// `true` if this command has been created from the GET_PARAMS_EXT guest command.
+ is_ext: bool,
},
SetParams {
stream_id: u32,
queue_type: QueueType,
params: Params,
+ /// `true` if this command has been created from the SET_PARAMS_EXT guest command.
+ is_ext: bool,
},
QueryControl {
query_ctrl_type: QueryCtrlType,
@@ -144,15 +140,29 @@ impl<'a> VideoCmd {
..
} = r.read_obj()?;
- if in_mem_type != VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT
- || out_mem_type != VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT
- {
- error!("mem_type must be VIRTIO_OBJECT");
- return Err(InvalidArgument);
- }
+ let input_resource_type = match in_mem_type.into() {
+ VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT => ResourceType::VirtioObject,
+ VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES => ResourceType::GuestPages,
+ m => {
+ error!("Unsupported input resource memory type 0x{:x}!", m);
+ return Err(InvalidArgument);
+ }
+ };
+
+ let output_resource_type = match out_mem_type.into() {
+ VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT => ResourceType::VirtioObject,
+ VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES => ResourceType::GuestPages,
+ m => {
+ error!("Unsupported output resource memory type 0x{:x}!", m);
+ return Err(InvalidArgument);
+ }
+ };
+
StreamCreate {
stream_id: hdr.stream_id.into(),
coded_format: coded_format.try_into()?,
+ input_resource_type,
+ output_resource_type,
}
}
VIRTIO_VIDEO_CMD_STREAM_DESTROY => {
@@ -174,40 +184,56 @@ impl<'a> VideoCmd {
planes_layout,
num_planes,
plane_offsets,
- ..
+ num_entries,
} = r.read_obj()?;
// Assume ChromeOS-specific requirements.
- if Into::<u32>::into(planes_layout) != VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER {
- error!(
- "each buffer must be a single DMAbuf: {}",
- Into::<u32>::into(planes_layout),
- );
+ let planes_layout = Into::<u32>::into(planes_layout);
+ if planes_layout != VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER {
+ error!("Only single-planar formats are supported for now");
return Err(InvalidArgument);
}
- let num_planes: u32 = num_planes.into();
- if num_planes as usize > plane_offsets.len() {
+ let num_planes = Into::<u32>::into(num_planes) as usize;
+ if num_planes > plane_offsets.len() {
error!(
- "num_planes must not exceed {} but {}",
+ "num_planes is {} but shall not exceed {}",
+ num_planes,
plane_offsets.len(),
+ );
+ return Err(InvalidArgument);
+ }
+ if planes_layout == VIRTIO_VIDEO_PLANES_LAYOUT_SINGLE_BUFFER && num_planes != 1 {
+ error!(
+ "Single-planar format specified but num_planes is {}",
num_planes
);
return Err(InvalidArgument);
}
- let plane_offsets = plane_offsets[0..num_planes as usize]
+
+ let plane_offsets = plane_offsets[0..num_planes]
.iter()
.map(|x| Into::<u32>::into(*x))
.collect::<Vec<u32>>();
- let virtio_video_object_entry { uuid } = r.read_obj()?;
+ // Read all the entries for all the planes.
+ let plane_entries = (0..num_planes as usize)
+ .into_iter()
+ .map(|i| {
+ let num_entries: u32 = num_entries[i].into();
+ (0..num_entries)
+ .into_iter()
+ .map(|_| r.read_obj::<UnresolvedResourceEntry>())
+ .collect::<Result<Vec<_>, _>>()
+ })
+ .collect::<Result<Vec<_>, _>>()?;
ResourceCreate {
stream_id: hdr.stream_id.into(),
queue_type: queue_type.try_into()?,
resource_id: resource_id.into(),
plane_offsets,
- uuid: u128::from_be_bytes(uuid),
+ plane_entries,
}
}
VIRTIO_VIDEO_CMD_RESOURCE_QUEUE => {
@@ -255,6 +281,7 @@ impl<'a> VideoCmd {
GetParams {
stream_id: hdr.stream_id.into(),
queue_type: queue_type.try_into()?,
+ is_ext: false,
}
}
VIRTIO_VIDEO_CMD_SET_PARAMS => {
@@ -263,6 +290,7 @@ impl<'a> VideoCmd {
stream_id: hdr.stream_id.into(),
queue_type: params.queue_type.try_into()?,
params: params.try_into()?,
+ is_ext: false,
}
}
VIRTIO_VIDEO_CMD_QUERY_CONTROL => {
@@ -288,9 +316,12 @@ impl<'a> VideoCmd {
let virtio_video_get_control { control, .. } = r.read_obj()?;
let ctrl_type = match control.into() {
VIRTIO_VIDEO_CONTROL_BITRATE => CtrlType::Bitrate,
+ VIRTIO_VIDEO_CONTROL_BITRATE_PEAK => CtrlType::BitratePeak,
+ VIRTIO_VIDEO_CONTROL_BITRATE_MODE => CtrlType::BitrateMode,
VIRTIO_VIDEO_CONTROL_PROFILE => CtrlType::Profile,
VIRTIO_VIDEO_CONTROL_LEVEL => CtrlType::Level,
VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME => CtrlType::ForceKeyframe,
+ VIRTIO_VIDEO_CONTROL_PREPEND_SPSPPS_TO_IDR => CtrlType::PrependSpsPpsToIdr,
t => {
return Err(ReadCmdError::UnsupportedCtrlType(t));
}
@@ -308,6 +339,16 @@ impl<'a> VideoCmd {
.bitrate
.into(),
),
+ VIRTIO_VIDEO_CONTROL_BITRATE_PEAK => CtrlVal::BitratePeak(
+ r.read_obj::<virtio_video_control_val_bitrate_peak>()?
+ .bitrate_peak
+ .into(),
+ ),
+ VIRTIO_VIDEO_CONTROL_BITRATE_MODE => CtrlVal::BitrateMode(
+ r.read_obj::<virtio_video_control_val_bitrate_mode>()?
+ .bitrate_mode
+ .try_into()?,
+ ),
VIRTIO_VIDEO_CONTROL_PROFILE => CtrlVal::Profile(
r.read_obj::<virtio_video_control_val_profile>()?
.profile
@@ -318,7 +359,12 @@ impl<'a> VideoCmd {
.level
.try_into()?,
),
- VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME => CtrlVal::ForceKeyframe(),
+ VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME => CtrlVal::ForceKeyframe,
+ VIRTIO_VIDEO_CONTROL_PREPEND_SPSPPS_TO_IDR => CtrlVal::PrependSpsPpsToIdr(
+ r.read_obj::<virtio_video_control_val_prepend_spspps_to_idr>()?
+ .prepend_spspps_to_idr
+ != 0,
+ ),
t => {
return Err(ReadCmdError::UnsupportedCtrlType(t));
}
@@ -328,6 +374,23 @@ impl<'a> VideoCmd {
ctrl_val,
}
}
+ VIRTIO_VIDEO_CMD_GET_PARAMS_EXT => {
+ let virtio_video_get_params_ext { queue_type, .. } = r.read_obj()?;
+ GetParams {
+ stream_id: hdr.stream_id.into(),
+ queue_type: queue_type.try_into()?,
+ is_ext: true,
+ }
+ }
+ VIRTIO_VIDEO_CMD_SET_PARAMS_EXT => {
+ let virtio_video_set_params_ext { params } = r.read_obj()?;
+ SetParams {
+ stream_id: hdr.stream_id.into(),
+ queue_type: params.base.queue_type.try_into()?,
+ params: params.try_into()?,
+ is_ext: true,
+ }
+ }
_ => return Err(ReadCmdError::InvalidCmdType(hdr.type_.into())),
})
}
diff --git a/devices/src/virtio/video/control.rs b/devices/src/virtio/video/control.rs
index e997a1193..4fcaf8d46 100644
--- a/devices/src/virtio/video/control.rs
+++ b/devices/src/virtio/video/control.rs
@@ -9,7 +9,7 @@ use std::io;
use data_model::Le32;
-use crate::virtio::video::format::{Format, Level, Profile};
+use crate::virtio::video::format::{BitrateMode, Format, Level, Profile};
use crate::virtio::video::protocol::*;
use crate::virtio::video::response::Response;
use crate::virtio::Writer;
@@ -54,6 +54,9 @@ pub enum CtrlType {
Profile,
Level,
ForceKeyframe,
+ BitrateMode,
+ BitratePeak,
+ PrependSpsPpsToIdr,
}
#[derive(Debug, Clone)]
@@ -61,7 +64,10 @@ pub enum CtrlVal {
Bitrate(u32),
Profile(Profile),
Level(Level),
- ForceKeyframe(),
+ ForceKeyframe,
+ BitrateMode(BitrateMode),
+ BitratePeak(u32),
+ PrependSpsPpsToIdr(bool),
}
impl Response for CtrlVal {
@@ -71,6 +77,14 @@ impl Response for CtrlVal {
bitrate: Le32::from(*r),
..Default::default()
}),
+ CtrlVal::BitratePeak(r) => w.write_obj(virtio_video_control_val_bitrate_peak {
+ bitrate_peak: Le32::from(*r),
+ ..Default::default()
+ }),
+ CtrlVal::BitrateMode(m) => w.write_obj(virtio_video_control_val_bitrate_mode {
+ bitrate_mode: Le32::from(*m as u32),
+ ..Default::default()
+ }),
CtrlVal::Profile(p) => w.write_obj(virtio_video_control_val_profile {
profile: Le32::from(*p as u32),
..Default::default()
@@ -79,10 +93,16 @@ impl Response for CtrlVal {
level: Le32::from(*l as u32),
..Default::default()
}),
- CtrlVal::ForceKeyframe() => Err(io::Error::new(
+ CtrlVal::ForceKeyframe => Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Button controls should not be queried.",
)),
+ CtrlVal::PrependSpsPpsToIdr(p) => {
+ w.write_obj(virtio_video_control_val_prepend_spspps_to_idr {
+ prepend_spspps_to_idr: Le32::from(*p as u32),
+ ..Default::default()
+ })
+ }
}
}
}
diff --git a/devices/src/virtio/video/decoder/backend/mod.rs b/devices/src/virtio/video/decoder/backend/mod.rs
index 0bd6b6bf0..35eb9c090 100644
--- a/devices/src/virtio/video/decoder/backend/mod.rs
+++ b/devices/src/virtio/video/decoder/backend/mod.rs
@@ -5,33 +5,30 @@
//! This module implements the interface that actual decoder devices need to
//! implement in order to provide video decoding capability to the guest.
-use std::fs::File;
-
use crate::virtio::video::{
+ decoder::Capability,
error::{VideoError, VideoResult},
format::{Format, Rect},
+ resource::{GuestResource, GuestResourceHandle},
};
-use base::RawDescriptor;
+use base::AsRawDescriptor;
+pub mod utils;
+#[cfg(feature = "libvda")]
pub mod vda;
-pub struct FramePlane {
- pub offset: i32,
- pub stride: i32,
-}
-
/// Contains the device's state for one playback session, i.e. one stream.
pub trait DecoderSession {
- /// Tell how many output buffers will be used for this session. This method
- /// Must be called after a `ProvidePictureBuffers` event is emitted, and
- /// before the first call to `use_output_buffers()`.
- fn set_output_buffer_count(&self, count: usize) -> VideoResult<()>;
+ /// Tell how many output buffers will be used for this session and which format they will carry.
+ /// This method must be called after a `ProvidePictureBuffers` event is emitted, and before the
+ /// first call to `use_output_buffer()`.
+ fn set_output_parameters(&mut self, buffer_count: usize, format: Format) -> VideoResult<()>;
/// Decode the compressed stream contained in [`offset`..`offset`+`bytes_used`]
- /// of the shared memory in `descriptor`. `bitstream_id` is the identifier for that
+ /// of the shared memory in `resource`. `bitstream_id` is the identifier for that
/// part of the stream (most likely, a timestamp).
///
- /// The device takes ownership of `descriptor` and is responsible for closing it
+ /// The device takes ownership of `resource` and is responsible for closing it
/// once it is not used anymore.
///
/// The device will emit a `NotifyEndOfBitstreamBuffer` event after the input
@@ -41,48 +38,46 @@ pub trait DecoderSession {
/// set to the same value as the argument of the same name for each picture
/// produced from that input buffer.
fn decode(
- &self,
+ &mut self,
bitstream_id: i32,
- descriptor: RawDescriptor,
+ resource: GuestResourceHandle,
offset: u32,
bytes_used: u32,
) -> VideoResult<()>;
- /// Flush the decoder device, i.e. finish processing of all queued decode
- /// requests.
+ /// Flush the decoder device, i.e. finish processing all queued decode requests and emit frames
+ /// for them.
///
/// The device will emit a `FlushCompleted` event once the flush is done.
- fn flush(&self) -> VideoResult<()>;
+ fn flush(&mut self) -> VideoResult<()>;
/// Reset the decoder device, i.e. cancel all pending decoding requests.
///
/// The device will emit a `ResetCompleted` event once the reset is done.
- fn reset(&self) -> VideoResult<()>;
+ fn reset(&mut self) -> VideoResult<()>;
+
+ /// Immediately release all buffers passed using `use_output_buffer()` and
+ /// `reuse_output_buffer()`.
+ fn clear_output_buffers(&mut self) -> VideoResult<()>;
- /// Returns the event pipe on which the availability of an event will be
- /// signaled.
- fn event_pipe(&self) -> &File;
+ /// Returns the event pipe on which the availability of events will be signaled. Note that the
+ /// returned value is borrowed and only valid as long as the session is alive.
+ fn event_pipe(&self) -> &dyn AsRawDescriptor;
- /// Ask the device to use the memory buffer in `output_buffer` to store
- /// decoded frames in pixel format `format`. `planes` describes how the
- /// frame's planes should be laid out in the buffer, and `picture_buffer_id`
- /// is the ID of the picture, that will be reproduced in `PictureReady` events
- /// using this buffer.
+ /// Ask the device to use `resource` to store decoded frames according to its layout.
+ /// `picture_buffer_id` is the ID of the picture that will be reproduced in `PictureReady`
+ /// events using this buffer.
///
- /// The device takes ownership of `output_buffer` and is responsible for
- /// closing it once the buffer is not used anymore (either when the session
- /// is closed, or a new set of buffers is provided for the session).
+ /// The device takes ownership of `resource` and is responsible for closing it once the buffer
+ /// is not used anymore (either when the session is closed, or a new set of buffers is provided
+ /// for the session).
///
- /// The device will emit a `PictureReady` event with the `picture_buffer_id`
- /// field set to the same value as the argument of the same name when a
- /// frame has been decoded into that buffer.
+ /// The device will emit a `PictureReady` event with the `picture_buffer_id` field set to the
+ /// same value as the argument of the same name when a frame has been decoded into that buffer.
fn use_output_buffer(
- &self,
+ &mut self,
picture_buffer_id: i32,
- format: Format,
- output_buffer: RawDescriptor,
- planes: &[FramePlane],
- modifier: u64,
+ resource: GuestResource,
) -> VideoResult<()>;
/// Ask the device to reuse an output buffer previously passed to
@@ -92,7 +87,7 @@ pub trait DecoderSession {
/// The device will emit a `PictureReady` event with the `picture_buffer_id`
/// field set to the same value as the argument of the same name when a
/// frame has been decoded into that buffer.
- fn reuse_output_buffer(&self, picture_buffer_id: i32) -> VideoResult<()>;
+ fn reuse_output_buffer(&mut self, picture_buffer_id: i32) -> VideoResult<()>;
/// Blocking call to read a single event from the event pipe.
fn read_event(&mut self) -> VideoResult<DecoderEvent>;
@@ -101,15 +96,19 @@ pub trait DecoderSession {
pub trait DecoderBackend {
type Session: DecoderSession;
- /// Create a new decoding session for the passed `profile`.
- fn new_session(&self, format: Format) -> VideoResult<Self::Session>;
+ /// Return the decoding capabilities for this backend instance.
+ fn get_capabilities(&self) -> Capability;
+
+ /// Create a new decoding session for the passed `format`.
+ fn new_session(&mut self, format: Format) -> VideoResult<Self::Session>;
}
#[derive(Debug)]
pub enum DecoderEvent {
- /// Emitted when the device knows the buffer format it will need to decode
- /// frames, and how many buffers it will need. The decoder is supposed to
- /// provide buffers of the requested dimensions using `use_output_buffer`.
+ /// Emitted when the device knows the buffer format it will need to decode frames, and how many
+ /// buffers it will need. The decoder is supposed to call `set_output_parameters()` to confirm
+ /// the pixel format and actual number of buffers used, and provide buffers of the requested
+ /// dimensions using `use_output_buffer()`.
ProvidePictureBuffers {
min_num_buffers: u32,
width: i32,
diff --git a/devices/src/virtio/video/decoder/backend/utils.rs b/devices/src/virtio/video/decoder/backend/utils.rs
new file mode 100644
index 000000000..d79d5c669
--- /dev/null
+++ b/devices/src/virtio/video/decoder/backend/utils.rs
@@ -0,0 +1,282 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TODO(acourbot): Remove once we start using this file
+#![allow(dead_code)]
+
+use std::{
+ collections::{btree_map::Entry, BTreeMap, VecDeque},
+ time::Duration,
+};
+
+use base::Event;
+use thiserror::Error as ThisError;
+
+use crate::virtio::video::resource::GuestResource;
+
+/// Manages a pollable queue of events to be sent to the decoder or encoder.
+pub struct EventQueue<T> {
+ /// Pipe used to signal available events.
+ event: Event,
+ /// FIFO of all pending events.
+ pending_events: VecDeque<T>,
+}
+
+impl<T> EventQueue<T> {
+ /// Create a new event queue.
+ pub fn new() -> base::Result<Self> {
+ Ok(Self {
+ // Use semaphore semantics so `eventfd` can be `read` as many times as it has been
+ // `write`n to without blocking.
+ event: Event::new()?,
+ pending_events: Default::default(),
+ })
+ }
+
+ /// Add `event` to the queue.
+ pub fn queue_event(&mut self, event: T) -> base::Result<()> {
+ self.pending_events.push_back(event);
+ self.event.write(1)?;
+ Ok(())
+ }
+
+ /// Read the next event, blocking until an event becomes available.
+ pub fn dequeue_event(&mut self) -> base::Result<T> {
+ // Wait until at least one event is written, if necessary.
+ let cpt = self.event.read()?;
+ let event = match self.pending_events.pop_front() {
+ Some(event) => event,
+ None => panic!("event signaled but no pending event - this is a bug."),
+ };
+ // If we have more than one event pending, write the remainder back into the event so it
+ // keeps signalling.
+ if cpt > 1 {
+ self.event.write(cpt - 1)?;
+ }
+
+ Ok(event)
+ }
+
+ /// Return a reference to an `Event` on which the caller can poll to know when `dequeue_event`
+ /// can be called without blocking.
+ pub fn event_pipe(&self) -> &Event {
+ &self.event
+ }
+
+ /// Remove all the posted events for which `predicate` returns `false`.
+ pub fn retain<P: FnMut(&T) -> bool>(&mut self, predicate: P) {
+ if self.pending_events.len() > 0 {
+ let _ = self
+ .event
+ .read_timeout(Duration::from_millis(0))
+ .expect("read_timeout failure");
+ }
+
+ self.pending_events.retain(predicate);
+
+ let num_pending_events = self.pending_events.len();
+ if num_pending_events > 0 {
+ self.event
+ .write(num_pending_events as u64)
+ .expect("write failure");
+ }
+ }
+
+ /// Returns the number of events currently pending on this queue, i.e. the number of times
+ /// `dequeue_event` can be called without blocking.
+ #[cfg(test)]
+ pub fn len(&self) -> usize {
+ self.pending_events.len()
+ }
+}
+
+/// Queue of all the output buffers provided by crosvm.
+pub struct OutputQueue {
+ // Max number of output buffers that can be imported into this queue.
+ num_buffers: usize,
+ // Maps picture IDs to the corresponding guest resource.
+ buffers: BTreeMap<u32, GuestResource>,
+ // Picture IDs of output buffers we can write into.
+ ready_buffers: VecDeque<u32>,
+}
+
+#[derive(Debug, ThisError)]
+pub enum OutputBufferImportError {
+ #[error("maximum number of imported buffers ({0}) already reached")]
+ MaxBuffersReached(usize),
+ #[error("a buffer with picture ID {0} is already imported")]
+ AlreadyImported(u32),
+}
+
+#[derive(Debug, ThisError)]
+pub enum OutputBufferReuseError {
+ #[error("no buffer with picture ID {0} is imported at the moment")]
+ NotYetImported(u32),
+ #[error("buffer with picture ID {0} is already ready for use")]
+ AlreadyUsed(u32),
+}
+
+impl OutputQueue {
+ /// Creates a new output queue capable of containing `num_buffers` buffers.
+ pub fn new(num_buffers: usize) -> Self {
+ Self {
+ num_buffers,
+ buffers: Default::default(),
+ ready_buffers: Default::default(),
+ }
+ }
+
+ /// Import a buffer, i.e. associate the buffer's `resource` to a given `picture_buffer_id`, and
+ /// make the buffer ready for use.
+ ///
+ /// A buffer with a given `picture_buffer_id` can only be imported once.
+ pub fn import_buffer(
+ &mut self,
+ picture_buffer_id: u32,
+ resource: GuestResource,
+ ) -> Result<(), OutputBufferImportError> {
+ if self.buffers.len() >= self.num_buffers {
+ return Err(OutputBufferImportError::MaxBuffersReached(self.num_buffers));
+ }
+
+ match self.buffers.entry(picture_buffer_id) {
+ Entry::Vacant(o) => {
+ o.insert(resource);
+ }
+ Entry::Occupied(_) => {
+ return Err(OutputBufferImportError::AlreadyImported(picture_buffer_id));
+ }
+ }
+
+ self.ready_buffers.push_back(picture_buffer_id);
+
+ Ok(())
+ }
+
+ /// Mark the previously-imported buffer with ID `picture_buffer_id` as ready for being used.
+ pub fn reuse_buffer(&mut self, picture_buffer_id: u32) -> Result<(), OutputBufferReuseError> {
+ if !self.buffers.contains_key(&picture_buffer_id) {
+ return Err(OutputBufferReuseError::NotYetImported(picture_buffer_id));
+ }
+
+ if self.ready_buffers.contains(&picture_buffer_id) {
+ return Err(OutputBufferReuseError::AlreadyUsed(picture_buffer_id));
+ }
+
+ self.ready_buffers.push_back(picture_buffer_id);
+
+ Ok(())
+ }
+
+ /// Get a buffer ready to be decoded into, if any is available.
+ pub fn try_get_ready_buffer(&mut self) -> Option<(u32, &mut GuestResource)> {
+ let picture_buffer_id = self.ready_buffers.pop_front()?;
+ // Unwrapping is safe here because our interface guarantees that ids in `ready_buffers` are
+ // valid keys for `buffers`.
+ Some((
+ picture_buffer_id,
+ self.buffers
+ .get_mut(&picture_buffer_id)
+ .expect("expected buffer not present in queue"),
+ ))
+ }
+
+ pub fn clear_ready_buffers(&mut self) {
+ self.ready_buffers.clear();
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::time::Duration;
+
+ use super::*;
+ use crate::virtio::video::{decoder::DecoderEvent, format::Rect};
+ use base::{PollToken, WaitContext};
+
+ /// Test basic queue/dequeue functionality of `EventQueue`.
+ #[test]
+ fn event_queue() {
+ let mut event_queue = EventQueue::new().unwrap();
+
+ assert_eq!(
+ event_queue.queue_event(DecoderEvent::NotifyEndOfBitstreamBuffer(1)),
+ Ok(())
+ );
+ assert_eq!(event_queue.len(), 1);
+ assert_eq!(
+ event_queue.queue_event(DecoderEvent::PictureReady {
+ picture_buffer_id: 0,
+ bitstream_id: 1,
+ visible_rect: Rect {
+ left: 0,
+ top: 0,
+ right: 320,
+ bottom: 240,
+ },
+ }),
+ Ok(())
+ );
+ assert_eq!(event_queue.len(), 2);
+
+ assert!(matches!(
+ event_queue.dequeue_event(),
+ Ok(DecoderEvent::NotifyEndOfBitstreamBuffer(1))
+ ));
+ assert_eq!(event_queue.len(), 1);
+ assert!(matches!(
+ event_queue.dequeue_event(),
+ Ok(DecoderEvent::PictureReady {
+ picture_buffer_id: 0,
+ bitstream_id: 1,
+ visible_rect: Rect {
+ left: 0,
+ top: 0,
+ right: 320,
+ bottom: 240,
+ }
+ })
+ ));
+ assert_eq!(event_queue.len(), 0);
+ }
+
+ /// Test polling of `DecoderEventQueue`'s `event_pipe`.
+ #[test]
+ fn decoder_event_queue_polling() {
+ #[derive(PollToken)]
+ enum Token {
+ Event,
+ }
+
+ let mut event_queue = EventQueue::new().unwrap();
+ let event_pipe = event_queue.event_pipe();
+ let wait_context = WaitContext::build_with(&[(event_pipe, Token::Event)]).unwrap();
+
+ // The queue is empty, so `event_pipe` should not signal.
+ assert_eq!(wait_context.wait_timeout(Duration::ZERO).unwrap().len(), 0);
+
+ // `event_pipe` should signal as long as the queue is not empty.
+ event_queue
+ .queue_event(DecoderEvent::NotifyEndOfBitstreamBuffer(1))
+ .unwrap();
+ assert_eq!(wait_context.wait_timeout(Duration::ZERO).unwrap().len(), 1);
+ event_queue
+ .queue_event(DecoderEvent::NotifyEndOfBitstreamBuffer(2))
+ .unwrap();
+ assert_eq!(wait_context.wait_timeout(Duration::ZERO).unwrap().len(), 1);
+ event_queue
+ .queue_event(DecoderEvent::NotifyEndOfBitstreamBuffer(3))
+ .unwrap();
+ assert_eq!(wait_context.wait_timeout(Duration::ZERO).unwrap().len(), 1);
+
+ event_queue.dequeue_event().unwrap();
+ assert_eq!(wait_context.wait_timeout(Duration::ZERO).unwrap().len(), 1);
+ event_queue.dequeue_event().unwrap();
+ assert_eq!(wait_context.wait_timeout(Duration::ZERO).unwrap().len(), 1);
+ event_queue.dequeue_event().unwrap();
+
+ // The queue is empty again, so `event_pipe` should not signal.
+ assert_eq!(wait_context.wait_timeout(Duration::ZERO).unwrap().len(), 0);
+ }
+}
diff --git a/devices/src/virtio/video/decoder/backend/vda.rs b/devices/src/virtio/video/decoder/backend/vda.rs
index 99ce7cbdf..e1d815c1a 100644
--- a/devices/src/virtio/video/decoder/backend/vda.rs
+++ b/devices/src/virtio/video/decoder/backend/vda.rs
@@ -2,15 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use base::{error, RawDescriptor};
+use anyhow::anyhow;
+use base::{error, warn, AsRawDescriptor, IntoRawDescriptor};
+use std::collections::btree_map::Entry;
+use std::collections::BTreeMap;
use std::convert::TryFrom;
use libvda::decode::Event as LibvdaEvent;
use crate::virtio::video::{
- decoder::backend::*,
+ decoder::{backend::*, Capability},
error::{VideoError, VideoResult},
- format::{Format, Rect},
+ format::*,
};
impl TryFrom<Format> for libvda::Profile {
@@ -46,8 +49,8 @@ impl TryFrom<Format> for libvda::PixelFormat {
impl From<&FramePlane> for libvda::FramePlane {
fn from(plane: &FramePlane) -> Self {
libvda::FramePlane {
- offset: plane.offset,
- stride: plane.stride,
+ offset: plane.offset as i32,
+ stride: plane.stride as i32,
}
}
}
@@ -59,7 +62,7 @@ impl From<libvda::decode::Event> for DecoderEvent {
fn vda_response_to_result(resp: libvda::decode::Response) -> VideoResult<()> {
match resp {
libvda::decode::Response::Success => Ok(()),
- resp => Err(VideoError::VdaFailure(resp)),
+ resp => Err(VideoError::BackendFailure(anyhow!("VDA failure: {}", resp))),
}
}
@@ -103,9 +106,9 @@ impl From<libvda::decode::Event> for DecoderEvent {
LibvdaEvent::NotifyEndOfBitstreamBuffer { bitstream_id } => {
DecoderEvent::NotifyEndOfBitstreamBuffer(bitstream_id)
}
- LibvdaEvent::NotifyError(resp) => {
- DecoderEvent::NotifyError(VideoError::VdaFailure(resp))
- }
+ LibvdaEvent::NotifyError(resp) => DecoderEvent::NotifyError(
+ VideoError::BackendFailure(anyhow!("VDA failure: {}", resp)),
+ ),
LibvdaEvent::ResetResponse(resp) => {
DecoderEvent::ResetCompleted(vda_response_to_result(resp))
}
@@ -116,79 +119,230 @@ impl From<libvda::decode::Event> for DecoderEvent {
}
}
-pub struct LibvdaSession<'a> {
- session: libvda::decode::Session<'a>,
+// Used by DecoderSession::get_capabilities().
+fn from_pixel_format(
+ fmt: &libvda::PixelFormat,
+ mask: u64,
+ width_range: FormatRange,
+ height_range: FormatRange,
+) -> FormatDesc {
+ let format = match fmt {
+ libvda::PixelFormat::NV12 => Format::NV12,
+ libvda::PixelFormat::YV12 => Format::YUV420,
+ };
+
+ let frame_formats = vec![FrameFormat {
+ width: width_range,
+ height: height_range,
+ bitrates: Vec::new(),
+ }];
+
+ FormatDesc {
+ mask,
+ format,
+ frame_formats,
+ }
+}
+
+pub struct VdaDecoderSession {
+ vda_session: libvda::decode::Session,
+ format: Option<libvda::PixelFormat>,
}
-impl<'a> DecoderSession for LibvdaSession<'a> {
- fn set_output_buffer_count(&self, count: usize) -> VideoResult<()> {
- Ok(self.session.set_output_buffer_count(count)?)
+impl DecoderSession for VdaDecoderSession {
+ fn set_output_parameters(&mut self, buffer_count: usize, format: Format) -> VideoResult<()> {
+ self.format = Some(libvda::PixelFormat::try_from(format)?);
+ Ok(self.vda_session.set_output_buffer_count(buffer_count)?)
}
fn decode(
- &self,
+ &mut self,
bitstream_id: i32,
- descriptor: RawDescriptor,
+ resource: GuestResourceHandle,
offset: u32,
bytes_used: u32,
) -> VideoResult<()> {
- Ok(self
- .session
- .decode(bitstream_id, descriptor, offset, bytes_used)?)
+ let handle = match resource {
+ GuestResourceHandle::VirtioObject(handle) => handle,
+ _ => {
+ return Err(VideoError::BackendFailure(anyhow!(
+ "VDA backend only supports virtio object resources"
+ )))
+ }
+ };
+
+ Ok(self.vda_session.decode(
+ bitstream_id,
+ // Steal the descriptor of the resource, as libvda will close it.
+ handle.desc.into_raw_descriptor(),
+ offset,
+ bytes_used,
+ )?)
+ }
+
+ fn flush(&mut self) -> VideoResult<()> {
+ Ok(self.vda_session.flush()?)
}
- fn flush(&self) -> VideoResult<()> {
- Ok(self.session.flush()?)
+ fn reset(&mut self) -> VideoResult<()> {
+ Ok(self.vda_session.reset()?)
}
- fn reset(&self) -> VideoResult<()> {
- Ok(self.session.reset()?)
+ fn clear_output_buffers(&mut self) -> VideoResult<()> {
+ Ok(())
}
- fn event_pipe(&self) -> &std::fs::File {
- self.session.pipe()
+ fn event_pipe(&self) -> &dyn AsRawDescriptor {
+ self.vda_session.pipe()
}
fn use_output_buffer(
- &self,
+ &mut self,
picture_buffer_id: i32,
- format: Format,
- output_buffer: RawDescriptor,
- planes: &[FramePlane],
- modifier: u64,
+ resource: GuestResource,
) -> VideoResult<()> {
- let vda_planes: Vec<libvda::FramePlane> = planes.into_iter().map(Into::into).collect();
- Ok(self.session.use_output_buffer(
+ let handle = match resource.handle {
+ GuestResourceHandle::VirtioObject(handle) => handle,
+ _ => {
+ return Err(VideoError::BackendFailure(anyhow!(
+ "VDA backend only supports virtio object resources"
+ )))
+ }
+ };
+ let vda_planes: Vec<libvda::FramePlane> = resource.planes.iter().map(Into::into).collect();
+
+ Ok(self.vda_session.use_output_buffer(
picture_buffer_id,
- libvda::PixelFormat::try_from(format)?,
- output_buffer,
+ self.format.ok_or(VideoError::BackendFailure(anyhow!(
+ "set_output_parameters() must be called before use_output_buffer()"
+ )))?,
+ // Steal the descriptor of the resource, as libvda will close it.
+ handle.desc.into_raw_descriptor(),
&vda_planes,
- modifier,
+ handle.modifier,
)?)
}
- fn reuse_output_buffer(&self, picture_buffer_id: i32) -> VideoResult<()> {
- Ok(self.session.reuse_output_buffer(picture_buffer_id)?)
+ fn reuse_output_buffer(&mut self, picture_buffer_id: i32) -> VideoResult<()> {
+ Ok(self.vda_session.reuse_output_buffer(picture_buffer_id)?)
}
fn read_event(&mut self) -> VideoResult<DecoderEvent> {
- self.session
+ self.vda_session
.read_event()
.map(Into::into)
.map_err(Into::into)
}
}
-impl<'a> DecoderBackend for &'a libvda::decode::VdaInstance {
- type Session = LibvdaSession<'a>;
+/// A VDA decoder backend that can be passed to `Decoder::new` in order to create a working decoder.
+pub struct LibvdaDecoder(libvda::decode::VdaInstance);
- fn new_session(&self, format: Format) -> VideoResult<Self::Session> {
+impl LibvdaDecoder {
+ /// Create a decoder backend instance that can be used to instantiate an decoder.
+ pub fn new(backend_type: libvda::decode::VdaImplType) -> VideoResult<Self> {
+ Ok(Self(libvda::decode::VdaInstance::new(backend_type)?))
+ }
+}
+
+impl DecoderBackend for LibvdaDecoder {
+ type Session = VdaDecoderSession;
+
+ fn new_session(&mut self, format: Format) -> VideoResult<Self::Session> {
let profile = libvda::Profile::try_from(format)?;
- let session = self.open_session(profile).map_err(|e| {
- error!("failed to open a session for {:?}: {}", format, e);
- VideoError::InvalidOperation
- })?;
- Ok(LibvdaSession { session })
+ Ok(VdaDecoderSession {
+ vda_session: self.0.open_session(profile).map_err(|e| {
+ error!("failed to open a session for {:?}: {}", format, e);
+ VideoError::InvalidOperation
+ })?,
+ format: None,
+ })
+ }
+
+ fn get_capabilities(&self) -> Capability {
+ let caps = libvda::decode::VdaInstance::get_capabilities(&self.0);
+
+ // Raise the first |# of supported raw formats|-th bits because we can assume that any
+ // combination of (a coded format, a raw format) is valid in Chrome.
+ let mask = !(u64::max_value() << caps.output_formats.len());
+
+ let mut in_fmts = vec![];
+ let mut profiles: BTreeMap<Format, Vec<Profile>> = Default::default();
+ for fmt in caps.input_formats.iter() {
+ match Profile::from_libvda_profile(fmt.profile) {
+ Some(profile) => {
+ let format = profile.to_format();
+ in_fmts.push(FormatDesc {
+ mask,
+ format,
+ frame_formats: vec![FrameFormat {
+ width: FormatRange {
+ min: fmt.min_width,
+ max: fmt.max_width,
+ step: 1,
+ },
+ height: FormatRange {
+ min: fmt.min_height,
+ max: fmt.max_height,
+ step: 1,
+ },
+ bitrates: Vec::new(),
+ }],
+ });
+ match profiles.entry(format) {
+ Entry::Occupied(mut e) => e.get_mut().push(profile),
+ Entry::Vacant(e) => {
+ e.insert(vec![profile]);
+ }
+ }
+ }
+ None => {
+ warn!(
+ "No virtio-video equivalent for libvda profile, skipping: {:?}",
+ fmt.profile
+ );
+ }
+ }
+ }
+
+ let levels: BTreeMap<Format, Vec<Level>> = if profiles.contains_key(&Format::H264) {
+ // We only support Level 1.0 for H.264.
+ vec![(Format::H264, vec![Level::H264_1_0])]
+ .into_iter()
+ .collect()
+ } else {
+ Default::default()
+ };
+
+ // Prepare {min, max} of {width, height}.
+ // While these values are associated with each input format in libvda,
+ // they are associated with each output format in virtio-video protocol.
+ // Thus, we compute max of min values and min of max values here.
+ let min_width = caps.input_formats.iter().map(|fmt| fmt.min_width).max();
+ let max_width = caps.input_formats.iter().map(|fmt| fmt.max_width).min();
+ let min_height = caps.input_formats.iter().map(|fmt| fmt.min_height).max();
+ let max_height = caps.input_formats.iter().map(|fmt| fmt.max_height).min();
+ let width_range = FormatRange {
+ min: min_width.unwrap_or(0),
+ max: max_width.unwrap_or(0),
+ step: 1,
+ };
+ let height_range = FormatRange {
+ min: min_height.unwrap_or(0),
+ max: max_height.unwrap_or(0),
+ step: 1,
+ };
+
+ // Raise the first |# of supported coded formats|-th bits because we can assume that any
+ // combination of (a coded format, a raw format) is valid in Chrome.
+ let mask = !(u64::max_value() << caps.input_formats.len());
+ let out_fmts = caps
+ .output_formats
+ .iter()
+ .map(|fmt| from_pixel_format(fmt, mask, width_range, height_range))
+ .collect();
+
+ Capability::new(in_fmts, out_fmts, profiles, levels)
}
}
diff --git a/devices/src/virtio/video/decoder/capability.rs b/devices/src/virtio/video/decoder/capability.rs
index c0c9035a6..9c635609b 100644
--- a/devices/src/virtio/video/decoder/capability.rs
+++ b/devices/src/virtio/video/decoder/capability.rs
@@ -4,40 +4,14 @@
//! Capablities of the virtio video decoder device.
-use base::warn;
-use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
use crate::virtio::video::control::*;
use crate::virtio::video::format::*;
-fn from_pixel_format(
- fmt: &libvda::PixelFormat,
- mask: u64,
- width_range: FormatRange,
- height_range: FormatRange,
-) -> FormatDesc {
- let format = match fmt {
- libvda::PixelFormat::NV12 => Format::NV12,
- libvda::PixelFormat::YV12 => Format::YUV420,
- };
-
- let frame_formats = vec![FrameFormat {
- width: width_range,
- height: height_range,
- bitrates: Vec::new(),
- }];
-
- FormatDesc {
- mask,
- format,
- frame_formats,
- }
-}
-
pub struct Capability {
- pub in_fmts: Vec<FormatDesc>,
- pub out_fmts: Vec<FormatDesc>,
+ in_fmts: Vec<FormatDesc>,
+ out_fmts: Vec<FormatDesc>,
// Stores supporterd profiles and levels for each format.
profiles: BTreeMap<Format, Vec<Profile>>,
@@ -45,76 +19,14 @@ pub struct Capability {
}
impl Capability {
- pub fn new(caps: &libvda::decode::Capabilities) -> Self {
- // Raise the first |# of supported raw formats|-th bits because we can assume that any
- // combination of (a coded format, a raw format) is valid in Chrome.
- let mask = !(u64::max_value() << caps.output_formats.len());
-
- let mut in_fmts = vec![];
- let mut profiles: BTreeMap<Format, Vec<Profile>> = Default::default();
- for fmt in caps.input_formats.iter() {
- match Profile::from_libvda_profile(fmt.profile) {
- Some(profile) => {
- let format = profile.to_format();
- in_fmts.push(FormatDesc {
- mask,
- format,
- frame_formats: vec![Default::default()],
- });
- match profiles.entry(format) {
- Entry::Occupied(mut e) => e.get_mut().push(profile),
- Entry::Vacant(e) => {
- e.insert(vec![profile]);
- }
- }
- }
- None => {
- warn!(
- "No virtio-video equivalent for libvda profile, skipping: {:?}",
- fmt.profile
- );
- }
- }
- }
-
- let levels: BTreeMap<Format, Vec<Level>> = if profiles.contains_key(&Format::H264) {
- // We only support Level 1.0 for H.264.
- vec![(Format::H264, vec![Level::H264_1_0])]
- .into_iter()
- .collect()
- } else {
- Default::default()
- };
-
- // Prepare {min, max} of {width, height}.
- // While these values are associated with each input format in libvda,
- // they are associated with each output format in virtio-video protocol.
- // Thus, we compute max of min values and min of max values here.
- let min_width = caps.input_formats.iter().map(|fmt| fmt.min_width).max();
- let max_width = caps.input_formats.iter().map(|fmt| fmt.max_width).min();
- let min_height = caps.input_formats.iter().map(|fmt| fmt.min_height).max();
- let max_height = caps.input_formats.iter().map(|fmt| fmt.max_height).min();
- let width_range = FormatRange {
- min: min_width.unwrap_or(0),
- max: max_width.unwrap_or(0),
- step: 1,
- };
- let height_range = FormatRange {
- min: min_height.unwrap_or(0),
- max: max_height.unwrap_or(0),
- step: 1,
- };
-
- // Raise the first |# of supported coded formats|-th bits because we can assume that any
- // combination of (a coded format, a raw format) is valid in Chrome.
- let mask = !(u64::max_value() << caps.input_formats.len());
- let out_fmts = caps
- .output_formats
- .iter()
- .map(|fmt| from_pixel_format(fmt, mask, width_range, height_range))
- .collect();
-
- Capability {
+ // Make this method pub(super) so backends can create capabilities.
+ pub(super) fn new(
+ in_fmts: Vec<FormatDesc>,
+ out_fmts: Vec<FormatDesc>,
+ profiles: BTreeMap<Format, Vec<Profile>>,
+ levels: BTreeMap<Format, Vec<Level>>,
+ ) -> Self {
+ Self {
in_fmts,
out_fmts,
profiles,
@@ -122,6 +34,14 @@ impl Capability {
}
}
+ pub fn input_formats(&self) -> &Vec<FormatDesc> {
+ &self.in_fmts
+ }
+
+ pub fn output_formats(&self) -> &Vec<FormatDesc> {
+ &self.out_fmts
+ }
+
pub fn query_control(&self, t: &QueryCtrlType) -> Option<QueryCtrlResponse> {
use QueryCtrlType::*;
match *t {
diff --git a/devices/src/virtio/video/decoder/mod.rs b/devices/src/virtio/video/decoder/mod.rs
index 9545bb9cb..e7c1b0916 100644
--- a/devices/src/virtio/video/decoder/mod.rs
+++ b/devices/src/virtio/video/decoder/mod.rs
@@ -6,12 +6,11 @@
use std::collections::btree_map::Entry;
use std::collections::{BTreeMap, BTreeSet, VecDeque};
-use std::convert::TryInto;
use backend::*;
-use base::{error, IntoRawDescriptor, Tube, WaitContext};
+use base::{error, Tube, WaitContext};
+use vm_memory::GuestMemory;
-use crate::virtio::resource_bridge::{self, BufferInfo, ResourceInfo, ResourceRequest};
use crate::virtio::video::async_cmd_desc_map::AsyncCmdDescMap;
use crate::virtio::video::command::{QueueType, VideoCmd};
use crate::virtio::video::control::{CtrlType, CtrlVal, QueryCtrlType};
@@ -21,9 +20,10 @@ use crate::virtio::video::event::*;
use crate::virtio::video::format::*;
use crate::virtio::video::params::Params;
use crate::virtio::video::protocol;
+use crate::virtio::video::resource::*;
use crate::virtio::video::response::CmdResponse;
-mod backend;
+pub mod backend;
mod capability;
use capability::*;
@@ -42,7 +42,6 @@ type OutputResourceId = u32;
// we don't need this value and can pass OutputResourceId to Chrome directly.
type FrameBufferId = i32;
-type ResourceHandle = u32;
type Timestamp = u64;
// The result of OutputResources.queue_resource().
@@ -58,7 +57,7 @@ struct InputResources {
timestamp_to_res_id: BTreeMap<Timestamp, InputResourceId>,
// InputResourceId -> ResourceHandle
- res_id_to_res_handle: BTreeMap<InputResourceId, ResourceHandle>,
+ res_id_to_res_handle: BTreeMap<InputResourceId, GuestResource>,
// InputResourceId -> data offset
res_id_to_offset: BTreeMap<InputResourceId, u32>,
@@ -79,16 +78,16 @@ struct OutputResources {
// destroyed.
eos_resource_id: Option<OutputResourceId>,
- // This is a flag that shows whether the device's set_output_buffer_count is called.
- // This will be set to true when ResourceCreate for OutputBuffer is called for the first time.
+ // This is a flag that shows whether the device's set_output_parameters has called.
+ // This will be set to true when ResourceQueue for OutputBuffer is called for the first time.
//
// TODO(b/1518105): This field is added as a hack because the current virtio-video v3 spec
// doesn't have a way to send a number of frame buffers the guest provides.
// Once we have the way in the virtio-video protocol, we should remove this flag.
- is_output_buffer_count_set: bool,
+ output_params_set: bool,
// OutputResourceId -> ResourceHandle
- res_id_to_res_handle: BTreeMap<OutputResourceId, ResourceHandle>,
+ res_id_to_res_handle: BTreeMap<OutputResourceId, GuestResource>,
}
impl OutputResources {
@@ -150,24 +149,25 @@ impl OutputResources {
self.queued_res_ids.take(&self.eos_resource_id?)
}
- fn set_output_buffer_count(&mut self) -> bool {
- if !self.is_output_buffer_count_set {
- self.is_output_buffer_count_set = true;
+ fn output_params_set(&mut self) -> bool {
+ if !self.output_params_set {
+ self.output_params_set = true;
return true;
}
false
}
}
-struct PictureReadyEvent {
- picture_buffer_id: i32,
- bitstream_id: i32,
- visible_rect: Rect,
+enum PendingResponse {
+ PictureReady {
+ picture_buffer_id: i32,
+ bitstream_id: i32,
+ },
+ FlushCompleted,
}
// Context is associated with one `DecoderSession`, which corresponds to one stream from the
// virtio-video's point of view.
-#[derive(Default)]
struct Context<S: DecoderSession> {
stream_id: StreamId,
@@ -180,116 +180,134 @@ struct Context<S: DecoderSession> {
// Set the flag when we ask the decoder reset, and unset when the reset is done.
is_resetting: bool,
- pending_ready_pictures: VecDeque<PictureReadyEvent>,
+ pending_responses: VecDeque<PendingResponse>,
session: Option<S>,
}
impl<S: DecoderSession> Context<S> {
- fn new(stream_id: StreamId, format: Format) -> Self {
+ fn new(
+ stream_id: StreamId,
+ format: Format,
+ in_resource_type: ResourceType,
+ out_resource_type: ResourceType,
+ ) -> Self {
+ const DEFAULT_WIDTH: u32 = 640;
+ const DEFAULT_HEIGHT: u32 = 480;
+ const DEFAULT_INPUT_BUFFER_SIZE: u32 = 1024 * 1024;
+
+ let out_plane_formats =
+ PlaneFormat::get_plane_layout(Format::NV12, DEFAULT_WIDTH, DEFAULT_HEIGHT).unwrap();
+
Context {
stream_id,
in_params: Params {
format: Some(format),
+ frame_width: DEFAULT_WIDTH,
+ frame_height: DEFAULT_HEIGHT,
+ resource_type: in_resource_type,
min_buffers: 1,
max_buffers: 32,
- plane_formats: vec![Default::default()],
+ plane_formats: vec![PlaneFormat {
+ plane_size: DEFAULT_INPUT_BUFFER_SIZE,
+ ..Default::default()
+ }],
+ ..Default::default()
+ },
+ out_params: Params {
+ format: Some(Format::NV12),
+ frame_width: DEFAULT_WIDTH,
+ frame_height: DEFAULT_HEIGHT,
+ resource_type: out_resource_type,
+ plane_formats: out_plane_formats,
..Default::default()
},
- out_params: Default::default(),
in_res: Default::default(),
out_res: Default::default(),
is_resetting: false,
- pending_ready_pictures: Default::default(),
+ pending_responses: Default::default(),
session: None,
}
}
- fn output_pending_pictures(&mut self) -> Vec<VideoEvtResponseType> {
- let mut responses = vec![];
- while let Some(async_response) = self.output_pending_picture() {
- responses.push(VideoEvtResponseType::AsyncCmd(async_response));
+ fn output_pending_responses(&mut self) -> Vec<VideoEvtResponseType> {
+ let mut event_responses = vec![];
+ while let Some(mut responses) = self.output_pending_response() {
+ event_responses.append(&mut responses);
}
- responses
+ event_responses
}
- fn output_pending_picture(&mut self) -> Option<AsyncCmdResponse> {
- let response = {
- let PictureReadyEvent {
+ fn output_pending_response(&mut self) -> Option<Vec<VideoEvtResponseType>> {
+ let responses = match self.pending_responses.front()? {
+ PendingResponse::PictureReady {
picture_buffer_id,
bitstream_id,
- visible_rect,
- } = self.pending_ready_pictures.front()?;
-
- let plane_size = ((visible_rect.right - visible_rect.left)
- * (visible_rect.bottom - visible_rect.top)) as u32;
- for fmt in self.out_params.plane_formats.iter_mut() {
- fmt.plane_size = plane_size;
- // We don't need to set `plane_formats[i].stride` for the decoder.
+ } => {
+ let resource_id = self
+ .out_res
+ .dequeue_frame_buffer(*picture_buffer_id, self.stream_id)?;
+
+ vec![VideoEvtResponseType::AsyncCmd(
+ AsyncCmdResponse::from_response(
+ AsyncCmdTag::Queue {
+ stream_id: self.stream_id,
+ queue_type: QueueType::Output,
+ resource_id,
+ },
+ CmdResponse::ResourceQueue {
+ // Conversion from sec to nsec.
+ timestamp: (*bitstream_id as u64) * 1_000_000_000,
+ // TODO(b/149725148): Set buffer flags once libvda exposes them.
+ flags: 0,
+ // `size` is only used for the encoder.
+ size: 0,
+ },
+ ),
+ )]
}
-
- let resource_id = self
- .out_res
- .dequeue_frame_buffer(*picture_buffer_id, self.stream_id)?;
-
- AsyncCmdResponse::from_response(
- AsyncCmdTag::Queue {
+ PendingResponse::FlushCompleted => {
+ let eos_resource_id = self.out_res.dequeue_eos_resource_id()?;
+ let eos_tag = AsyncCmdTag::Queue {
stream_id: self.stream_id,
queue_type: QueueType::Output,
- resource_id,
- },
- CmdResponse::ResourceQueue {
- // Conversion from sec to nsec.
- timestamp: (*bitstream_id as u64) * 1_000_000_000,
- // TODO(b/149725148): Set buffer flags once libvda exposes them.
- flags: 0,
- // `size` is only used for the encoder.
+ resource_id: eos_resource_id,
+ };
+ let eos_response = CmdResponse::ResourceQueue {
+ timestamp: 0,
+ flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_EOS,
size: 0,
- },
- )
+ };
+ vec![
+ VideoEvtResponseType::AsyncCmd(AsyncCmdResponse::from_response(
+ eos_tag,
+ eos_response,
+ )),
+ VideoEvtResponseType::AsyncCmd(AsyncCmdResponse::from_response(
+ AsyncCmdTag::Drain {
+ stream_id: self.stream_id,
+ },
+ CmdResponse::NoData,
+ )),
+ ]
+ }
};
- self.pending_ready_pictures.pop_front().unwrap();
+ self.pending_responses.pop_front().unwrap();
- Some(response)
+ Some(responses)
}
- fn get_resource_info(
- &self,
+ fn register_resource(
+ &mut self,
queue_type: QueueType,
- res_bridge: &Tube,
resource_id: u32,
- ) -> VideoResult<BufferInfo> {
- let res_id_to_res_handle = match queue_type {
- QueueType::Input => &self.in_res.res_id_to_res_handle,
- QueueType::Output => &self.out_res.res_id_to_res_handle,
- };
-
- let handle = res_id_to_res_handle.get(&resource_id).copied().ok_or(
- VideoError::InvalidResourceId {
- stream_id: self.stream_id,
- resource_id,
- },
- )?;
- match resource_bridge::get_resource_info(
- res_bridge,
- ResourceRequest::GetBuffer { id: handle },
- ) {
- Ok(ResourceInfo::Buffer(buffer_info)) => Ok(buffer_info),
- Ok(_) => Err(VideoError::InvalidArgument),
- Err(e) => Err(VideoError::ResourceBridgeFailure(e)),
- }
- }
-
- fn register_buffer(&mut self, queue_type: QueueType, resource_id: u32, uuid: &u128) {
- // TODO(stevensd): `Virtio3DBackend::resource_assign_uuid` is currently implemented to use
- // 32-bits resource_handles as UUIDs. Once it starts using real UUIDs, we need to update
- // this conversion.
- let handle = TryInto::<u32>::try_into(*uuid).expect("uuid is larger than 32 bits");
+ resource: GuestResource,
+ ) {
let res_id_to_res_handle = match queue_type {
QueueType::Input => &mut self.in_res.res_id_to_res_handle,
QueueType::Output => &mut self.out_res.res_id_to_res_handle,
};
- res_id_to_res_handle.insert(resource_id, handle);
+ res_id_to_res_handle.insert(resource_id, resource);
}
/*
@@ -306,18 +324,16 @@ impl<S: DecoderSession> Context<S> {
// We only support NV12.
let format = Some(Format::NV12);
- let rect_width: u32 = (visible_rect.right - visible_rect.left) as u32;
- let rect_height: u32 = (visible_rect.bottom - visible_rect.top) as u32;
+ let plane_formats =
+ PlaneFormat::get_plane_layout(Format::NV12, width as u32, height as u32).unwrap();
- let plane_size = rect_width * rect_height;
- let stride = rect_width;
- let plane_formats = vec![
- PlaneFormat { plane_size, stride },
- PlaneFormat { plane_size, stride },
- ];
+ self.in_params.frame_width = width as u32;
+ self.in_params.frame_height = height as u32;
self.out_params = Params {
format,
+ // The resource type is not changed by a provide picture buffers event.
+ resource_type: self.out_params.resource_type,
// Note that rect_width is sometimes smaller.
frame_width: width as u32,
frame_height: height as u32,
@@ -327,8 +343,8 @@ impl<S: DecoderSession> Context<S> {
crop: Crop {
left: visible_rect.left as u32,
top: visible_rect.top as u32,
- width: rect_width,
- height: rect_height,
+ width: (visible_rect.right - visible_rect.left) as u32,
+ height: (visible_rect.bottom - visible_rect.top) as u32,
},
plane_formats,
// No need to set `frame_rate`, as it's only for the encoder.
@@ -356,12 +372,6 @@ struct ContextMap<S: DecoderSession> {
}
impl<S: DecoderSession> ContextMap<S> {
- fn new() -> Self {
- ContextMap {
- map: Default::default(),
- }
- }
-
fn insert(&mut self, ctx: Context<S>) -> VideoResult<()> {
match self.map.entry(ctx.stream_id) {
Entry::Vacant(e) => {
@@ -390,22 +400,45 @@ impl<S: DecoderSession> ContextMap<S> {
}
}
+impl<S: DecoderSession> Default for ContextMap<S> {
+ fn default() -> Self {
+ Self {
+ map: Default::default(),
+ }
+ }
+}
+
/// Represents information of a decoder backed by a `DecoderBackend`.
pub struct Decoder<D: DecoderBackend> {
decoder: D,
capability: Capability,
contexts: ContextMap<D::Session>,
+ resource_bridge: Tube,
+ mem: GuestMemory,
}
impl<'a, D: DecoderBackend> Decoder<D> {
+ /// Build a new decoder using the provided `backend`.
+ pub fn new(backend: D, resource_bridge: Tube, mem: GuestMemory) -> Self {
+ let capability = backend.get_capabilities();
+
+ Self {
+ decoder: backend,
+ capability,
+ contexts: Default::default(),
+ resource_bridge,
+ mem,
+ }
+ }
+
/*
* Functions processing virtio-video commands.
*/
fn query_capabilities(&self, queue_type: QueueType) -> CmdResponse {
let descs = match queue_type {
- QueueType::Input => self.capability.in_fmts.clone(),
- QueueType::Output => self.capability.out_fmts.clone(),
+ QueueType::Input => self.capability.input_formats().clone(),
+ QueueType::Output => self.capability.output_formats().clone(),
};
CmdResponse::QueryCapability(descs)
@@ -415,13 +448,19 @@ impl<'a, D: DecoderBackend> Decoder<D> {
&mut self,
stream_id: StreamId,
coded_format: Format,
+ input_resource_type: ResourceType,
+ output_resource_type: ResourceType,
) -> VideoResult<VideoCmdResponseType> {
// Create an instance of `Context`.
// Note that the `DecoderSession` will be created not here but at the first call of
// `ResourceCreate`. This is because we need to fix a coded format for it, which
// will be set by `SetParams`.
- self.contexts
- .insert(Context::new(stream_id, coded_format))?;
+ self.contexts.insert(Context::new(
+ stream_id,
+ coded_format,
+ input_resource_type,
+ output_resource_type,
+ ))?;
Ok(VideoCmdResponseType::Sync(CmdResponse::NoData))
}
@@ -432,7 +471,7 @@ impl<'a, D: DecoderBackend> Decoder<D> {
}
fn create_session(
- decoder: &D,
+ decoder: &mut D,
wait_ctx: &WaitContext<Token>,
ctx: &Context<D::Session>,
stream_id: StreamId,
@@ -467,7 +506,7 @@ impl<'a, D: DecoderBackend> Decoder<D> {
queue_type: QueueType,
resource_id: ResourceId,
plane_offsets: Vec<u32>,
- uuid: u128,
+ plane_entries: Vec<Vec<UnresolvedResourceEntry>>,
) -> VideoResult<VideoCmdResponseType> {
let ctx = self.contexts.get_mut(&stream_id)?;
@@ -475,14 +514,57 @@ impl<'a, D: DecoderBackend> Decoder<D> {
// called here.
if ctx.session.is_none() {
ctx.session = Some(Self::create_session(
- &self.decoder,
+ &mut self.decoder,
wait_ctx,
ctx,
stream_id,
)?);
}
- ctx.register_buffer(queue_type, resource_id, &uuid);
+ // We only support single-buffer resources for now.
+ let entries = if plane_entries.len() != 1 {
+ return Err(VideoError::InvalidArgument);
+ } else {
+ // unwrap() is safe because we just tested that `plane_entries` had exactly one element.
+ plane_entries.get(0).unwrap()
+ };
+
+ // Now try to resolve our resource.
+ let (resource_type, plane_formats) = match queue_type {
+ QueueType::Input => (ctx.in_params.resource_type, &ctx.in_params.plane_formats),
+ QueueType::Output => (ctx.out_params.resource_type, &ctx.out_params.plane_formats),
+ };
+
+ let resource = match resource_type {
+ ResourceType::VirtioObject => {
+ // Virtio object resources only have one entry.
+ if entries.len() != 1 {
+ return Err(VideoError::InvalidArgument);
+ }
+ GuestResource::from_virtio_object_entry(
+ // Safe because we confirmed the correct type for the resource.
+ // unwrap() is also safe here because we just tested above that `entries` had
+ // exactly one element.
+ unsafe { entries.get(0).unwrap().object },
+ &self.resource_bridge,
+ )
+ .map_err(|_| VideoError::InvalidArgument)?
+ }
+ ResourceType::GuestPages => GuestResource::from_virtio_guest_mem_entry(
+ // Safe because we confirmed the correct type for the resource.
+ unsafe {
+ std::slice::from_raw_parts(
+ entries.as_ptr() as *const protocol::virtio_video_mem_entry,
+ entries.len(),
+ )
+ },
+ &self.mem,
+ plane_formats,
+ )
+ .map_err(|_| VideoError::InvalidArgument)?,
+ };
+
+ ctx.register_resource(queue_type, resource_id, resource);
if queue_type == QueueType::Input {
ctx.in_res
@@ -528,26 +610,26 @@ impl<'a, D: DecoderBackend> Decoder<D> {
fn queue_input_resource(
&mut self,
- resource_bridge: &Tube,
stream_id: StreamId,
resource_id: ResourceId,
timestamp: u64,
data_sizes: Vec<u32>,
) -> VideoResult<VideoCmdResponseType> {
let ctx = self.contexts.get_mut(&stream_id)?;
- let session = ctx.session.as_ref().ok_or(VideoError::InvalidOperation)?;
if data_sizes.len() != 1 {
error!("num_data_sizes must be 1 but {}", data_sizes.len());
return Err(VideoError::InvalidOperation);
}
- // Take an ownership of this file by `into_raw_descriptor()` as this file will be closed
- // by the `DecoderBackend`.
- let fd = ctx
- .get_resource_info(QueueType::Input, resource_bridge, resource_id)?
- .file
- .into_raw_descriptor();
+ let session = ctx.session.as_mut().ok_or(VideoError::InvalidOperation)?;
+
+ let resource = ctx.in_res.res_id_to_res_handle.get(&resource_id).ok_or(
+ VideoError::InvalidResourceId {
+ stream_id,
+ resource_id,
+ },
+ )?;
// Register a mapping of timestamp to resource_id
if let Some(old_resource_id) = ctx
@@ -578,7 +660,10 @@ impl<'a, D: DecoderBackend> Decoder<D> {
let ts_sec: i32 = (timestamp / 1_000_000_000) as i32;
session.decode(
ts_sec,
- fd,
+ resource
+ .handle
+ .try_clone()
+ .map_err(|_| VideoError::InvalidParameter)?,
offset,
data_sizes[0], // bytes_used
)?;
@@ -592,12 +677,10 @@ impl<'a, D: DecoderBackend> Decoder<D> {
fn queue_output_resource(
&mut self,
- resource_bridge: &Tube,
stream_id: StreamId,
resource_id: ResourceId,
) -> VideoResult<VideoCmdResponseType> {
let ctx = self.contexts.get_mut(&stream_id)?;
- let session = ctx.session.as_ref().ok_or(VideoError::InvalidOperation)?;
// Check if the current pixel format is set to NV12.
match ctx.out_params.format {
@@ -620,42 +703,37 @@ impl<'a, D: DecoderBackend> Decoder<D> {
// Don't enqueue this resource to the host.
Ok(())
}
- QueueOutputResourceResult::Reused(buffer_id) => session.reuse_output_buffer(buffer_id),
+ QueueOutputResourceResult::Reused(buffer_id) => ctx
+ .session
+ .as_mut()
+ .ok_or(VideoError::InvalidOperation)?
+ .reuse_output_buffer(buffer_id),
QueueOutputResourceResult::Registered(buffer_id) => {
- let resource_info =
- ctx.get_resource_info(QueueType::Output, resource_bridge, resource_id)?;
- let planes = vec![
- FramePlane {
- offset: resource_info.planes[0].offset as i32,
- stride: resource_info.planes[0].stride as i32,
- },
- FramePlane {
- offset: resource_info.planes[1].offset as i32,
- stride: resource_info.planes[1].stride as i32,
- },
- ];
+ // Take full ownership of the output resource, since we will only import it once
+ // into the backend.
+ let resource = ctx
+ .out_res
+ .res_id_to_res_handle
+ .remove(&resource_id)
+ .ok_or(VideoError::InvalidResourceId {
+ stream_id,
+ resource_id,
+ })?;
+
+ let session = ctx.session.as_mut().ok_or(VideoError::InvalidOperation)?;
// Set output_buffer_count before passing the first output buffer.
- if ctx.out_res.set_output_buffer_count() {
+ if ctx.out_res.output_params_set() {
const OUTPUT_BUFFER_COUNT: usize = 32;
// Set the buffer count to the maximum value.
// TODO(b/1518105): This is a hack due to the lack of way of telling a number of
// frame buffers explictly in virtio-video v3 RFC. Once we have the way,
// set_output_buffer_count should be called with a value passed by the guest.
- session.set_output_buffer_count(OUTPUT_BUFFER_COUNT)?;
+ session.set_output_parameters(OUTPUT_BUFFER_COUNT, Format::NV12)?;
}
- // Take ownership of this file by `into_raw_descriptor()` as this
- // file will be closed by libvda.
- let fd = resource_info.file.into_raw_descriptor();
- session.use_output_buffer(
- buffer_id as i32,
- Format::NV12,
- fd,
- &planes,
- resource_info.modifier,
- )
+ session.use_output_buffer(buffer_id as i32, resource)
}
}?;
Ok(VideoCmdResponseType::Async(AsyncCmdTag::Queue {
@@ -669,6 +747,7 @@ impl<'a, D: DecoderBackend> Decoder<D> {
&self,
stream_id: StreamId,
queue_type: QueueType,
+ is_ext: bool,
) -> VideoResult<VideoCmdResponseType> {
let ctx = self.contexts.get(&stream_id)?;
let params = match queue_type {
@@ -678,6 +757,7 @@ impl<'a, D: DecoderBackend> Decoder<D> {
Ok(VideoCmdResponseType::Sync(CmdResponse::GetParams {
queue_type,
params,
+ is_ext,
}))
}
@@ -686,6 +766,7 @@ impl<'a, D: DecoderBackend> Decoder<D> {
stream_id: StreamId,
queue_type: QueueType,
params: Params,
+ is_ext: bool,
) -> VideoResult<VideoCmdResponseType> {
let ctx = self.contexts.get_mut(&stream_id)?;
match queue_type {
@@ -698,9 +779,21 @@ impl<'a, D: DecoderBackend> Decoder<D> {
// Only a few parameters can be changed by the guest.
ctx.in_params.format = params.format;
ctx.in_params.plane_formats = params.plane_formats;
+ // The resource type can only be changed through the SET_PARAMS_EXT command.
+ if is_ext {
+ ctx.in_params.resource_type = params.resource_type;
+ }
}
QueueType::Output => {
- // The guest cannot update parameters for output queue in the decoder.
+ // The guest can only change the resource type of the output queue if no resource
+ // has been imported yet.
+ if ctx.out_res.output_params_set {
+ error!("parameter for output cannot be changed once resources are imported");
+ return Err(VideoError::InvalidParameter);
+ }
+ if is_ext {
+ ctx.out_params.resource_type = params.resource_type;
+ }
}
};
Ok(VideoCmdResponseType::Sync(CmdResponse::NoData))
@@ -765,9 +858,9 @@ impl<'a, D: DecoderBackend> Decoder<D> {
fn drain_stream(&mut self, stream_id: StreamId) -> VideoResult<VideoCmdResponseType> {
self.contexts
- .get(&stream_id)?
+ .get_mut(&stream_id)?
.session
- .as_ref()
+ .as_mut()
.ok_or(VideoError::InvalidOperation)?
.flush()?;
Ok(VideoCmdResponseType::Async(AsyncCmdTag::Drain {
@@ -781,7 +874,7 @@ impl<'a, D: DecoderBackend> Decoder<D> {
queue_type: QueueType,
) -> VideoResult<VideoCmdResponseType> {
let ctx = self.contexts.get_mut(&stream_id)?;
- let session = ctx.session.as_ref().ok_or(VideoError::InvalidOperation)?;
+ let session = ctx.session.as_mut().ok_or(VideoError::InvalidOperation)?;
// TODO(b/153406792): Though QUEUE_CLEAR is defined as a per-queue command in the
// specification, the VDA's `Reset()` clears the input buffers and may (or may not) drop
@@ -794,13 +887,14 @@ impl<'a, D: DecoderBackend> Decoder<D> {
QueueType::Input => {
session.reset()?;
ctx.is_resetting = true;
- ctx.pending_ready_pictures.clear();
+ ctx.pending_responses.clear();
Ok(VideoCmdResponseType::Async(AsyncCmdTag::Clear {
stream_id,
queue_type: QueueType::Input,
}))
}
QueueType::Output => {
+ session.clear_output_buffers()?;
ctx.out_res.queued_res_ids.clear();
Ok(VideoCmdResponseType::Sync(CmdResponse::NoData))
}
@@ -813,7 +907,6 @@ impl<D: DecoderBackend> Device for Decoder<D> {
&mut self,
cmd: VideoCmd,
wait_ctx: &WaitContext<Token>,
- resource_bridge: &Tube,
) -> (
VideoCmdResponseType,
Option<(u32, Vec<VideoEvtResponseType>)>,
@@ -827,7 +920,14 @@ impl<D: DecoderBackend> Device for Decoder<D> {
StreamCreate {
stream_id,
coded_format,
- } => self.create_stream(stream_id, coded_format),
+ input_resource_type,
+ output_resource_type,
+ } => self.create_stream(
+ stream_id,
+ coded_format,
+ input_resource_type,
+ output_resource_type,
+ ),
StreamDestroy { stream_id } => {
self.destroy_stream(stream_id);
Ok(Sync(CmdResponse::NoData))
@@ -837,14 +937,14 @@ impl<D: DecoderBackend> Device for Decoder<D> {
queue_type,
resource_id,
plane_offsets,
- uuid,
+ plane_entries,
} => self.create_resource(
wait_ctx,
stream_id,
queue_type,
resource_id,
plane_offsets,
- uuid,
+ plane_entries,
),
ResourceDestroyAll {
stream_id,
@@ -856,23 +956,17 @@ impl<D: DecoderBackend> Device for Decoder<D> {
resource_id,
timestamp,
data_sizes,
- } => self.queue_input_resource(
- resource_bridge,
- stream_id,
- resource_id,
- timestamp,
- data_sizes,
- ),
+ } => self.queue_input_resource(stream_id, resource_id, timestamp, data_sizes),
ResourceQueue {
stream_id,
queue_type: QueueType::Output,
resource_id,
..
} => {
- let resp = self.queue_output_resource(resource_bridge, stream_id, resource_id);
+ let resp = self.queue_output_resource(stream_id, resource_id);
if resp.is_ok() {
if let Ok(ctx) = self.contexts.get_mut(&stream_id) {
- event_ret = Some((stream_id, ctx.output_pending_pictures()));
+ event_ret = Some((stream_id, ctx.output_pending_responses()));
}
}
resp
@@ -880,12 +974,14 @@ impl<D: DecoderBackend> Device for Decoder<D> {
GetParams {
stream_id,
queue_type,
- } => self.get_params(stream_id, queue_type),
+ is_ext,
+ } => self.get_params(stream_id, queue_type, is_ext),
SetParams {
stream_id,
queue_type,
params,
- } => self.set_params(stream_id, queue_type, params),
+ is_ext,
+ } => self.set_params(stream_id, queue_type, params, is_ext),
QueryControl { query_ctrl_type } => self.query_control(query_ctrl_type),
GetControl {
stream_id,
@@ -962,17 +1058,17 @@ impl<D: DecoderBackend> Device for Decoder<D> {
DecoderEvent::PictureReady {
picture_buffer_id, // FrameBufferId
bitstream_id, // timestamp in second
- visible_rect,
+ ..
} => {
if ctx.is_resetting {
vec![]
} else {
- ctx.pending_ready_pictures.push_back(PictureReadyEvent {
- picture_buffer_id,
- bitstream_id,
- visible_rect,
- });
- ctx.output_pending_pictures()
+ ctx.pending_responses
+ .push_back(PendingResponse::PictureReady {
+ picture_buffer_id,
+ bitstream_id,
+ });
+ ctx.output_pending_responses()
}
}
DecoderEvent::NotifyEndOfBitstreamBuffer(bitstream_id) => {
@@ -994,40 +1090,9 @@ impl<D: DecoderBackend> Device for Decoder<D> {
DecoderEvent::FlushCompleted(flush_result) => {
match flush_result {
Ok(()) => {
- let eos_resource_id = match ctx.out_res.dequeue_eos_resource_id() {
- Some(r) => r,
- None => {
- // TODO(b/168750131): Instead of trigger error, we should wait for
- // the next output buffer enqueued, then dequeue the buffer with
- // EOS flag.
- error!(
- "No EOS resource available on successful flush response (stream id {})",
- stream_id);
- return Some(vec![Event(VideoEvt {
- typ: EvtType::Error,
- stream_id,
- })]);
- }
- };
-
- let eos_tag = AsyncCmdTag::Queue {
- stream_id,
- queue_type: QueueType::Output,
- resource_id: eos_resource_id,
- };
-
- let eos_response = CmdResponse::ResourceQueue {
- timestamp: 0,
- flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_EOS,
- size: 0,
- };
- vec![
- AsyncCmd(AsyncCmdResponse::from_response(eos_tag, eos_response)),
- AsyncCmd(AsyncCmdResponse::from_response(
- AsyncCmdTag::Drain { stream_id },
- CmdResponse::NoData,
- )),
- ]
+ ctx.pending_responses
+ .push_back(PendingResponse::FlushCompleted);
+ ctx.output_pending_responses()
}
Err(error) => {
// TODO(b/151810591): If `resp` is `libvda::decode::Response::Canceled`,
@@ -1087,15 +1152,3 @@ impl<D: DecoderBackend> Device for Decoder<D> {
Some(event_responses)
}
}
-
-/// Create a new decoder instance using a Libvda decoder instance to perform
-/// the decoding.
-impl<'a> Decoder<&'a libvda::decode::VdaInstance> {
- pub fn new(vda: &'a libvda::decode::VdaInstance) -> Self {
- Decoder {
- decoder: vda,
- capability: Capability::new(vda.get_capabilities()),
- contexts: ContextMap::new(),
- }
- }
-}
diff --git a/devices/src/virtio/video/device.rs b/devices/src/virtio/video/device.rs
index d56700cba..cdc82adf8 100644
--- a/devices/src/virtio/video/device.rs
+++ b/devices/src/virtio/video/device.rs
@@ -4,7 +4,7 @@
//! Definition of the trait `Device` that each backend video device must implement.
-use base::{PollToken, Tube, WaitContext};
+use base::{PollToken, WaitContext};
use crate::virtio::video::async_cmd_desc_map::AsyncCmdDescMap;
use crate::virtio::video::command::{QueueType, VideoCmd};
@@ -38,6 +38,7 @@ pub enum AsyncCmdTag {
queue_type: QueueType,
},
// Used exclusively by the encoder.
+ #[cfg(feature = "video-encoder")]
GetParams {
stream_id: u32,
queue_type: QueueType,
@@ -101,7 +102,6 @@ pub trait Device {
&mut self,
cmd: VideoCmd,
wait_ctx: &WaitContext<Token>,
- resource_bridge: &Tube,
) -> (
VideoCmdResponseType,
Option<(u32, Vec<VideoEvtResponseType>)>,
diff --git a/devices/src/virtio/video/encoder/backend/mod.rs b/devices/src/virtio/video/encoder/backend/mod.rs
new file mode 100644
index 000000000..d4147fb0a
--- /dev/null
+++ b/devices/src/virtio/video/encoder/backend/mod.rs
@@ -0,0 +1,72 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#[cfg(feature = "libvda")]
+pub mod vda;
+
+use base::AsRawDescriptor;
+
+use crate::virtio::video::error::VideoResult;
+use crate::virtio::video::{
+ format::Bitrate,
+ resource::{GuestResource, GuestResourceHandle},
+};
+
+use super::encoder::{
+ EncoderCapabilities, EncoderEvent, InputBufferId, OutputBufferId, SessionConfig,
+};
+
+pub trait EncoderSession {
+ /// Encodes the frame provided by `resource`.
+ /// `force_keyframe` forces the frame to be encoded as a keyframe.
+ /// When the buffer has been successfully processed, a `ProcessedInputBuffer` event will
+ /// be readable from the event pipe, with the same `InputBufferId` as returned by this
+ /// function.
+ /// When the corresponding encoded data is ready, `ProcessedOutputBuffer` events will be
+ /// readable from the event pipe, with the same timestamp as provided `timestamp`.
+ fn encode(
+ &mut self,
+ resource: GuestResource,
+ timestamp: u64,
+ force_keyframe: bool,
+ ) -> VideoResult<InputBufferId>;
+
+ /// Provides an output `resource` to store encoded output, where `offset` and `size` define the
+ /// region of memory to use.
+ /// When the buffer has been filled with encoded output, a `ProcessedOutputBuffer` event will be
+ /// readable from the event pipe, with the same `OutputBufferId` as returned by this function.
+ fn use_output_buffer(
+ &mut self,
+ resource: GuestResourceHandle,
+ offset: u32,
+ size: u32,
+ ) -> VideoResult<OutputBufferId>;
+
+ /// Requests the encoder to flush. When completed, an `EncoderEvent::FlushResponse` event will
+ /// be readable from the event pipe.
+ fn flush(&mut self) -> VideoResult<()>;
+
+ /// Requests the encoder to use new encoding parameters provided by `bitrate` and `framerate`.
+ fn request_encoding_params_change(
+ &mut self,
+ bitrate: Bitrate,
+ framerate: u32,
+ ) -> VideoResult<()>;
+
+ /// Returns the event pipe on which the availability of events will be signaled. Note that the
+ /// returned value is borrowed and only valid as long as the session is alive.
+ fn event_pipe(&self) -> &dyn AsRawDescriptor;
+
+ /// Performs a blocking read for an encoder event. This function should only be called when
+ /// the file descriptor returned by `event_pipe` is readable.
+ fn read_event(&mut self) -> VideoResult<EncoderEvent>;
+}
+
+pub trait Encoder {
+ type Session: EncoderSession;
+
+ fn query_capabilities(&self) -> VideoResult<EncoderCapabilities>;
+ fn start_session(&mut self, config: SessionConfig) -> VideoResult<Self::Session>;
+ fn stop_session(&mut self, session: Self::Session) -> VideoResult<()>;
+}
diff --git a/devices/src/virtio/video/encoder/libvda_encoder.rs b/devices/src/virtio/video/encoder/backend/vda.rs
index f8d9898c3..adda7c059 100644
--- a/devices/src/virtio/video/encoder/libvda_encoder.rs
+++ b/devices/src/virtio/video/encoder/backend/vda.rs
@@ -1,27 +1,51 @@
-// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::collections::btree_map::Entry;
use std::collections::BTreeMap;
-use std::fs::File;
+use anyhow::{anyhow, Context};
+use base::{error, warn, AsRawDescriptor, IntoRawDescriptor};
use libvda::encode::{EncodeCapabilities, VeaImplType, VeaInstance};
-use base::{error, warn, IntoRawDescriptor};
-
-use crate::virtio::video::encoder::encoder::*;
-use crate::virtio::video::format::{Format, FormatDesc, FormatRange, FrameFormat, Level, Profile};
+use super::*;
+use crate::virtio::video::format::{
+ Bitrate, Format, FormatDesc, FormatRange, FrameFormat, Level, Profile,
+};
+use crate::virtio::video::{
+ encoder::encoder::*,
+ error::{VideoError, VideoResult},
+ resource::{GuestResource, GuestResourceHandle},
+};
+
+impl From<Bitrate> for libvda::encode::Bitrate {
+ fn from(bitrate: Bitrate) -> Self {
+ libvda::encode::Bitrate {
+ mode: match bitrate {
+ Bitrate::VBR { .. } => libvda::encode::BitrateMode::VBR,
+ Bitrate::CBR { .. } => libvda::encode::BitrateMode::CBR,
+ },
+ target: bitrate.target(),
+ peak: match &bitrate {
+ // No need to specify peak if mode is CBR.
+ Bitrate::CBR { .. } => 0,
+ Bitrate::VBR { peak, .. } => *peak,
+ },
+ }
+ }
+}
+/// A VDA encoder backend that can be passed to `EncoderDevice::new` in order to create a working
+/// encoder.
pub struct LibvdaEncoder {
instance: VeaInstance,
capabilities: EncoderCapabilities,
}
impl LibvdaEncoder {
- pub fn new() -> Result<Self> {
- let instance = VeaInstance::new(VeaImplType::Gavea)
- .map_err(|e| EncoderError::Implementation(Box::new(e)))?;
+ pub fn new() -> VideoResult<Self> {
+ let instance = VeaInstance::new(VeaImplType::Gavea)?;
let EncodeCapabilities {
input_formats,
@@ -30,7 +54,7 @@ impl LibvdaEncoder {
if input_formats.len() == 0 || output_formats.len() == 0 {
error!("No input or output formats.");
- return Err(EncoderError::PlatformFailure);
+ return Err(VideoError::InvalidFormat);
}
let input_format_descs: Vec<FormatDesc> = input_formats
@@ -69,14 +93,13 @@ impl LibvdaEncoder {
})
.collect();
- if input_format_descs
+ if !input_format_descs
.iter()
- .find(|fd| fd.format == Format::NV12)
- .is_none()
+ .any(|fd| fd.format == Format::NV12)
{
// NV12 is currently the only supported pixel format for libvda.
error!("libvda encoder does not support NV12.");
- return Err(EncoderError::PlatformFailure);
+ return Err(VideoError::InvalidFormat);
}
struct ParsedFormat {
@@ -169,28 +192,28 @@ impl LibvdaEncoder {
}
}
-impl<'a> Encoder for &'a LibvdaEncoder {
- type Session = LibvdaEncoderSession<'a>;
+impl Encoder for LibvdaEncoder {
+ type Session = LibvdaEncoderSession;
- fn query_capabilities(&self) -> Result<EncoderCapabilities> {
+ fn query_capabilities(&self) -> VideoResult<EncoderCapabilities> {
Ok(self.capabilities.clone())
}
- fn start_session(&mut self, config: SessionConfig) -> Result<LibvdaEncoderSession<'a>> {
+ fn start_session(&mut self, config: SessionConfig) -> VideoResult<LibvdaEncoderSession> {
if config.dst_params.format.is_none() {
- return Err(EncoderError::InvalidArgument);
+ return Err(VideoError::InvalidArgument);
}
let input_format = match config
.src_params
.format
- .ok_or(EncoderError::InvalidArgument)?
+ .ok_or(VideoError::InvalidArgument)?
{
Format::NV12 => libvda::PixelFormat::NV12,
Format::YUV420 => libvda::PixelFormat::YV12,
unsupported_format => {
error!("Unsupported libvda format: {}", unsupported_format);
- return Err(EncoderError::InvalidArgument);
+ return Err(VideoError::InvalidArgument);
}
};
@@ -198,7 +221,7 @@ impl<'a> Encoder for &'a LibvdaEncoder {
Some(p) => p,
None => {
error!("Unsupported libvda profile");
- return Err(EncoderError::InvalidArgument);
+ return Err(VideoError::InvalidArgument);
}
};
@@ -207,7 +230,7 @@ impl<'a> Encoder for &'a LibvdaEncoder {
input_visible_width: config.src_params.frame_width,
input_visible_height: config.src_params.frame_height,
output_profile,
- initial_bitrate: config.dst_bitrate,
+ bitrate: config.dst_bitrate.into(),
initial_framerate: if config.frame_rate == 0 {
None
} else {
@@ -235,10 +258,7 @@ impl<'a> Encoder for &'a LibvdaEncoder {
}),
};
- let session = self
- .instance
- .open_session(config)
- .map_err(|e| EncoderError::Implementation(Box::new(e)))?;
+ let session = self.instance.open_session(config)?;
Ok(LibvdaEncoderSession {
session,
@@ -247,29 +267,37 @@ impl<'a> Encoder for &'a LibvdaEncoder {
})
}
- fn stop_session(&mut self, _session: LibvdaEncoderSession) -> Result<()> {
+ fn stop_session(&mut self, _session: LibvdaEncoderSession) -> VideoResult<()> {
// Resources will be freed when `_session` is dropped.
Ok(())
}
}
-pub struct LibvdaEncoderSession<'a> {
- session: libvda::encode::Session<'a>,
+pub struct LibvdaEncoderSession {
+ session: libvda::encode::Session,
next_input_buffer_id: InputBufferId,
next_output_buffer_id: OutputBufferId,
}
-impl<'a> EncoderSession for LibvdaEncoderSession<'a> {
+impl EncoderSession for LibvdaEncoderSession {
fn encode(
&mut self,
- resource: File,
- planes: &[VideoFramePlane],
+ resource: GuestResource,
timestamp: u64,
force_keyframe: bool,
- ) -> Result<InputBufferId> {
+ ) -> VideoResult<InputBufferId> {
let input_buffer_id = self.next_input_buffer_id;
+ let desc = match resource.handle {
+ GuestResourceHandle::VirtioObject(handle) => handle.desc,
+ _ => {
+ return Err(VideoError::BackendFailure(anyhow!(
+ "VDA backend only supports virtio object resources"
+ )))
+ }
+ };
- let libvda_planes = planes
+ let libvda_planes = resource
+ .planes
.iter()
.map(|plane| libvda::FramePlane {
offset: plane.offset as i32,
@@ -277,58 +305,73 @@ impl<'a> EncoderSession for LibvdaEncoderSession<'a> {
})
.collect::<Vec<_>>();
- self.session
- .encode(
- input_buffer_id as i32,
- resource.into_raw_descriptor(),
- &libvda_planes,
- timestamp as i64,
- force_keyframe,
- )
- .map_err(|e| EncoderError::Implementation(Box::new(e)))?;
+ self.session.encode(
+ input_buffer_id as i32,
+ // Steal the descriptor of the resource, as libvda will close it.
+ desc.into_raw_descriptor(),
+ &libvda_planes,
+ timestamp as i64,
+ force_keyframe,
+ )?;
self.next_input_buffer_id = self.next_input_buffer_id.wrapping_add(1);
Ok(input_buffer_id)
}
- fn use_output_buffer(&mut self, file: File, offset: u32, size: u32) -> Result<OutputBufferId> {
+ fn use_output_buffer(
+ &mut self,
+ resource: GuestResourceHandle,
+ offset: u32,
+ size: u32,
+ ) -> VideoResult<OutputBufferId> {
let output_buffer_id = self.next_output_buffer_id;
- self.next_output_buffer_id = self.next_output_buffer_id.wrapping_add(1);
+ let desc = match resource {
+ GuestResourceHandle::VirtioObject(handle) => handle.desc,
+ _ => {
+ return Err(VideoError::BackendFailure(anyhow!(
+ "VDA backend only supports virtio object resources"
+ )))
+ }
+ };
- self.session
- .use_output_buffer(
- output_buffer_id as i32,
- file.into_raw_descriptor(),
- offset,
- size,
- )
- .map_err(|e| EncoderError::Implementation(Box::new(e)))?;
+ self.session.use_output_buffer(
+ output_buffer_id as i32,
+ // Steal the descriptor of the resource, as libvda will close it.
+ desc.into_raw_descriptor(),
+ offset,
+ size,
+ )?;
+
+ self.next_output_buffer_id = self.next_output_buffer_id.wrapping_add(1);
Ok(output_buffer_id)
}
- fn flush(&mut self) -> Result<()> {
+ fn flush(&mut self) -> VideoResult<()> {
self.session
.flush()
- .map_err(|e| EncoderError::Implementation(Box::new(e)))
+ .context("while flushing")
+ .map_err(VideoError::BackendFailure)
}
- fn request_encoding_params_change(&mut self, bitrate: u32, framerate: u32) -> Result<()> {
+ fn request_encoding_params_change(
+ &mut self,
+ bitrate: Bitrate,
+ framerate: u32,
+ ) -> VideoResult<()> {
self.session
- .request_encoding_params_change(bitrate, framerate)
- .map_err(|e| EncoderError::Implementation(Box::new(e)))
+ .request_encoding_params_change(bitrate.into(), framerate)
+ .context("while requesting encoder parameter change")
+ .map_err(VideoError::BackendFailure)
}
- fn event_pipe(&self) -> &File {
+ fn event_pipe(&self) -> &dyn AsRawDescriptor {
self.session.pipe()
}
- fn read_event(&mut self) -> Result<EncoderEvent> {
- let event = self
- .session
- .read_event()
- .map_err(|e| EncoderError::Implementation(Box::new(e)))?;
+ fn read_event(&mut self) -> VideoResult<EncoderEvent> {
+ let event = self.session.read_event()?;
use libvda::encode::Event::*;
let encoder_event = match event {
@@ -358,7 +401,7 @@ impl<'a> EncoderSession for LibvdaEncoderSession<'a> {
},
FlushResponse { flush_done } => EncoderEvent::FlushResponse { flush_done },
NotifyError(err) => EncoderEvent::NotifyError {
- error: EncoderError::Implementation(Box::new(err)),
+ error: VideoError::BackendFailure(anyhow!(err)),
},
};
diff --git a/devices/src/virtio/video/encoder/encoder.rs b/devices/src/virtio/video/encoder/encoder.rs
index a37e8e200..da464604a 100644
--- a/devices/src/virtio/video/encoder/encoder.rs
+++ b/devices/src/virtio/video/encoder/encoder.rs
@@ -3,39 +3,14 @@
// found in the LICENSE file.
use std::collections::BTreeMap;
-use std::fs::File;
use base::error;
-use crate::virtio::video::format::{
- find_closest_resolution, Format, FormatDesc, Level, PlaneFormat, Profile,
-};
use crate::virtio::video::params::Params;
-
-pub type Result<T> = std::result::Result<T, EncoderError>;
-
-#[derive(Debug)]
-pub enum EncoderError {
- // Invalid argument.
- InvalidArgument,
- // Platform failure.
- PlatformFailure,
- // Implementation specific error.
- Implementation(Box<dyn std::error::Error + Send>),
-}
-
-impl std::fmt::Display for EncoderError {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- use self::EncoderError::*;
- match self {
- InvalidArgument => write!(f, "invalid argument"),
- PlatformFailure => write!(f, "platform failure"),
- Implementation(e) => write!(f, "implementation error: {}", e),
- }
- }
-}
-
-impl std::error::Error for EncoderError {}
+use crate::virtio::video::{
+ error::{VideoError, VideoResult},
+ format::{find_closest_resolution, Bitrate, Format, FormatDesc, Level, PlaneFormat, Profile},
+};
pub type InputBufferId = u32;
pub type OutputBufferId = u32;
@@ -61,7 +36,7 @@ pub enum EncoderEvent {
flush_done: bool,
},
NotifyError {
- error: EncoderError,
+ error: VideoError,
},
}
@@ -70,56 +45,11 @@ pub struct SessionConfig {
pub src_params: Params,
pub dst_params: Params,
pub dst_profile: Profile,
- pub dst_bitrate: u32,
+ pub dst_bitrate: Bitrate,
pub dst_h264_level: Option<Level>,
pub frame_rate: u32,
}
-#[derive(Debug)]
-pub struct VideoFramePlane {
- pub offset: usize,
- pub stride: usize,
-}
-
-pub trait EncoderSession {
- /// Encodes the frame provided by `resource`, with planes specified by `plane`.
- /// `force_keyframe` forces the frame to be encoded as a keyframe.
- /// When the buffer has been successfully processed, a `ProcessedInputBuffer` event will
- /// be readable from the event pipe, with the same `InputBufferId` as returned by this
- /// function.
- /// When the corresponding encoded data is ready, `ProcessedOutputBuffer` events will be
- /// readable from the event pipe, with the same timestamp as provided `timestamp`.
- fn encode(
- &mut self,
- resource: File,
- planes: &[VideoFramePlane],
- timestamp: u64,
- force_keyframe: bool,
- ) -> Result<InputBufferId>;
-
- /// Provides an output buffer `file` to store encoded output, where `offset` and `size`
- /// define the region of memory to use.
- /// When the buffer has been filled with encoded output, a `ProcessedOutputBuffer` event
- /// will be readable from the event pipe, with the same `OutputBufferId` as returned by this
- /// function.
- fn use_output_buffer(&mut self, file: File, offset: u32, size: u32) -> Result<OutputBufferId>;
-
- /// Requests the encoder to flush. When completed, an `EncoderEvent::FlushResponse` event will
- /// be readable from the event pipe.
- fn flush(&mut self) -> Result<()>;
-
- /// Requests the encoder to use new encoding parameters provided by `bitrate` and `framerate`.
- fn request_encoding_params_change(&mut self, bitrate: u32, framerate: u32) -> Result<()>;
-
- /// Returns the event pipe as a pollable file descriptor. When the file descriptor is
- /// readable, an event can be read by `read_event`.
- fn event_pipe(&self) -> &File;
-
- /// Performs a blocking read for an encoder event. This function should only be called when
- /// the file descriptor returned by `event_pipe` is readable.
- fn read_event(&mut self) -> Result<EncoderEvent>;
-}
-
#[derive(Clone)]
pub struct EncoderCapabilities {
pub input_format_descs: Vec<FormatDesc>,
@@ -135,7 +65,7 @@ impl EncoderCapabilities {
desired_width: u32,
desired_height: u32,
mut stride: u32,
- ) -> Result<()> {
+ ) -> VideoResult<()> {
let format_desc = self
.input_format_descs
.iter()
@@ -143,7 +73,7 @@ impl EncoderCapabilities {
.unwrap_or(
self.input_format_descs
.get(0)
- .ok_or(EncoderError::PlatformFailure)?,
+ .ok_or(VideoError::InvalidFormat)?,
);
let (allowed_width, allowed_height) =
@@ -153,26 +83,13 @@ impl EncoderCapabilities {
stride = allowed_width;
}
- let plane_formats = match format_desc.format {
- Format::NV12 => {
- let y_plane = PlaneFormat {
- plane_size: stride * allowed_height,
- stride,
- };
- let crcb_plane = PlaneFormat {
- plane_size: y_plane.plane_size / 2,
- stride,
- };
- vec![y_plane, crcb_plane]
- }
- _ => {
- return Err(EncoderError::PlatformFailure);
- }
- };
+ let plane_formats =
+ PlaneFormat::get_plane_layout(format_desc.format, stride, allowed_height)
+ .ok_or(VideoError::InvalidFormat)?;
src_params.frame_width = allowed_width;
src_params.frame_height = allowed_height;
- src_params.format = Some(format_desc.format.clone());
+ src_params.format = Some(format_desc.format);
src_params.plane_formats = plane_formats;
Ok(())
}
@@ -182,7 +99,7 @@ impl EncoderCapabilities {
dst_params: &mut Params,
desired_format: Format,
buffer_size: u32,
- ) -> Result<()> {
+ ) -> VideoResult<()> {
// TODO(alexlau): Should the first be the default?
let format_desc = self
.output_format_descs
@@ -191,9 +108,9 @@ impl EncoderCapabilities {
.unwrap_or(
self.output_format_descs
.get(0)
- .ok_or(EncoderError::PlatformFailure)?,
+ .ok_or(VideoError::InvalidFormat)?,
);
- dst_params.format = Some(format_desc.format.clone());
+ dst_params.format = Some(format_desc.format);
// The requested output buffer size might be adjusted by the encoder to match hardware
// requirements in RequireInputBuffers.
@@ -219,11 +136,3 @@ impl EncoderCapabilities {
}
}
}
-
-pub trait Encoder {
- type Session: EncoderSession;
-
- fn query_capabilities(&self) -> Result<EncoderCapabilities>;
- fn start_session(&mut self, config: SessionConfig) -> Result<Self::Session>;
- fn stop_session(&mut self, session: Self::Session) -> Result<()>;
-}
diff --git a/devices/src/virtio/video/encoder/mod.rs b/devices/src/virtio/video/encoder/mod.rs
index 7d32d378c..e1c6200bd 100644
--- a/devices/src/virtio/video/encoder/mod.rs
+++ b/devices/src/virtio/video/encoder/mod.rs
@@ -5,16 +5,13 @@
//! Implementation of the the `Encoder` struct, which is responsible for translation between the
//! virtio protocols and LibVDA APIs.
+pub mod backend;
mod encoder;
-mod libvda_encoder;
-pub use encoder::EncoderError;
-pub use libvda_encoder::LibvdaEncoder;
-
-use base::{error, warn, Tube, WaitContext};
+use base::{error, info, warn, Tube, WaitContext};
use std::collections::{BTreeMap, BTreeSet};
+use vm_memory::GuestMemory;
-use crate::virtio::resource_bridge::{self, BufferInfo, ResourceInfo, ResourceRequest};
use crate::virtio::video::async_cmd_desc_map::AsyncCmdDescMap;
use crate::virtio::video::command::{QueueType, VideoCmd};
use crate::virtio::video::control::*;
@@ -23,15 +20,17 @@ use crate::virtio::video::device::{
AsyncCmdResponse, AsyncCmdTag, Device, Token, VideoEvtResponseType,
};
use crate::virtio::video::encoder::encoder::{
- Encoder, EncoderEvent, EncoderSession, InputBufferId, OutputBufferId, SessionConfig,
- VideoFramePlane,
+ EncoderEvent, InputBufferId, OutputBufferId, SessionConfig,
};
use crate::virtio::video::error::*;
use crate::virtio::video::event::{EvtType, VideoEvt};
-use crate::virtio::video::format::{Format, Level, PlaneFormat, Profile};
+use crate::virtio::video::format::{Bitrate, BitrateMode, Format, Level, PlaneFormat, Profile};
use crate::virtio::video::params::Params;
use crate::virtio::video::protocol;
+use crate::virtio::video::resource::*;
use crate::virtio::video::response::CmdResponse;
+use crate::virtio::video::EosBufferManager;
+use backend::*;
#[derive(Debug)]
struct QueuedInputResourceParams {
@@ -41,8 +40,7 @@ struct QueuedInputResourceParams {
}
struct InputResource {
- resource_handle: u128,
- planes: Vec<VideoFramePlane>,
+ resource: GuestResource,
queue_params: Option<QueuedInputResourceParams>,
}
@@ -54,15 +52,17 @@ struct QueuedOutputResourceParams {
}
struct OutputResource {
- resource_handle: u128,
+ resource: GuestResource,
offset: u32,
queue_params: Option<QueuedOutputResourceParams>,
}
#[derive(Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
enum PendingCommand {
- GetSrcParams,
- GetDstParams,
+ // TODO(b/193202566): remove this is_ext parameter throughout the code along with
+ // support for the old GET_PARAMS and SET_PARAMS commands.
+ GetSrcParams { is_ext: bool },
+ GetDstParams { is_ext: bool },
Drain,
SrcQueueClear,
DstQueueClear,
@@ -72,10 +72,9 @@ struct Stream<T: EncoderSession> {
id: u32,
src_params: Params,
dst_params: Params,
- dst_bitrate: u32,
+ dst_bitrate: Bitrate,
dst_profile: Profile,
dst_h264_level: Option<Level>,
- frame_rate: u32,
force_keyframe: bool,
encoder_session: Option<T>,
@@ -88,12 +87,14 @@ struct Stream<T: EncoderSession> {
encoder_output_buffer_ids: BTreeMap<OutputBufferId, u32>,
pending_commands: BTreeSet<PendingCommand>,
- eos_notification_buffer: Option<OutputBufferId>,
+ eos_manager: EosBufferManager,
}
impl<T: EncoderSession> Stream<T> {
fn new<E: Encoder<Session = T>>(
id: u32,
+ src_resource_type: ResourceType,
+ dst_resource_type: ResourceType,
desired_format: Format,
encoder: &EncoderDevice<E>,
) -> VideoResult<Self> {
@@ -101,13 +102,20 @@ impl<T: EncoderSession> Stream<T> {
const MAX_BUFFERS: u32 = 342;
const DEFAULT_WIDTH: u32 = 640;
const DEFAULT_HEIGHT: u32 = 480;
- const DEFAULT_BITRATE: u32 = 6000;
+ const DEFAULT_BITRATE_TARGET: u32 = 6000;
+ const DEFAULT_BITRATE_PEAK: u32 = DEFAULT_BITRATE_TARGET * 2;
+ const DEFAULT_BITRATE: Bitrate = Bitrate::VBR {
+ target: DEFAULT_BITRATE_TARGET,
+ peak: DEFAULT_BITRATE_PEAK,
+ };
const DEFAULT_BUFFER_SIZE: u32 = 2097152; // 2MB; chosen empirically for 1080p video
const DEFAULT_FPS: u32 = 30;
let mut src_params = Params {
+ frame_rate: DEFAULT_FPS,
min_buffers: MIN_BUFFERS,
max_buffers: MAX_BUFFERS,
+ resource_type: src_resource_type,
..Default::default()
};
@@ -123,7 +131,13 @@ impl<T: EncoderSession> Stream<T> {
)
.map_err(|_| VideoError::InvalidArgument)?;
- let mut dst_params = Default::default();
+ let mut dst_params = Params {
+ resource_type: dst_resource_type,
+ frame_rate: DEFAULT_FPS,
+ frame_width: DEFAULT_WIDTH,
+ frame_height: DEFAULT_HEIGHT,
+ ..Default::default()
+ };
// In order to support requesting encoder params change, we must know the default frame
// rate, because VEA's request_encoding_params_change requires both framerate and
@@ -152,7 +166,6 @@ impl<T: EncoderSession> Stream<T> {
dst_bitrate: DEFAULT_BITRATE,
dst_profile,
dst_h264_level,
- frame_rate: DEFAULT_FPS,
force_keyframe: false,
encoder_session: None,
received_input_buffers_event: false,
@@ -161,7 +174,7 @@ impl<T: EncoderSession> Stream<T> {
dst_resources: Default::default(),
encoder_output_buffer_ids: Default::default(),
pending_commands: Default::default(),
- eos_notification_buffer: None,
+ eos_manager: EosBufferManager::new(id),
})
}
@@ -188,8 +201,8 @@ impl<T: EncoderSession> Stream<T> {
dst_params: self.dst_params.clone(),
dst_profile: self.dst_profile,
dst_bitrate: self.dst_bitrate,
- dst_h264_level: self.dst_h264_level.clone(),
- frame_rate: self.frame_rate,
+ dst_h264_level: self.dst_h264_level,
+ frame_rate: self.dst_params.frame_rate,
})
.map_err(|_| VideoError::InvalidOperation)?;
@@ -243,7 +256,20 @@ impl<T: EncoderSession> Stream<T> {
let mut responses = vec![];
// Respond to any GetParams commands that were waiting.
- if self.pending_commands.remove(&PendingCommand::GetSrcParams) {
+ let pending_get_src_params = if self
+ .pending_commands
+ .remove(&PendingCommand::GetSrcParams { is_ext: false })
+ {
+ Some(false)
+ } else if self
+ .pending_commands
+ .remove(&PendingCommand::GetSrcParams { is_ext: true })
+ {
+ Some(true)
+ } else {
+ None
+ };
+ if let Some(is_ext) = pending_get_src_params {
responses.push(VideoEvtResponseType::AsyncCmd(
AsyncCmdResponse::from_response(
AsyncCmdTag::GetParams {
@@ -253,11 +279,25 @@ impl<T: EncoderSession> Stream<T> {
CmdResponse::GetParams {
queue_type: QueueType::Input,
params: self.src_params.clone(),
+ is_ext,
},
),
));
}
- if self.pending_commands.remove(&PendingCommand::GetDstParams) {
+ let pending_get_dst_params = if self
+ .pending_commands
+ .remove(&PendingCommand::GetDstParams { is_ext: false })
+ {
+ Some(false)
+ } else if self
+ .pending_commands
+ .remove(&PendingCommand::GetDstParams { is_ext: true })
+ {
+ Some(true)
+ } else {
+ None
+ };
+ if let Some(is_ext) = pending_get_dst_params {
responses.push(VideoEvtResponseType::AsyncCmd(
AsyncCmdResponse::from_response(
AsyncCmdTag::GetParams {
@@ -267,6 +307,7 @@ impl<T: EncoderSession> Stream<T> {
CmdResponse::GetParams {
queue_type: QueueType::Output,
params: self.dst_params.clone(),
+ is_ext,
},
),
));
@@ -408,36 +449,7 @@ impl<T: EncoderSession> Stream<T> {
let mut async_responses = vec![];
- let eos_resource_id = match self.eos_notification_buffer {
- Some(r) => r,
- None => {
- error!(
- "No EOS resource available on successful flush response (stream id {})",
- self.id
- );
- return Some(vec![VideoEvtResponseType::Event(VideoEvt {
- typ: EvtType::Error,
- stream_id: self.id,
- })]);
- }
- };
-
- let eos_tag = AsyncCmdTag::Queue {
- stream_id: self.id,
- queue_type: QueueType::Output,
- resource_id: eos_resource_id,
- };
-
- let eos_response = CmdResponse::ResourceQueue {
- timestamp: 0,
- flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_EOS,
- size: 0,
- };
-
- async_responses.push(VideoEvtResponseType::AsyncCmd(
- AsyncCmdResponse::from_response(eos_tag, eos_response),
- ));
-
+ // First gather the responses for all completed commands.
if self.pending_commands.remove(&PendingCommand::Drain) {
async_responses.push(VideoEvtResponseType::AsyncCmd(
AsyncCmdResponse::from_response(
@@ -471,16 +483,12 @@ impl<T: EncoderSession> Stream<T> {
));
}
- if async_responses.is_empty() {
- error!("Received flush response but there are no pending commands.");
- None
- } else {
- Some(async_responses)
- }
+ // Then add the EOS buffer to the responses if it is available.
+ self.eos_manager.try_complete_eos(async_responses)
}
#[allow(clippy::unnecessary_wraps)]
- fn notify_error(&self, error: EncoderError) -> Option<Vec<VideoEvtResponseType>> {
+ fn notify_error(&self, error: VideoError) -> Option<Vec<VideoEvtResponseType>> {
error!(
"Received encoder error event for stream {}: {}",
self.id, error
@@ -496,25 +504,19 @@ pub struct EncoderDevice<T: Encoder> {
cros_capabilities: encoder::EncoderCapabilities,
encoder: T,
streams: BTreeMap<u32, Stream<T::Session>>,
-}
-
-fn get_resource_info(res_bridge: &Tube, uuid: u128) -> VideoResult<BufferInfo> {
- match resource_bridge::get_resource_info(
- res_bridge,
- ResourceRequest::GetBuffer { id: uuid as u32 },
- ) {
- Ok(ResourceInfo::Buffer(buffer_info)) => Ok(buffer_info),
- Ok(_) => Err(VideoError::InvalidArgument),
- Err(e) => Err(VideoError::ResourceBridgeFailure(e)),
- }
+ resource_bridge: Tube,
+ mem: GuestMemory,
}
impl<T: Encoder> EncoderDevice<T> {
- pub fn new(encoder: T) -> encoder::Result<Self> {
+ /// Build a new encoder using the provided `backend`.
+ pub fn new(backend: T, resource_bridge: Tube, mem: GuestMemory) -> VideoResult<Self> {
Ok(Self {
- cros_capabilities: encoder.query_capabilities()?,
- encoder,
+ cros_capabilities: backend.query_capabilities()?,
+ encoder: backend,
streams: Default::default(),
+ resource_bridge,
+ mem,
})
}
@@ -533,11 +535,19 @@ impl<T: Encoder> EncoderDevice<T> {
&mut self,
stream_id: u32,
desired_format: Format,
+ src_resource_type: ResourceType,
+ dst_resource_type: ResourceType,
) -> VideoResult<VideoCmdResponseType> {
if self.streams.contains_key(&stream_id) {
return Err(VideoError::InvalidStreamId(stream_id));
}
- let new_stream = Stream::new(stream_id, desired_format, self)?;
+ let new_stream = Stream::new(
+ stream_id,
+ src_resource_type,
+ dst_resource_type,
+ desired_format,
+ self,
+ )?;
self.streams.insert(stream_id, new_stream);
Ok(VideoCmdResponseType::Sync(CmdResponse::NoData))
@@ -597,12 +607,11 @@ impl<T: Encoder> EncoderDevice<T> {
fn resource_create(
&mut self,
wait_ctx: &WaitContext<Token>,
- resource_bridge: &Tube,
stream_id: u32,
queue_type: QueueType,
resource_id: u32,
plane_offsets: Vec<u32>,
- uuid: u128,
+ plane_entries: Vec<Vec<UnresolvedResourceEntry>>,
) -> VideoResult<VideoCmdResponseType> {
let stream = self
.streams
@@ -617,9 +626,20 @@ impl<T: Encoder> EncoderDevice<T> {
let num_planes = plane_offsets.len();
+ // We only support single-buffer resources for now.
+ let entries = if plane_entries.len() != 1 {
+ return Err(VideoError::InvalidArgument);
+ } else {
+ // unwrap() is safe because we just tested that `plane_entries` had exactly one element.
+ plane_entries.get(0).unwrap()
+ };
+
match queue_type {
QueueType::Input => {
- if num_planes != stream.src_params.plane_formats.len() {
+ // We currently only support single-buffer formats, but some clients may mistake
+ // color planes with memory planes and submit several planes to us. This doesn't
+ // matter as we will only consider the first one.
+ if num_planes < 1 {
return Err(VideoError::InvalidParameter);
}
@@ -627,27 +647,46 @@ impl<T: Encoder> EncoderDevice<T> {
warn!("Replacing source resource with id {}", resource_id);
}
- let resource_info = get_resource_info(resource_bridge, uuid)?;
-
- let planes: Vec<VideoFramePlane> = resource_info.planes[0..num_planes]
- .into_iter()
- .map(|plane_info| VideoFramePlane {
- offset: plane_info.offset as usize,
- stride: plane_info.stride as usize,
- })
- .collect();
+ let resource = match stream.src_params.resource_type {
+ ResourceType::VirtioObject => {
+ // Virtio object resources only have one entry.
+ if entries.len() != 1 {
+ return Err(VideoError::InvalidArgument);
+ }
+ GuestResource::from_virtio_object_entry(
+ // Safe because we confirmed the correct type for the resource.
+ // unwrap() is also safe here because we just tested above that `entries` had
+ // exactly one element.
+ unsafe { entries.get(0).unwrap().object },
+ &self.resource_bridge,
+ )
+ .map_err(|_| VideoError::InvalidArgument)?
+ }
+ ResourceType::GuestPages => GuestResource::from_virtio_guest_mem_entry(
+ // Safe because we confirmed the correct type for the resource.
+ unsafe {
+ std::slice::from_raw_parts(
+ entries.as_ptr() as *const protocol::virtio_video_mem_entry,
+ entries.len(),
+ )
+ },
+ &self.mem,
+ &stream.src_params.plane_formats,
+ )
+ .map_err(|_| VideoError::InvalidArgument)?,
+ };
stream.src_resources.insert(
resource_id,
InputResource {
- resource_handle: uuid,
- planes,
+ resource,
queue_params: None,
},
);
}
QueueType::Output => {
- if num_planes != stream.dst_params.plane_formats.len() {
+ // Bitstream buffers always have only one plane.
+ if num_planes != 1 {
return Err(VideoError::InvalidParameter);
}
@@ -655,11 +694,40 @@ impl<T: Encoder> EncoderDevice<T> {
warn!("Replacing dest resource with id {}", resource_id);
}
+ let resource = match stream.dst_params.resource_type {
+ ResourceType::VirtioObject => {
+ // Virtio object resources only have one entry.
+ if entries.len() != 1 {
+ return Err(VideoError::InvalidArgument);
+ }
+ GuestResource::from_virtio_object_entry(
+ // Safe because we confirmed the correct type for the resource.
+ // unwrap() is also safe here because we just tested above that `entries` had
+ // exactly one element.
+ unsafe { entries.get(0).unwrap().object },
+ &self.resource_bridge,
+ )
+ .map_err(|_| VideoError::InvalidArgument)?
+ }
+ ResourceType::GuestPages => GuestResource::from_virtio_guest_mem_entry(
+ // Safe because we confirmed the correct type for the resource.
+ unsafe {
+ std::slice::from_raw_parts(
+ entries.as_ptr() as *const protocol::virtio_video_mem_entry,
+ entries.len(),
+ )
+ },
+ &self.mem,
+ &stream.dst_params.plane_formats,
+ )
+ .map_err(|_| VideoError::InvalidArgument)?,
+ };
+
let offset = plane_offsets[0];
stream.dst_resources.insert(
resource_id,
OutputResource {
- resource_handle: uuid,
+ resource,
offset,
queue_params: None,
},
@@ -672,7 +740,6 @@ impl<T: Encoder> EncoderDevice<T> {
fn resource_queue(
&mut self,
- resource_bridge: &Tube,
stream_id: u32,
queue_type: QueueType,
resource_id: u32,
@@ -696,7 +763,10 @@ impl<T: Encoder> EncoderDevice<T> {
match queue_type {
QueueType::Input => {
- if data_sizes.len() != stream.src_params.plane_formats.len() {
+ // We currently only support single-buffer formats, but some clients may mistake
+ // color planes with memory planes and submit several planes to us. This doesn't
+ // matter as we will only consider the first one.
+ if data_sizes.len() < 1 {
return Err(VideoError::InvalidParameter);
}
@@ -707,14 +777,13 @@ impl<T: Encoder> EncoderDevice<T> {
},
)?;
- let resource_info =
- get_resource_info(resource_bridge, src_resource.resource_handle)?;
-
let force_keyframe = std::mem::replace(&mut stream.force_keyframe, false);
match encoder_session.encode(
- resource_info.file,
- &src_resource.planes,
+ src_resource
+ .resource
+ .try_clone()
+ .map_err(|_| VideoError::InvalidArgument)?,
timestamp,
force_keyframe,
) {
@@ -759,7 +828,8 @@ impl<T: Encoder> EncoderDevice<T> {
}))
}
QueueType::Output => {
- if data_sizes.len() != stream.dst_params.plane_formats.len() {
+ // Bitstream buffers always have only one plane.
+ if data_sizes.len() != 1 {
return Err(VideoError::InvalidParameter);
}
@@ -770,9 +840,6 @@ impl<T: Encoder> EncoderDevice<T> {
},
)?;
- let resource_info =
- get_resource_info(resource_bridge, dst_resource.resource_handle)?;
-
let mut buffer_size = data_sizes[0];
// It seems that data_sizes[0] is 0 here. For now, take the stride
@@ -780,15 +847,16 @@ impl<T: Encoder> EncoderDevice<T> {
// blobs..
// TODO(alexlau): Figure out how to fix this.
if buffer_size == 0 {
- buffer_size = resource_info.planes[0].offset + resource_info.planes[0].stride;
+ buffer_size = (dst_resource.resource.planes[0].offset
+ + dst_resource.resource.planes[0].stride)
+ as u32;
}
// Stores an output buffer to notify EOS.
// This is necessary because libvda is unable to indicate EOS along with returned buffers.
// For now, when a `Flush()` completes, this saved resource will be returned as a zero-sized
// buffer with the EOS flag.
- if stream.eos_notification_buffer.is_none() {
- stream.eos_notification_buffer = Some(resource_id);
+ if stream.eos_manager.try_reserve_eos_buffer(resource_id) {
return Ok(VideoCmdResponseType::Async(AsyncCmdTag::Queue {
stream_id,
queue_type: QueueType::Output,
@@ -797,7 +865,11 @@ impl<T: Encoder> EncoderDevice<T> {
}
match encoder_session.use_output_buffer(
- resource_info.file,
+ dst_resource
+ .resource
+ .handle
+ .try_clone()
+ .map_err(|_| VideoError::InvalidParameter)?,
dst_resource.offset,
buffer_size,
) {
@@ -850,7 +922,7 @@ impl<T: Encoder> EncoderDevice<T> {
stream.encoder_input_buffer_ids.clear();
stream.dst_resources.clear();
stream.encoder_output_buffer_ids.clear();
- stream.eos_notification_buffer.take();
+ stream.eos_manager.reset();
Ok(VideoCmdResponseType::Sync(CmdResponse::NoData))
}
@@ -889,7 +961,7 @@ impl<T: Encoder> EncoderDevice<T> {
queue_params.in_queue = false;
}
}
- stream.eos_notification_buffer = None;
+ stream.eos_manager.reset();
}
}
Ok(VideoCmdResponseType::Sync(CmdResponse::NoData))
@@ -899,6 +971,7 @@ impl<T: Encoder> EncoderDevice<T> {
&mut self,
stream_id: u32,
queue_type: QueueType,
+ is_ext: bool,
) -> VideoResult<VideoCmdResponseType> {
let stream = self
.streams
@@ -910,8 +983,8 @@ impl<T: Encoder> EncoderDevice<T> {
// event, we need to wait for that before replying so that
// the G_FMT response has the correct data.
let pending_command = match queue_type {
- QueueType::Input => PendingCommand::GetSrcParams,
- QueueType::Output => PendingCommand::GetDstParams,
+ QueueType::Input => PendingCommand::GetSrcParams { is_ext },
+ QueueType::Output => PendingCommand::GetDstParams { is_ext },
};
if !stream.pending_commands.insert(pending_command) {
@@ -932,6 +1005,7 @@ impl<T: Encoder> EncoderDevice<T> {
Ok(VideoCmdResponseType::Sync(CmdResponse::GetParams {
queue_type,
params,
+ is_ext,
}))
}
}
@@ -946,103 +1020,144 @@ impl<T: Encoder> EncoderDevice<T> {
frame_height: u32,
frame_rate: u32,
plane_formats: Vec<PlaneFormat>,
+ _is_ext: bool,
) -> VideoResult<VideoCmdResponseType> {
let stream = self
.streams
.get_mut(&stream_id)
.ok_or(VideoError::InvalidStreamId(stream_id))?;
- if stream.src_resources.len() > 0 || stream.dst_resources.len() > 0 {
- // Buffers have already been queued and encoding has already started.
- return Err(VideoError::InvalidOperation);
+ let mut create_session = stream.encoder_session.is_none();
+ let resources_queued = stream.src_resources.len() > 0 || stream.dst_resources.len() > 0;
+
+ // Dynamic framerate changes are allowed. The framerate can be set on either the input or
+ // output queue. Changing the framerate can influence the selected H.264 level, as the
+ // level might be adjusted to conform to the minimum requirements for the selected bitrate
+ // and framerate. As dynamic level changes are not supported we will just recreate the
+ // encoder session as long as no resources have been queued yet. If an encoder session is
+ // active we will request a dynamic framerate change instead, and it's up to the encoder
+ // backend to return an error on invalid requests.
+ if stream.dst_params.frame_rate != frame_rate {
+ stream.src_params.frame_rate = frame_rate;
+ stream.dst_params.frame_rate = frame_rate;
+ if let Some(ref mut encoder_session) = stream.encoder_session {
+ if !resources_queued {
+ create_session = true;
+ } else if let Err(e) = encoder_session.request_encoding_params_change(
+ stream.dst_bitrate,
+ stream.dst_params.frame_rate,
+ ) {
+ error!("failed to dynamically request framerate change: {}", e);
+ return Err(VideoError::InvalidOperation);
+ }
+ }
}
match queue_type {
QueueType::Input => {
- // There should be at least a single plane.
- if plane_formats.is_empty() {
- return Err(VideoError::InvalidArgument);
- }
+ if stream.src_params.frame_width != frame_width
+ || stream.src_params.frame_height != frame_height
+ || stream.src_params.format != format
+ || stream.src_params.plane_formats != plane_formats
+ {
+ if resources_queued {
+ // Buffers have already been queued and encoding has already started.
+ return Err(VideoError::InvalidOperation);
+ }
+
+ // There should be at least a single plane.
+ if plane_formats.is_empty() {
+ return Err(VideoError::InvalidArgument);
+ }
- let desired_format = format.or(stream.src_params.format).unwrap_or(Format::NV12);
- self.cros_capabilities
- .populate_src_params(
+ let desired_format =
+ format.or(stream.src_params.format).unwrap_or(Format::NV12);
+ self.cros_capabilities.populate_src_params(
&mut stream.src_params,
desired_format,
frame_width,
frame_height,
plane_formats[0].stride,
- )
- .map_err(VideoError::EncoderImpl)?;
+ )?;
+
+ stream.dst_params.frame_width = frame_width;
+ stream.dst_params.frame_height = frame_height;
- // Following the V4L2 standard the framerate requested on the
- // input queue should also be applied to the output queue.
- if frame_rate > 0 {
- stream.frame_rate = frame_rate;
+ create_session = true
}
}
QueueType::Output => {
- let desired_format = format.or(stream.dst_params.format).unwrap_or(Format::H264);
+ if stream.dst_params.format != format
+ || stream.dst_params.plane_formats != plane_formats
+ {
+ if resources_queued {
+ // Buffers have already been queued and encoding has already started.
+ return Err(VideoError::InvalidOperation);
+ }
- // There should be exactly one output buffer.
- if plane_formats.len() != 1 {
- return Err(VideoError::InvalidArgument);
- }
+ let desired_format =
+ format.or(stream.dst_params.format).unwrap_or(Format::H264);
- self.cros_capabilities
- .populate_dst_params(
+ // There should be exactly one output buffer.
+ if plane_formats.len() != 1 {
+ return Err(VideoError::InvalidArgument);
+ }
+
+ self.cros_capabilities.populate_dst_params(
&mut stream.dst_params,
desired_format,
plane_formats[0].plane_size,
- )
- .map_err(VideoError::EncoderImpl)?;
-
- if frame_rate > 0 {
- stream.frame_rate = frame_rate;
- }
-
- // Format is always populated for encoder.
- let new_format = stream
- .dst_params
- .format
- .ok_or(VideoError::InvalidArgument)?;
-
- // If the selected profile no longer corresponds to the selected coded format,
- // reset it.
- stream.dst_profile = self
- .cros_capabilities
- .get_default_profile(&new_format)
- .ok_or(VideoError::InvalidArgument)?;
+ )?;
+
+ // Format is always populated for encoder.
+ let new_format = stream
+ .dst_params
+ .format
+ .ok_or(VideoError::InvalidArgument)?;
+
+ // If the selected profile no longer corresponds to the selected coded format,
+ // reset it.
+ stream.dst_profile = self
+ .cros_capabilities
+ .get_default_profile(&new_format)
+ .ok_or(VideoError::InvalidArgument)?;
+
+ if new_format == Format::H264 {
+ stream.dst_h264_level = Some(Level::H264_1_0);
+ } else {
+ stream.dst_h264_level = None;
+ }
- if new_format == Format::H264 {
- stream.dst_h264_level = Some(Level::H264_1_0);
- } else {
- stream.dst_h264_level = None;
+ create_session = true;
}
}
}
- // An encoder session has to be created immediately upon a SetParams
- // (S_FMT) call, because we need to receive the RequireInputBuffers
- // callback which has output buffer size info, in order to populate
- // dst_params to have the correct size on subsequent GetParams (G_FMT) calls.
- if stream.encoder_session.is_some() {
- stream.clear_encode_session(wait_ctx)?;
- if !stream.received_input_buffers_event {
- // This could happen if two SetParams calls are occuring at the same time.
- // For example, the user calls SetParams for the input queue on one thread,
- // and a new encode session is created. Then on another thread, SetParams
- // is called for the output queue before the first SetParams call has returned.
- // At this point, there is a new EncodeSession being created that has not
- // yet received a RequireInputBuffers event.
- // Even if we clear the encoder session and recreate it, this case
- // is handled because stream.pending_commands will still contain
- // the waiting GetParams responses, which will then receive fresh data once
- // the new session's RequireInputBuffers event happens.
- warn!("New encoder session being created while waiting for RequireInputBuffers.")
+ if create_session {
+ // An encoder session has to be created immediately upon a SetParams
+ // (S_FMT) call, because we need to receive the RequireInputBuffers
+ // callback which has output buffer size info, in order to populate
+ // dst_params to have the correct size on subsequent GetParams (G_FMT) calls.
+ if stream.encoder_session.is_some() {
+ stream.clear_encode_session(wait_ctx)?;
+ if !stream.received_input_buffers_event {
+ // This could happen if two SetParams calls are occuring at the same time.
+ // For example, the user calls SetParams for the input queue on one thread,
+ // and a new encode session is created. Then on another thread, SetParams
+ // is called for the output queue before the first SetParams call has returned.
+ // At this point, there is a new EncodeSession being created that has not
+ // yet received a RequireInputBuffers event.
+ // Even if we clear the encoder session and recreate it, this case
+ // is handled because stream.pending_commands will still contain
+ // the waiting GetParams responses, which will then receive fresh data once
+ // the new session's RequireInputBuffers event happens.
+ warn!(
+ "New encoder session being created while waiting for RequireInputBuffers."
+ )
+ }
}
+ stream.set_encode_session(&mut self.encoder, wait_ctx)?;
}
- stream.set_encode_session(&mut self.encoder, wait_ctx)?;
Ok(VideoCmdResponseType::Sync(CmdResponse::NoData))
}
@@ -1096,7 +1211,13 @@ impl<T: Encoder> EncoderDevice<T> {
.get(&stream_id)
.ok_or(VideoError::InvalidStreamId(stream_id))?;
let ctrl_val = match ctrl_type {
- CtrlType::Bitrate => CtrlVal::Bitrate(stream.dst_bitrate),
+ CtrlType::BitrateMode => CtrlVal::BitrateMode(stream.dst_bitrate.mode()),
+ CtrlType::Bitrate => CtrlVal::Bitrate(stream.dst_bitrate.target()),
+ CtrlType::BitratePeak => CtrlVal::BitratePeak(match stream.dst_bitrate {
+ Bitrate::VBR { peak, .. } => peak,
+ // For CBR there is no peak, so return the target (which is technically correct).
+ Bitrate::CBR { target } => target,
+ }),
CtrlType::Profile => CtrlVal::Profile(stream.dst_profile),
CtrlType::Level => {
let format = stream
@@ -1115,6 +1236,9 @@ impl<T: Encoder> EncoderDevice<T> {
}
// Button controls should not be queried.
CtrlType::ForceKeyframe => return Err(VideoError::UnsupportedControl(ctrl_type)),
+ // Prepending SPS and PPS to IDR is always enabled in the libvda backend.
+ // TODO (b/161495502): account for other backends
+ CtrlType::PrependSpsPpsToIdr => CtrlVal::PrependSpsPpsToIdr(true),
};
Ok(VideoCmdResponseType::Sync(CmdResponse::GetControl(
ctrl_val,
@@ -1123,6 +1247,7 @@ impl<T: Encoder> EncoderDevice<T> {
fn set_control(
&mut self,
+ wait_ctx: &WaitContext<Token>,
stream_id: u32,
ctrl_val: CtrlVal,
) -> VideoResult<VideoCmdResponseType> {
@@ -1130,67 +1255,135 @@ impl<T: Encoder> EncoderDevice<T> {
.streams
.get_mut(&stream_id)
.ok_or(VideoError::InvalidStreamId(stream_id))?;
+ let mut recreate_session = false;
+ let resources_queued = stream.src_resources.len() > 0 || stream.dst_resources.len() > 0;
+
match ctrl_val {
- CtrlVal::Bitrate(bitrate) => {
- if let Some(ref mut encoder_session) = stream.encoder_session {
- if let Err(e) =
- encoder_session.request_encoding_params_change(bitrate, stream.frame_rate)
- {
- error!(
- "failed to dynamically request encoding params change: {}",
- e
- );
+ CtrlVal::BitrateMode(bitrate_mode) => {
+ if stream.dst_bitrate.mode() != bitrate_mode {
+ if resources_queued {
+ error!("set control called for bitrate mode but already encoding.");
return Err(VideoError::InvalidOperation);
}
+ stream.dst_bitrate = match bitrate_mode {
+ BitrateMode::CBR => Bitrate::CBR {
+ target: stream.dst_bitrate.target(),
+ },
+ BitrateMode::VBR => Bitrate::VBR {
+ target: stream.dst_bitrate.target(),
+ peak: stream.dst_bitrate.target(),
+ },
+ };
+ recreate_session = true;
}
- stream.dst_bitrate = bitrate;
}
- CtrlVal::Profile(profile) => {
- if stream.encoder_session.is_some() {
- // TODO(alexlau): If no resources have yet been queued,
- // should the encoder session be recreated with the new
- // desired level?
- error!("set control called for profile but encoder session already exists.");
- return Err(VideoError::InvalidOperation);
+ CtrlVal::Bitrate(bitrate) => {
+ if stream.dst_bitrate.target() != bitrate {
+ let mut new_bitrate = stream.dst_bitrate;
+ match &mut new_bitrate {
+ Bitrate::CBR { target } | Bitrate::VBR { target, .. } => *target = bitrate,
+ }
+ if let Some(ref mut encoder_session) = stream.encoder_session {
+ if let Err(e) = encoder_session.request_encoding_params_change(
+ new_bitrate,
+ stream.dst_params.frame_rate,
+ ) {
+ error!("failed to dynamically request target bitrate change: {}", e);
+ return Err(VideoError::InvalidOperation);
+ }
+ }
+ stream.dst_bitrate = new_bitrate;
}
- let format = stream
- .dst_params
- .format
- .ok_or(VideoError::InvalidArgument)?;
- if format != profile.to_format() {
- error!(
- "specified profile does not correspond to the selected format ({})",
- format
- );
- return Err(VideoError::InvalidOperation);
+ }
+ CtrlVal::BitratePeak(bitrate) => {
+ match stream.dst_bitrate {
+ Bitrate::VBR { peak, .. } => {
+ if peak != bitrate {
+ let new_bitrate = Bitrate::VBR {
+ target: stream.dst_bitrate.target(),
+ peak: bitrate,
+ };
+ if let Some(ref mut encoder_session) = stream.encoder_session {
+ if let Err(e) = encoder_session.request_encoding_params_change(
+ new_bitrate,
+ stream.dst_params.frame_rate,
+ ) {
+ error!(
+ "failed to dynamically request peak bitrate change: {}",
+ e
+ );
+ return Err(VideoError::InvalidOperation);
+ }
+ }
+ stream.dst_bitrate = new_bitrate;
+ }
+ }
+ // Trying to set the peak bitrate while in constant mode. This is not
+ // an error, just ignored.
+ Bitrate::CBR { .. } => {}
}
- stream.dst_profile = profile;
}
- CtrlVal::Level(level) => {
- if stream.encoder_session.is_some() {
- // TODO(alexlau): If no resources have yet been queued,
- // should the encoder session be recreated with the new
- // desired level?
- error!("set control called for level but encoder session already exists.");
- return Err(VideoError::InvalidOperation);
+ CtrlVal::Profile(profile) => {
+ if stream.dst_profile != profile {
+ if resources_queued {
+ error!("set control called for profile but already encoding.");
+ return Err(VideoError::InvalidOperation);
+ }
+ let format = stream
+ .dst_params
+ .format
+ .ok_or(VideoError::InvalidArgument)?;
+ if format != profile.to_format() {
+ error!(
+ "specified profile does not correspond to the selected format ({})",
+ format
+ );
+ return Err(VideoError::InvalidOperation);
+ }
+ stream.dst_profile = profile;
+ recreate_session = true;
}
- let format = stream
- .dst_params
- .format
- .ok_or(VideoError::InvalidArgument)?;
- if format != Format::H264 {
- error!(
- "set control called for level but format is not H264 ({})",
- format
- );
- return Err(VideoError::InvalidOperation);
+ }
+ CtrlVal::Level(level) => {
+ if stream.dst_h264_level != Some(level) {
+ if resources_queued {
+ error!("set control called for level but already encoding.");
+ return Err(VideoError::InvalidOperation);
+ }
+ let format = stream
+ .dst_params
+ .format
+ .ok_or(VideoError::InvalidArgument)?;
+ if format != Format::H264 {
+ error!(
+ "set control called for level but format is not H264 ({})",
+ format
+ );
+ return Err(VideoError::InvalidOperation);
+ }
+ stream.dst_h264_level = Some(level);
+ recreate_session = true;
}
- stream.dst_h264_level = Some(level);
}
- CtrlVal::ForceKeyframe() => {
+ CtrlVal::ForceKeyframe => {
stream.force_keyframe = true;
}
+ CtrlVal::PrependSpsPpsToIdr(prepend_sps_pps_to_idr) => {
+ // Prepending SPS and PPS to IDR is always enabled in the libvda backend,
+ // disabling it will always fail.
+ // TODO (b/161495502): account for other backends
+ if !prepend_sps_pps_to_idr {
+ return Err(VideoError::InvalidOperation);
+ }
+ }
}
+
+ // We can safely recreate the encoder session if no resources were queued yet.
+ if recreate_session && stream.encoder_session.is_some() {
+ stream.clear_encode_session(wait_ctx)?;
+ stream.set_encode_session(&mut self.encoder, wait_ctx)?;
+ }
+
Ok(VideoCmdResponseType::Sync(CmdResponse::SetControl))
}
}
@@ -1200,17 +1393,24 @@ impl<T: Encoder> Device for EncoderDevice<T> {
&mut self,
req: VideoCmd,
wait_ctx: &WaitContext<Token>,
- resource_bridge: &Tube,
) -> (
VideoCmdResponseType,
Option<(u32, Vec<VideoEvtResponseType>)>,
) {
+ let mut event_ret = None;
let cmd_response = match req {
VideoCmd::QueryCapability { queue_type } => self.query_capabilities(queue_type),
VideoCmd::StreamCreate {
stream_id,
coded_format: desired_format,
- } => self.stream_create(stream_id, desired_format),
+ input_resource_type,
+ output_resource_type,
+ } => self.stream_create(
+ stream_id,
+ desired_format,
+ input_resource_type,
+ output_resource_type,
+ ),
VideoCmd::StreamDestroy { stream_id } => self.stream_destroy(stream_id),
VideoCmd::StreamDrain { stream_id } => self.stream_drain(stream_id),
VideoCmd::ResourceCreate {
@@ -1218,15 +1418,14 @@ impl<T: Encoder> Device for EncoderDevice<T> {
queue_type,
resource_id,
plane_offsets,
- uuid,
+ plane_entries,
} => self.resource_create(
wait_ctx,
- resource_bridge,
stream_id,
queue_type,
resource_id,
plane_offsets,
- uuid,
+ plane_entries,
),
VideoCmd::ResourceQueue {
stream_id,
@@ -1234,14 +1433,50 @@ impl<T: Encoder> Device for EncoderDevice<T> {
resource_id,
timestamp,
data_sizes,
- } => self.resource_queue(
- resource_bridge,
- stream_id,
- queue_type,
- resource_id,
- timestamp,
- data_sizes,
- ),
+ } => {
+ let resp =
+ self.resource_queue(stream_id, queue_type, resource_id, timestamp, data_sizes);
+
+ if resp.is_ok() && queue_type == QueueType::Output {
+ if let Some(stream) = self.streams.get_mut(&stream_id) {
+ // If we have a flush pending, add the response for dequeueing the EOS
+ // buffer.
+ if stream.eos_manager.client_awaits_eos {
+ info!(
+ "stream {}: using queued buffer as EOS for pending flush",
+ stream_id
+ );
+ event_ret = match stream.eos_manager.try_complete_eos(vec![]) {
+ Some(eos_resps) => Some((stream_id, eos_resps)),
+ None => {
+ error!("stream {}: try_get_eos_buffer() should have returned a valid response. This is a bug.", stream_id);
+ Some((
+ stream_id,
+ vec![VideoEvtResponseType::Event(VideoEvt {
+ typ: EvtType::Error,
+ stream_id,
+ })],
+ ))
+ }
+ };
+ }
+ } else {
+ error!(
+ "stream {}: the stream ID should be valid here. This is a bug.",
+ stream_id
+ );
+ event_ret = Some((
+ stream_id,
+ vec![VideoEvtResponseType::Event(VideoEvt {
+ typ: EvtType::Error,
+ stream_id,
+ })],
+ ));
+ }
+ }
+
+ resp
+ }
VideoCmd::ResourceDestroyAll { stream_id, .. } => self.resource_destroy_all(stream_id),
VideoCmd::QueueClear {
stream_id,
@@ -1250,7 +1485,8 @@ impl<T: Encoder> Device for EncoderDevice<T> {
VideoCmd::GetParams {
stream_id,
queue_type,
- } => self.get_params(stream_id, queue_type),
+ is_ext,
+ } => self.get_params(stream_id, queue_type, is_ext),
VideoCmd::SetParams {
stream_id,
queue_type,
@@ -1263,6 +1499,7 @@ impl<T: Encoder> Device for EncoderDevice<T> {
plane_formats,
..
},
+ is_ext,
} => self.set_params(
wait_ctx,
stream_id,
@@ -1272,6 +1509,7 @@ impl<T: Encoder> Device for EncoderDevice<T> {
frame_height,
frame_rate,
plane_formats,
+ is_ext,
),
VideoCmd::QueryControl { query_ctrl_type } => self.query_control(query_ctrl_type),
VideoCmd::GetControl {
@@ -1281,7 +1519,7 @@ impl<T: Encoder> Device for EncoderDevice<T> {
VideoCmd::SetControl {
stream_id,
ctrl_val,
- } => self.set_control(stream_id, ctrl_val),
+ } => self.set_control(wait_ctx, stream_id, ctrl_val),
};
let cmd_ret = match cmd_response {
Ok(r) => r,
@@ -1290,7 +1528,7 @@ impl<T: Encoder> Device for EncoderDevice<T> {
VideoCmdResponseType::Sync(e.into())
}
};
- (cmd_ret, None)
+ (cmd_ret, event_ret)
}
fn process_event(
diff --git a/devices/src/virtio/video/error.rs b/devices/src/virtio/video/error.rs
index 49e456a76..535340ffa 100644
--- a/devices/src/virtio/video/error.rs
+++ b/devices/src/virtio/video/error.rs
@@ -4,69 +4,42 @@
//! Errors that can happen while encoding or decoding.
-use std::fmt;
+use remain::sorted;
+use thiserror::Error as ThisError;
-use crate::virtio::resource_bridge::ResourceBridgeError;
use crate::virtio::video::control::CtrlType;
-use crate::virtio::video::encoder::EncoderError;
/// An error indicating something went wrong while encoding or decoding.
/// Unlike `virtio::video::Error`, `VideoError` is not fatal for `Worker`.
-#[derive(Debug)]
+#[sorted]
+#[derive(Debug, ThisError)]
pub enum VideoError {
- /// The encoder implementation returned an error.
- EncoderImpl(EncoderError),
+ /// Backend-specific error.
+ #[error("backend failure: {0:#}")]
+ BackendFailure(anyhow::Error),
/// Invalid argument.
+ #[error("invalid argument")]
InvalidArgument,
- /// Invalid operation
+ /// No suitable format is supported.
+ #[error("invalid format")]
+ InvalidFormat,
+ /// Invalid operation.
+ #[error("invalid operation")]
InvalidOperation,
- /// Invalid stream ID is specified.
- InvalidStreamId(u32),
- /// Invalid resource ID is specified.
- InvalidResourceId { stream_id: u32, resource_id: u32 },
/// Invalid parameters are specified.
+ #[error("invalid parameter")]
InvalidParameter,
- /// Failed to get a resource FD via resource_bridge.
- ResourceBridgeFailure(ResourceBridgeError),
+ /// Invalid resource ID is specified.
+ #[error("invalid resource ID {resource_id} for stream {stream_id}")]
+ InvalidResourceId { stream_id: u32, resource_id: u32 },
+ /// Invalid stream ID is specified.
+ #[error("invalid stream ID {0}")]
+ InvalidStreamId(u32),
/// Unsupported control type is specified.
+ /// This is only used by the encoder for now, ignore warning if it is compiled out.
+ #[allow(dead_code)]
+ #[error("unsupported control: {0:?}")]
UnsupportedControl(CtrlType),
- /// `libvda` returned an error.
- VdaError(libvda::Error),
- /// `libvda` returned a failure response.
- VdaFailure(libvda::decode::Response),
-}
-
-impl fmt::Display for VideoError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::VideoError::*;
- match self {
- InvalidArgument => write!(f, "invalid argument"),
- InvalidOperation => write!(f, "invalid operation"),
- InvalidStreamId(id) => write!(f, "invalid stream ID {}", id),
- InvalidResourceId {
- stream_id,
- resource_id,
- } => write!(
- f,
- "invalid resource ID {} for stream {}",
- resource_id, stream_id
- ),
- InvalidParameter => write!(f, "invalid parameter"),
- ResourceBridgeFailure(id) => write!(f, "failed to get resource FD for id {}", id),
- UnsupportedControl(ctrl_type) => write!(f, "unsupported control: {:?}", ctrl_type),
- VdaError(e) => write!(f, "error occurred in libvda: {}", e),
- VdaFailure(r) => write!(f, "failed while processing a requst in VDA: {}", r),
- EncoderImpl(e) => write!(f, "error occurred in the encoder implementation: {}", e),
- }
- }
}
-impl From<libvda::Error> for VideoError {
- fn from(error: libvda::Error) -> Self {
- VideoError::VdaError(error)
- }
-}
-
-impl std::error::Error for VideoError {}
-
pub type VideoResult<T> = Result<T, VideoError>;
diff --git a/devices/src/virtio/video/event.rs b/devices/src/virtio/video/event.rs
index 072f353c8..16d59387c 100644
--- a/devices/src/virtio/video/event.rs
+++ b/devices/src/virtio/video/event.rs
@@ -16,6 +16,7 @@ use crate::virtio::Writer;
#[derive(Debug, Copy, Clone, N)]
pub enum EvtType {
Error = VIRTIO_VIDEO_EVENT_ERROR as isize,
+ #[cfg(feature = "video-decoder")]
DecResChanged = VIRTIO_VIDEO_EVENT_DECODER_RESOLUTION_CHANGED as isize,
}
diff --git a/devices/src/virtio/video/format.rs b/devices/src/virtio/video/format.rs
index e52695b93..752294c2f 100644
--- a/devices/src/virtio/video/format.rs
+++ b/devices/src/virtio/video/format.rs
@@ -45,27 +45,8 @@ pub enum Profile {
}
impl_try_from_le32_for_enumn!(Profile, "profile");
-macro_rules! impl_libvda_conversion {
- ( $( ( $x:ident, $y:ident ) ),* ) => {
- pub fn from_libvda_profile(p: libvda::Profile) -> Option<Self> {
- match p {
- $(libvda::Profile::$x => Some(Self::$y),)*
- _ => None
- }
- }
-
- // TODO(alexlau): Remove this after encoder CL lands.
- #[allow(dead_code)]
- pub fn to_libvda_profile(&self) -> Option<libvda::Profile> {
- match self {
- $(Self::$y => Some(libvda::Profile::$x),)*
- _ => None
- }
- }
- }
-}
-
impl Profile {
+ #[cfg(any(feature = "video-encoder", feature = "libvda"))]
pub fn to_format(&self) -> Format {
use Profile::*;
match self {
@@ -85,31 +66,6 @@ impl Profile {
VP9Profile0 | VP9Profile1 | VP9Profile2 | VP9Profile3 => Format::VP9,
}
}
-
- impl_libvda_conversion!(
- (H264ProfileBaseline, H264Baseline),
- (H264ProfileMain, H264Main),
- (H264ProfileExtended, H264Extended),
- (H264ProfileHigh, H264High),
- (H264ProfileHigh10Profile, H264High10),
- (H264ProfileHigh422Profile, H264High422),
- (
- H264ProfileHigh444PredictiveProfile,
- H264High444PredictiveProfile
- ),
- (H264ProfileScalableBaseline, H264ScalableBaseline),
- (H264ProfileScalableHigh, H264ScalableHigh),
- (H264ProfileStereoHigh, H264StereoHigh),
- (H264ProfileMultiviewHigh, H264MultiviewHigh),
- (HevcProfileMain, HevcMain),
- (HevcProfileMain10, HevcMain10),
- (HevcProfileMainStillPicture, HevcMainStillPicture),
- (VP8, VP8Profile0),
- (VP9Profile0, VP9Profile0),
- (VP9Profile1, VP9Profile1),
- (VP9Profile2, VP9Profile2),
- (VP9Profile3, VP9Profile3)
- );
}
#[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)]
@@ -162,6 +118,40 @@ impl Display for Format {
}
}
+#[derive(PartialEq, Eq, PartialOrd, Ord, N, Clone, Copy, Debug)]
+#[repr(u32)]
+pub enum BitrateMode {
+ VBR = VIRTIO_VIDEO_BITRATE_MODE_VBR,
+ CBR = VIRTIO_VIDEO_BITRATE_MODE_CBR,
+}
+impl_try_from_le32_for_enumn!(BitrateMode, "bitrate_mode");
+
+#[allow(dead_code)]
+#[derive(Debug, Copy, Clone)]
+pub enum Bitrate {
+ /// Constant bitrate.
+ CBR { target: u32 },
+ /// Variable bitrate.
+ VBR { target: u32, peak: u32 },
+}
+
+#[cfg(feature = "video-encoder")]
+impl Bitrate {
+ pub fn mode(&self) -> BitrateMode {
+ match self {
+ Bitrate::CBR { .. } => BitrateMode::CBR,
+ Bitrate::VBR { .. } => BitrateMode::VBR,
+ }
+ }
+
+ pub fn target(&self) -> u32 {
+ match self {
+ Bitrate::CBR { target } => *target,
+ Bitrate::VBR { target, .. } => *target,
+ }
+ }
+}
+
#[derive(Debug, Default, Copy, Clone)]
pub struct Crop {
pub left: u32,
@@ -171,13 +161,35 @@ pub struct Crop {
}
impl_from_for_interconvertible_structs!(virtio_video_crop, Crop, left, top, width, height);
-#[derive(Debug, Default, Clone, Copy)]
+#[derive(PartialEq, Eq, Debug, Default, Clone, Copy)]
pub struct PlaneFormat {
pub plane_size: u32,
pub stride: u32,
}
impl_from_for_interconvertible_structs!(virtio_video_plane_format, PlaneFormat, plane_size, stride);
+impl PlaneFormat {
+ pub fn get_plane_layout(format: Format, width: u32, height: u32) -> Option<Vec<PlaneFormat>> {
+ match format {
+ Format::NV12 => Some(vec![
+ // Y plane, 1 sample per pixel.
+ PlaneFormat {
+ plane_size: width * height,
+ stride: width,
+ },
+ // UV plane, 1 sample per group of 4 pixels for U and V.
+ PlaneFormat {
+ // Add one vertical line so odd resolutions result in an extra UV line to cover all the
+ // Y samples.
+ plane_size: width * ((height + 1) / 2),
+ stride: width,
+ },
+ ]),
+ _ => None,
+ }
+ }
+}
+
#[derive(Debug, Default, Clone, Copy)]
pub struct FormatRange {
pub min: u32,
@@ -231,6 +243,7 @@ impl Response for FormatDesc {
}
}
+#[cfg(feature = "video-encoder")]
fn clamp_size(size: u32, min: u32, step: u32) -> u32 {
match step {
0 | 1 => size,
@@ -247,6 +260,7 @@ fn clamp_size(size: u32, min: u32, step: u32) -> u32 {
/// Parses a slice of valid frame formats and the desired resolution
/// and returns the closest available resolution.
+#[cfg(feature = "video-encoder")]
pub fn find_closest_resolution(
frame_formats: &[FrameFormat],
desired_width: u32,
@@ -275,10 +289,17 @@ pub fn find_closest_resolution(
}
/// A rectangle used to describe portions of a frame.
-#[derive(Debug)]
+#[derive(Debug, Eq, PartialEq)]
pub struct Rect {
pub left: i32,
pub top: i32,
pub right: i32,
pub bottom: i32,
}
+
+/// Description of the layout for a single plane.
+#[derive(Debug, Clone)]
+pub struct FramePlane {
+ pub offset: usize,
+ pub stride: usize,
+}
diff --git a/devices/src/virtio/video/mod.rs b/devices/src/virtio/video/mod.rs
index b67c7adf2..d9dbfeb43 100644
--- a/devices/src/virtio/video/mod.rs
+++ b/devices/src/virtio/video/mod.rs
@@ -7,11 +7,14 @@
//!
//! [v3 RFC]: https://markmail.org/thread/wxdne5re7aaugbjg
-use std::fmt::{self, Display};
use std::thread;
+#[cfg(feature = "video-encoder")]
+use base::info;
use base::{error, AsRawDescriptor, Error as SysError, Event, RawDescriptor, Tube};
use data_model::{DataInit, Le32};
+use remain::sorted;
+use thiserror::Error;
use vm_memory::GuestMemory;
use crate::virtio::virtio_device::VirtioDevice;
@@ -22,74 +25,76 @@ mod macros;
mod async_cmd_desc_map;
mod command;
mod control;
+#[cfg(feature = "video-decoder")]
mod decoder;
mod device;
+#[cfg(feature = "video-encoder")]
mod encoder;
mod error;
mod event;
mod format;
mod params;
mod protocol;
+mod resource;
mod response;
mod worker;
+#[cfg(all(feature = "video-decoder", not(feature = "libvda")))]
+compile_error!("The \"video-decoder\" feature requires \"libvda\" to also be enabled.");
+
+#[cfg(all(feature = "video-encoder", not(feature = "libvda")))]
+compile_error!("The \"video-encoder\" feature requires \"libvda\" to also be enabled.");
+
+#[cfg(feature = "libvda")]
+mod vda;
+
use command::ReadCmdError;
+use device::Device;
use worker::Worker;
const QUEUE_SIZE: u16 = 256;
const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE];
/// An error indicating something went wrong in virtio-video's worker.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- /// Creating WaitContext failed.
- WaitContextCreationFailed(SysError),
- /// A DescriptorChain contains invalid data.
- InvalidDescriptorChain(DescriptorError),
/// No available descriptor in which an event is written to.
+ #[error("no available descriptor in which an event is written to")]
DescriptorNotAvailable,
- /// Error while polling for events.
- WaitError(SysError),
+ /// A DescriptorChain contains invalid data.
+ #[error("DescriptorChain contains invalid data: {0}")]
+ InvalidDescriptorChain(DescriptorError),
/// Failed to read a virtio-video command.
+ #[error("failed to read a command from the guest: {0}")]
ReadFailure(ReadCmdError),
+ /// Creating WaitContext failed.
+ #[error("failed to create WaitContext: {0}")]
+ WaitContextCreationFailed(SysError),
+ /// Error while polling for events.
+ #[error("failed to wait for events: {0}")]
+ WaitError(SysError),
/// Failed to write an event into the event queue.
+ #[error("failed to write an event {event:?} into event queue: {error}")]
WriteEventFailure {
event: event::VideoEvt,
error: std::io::Error,
},
}
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use Error::*;
- match self {
- WaitContextCreationFailed(e) => write!(f, "failed to create WaitContext: {}", e),
- InvalidDescriptorChain(e) => write!(f, "DescriptorChain contains invalid data: {}", e),
- DescriptorNotAvailable => {
- write!(f, "no available descriptor in which an event is written to")
- }
- WaitError(err) => write!(f, "failed to wait for events: {}", err),
- ReadFailure(e) => write!(f, "failed to read a command from the guest: {}", e),
- WriteEventFailure { event, error } => write!(
- f,
- "failed to write an event {:?} into event queue: {}",
- event, error
- ),
- }
- }
-}
-
-impl std::error::Error for Error {}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum VideoDeviceType {
+ #[cfg(feature = "video-decoder")]
Decoder,
+ #[cfg(feature = "video-encoder")]
Encoder,
}
pub struct VideoDevice {
device_type: VideoDeviceType,
+ backend: VideoBackendType,
kill_evt: Option<Event>,
resource_bridge: Option<Tube>,
base_features: u64,
@@ -99,10 +104,12 @@ impl VideoDevice {
pub fn new(
base_features: u64,
device_type: VideoDeviceType,
+ backend: VideoBackendType,
resource_bridge: Option<Tube>,
) -> VideoDevice {
VideoDevice {
device_type,
+ backend,
kill_evt: None,
resource_bridge,
base_features,
@@ -119,6 +126,14 @@ impl Drop for VideoDevice {
}
}
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum VideoBackendType {
+ #[cfg(feature = "libvda")]
+ Libvda,
+ #[cfg(feature = "libvda")]
+ LibvdaVd,
+}
+
impl VirtioDevice for VideoDevice {
fn keep_rds(&self) -> Vec<RawDescriptor> {
let mut keep_rds = Vec::new();
@@ -130,7 +145,9 @@ impl VirtioDevice for VideoDevice {
fn device_type(&self) -> u32 {
match &self.device_type {
+ #[cfg(feature = "video-decoder")]
VideoDeviceType::Decoder => virtio::TYPE_VIDEO_DEC,
+ #[cfg(feature = "video-encoder")]
VideoDeviceType::Encoder => virtio::TYPE_VIDEO_ENC,
}
}
@@ -140,16 +157,35 @@ impl VirtioDevice for VideoDevice {
}
fn features(&self) -> u64 {
- self.base_features
- | 1u64 << protocol::VIRTIO_VIDEO_F_RESOURCE_NON_CONTIG
- | 1u64 << protocol::VIRTIO_VIDEO_F_RESOURCE_VIRTIO_OBJECT
+ // We specify the type to avoid an extra compilation error in case no backend is enabled
+ // and this match statement becomes empty.
+ let backend_features: u64 = match self.backend {
+ #[cfg(feature = "libvda")]
+ VideoBackendType::Libvda | VideoBackendType::LibvdaVd => {
+ vda::supported_virtio_features()
+ }
+ };
+
+ self.base_features | backend_features
}
fn read_config(&self, offset: u64, data: &mut [u8]) {
+ let mut device_name = [0u8; 32];
+ match self.backend {
+ #[cfg(feature = "libvda")]
+ VideoBackendType::Libvda => {
+ (&mut device_name[0..6]).copy_from_slice("libvda".as_bytes())
+ }
+ #[cfg(feature = "libvda")]
+ VideoBackendType::LibvdaVd => {
+ (&mut device_name[0..8]).copy_from_slice("libvdavd".as_bytes())
+ }
+ };
let mut cfg = protocol::virtio_video_config {
version: Le32::from(0),
max_caps_length: Le32::from(1024), // Set a big number
max_resp_length: Le32::from(1024), // Set a big number
+ device_name,
};
copy_config(data, 0, cfg.as_mut_slice(), offset);
}
@@ -190,6 +226,7 @@ impl VirtioDevice for VideoDevice {
let cmd_evt = queue_evts.remove(0);
let event_queue = queues.remove(0);
let event_evt = queue_evts.remove(0);
+ let backend = self.backend;
let resource_bridge = match self.resource_bridge.take() {
Some(r) => r,
None => {
@@ -197,51 +234,85 @@ impl VirtioDevice for VideoDevice {
return;
}
};
- let mut worker = Worker {
+ let mut worker = Worker::new(
interrupt,
- mem,
+ mem.clone(),
+ cmd_queue,
cmd_evt,
+ event_queue,
event_evt,
kill_evt,
- resource_bridge,
- };
+ );
let worker_result = match &self.device_type {
+ #[cfg(feature = "video-decoder")]
VideoDeviceType::Decoder => thread::Builder::new()
.name("virtio video decoder".to_owned())
.spawn(move || {
- let vda = match libvda::decode::VdaInstance::new(
- libvda::decode::VdaImplType::Gavda,
- ) {
- Ok(vda) => vda,
- Err(e) => {
- error!("Failed to initialize vda: {}", e);
- return;
+ let device: Box<dyn Device> = match backend {
+ #[cfg(feature = "libvda")]
+ VideoBackendType::Libvda => {
+ let vda = match decoder::backend::vda::LibvdaDecoder::new(
+ libvda::decode::VdaImplType::Gavda,
+ ) {
+ Ok(vda) => vda,
+ Err(e) => {
+ error!("Failed to initialize VDA for decoder: {}", e);
+ return;
+ }
+ };
+ Box::new(decoder::Decoder::new(vda, resource_bridge, mem))
+ }
+ #[cfg(feature = "libvda")]
+ VideoBackendType::LibvdaVd => {
+ let vda = match decoder::backend::vda::LibvdaDecoder::new(
+ libvda::decode::VdaImplType::Gavd,
+ ) {
+ Ok(vda) => vda,
+ Err(e) => {
+ error!("Failed to initialize VD for decoder: {}", e);
+ return;
+ }
+ };
+ Box::new(decoder::Decoder::new(vda, resource_bridge, mem))
}
};
- let device = decoder::Decoder::new(&vda);
- if let Err(e) = worker.run(cmd_queue, event_queue, device) {
+
+ if let Err(e) = worker.run(device) {
error!("Failed to start decoder worker: {}", e);
};
// Don't return any information since the return value is never checked.
}),
+ #[cfg(feature = "video-encoder")]
VideoDeviceType::Encoder => thread::Builder::new()
.name("virtio video encoder".to_owned())
.spawn(move || {
- let encoder = match encoder::LibvdaEncoder::new() {
- Ok(vea) => vea,
- Err(e) => {
- error!("Failed to initialize vea: {}", e);
- return;
+ let device: Box<dyn Device> = match backend {
+ #[cfg(feature = "libvda")]
+ VideoBackendType::Libvda => {
+ let vda = match encoder::backend::vda::LibvdaEncoder::new() {
+ Ok(vda) => vda,
+ Err(e) => {
+ error!("Failed to initialize VDA for encoder: {}", e);
+ return;
+ }
+ };
+
+ match encoder::EncoderDevice::new(vda, resource_bridge, mem) {
+ Ok(encoder) => Box::new(encoder),
+ Err(e) => {
+ error!("Failed to create encoder device: {}", e);
+ return;
+ }
+ }
}
- };
- let device = match encoder::EncoderDevice::new(&encoder) {
- Ok(d) => d,
- Err(e) => {
- error!("Failed to create encoder device: {}", e);
+ #[cfg(feature = "libvda")]
+ VideoBackendType::LibvdaVd => {
+ error!("Invalid backend for encoder");
return;
}
};
- if let Err(e) = worker.run(cmd_queue, event_queue, device) {
+
+ if let Err(e) = worker.run(device) {
error!("Failed to start encoder worker: {}", e);
}
}),
@@ -255,3 +326,115 @@ impl VirtioDevice for VideoDevice {
}
}
}
+
+/// Manages the zero-length, EOS-marked buffer signaling the end of a stream.
+///
+/// Both the decoder and encoder need to signal end-of-stream events using a zero-sized buffer
+/// marked with the `VIRTIO_VIDEO_BUFFER_FLAG_EOS` flag. This struct allows to keep a buffer aside
+/// for that purpose.
+///
+/// TODO(b/149725148): Remove this when libvda supports buffer flags.
+#[cfg(feature = "video-encoder")]
+struct EosBufferManager {
+ stream_id: u32,
+ eos_buffer: Option<u32>,
+ client_awaits_eos: bool,
+ responses: Vec<device::VideoEvtResponseType>,
+}
+
+#[cfg(feature = "video-encoder")]
+impl EosBufferManager {
+ /// Create a new EOS manager for stream `stream_id`.
+ fn new(stream_id: u32) -> Self {
+ Self {
+ stream_id,
+ eos_buffer: None,
+ client_awaits_eos: false,
+ responses: Default::default(),
+ }
+ }
+
+ /// Attempt to reserve buffer `buffer_id` for use as EOS buffer.
+ ///
+ /// This method should be called by the output buffer queueing code of the device. It returns
+ /// `true` if the buffer has been kept aside for EOS, `false` otherwise (which means another
+ /// buffer is already kept aside for EOS). If `true` is returned, the client must not use the
+ /// buffer for any other purpose.
+ fn try_reserve_eos_buffer(&mut self, buffer_id: u32) -> bool {
+ let is_none = self.eos_buffer.is_none();
+
+ if is_none {
+ info!(
+ "stream {}: keeping buffer {} aside to signal EOS.",
+ self.stream_id, buffer_id
+ );
+ self.eos_buffer = Some(buffer_id);
+ }
+
+ is_none
+ }
+
+ /// Attempt to complete an EOS event using the previously reserved buffer, if available.
+ ///
+ /// `responses` is a vector of responses to be sent to the driver along with the EOS buffer. If
+ /// an EOS buffer has been made available using the `try_reserve_eos_buffer` method, then this
+ /// method returns the `responses` vector with the EOS buffer dequeue appended in first
+ /// position.
+ ///
+ /// If no EOS buffer is available, then the contents of `responses` is put aside, and will be
+ /// returned the next time this method is called with an EOS buffer available. When this
+ /// happens, `client_awaits_eos` will be set to true, and the client can check this member and
+ /// call this method again right after queuing the next buffer to obtain the EOS response as
+ /// soon as is possible.
+ fn try_complete_eos(
+ &mut self,
+ responses: Vec<device::VideoEvtResponseType>,
+ ) -> Option<Vec<device::VideoEvtResponseType>> {
+ let eos_buffer_id = self.eos_buffer.take().or_else(|| {
+ info!("stream {}: no EOS resource available on successful flush response, waiting for next buffer to be queued.", self.stream_id);
+ self.client_awaits_eos = true;
+ if !self.responses.is_empty() {
+ error!("stream {}: EOS requested while one is already in progress. This is a bug!", self.stream_id);
+ }
+ self.responses = responses;
+ None
+ })?;
+
+ let eos_tag = device::AsyncCmdTag::Queue {
+ stream_id: self.stream_id,
+ queue_type: command::QueueType::Output,
+ resource_id: eos_buffer_id,
+ };
+
+ let eos_response = response::CmdResponse::ResourceQueue {
+ timestamp: 0,
+ flags: protocol::VIRTIO_VIDEO_BUFFER_FLAG_EOS,
+ size: 0,
+ };
+
+ self.client_awaits_eos = false;
+
+ info!(
+ "stream {}: signaling EOS using buffer {}.",
+ self.stream_id, eos_buffer_id
+ );
+
+ let mut responses = std::mem::take(&mut self.responses);
+ responses.insert(
+ 0,
+ device::VideoEvtResponseType::AsyncCmd(device::AsyncCmdResponse::from_response(
+ eos_tag,
+ eos_response,
+ )),
+ );
+
+ Some(responses)
+ }
+
+ /// Reset the state of the manager, for use during e.g. stream resets.
+ fn reset(&mut self) {
+ self.eos_buffer = None;
+ self.client_awaits_eos = false;
+ self.responses.clear();
+ }
+}
diff --git a/devices/src/virtio/video/params.rs b/devices/src/virtio/video/params.rs
index cc16fa708..77ce5da0a 100644
--- a/devices/src/virtio/video/params.rs
+++ b/devices/src/virtio/video/params.rs
@@ -12,6 +12,7 @@ use data_model::Le32;
use crate::virtio::video::command::{QueueType, ReadCmdError};
use crate::virtio::video::format::*;
use crate::virtio::video::protocol::*;
+use crate::virtio::video::resource::ResourceType;
/// Safe wrapper of `virtio_video_params`.
/// Note that this struct doesn't have a field corresponding to `queue_type` in
@@ -21,6 +22,7 @@ pub struct Params {
// Use `Option<Format>` instead of `Format` because an image format may not be determined until
// video decoding is started in the decoder.
pub format: Option<Format>,
+ pub resource_type: ResourceType,
pub frame_width: u32,
pub frame_height: u32,
pub min_buffers: u32,
@@ -63,6 +65,7 @@ impl TryFrom<virtio_video_params> for Params {
Ok(Params {
format: Format::n(format.into()),
+ resource_type: Default::default(),
frame_width: frame_width.into(),
frame_height: frame_height.into(),
min_buffers: min_buffers.into(),
@@ -74,10 +77,32 @@ impl TryFrom<virtio_video_params> for Params {
}
}
+impl TryFrom<virtio_video_params_ext> for Params {
+ type Error = ReadCmdError;
+
+ fn try_from(
+ virtio_video_params_ext {
+ base,
+ resource_type,
+ ..
+ }: virtio_video_params_ext,
+ ) -> Result<Self, Self::Error> {
+ let mut params = Params::try_from(base)?;
+ params.resource_type = match resource_type.into() {
+ VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES => ResourceType::GuestPages,
+ VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT => ResourceType::VirtioObject,
+ _ => return Err(ReadCmdError::InvalidArgument),
+ };
+
+ Ok(params)
+ }
+}
+
impl Params {
pub fn to_virtio_video_params(&self, queue_type: QueueType) -> virtio_video_params {
let Params {
format,
+ resource_type: _,
frame_width,
frame_height,
min_buffers,
@@ -108,4 +133,19 @@ impl Params {
plane_formats: p_fmts,
}
}
+
+ pub fn to_virtio_video_params_ext(&self, queue_type: QueueType) -> virtio_video_params_ext {
+ let Params { resource_type, .. } = self;
+
+ let resource_type = match *resource_type {
+ ResourceType::GuestPages => VIRTIO_VIDEO_MEM_TYPE_GUEST_PAGES,
+ ResourceType::VirtioObject => VIRTIO_VIDEO_MEM_TYPE_VIRTIO_OBJECT,
+ };
+
+ virtio_video_params_ext {
+ base: self.to_virtio_video_params(queue_type),
+ resource_type: Le32::from(resource_type),
+ padding: Default::default(),
+ }
+ }
}
diff --git a/devices/src/virtio/video/protocol.rs b/devices/src/virtio/video/protocol.rs
index ec03f8c12..472b7a684 100644
--- a/devices/src/virtio/video/protocol.rs
+++ b/devices/src/virtio/video/protocol.rs
@@ -6,8 +6,8 @@
//!
//! ```shell
//! $ bindgen virtio_video.h \
-//! --whitelist-type "virtio_video.*" \
-//! --whitelist-var "VIRTIO_VIDEO_.*" \
+//! --allowlist-type "virtio_video.*" \
+//! --allowlist-var "VIRTIO_VIDEO_.*" \
//! --with-derive-default \
//! --no-layout-tests \
//! --no-prepend-enum-name > protocol.rs
@@ -19,6 +19,8 @@
//! * Removed `hdr` from each command struct so that we can read the header and a command body separately.
//! (cf. [related discussion](https://markmail.org/message/tr5g6axqq2zzq64y))
//! * Added implementations of DataInit for each struct.
+//! * Added GET_PARAMS_EXT and SET_PARAMS_EXT to allow querying and changing the resource type
+//! dynamically.
#![allow(dead_code, non_snake_case, non_camel_case_types)]
@@ -93,12 +95,17 @@ pub const VIRTIO_VIDEO_LEVEL_H264_5_0: virtio_video_level = 269;
pub const VIRTIO_VIDEO_LEVEL_H264_5_1: virtio_video_level = 270;
pub const VIRTIO_VIDEO_LEVEL_H264_MAX: virtio_video_level = 270;
pub type virtio_video_level = u32;
+pub const VIRTIO_VIDEO_BITRATE_MODE_VBR: virtio_video_bitrate_mode = 0;
+pub const VIRTIO_VIDEO_BITRATE_MODE_CBR: virtio_video_bitrate_mode = 1;
+pub type virtio_video_bitrate_mode = u32;
+
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct virtio_video_config {
pub version: Le32,
pub max_caps_length: Le32,
pub max_resp_length: Le32,
+ pub device_name: [u8; 32],
}
// Safe because auto-generated structs have no implicit padding.
unsafe impl DataInit for virtio_video_config {}
@@ -116,6 +123,8 @@ pub const VIRTIO_VIDEO_CMD_SET_PARAMS: virtio_video_cmd_type = 265;
pub const VIRTIO_VIDEO_CMD_QUERY_CONTROL: virtio_video_cmd_type = 266;
pub const VIRTIO_VIDEO_CMD_GET_CONTROL: virtio_video_cmd_type = 267;
pub const VIRTIO_VIDEO_CMD_SET_CONTROL: virtio_video_cmd_type = 268;
+pub const VIRTIO_VIDEO_CMD_GET_PARAMS_EXT: virtio_video_cmd_type = 269;
+pub const VIRTIO_VIDEO_CMD_SET_PARAMS_EXT: virtio_video_cmd_type = 270;
pub const VIRTIO_VIDEO_RESP_OK_NODATA: virtio_video_cmd_type = 512;
pub const VIRTIO_VIDEO_RESP_OK_QUERY_CAPABILITY: virtio_video_cmd_type = 513;
pub const VIRTIO_VIDEO_RESP_OK_RESOURCE_QUEUE: virtio_video_cmd_type = 514;
@@ -241,8 +250,6 @@ pub struct virtio_video_mem_entry {
pub struct virtio_video_object_entry {
pub uuid: [u8; 16usize],
}
-// Safe because auto-generated structs have no implicit padding.
-unsafe impl DataInit for virtio_video_object_entry {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
@@ -359,10 +366,49 @@ pub struct virtio_video_set_params {
// Safe because auto-generated structs have no implicit padding.
unsafe impl DataInit for virtio_video_set_params {}
+/// Extension of the {GET,SET}_PARAMS data to also include the resource type. Not including it
+/// was an oversight and the {GET,SET}_PARAMS_EXT commands use this structure to fix it, while
+/// the older {GET,SET}_PARAMS commands are kept for backward compatibility.
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_params_ext {
+ pub base: virtio_video_params,
+ pub resource_type: Le32,
+ pub padding: [u8; 4usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_get_params_ext {
+ pub queue_type: Le32,
+ pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_get_params_ext {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_get_params_ext_resp {
+ pub hdr: virtio_video_cmd_hdr,
+ pub params: virtio_video_params_ext,
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_get_params_ext_resp {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_set_params_ext {
+ pub params: virtio_video_params_ext,
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_set_params_ext {}
+
pub const VIRTIO_VIDEO_CONTROL_BITRATE: virtio_video_control_type = 1;
pub const VIRTIO_VIDEO_CONTROL_PROFILE: virtio_video_control_type = 2;
pub const VIRTIO_VIDEO_CONTROL_LEVEL: virtio_video_control_type = 3;
pub const VIRTIO_VIDEO_CONTROL_FORCE_KEYFRAME: virtio_video_control_type = 4;
+pub const VIRTIO_VIDEO_CONTROL_BITRATE_MODE: virtio_video_control_type = 5;
+pub const VIRTIO_VIDEO_CONTROL_BITRATE_PEAK: virtio_video_control_type = 6;
+pub const VIRTIO_VIDEO_CONTROL_PREPEND_SPSPPS_TO_IDR: virtio_video_control_type = 7;
pub type virtio_video_control_type = u32;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
@@ -437,6 +483,24 @@ unsafe impl DataInit for virtio_video_control_val_bitrate {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_control_val_bitrate_peak {
+ pub bitrate_peak: Le32,
+ pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_control_val_bitrate_peak {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_control_val_bitrate_mode {
+ pub bitrate_mode: Le32,
+ pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_control_val_bitrate_mode {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
pub struct virtio_video_control_val_profile {
pub profile: Le32,
pub padding: [u8; 4usize],
@@ -455,6 +519,15 @@ unsafe impl DataInit for virtio_video_control_val_level {}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_video_control_val_prepend_spspps_to_idr {
+ pub prepend_spspps_to_idr: Le32,
+ pub padding: [u8; 4usize],
+}
+// Safe because auto-generated structs have no implicit padding.
+unsafe impl DataInit for virtio_video_control_val_prepend_spspps_to_idr {}
+
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
pub struct virtio_video_get_control_resp {
pub hdr: virtio_video_cmd_hdr,
}
diff --git a/devices/src/virtio/video/resource.rs b/devices/src/virtio/video/resource.rs
new file mode 100644
index 000000000..d3c99c808
--- /dev/null
+++ b/devices/src/virtio/video/resource.rs
@@ -0,0 +1,402 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Resource management and resolution for the virtio-video device.
+
+use std::convert::TryInto;
+use std::fmt;
+
+use base::{
+ self, FromRawDescriptor, IntoRawDescriptor, MemoryMappingArena, MemoryMappingBuilder,
+ MemoryMappingBuilderUnix, MmapError, SafeDescriptor,
+};
+use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
+
+use thiserror::Error as ThisError;
+
+use crate::virtio::resource_bridge::{self, ResourceBridgeError, ResourceInfo, ResourceRequest};
+use crate::virtio::video::format::{FramePlane, PlaneFormat};
+use crate::virtio::video::protocol::{virtio_video_mem_entry, virtio_video_object_entry};
+
+/// Defines how resources for a given queue are represented.
+#[derive(Clone, Copy, Debug)]
+pub enum ResourceType {
+ /// Resources are backed by guest memory pages.
+ GuestPages,
+ /// Resources are backed by virtio objects.
+ VirtioObject,
+}
+
+impl Default for ResourceType {
+ fn default() -> Self {
+ ResourceType::VirtioObject
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+/// A guest resource entry which type is not decided yet.
+pub union UnresolvedResourceEntry {
+ pub object: virtio_video_object_entry,
+ pub guest_mem: virtio_video_mem_entry,
+}
+unsafe impl data_model::DataInit for UnresolvedResourceEntry {}
+
+impl fmt::Debug for UnresolvedResourceEntry {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ // Safe because `self.object` and `self.guest_mem` are the same size and both made of
+ // integers, making it safe to display them no matter their value.
+ write!(
+ f,
+ "unresolved {:?} or {:?}",
+ unsafe { self.object },
+ unsafe { self.guest_mem }
+ )
+ }
+}
+
+/// Trait for types that can serve as video buffer backing memory.
+pub trait BufferHandle: Sized {
+ /// Try to clone this handle. This must only create a new reference to the same backing memory
+ /// and not duplicate the buffer itself.
+ fn try_clone(&self) -> Result<Self, base::Error>;
+
+ /// Returns a linear mapping of [`offset`..`offset`+`size`] of the memory backing this buffer.
+ fn get_mapping(&self, offset: usize, size: usize) -> Result<MemoryMappingArena, MmapError>;
+}
+
+/// Linear memory area of a `GuestMemHandle`
+#[derive(Clone)]
+pub struct GuestMemArea {
+ /// Offset within the guest region to the start of the area.
+ pub offset: u64,
+ /// Length of the area within the memory region.
+ pub length: usize,
+}
+
+pub struct GuestMemHandle {
+ /// Descriptor to the guest memory region containing the buffer.
+ pub desc: SafeDescriptor,
+ /// Memory areas (i.e. sg list) that make the memory buffer.
+ pub mem_areas: Vec<GuestMemArea>,
+}
+
+impl BufferHandle for GuestMemHandle {
+ fn try_clone(&self) -> Result<Self, base::Error> {
+ Ok(Self {
+ desc: self.desc.try_clone()?,
+ mem_areas: self.mem_areas.clone(),
+ })
+ }
+
+ fn get_mapping(&self, offset: usize, size: usize) -> Result<MemoryMappingArena, MmapError> {
+ let mut arena = MemoryMappingArena::new(size)?;
+ let mut mapped_size = 0;
+ let mut area_iter = self.mem_areas.iter();
+ let mut area_offset = offset;
+ while mapped_size < size {
+ let area = match area_iter.next() {
+ Some(area) => area,
+ None => {
+ return Err(MmapError::InvalidRange(
+ offset,
+ size,
+ self.mem_areas.iter().map(|a| a.length).sum(),
+ ));
+ }
+ };
+ if area_offset > area.length {
+ area_offset -= area.length;
+ } else {
+ let mapping_length = std::cmp::min(area.length - area_offset, size - mapped_size);
+ arena.add_fd_offset(mapped_size, mapping_length, &self.desc, area.offset)?;
+ mapped_size += mapping_length;
+ area_offset = 0;
+ }
+ }
+ Ok(arena)
+ }
+}
+
+pub struct VirtioObjectHandle {
+ /// Descriptor for the object.
+ pub desc: SafeDescriptor,
+ /// Modifier to apply to frame resources.
+ pub modifier: u64,
+}
+
+impl BufferHandle for VirtioObjectHandle {
+ fn try_clone(&self) -> Result<Self, base::Error> {
+ Ok(Self {
+ desc: self.desc.try_clone()?,
+ modifier: self.modifier,
+ })
+ }
+
+ fn get_mapping(&self, offset: usize, size: usize) -> Result<MemoryMappingArena, MmapError> {
+ MemoryMappingBuilder::new(size)
+ .from_descriptor(&self.desc)
+ .offset(offset as u64)
+ .build()
+ .map(MemoryMappingArena::from)
+ }
+}
+
+pub enum GuestResourceHandle {
+ GuestPages(GuestMemHandle),
+ VirtioObject(VirtioObjectHandle),
+}
+
+impl BufferHandle for GuestResourceHandle {
+ fn try_clone(&self) -> Result<Self, base::Error> {
+ Ok(match self {
+ Self::GuestPages(handle) => Self::GuestPages(handle.try_clone()?),
+ Self::VirtioObject(handle) => Self::VirtioObject(handle.try_clone()?),
+ })
+ }
+
+ fn get_mapping(&self, offset: usize, size: usize) -> Result<MemoryMappingArena, MmapError> {
+ match self {
+ GuestResourceHandle::GuestPages(handle) => handle.get_mapping(offset, size),
+ GuestResourceHandle::VirtioObject(handle) => handle.get_mapping(offset, size),
+ }
+ }
+}
+
+pub struct GuestResource {
+ /// Handle to the backing memory.
+ pub handle: GuestResourceHandle,
+ /// Layout of color planes, if the resource will receive frames.
+ pub planes: Vec<FramePlane>,
+}
+
+#[derive(Debug, ThisError)]
+pub enum GuestMemResourceCreationError {
+ #[error("Provided slice of entries is empty")]
+ NoEntriesProvided,
+ #[error("cannot get shm region: {0}")]
+ CantGetShmRegion(GuestMemoryError),
+ #[error("cannot get shm offset: {0}")]
+ CantGetShmOffset(GuestMemoryError),
+ #[error("error while cloning shm region descriptor: {0}")]
+ DescriptorCloneError(base::Error),
+}
+
+#[derive(Debug, ThisError)]
+pub enum ObjectResourceCreationError {
+ #[error("uuid {0:08} is larger than 32 bits")]
+ UuidNot32Bits(u128),
+ #[error("resource returned by bridge is not a buffer")]
+ NotABuffer,
+ #[error("resource bridge failure: {0}")]
+ ResourceBridgeFailure(ResourceBridgeError),
+}
+
+impl GuestResource {
+ /// Try to convert an unresolved virtio guest memory entry into a resolved guest memory
+ /// resource.
+ ///
+ /// Convert `mem_entry` into the guest memory resource it represents and resolve it through
+ /// `mem`. `planes_format` describes the format of the individual planes for the buffer.
+ pub fn from_virtio_guest_mem_entry(
+ mem_entries: &[virtio_video_mem_entry],
+ mem: &GuestMemory,
+ planes_format: &[PlaneFormat],
+ ) -> Result<GuestResource, GuestMemResourceCreationError> {
+ let region_desc = match mem_entries.first() {
+ None => return Err(GuestMemResourceCreationError::NoEntriesProvided),
+ Some(entry) => {
+ let addr: u64 = entry.addr.into();
+
+ let guest_region = mem
+ .shm_region(GuestAddress(addr))
+ .map_err(GuestMemResourceCreationError::CantGetShmRegion)?;
+ let desc = base::clone_descriptor(guest_region)
+ .map_err(GuestMemResourceCreationError::DescriptorCloneError)?;
+ // Safe because we are the sole owner of the duplicated descriptor.
+ unsafe { SafeDescriptor::from_raw_descriptor(desc) }
+ }
+ };
+
+ let mem_areas = mem_entries
+ .iter()
+ .map(|entry| {
+ let addr: u64 = entry.addr.into();
+ let length: u32 = entry.length.into();
+ let region_offset = mem
+ .offset_from_base(GuestAddress(addr))
+ .map_err(GuestMemResourceCreationError::CantGetShmOffset)
+ .unwrap();
+
+ GuestMemArea {
+ offset: region_offset,
+ length: length as usize,
+ }
+ })
+ .collect();
+
+ // The plane information can be computed from the currently set format.
+ let mut buffer_offset = 0;
+ let planes = planes_format
+ .iter()
+ .map(|p| {
+ let plane_offset = buffer_offset;
+ buffer_offset += p.plane_size;
+
+ FramePlane {
+ offset: plane_offset as usize,
+ stride: p.stride as usize,
+ }
+ })
+ .collect();
+
+ Ok(GuestResource {
+ handle: GuestResourceHandle::GuestPages(GuestMemHandle {
+ desc: region_desc,
+ mem_areas,
+ }),
+ planes,
+ })
+ }
+
+ /// Try to convert an unresolved virtio object entry into a resolved object resource.
+ ///
+ /// Convert `object` into the object resource it represents and resolve it through `res_bridge`.
+ /// Returns an error if the object's UUID is invalid or cannot be resolved to a buffer object
+ /// by `res_bridge`.
+ pub fn from_virtio_object_entry(
+ object: virtio_video_object_entry,
+ res_bridge: &base::Tube,
+ ) -> Result<GuestResource, ObjectResourceCreationError> {
+ // We trust that the caller has chosen the correct object type.
+ let uuid = u128::from_be_bytes(object.uuid);
+
+ // TODO(stevensd): `Virtio3DBackend::resource_assign_uuid` is currently implemented to use
+ // 32-bits resource_handles as UUIDs. Once it starts using real UUIDs, we need to update
+ // this conversion.
+ let handle = TryInto::<u32>::try_into(uuid)
+ .map_err(|_| ObjectResourceCreationError::UuidNot32Bits(uuid))?;
+
+ let buffer_info = match resource_bridge::get_resource_info(
+ res_bridge,
+ ResourceRequest::GetBuffer { id: handle },
+ ) {
+ Ok(ResourceInfo::Buffer(buffer_info)) => buffer_info,
+ Ok(_) => return Err(ObjectResourceCreationError::NotABuffer),
+ Err(e) => return Err(ObjectResourceCreationError::ResourceBridgeFailure(e)),
+ };
+
+ Ok(GuestResource {
+ handle: GuestResourceHandle::VirtioObject(VirtioObjectHandle {
+ // Safe because `buffer_info.file` is a valid file descriptor and we are stealing
+ // it.
+ desc: unsafe {
+ SafeDescriptor::from_raw_descriptor(buffer_info.file.into_raw_descriptor())
+ },
+ modifier: buffer_info.modifier,
+ }),
+ planes: buffer_info
+ .planes
+ .iter()
+ .take_while(|p| p.offset != 0 || p.stride != 0)
+ .map(|p| FramePlane {
+ offset: p.offset as usize,
+ stride: p.stride as usize,
+ })
+ .collect(),
+ })
+ }
+
+ #[cfg(feature = "video-encoder")]
+ pub fn try_clone(&self) -> Result<Self, base::Error> {
+ Ok(Self {
+ handle: self.handle.try_clone()?,
+ planes: self.planes.clone(),
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use base::{MappedRegion, SafeDescriptor, SharedMemory};
+
+ /// Creates a sparse guest memory handle using as many pages as there are entries in
+ /// `page_order`. The page with index `0` will be the first page, `1` will be the second page,
+ /// etc.
+ ///
+ /// The memory handle is filled with increasing u32s starting from page 0, then page 1, and so
+ /// on. Finally the handle is mapped into a linear space and we check that the written integers
+ /// appear in the expected order.
+ fn check_guest_mem_handle(page_order: &[usize]) {
+ const PAGE_SIZE: usize = 0x1000;
+ const U32_SIZE: usize = std::mem::size_of::<u32>();
+ const ENTRIES_PER_PAGE: usize = PAGE_SIZE as usize / std::mem::size_of::<u32>();
+
+ // Fill a vector of the same size as the handle with u32s of increasing value, following
+ // the page layout given as argument.
+ let mut data = vec![0u8; PAGE_SIZE * page_order.len()];
+ for (page_index, page) in page_order.iter().enumerate() {
+ let page_slice =
+ &mut data[(page * PAGE_SIZE as usize)..((page + 1) * PAGE_SIZE as usize)];
+ for (index, chunk) in page_slice.chunks_exact_mut(4).enumerate() {
+ let sized_chunk: &mut [u8; 4] = chunk.try_into().unwrap();
+ *sized_chunk = (((page_index * ENTRIES_PER_PAGE) + index) as u32).to_ne_bytes();
+ }
+ }
+
+ // Copy the initialized vector's content into an anonymous shared memory.
+ let mem = SharedMemory::anon(data.len() as u64).unwrap();
+ let mapping = MemoryMappingBuilder::new(mem.size() as usize)
+ .from_shared_memory(&mem)
+ .build()
+ .unwrap();
+ assert_eq!(mapping.write_slice(&data, 0).unwrap(), data.len());
+
+ // Create the `GuestMemHandle` we will try to map and retrieve the data from.
+ let mem_handle = GuestResourceHandle::GuestPages(GuestMemHandle {
+ desc: unsafe {
+ SafeDescriptor::from_raw_descriptor(base::clone_descriptor(&mem).unwrap())
+ },
+ mem_areas: page_order
+ .iter()
+ .map(|&page| GuestMemArea {
+ offset: page as u64 * PAGE_SIZE as u64,
+ length: PAGE_SIZE as usize,
+ })
+ .collect(),
+ });
+
+ // Map the handle into a linear memory area, retrieve its data into a new vector, and check
+ // that its u32s appear to increase linearly.
+ let mapping = mem_handle.get_mapping(0, mem.size() as usize).unwrap();
+ let mut data = vec![0u8; PAGE_SIZE * page_order.len()];
+ unsafe { std::ptr::copy_nonoverlapping(mapping.as_ptr(), data.as_mut_ptr(), data.len()) };
+ for (index, chunk) in data.chunks_exact(U32_SIZE).enumerate() {
+ let sized_chunk: &[u8; 4] = chunk.try_into().unwrap();
+ assert_eq!(u32::from_ne_bytes(*sized_chunk), index as u32);
+ }
+ }
+
+ // Fill a guest memory handle with a single memory page.
+ // Then check that the data can be properly mapped and appears in the expected order.
+ #[test]
+ fn test_single_guest_mem_handle() {
+ check_guest_mem_handle(&[0])
+ }
+
+ // Fill a guest memory handle with 4 memory pages that are contiguous.
+ // Then check that the pages appear in the expected order in the mapping.
+ #[test]
+ fn test_linear_guest_mem_handle() {
+ check_guest_mem_handle(&[0, 1, 2, 3])
+ }
+
+ // Fill a guest memory handle with 8 pages mapped in non-linear order.
+ // Then check that the pages appear in the expected order in the mapping.
+ #[test]
+ fn test_sparse_guest_mem_handle() {
+ check_guest_mem_handle(&[1, 7, 6, 3, 5, 0, 4, 2])
+ }
+}
diff --git a/devices/src/virtio/video/response.rs b/devices/src/virtio/video/response.rs
index a32d3fc1e..05cd05e3d 100644
--- a/devices/src/virtio/video/response.rs
+++ b/devices/src/virtio/video/response.rs
@@ -43,6 +43,7 @@ pub enum CmdResponse {
GetParams {
queue_type: QueueType,
params: Params,
+ is_ext: bool,
},
QueryControl(QueryCtrlResponse),
GetControl(CtrlVal),
@@ -116,9 +117,18 @@ impl Response for CmdResponse {
flags: Le32::from(*flags),
size: Le32::from(*size),
}),
- GetParams { queue_type, params } => {
- let params = params.to_virtio_video_params(*queue_type);
- w.write_obj(virtio_video_get_params_resp { hdr, params })
+ GetParams {
+ queue_type,
+ params,
+ is_ext,
+ } => {
+ if *is_ext {
+ let params = params.to_virtio_video_params_ext(*queue_type);
+ w.write_obj(virtio_video_get_params_ext_resp { hdr, params })
+ } else {
+ let params = params.to_virtio_video_params(*queue_type);
+ w.write_obj(virtio_video_get_params_resp { hdr, params })
+ }
}
QueryControl(r) => {
w.write_obj(virtio_video_query_control_resp { hdr })?;
diff --git a/devices/src/virtio/video/vda.rs b/devices/src/virtio/video/vda.rs
new file mode 100644
index 000000000..e869ba979
--- /dev/null
+++ b/devices/src/virtio/video/vda.rs
@@ -0,0 +1,65 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//! Utility features shared by both the decoder and encoder VDA backends.
+
+use crate::virtio::video::{error::VideoError, format::Profile, protocol};
+
+/// Transparent convertion from libvda error to VideoError backend failure.
+impl From<libvda::Error> for VideoError {
+ fn from(error: libvda::Error) -> Self {
+ VideoError::BackendFailure(error.into())
+ }
+}
+
+macro_rules! impl_libvda_conversion {
+ ( $( ( $x:ident, $y:ident ) ),* ) => {
+ pub fn from_libvda_profile(p: libvda::Profile) -> Option<Self> {
+ match p {
+ $(libvda::Profile::$x => Some(Self::$y),)*
+ _ => None
+ }
+ }
+
+ #[cfg(feature = "video-encoder")]
+ pub fn to_libvda_profile(&self) -> Option<libvda::Profile> {
+ match self {
+ $(Self::$y => Some(libvda::Profile::$x),)*
+ _ => None
+ }
+ }
+ }
+}
+
+impl Profile {
+ impl_libvda_conversion!(
+ (H264ProfileBaseline, H264Baseline),
+ (H264ProfileMain, H264Main),
+ (H264ProfileExtended, H264Extended),
+ (H264ProfileHigh, H264High),
+ (H264ProfileHigh10Profile, H264High10),
+ (H264ProfileHigh422Profile, H264High422),
+ (
+ H264ProfileHigh444PredictiveProfile,
+ H264High444PredictiveProfile
+ ),
+ (H264ProfileScalableBaseline, H264ScalableBaseline),
+ (H264ProfileScalableHigh, H264ScalableHigh),
+ (H264ProfileStereoHigh, H264StereoHigh),
+ (H264ProfileMultiviewHigh, H264MultiviewHigh),
+ (HevcProfileMain, HevcMain),
+ (HevcProfileMain10, HevcMain10),
+ (HevcProfileMainStillPicture, HevcMainStillPicture),
+ (VP8, VP8Profile0),
+ (VP9Profile0, VP9Profile0),
+ (VP9Profile1, VP9Profile1),
+ (VP9Profile2, VP9Profile2),
+ (VP9Profile3, VP9Profile3)
+ );
+}
+
+/// The same set of virtio features is supported by the decoder and encoder.
+pub fn supported_virtio_features() -> u64 {
+ 1u64 << protocol::VIRTIO_VIDEO_F_RESOURCE_NON_CONTIG
+ | 1u64 << protocol::VIRTIO_VIDEO_F_RESOURCE_VIRTIO_OBJECT
+}
diff --git a/devices/src/virtio/video/worker.rs b/devices/src/virtio/video/worker.rs
index f7a3a3214..77e088e9e 100644
--- a/devices/src/virtio/video/worker.rs
+++ b/devices/src/virtio/video/worker.rs
@@ -6,7 +6,7 @@
use std::collections::VecDeque;
-use base::{error, info, Event, Tube, WaitContext};
+use base::{error, info, Event, WaitContext};
use vm_memory::GuestMemory;
use crate::virtio::queue::{DescriptorChain, Queue};
@@ -21,24 +21,44 @@ use crate::virtio::video::{Error, Result};
use crate::virtio::{Interrupt, Reader, SignalableInterrupt, Writer};
pub struct Worker {
- pub interrupt: Interrupt,
- pub mem: GuestMemory,
- pub cmd_evt: Event,
- pub event_evt: Event,
- pub kill_evt: Event,
- pub resource_bridge: Tube,
+ interrupt: Interrupt,
+ mem: GuestMemory,
+ cmd_queue: Queue,
+ cmd_evt: Event,
+ event_queue: Queue,
+ event_evt: Event,
+ kill_evt: Event,
+ // Stores descriptors in which responses for asynchronous commands will be written.
+ desc_map: AsyncCmdDescMap,
}
/// Pair of a descriptor chain and a response to be written.
type WritableResp = (DescriptorChain, response::CmdResponse);
impl Worker {
+ pub fn new(
+ interrupt: Interrupt,
+ mem: GuestMemory,
+ cmd_queue: Queue,
+ cmd_evt: Event,
+ event_queue: Queue,
+ event_evt: Event,
+ kill_evt: Event,
+ ) -> Self {
+ Self {
+ interrupt,
+ mem,
+ cmd_queue,
+ cmd_evt,
+ event_queue,
+ event_evt,
+ kill_evt,
+ desc_map: Default::default(),
+ }
+ }
+
/// Writes responses into the command queue.
- fn write_responses(
- &self,
- cmd_queue: &mut Queue,
- responses: &mut VecDeque<WritableResp>,
- ) -> Result<()> {
+ fn write_responses(&mut self, responses: &mut VecDeque<WritableResp>) -> Result<()> {
if responses.is_empty() {
return Ok(());
}
@@ -52,18 +72,19 @@ impl Worker {
response, e
);
}
- cmd_queue.add_used(&self.mem, desc_index, writer.bytes_written() as u32);
+ self.cmd_queue
+ .add_used(&self.mem, desc_index, writer.bytes_written() as u32);
}
- self.interrupt.signal_used_queue(cmd_queue.vector);
+ self.cmd_queue.trigger_interrupt(&self.mem, &self.interrupt);
Ok(())
}
/// Writes a `VideoEvt` into the event queue.
- fn write_event(&self, event_queue: &mut Queue, event: event::VideoEvt) -> Result<()> {
- let desc = event_queue
- .peek(&self.mem)
+ fn write_event(&mut self, event: event::VideoEvt) -> Result<()> {
+ let desc = self
+ .event_queue
+ .pop(&self.mem)
.ok_or(Error::DescriptorNotAvailable)?;
- event_queue.pop_peeked(&self.mem);
let desc_index = desc.index;
let mut writer =
@@ -71,17 +92,16 @@ impl Worker {
event
.write(&mut writer)
.map_err(|error| Error::WriteEventFailure { event, error })?;
- event_queue.add_used(&self.mem, desc_index, writer.bytes_written() as u32);
- self.interrupt.signal_used_queue(event_queue.vector);
+ self.event_queue
+ .add_used(&self.mem, desc_index, writer.bytes_written() as u32);
+ self.event_queue
+ .trigger_interrupt(&self.mem, &self.interrupt);
Ok(())
}
fn write_event_responses(
- &self,
+ &mut self,
event_responses: Vec<VideoEvtResponseType>,
- cmd_queue: &mut Queue,
- event_queue: &mut Queue,
- desc_map: &mut AsyncCmdDescMap,
stream_id: u32,
) -> Result<()> {
let mut responses: VecDeque<WritableResp> = Default::default();
@@ -92,7 +112,7 @@ impl Worker {
tag,
response: cmd_result,
} = async_response;
- match desc_map.remove(&tag) {
+ match self.desc_map.remove(&tag) {
Some(desc) => {
let cmd_response = match cmd_result {
Ok(r) => r,
@@ -119,21 +139,18 @@ impl Worker {
}
}
VideoEvtResponseType::Event(evt) => {
- self.write_event(event_queue, evt)?;
+ self.write_event(evt)?;
}
}
}
- if let Err(e) = self.write_responses(cmd_queue, &mut responses) {
+ if let Err(e) = self.write_responses(&mut responses) {
error!("Failed to write event responses: {:?}", e);
// Ignore result of write_event for a fatal error.
- let _ = self.write_event(
- event_queue,
- VideoEvt {
- typ: EvtType::Error,
- stream_id,
- },
- );
+ let _ = self.write_event(VideoEvt {
+ typ: EvtType::Error,
+ stream_id,
+ });
return Err(e);
}
@@ -142,13 +159,10 @@ impl Worker {
/// Handles a `DescriptorChain` value sent via the command queue and returns a `VecDeque`
/// of `WritableResp` to be sent to the guest.
- fn handle_command_desc<T: Device>(
- &self,
- cmd_queue: &mut Queue,
- event_queue: &mut Queue,
- device: &mut T,
+ fn handle_command_desc(
+ &mut self,
+ device: &mut dyn Device,
wait_ctx: &WaitContext<Token>,
- desc_map: &mut AsyncCmdDescMap,
desc: DescriptorChain,
) -> Result<VecDeque<WritableResp>> {
let mut responses: VecDeque<WritableResp> = Default::default();
@@ -164,10 +178,12 @@ impl Worker {
VideoCmd::ResourceDestroyAll {
stream_id,
queue_type,
- } => desc_map.create_cancellation_responses(&stream_id, Some(queue_type), None),
- VideoCmd::StreamDestroy { stream_id } => {
- desc_map.create_cancellation_responses(&stream_id, None, None)
- }
+ } => self
+ .desc_map
+ .create_cancellation_responses(&stream_id, Some(queue_type), None),
+ VideoCmd::StreamDestroy { stream_id } => self
+ .desc_map
+ .create_cancellation_responses(&stream_id, None, None),
VideoCmd::QueueClear {
stream_id,
queue_type: QueueType::Output,
@@ -175,7 +191,11 @@ impl Worker {
// TODO(b/153406792): Due to a workaround for a limitation in the VDA api,
// clearing the output queue doesn't go through the same Async path as clearing
// the input queue. However, we still need to cancel the pending resources.
- desc_map.create_cancellation_responses(&stream_id, Some(QueueType::Output), None)
+ self.desc_map.create_cancellation_responses(
+ &stream_id,
+ Some(QueueType::Output),
+ None,
+ )
}
_ => Default::default(),
};
@@ -191,7 +211,7 @@ impl Worker {
e.into()
}
};
- match desc_map.remove(&tag) {
+ match self.desc_map.remove(&tag) {
Some(destroy_desc) => {
responses.push_back((destroy_desc, destroy_response));
}
@@ -200,8 +220,7 @@ impl Worker {
}
// Process the command by the device.
- let (cmd_response, event_responses_with_id) =
- device.process_cmd(cmd, &wait_ctx, &self.resource_bridge);
+ let (cmd_response, event_responses_with_id) = device.process_cmd(cmd, wait_ctx);
match cmd_response {
VideoCmdResponseType::Sync(r) => {
responses.push_back((desc, r));
@@ -210,67 +229,39 @@ impl Worker {
// If the command expects an asynchronous response,
// store `desc` to use it after the back-end device notifies the
// completion.
- desc_map.insert(tag, desc);
+ self.desc_map.insert(tag, desc);
}
}
if let Some((stream_id, event_responses)) = event_responses_with_id {
- self.write_event_responses(
- event_responses,
- cmd_queue,
- event_queue,
- desc_map,
- stream_id,
- )?;
+ self.write_event_responses(event_responses, stream_id)?;
}
Ok(responses)
}
/// Handles each command in the command queue.
- fn handle_command_queue<T: Device>(
- &self,
- cmd_queue: &mut Queue,
- event_queue: &mut Queue,
- device: &mut T,
+ fn handle_command_queue(
+ &mut self,
+ device: &mut dyn Device,
wait_ctx: &WaitContext<Token>,
- desc_map: &mut AsyncCmdDescMap,
) -> Result<()> {
let _ = self.cmd_evt.read();
- while let Some(desc) = cmd_queue.pop(&self.mem) {
- let mut resps =
- self.handle_command_desc(cmd_queue, event_queue, device, wait_ctx, desc_map, desc)?;
- self.write_responses(cmd_queue, &mut resps)?;
+ while let Some(desc) = self.cmd_queue.pop(&self.mem) {
+ let mut resps = self.handle_command_desc(device, wait_ctx, desc)?;
+ self.write_responses(&mut resps)?;
}
Ok(())
}
/// Handles an event notified via an event.
- fn handle_event<T: Device>(
- &self,
- cmd_queue: &mut Queue,
- event_queue: &mut Queue,
- device: &mut T,
- desc_map: &mut AsyncCmdDescMap,
- stream_id: u32,
- ) -> Result<()> {
- if let Some(event_responses) = device.process_event(desc_map, stream_id) {
- self.write_event_responses(
- event_responses,
- cmd_queue,
- event_queue,
- desc_map,
- stream_id,
- )?;
+ fn handle_event(&mut self, device: &mut dyn Device, stream_id: u32) -> Result<()> {
+ if let Some(event_responses) = device.process_event(&mut self.desc_map, stream_id) {
+ self.write_event_responses(event_responses, stream_id)?;
}
Ok(())
}
- pub fn run<T: Device>(
- &mut self,
- mut cmd_queue: Queue,
- mut event_queue: Queue,
- mut device: T,
- ) -> Result<()> {
+ pub fn run(&mut self, mut device: Box<dyn Device>) -> Result<()> {
let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
(&self.cmd_evt, Token::CmdQueue),
(&self.event_evt, Token::EventQueue),
@@ -284,34 +275,19 @@ impl Worker {
})
.map_err(Error::WaitContextCreationFailed)?;
- // Stores descriptors in which responses for asynchronous commands will be written.
- let mut desc_map: AsyncCmdDescMap = Default::default();
-
loop {
let wait_events = wait_ctx.wait().map_err(Error::WaitError)?;
for wait_event in wait_events.iter().filter(|e| e.is_readable) {
match wait_event.token {
Token::CmdQueue => {
- self.handle_command_queue(
- &mut cmd_queue,
- &mut event_queue,
- &mut device,
- &wait_ctx,
- &mut desc_map,
- )?;
+ self.handle_command_queue(device.as_mut(), &wait_ctx)?;
}
Token::EventQueue => {
let _ = self.event_evt.read();
}
Token::Event { id } => {
- self.handle_event(
- &mut cmd_queue,
- &mut event_queue,
- &mut device,
- &mut desc_map,
- id,
- )?;
+ self.handle_event(device.as_mut(), id)?;
}
Token::InterruptResample => {
self.interrupt.interrupt_resample();
diff --git a/devices/src/virtio/virtio_device.rs b/devices/src/virtio/virtio_device.rs
index c181b448e..75b2b8bf7 100644
--- a/devices/src/virtio/virtio_device.rs
+++ b/devices/src/virtio/virtio_device.rs
@@ -2,11 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use acpi_tables::sdt::SDT;
use base::{Event, RawDescriptor};
use vm_memory::GuestMemory;
use super::*;
-use crate::pci::{MsixStatus, PciAddress, PciBarConfiguration, PciCapability};
+use crate::pci::{MsixStatus, PciAddress, PciBarConfiguration, PciBarIndex, PciCapability};
/// Trait for virtio devices to be driven by a virtio transport.
///
@@ -34,7 +36,13 @@ pub trait VirtioDevice: Send {
/// The maximum size of each queue that this device supports.
fn queue_max_sizes(&self) -> &[u16];
+ /// The number of interrupts used by this device.
+ fn num_interrupts(&self) -> usize {
+ self.queue_max_sizes().len()
+ }
+
/// The set of feature bits that this device supports in addition to the base features.
+ /// If this returns VIRTIO_F_ACCESS_PLATFORM, virtio-iommu will be enabled for this device.
fn features(&self) -> u64 {
0
}
@@ -87,4 +95,29 @@ pub trait VirtioDevice: Send {
fn on_device_sandboxed(&mut self) {}
fn control_notify(&self, _behavior: MsixStatus) {}
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ fn generate_acpi(
+ &mut self,
+ _pci_address: &Option<PciAddress>,
+ sdts: Vec<SDT>,
+ ) -> Option<Vec<SDT>> {
+ Some(sdts)
+ }
+
+ /// Reads from a BAR region mapped in to the device.
+ /// * `addr` - The guest address inside the BAR.
+ /// * `data` - Filled with the data from `addr`.
+ fn read_bar(&mut self, _bar_index: PciBarIndex, _offset: u64, _data: &mut [u8]) {}
+
+ /// Writes to a BAR region mapped in to the device.
+ /// * `addr` - The guest address inside the BAR.
+ /// * `data` - The data to write.
+ fn write_bar(&mut self, _bar_index: PciBarIndex, _offset: u64, _data: &[u8]) {}
+
+ /// Returns the PCI address where the device will be allocated.
+ /// Returns `None` if any address is good for the device.
+ fn pci_address(&self) -> Option<PciAddress> {
+ None
+ }
}
diff --git a/devices/src/virtio/virtio_pci_common_config.rs b/devices/src/virtio/virtio_pci_common_config.rs
index ea8975f9e..023c1b065 100644
--- a/devices/src/virtio/virtio_pci_common_config.rs
+++ b/devices/src/virtio/virtio_pci_common_config.rs
@@ -248,7 +248,7 @@ mod tests {
struct DummyDevice(u32);
const QUEUE_SIZE: u16 = 256;
- const QUEUE_SIZES: &'static [u16] = &[QUEUE_SIZE];
+ const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE];
const DUMMY_FEATURES: u64 = 0x5555_aaaa;
impl VirtioDevice for DummyDevice {
fn keep_rds(&self) -> Vec<RawDescriptor> {
diff --git a/devices/src/virtio/virtio_pci_device.rs b/devices/src/virtio/virtio_pci_device.rs
index b0f63d1d1..fc1a852e4 100644
--- a/devices/src/virtio/virtio_pci_device.rs
+++ b/devices/src/virtio/virtio_pci_device.rs
@@ -6,7 +6,10 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use sync::Mutex;
-use base::{warn, AsRawDescriptor, Event, RawDescriptor, Result, Tube};
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use acpi_tables::sdt::SDT;
+use anyhow::{anyhow, bail, Context};
+use base::{error, AsRawDescriptors, Event, RawDescriptor, Result, Tube};
use data_model::{DataInit, Le32};
use hypervisor::Datamatch;
use libc::ERANGE;
@@ -15,36 +18,45 @@ use vm_memory::GuestMemory;
use super::*;
use crate::pci::{
- MsixCap, MsixConfig, PciAddress, PciBarConfiguration, PciCapability, PciCapabilityID,
- PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciDisplaySubclass, PciHeaderType,
- PciInterruptPin, PciSubclass,
+ BarRange, MsixCap, MsixConfig, PciAddress, PciBarConfiguration, PciBarPrefetchable,
+ PciBarRegionType, PciCapability, PciCapabilityID, PciClassCode, PciConfiguration, PciDevice,
+ PciDeviceError, PciDisplaySubclass, PciHeaderType, PciId, PciInterruptPin, PciSubclass,
};
+use crate::virtio::ipc_memory_mapper::IpcMemoryMapper;
+use crate::IrqLevelEvent;
use self::virtio_pci_common_config::VirtioPciCommonConfig;
+#[repr(u8)]
+#[derive(Debug, Copy, Clone, enumn::N)]
pub enum PciCapabilityType {
CommonConfig = 1,
NotifyConfig = 2,
IsrConfig = 3,
DeviceConfig = 4,
PciConfig = 5,
+ // Doorbell, Notification and SharedMemory are Virtio Vhost User related PCI
+ // capabilities. Specified in 5.7.7.4 here
+ // https://stefanha.github.io/virtio/vhost-user-slave.html#x1-2830007.
+ DoorbellConfig = 6,
+ NotificationConfig = 7,
SharedMemoryConfig = 8,
}
#[allow(dead_code)]
#[repr(C)]
#[derive(Clone, Copy)]
-struct VirtioPciCap {
- // _cap_vndr and _cap_next are autofilled based on id() in pci configuration
- _cap_vndr: u8, // Generic PCI field: PCI_CAP_ID_VNDR
- _cap_next: u8, // Generic PCI field: next ptr
- cap_len: u8, // Generic PCI field: capability length
- cfg_type: u8, // Identifies the structure.
- bar: u8, // Where to find it.
+pub struct VirtioPciCap {
+ // cap_vndr and cap_next are autofilled based on id() in pci configuration
+ pub cap_vndr: u8, // Generic PCI field: PCI_CAP_ID_VNDR
+ pub cap_next: u8, // Generic PCI field: next ptr
+ pub cap_len: u8, // Generic PCI field: capability length
+ pub cfg_type: u8, // Identifies the structure.
+ pub bar: u8, // Where to find it.
id: u8, // Multiple capabilities of the same type
padding: [u8; 2], // Pad to full dword.
- offset: Le32, // Offset within bar.
- length: Le32, // Length of the structure, in bytes.
+ pub offset: Le32, // Offset within bar.
+ pub length: Le32, // Length of the structure, in bytes.
}
// It is safe to implement DataInit; all members are simple numbers and any value is valid.
unsafe impl DataInit for VirtioPciCap {}
@@ -57,13 +69,17 @@ impl PciCapability for VirtioPciCap {
fn id(&self) -> PciCapabilityID {
PciCapabilityID::VendorSpecific
}
+
+ fn writable_bits(&self) -> Vec<u32> {
+ vec![0u32; 4]
+ }
}
impl VirtioPciCap {
pub fn new(cfg_type: PciCapabilityType, bar: u8, offset: u32, length: u32) -> Self {
VirtioPciCap {
- _cap_vndr: 0,
- _cap_next: 0,
+ cap_vndr: 0,
+ cap_next: 0,
cap_len: std::mem::size_of::<VirtioPciCap>() as u8,
cfg_type: cfg_type as u8,
bar,
@@ -73,6 +89,10 @@ impl VirtioPciCap {
length: Le32::from(length),
}
}
+
+ pub fn set_cap_len(&mut self, cap_len: u8) {
+ self.cap_len = cap_len;
+ }
}
#[allow(dead_code)]
@@ -93,6 +113,10 @@ impl PciCapability for VirtioPciNotifyCap {
fn id(&self) -> PciCapabilityID {
PciCapabilityID::VendorSpecific
}
+
+ fn writable_bits(&self) -> Vec<u32> {
+ vec![0u32; 5]
+ }
}
impl VirtioPciNotifyCap {
@@ -105,8 +129,8 @@ impl VirtioPciNotifyCap {
) -> Self {
VirtioPciNotifyCap {
cap: VirtioPciCap {
- _cap_vndr: 0,
- _cap_next: 0,
+ cap_vndr: 0,
+ cap_next: 0,
cap_len: std::mem::size_of::<VirtioPciNotifyCap>() as u8,
cfg_type: cfg_type as u8,
bar,
@@ -138,14 +162,18 @@ impl PciCapability for VirtioPciShmCap {
fn id(&self) -> PciCapabilityID {
PciCapabilityID::VendorSpecific
}
+
+ fn writable_bits(&self) -> Vec<u32> {
+ vec![0u32; 6]
+ }
}
impl VirtioPciShmCap {
pub fn new(cfg_type: PciCapabilityType, bar: u8, offset: u64, length: u64, shmid: u8) -> Self {
VirtioPciShmCap {
cap: VirtioPciCap {
- _cap_vndr: 0,
- _cap_next: 0,
+ cap_vndr: 0,
+ cap_next: 0,
cap_len: std::mem::size_of::<VirtioPciShmCap>() as u8,
cfg_type: cfg_type as u8,
bar,
@@ -205,15 +233,16 @@ pub struct VirtioPciDevice {
device_activated: bool,
interrupt_status: Arc<AtomicUsize>,
- interrupt_evt: Option<Event>,
- interrupt_resample_evt: Option<Event>,
+ interrupt_evt: Option<IrqLevelEvent>,
queues: Vec<Queue>,
queue_evts: Vec<Event>,
- mem: Option<GuestMemory>,
+ mem: GuestMemory,
settings_bar: u8,
msix_config: Arc<Mutex<MsixConfig>>,
msix_cap_reg_idx: Option<usize>,
common_config: VirtioPciCommonConfig,
+
+ iommu: Option<Arc<Mutex<IpcMemoryMapper>>>,
}
impl VirtioPciDevice {
@@ -227,7 +256,7 @@ impl VirtioPciDevice {
for _ in device.queue_max_sizes() {
queue_evts.push(Event::new()?)
}
- let queues = device
+ let queues: Vec<Queue> = device
.queue_max_sizes()
.iter()
.map(|&s| Queue::new(s))
@@ -241,16 +270,21 @@ impl VirtioPciDevice {
&PciDisplaySubclass::Other as &dyn PciSubclass,
),
_ => (
- PciClassCode::Other,
+ PciClassCode::TooOld,
&PciVirtioSubclass::NonTransitionalBase as &dyn PciSubclass,
),
};
- let num_queues = device.queue_max_sizes().len();
+ let num_interrupts = device.num_interrupts();
// One MSI-X vector per queue plus one for configuration changes.
- let msix_num = u16::try_from(num_queues + 1).map_err(|_| base::Error::new(ERANGE))?;
- let msix_config = Arc::new(Mutex::new(MsixConfig::new(msix_num, msi_device_tube)));
+ let msix_num = u16::try_from(num_interrupts + 1).map_err(|_| base::Error::new(ERANGE))?;
+ let msix_config = Arc::new(Mutex::new(MsixConfig::new(
+ msix_num,
+ msi_device_tube,
+ PciId::new(VIRTIO_PCI_VENDOR_ID, pci_device_id).into(),
+ device.debug_label(),
+ )));
let config_regs = PciConfiguration::new(
VIRTIO_PCI_VENDOR_ID,
@@ -266,15 +300,14 @@ impl VirtioPciDevice {
Ok(VirtioPciDevice {
config_regs,
- pci_address: None,
+ pci_address: device.pci_address(),
device,
device_activated: false,
interrupt_status: Arc::new(AtomicUsize::new(0)),
interrupt_evt: None,
- interrupt_resample_evt: None,
queues,
queue_evts,
- mem: Some(mem),
+ mem,
settings_bar: 0,
msix_config,
msix_cap_reg_idx: None,
@@ -286,6 +319,7 @@ impl VirtioPciDevice {
queue_select: 0,
msix_config: VIRTIO_MSI_NO_VECTOR,
},
+ iommu: None,
})
}
@@ -302,15 +336,11 @@ impl VirtioPciDevice {
}
fn are_queues_valid(&self) -> bool {
- if let Some(mem) = self.mem.as_ref() {
- // All queues marked as ready must be valid.
- self.queues
- .iter()
- .filter(|q| q.ready)
- .all(|q| q.is_valid(mem))
- } else {
- false
- }
+ // All queues marked as ready must be valid.
+ self.queues
+ .iter()
+ .filter(|q| q.ready)
+ .all(|q| q.is_valid(&self.mem))
}
fn add_settings_pci_capabilities(
@@ -386,9 +416,58 @@ impl VirtioPciDevice {
fn clone_queue_evts(&self) -> Result<Vec<Event>> {
self.queue_evts.iter().map(|e| e.try_clone()).collect()
}
+
+ /// Activates the underlying `VirtioDevice`. `assign_irq` has to be called first.
+ fn activate(&mut self) -> anyhow::Result<()> {
+ let interrupt_evt = if let Some(ref evt) = self.interrupt_evt {
+ evt.try_clone()
+ .with_context(|| format!("{} failed to clone interrupt_evt", self.debug_label()))?
+ } else {
+ return Err(anyhow!("{} interrupt_evt is none", self.debug_label()));
+ };
+
+ let mem = self.mem.clone();
+
+ let interrupt = Interrupt::new(
+ self.interrupt_status.clone(),
+ interrupt_evt,
+ Some(self.msix_config.clone()),
+ self.common_config.msix_config,
+ );
+
+ match self.clone_queue_evts() {
+ Ok(queue_evts) => {
+ // Use ready queues and their events.
+ let (queues, queue_evts) = self
+ .queues
+ .clone()
+ .into_iter()
+ .zip(queue_evts.into_iter())
+ .filter(|(q, _)| q.ready)
+ .unzip();
+
+ self.device.activate(mem, interrupt, queues, queue_evts);
+ self.device_activated = true;
+ }
+ Err(e) => {
+ bail!(
+ "{} not activate due to failed to clone queue_evts: {}",
+ self.debug_label(),
+ e
+ );
+ }
+ }
+ Ok(())
+ }
}
impl PciDevice for VirtioPciDevice {
+ fn supports_iommu(&self) -> bool {
+ // ANDROID: b/226445312
+ // (self.device.features() & (1 << VIRTIO_F_ACCESS_PLATFORM)) != 0
+ false
+ }
+
fn debug_label(&self) -> String {
format!("pci{}", self.device.debug_label())
}
@@ -398,7 +477,7 @@ impl PciDevice for VirtioPciDevice {
resources: &mut SystemAllocator,
) -> std::result::Result<PciAddress, PciDeviceError> {
if self.pci_address.is_none() {
- self.pci_address = match resources.allocate_pci(self.debug_label()) {
+ self.pci_address = match resources.allocate_pci(0, self.debug_label()) {
Some(Alloc::PciBar {
bus,
dev,
@@ -414,37 +493,40 @@ impl PciDevice for VirtioPciDevice {
fn keep_rds(&self) -> Vec<RawDescriptor> {
let mut rds = self.device.keep_rds();
if let Some(interrupt_evt) = &self.interrupt_evt {
- rds.push(interrupt_evt.as_raw_descriptor());
- }
- if let Some(interrupt_resample_evt) = &self.interrupt_resample_evt {
- rds.push(interrupt_resample_evt.as_raw_descriptor());
+ rds.extend(interrupt_evt.as_raw_descriptors());
}
let descriptor = self.msix_config.lock().get_msi_socket();
rds.push(descriptor);
+ if let Some(iommu) = &self.iommu {
+ rds.append(&mut iommu.lock().as_raw_descriptors());
+ }
rds
}
fn assign_irq(
&mut self,
- irq_evt: Event,
- irq_resample_evt: Event,
- irq_num: u32,
- irq_pin: PciInterruptPin,
- ) {
- self.config_regs.set_irq(irq_num as u8, irq_pin);
- self.interrupt_evt = Some(irq_evt);
- self.interrupt_resample_evt = Some(irq_resample_evt);
+ irq_evt: &IrqLevelEvent,
+ irq_num: Option<u32>,
+ ) -> Option<(u32, PciInterruptPin)> {
+ self.interrupt_evt = Some(irq_evt.try_clone().ok()?);
+ let gsi = irq_num?;
+ let pin = self.pci_address.map_or(
+ PciInterruptPin::IntA,
+ PciConfiguration::suggested_interrupt_pin,
+ );
+ self.config_regs.set_irq(gsi as u8, pin);
+ Some((gsi, pin))
}
fn allocate_io_bars(
&mut self,
resources: &mut SystemAllocator,
- ) -> std::result::Result<Vec<(u64, u64)>, PciDeviceError> {
+ ) -> std::result::Result<Vec<BarRange>, PciDeviceError> {
let address = self
.pci_address
.expect("allocaten_address must be called prior to allocate_io_bars");
// Allocate one bar for the structures pointed to by the capability structures.
- let mut ranges = Vec::new();
+ let mut ranges: Vec<BarRange> = Vec::new();
let settings_config_addr = resources
.mmio_allocator(MmioType::Low)
.allocate_with_align(
@@ -462,16 +544,23 @@ impl PciDevice for VirtioPciDevice {
CAPABILITY_BAR_SIZE,
)
.map_err(|e| PciDeviceError::IoAllocationFailed(CAPABILITY_BAR_SIZE, e))?;
- let config = PciBarConfiguration::default()
- .set_register_index(0)
- .set_address(settings_config_addr)
- .set_size(CAPABILITY_BAR_SIZE);
+ let config = PciBarConfiguration::new(
+ 0,
+ CAPABILITY_BAR_SIZE,
+ PciBarRegionType::Memory32BitRegion,
+ PciBarPrefetchable::NotPrefetchable,
+ )
+ .set_address(settings_config_addr);
let settings_bar = self
.config_regs
.add_pci_bar(config)
.map_err(|e| PciDeviceError::IoRegistrationFailed(settings_config_addr, e))?
as u8;
- ranges.push((settings_config_addr, CAPABILITY_BAR_SIZE));
+ ranges.push(BarRange {
+ addr: settings_config_addr,
+ size: CAPABILITY_BAR_SIZE,
+ prefetchable: false,
+ });
// Once the BARs are allocated, the capabilities can be added to the PCI configuration.
self.add_settings_pci_capabilities(settings_bar)?;
@@ -482,39 +571,47 @@ impl PciDevice for VirtioPciDevice {
fn allocate_device_bars(
&mut self,
resources: &mut SystemAllocator,
- ) -> std::result::Result<Vec<(u64, u64)>, PciDeviceError> {
+ ) -> std::result::Result<Vec<BarRange>, PciDeviceError> {
let address = self
.pci_address
.expect("allocaten_address must be called prior to allocate_device_bars");
- let mut ranges = Vec::new();
+ let mut ranges: Vec<BarRange> = Vec::new();
for config in self.device.get_device_bars(address) {
let device_addr = resources
- .mmio_allocator_any()
+ .mmio_allocator(MmioType::High)
.allocate_with_align(
- config.get_size(),
+ config.size(),
Alloc::PciBar {
bus: address.bus,
dev: address.dev,
func: address.func,
- bar: config.get_register_index() as u8,
+ bar: config.bar_index() as u8,
},
format!(
"virtio-{}-custom_bar",
type_to_str(self.device.device_type()).unwrap_or("?")
),
- config.get_size(),
+ config.size(),
)
- .map_err(|e| PciDeviceError::IoAllocationFailed(config.get_size(), e))?;
+ .map_err(|e| PciDeviceError::IoAllocationFailed(config.size(), e))?;
let config = config.set_address(device_addr);
let _device_bar = self
.config_regs
.add_pci_bar(config)
.map_err(|e| PciDeviceError::IoRegistrationFailed(device_addr, e))?;
- ranges.push((device_addr, config.get_size()));
+ ranges.push(BarRange {
+ addr: device_addr,
+ size: config.size(),
+ prefetchable: false,
+ });
}
Ok(ranges)
}
+ fn get_bar_configuration(&self, bar_num: usize) -> Option<PciBarConfiguration> {
+ self.config_regs.get_bar_configuration(bar_num)
+ }
+
fn register_device_capabilities(&mut self) -> std::result::Result<(), PciDeviceError> {
for cap in self.device.get_device_caps() {
self.config_regs
@@ -567,159 +664,133 @@ impl PciDevice for VirtioPciDevice {
// expression `COMMON_CONFIG_BAR_OFFSET <= o` is always true, but this code
// is written such that the value of the const may be changed independently.
#[allow(clippy::absurd_extreme_comparisons)]
+ #[allow(clippy::manual_range_contains)]
fn read_bar(&mut self, addr: u64, data: &mut [u8]) {
// The driver is only allowed to do aligned, properly sized access.
let bar0 = self.config_regs.get_bar_addr(self.settings_bar as usize);
- let offset = addr - bar0;
- match offset {
- o if COMMON_CONFIG_BAR_OFFSET <= o
- && o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE =>
- {
- self.common_config.read(
- o - COMMON_CONFIG_BAR_OFFSET,
- data,
- &mut self.queues,
- self.device.as_mut(),
- )
+ if addr < bar0 || addr >= bar0 + CAPABILITY_BAR_SIZE {
+ let bar_config = self.config_regs.get_bars().find(|config| {
+ addr >= config.address() && addr < (config.address() + config.size())
+ });
+ if let Some(c) = bar_config {
+ self.device
+ .read_bar(c.bar_index(), addr - c.address(), data);
}
- o if ISR_CONFIG_BAR_OFFSET <= o && o < ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE => {
- if let Some(v) = data.get_mut(0) {
- // Reading this register resets it to 0.
- *v = self.interrupt_status.swap(0, Ordering::SeqCst) as u8;
+ } else {
+ let offset = addr - bar0;
+ match offset {
+ o if COMMON_CONFIG_BAR_OFFSET <= o
+ && o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE =>
+ {
+ self.common_config.read(
+ o - COMMON_CONFIG_BAR_OFFSET,
+ data,
+ &mut self.queues,
+ self.device.as_mut(),
+ )
+ }
+ o if ISR_CONFIG_BAR_OFFSET <= o && o < ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE => {
+ if let Some(v) = data.get_mut(0) {
+ // Reading this register resets it to 0.
+ *v = self.interrupt_status.swap(0, Ordering::SeqCst) as u8;
+ }
+ }
+ o if DEVICE_CONFIG_BAR_OFFSET <= o
+ && o < DEVICE_CONFIG_BAR_OFFSET + DEVICE_CONFIG_SIZE =>
+ {
+ self.device.read_config(o - DEVICE_CONFIG_BAR_OFFSET, data);
+ }
+ o if NOTIFICATION_BAR_OFFSET <= o
+ && o < NOTIFICATION_BAR_OFFSET + NOTIFICATION_SIZE =>
+ {
+ // Handled with ioevents.
}
- }
- o if DEVICE_CONFIG_BAR_OFFSET <= o
- && o < DEVICE_CONFIG_BAR_OFFSET + DEVICE_CONFIG_SIZE =>
- {
- self.device.read_config(o - DEVICE_CONFIG_BAR_OFFSET, data);
- }
- o if NOTIFICATION_BAR_OFFSET <= o
- && o < NOTIFICATION_BAR_OFFSET + NOTIFICATION_SIZE =>
- {
- // Handled with ioevents.
- }
- o if MSIX_TABLE_BAR_OFFSET <= o && o < MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE => {
- self.msix_config
- .lock()
- .read_msix_table(o - MSIX_TABLE_BAR_OFFSET, data);
- }
+ o if MSIX_TABLE_BAR_OFFSET <= o && o < MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE => {
+ self.msix_config
+ .lock()
+ .read_msix_table(o - MSIX_TABLE_BAR_OFFSET, data);
+ }
- o if MSIX_PBA_BAR_OFFSET <= o && o < MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE => {
- self.msix_config
- .lock()
- .read_pba_entries(o - MSIX_PBA_BAR_OFFSET, data);
- }
+ o if MSIX_PBA_BAR_OFFSET <= o && o < MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE => {
+ self.msix_config
+ .lock()
+ .read_pba_entries(o - MSIX_PBA_BAR_OFFSET, data);
+ }
- _ => (),
+ _ => (),
+ }
}
}
#[allow(clippy::absurd_extreme_comparisons)]
+ #[allow(clippy::manual_range_contains)]
fn write_bar(&mut self, addr: u64, data: &[u8]) {
let bar0 = self.config_regs.get_bar_addr(self.settings_bar as usize);
- let offset = addr - bar0;
- match offset {
- o if COMMON_CONFIG_BAR_OFFSET <= o
- && o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE =>
- {
- self.common_config.write(
- o - COMMON_CONFIG_BAR_OFFSET,
- data,
- &mut self.queues,
- self.device.as_mut(),
- )
+ if addr < bar0 || addr >= bar0 + CAPABILITY_BAR_SIZE {
+ let bar_config = self.config_regs.get_bars().find(|config| {
+ addr >= config.address() && addr < (config.address() + config.size())
+ });
+ if let Some(c) = bar_config {
+ self.device
+ .write_bar(c.bar_index(), addr - c.address(), data);
}
- o if ISR_CONFIG_BAR_OFFSET <= o && o < ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE => {
- if let Some(v) = data.get(0) {
- self.interrupt_status
- .fetch_and(!(*v as usize), Ordering::SeqCst);
+ } else {
+ let offset = addr - bar0;
+ match offset {
+ o if COMMON_CONFIG_BAR_OFFSET <= o
+ && o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE =>
+ {
+ self.common_config.write(
+ o - COMMON_CONFIG_BAR_OFFSET,
+ data,
+ &mut self.queues,
+ self.device.as_mut(),
+ )
}
+ o if ISR_CONFIG_BAR_OFFSET <= o && o < ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE => {
+ if let Some(v) = data.get(0) {
+ self.interrupt_status
+ .fetch_and(!(*v as usize), Ordering::SeqCst);
+ }
+ }
+ o if DEVICE_CONFIG_BAR_OFFSET <= o
+ && o < DEVICE_CONFIG_BAR_OFFSET + DEVICE_CONFIG_SIZE =>
+ {
+ self.device.write_config(o - DEVICE_CONFIG_BAR_OFFSET, data);
+ }
+ o if NOTIFICATION_BAR_OFFSET <= o
+ && o < NOTIFICATION_BAR_OFFSET + NOTIFICATION_SIZE =>
+ {
+ // Handled with ioevents.
+ }
+ o if MSIX_TABLE_BAR_OFFSET <= o && o < MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE => {
+ let behavior = self
+ .msix_config
+ .lock()
+ .write_msix_table(o - MSIX_TABLE_BAR_OFFSET, data);
+ self.device.control_notify(behavior);
+ }
+ o if MSIX_PBA_BAR_OFFSET <= o && o < MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE => {
+ self.msix_config
+ .lock()
+ .write_pba_entries(o - MSIX_PBA_BAR_OFFSET, data);
+ }
+
+ _ => (),
}
- o if DEVICE_CONFIG_BAR_OFFSET <= o
- && o < DEVICE_CONFIG_BAR_OFFSET + DEVICE_CONFIG_SIZE =>
- {
- self.device.write_config(o - DEVICE_CONFIG_BAR_OFFSET, data);
- }
- o if NOTIFICATION_BAR_OFFSET <= o
- && o < NOTIFICATION_BAR_OFFSET + NOTIFICATION_SIZE =>
- {
- // Handled with ioevents.
- }
- o if MSIX_TABLE_BAR_OFFSET <= o && o < MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE => {
- let behavior = self
- .msix_config
- .lock()
- .write_msix_table(o - MSIX_TABLE_BAR_OFFSET, data);
- self.device.control_notify(behavior);
- }
- o if MSIX_PBA_BAR_OFFSET <= o && o < MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE => {
- self.msix_config
- .lock()
- .write_pba_entries(o - MSIX_PBA_BAR_OFFSET, data);
- }
+ }
- _ => (),
- };
+ if !self.device_activated && self.is_driver_ready() {
+ if let Some(iommu) = &self.iommu {
+ for q in &mut self.queues {
+ q.set_iommu(Arc::clone(iommu));
+ }
+ }
- if !self.device_activated && self.is_driver_ready() && self.are_queues_valid() {
- if let Some(interrupt_evt) = self.interrupt_evt.take() {
- self.interrupt_evt = match interrupt_evt.try_clone() {
- Ok(evt) => Some(evt),
- Err(e) => {
- warn!(
- "{} failed to clone interrupt_evt: {}",
- self.debug_label(),
- e
- );
- None
- }
- };
- if let Some(interrupt_resample_evt) = self.interrupt_resample_evt.take() {
- self.interrupt_resample_evt = match interrupt_resample_evt.try_clone() {
- Ok(evt) => Some(evt),
- Err(e) => {
- warn!(
- "{} failed to clone interrupt_resample_evt: {}",
- self.debug_label(),
- e
- );
- None
- }
- };
- if let Some(mem) = self.mem.take() {
- self.mem = Some(mem.clone());
- let interrupt = Interrupt::new(
- self.interrupt_status.clone(),
- interrupt_evt,
- interrupt_resample_evt,
- Some(self.msix_config.clone()),
- self.common_config.msix_config,
- );
-
- match self.clone_queue_evts() {
- Ok(queue_evts) => {
- // Use ready queues and their events.
- let (queues, queue_evts) = self
- .queues
- .clone()
- .into_iter()
- .zip(queue_evts.into_iter())
- .filter(|(q, _)| q.ready)
- .unzip();
-
- self.device.activate(mem, interrupt, queues, queue_evts);
- self.device_activated = true;
- }
- Err(e) => {
- warn!(
- "{} not activate due to failed to clone queue_evts: {}",
- self.debug_label(),
- e
- );
- }
- }
- }
+ if self.are_queues_valid() {
+ if let Err(e) = self.activate() {
+ error!("failed to activate device: {:#}", e);
}
}
}
@@ -737,4 +808,15 @@ impl PciDevice for VirtioPciDevice {
fn on_device_sandboxed(&mut self) {
self.device.on_device_sandboxed();
}
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ fn generate_acpi(&mut self, sdts: Vec<SDT>) -> Option<Vec<SDT>> {
+ self.device.generate_acpi(&self.pci_address, sdts)
+ }
+
+ fn set_iommu(&mut self, iommu: IpcMemoryMapper) -> anyhow::Result<()> {
+ assert!(self.supports_iommu());
+ self.iommu = Some(Arc::new(Mutex::new(iommu)));
+ Ok(())
+ }
}
diff --git a/devices/src/virtio/wl.rs b/devices/src/virtio/wl.rs
index 887ca2d19..11bb39551 100644
--- a/devices/src/virtio/wl.rs
+++ b/devices/src/virtio/wl.rs
@@ -32,9 +32,9 @@ use std::collections::btree_map::Entry;
use std::collections::{BTreeMap as Map, BTreeSet as Set, VecDeque};
use std::convert::From;
use std::error::Error as StdError;
-use std::fmt::{self, Display};
+use std::fmt;
use std::fs::File;
-use std::io::{self, Read, Seek, SeekFrom, Write};
+use std::io::{self, IoSliceMut, Read, Seek, SeekFrom, Write};
use std::mem::size_of;
#[cfg(feature = "minigbm")]
use std::os::raw::{c_uint, c_ulonglong};
@@ -50,27 +50,28 @@ use libc::{EBADF, EINVAL};
use data_model::*;
+#[cfg(feature = "minigbm")]
+use base::ioctl_iow_nr;
use base::{
- error, pipe, round_up_to_page_size, warn, AsRawDescriptor, Error, Event, FileFlags,
- FromRawDescriptor, PollToken, RawDescriptor, Result, ScmSocket, SharedMemory, SharedMemoryUnix,
- Tube, TubeError, WaitContext,
+ error, ioctl_iowr_nr, ioctl_with_ref, pipe, round_up_to_page_size, warn, AsRawDescriptor,
+ Error, Event, EventType, FileFlags, FromRawDescriptor, PollToken, RawDescriptor, Result,
+ ScmSocket, SharedMemory, SharedMemoryUnix, Tube, TubeError, WaitContext,
};
-#[cfg(feature = "minigbm")]
-use base::{ioctl_iow_nr, ioctl_with_ref};
#[cfg(feature = "gpu")]
use base::{IntoRawDescriptor, SafeDescriptor};
+use remain::sorted;
+use thiserror::Error as ThisError;
use vm_memory::{GuestMemory, GuestMemoryError};
#[cfg(feature = "minigbm")]
use vm_control::GpuMemoryDesc;
+#[cfg(feature = "gpu")]
use super::resource_bridge::{
get_resource_info, BufferInfo, ResourceBridgeError, ResourceInfo, ResourceRequest,
};
-use super::{
- DescriptorChain, Interrupt, Queue, Reader, SignalableInterrupt, VirtioDevice, Writer, TYPE_WL,
-};
-use vm_control::{MemSlot, VmMemoryRequest, VmMemoryResponse};
+use super::{Interrupt, Queue, Reader, SignalableInterrupt, VirtioDevice, Writer, TYPE_WL};
+use vm_control::{MemSlot, VmMemoryDestination, VmMemoryRequest, VmMemoryResponse, VmMemorySource};
const VIRTWL_SEND_MAX_ALLOCS: usize = 28;
const VIRTIO_WL_CMD_VFD_NEW: u32 = 256;
@@ -101,11 +102,12 @@ const VIRTIO_WL_VFD_WRITE: u32 = 0x1;
const VIRTIO_WL_VFD_READ: u32 = 0x2;
const VIRTIO_WL_VFD_MAP: u32 = 0x2;
const VIRTIO_WL_VFD_CONTROL: u32 = 0x4;
-const VIRTIO_WL_F_TRANS_FLAGS: u32 = 0x01;
-const VIRTIO_WL_F_SEND_FENCES: u32 = 0x02;
+const VIRTIO_WL_VFD_FENCE: u32 = 0x8;
+pub const VIRTIO_WL_F_TRANS_FLAGS: u32 = 0x01;
+pub const VIRTIO_WL_F_SEND_FENCES: u32 = 0x02;
-const QUEUE_SIZE: u16 = 16;
-const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE];
+pub const QUEUE_SIZE: u16 = 256;
+pub const QUEUE_SIZES: &[u16] = &[QUEUE_SIZE, QUEUE_SIZE];
const NEXT_VFD_ID_BASE: u32 = 0x40000000;
const VFD_ID_HOST_MASK: u32 = NEXT_VFD_ID_BASE;
@@ -130,6 +132,25 @@ struct dma_buf_sync {
#[cfg(feature = "minigbm")]
ioctl_iow_nr!(DMA_BUF_IOCTL_SYNC, DMA_BUF_IOCTL_BASE, 0, dma_buf_sync);
+#[repr(C)]
+#[derive(Copy, Clone, Default)]
+struct sync_file_info {
+ name: [u8; 32],
+ status: i32,
+ flags: u32,
+ num_fences: u32,
+ pad: u32,
+ sync_fence_info: u64,
+}
+
+ioctl_iowr_nr!(SYNC_IOC_FILE_INFO, 0x3e, 4, sync_file_info);
+
+fn is_fence(f: &File) -> bool {
+ let info = sync_file_info::default();
+ // Safe as f is a valid file
+ unsafe { ioctl_with_ref(f, SYNC_IOC_FILE_INFO(), &info) == 0 }
+}
+
const VIRTIO_WL_CTRL_VFD_SEND_KIND_LOCAL: u32 = 0;
const VIRTIO_WL_CTRL_VFD_SEND_KIND_VIRTGPU: u32 = 1;
const VIRTIO_WL_CTRL_VFD_SEND_KIND_VIRTGPU_FENCE: u32 = 2;
@@ -259,77 +280,53 @@ fn encode_resp(writer: &mut Writer, resp: WlResp) -> WlResult<()> {
}
#[allow(dead_code)]
-#[derive(Debug)]
+#[sorted]
+#[derive(ThisError, Debug)]
enum WlError {
+ #[error("overflow in calculation")]
+ CheckedOffset,
+ #[error("failed to synchronize DMABuf access: {0}")]
+ DmabufSync(io::Error),
+ #[error("failed to create shared memory from descriptor: {0}")]
+ FromSharedMemory(Error),
+ #[error("access violation in guest memory: {0}")]
+ GuestMemory(#[from] GuestMemoryError),
+ #[error("invalid string: {0}")]
+ InvalidString(std::str::Utf8Error),
+ #[error("failed to create shared memory allocation: {0}")]
NewAlloc(Error),
+ #[error("failed to create pipe: {0}")]
NewPipe(Error),
+ #[error("error parsing descriptor: {0}")]
+ ParseDesc(io::Error),
+ #[error("failed to read a pipe: {0}")]
+ ReadPipe(io::Error),
+ #[error("failed to recv on a socket: {0}")]
+ RecvVfd(Error),
+ #[error("failed to send on a socket: {0}")]
+ SendVfd(Error),
+ #[error("failed to connect socket: {0}")]
SocketConnect(io::Error),
+ #[error("failed to set socket as non-blocking: {0}")]
SocketNonBlock(io::Error),
- VmControl(TubeError),
+ #[error("unknown socket name: {0}")]
+ UnknownSocketName(String),
+ #[error("invalid response from parent VM")]
VmBadResponse,
- CheckedOffset,
- ParseDesc(io::Error),
- GuestMemory(GuestMemoryError),
- VolatileMemory(VolatileMemoryError),
- SendVfd(Error),
- WritePipe(io::Error),
- RecvVfd(Error),
- ReadPipe(io::Error),
+ #[error("failed to control parent VM: {0}")]
+ VmControl(TubeError),
+ #[error("access violating in guest volatile memory: {0}")]
+ VolatileMemory(#[from] VolatileMemoryError),
+ #[error("failed to listen to descriptor on wait context: {0}")]
WaitContextAdd(Error),
- DmabufSync(io::Error),
- FromSharedMemory(Error),
+ #[error("failed to write to a pipe: {0}")]
+ WritePipe(io::Error),
+ #[error("failed to write response: {0}")]
WriteResponse(io::Error),
- InvalidString(std::str::Utf8Error),
- UnknownSocketName(String),
-}
-
-impl Display for WlError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::WlError::*;
-
- match self {
- NewAlloc(e) => write!(f, "failed to create shared memory allocation: {}", e),
- NewPipe(e) => write!(f, "failed to create pipe: {}", e),
- SocketConnect(e) => write!(f, "failed to connect socket: {}", e),
- SocketNonBlock(e) => write!(f, "failed to set socket as non-blocking: {}", e),
- VmControl(e) => write!(f, "failed to control parent VM: {}", e),
- VmBadResponse => write!(f, "invalid response from parent VM"),
- CheckedOffset => write!(f, "overflow in calculation"),
- ParseDesc(e) => write!(f, "error parsing descriptor: {}", e),
- GuestMemory(e) => write!(f, "access violation in guest memory: {}", e),
- VolatileMemory(e) => write!(f, "access violating in guest volatile memory: {}", e),
- SendVfd(e) => write!(f, "failed to send on a socket: {}", e),
- WritePipe(e) => write!(f, "failed to write to a pipe: {}", e),
- RecvVfd(e) => write!(f, "failed to recv on a socket: {}", e),
- ReadPipe(e) => write!(f, "failed to read a pipe: {}", e),
- WaitContextAdd(e) => write!(f, "failed to listen to descriptor on wait context: {}", e),
- DmabufSync(e) => write!(f, "failed to synchronize DMABuf access: {}", e),
- FromSharedMemory(e) => {
- write!(f, "failed to create shared memory from descriptor: {}", e)
- }
- WriteResponse(e) => write!(f, "failed to write response: {}", e),
- InvalidString(e) => write!(f, "invalid string: {}", e),
- UnknownSocketName(name) => write!(f, "unknown socket name: {}", name),
- }
- }
}
-impl std::error::Error for WlError {}
-
type WlResult<T> = result::Result<T, WlError>;
-impl From<GuestMemoryError> for WlError {
- fn from(e: GuestMemoryError) -> WlError {
- WlError::GuestMemory(e)
- }
-}
-
-impl From<VolatileMemoryError> for WlError {
- fn from(e: VolatileMemoryError) -> WlError {
- WlError::VolatileMemory(e)
- }
-}
-
#[derive(Clone)]
struct VmRequester {
inner: Rc<Tube>,
@@ -348,10 +345,17 @@ impl VmRequester {
}
fn register_memory(&self, shm: SharedMemory) -> WlResult<(SharedMemory, VmMemoryResponse)> {
- let request = VmMemoryRequest::RegisterMemory(shm);
+ let request = VmMemoryRequest::RegisterMemory {
+ source: VmMemorySource::SharedMemory(shm),
+ dest: VmMemoryDestination::NewAllocation,
+ read_only: false,
+ };
let response = self.request(&request)?;
match request {
- VmMemoryRequest::RegisterMemory(shm) => Ok((shm, response)),
+ VmMemoryRequest::RegisterMemory {
+ source: VmMemorySource::SharedMemory(shm),
+ ..
+ } => Ok((shm, response)),
_ => unreachable!(),
}
}
@@ -489,6 +493,7 @@ impl CtrlVfdSendVfdV2 {
);
unsafe { self.payload.id }
}
+ #[cfg(feature = "gpu")]
fn seqno(&self) -> Le64 {
assert!(self.kind == VIRTIO_WL_CTRL_VFD_SEND_KIND_VIRTGPU_FENCE);
unsafe { self.payload.seqno }
@@ -566,6 +571,8 @@ struct WlVfd {
slot: Option<(MemSlot, u64 /* pfn */, VmRequester)>,
#[cfg(feature = "minigbm")]
is_dmabuf: bool,
+ fence: Option<File>,
+ is_fence: bool,
}
impl fmt::Debug for WlVfd {
@@ -600,15 +607,11 @@ impl WlVfd {
let vfd_shm =
SharedMemory::named("virtwl_alloc", size_page_aligned).map_err(WlError::NewAlloc)?;
- let register_request = VmMemoryRequest::RegisterMemory(vfd_shm);
- let register_response = vm.request(&register_request)?;
+ let (vfd_shm, register_response) = vm.register_memory(vfd_shm)?;
+
match register_response {
VmMemoryResponse::RegisterMemory { pfn, slot } => {
let mut vfd = WlVfd::default();
- let vfd_shm = match register_request {
- VmMemoryRequest::RegisterMemory(shm) => shm,
- _ => unreachable!(),
- };
vfd.guest_shared_memory = Some(vfd_shm);
vfd.slot = Some((slot, pfn, vm));
Ok(vfd)
@@ -629,6 +632,7 @@ impl WlVfd {
width,
height,
format,
+ dest: VmMemoryDestination::NewAllocation,
})?;
match allocate_and_register_gpu_memory_response {
VmMemoryResponse::AllocateAndRegisterGpuMemory {
@@ -693,32 +697,34 @@ impl WlVfd {
// pipe/socket because those have no end. We can even use that seek location as an indicator
// for how big the shared memory chunk to map into guest memory is. If seeking to the end
// fails, we assume it's a socket or pipe with read/write semantics.
- match descriptor.seek(SeekFrom::End(0)) {
- Ok(_) => {
- let shm = SharedMemory::from_file(descriptor).map_err(WlError::FromSharedMemory)?;
- let (shm, register_response) = vm.register_memory(shm)?;
-
- match register_response {
- VmMemoryResponse::RegisterMemory { pfn, slot } => {
- let mut vfd = WlVfd::default();
- vfd.guest_shared_memory = Some(shm);
- vfd.slot = Some((slot, pfn, vm));
- Ok(vfd)
- }
- _ => Err(WlError::VmBadResponse),
+ if descriptor.seek(SeekFrom::End(0)).is_ok() {
+ let shm = SharedMemory::from_file(descriptor).map_err(WlError::FromSharedMemory)?;
+ let (shm, register_response) = vm.register_memory(shm)?;
+
+ match register_response {
+ VmMemoryResponse::RegisterMemory { pfn, slot } => {
+ let mut vfd = WlVfd::default();
+ vfd.guest_shared_memory = Some(shm);
+ vfd.slot = Some((slot, pfn, vm));
+ Ok(vfd)
}
+ _ => Err(WlError::VmBadResponse),
}
- _ => {
- let flags = match FileFlags::from_file(&descriptor) {
- Ok(FileFlags::Read) => VIRTIO_WL_VFD_READ,
- Ok(FileFlags::Write) => VIRTIO_WL_VFD_WRITE,
- Ok(FileFlags::ReadWrite) => VIRTIO_WL_VFD_READ | VIRTIO_WL_VFD_WRITE,
- _ => 0,
- };
- let mut vfd = WlVfd::default();
- vfd.local_pipe = Some((flags, descriptor));
- Ok(vfd)
- }
+ } else if is_fence(&descriptor) {
+ let mut vfd = WlVfd::default();
+ vfd.is_fence = true;
+ vfd.fence = Some(descriptor);
+ Ok(vfd)
+ } else {
+ let flags = match FileFlags::from_file(&descriptor) {
+ Ok(FileFlags::Read) => VIRTIO_WL_VFD_READ,
+ Ok(FileFlags::Write) => VIRTIO_WL_VFD_WRITE,
+ Ok(FileFlags::ReadWrite) => VIRTIO_WL_VFD_READ | VIRTIO_WL_VFD_WRITE,
+ _ => 0,
+ };
+ let mut vfd = WlVfd::default();
+ vfd.local_pipe = Some((flags, descriptor));
+ Ok(vfd)
}
}
@@ -731,6 +737,9 @@ impl WlVfd {
if let Some((f, _)) = self.local_pipe {
flags |= f;
}
+ if self.is_fence {
+ flags |= VIRTIO_WL_VFD_FENCE;
+ }
} else {
if self.socket.is_some() {
flags |= VIRTIO_WL_VFD_CONTROL;
@@ -759,6 +768,7 @@ impl WlVfd {
.map(|shm| shm.as_raw_descriptor())
.or(self.socket.as_ref().map(|s| s.as_raw_descriptor()))
.or(self.remote_pipe.as_ref().map(|p| p.as_raw_descriptor()))
+ .or(self.fence.as_ref().map(|f| f.as_raw_descriptor()))
}
// The FD that is used for polling for events on this VFD.
@@ -771,6 +781,7 @@ impl WlVfd {
.as_ref()
.map(|(_, p)| p as &dyn AsRawDescriptor)
})
+ .or_else(|| self.fence.as_ref().map(|f| f as &dyn AsRawDescriptor))
}
// Sends data/files from the guest to the host over this VFD.
@@ -802,7 +813,7 @@ impl WlVfd {
let mut fd_buf = [0; VIRTWL_SEND_MAX_ALLOCS];
// If any errors happen, the socket will get dropped, preventing more reading.
let (len, file_count) = socket
- .recv_with_fds(&mut buf[..], &mut fd_buf)
+ .recv_with_fds(IoSliceMut::new(&mut buf), &mut fd_buf)
.map_err(WlError::RecvVfd)?;
// If any data gets read, the put the socket back for future recv operations.
if len != 0 || file_count != 0 {
@@ -865,7 +876,7 @@ enum WlRecv {
Hup,
}
-struct WlState {
+pub struct WlState {
wayland_paths: Map<String, PathBuf>,
vm: VmRequester,
resource_bridge: Option<Tube>,
@@ -883,7 +894,8 @@ struct WlState {
}
impl WlState {
- fn new(
+ /// Create a new `WlState` instance for running a virtio-wl device.
+ pub fn new(
wayland_paths: Map<String, PathBuf>,
vm_tube: Tube,
use_transition_flags: bool,
@@ -908,6 +920,13 @@ impl WlState {
}
}
+ /// This is a hack so that we can drive the inner WaitContext from an async fn. The proper
+ /// long-term solution is to replace the WaitContext completely by spawning async workers
+ /// instead.
+ pub fn wait_ctx(&self) -> &WaitContext<u32> {
+ &self.wait_ctx
+ }
+
fn new_pipe(&mut self, id: u32, flags: u32) -> WlResult<WlResp> {
if id & VFD_ID_HOST_MASK != 0 {
return Ok(WlResp::InvalidId);
@@ -1263,9 +1282,20 @@ impl WlState {
fn recv(&mut self, vfd_id: u32) -> WlResult<()> {
let buf = match self.vfds.get_mut(&vfd_id) {
- Some(vfd) => vfd.recv(&mut self.in_file_queue)?,
+ Some(vfd) => {
+ if vfd.is_fence {
+ if let Err(e) = self.wait_ctx.delete(vfd.wait_descriptor().unwrap()) {
+ warn!("failed to remove hungup vfd from poll context: {}", e);
+ }
+ self.in_queue.push_back((vfd_id, WlRecv::Hup));
+ return Ok(());
+ } else {
+ vfd.recv(&mut self.in_file_queue)?
+ }
+ }
None => return Ok(()),
};
+
if self.in_file_queue.is_empty() && buf.is_empty() {
self.in_queue.push_back((vfd_id, WlRecv::Hup));
return Ok(());
@@ -1462,6 +1492,124 @@ impl WlState {
}
}
+#[derive(ThisError, Debug)]
+#[error("no descriptors available in queue")]
+pub struct DescriptorsExhausted;
+
+/// Handle incoming events and forward them to the VM over the input queue.
+pub fn process_in_queue<I: SignalableInterrupt>(
+ interrupt: &I,
+ in_queue: &mut Queue,
+ mem: &GuestMemory,
+ state: &mut WlState,
+) -> ::std::result::Result<(), DescriptorsExhausted> {
+ const MIN_IN_DESC_LEN: u32 =
+ (size_of::<CtrlVfdRecv>() + size_of::<Le32>() * VIRTWL_SEND_MAX_ALLOCS) as u32;
+
+ state.process_wait_context();
+
+ let mut needs_interrupt = false;
+ let mut exhausted_queue = false;
+ loop {
+ let desc = if let Some(d) = in_queue.peek(mem) {
+ d
+ } else {
+ exhausted_queue = true;
+ break;
+ };
+ if desc.len < MIN_IN_DESC_LEN || desc.is_read_only() {
+ needs_interrupt = true;
+ in_queue.pop_peeked(mem);
+ in_queue.add_used(mem, desc.index, 0);
+ continue;
+ }
+
+ let index = desc.index;
+ let mut should_pop = false;
+ if let Some(in_resp) = state.next_recv() {
+ let bytes_written = match Writer::new(mem.clone(), desc) {
+ Ok(mut writer) => {
+ match encode_resp(&mut writer, in_resp) {
+ Ok(()) => {
+ should_pop = true;
+ }
+ Err(e) => {
+ error!("failed to encode response to descriptor chain: {}", e);
+ }
+ };
+ writer.bytes_written() as u32
+ }
+ Err(e) => {
+ error!("invalid descriptor: {}", e);
+ 0
+ }
+ };
+
+ needs_interrupt = true;
+ in_queue.pop_peeked(mem);
+ in_queue.add_used(mem, index, bytes_written);
+ } else {
+ break;
+ }
+ if should_pop {
+ state.pop_recv();
+ }
+ }
+
+ if needs_interrupt {
+ in_queue.trigger_interrupt(mem, interrupt);
+ }
+
+ if exhausted_queue {
+ Err(DescriptorsExhausted)
+ } else {
+ Ok(())
+ }
+}
+
+/// Handle messages from the output queue and forward them to the display sever, if necessary.
+pub fn process_out_queue<I: SignalableInterrupt>(
+ interrupt: &I,
+ out_queue: &mut Queue,
+ mem: &GuestMemory,
+ state: &mut WlState,
+) {
+ let mut needs_interrupt = false;
+ while let Some(desc) = out_queue.pop(mem) {
+ let desc_index = desc.index;
+ match (
+ Reader::new(mem.clone(), desc.clone()),
+ Writer::new(mem.clone(), desc),
+ ) {
+ (Ok(mut reader), Ok(mut writer)) => {
+ let resp = match state.execute(&mut reader) {
+ Ok(r) => r,
+ Err(e) => WlResp::Err(Box::new(e)),
+ };
+
+ match encode_resp(&mut writer, resp) {
+ Ok(()) => {}
+ Err(e) => {
+ error!("failed to encode response to descriptor chain: {}", e);
+ }
+ }
+
+ out_queue.add_used(mem, desc_index, writer.bytes_written() as u32);
+ needs_interrupt = true;
+ }
+ (_, Err(e)) | (Err(e), _) => {
+ error!("invalid descriptor: {}", e);
+ out_queue.add_used(mem, desc_index, 0);
+ needs_interrupt = true;
+ }
+ }
+ }
+
+ if needs_interrupt {
+ out_queue.trigger_interrupt(mem, interrupt);
+ }
+}
+
struct Worker {
interrupt: Interrupt,
mem: GuestMemory,
@@ -1498,8 +1646,6 @@ impl Worker {
}
fn run(&mut self, mut queue_evts: Vec<Event>, kill_evt: Event) {
- let mut in_desc_chains: VecDeque<DescriptorChain> =
- VecDeque::with_capacity(QUEUE_SIZE as usize);
let in_queue_evt = queue_evts.remove(0);
let out_queue_evt = queue_evts.remove(0);
#[derive(PollToken)]
@@ -1533,9 +1679,8 @@ impl Worker {
}
}
+ let mut watching_state_ctx = true;
'wait: loop {
- let mut signal_used_in = false;
- let mut signal_used_out = false;
let events = match wait_ctx.wait() {
Ok(v) => v,
Err(e) => {
@@ -1548,119 +1693,50 @@ impl Worker {
match event.token {
Token::InQueue => {
let _ = in_queue_evt.read();
- // Used to buffer descriptor indexes that are invalid for our uses.
- let mut rejects = [0u16; QUEUE_SIZE as usize];
- let mut rejects_len = 0;
- let min_in_desc_len = (size_of::<CtrlVfdRecv>()
- + size_of::<Le32>() * VIRTWL_SEND_MAX_ALLOCS)
- as u32;
- in_desc_chains.extend(self.in_queue.iter(&self.mem).filter(|d| {
- if d.len >= min_in_desc_len && d.is_write_only() {
- true
- } else {
- // Can not use queue.add_used directly because it's being borrowed
- // for the iterator chain, so we buffer the descriptor index in
- // rejects.
- rejects[rejects_len] = d.index;
- rejects_len += 1;
- false
+ if !watching_state_ctx {
+ if let Err(e) =
+ wait_ctx.modify(&self.state.wait_ctx, EventType::Read, Token::State)
+ {
+ error!("Failed to modify wait_ctx descriptor for WlState: {}", e);
+ break;
}
- }));
- for &reject in &rejects[..rejects_len] {
- signal_used_in = true;
- self.in_queue.add_used(&self.mem, reject, 0);
+ watching_state_ctx = true;
}
}
Token::OutQueue => {
let _ = out_queue_evt.read();
- while let Some(desc) = self.out_queue.pop(&self.mem) {
- let desc_index = desc.index;
- match (
- Reader::new(self.mem.clone(), desc.clone()),
- Writer::new(self.mem.clone(), desc),
- ) {
- (Ok(mut reader), Ok(mut writer)) => {
- let resp = match self.state.execute(&mut reader) {
- Ok(r) => r,
- Err(e) => WlResp::Err(Box::new(e)),
- };
-
- match encode_resp(&mut writer, resp) {
- Ok(()) => {}
- Err(e) => {
- error!(
- "failed to encode response to descriptor chain: {}",
- e
- );
- }
- }
-
- self.out_queue.add_used(
- &self.mem,
- desc_index,
- writer.bytes_written() as u32,
- );
- signal_used_out = true;
- }
- (_, Err(e)) | (Err(e), _) => {
- error!("invalid descriptor: {}", e);
- self.out_queue.add_used(&self.mem, desc_index, 0);
- signal_used_out = true;
- }
+ process_out_queue(
+ &self.interrupt,
+ &mut self.out_queue,
+ &self.mem,
+ &mut self.state,
+ );
+ }
+ Token::Kill => break 'wait,
+ Token::State => {
+ if let Err(DescriptorsExhausted) = process_in_queue(
+ &self.interrupt,
+ &mut self.in_queue,
+ &self.mem,
+ &mut self.state,
+ ) {
+ if let Err(e) =
+ wait_ctx.modify(&self.state.wait_ctx, EventType::None, Token::State)
+ {
+ error!(
+ "Failed to stop watching wait_ctx descriptor for WlState: {}",
+ e
+ );
+ break;
}
+ watching_state_ctx = false;
}
}
- Token::Kill => break 'wait,
- Token::State => self.state.process_wait_context(),
Token::InterruptResample => {
self.interrupt.interrupt_resample();
}
}
}
-
- // Because this loop should be retried after the in queue is usable or after one of the
- // VFDs was read, we do it after the poll event responses.
- while !in_desc_chains.is_empty() {
- let mut should_pop = false;
- if let Some(in_resp) = self.state.next_recv() {
- // in_desc_chains is not empty (checked by loop condition) so unwrap is safe.
- let desc = in_desc_chains.pop_front().unwrap();
- let index = desc.index;
- match Writer::new(self.mem.clone(), desc) {
- Ok(mut writer) => {
- match encode_resp(&mut writer, in_resp) {
- Ok(()) => {
- should_pop = true;
- }
- Err(e) => {
- error!("failed to encode response to descriptor chain: {}", e);
- }
- };
- signal_used_in = true;
- self.in_queue
- .add_used(&self.mem, index, writer.bytes_written() as u32);
- }
- Err(e) => {
- error!("invalid descriptor: {}", e);
- self.in_queue.add_used(&self.mem, index, 0);
- signal_used_in = true;
- }
- }
- } else {
- break;
- }
- if should_pop {
- self.state.pop_recv();
- }
- }
-
- if signal_used_in {
- self.interrupt.signal_used_queue(self.in_queue.vector);
- }
-
- if signal_used_out {
- self.interrupt.signal_used_queue(self.out_queue.vector);
- }
}
}
}
diff --git a/disk/Android.bp b/disk/Android.bp
index 8ad224e42..2bb3526a5 100644
--- a/disk/Android.bp
+++ b/disk/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -10,14 +10,20 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "disk_defaults",
+rust_test {
+ name: "disk_test_src_disk",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "disk",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/disk.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2021",
rustlibs: [
"libbase_rust",
"libcros_async",
@@ -25,6 +31,7 @@ rust_defaults {
"libfutures",
"liblibc",
"libtempfile",
+ "libthiserror",
"libvm_memory",
],
proc_macros: [
@@ -33,38 +40,30 @@ rust_defaults {
],
}
-rust_test_host {
- name: "disk_host_test_src_disk",
- defaults: ["disk_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "disk_device_test_src_disk",
- defaults: ["disk_defaults"],
-}
-
rust_library {
name: "libdisk",
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "disk",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/disk.rs"],
- edition: "2018",
+ edition: "2021",
features: [
- "composite-disk", // Added manually
+ "composite-disk", // Added manually
],
rustlibs: [
"libbase_rust",
+ "libcrc32fast", // Added manually
"libcros_async",
"libdata_model",
"libfutures",
"liblibc",
- "libprotobuf", // Added manually
- "libprotos", // Added manually
+ "libprotobuf", // Added manually
+ "libprotos", // Added manually
"libtempfile",
+ "libthiserror",
+ "libuuid", // Added manually
"libvm_memory",
],
proc_macros: [
@@ -72,54 +71,3 @@ rust_library {
"libremain",
],
}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// ../vm_memory/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// cfg-if-1.0.0
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// getrandom-0.2.2 "std"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.3 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.0 "std"
-// rand_core-0.6.2 "alloc,getrandom,std"
-// remain-0.2.2
-// remove_dir_all-0.5.3
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/disk/Cargo.toml b/disk/Cargo.toml
index 7182dafc6..941e69452 100644
--- a/disk/Cargo.toml
+++ b/disk/Cargo.toml
@@ -2,24 +2,27 @@
name = "disk"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[lib]
path = "src/disk.rs"
[features]
-composite-disk = ["protos", "protobuf"]
+composite-disk = ["crc32fast", "protos", "protobuf", "uuid"]
[dependencies]
async-trait = "0.1.36"
base = { path = "../base" }
+crc32fast = { version = "1.2.1", optional = true }
libc = "*"
protobuf = { version = "2.3", optional = true }
remain = "*"
-tempfile = "*"
+tempfile = "3"
+thiserror = "*"
+uuid = { version = "0.8.2", features = ["v4"], optional = true }
cros_async = { path = "../cros_async" }
-data_model = { path = "../data_model" }
-protos = { path = "../protos", optional = true }
+data_model = { path = "../common/data_model" }
+protos = { path = "../protos", features = ["composite-disk"], optional = true }
vm_memory = { path = "../vm_memory" }
[dependencies.futures]
diff --git a/disk/cargo2android.json b/disk/cargo2android.json
new file mode 100644
index 000000000..a0c7bb4d3
--- /dev/null
+++ b/disk/cargo2android.json
@@ -0,0 +1,8 @@
+{
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "patch": "patches/Android.bp.patch",
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/disk/patches/Android.bp.patch b/disk/patches/Android.bp.patch
new file mode 100644
index 000000000..62dcfa6d4
--- /dev/null
+++ b/disk/patches/Android.bp.patch
@@ -0,0 +1,26 @@
+diff --git a/disk/Android.bp b/disk/Android.bp
+index 78271ca1..e2ef84af 100644
+--- a/disk/Android.bp
++++ b/disk/Android.bp
+@@ -54,14 +54,21 @@ rust_library {
+ crate_name: "disk",
+ srcs: ["src/disk.rs"],
+ edition: "2021",
++ features: [
++ "composite-disk", // Added manually
++ ],
+ rustlibs: [
+ "libbase_rust",
++ "libcrc32fast", // Added manually
+ "libcros_async",
+ "libdata_model",
+ "libfutures",
+ "liblibc",
++ "libprotobuf", // Added manually
++ "libprotos", // Added manually
+ "libtempfile",
+ "libthiserror",
++ "libuuid", // Added manually
+ "libvm_memory",
+ ],
+ proc_macros: [
diff --git a/disk/src/android_sparse.rs b/disk/src/android_sparse.rs
index 2c0fd34e0..b191155bd 100644
--- a/disk/src/android_sparse.rs
+++ b/disk/src/android_sparse.rs
@@ -5,7 +5,6 @@
// https://android.googlesource.com/platform/system/core/+/7b444f0/libsparse/sparse_format.h
use std::collections::BTreeMap;
-use std::fmt::{self, Display};
use std::fs::File;
use std::io::{self, ErrorKind, Read, Seek, SeekFrom};
use std::mem;
@@ -17,29 +16,19 @@ use base::{
};
use data_model::{DataInit, Le16, Le32, VolatileSlice};
use remain::sorted;
+use thiserror::Error;
#[sorted]
-#[derive(Debug)]
+#[derive(Error, Debug)]
pub enum Error {
+ #[error("invalid magic header for android sparse format")]
InvalidMagicHeader,
+ #[error("invalid specification: \"{0}\"")]
InvalidSpecification(String),
+ #[error("failed to read specification: \"{0}\"")]
ReadSpecificationError(io::Error),
}
-impl Display for Error {
- #[remain::check]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- #[sorted]
- match self {
- InvalidMagicHeader => write!(f, "invalid magic header for android sparse format"),
- InvalidSpecification(s) => write!(f, "invalid specification: \"{}\"", s),
- ReadSpecificationError(e) => write!(f, "failed to read specification: \"{}\"", e),
- }
- }
-}
-
pub type Result<T> = std::result::Result<T, Error>;
pub const SPARSE_HEADER_MAGIC: u32 = 0xed26ff3a;
diff --git a/disk/src/composite.rs b/disk/src/composite.rs
index efa5e1de8..3e09b6c35 100644
--- a/disk/src/composite.rs
+++ b/disk/src/composite.rs
@@ -3,49 +3,86 @@
// found in the LICENSE file.
use std::cmp::{max, min};
-use std::fmt::{self, Display};
+use std::collections::HashSet;
+use std::convert::TryInto;
use std::fs::{File, OpenOptions};
-use std::io::{self, ErrorKind, Read, Seek, SeekFrom};
+use std::io::{self, ErrorKind, Read, Seek, SeekFrom, Write};
use std::ops::Range;
+use std::path::{Path, PathBuf};
-use crate::{create_disk_file, DiskFile, DiskGetLen, ImageType};
use base::{
- AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile, FileSetLen, FileSync, PunchHole,
- RawDescriptor, WriteZeroesAt,
+ open_file, AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile, FileSetLen, FileSync,
+ PunchHole, RawDescriptor, WriteZeroesAt,
};
+use crc32fast::Hasher;
use data_model::VolatileSlice;
-use protos::cdisk_spec;
+use protobuf::Message;
+use protos::cdisk_spec::{self, ComponentDisk, CompositeDisk, ReadWriteCapability};
use remain::sorted;
+use thiserror::Error;
+use uuid::Uuid;
+
+use crate::gpt::{
+ self, write_gpt_header, write_protective_mbr, GptPartitionEntry, GPT_BEGINNING_SIZE,
+ GPT_END_SIZE, GPT_HEADER_SIZE, GPT_NUM_PARTITIONS, GPT_PARTITION_ENTRY_SIZE, SECTOR_SIZE,
+};
+use crate::{create_disk_file, DiskFile, DiskGetLen, ImageType};
+
+/// The amount of padding needed between the last partition entry and the first partition, to align
+/// the partition appropriately. The two sectors are for the MBR and the GPT header.
+const PARTITION_ALIGNMENT_SIZE: usize = GPT_BEGINNING_SIZE as usize
+ - 2 * SECTOR_SIZE as usize
+ - GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize;
+const HEADER_PADDING_LENGTH: usize = SECTOR_SIZE as usize - GPT_HEADER_SIZE as usize;
+// Keep all partitions 4k aligned for performance.
+const PARTITION_SIZE_SHIFT: u8 = 12;
+// Keep the disk size a multiple of 64k for crosvm's virtio_blk driver.
+const DISK_SIZE_SHIFT: u8 = 16;
+
+// From https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs.
+const LINUX_FILESYSTEM_GUID: Uuid = Uuid::from_u128(0x0FC63DAF_8483_4772_8E79_3D69D8477DE4);
+const EFI_SYSTEM_PARTITION_GUID: Uuid = Uuid::from_u128(0xC12A7328_F81F_11D2_BA4B_00A0C93EC93B);
#[sorted]
-#[derive(Debug)]
+#[derive(Error, Debug)]
pub enum Error {
+ #[error("failed to use underlying disk: \"{0}\"")]
DiskError(Box<crate::Error>),
+ #[error("duplicate GPT partition label \"{0}\"")]
+ DuplicatePartitionLabel(String),
+ #[error("failed to write GPT header: \"{0}\"")]
+ GptError(gpt::Error),
+ #[error("invalid magic header for composite disk format")]
InvalidMagicHeader,
+ #[error("invalid partition path {0:?}")]
+ InvalidPath(PathBuf),
+ #[error("failed to parse specification proto: \"{0}\"")]
InvalidProto(protobuf::ProtobufError),
+ #[error("invalid specification: \"{0}\"")]
InvalidSpecification(String),
+ #[error("no image files for partition {0:?}")]
+ NoImageFiles(PartitionInfo),
+ #[error("failed to open component file \"{1}\": \"{0}\"")]
OpenFile(io::Error, String),
+ #[error("failed to read specification: \"{0}\"")]
ReadSpecificationError(io::Error),
+ #[error("Read-write partition {0:?} size is not a multiple of {}.", 1 << PARTITION_SIZE_SHIFT)]
+ UnalignedReadWrite(PartitionInfo),
+ #[error("unknown version {0} in specification")]
UnknownVersion(u64),
+ #[error("unsupported component disk type \"{0:?}\"")]
UnsupportedComponent(ImageType),
+ #[error("failed to write composite disk header: \"{0}\"")]
+ WriteHeader(io::Error),
+ #[error("failed to write specification proto: \"{0}\"")]
+ WriteProto(protobuf::ProtobufError),
+ #[error("failed to write zero filler: \"{0}\"")]
+ WriteZeroFiller(io::Error),
}
-impl Display for Error {
- #[remain::check]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- #[sorted]
- match self {
- DiskError(e) => write!(f, "failed to use underlying disk: \"{}\"", e),
- InvalidMagicHeader => write!(f, "invalid magic header for composite disk format"),
- InvalidProto(e) => write!(f, "failed to parse specification proto: \"{}\"", e),
- InvalidSpecification(s) => write!(f, "invalid specification: \"{}\"", s),
- OpenFile(e, p) => write!(f, "failed to open component file \"{}\": \"{}\"", p, e),
- ReadSpecificationError(e) => write!(f, "failed to read specification: \"{}\"", e),
- UnknownVersion(v) => write!(f, "unknown version {} in specification", v),
- UnsupportedComponent(c) => write!(f, "unsupported component disk type \"{:?}\"", c),
- }
+impl From<gpt::Error> for Error {
+ fn from(e: gpt::Error) -> Self {
+ Self::GptError(e)
}
}
@@ -86,11 +123,14 @@ fn range_intersection(a: &Range<u64>, b: &Range<u64>) -> Range<u64> {
}
}
+/// The version of the composite disk format supported by this implementation.
+const COMPOSITE_DISK_VERSION: u64 = 2;
+
/// A magic string placed at the beginning of a composite disk file to identify it.
-pub static CDISK_MAGIC: &str = "composite_disk\x1d";
+pub const CDISK_MAGIC: &str = "composite_disk\x1d";
/// The length of the CDISK_MAGIC string. Created explicitly as a static constant so that it is
/// possible to create a character array of the same length.
-pub const CDISK_MAGIC_LEN: usize = 15;
+pub const CDISK_MAGIC_LEN: usize = CDISK_MAGIC.len();
impl CompositeDiskFile {
fn new(mut disks: Vec<ComponentDiskPart>) -> Result<CompositeDiskFile> {
@@ -117,7 +157,11 @@ impl CompositeDiskFile {
/// Set up a composite disk by reading the specification from a file. The file must consist of
/// the CDISK_MAGIC string followed by one binary instance of the CompositeDisk protocol
/// buffer. Returns an error if it could not read the file or if the specification was invalid.
- pub fn from_file(mut file: File) -> Result<CompositeDiskFile> {
+ pub fn from_file(
+ mut file: File,
+ max_nesting_depth: u32,
+ image_path: &Path,
+ ) -> Result<CompositeDiskFile> {
file.seek(SeekFrom::Start(0))
.map_err(Error::ReadSpecificationError)?;
let mut magic_space = [0u8; CDISK_MAGIC_LEN];
@@ -127,24 +171,30 @@ impl CompositeDiskFile {
return Err(Error::InvalidMagicHeader);
}
let proto: cdisk_spec::CompositeDisk =
- protobuf::parse_from_reader(&mut file).map_err(Error::InvalidProto)?;
- if proto.get_version() != 1 {
+ Message::parse_from_reader(&mut file).map_err(Error::InvalidProto)?;
+ if proto.get_version() > COMPOSITE_DISK_VERSION {
return Err(Error::UnknownVersion(proto.get_version()));
}
- let mut open_options = OpenOptions::new();
- open_options.read(true);
let mut disks: Vec<ComponentDiskPart> = proto
.get_component_disks()
.iter()
.map(|disk| {
- open_options.write(
- disk.get_read_write_capability() == cdisk_spec::ReadWriteCapability::READ_WRITE,
- );
- let file = open_options
- .open(disk.get_file_path())
- .map_err(|e| Error::OpenFile(e, disk.get_file_path().to_string()))?;
+ let path = if proto.get_version() == 1 {
+ PathBuf::from(disk.get_file_path())
+ } else {
+ image_path.parent().unwrap().join(disk.get_file_path())
+ };
+ let comp_file = open_file(
+ &path,
+ OpenOptions::new().read(true).write(
+ disk.get_read_write_capability()
+ == cdisk_spec::ReadWriteCapability::READ_WRITE,
+ ), // TODO(b/190435784): add support for O_DIRECT.
+ )
+ .map_err(|e| Error::OpenFile(e.into(), disk.get_file_path().to_string()))?;
Ok(ComponentDiskPart {
- file: create_disk_file(file).map_err(|e| Error::DiskError(Box::new(e)))?,
+ file: create_disk_file(comp_file, max_nesting_depth, &path)
+ .map_err(|e| Error::DiskError(Box::new(e)))?,
offset: disk.get_offset(),
length: 0, // Assigned later
})
@@ -286,9 +336,7 @@ impl PunchHole for CompositeDiskFile {
intersection.start - disk.offset,
intersection.end - intersection.start,
);
- if result.is_err() {
- return result;
- }
+ result?;
}
Ok(())
}
@@ -307,9 +355,7 @@ impl FileAllocate for CompositeDiskFile {
intersection.start - disk.offset,
intersection.end - intersection.start,
);
- if result.is_err() {
- return result;
- }
+ result?;
}
Ok(())
}
@@ -333,15 +379,289 @@ impl AsRawDescriptors for CompositeDiskFile {
fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
self.component_disks
.iter()
- .map(|d| d.file.as_raw_descriptors())
- .flatten()
+ .flat_map(|d| d.file.as_raw_descriptors())
.collect()
}
}
+/// Information about a partition to create.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct PartitionInfo {
+ pub label: String,
+ pub path: PathBuf,
+ pub partition_type: ImagePartitionType,
+ pub writable: bool,
+ pub size: u64,
+}
+
+/// Round `val` up to the next multiple of 2**`align_log`.
+fn align_to_power_of_2(val: u64, align_log: u8) -> u64 {
+ let align = 1 << align_log;
+ ((val + (align - 1)) / align) * align
+}
+
+impl PartitionInfo {
+ fn aligned_size(&self) -> u64 {
+ align_to_power_of_2(self.size, PARTITION_SIZE_SHIFT)
+ }
+}
+
+/// The type of partition.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum ImagePartitionType {
+ LinuxFilesystem,
+ EfiSystemPartition,
+}
+
+impl ImagePartitionType {
+ fn guid(self) -> Uuid {
+ match self {
+ Self::LinuxFilesystem => LINUX_FILESYSTEM_GUID,
+ Self::EfiSystemPartition => EFI_SYSTEM_PARTITION_GUID,
+ }
+ }
+}
+
+/// Write protective MBR and primary GPT table.
+fn write_beginning(
+ file: &mut impl Write,
+ disk_guid: Uuid,
+ partitions: &[u8],
+ partition_entries_crc32: u32,
+ secondary_table_offset: u64,
+ disk_size: u64,
+) -> Result<()> {
+ // Write the protective MBR to the first sector.
+ write_protective_mbr(file, disk_size)?;
+
+ // Write the GPT header, and pad out to the end of the sector.
+ write_gpt_header(
+ file,
+ disk_guid,
+ partition_entries_crc32,
+ secondary_table_offset,
+ false,
+ )?;
+ file.write_all(&[0; HEADER_PADDING_LENGTH])
+ .map_err(Error::WriteHeader)?;
+
+ // Write partition entries, including unused ones.
+ file.write_all(partitions).map_err(Error::WriteHeader)?;
+
+ // Write zeroes to align the first partition appropriately.
+ file.write_all(&[0; PARTITION_ALIGNMENT_SIZE])
+ .map_err(Error::WriteHeader)?;
+
+ Ok(())
+}
+
+/// Write secondary GPT table.
+fn write_end(
+ file: &mut impl Write,
+ disk_guid: Uuid,
+ partitions: &[u8],
+ partition_entries_crc32: u32,
+ secondary_table_offset: u64,
+ disk_size: u64,
+) -> Result<()> {
+ // Write partition entries, including unused ones.
+ file.write_all(partitions).map_err(Error::WriteHeader)?;
+
+ // Write the GPT header, and pad out to the end of the sector.
+ write_gpt_header(
+ file,
+ disk_guid,
+ partition_entries_crc32,
+ secondary_table_offset,
+ true,
+ )?;
+ file.write_all(&[0; HEADER_PADDING_LENGTH])
+ .map_err(Error::WriteHeader)?;
+
+ // Pad out to the aligned disk size.
+ let used_disk_size = secondary_table_offset + GPT_END_SIZE;
+ let padding = disk_size - used_disk_size;
+ file.write_all(&vec![0; padding as usize])
+ .map_err(Error::WriteHeader)?;
+
+ Ok(())
+}
+
+/// Create the `GptPartitionEntry` for the given partition.
+fn create_gpt_entry(partition: &PartitionInfo, offset: u64) -> GptPartitionEntry {
+ let mut partition_name: Vec<u16> = partition.label.encode_utf16().collect();
+ partition_name.resize(36, 0);
+
+ GptPartitionEntry {
+ partition_type_guid: partition.partition_type.guid(),
+ unique_partition_guid: Uuid::new_v4(),
+ first_lba: offset / SECTOR_SIZE,
+ last_lba: (offset + partition.aligned_size()) / SECTOR_SIZE - 1,
+ attributes: 0,
+ partition_name: partition_name.try_into().unwrap(),
+ }
+}
+
+/// Create one or more `ComponentDisk` proto messages for the given partition.
+fn create_component_disks(
+ partition: &PartitionInfo,
+ offset: u64,
+ zero_filler_path: &str,
+) -> Result<Vec<ComponentDisk>> {
+ let aligned_size = partition.aligned_size();
+
+ let mut component_disks = vec![ComponentDisk {
+ offset,
+ file_path: partition
+ .path
+ .to_str()
+ .ok_or_else(|| Error::InvalidPath(partition.path.to_owned()))?
+ .to_string(),
+ read_write_capability: if partition.writable {
+ ReadWriteCapability::READ_WRITE
+ } else {
+ ReadWriteCapability::READ_ONLY
+ },
+ ..ComponentDisk::new()
+ }];
+
+ if partition.size != aligned_size {
+ if partition.writable {
+ return Err(Error::UnalignedReadWrite(partition.to_owned()));
+ } else {
+ // Fill in the gap by reusing the zero filler file, because we know it is always bigger
+ // than the alignment size. Its size is 1 << PARTITION_SIZE_SHIFT (4k).
+ component_disks.push(ComponentDisk {
+ offset: offset + partition.size,
+ file_path: zero_filler_path.to_owned(),
+ read_write_capability: ReadWriteCapability::READ_ONLY,
+ ..ComponentDisk::new()
+ });
+ }
+ }
+
+ Ok(component_disks)
+}
+
+/// Create a new composite disk image containing the given partitions, and write it out to the given
+/// files.
+pub fn create_composite_disk(
+ partitions: &[PartitionInfo],
+ zero_filler_path: &Path,
+ header_path: &Path,
+ header_file: &mut File,
+ footer_path: &Path,
+ footer_file: &mut File,
+ output_composite: &mut File,
+) -> Result<()> {
+ let zero_filler_path = zero_filler_path
+ .to_str()
+ .ok_or_else(|| Error::InvalidPath(zero_filler_path.to_owned()))?
+ .to_string();
+ let header_path = header_path
+ .to_str()
+ .ok_or_else(|| Error::InvalidPath(header_path.to_owned()))?
+ .to_string();
+ let footer_path = footer_path
+ .to_str()
+ .ok_or_else(|| Error::InvalidPath(footer_path.to_owned()))?
+ .to_string();
+
+ let mut composite_proto = CompositeDisk::new();
+ composite_proto.version = COMPOSITE_DISK_VERSION;
+ composite_proto.component_disks.push(ComponentDisk {
+ file_path: header_path,
+ offset: 0,
+ read_write_capability: ReadWriteCapability::READ_ONLY,
+ ..ComponentDisk::new()
+ });
+
+ // Write partitions to a temporary buffer so that we can calculate the CRC, and construct the
+ // ComponentDisk proto messages at the same time.
+ let mut partitions_buffer =
+ [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
+ let mut writer: &mut [u8] = &mut partitions_buffer;
+ let mut next_disk_offset = GPT_BEGINNING_SIZE;
+ let mut labels = HashSet::with_capacity(partitions.len());
+ for partition in partitions {
+ let gpt_entry = create_gpt_entry(partition, next_disk_offset);
+ if !labels.insert(gpt_entry.partition_name) {
+ return Err(Error::DuplicatePartitionLabel(partition.label.clone()));
+ }
+ gpt_entry.write_bytes(&mut writer)?;
+
+ for component_disk in
+ create_component_disks(partition, next_disk_offset, &zero_filler_path)?
+ {
+ composite_proto.component_disks.push(component_disk);
+ }
+
+ next_disk_offset += partition.aligned_size();
+ }
+ let secondary_table_offset = next_disk_offset;
+ let disk_size = align_to_power_of_2(secondary_table_offset + GPT_END_SIZE, DISK_SIZE_SHIFT);
+
+ composite_proto.component_disks.push(ComponentDisk {
+ file_path: footer_path,
+ offset: secondary_table_offset,
+ read_write_capability: ReadWriteCapability::READ_ONLY,
+ ..ComponentDisk::new()
+ });
+
+ // Calculate CRC32 of partition entries.
+ let mut hasher = Hasher::new();
+ hasher.update(&partitions_buffer);
+ let partition_entries_crc32 = hasher.finalize();
+
+ let disk_guid = Uuid::new_v4();
+ write_beginning(
+ header_file,
+ disk_guid,
+ &partitions_buffer,
+ partition_entries_crc32,
+ secondary_table_offset,
+ disk_size,
+ )?;
+ write_end(
+ footer_file,
+ disk_guid,
+ &partitions_buffer,
+ partition_entries_crc32,
+ secondary_table_offset,
+ disk_size,
+ )?;
+
+ composite_proto.length = disk_size;
+ output_composite
+ .write_all(CDISK_MAGIC.as_bytes())
+ .map_err(Error::WriteHeader)?;
+ composite_proto
+ .write_to_writer(output_composite)
+ .map_err(Error::WriteProto)?;
+
+ Ok(())
+}
+
+/// Create a zero filler file which can be used to fill the gaps between partition files.
+/// The filler is sized to be big enough to fill the gaps. (1 << PARTITION_SIZE_SHIFT)
+pub fn create_zero_filler<P: AsRef<Path>>(zero_filler_path: P) -> Result<()> {
+ let f = OpenOptions::new()
+ .create(true)
+ .read(true)
+ .write(true)
+ .truncate(true)
+ .open(zero_filler_path.as_ref())
+ .map_err(Error::WriteZeroFiller)?;
+ f.set_len(1 << PARTITION_SIZE_SHIFT)
+ .map_err(Error::WriteZeroFiller)
+}
+
#[cfg(test)]
mod tests {
use super::*;
+
+ use std::matches;
+
use base::AsRawDescriptor;
use data_model::VolatileMemory;
use tempfile::tempfile;
@@ -414,7 +734,7 @@ mod tests {
file2.as_raw_descriptor(),
file3.as_raw_descriptor(),
];
- in_fds.sort();
+ in_fds.sort_unstable();
let disk_part1 = ComponentDiskPart {
file: Box::new(file1),
offset: 0,
@@ -432,7 +752,7 @@ mod tests {
};
let composite = CompositeDiskFile::new(vec![disk_part1, disk_part2, disk_part3]).unwrap();
let mut out_fds = composite.as_raw_descriptors();
- out_fds.sort();
+ out_fds.sort_unstable();
assert_eq!(in_fds, out_fds);
}
@@ -505,9 +825,7 @@ mod tests {
.read_exact_at_volatile(output_volatile_memory.get_slice(0, 300).unwrap(), 0)
.unwrap();
- for i in 50..250 {
- input_memory[i] = 0;
- }
+ input_memory[50..250].iter_mut().for_each(|x| *x = 0);
assert!(input_memory.iter().eq(output_memory.iter()));
}
@@ -550,9 +868,7 @@ mod tests {
.read_exact_at_volatile(output_volatile_memory.get_slice(0, 300).unwrap(), 0)
.unwrap();
- for i in 50..250 {
- input_memory[i] = 0;
- }
+ input_memory[50..250].iter_mut().for_each(|x| *x = 0);
for i in 0..300 {
println!(
"input[{0}] = {1}, output[{0}] = {2}",
@@ -561,4 +877,146 @@ mod tests {
}
assert!(input_memory.iter().eq(output_memory.iter()));
}
+
+ #[test]
+ fn beginning_size() {
+ let mut buffer = vec![];
+ let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
+ let disk_size = 1000 * SECTOR_SIZE;
+ write_beginning(
+ &mut buffer,
+ Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
+ &partitions,
+ 42,
+ disk_size - GPT_END_SIZE,
+ disk_size,
+ )
+ .unwrap();
+
+ assert_eq!(buffer.len(), GPT_BEGINNING_SIZE as usize);
+ }
+
+ #[test]
+ fn end_size() {
+ let mut buffer = vec![];
+ let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
+ let disk_size = 1000 * SECTOR_SIZE;
+ write_end(
+ &mut buffer,
+ Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
+ &partitions,
+ 42,
+ disk_size - GPT_END_SIZE,
+ disk_size,
+ )
+ .unwrap();
+
+ assert_eq!(buffer.len(), GPT_END_SIZE as usize);
+ }
+
+ #[test]
+ fn end_size_with_padding() {
+ let mut buffer = vec![];
+ let partitions = [0u8; GPT_NUM_PARTITIONS as usize * GPT_PARTITION_ENTRY_SIZE as usize];
+ let disk_size = 1000 * SECTOR_SIZE;
+ let padding = 3 * SECTOR_SIZE;
+ write_end(
+ &mut buffer,
+ Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
+ &partitions,
+ 42,
+ disk_size - GPT_END_SIZE - padding,
+ disk_size,
+ )
+ .unwrap();
+
+ assert_eq!(buffer.len(), GPT_END_SIZE as usize + padding as usize);
+ }
+
+ /// Creates a composite disk image with no partitions.
+ #[test]
+ fn create_composite_disk_empty() {
+ let mut header_image = tempfile().unwrap();
+ let mut footer_image = tempfile().unwrap();
+ let mut composite_image = tempfile().unwrap();
+
+ create_composite_disk(
+ &[],
+ Path::new("/zero_filler.img"),
+ Path::new("/header_path.img"),
+ &mut header_image,
+ Path::new("/footer_path.img"),
+ &mut footer_image,
+ &mut composite_image,
+ )
+ .unwrap();
+ }
+
+ /// Creates a composite disk image with two partitions.
+ #[test]
+ fn create_composite_disk_success() {
+ let mut header_image = tempfile().unwrap();
+ let mut footer_image = tempfile().unwrap();
+ let mut composite_image = tempfile().unwrap();
+
+ create_composite_disk(
+ &[
+ PartitionInfo {
+ label: "partition1".to_string(),
+ path: "/partition1.img".to_string().into(),
+ partition_type: ImagePartitionType::LinuxFilesystem,
+ writable: false,
+ size: 0,
+ },
+ PartitionInfo {
+ label: "partition2".to_string(),
+ path: "/partition2.img".to_string().into(),
+ partition_type: ImagePartitionType::LinuxFilesystem,
+ writable: true,
+ size: 0,
+ },
+ ],
+ Path::new("/zero_filler.img"),
+ Path::new("/header_path.img"),
+ &mut header_image,
+ Path::new("/footer_path.img"),
+ &mut footer_image,
+ &mut composite_image,
+ )
+ .unwrap();
+ }
+
+ /// Attempts to create a composite disk image with two partitions with the same label.
+ #[test]
+ fn create_composite_disk_duplicate_label() {
+ let mut header_image = tempfile().unwrap();
+ let mut footer_image = tempfile().unwrap();
+ let mut composite_image = tempfile().unwrap();
+
+ let result = create_composite_disk(
+ &[
+ PartitionInfo {
+ label: "label".to_string(),
+ path: "/partition1.img".to_string().into(),
+ partition_type: ImagePartitionType::LinuxFilesystem,
+ writable: false,
+ size: 0,
+ },
+ PartitionInfo {
+ label: "label".to_string(),
+ path: "/partition2.img".to_string().into(),
+ partition_type: ImagePartitionType::LinuxFilesystem,
+ writable: true,
+ size: 0,
+ },
+ ],
+ Path::new("/zero_filler.img"),
+ Path::new("/header_path.img"),
+ &mut header_image,
+ Path::new("/footer_path.img"),
+ &mut footer_image,
+ &mut composite_image,
+ );
+ assert!(matches!(result, Err(Error::DuplicatePartitionLabel(label)) if label == "label"));
+ }
}
diff --git a/disk/src/disk.rs b/disk/src/disk.rs
index bd1945d30..9a8aaf66c 100644
--- a/disk/src/disk.rs
+++ b/disk/src/disk.rs
@@ -3,19 +3,20 @@
// found in the LICENSE file.
use std::cmp::min;
-use std::fmt::{self, Debug, Display};
+use std::fmt::Debug;
use std::fs::File;
-use std::io::{self, Read, Seek, SeekFrom, Write};
+use std::io::{self, Read, Seek, SeekFrom};
+use std::path::Path;
use std::sync::Arc;
use async_trait::async_trait;
use base::{
- AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile, FileSetLen, FileSync, PunchHole,
- SeekHole, WriteZeroesAt,
+ get_filesystem_type, info, AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile, FileSetLen,
+ FileSync, PunchHole, WriteZeroesAt,
};
use cros_async::Executor;
-use libc::EINVAL;
use remain::sorted;
+use thiserror::Error as ThisError;
use vm_memory::GuestMemory;
mod qcow;
@@ -25,30 +26,63 @@ pub use qcow::{QcowFile, QCOW_MAGIC};
mod composite;
#[cfg(feature = "composite-disk")]
use composite::{CompositeDiskFile, CDISK_MAGIC, CDISK_MAGIC_LEN};
+#[cfg(feature = "composite-disk")]
+mod gpt;
+#[cfg(feature = "composite-disk")]
+pub use composite::{
+ create_composite_disk, create_zero_filler, Error as CompositeError, ImagePartitionType,
+ PartitionInfo,
+};
+#[cfg(feature = "composite-disk")]
+pub use gpt::Error as GptError;
mod android_sparse;
use android_sparse::{AndroidSparse, SPARSE_HEADER_MAGIC};
+/// Nesting depth limit for disk formats that can open other disk files.
+pub const MAX_NESTING_DEPTH: u32 = 10;
+
#[sorted]
-#[derive(Debug)]
+#[derive(ThisError, Debug)]
pub enum Error {
+ #[error("failed to create block device: {0}")]
BlockDeviceNew(base::Error),
+ #[error("requested file conversion not supported")]
ConversionNotSupported,
+ #[error("failure in android sparse disk: {0}")]
CreateAndroidSparseDisk(android_sparse::Error),
#[cfg(feature = "composite-disk")]
+ #[error("failure in composite disk: {0}")]
CreateCompositeDisk(composite::Error),
+ #[error("failure creating single file disk: {0}")]
CreateSingleFileDisk(cros_async::AsyncError),
+ #[error("failure with fallocate: {0}")]
Fallocate(cros_async::AsyncError),
+ #[error("failure with fsync: {0}")]
Fsync(cros_async::AsyncError),
+ #[error("checking host fs type: {0}")]
+ HostFsType(base::Error),
+ #[error("maximum disk nesting depth exceeded")]
+ MaxNestingDepthExceeded,
+ #[error("failure in qcow: {0}")]
QcowError(qcow::Error),
+ #[error("failed to read data: {0}")]
ReadingData(io::Error),
+ #[error("failed to read header: {0}")]
ReadingHeader(io::Error),
+ #[error("failed to read to memory: {0}")]
ReadToMem(cros_async::AsyncError),
+ #[error("failed to seek file: {0}")]
SeekingFile(io::Error),
+ #[error("failed to set file size: {0}")]
SettingFileSize(io::Error),
+ #[error("unknown disk type")]
UnknownType,
+ #[error("failed to write from memory: {0}")]
WriteFromMem(cros_async::AsyncError),
+ #[error("failed to write from vec: {0}")]
WriteFromVec(cros_async::AsyncError),
+ #[error("failed to write data: {0}")]
WritingData(io::Error),
}
@@ -116,35 +150,6 @@ impl ToAsyncDisk for File {
}
}
-impl Display for Error {
- #[remain::check]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- #[sorted]
- match self {
- BlockDeviceNew(e) => write!(f, "failed to create block device: {}", e),
- ConversionNotSupported => write!(f, "requested file conversion not supported"),
- CreateAndroidSparseDisk(e) => write!(f, "failure in android sparse disk: {}", e),
- #[cfg(feature = "composite-disk")]
- CreateCompositeDisk(e) => write!(f, "failure in composite disk: {}", e),
- CreateSingleFileDisk(e) => write!(f, "failure creating single file disk: {}", e),
- Fallocate(e) => write!(f, "failure with fallocate: {}", e),
- Fsync(e) => write!(f, "failure with fsync: {}", e),
- QcowError(e) => write!(f, "failure in qcow: {}", e),
- ReadingData(e) => write!(f, "failed to read data: {}", e),
- ReadingHeader(e) => write!(f, "failed to read header: {}", e),
- ReadToMem(e) => write!(f, "failed to read to memory: {}", e),
- SeekingFile(e) => write!(f, "failed to seek file: {}", e),
- SettingFileSize(e) => write!(f, "failed to set file size: {}", e),
- UnknownType => write!(f, "unknown disk type"),
- WriteFromMem(e) => write!(f, "failed to write from memory: {}", e),
- WriteFromVec(e) => write!(f, "failed to write from vec: {}", e),
- WritingData(e) => write!(f, "failed to write data: {}", e),
- }
- }
-}
-
/// The variants of image files on the host that can be used as virtual disks.
#[derive(Debug, PartialEq, Eq)]
pub enum ImageType {
@@ -154,116 +159,12 @@ pub enum ImageType {
AndroidSparse,
}
-fn convert_copy<R, W>(reader: &mut R, writer: &mut W, offset: u64, size: u64) -> Result<()>
-where
- R: Read + Seek,
- W: Write + Seek,
-{
- const CHUNK_SIZE: usize = 65536;
- let mut buf = [0; CHUNK_SIZE];
- let mut read_count = 0;
- reader
- .seek(SeekFrom::Start(offset))
- .map_err(Error::SeekingFile)?;
- writer
- .seek(SeekFrom::Start(offset))
- .map_err(Error::SeekingFile)?;
- loop {
- let this_count = min(CHUNK_SIZE as u64, size - read_count) as usize;
- let nread = reader
- .read(&mut buf[..this_count])
- .map_err(Error::ReadingData)?;
- writer.write(&buf[..nread]).map_err(Error::WritingData)?;
- read_count += nread as u64;
- if nread == 0 || read_count == size {
- break;
- }
- }
-
- Ok(())
-}
-
-fn convert_reader_writer<R, W>(reader: &mut R, writer: &mut W, size: u64) -> Result<()>
-where
- R: Read + Seek + SeekHole,
- W: Write + Seek,
-{
- let mut offset = 0;
- while offset < size {
- // Find the next range of data.
- let next_data = match reader.seek_data(offset).map_err(Error::SeekingFile)? {
- Some(o) => o,
- None => {
- // No more data in the file.
- break;
- }
- };
- let next_hole = match reader.seek_hole(next_data).map_err(Error::SeekingFile)? {
- Some(o) => o,
- None => {
- // This should not happen - there should always be at least one hole
- // after any data.
- return Err(Error::SeekingFile(io::Error::from_raw_os_error(EINVAL)));
- }
- };
- let count = next_hole - next_data;
- convert_copy(reader, writer, next_data, count)?;
- offset = next_hole;
- }
-
+fn log_host_fs_type(file: &File) -> Result<()> {
+ let fstype = get_filesystem_type(file).map_err(Error::HostFsType)?;
+ info!("Disk image file is hosted on file system type {:x}", fstype);
Ok(())
}
-fn convert_reader<R>(reader: &mut R, dst_file: File, dst_type: ImageType) -> Result<()>
-where
- R: Read + Seek + SeekHole,
-{
- let src_size = reader.seek(SeekFrom::End(0)).map_err(Error::SeekingFile)?;
- reader
- .seek(SeekFrom::Start(0))
- .map_err(Error::SeekingFile)?;
-
- // Ensure the destination file is empty before writing to it.
- dst_file.set_len(0).map_err(Error::SettingFileSize)?;
-
- match dst_type {
- ImageType::Qcow2 => {
- let mut dst_writer = QcowFile::new(dst_file, src_size).map_err(Error::QcowError)?;
- convert_reader_writer(reader, &mut dst_writer, src_size)
- }
- ImageType::Raw => {
- let mut dst_writer = dst_file;
- // Set the length of the destination file to convert it into a sparse file
- // of the desired size.
- dst_writer
- .set_len(src_size)
- .map_err(Error::SettingFileSize)?;
- convert_reader_writer(reader, &mut dst_writer, src_size)
- }
- _ => Err(Error::ConversionNotSupported),
- }
-}
-
-/// Copy the contents of a disk image in `src_file` into `dst_file`.
-/// The type of `src_file` is automatically detected, and the output file type is
-/// determined by `dst_type`.
-pub fn convert(src_file: File, dst_file: File, dst_type: ImageType) -> Result<()> {
- let src_type = detect_image_type(&src_file)?;
- match src_type {
- ImageType::Qcow2 => {
- let mut src_reader = QcowFile::from(src_file).map_err(Error::QcowError)?;
- convert_reader(&mut src_reader, dst_file, dst_type)
- }
- ImageType::Raw => {
- // src_file is a raw file.
- let mut src_reader = src_file;
- convert_reader(&mut src_reader, dst_file, dst_type)
- }
- // TODO(schuffelen): Implement Read + Write + SeekHole for CompositeDiskFile
- _ => Err(Error::ConversionNotSupported),
- }
-}
-
/// Detect the type of an image file by checking for a valid header of the supported formats.
pub fn detect_image_type(file: &File) -> Result<ImageType> {
let mut f = file;
@@ -271,9 +172,17 @@ pub fn detect_image_type(file: &File) -> Result<ImageType> {
let orig_seek = f.seek(SeekFrom::Current(0)).map_err(Error::SeekingFile)?;
f.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
+ info!("disk size {}, ", disk_size);
+ log_host_fs_type(f)?;
// Try to read the disk in a nicely-aligned block size unless the whole file is smaller.
const MAGIC_BLOCK_SIZE: usize = 4096;
- let mut magic = [0u8; MAGIC_BLOCK_SIZE];
+ #[repr(align(4096))]
+ struct BlockAlignedBuffer {
+ data: [u8; MAGIC_BLOCK_SIZE],
+ }
+ let mut magic = BlockAlignedBuffer {
+ data: [0u8; MAGIC_BLOCK_SIZE],
+ };
let magic_read_len = if disk_size > MAGIC_BLOCK_SIZE as u64 {
MAGIC_BLOCK_SIZE
} else {
@@ -282,19 +191,19 @@ pub fn detect_image_type(file: &File) -> Result<ImageType> {
disk_size as usize
};
- f.read_exact(&mut magic[0..magic_read_len])
+ f.read_exact(&mut magic.data[0..magic_read_len])
.map_err(Error::ReadingHeader)?;
f.seek(SeekFrom::Start(orig_seek))
.map_err(Error::SeekingFile)?;
#[cfg(feature = "composite-disk")]
- if let Some(cdisk_magic) = magic.get(0..CDISK_MAGIC_LEN) {
+ if let Some(cdisk_magic) = magic.data.get(0..CDISK_MAGIC_LEN) {
if cdisk_magic == CDISK_MAGIC.as_bytes() {
return Ok(ImageType::CompositeDisk);
}
}
- if let Some(magic4) = magic.get(0..4) {
+ if let Some(magic4) = magic.data.get(0..4) {
if magic4 == QCOW_MAGIC.to_be_bytes() {
return Ok(ImageType::Qcow2);
} else if magic4 == SPARSE_HEADER_MAGIC.to_le_bytes() {
@@ -326,18 +235,31 @@ pub fn create_async_disk_file(raw_image: File) -> Result<Box<dyn ToAsyncDisk>> {
}
/// Inspect the image file type and create an appropriate disk file to match it.
-pub fn create_disk_file(raw_image: File) -> Result<Box<dyn DiskFile>> {
+pub fn create_disk_file(
+ raw_image: File,
+ mut max_nesting_depth: u32,
+ // image_path is only used if the composite-disk feature is enabled.
+ #[allow(unused_variables)] image_path: &Path,
+) -> Result<Box<dyn DiskFile>> {
+ if max_nesting_depth == 0 {
+ return Err(Error::MaxNestingDepthExceeded);
+ }
+ max_nesting_depth -= 1;
+
let image_type = detect_image_type(&raw_image)?;
Ok(match image_type {
ImageType::Raw => Box::new(raw_image) as Box<dyn DiskFile>,
ImageType::Qcow2 => {
- Box::new(QcowFile::from(raw_image).map_err(Error::QcowError)?) as Box<dyn DiskFile>
+ Box::new(QcowFile::from(raw_image, max_nesting_depth).map_err(Error::QcowError)?)
+ as Box<dyn DiskFile>
}
#[cfg(feature = "composite-disk")]
ImageType::CompositeDisk => {
// Valid composite disk header present
- Box::new(CompositeDiskFile::from_file(raw_image).map_err(Error::CreateCompositeDisk)?)
- as Box<dyn DiskFile>
+ Box::new(
+ CompositeDiskFile::from_file(raw_image, max_nesting_depth, image_path)
+ .map_err(Error::CreateCompositeDisk)?,
+ ) as Box<dyn DiskFile>
}
#[cfg(not(feature = "composite-disk"))]
ImageType::CompositeDisk => return Err(Error::UnknownType),
@@ -431,7 +353,7 @@ impl AsyncDisk for SingleFileDisk {
mem_offsets: &'a [cros_async::MemRegion],
) -> Result<usize> {
self.inner
- .read_to_mem(file_offset, mem, mem_offsets)
+ .read_to_mem(Some(file_offset), mem, mem_offsets)
.await
.map_err(Error::ReadToMem)
}
@@ -443,7 +365,7 @@ impl AsyncDisk for SingleFileDisk {
mem_offsets: &'a [cros_async::MemRegion],
) -> Result<usize> {
self.inner
- .write_from_mem(file_offset, mem, mem_offsets)
+ .write_from_mem(Some(file_offset), mem, mem_offsets)
.await
.map_err(Error::WriteFromMem)
}
@@ -482,7 +404,7 @@ impl AsyncDisk for SingleFileDisk {
let buf = vec![0u8; write_size];
nwritten += self
.inner
- .write_from_vec(file_offset + nwritten as u64, buf)
+ .write_from_vec(Some(file_offset + nwritten as u64), buf)
.await
.map(|(n, _)| n as u64)
.map_err(Error::WriteFromVec)?;
@@ -496,6 +418,7 @@ mod tests {
use super::*;
use std::fs::{File, OpenOptions};
+ use std::io::Write;
use cros_async::{Executor, MemRegion};
use vm_memory::{GuestAddress, GuestMemory};
@@ -557,7 +480,7 @@ mod tests {
// detect_image_type is ever updated to validate more of the header, this test would need
// to be updated.
let buf: &[u8] = &[0x51, 0x46, 0x49, 0xfb];
- t.write_all(&buf).unwrap();
+ t.write_all(buf).unwrap();
let image_type = detect_image_type(&t).expect("failed to detect image type");
assert_eq!(image_type, ImageType::Qcow2);
}
@@ -569,7 +492,7 @@ mod tests {
// detect_image_type is ever updated to validate more of the header, this test would need
// to be updated.
let buf: &[u8] = &[0x3a, 0xff, 0x26, 0xed];
- t.write_all(&buf).unwrap();
+ t.write_all(buf).unwrap();
let image_type = detect_image_type(&t).expect("failed to detect image type");
assert_eq!(image_type, ImageType::AndroidSparse);
}
@@ -582,7 +505,7 @@ mod tests {
// detect_image_type is ever updated to validate more of the header, this test would need
// to be updated.
let buf = "composite_disk\x1d".as_bytes();
- t.write_all(&buf).unwrap();
+ t.write_all(buf).unwrap();
let image_type = detect_image_type(&t).expect("failed to detect image type");
assert_eq!(image_type, ImageType::CompositeDisk);
}
@@ -593,7 +516,7 @@ mod tests {
// Write a file smaller than the four-byte qcow2/sparse magic to ensure the small file logic
// works correctly and handles it as a raw file.
let buf: &[u8] = &[0xAA, 0xBB];
- t.write_all(&buf).unwrap();
+ t.write_all(buf).unwrap();
let image_type = detect_image_type(&t).expect("failed to detect image type");
assert_eq!(image_type, ImageType::Raw);
}
diff --git a/disk/src/gpt.rs b/disk/src/gpt.rs
new file mode 100644
index 000000000..39e6d590c
--- /dev/null
+++ b/disk/src/gpt.rs
@@ -0,0 +1,275 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Functions for writing GUID Partition Tables for use in a composite disk image.
+
+use std::convert::TryInto;
+use std::io::{self, Write};
+use std::num::TryFromIntError;
+
+use crc32fast::Hasher;
+use remain::sorted;
+use thiserror::Error as ThisError;
+use uuid::Uuid;
+
+/// The size in bytes of a disk sector (also called a block).
+pub const SECTOR_SIZE: u64 = 1 << 9;
+/// The size in bytes on an MBR partition entry.
+const MBR_PARTITION_ENTRY_SIZE: usize = 16;
+/// The size in bytes of a GPT header.
+pub const GPT_HEADER_SIZE: u32 = 92;
+/// The number of partition entries in the GPT, which is the maximum number of partitions which are
+/// supported.
+pub const GPT_NUM_PARTITIONS: u32 = 128;
+/// The size in bytes of a single GPT partition entry.
+pub const GPT_PARTITION_ENTRY_SIZE: u32 = 128;
+/// The size in bytes of everything before the first partition: i.e. the MBR, GPT header and GPT
+/// partition entries.
+pub const GPT_BEGINNING_SIZE: u64 = SECTOR_SIZE * 40;
+/// The size in bytes of everything after the last partition: i.e. the GPT partition entries and GPT
+/// footer.
+pub const GPT_END_SIZE: u64 = SECTOR_SIZE * 33;
+
+#[sorted]
+#[derive(ThisError, Debug)]
+pub enum Error {
+ /// The disk size was invalid (too large).
+ #[error("invalid disk size: {0}")]
+ InvalidDiskSize(TryFromIntError),
+ /// There was an error writing data to one of the image files.
+ #[error("failed to write data: {0}")]
+ WritingData(io::Error),
+}
+
+/// Write a protective MBR for a disk of the given total size (in bytes).
+///
+/// This should be written at the start of the disk, before the GPT header. It is one `SECTOR_SIZE`
+/// long.
+pub fn write_protective_mbr(file: &mut impl Write, disk_size: u64) -> Result<(), Error> {
+ // Bootstrap code
+ file.write_all(&[0; 446]).map_err(Error::WritingData)?;
+
+ // Partition status
+ file.write_all(&[0x00]).map_err(Error::WritingData)?;
+ // Begin CHS
+ file.write_all(&[0; 3]).map_err(Error::WritingData)?;
+ // Partition type
+ file.write_all(&[0xEE]).map_err(Error::WritingData)?;
+ // End CHS
+ file.write_all(&[0; 3]).map_err(Error::WritingData)?;
+ let first_lba: u32 = 1;
+ file.write_all(&first_lba.to_le_bytes())
+ .map_err(Error::WritingData)?;
+ let number_of_sectors: u32 = (disk_size / SECTOR_SIZE)
+ .try_into()
+ .map_err(Error::InvalidDiskSize)?;
+ file.write_all(&number_of_sectors.to_le_bytes())
+ .map_err(Error::WritingData)?;
+
+ // Three more empty partitions
+ file.write_all(&[0; MBR_PARTITION_ENTRY_SIZE * 3])
+ .map_err(Error::WritingData)?;
+
+ // Boot signature
+ file.write_all(&[0x55, 0xAA]).map_err(Error::WritingData)?;
+
+ Ok(())
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+struct GptHeader {
+ signature: [u8; 8],
+ revision: [u8; 4],
+ header_size: u32,
+ header_crc32: u32,
+ current_lba: u64,
+ backup_lba: u64,
+ first_usable_lba: u64,
+ last_usable_lba: u64,
+ disk_guid: Uuid,
+ partition_entries_lba: u64,
+ num_partition_entries: u32,
+ partition_entry_size: u32,
+ partition_entries_crc32: u32,
+}
+
+impl GptHeader {
+ fn write_bytes(&self, out: &mut impl Write) -> Result<(), Error> {
+ out.write_all(&self.signature).map_err(Error::WritingData)?;
+ out.write_all(&self.revision).map_err(Error::WritingData)?;
+ out.write_all(&self.header_size.to_le_bytes())
+ .map_err(Error::WritingData)?;
+ out.write_all(&self.header_crc32.to_le_bytes())
+ .map_err(Error::WritingData)?;
+ // Reserved
+ out.write_all(&[0; 4]).map_err(Error::WritingData)?;
+ out.write_all(&self.current_lba.to_le_bytes())
+ .map_err(Error::WritingData)?;
+ out.write_all(&self.backup_lba.to_le_bytes())
+ .map_err(Error::WritingData)?;
+ out.write_all(&self.first_usable_lba.to_le_bytes())
+ .map_err(Error::WritingData)?;
+ out.write_all(&self.last_usable_lba.to_le_bytes())
+ .map_err(Error::WritingData)?;
+
+ // GUID is mixed-endian for some reason, so we can't just use `Uuid::as_bytes()`.
+ write_guid(out, self.disk_guid).map_err(Error::WritingData)?;
+
+ out.write_all(&self.partition_entries_lba.to_le_bytes())
+ .map_err(Error::WritingData)?;
+ out.write_all(&self.num_partition_entries.to_le_bytes())
+ .map_err(Error::WritingData)?;
+ out.write_all(&self.partition_entry_size.to_le_bytes())
+ .map_err(Error::WritingData)?;
+ out.write_all(&self.partition_entries_crc32.to_le_bytes())
+ .map_err(Error::WritingData)?;
+ Ok(())
+ }
+}
+
+/// Write a GPT header for the disk.
+///
+/// It may either be a primary header (which should go at LBA 1) or a secondary header (which should
+/// go at the end of the disk).
+pub fn write_gpt_header(
+ out: &mut impl Write,
+ disk_guid: Uuid,
+ partition_entries_crc32: u32,
+ secondary_table_offset: u64,
+ secondary: bool,
+) -> Result<(), Error> {
+ let primary_header_lba = 1;
+ let secondary_header_lba = (secondary_table_offset + GPT_END_SIZE) / SECTOR_SIZE - 1;
+ let mut gpt_header = GptHeader {
+ signature: *b"EFI PART",
+ revision: [0, 0, 1, 0],
+ header_size: GPT_HEADER_SIZE,
+ current_lba: if secondary {
+ secondary_header_lba
+ } else {
+ primary_header_lba
+ },
+ backup_lba: if secondary {
+ primary_header_lba
+ } else {
+ secondary_header_lba
+ },
+ first_usable_lba: GPT_BEGINNING_SIZE / SECTOR_SIZE,
+ last_usable_lba: secondary_table_offset / SECTOR_SIZE - 1,
+ disk_guid,
+ partition_entries_lba: 2,
+ num_partition_entries: GPT_NUM_PARTITIONS,
+ partition_entry_size: GPT_PARTITION_ENTRY_SIZE,
+ partition_entries_crc32,
+ header_crc32: 0,
+ };
+
+ // Write once to a temporary buffer to calculate the CRC.
+ let mut header_without_crc = [0u8; GPT_HEADER_SIZE as usize];
+ gpt_header.write_bytes(&mut &mut header_without_crc[..])?;
+ let mut hasher = Hasher::new();
+ hasher.update(&header_without_crc);
+ gpt_header.header_crc32 = hasher.finalize();
+
+ gpt_header.write_bytes(out)?;
+
+ Ok(())
+}
+
+/// A GPT entry for a particular partition.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct GptPartitionEntry {
+ pub partition_type_guid: Uuid,
+ pub unique_partition_guid: Uuid,
+ pub first_lba: u64,
+ pub last_lba: u64,
+ pub attributes: u64,
+ /// UTF-16LE
+ pub partition_name: [u16; 36],
+}
+
+// This is implemented manually because `Default` isn't implemented in the standard library for
+// arrays of more than 32 elements. If that gets implemented (now than const generics are in) then
+// we can derive this instead.
+impl Default for GptPartitionEntry {
+ fn default() -> Self {
+ Self {
+ partition_type_guid: Default::default(),
+ unique_partition_guid: Default::default(),
+ first_lba: 0,
+ last_lba: 0,
+ attributes: 0,
+ partition_name: [0; 36],
+ }
+ }
+}
+
+impl GptPartitionEntry {
+ /// Write out the partition table entry. It will take
+ /// `GPT_PARTITION_ENTRY_SIZE` bytes.
+ pub fn write_bytes(&self, out: &mut impl Write) -> Result<(), Error> {
+ write_guid(out, self.partition_type_guid).map_err(Error::WritingData)?;
+ write_guid(out, self.unique_partition_guid).map_err(Error::WritingData)?;
+ out.write_all(&self.first_lba.to_le_bytes())
+ .map_err(Error::WritingData)?;
+ out.write_all(&self.last_lba.to_le_bytes())
+ .map_err(Error::WritingData)?;
+ out.write_all(&self.attributes.to_le_bytes())
+ .map_err(Error::WritingData)?;
+ for code_unit in &self.partition_name {
+ out.write_all(&code_unit.to_le_bytes())
+ .map_err(Error::WritingData)?;
+ }
+ Ok(())
+ }
+}
+
+/// Write a UUID in the mixed-endian format which GPT uses for GUIDs.
+fn write_guid(out: &mut impl Write, guid: Uuid) -> Result<(), io::Error> {
+ let guid_fields = guid.as_fields();
+ out.write_all(&guid_fields.0.to_le_bytes())?;
+ out.write_all(&guid_fields.1.to_le_bytes())?;
+ out.write_all(&guid_fields.2.to_le_bytes())?;
+ out.write_all(guid_fields.3)?;
+
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn protective_mbr_size() {
+ let mut buffer = vec![];
+ write_protective_mbr(&mut buffer, 1000 * SECTOR_SIZE).unwrap();
+
+ assert_eq!(buffer.len(), SECTOR_SIZE as usize);
+ }
+
+ #[test]
+ fn header_size() {
+ let mut buffer = vec![];
+ write_gpt_header(
+ &mut buffer,
+ Uuid::from_u128(0x12345678_1234_5678_abcd_12345678abcd),
+ 42,
+ 1000 * SECTOR_SIZE,
+ false,
+ )
+ .unwrap();
+
+ assert_eq!(buffer.len(), GPT_HEADER_SIZE as usize);
+ }
+
+ #[test]
+ fn partition_entry_size() {
+ let mut buffer = vec![];
+ GptPartitionEntry::default()
+ .write_bytes(&mut buffer)
+ .unwrap();
+
+ assert_eq!(buffer.len(), GPT_PARTITION_ENTRY_SIZE as usize);
+ }
+}
diff --git a/disk/src/qcow/mod.rs b/disk/src/qcow/mod.rs
index 3a3ef9764..27d60f4d9 100644
--- a/disk/src/qcow/mod.rs
+++ b/disk/src/qcow/mod.rs
@@ -7,18 +7,19 @@ mod refcount;
mod vec_cache;
use base::{
- error, AsRawDescriptor, AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile,
- FileReadWriteVolatile, FileSetLen, FileSync, PunchHole, RawDescriptor, SeekHole, WriteZeroesAt,
+ error, open_file, AsRawDescriptor, AsRawDescriptors, FileAllocate, FileReadWriteAtVolatile,
+ FileSetLen, FileSync, PunchHole, RawDescriptor, WriteZeroesAt,
};
use data_model::{VolatileMemory, VolatileSlice};
use libc::{EINVAL, ENOSPC, ENOTSUP};
use remain::sorted;
+use thiserror::Error;
use std::cmp::{max, min};
-use std::fmt::{self, Display};
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Seek, SeekFrom, Write};
use std::mem::size_of;
+use std::path::Path;
use std::str;
use crate::qcow::qcow_raw_file::QcowRawFile;
@@ -27,103 +28,86 @@ use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache};
use crate::{create_disk_file, DiskFile, DiskGetLen};
#[sorted]
-#[derive(Debug)]
+#[derive(Error, Debug)]
pub enum Error {
+ #[error("backing file io error: {0}")]
BackingFileIo(io::Error),
+ #[error("backing file open error: {0}")]
BackingFileOpen(Box<crate::Error>),
+ #[error("backing file name is too long: {0} bytes over")]
BackingFileTooLong(usize),
+ #[error("compressed blocks not supported")]
CompressedBlocksNotSupported,
+ #[error("failed to evict cache: {0}")]
EvictingCache(io::Error),
+ #[error("file larger than max of {}: {0}", MAX_QCOW_FILE_SIZE)]
FileTooBig(u64),
+ #[error("failed to get file size: {0}")]
GettingFileSize(io::Error),
+ #[error("failed to get refcount: {0}")]
GettingRefcount(refcount::Error),
+ #[error("failed to parse filename: {0}")]
InvalidBackingFileName(str::Utf8Error),
+ #[error("invalid cluster index")]
InvalidClusterIndex,
+ #[error("invalid cluster size")]
InvalidClusterSize,
+ #[error("invalid index")]
InvalidIndex,
+ #[error("invalid L1 table offset")]
InvalidL1TableOffset,
+ #[error("invalid L1 table size {0}")]
InvalidL1TableSize(u32),
+ #[error("invalid magic")]
InvalidMagic,
+ #[error("invalid offset")]
InvalidOffset(u64),
+ #[error("invalid refcount table offset")]
InvalidRefcountTableOffset,
+ #[error("invalid refcount table size: {0}")]
InvalidRefcountTableSize(u64),
+ #[error("no free clusters")]
NoFreeClusters,
+ #[error("no refcount clusters")]
NoRefcountClusters,
+ #[error("not enough space for refcounts")]
NotEnoughSpaceForRefcounts,
+ #[error("failed to open file: {0}")]
OpeningFile(io::Error),
+ #[error("failed to open file: {0}")]
ReadingHeader(io::Error),
+ #[error("failed to read pointers: {0}")]
ReadingPointers(io::Error),
+ #[error("failed to read ref count block: {0}")]
ReadingRefCountBlock(refcount::Error),
+ #[error("failed to read ref counts: {0}")]
ReadingRefCounts(io::Error),
+ #[error("failed to rebuild ref counts: {0}")]
RebuildingRefCounts(io::Error),
+ #[error("refcount table offset past file end")]
RefcountTableOffEnd,
+ #[error("too many clusters specified for refcount table")]
RefcountTableTooLarge,
+ #[error("failed to seek file: {0}")]
SeekingFile(io::Error),
+ #[error("failed to set refcount refcount: {0}")]
SettingRefcountRefcount(io::Error),
+ #[error("size too small for number of clusters")]
SizeTooSmallForNumberOfClusters,
+ #[error("l1 entry table too large: {0}")]
TooManyL1Entries(u64),
+ #[error("ref count table too large: {0}")]
TooManyRefcounts(u64),
+ #[error("unsupported refcount order")]
UnsupportedRefcountOrder,
+ #[error("unsupported version: {0}")]
UnsupportedVersion(u32),
+ #[error("failed to write header: {0}")]
WritingHeader(io::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- #[remain::check]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- #[sorted]
- match self {
- BackingFileIo(e) => write!(f, "backing file io error: {}", e),
- BackingFileOpen(e) => write!(f, "backing file open error: {}", *e),
- BackingFileTooLong(len) => {
- write!(f, "backing file name is too long: {} bytes over", len)
- }
- CompressedBlocksNotSupported => write!(f, "compressed blocks not supported"),
- EvictingCache(e) => write!(f, "failed to evict cache: {}", e),
- FileTooBig(size) => write!(
- f,
- "file larger than max of {}: {}",
- MAX_QCOW_FILE_SIZE, size
- ),
- GettingFileSize(e) => write!(f, "failed to get file size: {}", e),
- GettingRefcount(e) => write!(f, "failed to get refcount: {}", e),
- InvalidBackingFileName(e) => write!(f, "failed to parse filename: {}", e),
- InvalidClusterIndex => write!(f, "invalid cluster index"),
- InvalidClusterSize => write!(f, "invalid cluster size"),
- InvalidIndex => write!(f, "invalid index"),
- InvalidL1TableOffset => write!(f, "invalid L1 table offset"),
- InvalidL1TableSize(size) => write!(f, "invalid L1 table size {}", size),
- InvalidMagic => write!(f, "invalid magic"),
- InvalidOffset(_) => write!(f, "invalid offset"),
- InvalidRefcountTableOffset => write!(f, "invalid refcount table offset"),
- InvalidRefcountTableSize(size) => write!(f, "invalid refcount table size: {}", size),
- NoFreeClusters => write!(f, "no free clusters"),
- NoRefcountClusters => write!(f, "no refcount clusters"),
- NotEnoughSpaceForRefcounts => write!(f, "not enough space for refcounts"),
- OpeningFile(e) => write!(f, "failed to open file: {}", e),
- ReadingHeader(e) => write!(f, "failed to read header: {}", e),
- ReadingPointers(e) => write!(f, "failed to read pointers: {}", e),
- ReadingRefCountBlock(e) => write!(f, "failed to read ref count block: {}", e),
- ReadingRefCounts(e) => write!(f, "failed to read ref counts: {}", e),
- RebuildingRefCounts(e) => write!(f, "failed to rebuild ref counts: {}", e),
- RefcountTableOffEnd => write!(f, "refcount table offset past file end"),
- RefcountTableTooLarge => write!(f, "too many clusters specified for refcount table"),
- SeekingFile(e) => write!(f, "failed to seek file: {}", e),
- SettingRefcountRefcount(e) => write!(f, "failed to set refcount refcount: {}", e),
- SizeTooSmallForNumberOfClusters => write!(f, "size too small for number of clusters"),
- TooManyL1Entries(count) => write!(f, "l1 entry table too large: {}", count),
- TooManyRefcounts(count) => write!(f, "ref count table too large: {}", count),
- UnsupportedRefcountOrder => write!(f, "unsupported refcount order"),
- UnsupportedVersion(v) => write!(f, "unsupported version: {}", v),
- WritingHeader(e) => write!(f, "failed to write header: {}", e),
- }
- }
-}
-
// Maximum data size supported.
const MAX_QCOW_FILE_SIZE: u64 = 0x01 << 44; // 16 TB.
@@ -391,13 +375,14 @@ fn max_refcount_clusters(refcount_order: u32, cluster_size: u32, num_clusters: u
/// # Example
///
/// ```
-/// # use std::io::{Read, Seek, SeekFrom};
+/// # use base::FileReadWriteAtVolatile;
+/// # use data_model::VolatileSlice;
/// # use disk::QcowFile;
/// # fn test(file: std::fs::File) -> std::io::Result<()> {
-/// let mut q = QcowFile::from(file).expect("Can't open qcow file");
+/// let mut q = QcowFile::from(file, disk::MAX_NESTING_DEPTH).expect("Can't open qcow file");
/// let mut buf = [0u8; 12];
-/// q.seek(SeekFrom::Start(10 as u64))?;
-/// q.read(&mut buf[..])?;
+/// let mut vslice = VolatileSlice::new(&mut buf);
+/// q.read_at_volatile(vslice, 10)?;
/// # Ok(())
/// # }
/// ```
@@ -419,7 +404,7 @@ pub struct QcowFile {
impl QcowFile {
/// Creates a QcowFile from `file`. File must be a valid qcow2 image.
- pub fn from(mut file: File) -> Result<QcowFile> {
+ pub fn from(mut file: File, max_nesting_depth: u32) -> Result<QcowFile> {
let header = QcowHeader::new(&mut file)?;
// Only v3 files are supported.
@@ -445,12 +430,14 @@ impl QcowFile {
let backing_file = if let Some(backing_file_path) = header.backing_file_path.as_ref() {
let path = backing_file_path.clone();
- let backing_raw_file = OpenOptions::new()
- .read(true)
- .open(path)
- .map_err(Error::BackingFileIo)?;
- let backing_file = create_disk_file(backing_raw_file)
- .map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
+ let backing_raw_file = open_file(
+ Path::new(&path),
+ OpenOptions::new().read(true), // TODO(b/190435784): Add support for O_DIRECT.
+ )
+ .map_err(|e| Error::BackingFileIo(e.into()))?;
+ let backing_file =
+ create_disk_file(backing_raw_file, max_nesting_depth, Path::new(&path))
+ .map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
Some(backing_file)
} else {
None
@@ -577,29 +564,43 @@ impl QcowFile {
/// Creates a new QcowFile at the given path.
pub fn new(file: File, virtual_size: u64) -> Result<QcowFile> {
let header = QcowHeader::create_for_size_and_path(virtual_size, None)?;
- QcowFile::new_from_header(file, header)
+ QcowFile::new_from_header(file, header, 1)
}
/// Creates a new QcowFile at the given path.
- pub fn new_from_backing(file: File, backing_file_name: &str) -> Result<QcowFile> {
- let backing_raw_file = OpenOptions::new()
- .read(true)
- .open(backing_file_name)
- .map_err(Error::BackingFileIo)?;
- let backing_file =
- create_disk_file(backing_raw_file).map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
+ pub fn new_from_backing(
+ file: File,
+ backing_file_name: &str,
+ backing_file_max_nesting_depth: u32,
+ ) -> Result<QcowFile> {
+ let backing_path = Path::new(backing_file_name);
+ let backing_raw_file = open_file(
+ backing_path,
+ OpenOptions::new().read(true), // TODO(b/190435784): add support for O_DIRECT.
+ )
+ .map_err(|e| Error::BackingFileIo(e.into()))?;
+ let backing_file = create_disk_file(
+ backing_raw_file,
+ backing_file_max_nesting_depth,
+ backing_path,
+ )
+ .map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
let size = backing_file.get_len().map_err(Error::BackingFileIo)?;
let header = QcowHeader::create_for_size_and_path(size, Some(backing_file_name))?;
- let mut result = QcowFile::new_from_header(file, header)?;
+ let mut result = QcowFile::new_from_header(file, header, backing_file_max_nesting_depth)?;
result.backing_file = Some(backing_file);
Ok(result)
}
- fn new_from_header(mut file: File, header: QcowHeader) -> Result<QcowFile> {
+ fn new_from_header(
+ mut file: File,
+ header: QcowHeader,
+ max_nesting_depth: u32,
+ ) -> Result<QcowFile> {
file.seek(SeekFrom::Start(0)).map_err(Error::SeekingFile)?;
header.write_to(&mut file)?;
- let mut qcow = Self::from(file)?;
+ let mut qcow = Self::from(file, max_nesting_depth)?;
// Set the refcount for each refcount table cluster.
let cluster_size = 0x01u64 << qcow.header.cluster_bits;
@@ -623,60 +624,6 @@ impl QcowFile {
self.backing_file = backing;
}
- /// Returns the `QcowHeader` for this file.
- pub fn header(&self) -> &QcowHeader {
- &self.header
- }
-
- /// Returns the L1 lookup table for this file. This is only useful for debugging.
- pub fn l1_table(&self) -> &[u64] {
- &self.l1_table.get_values()
- }
-
- /// Returns an L2_table of cluster addresses, only used for debugging.
- pub fn l2_table(&mut self, l1_index: usize) -> Result<Option<&[u64]>> {
- let l2_addr_disk = *self.l1_table.get(l1_index).ok_or(Error::InvalidIndex)?;
-
- if l2_addr_disk == 0 {
- // Reading from an unallocated cluster will return zeros.
- return Ok(None);
- }
-
- if !self.l2_cache.contains_key(&l1_index) {
- // Not in the cache.
- let table = VecCache::from_vec(
- Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk)
- .map_err(Error::ReadingPointers)?,
- );
- let l1_table = &self.l1_table;
- let raw_file = &mut self.raw_file;
- self.l2_cache
- .insert(l1_index, table, |index, evicted| {
- raw_file.write_pointer_table(
- l1_table[index],
- evicted.get_values(),
- CLUSTER_USED_FLAG,
- )
- })
- .map_err(Error::EvictingCache)?;
- }
-
- // The index must exist as it was just inserted if it didn't already.
- Ok(Some(self.l2_cache.get(&l1_index).unwrap().get_values()))
- }
-
- /// Returns the refcount table for this file. This is only useful for debugging.
- pub fn ref_table(&self) -> &[u64] {
- &self.refcounts.ref_table()
- }
-
- /// Returns the `index`th refcount block from the file.
- pub fn refcount_block(&mut self, index: usize) -> Result<Option<&[u16]>> {
- self.refcounts
- .refcount_block(&mut self.raw_file, index)
- .map_err(Error::ReadingRefCountBlock)
- }
-
/// Returns the first cluster in the file with a 0 refcount. Used for testing.
pub fn first_zero_refcount(&mut self) -> Result<Option<u64>> {
let file_size = self
@@ -885,7 +832,7 @@ impl QcowFile {
// Rewrite the top-level refcount table.
raw_file
- .write_pointer_table(header.refcount_table_offset, &ref_table, 0)
+ .write_pointer_table(header.refcount_table_offset, ref_table, 0)
.map_err(Error::WritingHeader)?;
// Rewrite the header again, now with lazy refcounts disabled.
@@ -1178,77 +1125,6 @@ impl QcowFile {
Ok(new_addr)
}
- // Returns true if the cluster containing `address` is already allocated.
- fn cluster_allocated(&mut self, address: u64) -> std::io::Result<bool> {
- if address >= self.virtual_size() as u64 {
- return Err(std::io::Error::from_raw_os_error(EINVAL));
- }
-
- let l1_index = self.l1_table_index(address) as usize;
- let l2_addr_disk = *self
- .l1_table
- .get(l1_index)
- .ok_or_else(|| std::io::Error::from_raw_os_error(EINVAL))?;
- let l2_index = self.l2_table_index(address) as usize;
-
- if l2_addr_disk == 0 {
- // The whole L2 table for this address is not allocated yet,
- // so the cluster must also be unallocated.
- return Ok(false);
- }
-
- if !self.l2_cache.contains_key(&l1_index) {
- // Not in the cache.
- let table =
- VecCache::from_vec(Self::read_l2_cluster(&mut self.raw_file, l2_addr_disk)?);
- let l1_table = &self.l1_table;
- let raw_file = &mut self.raw_file;
- self.l2_cache.insert(l1_index, table, |index, evicted| {
- raw_file.write_pointer_table(
- l1_table[index],
- evicted.get_values(),
- CLUSTER_USED_FLAG,
- )
- })?;
- }
-
- let cluster_addr = self.l2_cache.get(&l1_index).unwrap()[l2_index];
- // If cluster_addr != 0, the cluster is allocated.
- Ok(cluster_addr != 0)
- }
-
- // Find the first guest address greater than or equal to `address` whose allocation state
- // matches `allocated`.
- fn find_allocated_cluster(
- &mut self,
- address: u64,
- allocated: bool,
- ) -> std::io::Result<Option<u64>> {
- let size = self.virtual_size();
- if address >= size {
- return Ok(None);
- }
-
- // If offset is already within a hole, return it.
- if self.cluster_allocated(address)? == allocated {
- return Ok(Some(address));
- }
-
- // Skip to the next cluster boundary.
- let cluster_size = self.raw_file.cluster_size();
- let mut cluster_addr = (address / cluster_size + 1) * cluster_size;
-
- // Search for clusters with the desired allocation state.
- while cluster_addr < size {
- if self.cluster_allocated(cluster_addr)? == allocated {
- return Ok(Some(cluster_addr));
- }
- cluster_addr += cluster_size;
- }
-
- Ok(None)
- }
-
// Deallocate the storage for the cluster starting at `address`.
// Any future reads of this cluster will return all zeroes (or the backing file, if in use).
fn deallocate_cluster(&mut self, address: u64) -> std::io::Result<()> {
@@ -1457,7 +1333,7 @@ impl QcowFile {
if self.l1_table.dirty() {
self.raw_file.write_pointer_table(
self.header.l1_table_offset,
- &self.l1_table.get_values(),
+ self.l1_table.get_values(),
0,
)?;
self.l1_table.mark_clean();
@@ -1500,10 +1376,11 @@ impl QcowFile {
}
// Writes `count` bytes starting at `address`, calling `cb` repeatedly with the backing file,
- // number of bytes written so far, and number of bytes to write to the file in that invocation.
+ // number of bytes written so far, raw file offset, and number of bytes to write to the file in
+ // that invocation.
fn write_cb<F>(&mut self, address: u64, count: usize, mut cb: F) -> std::io::Result<usize>
where
- F: FnMut(&mut File, usize, usize) -> std::io::Result<()>,
+ F: FnMut(&mut File, usize, u64, usize) -> std::io::Result<()>,
{
let write_count: usize = self.limit_range_file(address, count);
@@ -1513,10 +1390,7 @@ impl QcowFile {
let offset = self.file_offset_write(curr_addr)?;
let count = self.limit_range_cluster(curr_addr, write_count - nwritten);
- if let Err(e) = self.raw_file.file_mut().seek(SeekFrom::Start(offset)) {
- return Err(e);
- }
- if let Err(e) = cb(self.raw_file.file_mut(), nwritten, count) {
+ if let Err(e) = cb(self.raw_file.file_mut(), nwritten, offset, count) {
return Err(e);
}
@@ -1599,49 +1473,20 @@ impl Seek for QcowFile {
impl Write for QcowFile {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
- let write_count =
- self.write_cb(self.current_offset, buf.len(), |file, offset, count| {
+ let write_count = self.write_cb(
+ self.current_offset,
+ buf.len(),
+ |file, offset, raw_offset, count| {
+ file.seek(SeekFrom::Start(raw_offset))?;
file.write_all(&buf[offset..(offset + count)])
- })?;
+ },
+ )?;
self.current_offset += write_count as u64;
Ok(write_count)
}
fn flush(&mut self) -> std::io::Result<()> {
- self.sync_caches()?;
- self.avail_clusters.append(&mut self.unref_clusters);
- Ok(())
- }
-}
-
-impl FileReadWriteVolatile for QcowFile {
- fn read_volatile(&mut self, slice: VolatileSlice) -> io::Result<usize> {
- let read_count = self.read_cb(
- self.current_offset,
- slice.size(),
- |file, read, offset, count| {
- let sub_slice = slice.get_slice(read, count).unwrap();
- match file {
- Some(f) => f.read_exact_at_volatile(sub_slice, offset),
- None => {
- sub_slice.write_bytes(0);
- Ok(())
- }
- }
- },
- )?;
- self.current_offset += read_count as u64;
- Ok(read_count)
- }
-
- fn write_volatile(&mut self, slice: VolatileSlice) -> io::Result<usize> {
- let write_count =
- self.write_cb(self.current_offset, slice.size(), |file, offset, count| {
- let sub_slice = slice.get_slice(offset, count).unwrap();
- file.write_all_volatile(sub_slice)
- })?;
- self.current_offset += write_count as u64;
- Ok(write_count)
+ self.fsync()
}
}
@@ -1660,16 +1505,18 @@ impl FileReadWriteAtVolatile for QcowFile {
}
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> io::Result<usize> {
- self.write_cb(offset, slice.size(), |file, offset, count| {
+ self.write_cb(offset, slice.size(), |file, offset, raw_offset, count| {
let sub_slice = slice.get_slice(offset, count).unwrap();
- file.write_all_volatile(sub_slice)
+ file.write_all_at_volatile(sub_slice, raw_offset)
})
}
}
impl FileSync for QcowFile {
fn fsync(&mut self) -> std::io::Result<()> {
- self.flush()
+ self.sync_caches()?;
+ self.avail_clusters.append(&mut self.unref_clusters);
+ Ok(())
}
}
@@ -1692,7 +1539,11 @@ impl FileAllocate for QcowFile {
fn allocate(&mut self, offset: u64, len: u64) -> io::Result<()> {
// Call write_cb with a do-nothing callback, which will have the effect
// of allocating all clusters in the specified range.
- self.write_cb(offset, len as usize, |_file, _offset, _count| Ok(()))?;
+ self.write_cb(
+ offset,
+ len as usize,
+ |_file, _offset, _raw_offset, _count| Ok(()),
+ )?;
Ok(())
}
}
@@ -1718,36 +1569,6 @@ impl WriteZeroesAt for QcowFile {
}
}
-impl SeekHole for QcowFile {
- fn seek_hole(&mut self, offset: u64) -> io::Result<Option<u64>> {
- match self.find_allocated_cluster(offset, false) {
- Err(e) => Err(e),
- Ok(None) => {
- if offset < self.virtual_size() {
- Ok(Some(self.seek(SeekFrom::End(0))?))
- } else {
- Ok(None)
- }
- }
- Ok(Some(o)) => {
- self.seek(SeekFrom::Start(o))?;
- Ok(Some(o))
- }
- }
- }
-
- fn seek_data(&mut self, offset: u64) -> io::Result<Option<u64>> {
- match self.find_allocated_cluster(offset, true) {
- Err(e) => Err(e),
- Ok(None) => Ok(None),
- Ok(Some(o)) => {
- self.seek(SeekFrom::Start(o))?;
- Ok(Some(o))
- }
- }
- }
-}
-
// Returns an Error if the given offset doesn't align to a cluster boundary.
fn offset_is_cluster_boundary(offset: u64, cluster_bits: u32) -> Result<()> {
if offset & ((0x01 << cluster_bits) - 1) != 0 {
@@ -1769,9 +1590,10 @@ fn div_round_up_u32(dividend: u32, divisor: u32) -> u32 {
#[cfg(test)]
mod tests {
use super::*;
- use base::WriteZeroes;
+ use crate::MAX_NESTING_DEPTH;
+ use std::fs::OpenOptions;
use std::io::{Read, Seek, SeekFrom, Write};
- use tempfile::tempfile;
+ use tempfile::{tempfile, TempDir};
fn valid_header() -> Vec<u8> {
vec![
@@ -1822,7 +1644,7 @@ mod tests {
fn basic_file(header: &[u8]) -> File {
let mut disk_file = tempfile().expect("failed to create tempfile");
- disk_file.write_all(&header).unwrap();
+ disk_file.write_all(header).unwrap();
disk_file.set_len(0x8000_0000).unwrap();
disk_file.seek(SeekFrom::Start(0)).unwrap();
disk_file
@@ -1845,6 +1667,22 @@ mod tests {
testfn(qcow_file); // File closed when the function exits.
}
+ // Test helper function to convert a normal slice to a VolatileSlice and write it.
+ fn write_all_at(qcow: &mut QcowFile, data: &[u8], offset: u64) -> std::io::Result<()> {
+ let mut mem = data.to_owned();
+ let vslice = VolatileSlice::new(&mut mem);
+ qcow.write_all_at_volatile(vslice, offset)
+ }
+
+ // Test helper function to read to a VolatileSlice and copy it to a normal slice.
+ fn read_exact_at(qcow: &mut QcowFile, data: &mut [u8], offset: u64) -> std::io::Result<()> {
+ let mut mem = data.to_owned();
+ let vslice = VolatileSlice::new(&mut mem);
+ qcow.read_exact_at_volatile(vslice, offset)?;
+ vslice.copy_to(data);
+ Ok(())
+ }
+
#[test]
fn default_header() {
let header = QcowHeader::create_for_size_and_path(0x10_0000, None);
@@ -1854,7 +1692,8 @@ mod tests {
.write_to(&mut disk_file)
.expect("Failed to write header to shm.");
disk_file.seek(SeekFrom::Start(0)).unwrap();
- QcowFile::from(disk_file).expect("Failed to create Qcow from default Header");
+ QcowFile::from(disk_file, MAX_NESTING_DEPTH)
+ .expect("Failed to create Qcow from default Header");
}
#[test]
@@ -1894,7 +1733,8 @@ mod tests {
let mut header = valid_header();
header[99] = 2;
with_basic_file(&header, |disk_file: File| {
- QcowFile::from(disk_file).expect_err("Invalid refcount order worked.");
+ QcowFile::from(disk_file, MAX_NESTING_DEPTH)
+ .expect_err("Invalid refcount order worked.");
});
}
@@ -1903,7 +1743,7 @@ mod tests {
let mut header = valid_header();
header[23] = 3;
with_basic_file(&header, |disk_file: File| {
- QcowFile::from(disk_file).expect_err("Failed to create file.");
+ QcowFile::from(disk_file, MAX_NESTING_DEPTH).expect_err("Failed to create file.");
});
}
@@ -1911,16 +1751,16 @@ mod tests {
fn test_header_huge_file() {
let header = test_huge_header();
with_basic_file(&header, |disk_file: File| {
- QcowFile::from(disk_file).expect_err("Failed to create file.");
+ QcowFile::from(disk_file, MAX_NESTING_DEPTH).expect_err("Failed to create file.");
});
}
#[test]
- fn test_header_crazy_file_size_rejected() {
+ fn test_header_excessive_file_size_rejected() {
let mut header = valid_header();
- &mut header[24..32].copy_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1e]);
+ header[24..32].copy_from_slice(&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1e]);
with_basic_file(&header, |disk_file: File| {
- QcowFile::from(disk_file).expect_err("Failed to create file.");
+ QcowFile::from(disk_file, MAX_NESTING_DEPTH).expect_err("Failed to create file.");
});
}
@@ -1929,7 +1769,7 @@ mod tests {
let mut header = valid_header();
header[36] = 0x12;
with_basic_file(&header, |disk_file: File| {
- QcowFile::from(disk_file).expect_err("Failed to create file.");
+ QcowFile::from(disk_file, MAX_NESTING_DEPTH).expect_err("Failed to create file.");
});
}
@@ -1941,7 +1781,7 @@ mod tests {
header[31] = 0;
// 1 TB with the min cluster size makes the arrays too big, it should fail.
with_basic_file(&header, |disk_file: File| {
- QcowFile::from(disk_file).expect_err("Failed to create file.");
+ QcowFile::from(disk_file, MAX_NESTING_DEPTH).expect_err("Failed to create file.");
});
}
@@ -1955,11 +1795,10 @@ mod tests {
// set cluster_bits
header[23] = 16;
with_basic_file(&header, |disk_file: File| {
- let mut qcow = QcowFile::from(disk_file).expect("Failed to create file.");
- qcow.seek(SeekFrom::Start(0x100_0000_0000 - 8))
- .expect("Failed to seek.");
+ let mut qcow =
+ QcowFile::from(disk_file, MAX_NESTING_DEPTH).expect("Failed to create file.");
let value = 0x0000_0040_3f00_ffffu64;
- qcow.write_all(&value.to_le_bytes())
+ write_all_at(&mut qcow, &value.to_le_bytes(), 0x100_0000_0000 - 8)
.expect("failed to write data");
});
}
@@ -1967,30 +1806,30 @@ mod tests {
#[test]
fn test_header_huge_num_refcounts() {
let mut header = valid_header();
- &mut header[56..60].copy_from_slice(&[0x02, 0x00, 0xe8, 0xff]);
+ header[56..60].copy_from_slice(&[0x02, 0x00, 0xe8, 0xff]);
with_basic_file(&header, |disk_file: File| {
- QcowFile::from(disk_file).expect_err("Created disk with crazy refcount clusters");
+ QcowFile::from(disk_file, MAX_NESTING_DEPTH)
+ .expect_err("Created disk with excessive refcount clusters");
});
}
#[test]
fn test_header_huge_refcount_offset() {
let mut header = valid_header();
- &mut header[48..56].copy_from_slice(&[0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00]);
+ header[48..56].copy_from_slice(&[0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x02, 0x00]);
with_basic_file(&header, |disk_file: File| {
- QcowFile::from(disk_file).expect_err("Created disk with crazy refcount offset");
+ QcowFile::from(disk_file, MAX_NESTING_DEPTH)
+ .expect_err("Created disk with excessive refcount offset");
});
}
#[test]
fn write_read_start() {
with_basic_file(&valid_header(), |disk_file: File| {
- let mut q = QcowFile::from(disk_file).unwrap();
- q.write(b"test first bytes")
- .expect("Failed to write test string.");
+ let mut q = QcowFile::from(disk_file, MAX_NESTING_DEPTH).unwrap();
+ write_all_at(&mut q, b"test first bytes", 0).expect("Failed to write test string.");
let mut buf = [0u8; 4];
- q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
- q.read(&mut buf).expect("Failed to read.");
+ read_exact_at(&mut q, &mut buf, 0).expect("Failed to read.");
assert_eq!(&buf, b"test");
});
}
@@ -1998,49 +1837,38 @@ mod tests {
#[test]
fn write_read_start_backing() {
let disk_file = basic_file(&valid_header());
- let mut backing = QcowFile::from(disk_file).unwrap();
- backing
- .write(b"test first bytes")
- .expect("Failed to write test string.");
+ let mut backing = QcowFile::from(disk_file, MAX_NESTING_DEPTH).unwrap();
+ write_all_at(&mut backing, b"test first bytes", 0).expect("Failed to write test string.");
let mut buf = [0u8; 4];
let wrapping_disk_file = basic_file(&valid_header());
- let mut wrapping = QcowFile::from(wrapping_disk_file).unwrap();
+ let mut wrapping = QcowFile::from(wrapping_disk_file, MAX_NESTING_DEPTH).unwrap();
wrapping.set_backing_file(Some(Box::new(backing)));
- wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek.");
- wrapping.read(&mut buf).expect("Failed to read.");
+ read_exact_at(&mut wrapping, &mut buf, 0).expect("Failed to read.");
assert_eq!(&buf, b"test");
}
#[test]
fn write_read_start_backing_overlap() {
let disk_file = basic_file(&valid_header());
- let mut backing = QcowFile::from(disk_file).unwrap();
- backing
- .write(b"test first bytes")
- .expect("Failed to write test string.");
+ let mut backing = QcowFile::from(disk_file, MAX_NESTING_DEPTH).unwrap();
+ write_all_at(&mut backing, b"test first bytes", 0).expect("Failed to write test string.");
let wrapping_disk_file = basic_file(&valid_header());
- let mut wrapping = QcowFile::from(wrapping_disk_file).unwrap();
+ let mut wrapping = QcowFile::from(wrapping_disk_file, MAX_NESTING_DEPTH).unwrap();
wrapping.set_backing_file(Some(Box::new(backing)));
- wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek.");
- wrapping
- .write(b"TEST")
- .expect("Failed to write second test string.");
+ write_all_at(&mut wrapping, b"TEST", 0).expect("Failed to write second test string.");
let mut buf = [0u8; 10];
- wrapping.seek(SeekFrom::Start(0)).expect("Failed to seek.");
- wrapping.read(&mut buf).expect("Failed to read.");
+ read_exact_at(&mut wrapping, &mut buf, 0).expect("Failed to read.");
assert_eq!(&buf, b"TEST first");
}
#[test]
fn offset_write_read() {
with_basic_file(&valid_header(), |disk_file: File| {
- let mut q = QcowFile::from(disk_file).unwrap();
+ let mut q = QcowFile::from(disk_file, MAX_NESTING_DEPTH).unwrap();
let b = [0x55u8; 0x1000];
- q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
- q.write(&b).expect("Failed to write test string.");
+ write_all_at(&mut q, &b, 0xfff2000).expect("Failed to write test string.");
let mut buf = [0u8; 4];
- q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
- q.read(&mut buf).expect("Failed to read.");
+ read_exact_at(&mut q, &mut buf, 0xfff2000).expect("Failed to read.");
assert_eq!(buf[0], 0x55);
});
}
@@ -2048,18 +1876,16 @@ mod tests {
#[test]
fn write_zeroes_read() {
with_basic_file(&valid_header(), |disk_file: File| {
- let mut q = QcowFile::from(disk_file).unwrap();
+ let mut q = QcowFile::from(disk_file, MAX_NESTING_DEPTH).unwrap();
// Write some test data.
let b = [0x55u8; 0x1000];
- q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
- q.write(&b).expect("Failed to write test string.");
+ write_all_at(&mut q, &b, 0xfff2000).expect("Failed to write test string.");
// Overwrite the test data with zeroes.
- q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
- q.write_zeroes_all(0x200).expect("Failed to write zeroes.");
+ q.write_zeroes_all_at(0xfff2000, 0x200)
+ .expect("Failed to write zeroes.");
// Verify that the correct part of the data was zeroed out.
let mut buf = [0u8; 0x1000];
- q.seek(SeekFrom::Start(0xfff2000)).expect("Failed to seek.");
- q.read(&mut buf).expect("Failed to read.");
+ read_exact_at(&mut q, &mut buf, 0xfff2000).expect("Failed to read.");
assert_eq!(buf[0], 0);
assert_eq!(buf[0x1FF], 0);
assert_eq!(buf[0x200], 0x55);
@@ -2073,19 +1899,16 @@ mod tests {
// valid_header uses cluster_bits = 12, which corresponds to a cluster size of 4096.
const CHUNK_SIZE: usize = 4096 * 2 + 512;
with_basic_file(&valid_header(), |disk_file: File| {
- let mut q = QcowFile::from(disk_file).unwrap();
+ let mut q = QcowFile::from(disk_file, MAX_NESTING_DEPTH).unwrap();
// Write some test data.
let b = [0x55u8; CHUNK_SIZE];
- q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
- q.write(&b).expect("Failed to write test string.");
+ write_all_at(&mut q, &b, 0).expect("Failed to write test string.");
// Overwrite the full cluster with zeroes.
- q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
- q.write_zeroes_all(CHUNK_SIZE)
+ q.write_zeroes_all_at(0, CHUNK_SIZE)
.expect("Failed to write zeroes.");
// Verify that the data was zeroed out.
let mut buf = [0u8; CHUNK_SIZE];
- q.seek(SeekFrom::Start(0)).expect("Failed to seek.");
- q.read(&mut buf).expect("Failed to read.");
+ read_exact_at(&mut q, &mut buf, 0).expect("Failed to read.");
assert_eq!(buf[0], 0);
assert_eq!(buf[CHUNK_SIZE - 1], 0);
});
@@ -2094,40 +1917,30 @@ mod tests {
#[test]
fn write_zeroes_backing() {
let disk_file = basic_file(&valid_header());
- let mut backing = QcowFile::from(disk_file).unwrap();
+ let mut backing = QcowFile::from(disk_file, MAX_NESTING_DEPTH).unwrap();
// Write some test data.
let b = [0x55u8; 0x1000];
- backing
- .seek(SeekFrom::Start(0xfff2000))
- .expect("Failed to seek.");
- backing.write(&b).expect("Failed to write test string.");
+ write_all_at(&mut backing, &b, 0xfff2000).expect("Failed to write test string.");
let wrapping_disk_file = basic_file(&valid_header());
- let mut wrapping = QcowFile::from(wrapping_disk_file).unwrap();
+ let mut wrapping = QcowFile::from(wrapping_disk_file, MAX_NESTING_DEPTH).unwrap();
wrapping.set_backing_file(Some(Box::new(backing)));
// Overwrite the test data with zeroes.
// This should allocate new clusters in the wrapping file so that they can be zeroed.
wrapping
- .seek(SeekFrom::Start(0xfff2000))
- .expect("Failed to seek.");
- wrapping
- .write_zeroes_all(0x200)
+ .write_zeroes_all_at(0xfff2000, 0x200)
.expect("Failed to write zeroes.");
// Verify that the correct part of the data was zeroed out.
let mut buf = [0u8; 0x1000];
- wrapping
- .seek(SeekFrom::Start(0xfff2000))
- .expect("Failed to seek.");
- wrapping.read(&mut buf).expect("Failed to read.");
+ read_exact_at(&mut wrapping, &mut buf, 0xfff2000).expect("Failed to read.");
assert_eq!(buf[0], 0);
assert_eq!(buf[0x1FF], 0);
assert_eq!(buf[0x200], 0x55);
assert_eq!(buf[0xFFF], 0x55);
}
-
#[test]
fn test_header() {
with_basic_file(&valid_header(), |disk_file: File| {
- let q = QcowFile::from(disk_file).unwrap();
+ let q = QcowFile::from(disk_file, MAX_NESTING_DEPTH).unwrap();
assert_eq!(q.virtual_size(), 0x20_0000_0000);
});
}
@@ -2135,10 +1948,9 @@ mod tests {
#[test]
fn read_small_buffer() {
with_basic_file(&valid_header(), |disk_file: File| {
- let mut q = QcowFile::from(disk_file).unwrap();
+ let mut q = QcowFile::from(disk_file, MAX_NESTING_DEPTH).unwrap();
let mut b = [5u8; 16];
- q.seek(SeekFrom::Start(1000)).expect("Failed to seek.");
- q.read(&mut b).expect("Failed to read.");
+ read_exact_at(&mut q, &mut b, 1000).expect("Failed to read.");
assert_eq!(0, b[0]);
assert_eq!(0, b[15]);
});
@@ -2147,14 +1959,14 @@ mod tests {
#[test]
fn replay_ext4() {
with_basic_file(&valid_header(), |disk_file: File| {
- let mut q = QcowFile::from(disk_file).unwrap();
+ let mut q = QcowFile::from(disk_file, MAX_NESTING_DEPTH).unwrap();
const BUF_SIZE: usize = 0x1000;
let mut b = [0u8; BUF_SIZE];
struct Transfer {
pub write: bool,
pub addr: u64,
- };
+ }
// Write transactions from mkfs.ext4.
let xfers: Vec<Transfer> = vec![
@@ -2509,12 +2321,10 @@ mod tests {
];
for xfer in &xfers {
- q.seek(SeekFrom::Start(xfer.addr)).expect("Failed to seek.");
if xfer.write {
- q.write(&b).expect("Failed to write.");
+ write_all_at(&mut q, &b, xfer.addr).expect("Failed to write.");
} else {
- let read_count: usize = q.read(&mut b).expect("Failed to read.");
- assert_eq!(read_count, BUF_SIZE);
+ read_exact_at(&mut q, &mut b, xfer.addr).expect("Failed to read.");
}
}
});
@@ -2530,36 +2340,23 @@ mod tests {
let mut readback = [0u8; BLOCK_SIZE];
for i in 0..NUM_BLOCKS {
let seek_offset = OFFSET + (i as u64) * (BLOCK_SIZE as u64);
- qcow_file
- .seek(SeekFrom::Start(seek_offset))
- .expect("Failed to seek.");
- let nwritten = qcow_file.write(&data).expect("Failed to write test data.");
- assert_eq!(nwritten, BLOCK_SIZE);
+ write_all_at(&mut qcow_file, &data, seek_offset)
+ .expect("Failed to write test data.");
// Read back the data to check it was written correctly.
- qcow_file
- .seek(SeekFrom::Start(seek_offset))
- .expect("Failed to seek.");
- let nread = qcow_file.read(&mut readback).expect("Failed to read.");
- assert_eq!(nread, BLOCK_SIZE);
+ read_exact_at(&mut qcow_file, &mut readback, seek_offset).expect("Failed to read.");
for (orig, read) in data.iter().zip(readback.iter()) {
assert_eq!(orig, read);
}
}
// Check that address 0 is still zeros.
- qcow_file.seek(SeekFrom::Start(0)).expect("Failed to seek.");
- let nread = qcow_file.read(&mut readback).expect("Failed to read.");
- assert_eq!(nread, BLOCK_SIZE);
+ read_exact_at(&mut qcow_file, &mut readback, 0).expect("Failed to read.");
for read in readback.iter() {
assert_eq!(*read, 0);
}
// Check the data again after the writes have happened.
for i in 0..NUM_BLOCKS {
let seek_offset = OFFSET + (i as u64) * (BLOCK_SIZE as u64);
- qcow_file
- .seek(SeekFrom::Start(seek_offset))
- .expect("Failed to seek.");
- let nread = qcow_file.read(&mut readback).expect("Failed to read.");
- assert_eq!(nread, BLOCK_SIZE);
+ read_exact_at(&mut qcow_file, &mut readback, seek_offset).expect("Failed to read.");
for (orig, read) in data.iter().zip(readback.iter()) {
assert_eq!(orig, read);
}
@@ -2569,126 +2366,145 @@ mod tests {
});
}
- fn seek_cur(file: &mut QcowFile) -> u64 {
- file.seek(SeekFrom::Current(0)).unwrap()
+ #[test]
+ fn rebuild_refcounts() {
+ with_basic_file(&valid_header(), |mut disk_file: File| {
+ let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
+ let cluster_size = 65536;
+ let mut raw_file =
+ QcowRawFile::from(disk_file, cluster_size).expect("Failed to create QcowRawFile.");
+ QcowFile::rebuild_refcounts(&mut raw_file, header)
+ .expect("Failed to rebuild recounts.");
+ });
}
#[test]
- fn seek_data() {
- with_default_file(0x30000, |mut file| {
- // seek_data at or after the end of the file should return None
- assert_eq!(file.seek_data(0x10000).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
- assert_eq!(file.seek_data(0x10001).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
-
- // Write some data to [0x10000, 0x20000)
- let b = [0x55u8; 0x10000];
- file.seek(SeekFrom::Start(0x10000)).unwrap();
- file.write_all(&b).unwrap();
- assert_eq!(file.seek_data(0).unwrap(), Some(0x10000));
- assert_eq!(seek_cur(&mut file), 0x10000);
-
- // seek_data within data should return the same offset
- assert_eq!(file.seek_data(0x10000).unwrap(), Some(0x10000));
- assert_eq!(seek_cur(&mut file), 0x10000);
- assert_eq!(file.seek_data(0x10001).unwrap(), Some(0x10001));
- assert_eq!(seek_cur(&mut file), 0x10001);
- assert_eq!(file.seek_data(0x1FFFF).unwrap(), Some(0x1FFFF));
- assert_eq!(seek_cur(&mut file), 0x1FFFF);
-
- assert_eq!(file.seek_data(0).unwrap(), Some(0x10000));
- assert_eq!(seek_cur(&mut file), 0x10000);
- assert_eq!(file.seek_data(0x1FFFF).unwrap(), Some(0x1FFFF));
- assert_eq!(seek_cur(&mut file), 0x1FFFF);
- assert_eq!(file.seek_data(0x20000).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0x1FFFF);
- });
+ fn nested_qcow() {
+ let tmp_dir = TempDir::new().unwrap();
+
+ // A file `backing` is backing a qcow file `qcow.l1`, which in turn is backing another
+ // qcow file.
+ let backing_file_path = tmp_dir.path().join("backing");
+ let _backing_file = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create(true)
+ .open(&backing_file_path)
+ .unwrap();
+
+ let level1_qcow_file_path = tmp_dir.path().join("qcow.l1");
+ let level1_qcow_file = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .create(true)
+ .open(&level1_qcow_file_path)
+ .unwrap();
+ let _level1_qcow_file = QcowFile::new_from_backing(
+ level1_qcow_file,
+ backing_file_path.to_str().unwrap(),
+ 1000, /* allow deep nesting */
+ )
+ .unwrap();
+
+ let level2_qcow_file = tempfile().unwrap();
+ let _level2_qcow_file = QcowFile::new_from_backing(
+ level2_qcow_file,
+ level1_qcow_file_path.to_str().unwrap(),
+ 1000, /* allow deep nesting */
+ )
+ .expect("failed to create level2 qcow file");
}
#[test]
- fn seek_hole() {
- with_default_file(0x30000, |mut file| {
- // File consisting entirely of a hole
- assert_eq!(file.seek_hole(0).unwrap(), Some(0));
- assert_eq!(seek_cur(&mut file), 0);
- assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
- assert_eq!(seek_cur(&mut file), 0xFFFF);
-
- // seek_hole at or after the end of the file should return None
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x30000).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
- assert_eq!(file.seek_hole(0x30001).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
-
- // Write some data to [0x10000, 0x20000)
- let b = [0x55u8; 0x10000];
- file.seek(SeekFrom::Start(0x10000)).unwrap();
- file.write_all(&b).unwrap();
-
- // seek_hole within a hole should return the same offset
- assert_eq!(file.seek_hole(0).unwrap(), Some(0));
- assert_eq!(seek_cur(&mut file), 0);
- assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
- assert_eq!(seek_cur(&mut file), 0xFFFF);
-
- // seek_hole within data should return the next hole
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
- assert_eq!(seek_cur(&mut file), 0x20000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x10001).unwrap(), Some(0x20000));
- assert_eq!(seek_cur(&mut file), 0x20000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
- assert_eq!(seek_cur(&mut file), 0x20000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
- assert_eq!(seek_cur(&mut file), 0xFFFF);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
- assert_eq!(seek_cur(&mut file), 0x20000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
- assert_eq!(seek_cur(&mut file), 0x20000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x20000));
- assert_eq!(seek_cur(&mut file), 0x20000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x20001));
- assert_eq!(seek_cur(&mut file), 0x20001);
-
- // seek_hole at EOF should return None
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x30000).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
-
- // Write some data to [0x20000, 0x30000)
- file.seek(SeekFrom::Start(0x20000)).unwrap();
- file.write_all(&b).unwrap();
-
- // seek_hole within [0x20000, 0x30000) should now find the hole at EOF
- assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x30000));
- assert_eq!(seek_cur(&mut file), 0x30000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x30000));
- assert_eq!(seek_cur(&mut file), 0x30000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x30000).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
+ fn io_seek() {
+ with_default_file(1024 * 1024 * 10, |mut qcow_file| {
+ // Cursor should start at 0.
+ assert_eq!(qcow_file.seek(SeekFrom::Current(0)).unwrap(), 0);
+
+ // Seek 1 MB from start.
+ assert_eq!(
+ qcow_file.seek(SeekFrom::Start(1024 * 1024)).unwrap(),
+ 1024 * 1024
+ );
+
+ // Rewind 1 MB + 1 byte (past beginning) - seeking to a negative offset is an error and
+ // should not move the cursor.
+ qcow_file
+ .seek(SeekFrom::Current(-(1024 * 1024 + 1)))
+ .expect_err("negative offset seek should fail");
+ assert_eq!(qcow_file.seek(SeekFrom::Current(0)).unwrap(), 1024 * 1024);
+
+ // Seek to last byte.
+ assert_eq!(
+ qcow_file.seek(SeekFrom::End(-1)).unwrap(),
+ 1024 * 1024 * 10 - 1
+ );
+
+ // Seek to EOF.
+ assert_eq!(qcow_file.seek(SeekFrom::End(0)).unwrap(), 1024 * 1024 * 10);
+
+ // Seek past EOF is not allowed.
+ qcow_file
+ .seek(SeekFrom::End(1))
+ .expect_err("seek past EOF should fail");
});
}
#[test]
- fn rebuild_refcounts() {
- with_basic_file(&valid_header(), |mut disk_file: File| {
- let header = QcowHeader::new(&mut disk_file).expect("Failed to create Header.");
- let cluster_size = 65536;
- let mut raw_file =
- QcowRawFile::from(disk_file, cluster_size).expect("Failed to create QcowRawFile.");
- QcowFile::rebuild_refcounts(&mut raw_file, header)
- .expect("Failed to rebuild recounts.");
+ fn io_write_read() {
+ with_default_file(1024 * 1024 * 10, |mut qcow_file| {
+ const BLOCK_SIZE: usize = 0x1_0000;
+ let data_55 = [0x55u8; BLOCK_SIZE];
+ let data_aa = [0xaau8; BLOCK_SIZE];
+ let mut readback = [0u8; BLOCK_SIZE];
+
+ qcow_file.write_all(&data_55).unwrap();
+ assert_eq!(
+ qcow_file.seek(SeekFrom::Current(0)).unwrap(),
+ BLOCK_SIZE as u64
+ );
+
+ qcow_file.write_all(&data_aa).unwrap();
+ assert_eq!(
+ qcow_file.seek(SeekFrom::Current(0)).unwrap(),
+ BLOCK_SIZE as u64 * 2
+ );
+
+ // Read BLOCK_SIZE of just 0xaa.
+ assert_eq!(
+ qcow_file
+ .seek(SeekFrom::Current(-(BLOCK_SIZE as i64)))
+ .unwrap(),
+ BLOCK_SIZE as u64
+ );
+ qcow_file.read_exact(&mut readback).unwrap();
+ assert_eq!(
+ qcow_file.seek(SeekFrom::Current(0)).unwrap(),
+ BLOCK_SIZE as u64 * 2
+ );
+ for (orig, read) in data_aa.iter().zip(readback.iter()) {
+ assert_eq!(orig, read);
+ }
+
+ // Read BLOCK_SIZE of just 0x55.
+ qcow_file.rewind().unwrap();
+ qcow_file.read_exact(&mut readback).unwrap();
+ for (orig, read) in data_55.iter().zip(readback.iter()) {
+ assert_eq!(orig, read);
+ }
+
+ // Read BLOCK_SIZE crossing between the block of 0x55 and 0xaa.
+ qcow_file
+ .seek(SeekFrom::Start(BLOCK_SIZE as u64 / 2))
+ .unwrap();
+ qcow_file.read_exact(&mut readback).unwrap();
+ for (orig, read) in data_55[BLOCK_SIZE / 2..]
+ .iter()
+ .chain(data_aa[..BLOCK_SIZE / 2].iter())
+ .zip(readback.iter())
+ {
+ assert_eq!(orig, read);
+ }
});
}
}
diff --git a/disk/src/qcow/refcount.rs b/disk/src/qcow/refcount.rs
index ab399c3ba..90cd95f83 100644
--- a/disk/src/qcow/refcount.rs
+++ b/disk/src/qcow/refcount.rs
@@ -2,50 +2,37 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fmt::{self, Display};
use std::io;
use libc::EINVAL;
+use remain::sorted;
+use thiserror::Error;
use crate::qcow::qcow_raw_file::QcowRawFile;
use crate::qcow::vec_cache::{CacheMap, Cacheable, VecCache};
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
/// `EvictingCache` - Error writing a refblock from the cache to disk.
+ #[error("failed to write a refblock from the cache to disk: {0}")]
EvictingRefCounts(io::Error),
/// `InvalidIndex` - Address requested isn't within the range of the disk.
+ #[error("address requested is not within the range of the disk")]
InvalidIndex,
/// `NeedCluster` - Handle this error by reading the cluster and calling the function again.
+ #[error("cluster with addr={0} needs to be read")]
NeedCluster(u64),
/// `NeedNewCluster` - Handle this error by allocating a cluster and calling the function again.
+ #[error("new cluster needs to be allocated for refcounts")]
NeedNewCluster,
/// `ReadingRefCounts` - Error reading the file in to the refcount cache.
+ #[error("failed to read the file into the refcount cache: {0}")]
ReadingRefCounts(io::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- EvictingRefCounts(e) => write!(
- f,
- "failed to write a refblock from the cache to disk: {}",
- e
- ),
- InvalidIndex => write!(f, "address requested is not within the range of the disk"),
- NeedCluster(addr) => write!(f, "cluster with addr={} needs to be read", addr),
- NeedNewCluster => write!(f, "new cluster needs to be allocated for refcounts"),
- ReadingRefCounts(e) => {
- write!(f, "failed to read the file into the refcount cache: {}", e)
- }
- }
- }
-}
-
/// Represents the refcount entries for an open qcow file.
#[derive(Debug)]
pub struct RefCount {
@@ -171,7 +158,7 @@ impl RefCount {
if self.ref_table.dirty() {
raw_file.write_pointer_table(
self.refcount_table_offset,
- &self.ref_table.get_values(),
+ self.ref_table.get_values(),
0,
)?;
self.ref_table.mark_clean();
@@ -208,41 +195,6 @@ impl RefCount {
Ok(self.refblock_cache.get(&table_index).unwrap()[block_index])
}
- /// Returns the refcount table for this file. This is only useful for debugging.
- pub fn ref_table(&self) -> &[u64] {
- &self.ref_table.get_values()
- }
-
- /// Returns the refcounts stored in the given block.
- pub fn refcount_block(
- &mut self,
- raw_file: &mut QcowRawFile,
- table_index: usize,
- ) -> Result<Option<&[u16]>> {
- let block_addr_disk = *self.ref_table.get(table_index).ok_or(Error::InvalidIndex)?;
- if block_addr_disk == 0 {
- return Ok(None);
- }
- if !self.refblock_cache.contains_key(&table_index) {
- let table = VecCache::from_vec(
- raw_file
- .read_refcount_block(block_addr_disk)
- .map_err(Error::ReadingRefCounts)?,
- );
- // TODO(dgreid) - closure needs to return an error.
- let ref_table = &self.ref_table;
- self.refblock_cache
- .insert(table_index, table, |index, evicted| {
- raw_file.write_refcount_block(ref_table[index], evicted.get_values())
- })
- .map_err(Error::EvictingRefCounts)?;
- }
- // The index must exist as it was just inserted if it didn't already.
- Ok(Some(
- self.refblock_cache.get(&table_index).unwrap().get_values(),
- ))
- }
-
// Gets the address of the refcount block and the index into the block for the given address.
fn get_refcount_index(&self, address: u64) -> (usize, usize) {
let block_index = (address / self.cluster_size) % self.refcount_block_entries;
diff --git a/disk/src/qcow/vec_cache.rs b/disk/src/qcow/vec_cache.rs
index 18cfc6978..b5f6db2d3 100644
--- a/disk/src/qcow/vec_cache.rs
+++ b/disk/src/qcow/vec_cache.rs
@@ -178,7 +178,7 @@ mod tests {
// Check that three of the four items inserted are still there and that the most recently
// inserted is one of them.
- let num_items = (0..=3).filter(|k| cache.contains_key(&k)).count();
+ let num_items = (0..=3).filter(|k| cache.contains_key(k)).count();
assert_eq!(num_items, 3);
assert!(cache.contains_key(&3));
}
diff --git a/docs/architecture.md b/docs/architecture.md
deleted file mode 100644
index 064826c68..000000000
--- a/docs/architecture.md
+++ /dev/null
@@ -1,82 +0,0 @@
-# Architectural Overview (last edit: January 21, 2020)
-
-The principle characteristics of crosvm are:
-
-- A process per virtual device, made using fork
-- Each process is sandboxed using [minijail]
-- Takes full advantage of KVM and low-level Linux syscalls, and so only runs on Linux
-- Written in Rust for security and safety
-
-A typical session of crosvm starts in `main.rs` where command line parsing is done to build up a `Config` structure. The `Config` is used by `run_config` in `linux.rs` to setup and execute a VM. Broken down into rough steps:
-
-1. Load the linux kernel from an ELF file.
-1. Create a handful of control sockets used by the virtual devices.
-1. Invoke the architecture specific VM builder `Arch::build_vm` (located in `x86_64/src/lib.rs` or `aarch64/src/lib.rs`).
-1. `Arch::build_vm` will itself invoke the provided `create_devices` function from `linux.rs`
-1. `create_devices` creates every PCI device, including the virtio devices, that were configured in `Config`, along with matching [minijail] configs for each.
-1. `Arch::generate_pci_root`, using a list of every PCI device with optional `Minijail`, will finally jail the PCI devices and construct a `PciRoot` that communicates with them.
-1. Once the VM has been built, it's contained within a `RunnableLinuxVm` object that is used by the VCPUs and control loop to service requests until shutdown.
-
-## Forking
-
-During the device creation routine, each device will be created and then wrapped in a `ProxyDevice` which will internally `fork` (but not `exec`) and [minijail] the device, while dropping it for the main process. The only interaction that the device is capable of having with the main process is via the proxied trait methods of `BusDevice`, shared memory mappings such as the guest memory, and file descriptors that were specifically whitelisted by that device's security policy. This can lead to some surprising behavior to be aware of such as why some file descriptors which were once valid are now invalid.
-
-## Sandboxing Policy
-
-Every sandbox is made with [minijail] and starts with `create_base_minijail` in `linux.rs` which set some very restrictive settings. Linux namespaces and seccomp filters are used extensively. Each seccomp policy can be found under `seccomp/{arch}/{device}.policy` and should start by `@include`-ing the `common_device.policy`. With the exception of architecture specific devices (such as `Pl030` on ARM or `I8042` on x86_64), every device will need a different policy for each supported architecture.
-
-## The VM Control Sockets
-
-For the operations that devices need to perform on the global VM state, such as mapping into guest memory address space, there are the vm control sockets. There are a few kinds, split by the type of request and response that the socket will process. This also proves basic security privilege separation in case a device becomes compromised by a malicious guest. For example, a rogue device that is able to allocate MSI routes would not be able to use the same socket to (de)register guest memory. During the device initialization stage, each device that requires some aspect of VM control will have a constructor that requires the corresponding control socket. The control socket will get preserved when the device is sandboxed and and the other side of the socket will be waited on in the main process's control loop.
-
-The socket exposed by crosvm with the `--socket` command line argument is another form of the VM control socket. Because the protocol of the control socket is internal and unstable, the only supported way of using that resulting named unix domain socket is via crosvm command line subcommands such as `crosvm stop`.
-
-## GuestMemory
-
-`GuestMemory` and its friends `VolatileMemory`, `VolatileSlice`, `MemoryMapping`, and `SharedMemory`, are common types used throughout crosvm to interact with guest memory. Know which one to use in what place using some guidelines
-
-- `GuestMemory` is for sending around references to all of the guest memory. It can be cloned freely, but the underlying guest memory is always the same. Internally, it's implemented using `MemoryMapping` and `SharedMemory`. Note that `GuestMemory` is mapped into the host address space, but it is non-contiguous. Device memory, such as mapped DMA-Bufs, are not present in `GuestMemory`.
-- `SharedMemory` wraps a `memfd` and can be mapped using `MemoryMapping` to access its data. `SharedMemory` can't be cloned.
-- `VolatileMemory` is a trait that exposes generic access to non-contiguous memory. `GuestMemory` implements this trait. Use this trait for functions that operate on a memory space but don't necessarily need it to be guest memory.
-- `VolatileSlice` is analogous to a Rust slice, but unlike those, a `VolatileSlice` has data that changes asynchronously by all those that reference it. Exclusive mutability and data synchronization are not available when it comes to a `VolatileSlice`. This type is useful for functions that operate on contiguous shared memory, such as a single entry from a scatter gather table, or for safe wrappers around functions which operate on pointers, such as a `read` or `write` syscall.
-- `MemoryMapping` is a safe wrapper around anonymous and file mappings. Access via Rust references is forbidden, but indirect reading and writing is available via `VolatileSlice` and several convenience functions. This type is most useful for mapping memory unrelated to `GuestMemory`.
-
-### Device Model
-
-### `Bus`/`BusDevice`
-
-The root of the crosvm device model is the `Bus` structure and its friend the `BusDevice` trait. The `Bus` structure is a virtual computer bus used to emulate the memory-mapped I/O bus and also I/O ports for x86 VMs. On a read or write to an address on a VM's bus, the corresponding `Bus` object is queried for a `BusDevice` that occupies that address. `Bus` will then forward the read/write to the `BusDevice`. Because of this behavior, only one `BusDevice` may exist at any given address. However, a `BusDevice` may be placed at more than one address range. Depending on how a `BusDevice` was inserted into the `Bus`, the forwarded read/write will be relative to 0 or to the start of the address range that the `BusDevice` occupies (which would be ambiguous if the `BusDevice` occupied more than one range).
-
-Only the base address of a multi-byte read/write is used to search for a device, so a device implementation should be aware that the last address of a single read/write may be outside its address range. For example, if a `BusDevice` was inserted at base address 0x1000 with a length of 0x40, a 4-byte read by a VCPU at 0x39 would be forwarded to that `BusDevice`.
-
-Each `BusDevice` is reference counted and wrapped in a mutex, so implementations of `BusDevice` need not worry about synchronizing their access across multiple VCPUs and threads. Each VCPU will get a complete copy of the `Bus`, so there is no contention for querying the `Bus` about an address. Once the `BusDevice` is found, the `Bus` will acquire an exclusive lock to the device and forward the VCPU's read/write. The implementation of the `BusDevice` will block execution of the VCPU that invoked it, as well as any other VCPU attempting access, until it returns from its method.
-
-Most devices in crosvm do not implement `BusDevice` directly, but some are examples are `i8042` and `Serial`. With the exception of PCI devices, all devices are inserted by architecture specific code (which may call into the architecture-neutral `arch` crate). A `BusDevice` can be proxied to a sandboxed process using `ProxyDevice`, which will create the second process using a fork, with no exec.
-
-### `PciConfigIo`/`PciConfigMmio`
-
-In order to use the more complex PCI bus, there are a couple adapters that implement `BusDevice` and call into a `PciRoot` with higher level calls to `config_space_read`/`config_space_write`. The `PciConfigMmio` is a `BusDevice` for insertion into the MMIO `Bus` for ARM devices. For x86_64, `PciConfigIo` is inserted into the I/O port `Bus`. There is only one implementation of `PciRoot` that is used by either of the `PciConfig*` structures. Because these devices are very simple, they have very little code or state. They aren't sandboxed and are run as part of the main process.
-
-### `PciRoot`/`PciDevice`/`VirtioPciDevice`
-
-The `PciRoot`, analogous to `BusDevice` for `Bus`s, contains all the `PciDevice` trait objects. Because of a shortcut (or hack), the `ProxyDevice` only supports jailing `BusDevice` traits. Therefore, `PciRoot` only contains `BusDevice`s, even though they also implement `PciDevice`. In fact, every `PciDevice` also implements `BusDevice` because of a blanket implementation (`impl<T: PciDevice> BusDevice for T { … }`). There are a few PCI related methods in `BusDevice` to allow the `PciRoot` to still communicate with the underlying `PciDevice` (yes, this abstraction is very leaky). Most devices will not implement `PciDevice` directly, instead using the `VirtioPciDevice` implementation for virtio devices, but the xHCI (USB) controller is an example that implements `PciDevice` directly. The `VirtioPciDevice` is an implementation of `PciDevice` that wraps a `VirtioDevice`, which is how the virtio specified PCI transport is adapted to a transport agnostic `VirtioDevice` implementation.
-
-### `VirtioDevice`
-
-The `VirtioDevice` is the most widely implemented trait among the device traits. Each of the different virtio devices (block, rng, net, etc.) implement this trait directly and they follow a similar pattern. Most of the trait methods are easily filled in with basic information about the specific device, but `activate` will be the heart of the implementation. It's called by the virtio transport after the guest's driver has indicated the device has been configured and is ready to run. The virtio device implementation will receive the run time related resources (`GuestMemory`, `Interrupt`, etc.) for processing virtio queues and associated interrupts via the arguments to `activate`, but `activate` can't spend its time actually processing the queues. A VCPU will be blocked as long as `activate` is running. Every device uses `activate` to launch a worker thread that takes ownership of run time resources to do the actual processing. There is some subtlety in dealing with virtio queues, so the smart thing to do is copy a simpler device and adapt it, such as the rng device (`rng.rs`).
-
-## Communication Framework
-
-Because of the multi-process nature of crosvm, communication is done over several IPC primitives. The common ones are shared memory pages, unix sockets, anonymous pipes, and various other file descriptor variants (DMA-buf, eventfd, etc.). Standard methods (`read`/`write`) of using these primitives may be used, but crosvm has developed some helpers which should be used where applicable.
-
-### `PollContext`/`EpollContext`
-
-Most threads in crosvm will have a wait loop using a `PollContext`, which is a wrapper around Linux's `epoll` primitive for selecting over file descriptors. `EpollContext` is very similar but has slightly fewer features, but is usable by multiple threads at once. In either case, each FD is added to the context along with an associated token, whose type is the type parameter of `PollContext`. This token must be convertible to and from a `u64`, which is a limitation imposed by how `epoll` works. There is a custom derive `#[derive(PollToken)]` which can be applied to an `enum` declaration that makes it easy to use your own enum in a `PollContext`.
-
-Note that the limitations of `PollContext` are the same as the limitations of `epoll`. The same FD can not be inserted more than once, and the FD will be automatically removed if the process runs out of references to that FD. A `dup`/`fork` call will increment that reference count, so closing the original FD will not actually remove it from the `PollContext`. It is possible to receive tokens from `PollContext` for an FD that was closed because of a race condition in which an event was registered in the background before the `close` happened. Best practice is to remove an FD before closing it so that events associated with it can be reliably eliminated.
-
-### `serde` with Descriptors.
-
-Using raw sockets and pipes to communicate is very inconvenient for rich data types. To help make this easier and less error prone, crosvm uses the `serde` crate. To allow transmitting types with embedded descriptors (FDs on Linux or HANDLEs on Windows), a module is provided for sending and receiving descriptors alongside the plain old bytes that serde consumes.
-
-[minijail]: https://android.googlesource.com/platform/external/minijail
diff --git a/docs/book/.gitignore b/docs/book/.gitignore
new file mode 100644
index 000000000..7585238ef
--- /dev/null
+++ b/docs/book/.gitignore
@@ -0,0 +1 @@
+book
diff --git a/docs/book/book.toml b/docs/book/book.toml
new file mode 100644
index 000000000..5e598ad35
--- /dev/null
+++ b/docs/book/book.toml
@@ -0,0 +1,16 @@
+[book]
+authors = ["The Chromium OS Authors"]
+language = "en"
+multilingual = false
+src = "src"
+title = "Book of crosvm"
+
+[preprocessor]
+[preprocessor.mermaid]
+command = "mdbook-mermaid"
+
+[output]
+[output.html]
+additional-js = ["mermaid.min.js", "mermaid-init.js"]
+
+[output.linkcheck]
diff --git a/docs/book/mermaid-init.js b/docs/book/mermaid-init.js
new file mode 100644
index 000000000..313a6e8bc
--- /dev/null
+++ b/docs/book/mermaid-init.js
@@ -0,0 +1 @@
+mermaid.initialize({startOnLoad:true});
diff --git a/docs/book/mermaid.min.js b/docs/book/mermaid.min.js
new file mode 100644
index 000000000..14ef691fa
--- /dev/null
+++ b/docs/book/mermaid.min.js
@@ -0,0 +1,32 @@
+!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.mermaid=e():t.mermaid=e()}("undefined"!=typeof self?self:this,(function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=383)}([function(t,e,n){"use strict";n.r(e);var r=function(t,e){return t<e?-1:t>e?1:t>=e?0:NaN},i=function(t){var e;return 1===t.length&&(e=t,t=function(t,n){return r(e(t),n)}),{left:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r<i;){var a=r+i>>>1;t(e[a],n)<0?r=a+1:i=a}return r},right:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r<i;){var a=r+i>>>1;t(e[a],n)>0?i=a:r=a+1}return r}}};var a=i(r),o=a.right,s=a.left,c=o,u=function(t,e){null==e&&(e=l);for(var n=0,r=t.length-1,i=t[0],a=new Array(r<0?0:r);n<r;)a[n]=e(i,i=t[++n]);return a};function l(t,e){return[t,e]}var h=function(t,e,n){var r,i,a,o,s=t.length,c=e.length,u=new Array(s*c);for(null==n&&(n=l),r=a=0;r<s;++r)for(o=t[r],i=0;i<c;++i,++a)u[a]=n(o,e[i]);return u},f=function(t,e){return e<t?-1:e>t?1:e>=t?0:NaN},d=function(t){return null===t?NaN:+t},p=function(t,e){var n,r,i=t.length,a=0,o=-1,s=0,c=0;if(null==e)for(;++o<i;)isNaN(n=d(t[o]))||(c+=(r=n-s)*(n-(s+=r/++a)));else for(;++o<i;)isNaN(n=d(e(t[o],o,t)))||(c+=(r=n-s)*(n-(s+=r/++a)));if(a>1)return c/(a-1)},g=function(t,e){var n=p(t,e);return n?Math.sqrt(n):n},y=function(t,e){var n,r,i,a=t.length,o=-1;if(null==e){for(;++o<a;)if(null!=(n=t[o])&&n>=n)for(r=i=n;++o<a;)null!=(n=t[o])&&(r>n&&(r=n),i<n&&(i=n))}else for(;++o<a;)if(null!=(n=e(t[o],o,t))&&n>=n)for(r=i=n;++o<a;)null!=(n=e(t[o],o,t))&&(r>n&&(r=n),i<n&&(i=n));return[r,i]},v=Array.prototype,m=v.slice,b=v.map,x=function(t){return function(){return t}},_=function(t){return t},k=function(t,e,n){t=+t,e=+e,n=(i=arguments.length)<2?(e=t,t=0,1):i<3?1:+n;for(var r=-1,i=0|Math.max(0,Math.ceil((e-t)/n)),a=new Array(i);++r<i;)a[r]=t+r*n;return a},w=Math.sqrt(50),E=Math.sqrt(10),T=Math.sqrt(2),C=function(t,e,n){var r,i,a,o,s=-1;if(n=+n,(t=+t)===(e=+e)&&n>0)return[t];if((r=e<t)&&(i=t,t=e,e=i),0===(o=A(t,e,n))||!isFinite(o))return[];if(o>0)for(t=Math.ceil(t/o),e=Math.floor(e/o),a=new Array(i=Math.ceil(e-t+1));++s<i;)a[s]=(t+s)*o;else for(t=Math.floor(t*o),e=Math.ceil(e*o),a=new Array(i=Math.ceil(t-e+1));++s<i;)a[s]=(t-s)/o;return r&&a.reverse(),a};function A(t,e,n){var r=(e-t)/Math.max(0,n),i=Math.floor(Math.log(r)/Math.LN10),a=r/Math.pow(10,i);return i>=0?(a>=w?10:a>=E?5:a>=T?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(a>=w?10:a>=E?5:a>=T?2:1)}function S(t,e,n){var r=Math.abs(e-t)/Math.max(0,n),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),a=r/i;return a>=w?i*=10:a>=E?i*=5:a>=T&&(i*=2),e<t?-i:i}var M=function(t){return Math.ceil(Math.log(t.length)/Math.LN2)+1},O=function(){var t=_,e=y,n=M;function r(r){var i,a,o=r.length,s=new Array(o);for(i=0;i<o;++i)s[i]=t(r[i],i,r);var u=e(s),l=u[0],h=u[1],f=n(s,l,h);Array.isArray(f)||(f=S(l,h,f),f=k(Math.ceil(l/f)*f,h,f));for(var d=f.length;f[0]<=l;)f.shift(),--d;for(;f[d-1]>h;)f.pop(),--d;var p,g=new Array(d+1);for(i=0;i<=d;++i)(p=g[i]=[]).x0=i>0?f[i-1]:l,p.x1=i<d?f[i]:h;for(i=0;i<o;++i)l<=(a=s[i])&&a<=h&&g[c(f,a,0,d)].push(r[i]);return g}return r.value=function(e){return arguments.length?(t="function"==typeof e?e:x(e),r):t},r.domain=function(t){return arguments.length?(e="function"==typeof t?t:x([t[0],t[1]]),r):e},r.thresholds=function(t){return arguments.length?(n="function"==typeof t?t:Array.isArray(t)?x(m.call(t)):x(t),r):n},r},D=function(t,e,n){if(null==n&&(n=d),r=t.length){if((e=+e)<=0||r<2)return+n(t[0],0,t);if(e>=1)return+n(t[r-1],r-1,t);var r,i=(r-1)*e,a=Math.floor(i),o=+n(t[a],a,t);return o+(+n(t[a+1],a+1,t)-o)*(i-a)}},N=function(t,e,n){return t=b.call(t,d).sort(r),Math.ceil((n-e)/(2*(D(t,.75)-D(t,.25))*Math.pow(t.length,-1/3)))},B=function(t,e,n){return Math.ceil((n-e)/(3.5*g(t)*Math.pow(t.length,-1/3)))},L=function(t,e){var n,r,i=t.length,a=-1;if(null==e){for(;++a<i;)if(null!=(n=t[a])&&n>=n)for(r=n;++a<i;)null!=(n=t[a])&&n>r&&(r=n)}else for(;++a<i;)if(null!=(n=e(t[a],a,t))&&n>=n)for(r=n;++a<i;)null!=(n=e(t[a],a,t))&&n>r&&(r=n);return r},F=function(t,e){var n,r=t.length,i=r,a=-1,o=0;if(null==e)for(;++a<r;)isNaN(n=d(t[a]))?--i:o+=n;else for(;++a<r;)isNaN(n=d(e(t[a],a,t)))?--i:o+=n;if(i)return o/i},P=function(t,e){var n,i=t.length,a=-1,o=[];if(null==e)for(;++a<i;)isNaN(n=d(t[a]))||o.push(n);else for(;++a<i;)isNaN(n=d(e(t[a],a,t)))||o.push(n);return D(o.sort(r),.5)},I=function(t){for(var e,n,r,i=t.length,a=-1,o=0;++a<i;)o+=t[a].length;for(n=new Array(o);--i>=0;)for(e=(r=t[i]).length;--e>=0;)n[--o]=r[e];return n},j=function(t,e){var n,r,i=t.length,a=-1;if(null==e){for(;++a<i;)if(null!=(n=t[a])&&n>=n)for(r=n;++a<i;)null!=(n=t[a])&&r>n&&(r=n)}else for(;++a<i;)if(null!=(n=e(t[a],a,t))&&n>=n)for(r=n;++a<i;)null!=(n=e(t[a],a,t))&&r>n&&(r=n);return r},R=function(t,e){for(var n=e.length,r=new Array(n);n--;)r[n]=t[e[n]];return r},Y=function(t,e){if(n=t.length){var n,i,a=0,o=0,s=t[o];for(null==e&&(e=r);++a<n;)(e(i=t[a],s)<0||0!==e(s,s))&&(s=i,o=a);return 0===e(s,s)?o:void 0}},z=function(t,e,n){for(var r,i,a=(null==n?t.length:n)-(e=null==e?0:+e);a;)i=Math.random()*a--|0,r=t[a+e],t[a+e]=t[i+e],t[i+e]=r;return t},U=function(t,e){var n,r=t.length,i=-1,a=0;if(null==e)for(;++i<r;)(n=+t[i])&&(a+=n);else for(;++i<r;)(n=+e(t[i],i,t))&&(a+=n);return a},$=function(t){if(!(i=t.length))return[];for(var e=-1,n=j(t,W),r=new Array(n);++e<n;)for(var i,a=-1,o=r[e]=new Array(i);++a<i;)o[a]=t[a][e];return r};function W(t){return t.length}var H=function(){return $(arguments)},V=Array.prototype.slice,G=function(t){return t};function q(t){return"translate("+(t+.5)+",0)"}function X(t){return"translate(0,"+(t+.5)+")"}function Z(t){return function(e){return+t(e)}}function J(t){var e=Math.max(0,t.bandwidth()-1)/2;return t.round()&&(e=Math.round(e)),function(n){return+t(n)+e}}function K(){return!this.__axis}function Q(t,e){var n=[],r=null,i=null,a=6,o=6,s=3,c=1===t||4===t?-1:1,u=4===t||2===t?"x":"y",l=1===t||3===t?q:X;function h(h){var f=null==r?e.ticks?e.ticks.apply(e,n):e.domain():r,d=null==i?e.tickFormat?e.tickFormat.apply(e,n):G:i,p=Math.max(a,0)+s,g=e.range(),y=+g[0]+.5,v=+g[g.length-1]+.5,m=(e.bandwidth?J:Z)(e.copy()),b=h.selection?h.selection():h,x=b.selectAll(".domain").data([null]),_=b.selectAll(".tick").data(f,e).order(),k=_.exit(),w=_.enter().append("g").attr("class","tick"),E=_.select("line"),T=_.select("text");x=x.merge(x.enter().insert("path",".tick").attr("class","domain").attr("stroke","currentColor")),_=_.merge(w),E=E.merge(w.append("line").attr("stroke","currentColor").attr(u+"2",c*a)),T=T.merge(w.append("text").attr("fill","currentColor").attr(u,c*p).attr("dy",1===t?"0em":3===t?"0.71em":"0.32em")),h!==b&&(x=x.transition(h),_=_.transition(h),E=E.transition(h),T=T.transition(h),k=k.transition(h).attr("opacity",1e-6).attr("transform",(function(t){return isFinite(t=m(t))?l(t):this.getAttribute("transform")})),w.attr("opacity",1e-6).attr("transform",(function(t){var e=this.parentNode.__axis;return l(e&&isFinite(e=e(t))?e:m(t))}))),k.remove(),x.attr("d",4===t||2==t?o?"M"+c*o+","+y+"H0.5V"+v+"H"+c*o:"M0.5,"+y+"V"+v:o?"M"+y+","+c*o+"V0.5H"+v+"V"+c*o:"M"+y+",0.5H"+v),_.attr("opacity",1).attr("transform",(function(t){return l(m(t))})),E.attr(u+"2",c*a),T.attr(u,c*p).text(d),b.filter(K).attr("fill","none").attr("font-size",10).attr("font-family","sans-serif").attr("text-anchor",2===t?"start":4===t?"end":"middle"),b.each((function(){this.__axis=m}))}return h.scale=function(t){return arguments.length?(e=t,h):e},h.ticks=function(){return n=V.call(arguments),h},h.tickArguments=function(t){return arguments.length?(n=null==t?[]:V.call(t),h):n.slice()},h.tickValues=function(t){return arguments.length?(r=null==t?null:V.call(t),h):r&&r.slice()},h.tickFormat=function(t){return arguments.length?(i=t,h):i},h.tickSize=function(t){return arguments.length?(a=o=+t,h):a},h.tickSizeInner=function(t){return arguments.length?(a=+t,h):a},h.tickSizeOuter=function(t){return arguments.length?(o=+t,h):o},h.tickPadding=function(t){return arguments.length?(s=+t,h):s},h}function tt(t){return Q(1,t)}function et(t){return Q(2,t)}function nt(t){return Q(3,t)}function rt(t){return Q(4,t)}var it={value:function(){}};function at(){for(var t,e=0,n=arguments.length,r={};e<n;++e){if(!(t=arguments[e]+"")||t in r||/[\s.]/.test(t))throw new Error("illegal type: "+t);r[t]=[]}return new ot(r)}function ot(t){this._=t}function st(t,e){return t.trim().split(/^|\s+/).map((function(t){var n="",r=t.indexOf(".");if(r>=0&&(n=t.slice(r+1),t=t.slice(0,r)),t&&!e.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:n}}))}function ct(t,e){for(var n,r=0,i=t.length;r<i;++r)if((n=t[r]).name===e)return n.value}function ut(t,e,n){for(var r=0,i=t.length;r<i;++r)if(t[r].name===e){t[r]=it,t=t.slice(0,r).concat(t.slice(r+1));break}return null!=n&&t.push({name:e,value:n}),t}ot.prototype=at.prototype={constructor:ot,on:function(t,e){var n,r=this._,i=st(t+"",r),a=-1,o=i.length;if(!(arguments.length<2)){if(null!=e&&"function"!=typeof e)throw new Error("invalid callback: "+e);for(;++a<o;)if(n=(t=i[a]).type)r[n]=ut(r[n],t.name,e);else if(null==e)for(n in r)r[n]=ut(r[n],t.name,null);return this}for(;++a<o;)if((n=(t=i[a]).type)&&(n=ct(r[n],t.name)))return n},copy:function(){var t={},e=this._;for(var n in e)t[n]=e[n].slice();return new ot(t)},call:function(t,e){if((n=arguments.length-2)>0)for(var n,r,i=new Array(n),a=0;a<n;++a)i[a]=arguments[a+2];if(!this._.hasOwnProperty(t))throw new Error("unknown type: "+t);for(a=0,n=(r=this._[t]).length;a<n;++a)r[a].value.apply(e,i)},apply:function(t,e,n){if(!this._.hasOwnProperty(t))throw new Error("unknown type: "+t);for(var r=this._[t],i=0,a=r.length;i<a;++i)r[i].value.apply(e,n)}};var lt=at;function ht(){}var ft=function(t){return null==t?ht:function(){return this.querySelector(t)}};function dt(){return[]}var pt=function(t){return null==t?dt:function(){return this.querySelectorAll(t)}},gt=function(t){return function(){return this.matches(t)}},yt=function(t){return new Array(t.length)};function vt(t,e){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=e}vt.prototype={constructor:vt,appendChild:function(t){return this._parent.insertBefore(t,this._next)},insertBefore:function(t,e){return this._parent.insertBefore(t,e)},querySelector:function(t){return this._parent.querySelector(t)},querySelectorAll:function(t){return this._parent.querySelectorAll(t)}};function mt(t,e,n,r,i,a){for(var o,s=0,c=e.length,u=a.length;s<u;++s)(o=e[s])?(o.__data__=a[s],r[s]=o):n[s]=new vt(t,a[s]);for(;s<c;++s)(o=e[s])&&(i[s]=o)}function bt(t,e,n,r,i,a,o){var s,c,u,l={},h=e.length,f=a.length,d=new Array(h);for(s=0;s<h;++s)(c=e[s])&&(d[s]=u="$"+o.call(c,c.__data__,s,e),u in l?i[s]=c:l[u]=c);for(s=0;s<f;++s)(c=l[u="$"+o.call(t,a[s],s,a)])?(r[s]=c,c.__data__=a[s],l[u]=null):n[s]=new vt(t,a[s]);for(s=0;s<h;++s)(c=e[s])&&l[d[s]]===c&&(i[s]=c)}function xt(t,e){return t<e?-1:t>e?1:t>=e?0:NaN}var _t="http://www.w3.org/1999/xhtml",kt={svg:"http://www.w3.org/2000/svg",xhtml:_t,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"},wt=function(t){var e=t+="",n=e.indexOf(":");return n>=0&&"xmlns"!==(e=t.slice(0,n))&&(t=t.slice(n+1)),kt.hasOwnProperty(e)?{space:kt[e],local:t}:t};function Et(t){return function(){this.removeAttribute(t)}}function Tt(t){return function(){this.removeAttributeNS(t.space,t.local)}}function Ct(t,e){return function(){this.setAttribute(t,e)}}function At(t,e){return function(){this.setAttributeNS(t.space,t.local,e)}}function St(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttribute(t):this.setAttribute(t,n)}}function Mt(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,n)}}var Ot=function(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView};function Dt(t){return function(){this.style.removeProperty(t)}}function Nt(t,e,n){return function(){this.style.setProperty(t,e,n)}}function Bt(t,e,n){return function(){var r=e.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,n)}}function Lt(t,e){return t.style.getPropertyValue(e)||Ot(t).getComputedStyle(t,null).getPropertyValue(e)}function Ft(t){return function(){delete this[t]}}function Pt(t,e){return function(){this[t]=e}}function It(t,e){return function(){var n=e.apply(this,arguments);null==n?delete this[t]:this[t]=n}}function jt(t){return t.trim().split(/^|\s+/)}function Rt(t){return t.classList||new Yt(t)}function Yt(t){this._node=t,this._names=jt(t.getAttribute("class")||"")}function zt(t,e){for(var n=Rt(t),r=-1,i=e.length;++r<i;)n.add(e[r])}function Ut(t,e){for(var n=Rt(t),r=-1,i=e.length;++r<i;)n.remove(e[r])}function $t(t){return function(){zt(this,t)}}function Wt(t){return function(){Ut(this,t)}}function Ht(t,e){return function(){(e.apply(this,arguments)?zt:Ut)(this,t)}}Yt.prototype={add:function(t){this._names.indexOf(t)<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},remove:function(t){var e=this._names.indexOf(t);e>=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};function Vt(){this.textContent=""}function Gt(t){return function(){this.textContent=t}}function qt(t){return function(){var e=t.apply(this,arguments);this.textContent=null==e?"":e}}function Xt(){this.innerHTML=""}function Zt(t){return function(){this.innerHTML=t}}function Jt(t){return function(){var e=t.apply(this,arguments);this.innerHTML=null==e?"":e}}function Kt(){this.nextSibling&&this.parentNode.appendChild(this)}function Qt(){this.previousSibling&&this.parentNode.insertBefore(this,this.parentNode.firstChild)}function te(t){return function(){var e=this.ownerDocument,n=this.namespaceURI;return n===_t&&e.documentElement.namespaceURI===_t?e.createElement(t):e.createElementNS(n,t)}}function ee(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}var ne=function(t){var e=wt(t);return(e.local?ee:te)(e)};function re(){return null}function ie(){var t=this.parentNode;t&&t.removeChild(this)}function ae(){var t=this.cloneNode(!1),e=this.parentNode;return e?e.insertBefore(t,this.nextSibling):t}function oe(){var t=this.cloneNode(!0),e=this.parentNode;return e?e.insertBefore(t,this.nextSibling):t}var se={},ce=null;"undefined"!=typeof document&&("onmouseenter"in document.documentElement||(se={mouseenter:"mouseover",mouseleave:"mouseout"}));function ue(t,e,n){return t=le(t,e,n),function(e){var n=e.relatedTarget;n&&(n===this||8&n.compareDocumentPosition(this))||t.call(this,e)}}function le(t,e,n){return function(r){var i=ce;ce=r;try{t.call(this,this.__data__,e,n)}finally{ce=i}}}function he(t){return t.trim().split(/^|\s+/).map((function(t){var e="",n=t.indexOf(".");return n>=0&&(e=t.slice(n+1),t=t.slice(0,n)),{type:t,name:e}}))}function fe(t){return function(){var e=this.__on;if(e){for(var n,r=0,i=-1,a=e.length;r<a;++r)n=e[r],t.type&&n.type!==t.type||n.name!==t.name?e[++i]=n:this.removeEventListener(n.type,n.listener,n.capture);++i?e.length=i:delete this.__on}}}function de(t,e,n){var r=se.hasOwnProperty(t.type)?ue:le;return function(i,a,o){var s,c=this.__on,u=r(e,a,o);if(c)for(var l=0,h=c.length;l<h;++l)if((s=c[l]).type===t.type&&s.name===t.name)return this.removeEventListener(s.type,s.listener,s.capture),this.addEventListener(s.type,s.listener=u,s.capture=n),void(s.value=e);this.addEventListener(t.type,u,n),s={type:t.type,name:t.name,value:e,listener:u,capture:n},c?c.push(s):this.__on=[s]}}function pe(t,e,n,r){var i=ce;t.sourceEvent=ce,ce=t;try{return e.apply(n,r)}finally{ce=i}}function ge(t,e,n){var r=Ot(t),i=r.CustomEvent;"function"==typeof i?i=new i(e,n):(i=r.document.createEvent("Event"),n?(i.initEvent(e,n.bubbles,n.cancelable),i.detail=n.detail):i.initEvent(e,!1,!1)),t.dispatchEvent(i)}function ye(t,e){return function(){return ge(this,t,e)}}function ve(t,e){return function(){return ge(this,t,e.apply(this,arguments))}}var me=[null];function be(t,e){this._groups=t,this._parents=e}function xe(){return new be([[document.documentElement]],me)}be.prototype=xe.prototype={constructor:be,select:function(t){"function"!=typeof t&&(t=ft(t));for(var e=this._groups,n=e.length,r=new Array(n),i=0;i<n;++i)for(var a,o,s=e[i],c=s.length,u=r[i]=new Array(c),l=0;l<c;++l)(a=s[l])&&(o=t.call(a,a.__data__,l,s))&&("__data__"in a&&(o.__data__=a.__data__),u[l]=o);return new be(r,this._parents)},selectAll:function(t){"function"!=typeof t&&(t=pt(t));for(var e=this._groups,n=e.length,r=[],i=[],a=0;a<n;++a)for(var o,s=e[a],c=s.length,u=0;u<c;++u)(o=s[u])&&(r.push(t.call(o,o.__data__,u,s)),i.push(o));return new be(r,i)},filter:function(t){"function"!=typeof t&&(t=gt(t));for(var e=this._groups,n=e.length,r=new Array(n),i=0;i<n;++i)for(var a,o=e[i],s=o.length,c=r[i]=[],u=0;u<s;++u)(a=o[u])&&t.call(a,a.__data__,u,o)&&c.push(a);return new be(r,this._parents)},data:function(t,e){if(!t)return p=new Array(this.size()),l=-1,this.each((function(t){p[++l]=t})),p;var n,r=e?bt:mt,i=this._parents,a=this._groups;"function"!=typeof t&&(n=t,t=function(){return n});for(var o=a.length,s=new Array(o),c=new Array(o),u=new Array(o),l=0;l<o;++l){var h=i[l],f=a[l],d=f.length,p=t.call(h,h&&h.__data__,l,i),g=p.length,y=c[l]=new Array(g),v=s[l]=new Array(g);r(h,f,y,v,u[l]=new Array(d),p,e);for(var m,b,x=0,_=0;x<g;++x)if(m=y[x]){for(x>=_&&(_=x+1);!(b=v[_])&&++_<g;);m._next=b||null}}return(s=new be(s,i))._enter=c,s._exit=u,s},enter:function(){return new be(this._enter||this._groups.map(yt),this._parents)},exit:function(){return new be(this._exit||this._groups.map(yt),this._parents)},join:function(t,e,n){var r=this.enter(),i=this,a=this.exit();return r="function"==typeof t?t(r):r.append(t+""),null!=e&&(i=e(i)),null==n?a.remove():n(a),r&&i?r.merge(i).order():i},merge:function(t){for(var e=this._groups,n=t._groups,r=e.length,i=n.length,a=Math.min(r,i),o=new Array(r),s=0;s<a;++s)for(var c,u=e[s],l=n[s],h=u.length,f=o[s]=new Array(h),d=0;d<h;++d)(c=u[d]||l[d])&&(f[d]=c);for(;s<r;++s)o[s]=e[s];return new be(o,this._parents)},order:function(){for(var t=this._groups,e=-1,n=t.length;++e<n;)for(var r,i=t[e],a=i.length-1,o=i[a];--a>=0;)(r=i[a])&&(o&&4^r.compareDocumentPosition(o)&&o.parentNode.insertBefore(r,o),o=r);return this},sort:function(t){function e(e,n){return e&&n?t(e.__data__,n.__data__):!e-!n}t||(t=xt);for(var n=this._groups,r=n.length,i=new Array(r),a=0;a<r;++a){for(var o,s=n[a],c=s.length,u=i[a]=new Array(c),l=0;l<c;++l)(o=s[l])&&(u[l]=o);u.sort(e)}return new be(i,this._parents).order()},call:function(){var t=arguments[0];return arguments[0]=this,t.apply(null,arguments),this},nodes:function(){var t=new Array(this.size()),e=-1;return this.each((function(){t[++e]=this})),t},node:function(){for(var t=this._groups,e=0,n=t.length;e<n;++e)for(var r=t[e],i=0,a=r.length;i<a;++i){var o=r[i];if(o)return o}return null},size:function(){var t=0;return this.each((function(){++t})),t},empty:function(){return!this.node()},each:function(t){for(var e=this._groups,n=0,r=e.length;n<r;++n)for(var i,a=e[n],o=0,s=a.length;o<s;++o)(i=a[o])&&t.call(i,i.__data__,o,a);return this},attr:function(t,e){var n=wt(t);if(arguments.length<2){var r=this.node();return n.local?r.getAttributeNS(n.space,n.local):r.getAttribute(n)}return this.each((null==e?n.local?Tt:Et:"function"==typeof e?n.local?Mt:St:n.local?At:Ct)(n,e))},style:function(t,e,n){return arguments.length>1?this.each((null==e?Dt:"function"==typeof e?Bt:Nt)(t,e,null==n?"":n)):Lt(this.node(),t)},property:function(t,e){return arguments.length>1?this.each((null==e?Ft:"function"==typeof e?It:Pt)(t,e)):this.node()[t]},classed:function(t,e){var n=jt(t+"");if(arguments.length<2){for(var r=Rt(this.node()),i=-1,a=n.length;++i<a;)if(!r.contains(n[i]))return!1;return!0}return this.each(("function"==typeof e?Ht:e?$t:Wt)(n,e))},text:function(t){return arguments.length?this.each(null==t?Vt:("function"==typeof t?qt:Gt)(t)):this.node().textContent},html:function(t){return arguments.length?this.each(null==t?Xt:("function"==typeof t?Jt:Zt)(t)):this.node().innerHTML},raise:function(){return this.each(Kt)},lower:function(){return this.each(Qt)},append:function(t){var e="function"==typeof t?t:ne(t);return this.select((function(){return this.appendChild(e.apply(this,arguments))}))},insert:function(t,e){var n="function"==typeof t?t:ne(t),r=null==e?re:"function"==typeof e?e:ft(e);return this.select((function(){return this.insertBefore(n.apply(this,arguments),r.apply(this,arguments)||null)}))},remove:function(){return this.each(ie)},clone:function(t){return this.select(t?oe:ae)},datum:function(t){return arguments.length?this.property("__data__",t):this.node().__data__},on:function(t,e,n){var r,i,a=he(t+""),o=a.length;if(!(arguments.length<2)){for(s=e?de:fe,null==n&&(n=!1),r=0;r<o;++r)this.each(s(a[r],e,n));return this}var s=this.node().__on;if(s)for(var c,u=0,l=s.length;u<l;++u)for(r=0,c=s[u];r<o;++r)if((i=a[r]).type===c.type&&i.name===c.name)return c.value},dispatch:function(t,e){return this.each(("function"==typeof e?ve:ye)(t,e))}};var _e=xe,ke=function(t){return"string"==typeof t?new be([[document.querySelector(t)]],[document.documentElement]):new be([[t]],me)};function we(){ce.stopImmediatePropagation()}var Ee=function(){ce.preventDefault(),ce.stopImmediatePropagation()},Te=function(t){var e=t.document.documentElement,n=ke(t).on("dragstart.drag",Ee,!0);"onselectstart"in e?n.on("selectstart.drag",Ee,!0):(e.__noselect=e.style.MozUserSelect,e.style.MozUserSelect="none")};function Ce(t,e){var n=t.document.documentElement,r=ke(t).on("dragstart.drag",null);e&&(r.on("click.drag",Ee,!0),setTimeout((function(){r.on("click.drag",null)}),0)),"onselectstart"in n?r.on("selectstart.drag",null):(n.style.MozUserSelect=n.__noselect,delete n.__noselect)}var Ae=function(t,e,n){t.prototype=e.prototype=n,n.constructor=t};function Se(t,e){var n=Object.create(t.prototype);for(var r in e)n[r]=e[r];return n}function Me(){}var Oe="\\s*([+-]?\\d+)\\s*",De="\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\s*",Ne="\\s*([+-]?\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)%\\s*",Be=/^#([0-9a-f]{3,8})$/,Le=new RegExp("^rgb\\("+[Oe,Oe,Oe]+"\\)$"),Fe=new RegExp("^rgb\\("+[Ne,Ne,Ne]+"\\)$"),Pe=new RegExp("^rgba\\("+[Oe,Oe,Oe,De]+"\\)$"),Ie=new RegExp("^rgba\\("+[Ne,Ne,Ne,De]+"\\)$"),je=new RegExp("^hsl\\("+[De,Ne,Ne]+"\\)$"),Re=new RegExp("^hsla\\("+[De,Ne,Ne,De]+"\\)$"),Ye={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};function ze(){return this.rgb().formatHex()}function Ue(){return this.rgb().formatRgb()}function $e(t){var e,n;return t=(t+"").trim().toLowerCase(),(e=Be.exec(t))?(n=e[1].length,e=parseInt(e[1],16),6===n?We(e):3===n?new qe(e>>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):8===n?new qe(e>>24&255,e>>16&255,e>>8&255,(255&e)/255):4===n?new qe(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|240&e,((15&e)<<4|15&e)/255):null):(e=Le.exec(t))?new qe(e[1],e[2],e[3],1):(e=Fe.exec(t))?new qe(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=Pe.exec(t))?He(e[1],e[2],e[3],e[4]):(e=Ie.exec(t))?He(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=je.exec(t))?Ke(e[1],e[2]/100,e[3]/100,1):(e=Re.exec(t))?Ke(e[1],e[2]/100,e[3]/100,e[4]):Ye.hasOwnProperty(t)?We(Ye[t]):"transparent"===t?new qe(NaN,NaN,NaN,0):null}function We(t){return new qe(t>>16&255,t>>8&255,255&t,1)}function He(t,e,n,r){return r<=0&&(t=e=n=NaN),new qe(t,e,n,r)}function Ve(t){return t instanceof Me||(t=$e(t)),t?new qe((t=t.rgb()).r,t.g,t.b,t.opacity):new qe}function Ge(t,e,n,r){return 1===arguments.length?Ve(t):new qe(t,e,n,null==r?1:r)}function qe(t,e,n,r){this.r=+t,this.g=+e,this.b=+n,this.opacity=+r}function Xe(){return"#"+Je(this.r)+Je(this.g)+Je(this.b)}function Ze(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function Je(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function Ke(t,e,n,r){return r<=0?t=e=n=NaN:n<=0||n>=1?t=e=NaN:e<=0&&(t=NaN),new en(t,e,n,r)}function Qe(t){if(t instanceof en)return new en(t.h,t.s,t.l,t.opacity);if(t instanceof Me||(t=$e(t)),!t)return new en;if(t instanceof en)return t;var e=(t=t.rgb()).r/255,n=t.g/255,r=t.b/255,i=Math.min(e,n,r),a=Math.max(e,n,r),o=NaN,s=a-i,c=(a+i)/2;return s?(o=e===a?(n-r)/s+6*(n<r):n===a?(r-e)/s+2:(e-n)/s+4,s/=c<.5?a+i:2-a-i,o*=60):s=c>0&&c<1?0:o,new en(o,s,c,t.opacity)}function tn(t,e,n,r){return 1===arguments.length?Qe(t):new en(t,e,n,null==r?1:r)}function en(t,e,n,r){this.h=+t,this.s=+e,this.l=+n,this.opacity=+r}function nn(t,e,n){return 255*(t<60?e+(n-e)*t/60:t<180?n:t<240?e+(n-e)*(240-t)/60:e)}function rn(t,e,n,r,i){var a=t*t,o=a*t;return((1-3*t+3*a-o)*e+(4-6*a+3*o)*n+(1+3*t+3*a-3*o)*r+o*i)/6}Ae(Me,$e,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:ze,formatHex:ze,formatHsl:function(){return Qe(this).formatHsl()},formatRgb:Ue,toString:Ue}),Ae(qe,Ge,Se(Me,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new qe(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new qe(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:Xe,formatHex:Xe,formatRgb:Ze,toString:Ze})),Ae(en,tn,Se(Me,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new en(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new en(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),e=isNaN(t)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*e,i=2*n-r;return new qe(nn(t>=240?t-240:t+120,i,r),nn(t,i,r),nn(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));var an=function(t){var e=t.length-1;return function(n){var r=n<=0?n=0:n>=1?(n=1,e-1):Math.floor(n*e),i=t[r],a=t[r+1],o=r>0?t[r-1]:2*i-a,s=r<e-1?t[r+2]:2*a-i;return rn((n-r/e)*e,o,i,a,s)}},on=function(t){var e=t.length;return function(n){var r=Math.floor(((n%=1)<0?++n:n)*e),i=t[(r+e-1)%e],a=t[r%e],o=t[(r+1)%e],s=t[(r+2)%e];return rn((n-r/e)*e,i,a,o,s)}},sn=function(t){return function(){return t}};function cn(t,e){return function(n){return t+n*e}}function un(t,e){var n=e-t;return n?cn(t,n>180||n<-180?n-360*Math.round(n/360):n):sn(isNaN(t)?e:t)}function ln(t){return 1==(t=+t)?hn:function(e,n){return n-e?function(t,e,n){return t=Math.pow(t,n),e=Math.pow(e,n)-t,n=1/n,function(r){return Math.pow(t+r*e,n)}}(e,n,t):sn(isNaN(e)?n:e)}}function hn(t,e){var n=e-t;return n?cn(t,n):sn(isNaN(t)?e:t)}var fn=function t(e){var n=ln(e);function r(t,e){var r=n((t=Ge(t)).r,(e=Ge(e)).r),i=n(t.g,e.g),a=n(t.b,e.b),o=hn(t.opacity,e.opacity);return function(e){return t.r=r(e),t.g=i(e),t.b=a(e),t.opacity=o(e),t+""}}return r.gamma=t,r}(1);function dn(t){return function(e){var n,r,i=e.length,a=new Array(i),o=new Array(i),s=new Array(i);for(n=0;n<i;++n)r=Ge(e[n]),a[n]=r.r||0,o[n]=r.g||0,s[n]=r.b||0;return a=t(a),o=t(o),s=t(s),r.opacity=1,function(t){return r.r=a(t),r.g=o(t),r.b=s(t),r+""}}}var pn=dn(an),gn=dn(on),yn=function(t,e){e||(e=[]);var n,r=t?Math.min(e.length,t.length):0,i=e.slice();return function(a){for(n=0;n<r;++n)i[n]=t[n]*(1-a)+e[n]*a;return i}};function vn(t){return ArrayBuffer.isView(t)&&!(t instanceof DataView)}var mn=function(t,e){return(vn(e)?yn:bn)(t,e)};function bn(t,e){var n,r=e?e.length:0,i=t?Math.min(r,t.length):0,a=new Array(i),o=new Array(r);for(n=0;n<i;++n)a[n]=Sn(t[n],e[n]);for(;n<r;++n)o[n]=e[n];return function(t){for(n=0;n<i;++n)o[n]=a[n](t);return o}}var xn=function(t,e){var n=new Date;return t=+t,e=+e,function(r){return n.setTime(t*(1-r)+e*r),n}},_n=function(t,e){return t=+t,e=+e,function(n){return t*(1-n)+e*n}},kn=function(t,e){var n,r={},i={};for(n in null!==t&&"object"==typeof t||(t={}),null!==e&&"object"==typeof e||(e={}),e)n in t?r[n]=Sn(t[n],e[n]):i[n]=e[n];return function(t){for(n in r)i[n]=r[n](t);return i}},wn=/[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g,En=new RegExp(wn.source,"g");var Tn,Cn,An=function(t,e){var n,r,i,a=wn.lastIndex=En.lastIndex=0,o=-1,s=[],c=[];for(t+="",e+="";(n=wn.exec(t))&&(r=En.exec(e));)(i=r.index)>a&&(i=e.slice(a,i),s[o]?s[o]+=i:s[++o]=i),(n=n[0])===(r=r[0])?s[o]?s[o]+=r:s[++o]=r:(s[++o]=null,c.push({i:o,x:_n(n,r)})),a=En.lastIndex;return a<e.length&&(i=e.slice(a),s[o]?s[o]+=i:s[++o]=i),s.length<2?c[0]?function(t){return function(e){return t(e)+""}}(c[0].x):function(t){return function(){return t}}(e):(e=c.length,function(t){for(var n,r=0;r<e;++r)s[(n=c[r]).i]=n.x(t);return s.join("")})},Sn=function(t,e){var n,r=typeof e;return null==e||"boolean"===r?sn(e):("number"===r?_n:"string"===r?(n=$e(e))?(e=n,fn):An:e instanceof $e?fn:e instanceof Date?xn:vn(e)?yn:Array.isArray(e)?bn:"function"!=typeof e.valueOf&&"function"!=typeof e.toString||isNaN(e)?kn:_n)(t,e)},Mn=function(){for(var t,e=ce;t=e.sourceEvent;)e=t;return e},On=function(t,e){var n=t.ownerSVGElement||t;if(n.createSVGPoint){var r=n.createSVGPoint();return r.x=e.clientX,r.y=e.clientY,[(r=r.matrixTransform(t.getScreenCTM().inverse())).x,r.y]}var i=t.getBoundingClientRect();return[e.clientX-i.left-t.clientLeft,e.clientY-i.top-t.clientTop]},Dn=function(t,e,n){arguments.length<3&&(n=e,e=Mn().changedTouches);for(var r,i=0,a=e?e.length:0;i<a;++i)if((r=e[i]).identifier===n)return On(t,r);return null},Nn=function(t){var e=Mn();return e.changedTouches&&(e=e.changedTouches[0]),On(t,e)},Bn=0,Ln=0,Fn=0,Pn=0,In=0,jn=0,Rn="object"==typeof performance&&performance.now?performance:Date,Yn="object"==typeof window&&window.requestAnimationFrame?window.requestAnimationFrame.bind(window):function(t){setTimeout(t,17)};function zn(){return In||(Yn(Un),In=Rn.now()+jn)}function Un(){In=0}function $n(){this._call=this._time=this._next=null}function Wn(t,e,n){var r=new $n;return r.restart(t,e,n),r}function Hn(){zn(),++Bn;for(var t,e=Tn;e;)(t=In-e._time)>=0&&e._call.call(null,t),e=e._next;--Bn}function Vn(){In=(Pn=Rn.now())+jn,Bn=Ln=0;try{Hn()}finally{Bn=0,function(){var t,e,n=Tn,r=1/0;for(;n;)n._call?(r>n._time&&(r=n._time),t=n,n=n._next):(e=n._next,n._next=null,n=t?t._next=e:Tn=e);Cn=t,qn(r)}(),In=0}}function Gn(){var t=Rn.now(),e=t-Pn;e>1e3&&(jn-=e,Pn=t)}function qn(t){Bn||(Ln&&(Ln=clearTimeout(Ln)),t-In>24?(t<1/0&&(Ln=setTimeout(Vn,t-Rn.now()-jn)),Fn&&(Fn=clearInterval(Fn))):(Fn||(Pn=Rn.now(),Fn=setInterval(Gn,1e3)),Bn=1,Yn(Vn)))}$n.prototype=Wn.prototype={constructor:$n,restart:function(t,e,n){if("function"!=typeof t)throw new TypeError("callback is not a function");n=(null==n?zn():+n)+(null==e?0:+e),this._next||Cn===this||(Cn?Cn._next=this:Tn=this,Cn=this),this._call=t,this._time=n,qn()},stop:function(){this._call&&(this._call=null,this._time=1/0,qn())}};var Xn=function(t,e,n){var r=new $n;return e=null==e?0:+e,r.restart((function(n){r.stop(),t(n+e)}),e,n),r},Zn=lt("start","end","cancel","interrupt"),Jn=[],Kn=function(t,e,n,r,i,a){var o=t.__transition;if(o){if(n in o)return}else t.__transition={};!function(t,e,n){var r,i=t.__transition;function a(c){var u,l,h,f;if(1!==n.state)return s();for(u in i)if((f=i[u]).name===n.name){if(3===f.state)return Xn(a);4===f.state?(f.state=6,f.timer.stop(),f.on.call("interrupt",t,t.__data__,f.index,f.group),delete i[u]):+u<e&&(f.state=6,f.timer.stop(),f.on.call("cancel",t,t.__data__,f.index,f.group),delete i[u])}if(Xn((function(){3===n.state&&(n.state=4,n.timer.restart(o,n.delay,n.time),o(c))})),n.state=2,n.on.call("start",t,t.__data__,n.index,n.group),2===n.state){for(n.state=3,r=new Array(h=n.tween.length),u=0,l=-1;u<h;++u)(f=n.tween[u].value.call(t,t.__data__,n.index,n.group))&&(r[++l]=f);r.length=l+1}}function o(e){for(var i=e<n.duration?n.ease.call(null,e/n.duration):(n.timer.restart(s),n.state=5,1),a=-1,o=r.length;++a<o;)r[a].call(t,i);5===n.state&&(n.on.call("end",t,t.__data__,n.index,n.group),s())}function s(){for(var r in n.state=6,n.timer.stop(),delete i[e],i)return;delete t.__transition}i[e]=n,n.timer=Wn((function(t){n.state=1,n.timer.restart(a,n.delay,n.time),n.delay<=t&&a(t-n.delay)}),0,n.time)}(t,n,{name:e,index:r,group:i,on:Zn,tween:Jn,time:a.time,delay:a.delay,duration:a.duration,ease:a.ease,timer:null,state:0})};function Qn(t,e){var n=er(t,e);if(n.state>0)throw new Error("too late; already scheduled");return n}function tr(t,e){var n=er(t,e);if(n.state>3)throw new Error("too late; already running");return n}function er(t,e){var n=t.__transition;if(!n||!(n=n[e]))throw new Error("transition not found");return n}var nr,rr,ir,ar,or=function(t,e){var n,r,i,a=t.__transition,o=!0;if(a){for(i in e=null==e?null:e+"",a)(n=a[i]).name===e?(r=n.state>2&&n.state<5,n.state=6,n.timer.stop(),n.on.call(r?"interrupt":"cancel",t,t.__data__,n.index,n.group),delete a[i]):o=!1;o&&delete t.__transition}},sr=180/Math.PI,cr={translateX:0,translateY:0,rotate:0,skewX:0,scaleX:1,scaleY:1},ur=function(t,e,n,r,i,a){var o,s,c;return(o=Math.sqrt(t*t+e*e))&&(t/=o,e/=o),(c=t*n+e*r)&&(n-=t*c,r-=e*c),(s=Math.sqrt(n*n+r*r))&&(n/=s,r/=s,c/=s),t*r<e*n&&(t=-t,e=-e,c=-c,o=-o),{translateX:i,translateY:a,rotate:Math.atan2(e,t)*sr,skewX:Math.atan(c)*sr,scaleX:o,scaleY:s}};function lr(t,e,n,r){function i(t){return t.length?t.pop()+" ":""}return function(a,o){var s=[],c=[];return a=t(a),o=t(o),function(t,r,i,a,o,s){if(t!==i||r!==a){var c=o.push("translate(",null,e,null,n);s.push({i:c-4,x:_n(t,i)},{i:c-2,x:_n(r,a)})}else(i||a)&&o.push("translate("+i+e+a+n)}(a.translateX,a.translateY,o.translateX,o.translateY,s,c),function(t,e,n,a){t!==e?(t-e>180?e+=360:e-t>180&&(t+=360),a.push({i:n.push(i(n)+"rotate(",null,r)-2,x:_n(t,e)})):e&&n.push(i(n)+"rotate("+e+r)}(a.rotate,o.rotate,s,c),function(t,e,n,a){t!==e?a.push({i:n.push(i(n)+"skewX(",null,r)-2,x:_n(t,e)}):e&&n.push(i(n)+"skewX("+e+r)}(a.skewX,o.skewX,s,c),function(t,e,n,r,a,o){if(t!==n||e!==r){var s=a.push(i(a)+"scale(",null,",",null,")");o.push({i:s-4,x:_n(t,n)},{i:s-2,x:_n(e,r)})}else 1===n&&1===r||a.push(i(a)+"scale("+n+","+r+")")}(a.scaleX,a.scaleY,o.scaleX,o.scaleY,s,c),a=o=null,function(t){for(var e,n=-1,r=c.length;++n<r;)s[(e=c[n]).i]=e.x(t);return s.join("")}}}var hr=lr((function(t){return"none"===t?cr:(nr||(nr=document.createElement("DIV"),rr=document.documentElement,ir=document.defaultView),nr.style.transform=t,t=ir.getComputedStyle(rr.appendChild(nr),null).getPropertyValue("transform"),rr.removeChild(nr),t=t.slice(7,-1).split(","),ur(+t[0],+t[1],+t[2],+t[3],+t[4],+t[5]))}),"px, ","px)","deg)"),fr=lr((function(t){return null==t?cr:(ar||(ar=document.createElementNS("http://www.w3.org/2000/svg","g")),ar.setAttribute("transform",t),(t=ar.transform.baseVal.consolidate())?(t=t.matrix,ur(t.a,t.b,t.c,t.d,t.e,t.f)):cr)}),", ",")",")");function dr(t,e){var n,r;return function(){var i=tr(this,t),a=i.tween;if(a!==n)for(var o=0,s=(r=n=a).length;o<s;++o)if(r[o].name===e){(r=r.slice()).splice(o,1);break}i.tween=r}}function pr(t,e,n){var r,i;if("function"!=typeof n)throw new Error;return function(){var a=tr(this,t),o=a.tween;if(o!==r){i=(r=o).slice();for(var s={name:e,value:n},c=0,u=i.length;c<u;++c)if(i[c].name===e){i[c]=s;break}c===u&&i.push(s)}a.tween=i}}function gr(t,e,n){var r=t._id;return t.each((function(){var t=tr(this,r);(t.value||(t.value={}))[e]=n.apply(this,arguments)})),function(t){return er(t,r).value[e]}}var yr=function(t,e){var n;return("number"==typeof e?_n:e instanceof $e?fn:(n=$e(e))?(e=n,fn):An)(t,e)};function vr(t){return function(){this.removeAttribute(t)}}function mr(t){return function(){this.removeAttributeNS(t.space,t.local)}}function br(t,e,n){var r,i,a=n+"";return function(){var o=this.getAttribute(t);return o===a?null:o===r?i:i=e(r=o,n)}}function xr(t,e,n){var r,i,a=n+"";return function(){var o=this.getAttributeNS(t.space,t.local);return o===a?null:o===r?i:i=e(r=o,n)}}function _r(t,e,n){var r,i,a;return function(){var o,s,c=n(this);if(null!=c)return(o=this.getAttribute(t))===(s=c+"")?null:o===r&&s===i?a:(i=s,a=e(r=o,c));this.removeAttribute(t)}}function kr(t,e,n){var r,i,a;return function(){var o,s,c=n(this);if(null!=c)return(o=this.getAttributeNS(t.space,t.local))===(s=c+"")?null:o===r&&s===i?a:(i=s,a=e(r=o,c));this.removeAttributeNS(t.space,t.local)}}function wr(t,e){return function(n){this.setAttribute(t,e.call(this,n))}}function Er(t,e){return function(n){this.setAttributeNS(t.space,t.local,e.call(this,n))}}function Tr(t,e){var n,r;function i(){var i=e.apply(this,arguments);return i!==r&&(n=(r=i)&&Er(t,i)),n}return i._value=e,i}function Cr(t,e){var n,r;function i(){var i=e.apply(this,arguments);return i!==r&&(n=(r=i)&&wr(t,i)),n}return i._value=e,i}function Ar(t,e){return function(){Qn(this,t).delay=+e.apply(this,arguments)}}function Sr(t,e){return e=+e,function(){Qn(this,t).delay=e}}function Mr(t,e){return function(){tr(this,t).duration=+e.apply(this,arguments)}}function Or(t,e){return e=+e,function(){tr(this,t).duration=e}}function Dr(t,e){if("function"!=typeof e)throw new Error;return function(){tr(this,t).ease=e}}function Nr(t,e,n){var r,i,a=function(t){return(t+"").trim().split(/^|\s+/).every((function(t){var e=t.indexOf(".");return e>=0&&(t=t.slice(0,e)),!t||"start"===t}))}(e)?Qn:tr;return function(){var o=a(this,t),s=o.on;s!==r&&(i=(r=s).copy()).on(e,n),o.on=i}}var Br=_e.prototype.constructor;function Lr(t){return function(){this.style.removeProperty(t)}}function Fr(t,e,n){return function(r){this.style.setProperty(t,e.call(this,r),n)}}function Pr(t,e,n){var r,i;function a(){var a=e.apply(this,arguments);return a!==i&&(r=(i=a)&&Fr(t,a,n)),r}return a._value=e,a}function Ir(t){return function(e){this.textContent=t.call(this,e)}}function jr(t){var e,n;function r(){var r=t.apply(this,arguments);return r!==n&&(e=(n=r)&&Ir(r)),e}return r._value=t,r}var Rr=0;function Yr(t,e,n,r){this._groups=t,this._parents=e,this._name=n,this._id=r}function zr(t){return _e().transition(t)}function Ur(){return++Rr}var $r=_e.prototype;function Wr(t){return t*t*t}function Hr(t){return--t*t*t+1}function Vr(t){return((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2}Yr.prototype=zr.prototype={constructor:Yr,select:function(t){var e=this._name,n=this._id;"function"!=typeof t&&(t=ft(t));for(var r=this._groups,i=r.length,a=new Array(i),o=0;o<i;++o)for(var s,c,u=r[o],l=u.length,h=a[o]=new Array(l),f=0;f<l;++f)(s=u[f])&&(c=t.call(s,s.__data__,f,u))&&("__data__"in s&&(c.__data__=s.__data__),h[f]=c,Kn(h[f],e,n,f,h,er(s,n)));return new Yr(a,this._parents,e,n)},selectAll:function(t){var e=this._name,n=this._id;"function"!=typeof t&&(t=pt(t));for(var r=this._groups,i=r.length,a=[],o=[],s=0;s<i;++s)for(var c,u=r[s],l=u.length,h=0;h<l;++h)if(c=u[h]){for(var f,d=t.call(c,c.__data__,h,u),p=er(c,n),g=0,y=d.length;g<y;++g)(f=d[g])&&Kn(f,e,n,g,d,p);a.push(d),o.push(c)}return new Yr(a,o,e,n)},filter:function(t){"function"!=typeof t&&(t=gt(t));for(var e=this._groups,n=e.length,r=new Array(n),i=0;i<n;++i)for(var a,o=e[i],s=o.length,c=r[i]=[],u=0;u<s;++u)(a=o[u])&&t.call(a,a.__data__,u,o)&&c.push(a);return new Yr(r,this._parents,this._name,this._id)},merge:function(t){if(t._id!==this._id)throw new Error;for(var e=this._groups,n=t._groups,r=e.length,i=n.length,a=Math.min(r,i),o=new Array(r),s=0;s<a;++s)for(var c,u=e[s],l=n[s],h=u.length,f=o[s]=new Array(h),d=0;d<h;++d)(c=u[d]||l[d])&&(f[d]=c);for(;s<r;++s)o[s]=e[s];return new Yr(o,this._parents,this._name,this._id)},selection:function(){return new Br(this._groups,this._parents)},transition:function(){for(var t=this._name,e=this._id,n=Ur(),r=this._groups,i=r.length,a=0;a<i;++a)for(var o,s=r[a],c=s.length,u=0;u<c;++u)if(o=s[u]){var l=er(o,e);Kn(o,t,n,u,s,{time:l.time+l.delay+l.duration,delay:0,duration:l.duration,ease:l.ease})}return new Yr(r,this._parents,t,n)},call:$r.call,nodes:$r.nodes,node:$r.node,size:$r.size,empty:$r.empty,each:$r.each,on:function(t,e){var n=this._id;return arguments.length<2?er(this.node(),n).on.on(t):this.each(Nr(n,t,e))},attr:function(t,e){var n=wt(t),r="transform"===n?fr:yr;return this.attrTween(t,"function"==typeof e?(n.local?kr:_r)(n,r,gr(this,"attr."+t,e)):null==e?(n.local?mr:vr)(n):(n.local?xr:br)(n,r,e))},attrTween:function(t,e){var n="attr."+t;if(arguments.length<2)return(n=this.tween(n))&&n._value;if(null==e)return this.tween(n,null);if("function"!=typeof e)throw new Error;var r=wt(t);return this.tween(n,(r.local?Tr:Cr)(r,e))},style:function(t,e,n){var r="transform"==(t+="")?hr:yr;return null==e?this.styleTween(t,function(t,e){var n,r,i;return function(){var a=Lt(this,t),o=(this.style.removeProperty(t),Lt(this,t));return a===o?null:a===n&&o===r?i:i=e(n=a,r=o)}}(t,r)).on("end.style."+t,Lr(t)):"function"==typeof e?this.styleTween(t,function(t,e,n){var r,i,a;return function(){var o=Lt(this,t),s=n(this),c=s+"";return null==s&&(this.style.removeProperty(t),c=s=Lt(this,t)),o===c?null:o===r&&c===i?a:(i=c,a=e(r=o,s))}}(t,r,gr(this,"style."+t,e))).each(function(t,e){var n,r,i,a,o="style."+e,s="end."+o;return function(){var c=tr(this,t),u=c.on,l=null==c.value[o]?a||(a=Lr(e)):void 0;u===n&&i===l||(r=(n=u).copy()).on(s,i=l),c.on=r}}(this._id,t)):this.styleTween(t,function(t,e,n){var r,i,a=n+"";return function(){var o=Lt(this,t);return o===a?null:o===r?i:i=e(r=o,n)}}(t,r,e),n).on("end.style."+t,null)},styleTween:function(t,e,n){var r="style."+(t+="");if(arguments.length<2)return(r=this.tween(r))&&r._value;if(null==e)return this.tween(r,null);if("function"!=typeof e)throw new Error;return this.tween(r,Pr(t,e,null==n?"":n))},text:function(t){return this.tween("text","function"==typeof t?function(t){return function(){var e=t(this);this.textContent=null==e?"":e}}(gr(this,"text",t)):function(t){return function(){this.textContent=t}}(null==t?"":t+""))},textTween:function(t){var e="text";if(arguments.length<1)return(e=this.tween(e))&&e._value;if(null==t)return this.tween(e,null);if("function"!=typeof t)throw new Error;return this.tween(e,jr(t))},remove:function(){return this.on("end.remove",(t=this._id,function(){var e=this.parentNode;for(var n in this.__transition)if(+n!==t)return;e&&e.removeChild(this)}));var t},tween:function(t,e){var n=this._id;if(t+="",arguments.length<2){for(var r,i=er(this.node(),n).tween,a=0,o=i.length;a<o;++a)if((r=i[a]).name===t)return r.value;return null}return this.each((null==e?dr:pr)(n,t,e))},delay:function(t){var e=this._id;return arguments.length?this.each(("function"==typeof t?Ar:Sr)(e,t)):er(this.node(),e).delay},duration:function(t){var e=this._id;return arguments.length?this.each(("function"==typeof t?Mr:Or)(e,t)):er(this.node(),e).duration},ease:function(t){var e=this._id;return arguments.length?this.each(Dr(e,t)):er(this.node(),e).ease},end:function(){var t,e,n=this,r=n._id,i=n.size();return new Promise((function(a,o){var s={value:o},c={value:function(){0==--i&&a()}};n.each((function(){var n=tr(this,r),i=n.on;i!==t&&((e=(t=i).copy())._.cancel.push(s),e._.interrupt.push(s),e._.end.push(c)),n.on=e}))}))}};var Gr={time:null,delay:0,duration:250,ease:Vr};function qr(t,e){for(var n;!(n=t.__transition)||!(n=n[e]);)if(!(t=t.parentNode))return Gr.time=zn(),Gr;return n}_e.prototype.interrupt=function(t){return this.each((function(){or(this,t)}))},_e.prototype.transition=function(t){var e,n;t instanceof Yr?(e=t._id,t=t._name):(e=Ur(),(n=Gr).time=zn(),t=null==t?null:t+"");for(var r=this._groups,i=r.length,a=0;a<i;++a)for(var o,s=r[a],c=s.length,u=0;u<c;++u)(o=s[u])&&Kn(o,t,e,u,s,n||qr(o,e));return new Yr(r,this._parents,t,e)};var Xr=[null],Zr=function(t,e){var n,r,i=t.__transition;if(i)for(r in e=null==e?null:e+"",i)if((n=i[r]).state>1&&n.name===e)return new Yr([[t]],Xr,e,+r);return null},Jr=function(t){return function(){return t}},Kr=function(t,e,n){this.target=t,this.type=e,this.selection=n};function Qr(){ce.stopImmediatePropagation()}var ti=function(){ce.preventDefault(),ce.stopImmediatePropagation()},ei={name:"drag"},ni={name:"space"},ri={name:"handle"},ii={name:"center"};function ai(t){return[+t[0],+t[1]]}function oi(t){return[ai(t[0]),ai(t[1])]}function si(t){return function(e){return Dn(e,ce.touches,t)}}var ci={name:"x",handles:["w","e"].map(yi),input:function(t,e){return null==t?null:[[+t[0],e[0][1]],[+t[1],e[1][1]]]},output:function(t){return t&&[t[0][0],t[1][0]]}},ui={name:"y",handles:["n","s"].map(yi),input:function(t,e){return null==t?null:[[e[0][0],+t[0]],[e[1][0],+t[1]]]},output:function(t){return t&&[t[0][1],t[1][1]]}},li={name:"xy",handles:["n","w","e","s","nw","ne","sw","se"].map(yi),input:function(t){return null==t?null:oi(t)},output:function(t){return t}},hi={overlay:"crosshair",selection:"move",n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},fi={e:"w",w:"e",nw:"ne",ne:"nw",se:"sw",sw:"se"},di={n:"s",s:"n",nw:"sw",ne:"se",se:"ne",sw:"nw"},pi={overlay:1,selection:1,n:null,e:1,s:null,w:-1,nw:-1,ne:1,se:1,sw:-1},gi={overlay:1,selection:1,n:-1,e:null,s:1,w:null,nw:-1,ne:-1,se:1,sw:1};function yi(t){return{type:t}}function vi(){return!ce.ctrlKey&&!ce.button}function mi(){var t=this.ownerSVGElement||this;return t.hasAttribute("viewBox")?[[(t=t.viewBox.baseVal).x,t.y],[t.x+t.width,t.y+t.height]]:[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]}function bi(){return navigator.maxTouchPoints||"ontouchstart"in this}function xi(t){for(;!t.__brush;)if(!(t=t.parentNode))return;return t.__brush}function _i(t){return t[0][0]===t[1][0]||t[0][1]===t[1][1]}function ki(t){var e=t.__brush;return e?e.dim.output(e.selection):null}function wi(){return Ci(ci)}function Ei(){return Ci(ui)}var Ti=function(){return Ci(li)};function Ci(t){var e,n=mi,r=vi,i=bi,a=!0,o=lt("start","brush","end"),s=6;function c(e){var n=e.property("__brush",g).selectAll(".overlay").data([yi("overlay")]);n.enter().append("rect").attr("class","overlay").attr("pointer-events","all").attr("cursor",hi.overlay).merge(n).each((function(){var t=xi(this).extent;ke(this).attr("x",t[0][0]).attr("y",t[0][1]).attr("width",t[1][0]-t[0][0]).attr("height",t[1][1]-t[0][1])})),e.selectAll(".selection").data([yi("selection")]).enter().append("rect").attr("class","selection").attr("cursor",hi.selection).attr("fill","#777").attr("fill-opacity",.3).attr("stroke","#fff").attr("shape-rendering","crispEdges");var r=e.selectAll(".handle").data(t.handles,(function(t){return t.type}));r.exit().remove(),r.enter().append("rect").attr("class",(function(t){return"handle handle--"+t.type})).attr("cursor",(function(t){return hi[t.type]})),e.each(u).attr("fill","none").attr("pointer-events","all").on("mousedown.brush",f).filter(i).on("touchstart.brush",f).on("touchmove.brush",d).on("touchend.brush touchcancel.brush",p).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function u(){var t=ke(this),e=xi(this).selection;e?(t.selectAll(".selection").style("display",null).attr("x",e[0][0]).attr("y",e[0][1]).attr("width",e[1][0]-e[0][0]).attr("height",e[1][1]-e[0][1]),t.selectAll(".handle").style("display",null).attr("x",(function(t){return"e"===t.type[t.type.length-1]?e[1][0]-s/2:e[0][0]-s/2})).attr("y",(function(t){return"s"===t.type[0]?e[1][1]-s/2:e[0][1]-s/2})).attr("width",(function(t){return"n"===t.type||"s"===t.type?e[1][0]-e[0][0]+s:s})).attr("height",(function(t){return"e"===t.type||"w"===t.type?e[1][1]-e[0][1]+s:s}))):t.selectAll(".selection,.handle").style("display","none").attr("x",null).attr("y",null).attr("width",null).attr("height",null)}function l(t,e,n){return!n&&t.__brush.emitter||new h(t,e)}function h(t,e){this.that=t,this.args=e,this.state=t.__brush,this.active=0}function f(){if((!e||ce.touches)&&r.apply(this,arguments)){var n,i,o,s,c,h,f,d,p,g,y,v=this,m=ce.target.__data__.type,b="selection"===(a&&ce.metaKey?m="overlay":m)?ei:a&&ce.altKey?ii:ri,x=t===ui?null:pi[m],_=t===ci?null:gi[m],k=xi(v),w=k.extent,E=k.selection,T=w[0][0],C=w[0][1],A=w[1][0],S=w[1][1],M=0,O=0,D=x&&_&&a&&ce.shiftKey,N=ce.touches?si(ce.changedTouches[0].identifier):Nn,B=N(v),L=B,F=l(v,arguments,!0).beforestart();"overlay"===m?(E&&(p=!0),k.selection=E=[[n=t===ui?T:B[0],o=t===ci?C:B[1]],[c=t===ui?A:n,f=t===ci?S:o]]):(n=E[0][0],o=E[0][1],c=E[1][0],f=E[1][1]),i=n,s=o,h=c,d=f;var P=ke(v).attr("pointer-events","none"),I=P.selectAll(".overlay").attr("cursor",hi[m]);if(ce.touches)F.moved=R,F.ended=z;else{var j=ke(ce.view).on("mousemove.brush",R,!0).on("mouseup.brush",z,!0);a&&j.on("keydown.brush",U,!0).on("keyup.brush",$,!0),Te(ce.view)}Qr(),or(v),u.call(v),F.start()}function R(){var t=N(v);!D||g||y||(Math.abs(t[0]-L[0])>Math.abs(t[1]-L[1])?y=!0:g=!0),L=t,p=!0,ti(),Y()}function Y(){var t;switch(M=L[0]-B[0],O=L[1]-B[1],b){case ni:case ei:x&&(M=Math.max(T-n,Math.min(A-c,M)),i=n+M,h=c+M),_&&(O=Math.max(C-o,Math.min(S-f,O)),s=o+O,d=f+O);break;case ri:x<0?(M=Math.max(T-n,Math.min(A-n,M)),i=n+M,h=c):x>0&&(M=Math.max(T-c,Math.min(A-c,M)),i=n,h=c+M),_<0?(O=Math.max(C-o,Math.min(S-o,O)),s=o+O,d=f):_>0&&(O=Math.max(C-f,Math.min(S-f,O)),s=o,d=f+O);break;case ii:x&&(i=Math.max(T,Math.min(A,n-M*x)),h=Math.max(T,Math.min(A,c+M*x))),_&&(s=Math.max(C,Math.min(S,o-O*_)),d=Math.max(C,Math.min(S,f+O*_)))}h<i&&(x*=-1,t=n,n=c,c=t,t=i,i=h,h=t,m in fi&&I.attr("cursor",hi[m=fi[m]])),d<s&&(_*=-1,t=o,o=f,f=t,t=s,s=d,d=t,m in di&&I.attr("cursor",hi[m=di[m]])),k.selection&&(E=k.selection),g&&(i=E[0][0],h=E[1][0]),y&&(s=E[0][1],d=E[1][1]),E[0][0]===i&&E[0][1]===s&&E[1][0]===h&&E[1][1]===d||(k.selection=[[i,s],[h,d]],u.call(v),F.brush())}function z(){if(Qr(),ce.touches){if(ce.touches.length)return;e&&clearTimeout(e),e=setTimeout((function(){e=null}),500)}else Ce(ce.view,p),j.on("keydown.brush keyup.brush mousemove.brush mouseup.brush",null);P.attr("pointer-events","all"),I.attr("cursor",hi.overlay),k.selection&&(E=k.selection),_i(E)&&(k.selection=null,u.call(v)),F.end()}function U(){switch(ce.keyCode){case 16:D=x&&_;break;case 18:b===ri&&(x&&(c=h-M*x,n=i+M*x),_&&(f=d-O*_,o=s+O*_),b=ii,Y());break;case 32:b!==ri&&b!==ii||(x<0?c=h-M:x>0&&(n=i-M),_<0?f=d-O:_>0&&(o=s-O),b=ni,I.attr("cursor",hi.selection),Y());break;default:return}ti()}function $(){switch(ce.keyCode){case 16:D&&(g=y=D=!1,Y());break;case 18:b===ii&&(x<0?c=h:x>0&&(n=i),_<0?f=d:_>0&&(o=s),b=ri,Y());break;case 32:b===ni&&(ce.altKey?(x&&(c=h-M*x,n=i+M*x),_&&(f=d-O*_,o=s+O*_),b=ii):(x<0?c=h:x>0&&(n=i),_<0?f=d:_>0&&(o=s),b=ri),I.attr("cursor",hi[m]),Y());break;default:return}ti()}}function d(){l(this,arguments).moved()}function p(){l(this,arguments).ended()}function g(){var e=this.__brush||{selection:null};return e.extent=oi(n.apply(this,arguments)),e.dim=t,e}return c.move=function(e,n){e.selection?e.on("start.brush",(function(){l(this,arguments).beforestart().start()})).on("interrupt.brush end.brush",(function(){l(this,arguments).end()})).tween("brush",(function(){var e=this,r=e.__brush,i=l(e,arguments),a=r.selection,o=t.input("function"==typeof n?n.apply(this,arguments):n,r.extent),s=Sn(a,o);function c(t){r.selection=1===t&&null===o?null:s(t),u.call(e),i.brush()}return null!==a&&null!==o?c:c(1)})):e.each((function(){var e=this,r=arguments,i=e.__brush,a=t.input("function"==typeof n?n.apply(e,r):n,i.extent),o=l(e,r).beforestart();or(e),i.selection=null===a?null:a,u.call(e),o.start().brush().end()}))},c.clear=function(t){c.move(t,null)},h.prototype={beforestart:function(){return 1==++this.active&&(this.state.emitter=this,this.starting=!0),this},start:function(){return this.starting?(this.starting=!1,this.emit("start")):this.emit("brush"),this},brush:function(){return this.emit("brush"),this},end:function(){return 0==--this.active&&(delete this.state.emitter,this.emit("end")),this},emit:function(e){pe(new Kr(c,e,t.output(this.state.selection)),o.apply,o,[e,this.that,this.args])}},c.extent=function(t){return arguments.length?(n="function"==typeof t?t:Jr(oi(t)),c):n},c.filter=function(t){return arguments.length?(r="function"==typeof t?t:Jr(!!t),c):r},c.touchable=function(t){return arguments.length?(i="function"==typeof t?t:Jr(!!t),c):i},c.handleSize=function(t){return arguments.length?(s=+t,c):s},c.keyModifiers=function(t){return arguments.length?(a=!!t,c):a},c.on=function(){var t=o.on.apply(o,arguments);return t===o?c:t},c}var Ai=Math.cos,Si=Math.sin,Mi=Math.PI,Oi=Mi/2,Di=2*Mi,Ni=Math.max;function Bi(t){return function(e,n){return t(e.source.value+e.target.value,n.source.value+n.target.value)}}var Li=function(){var t=0,e=null,n=null,r=null;function i(i){var a,o,s,c,u,l,h=i.length,f=[],d=k(h),p=[],g=[],y=g.groups=new Array(h),v=new Array(h*h);for(a=0,u=-1;++u<h;){for(o=0,l=-1;++l<h;)o+=i[u][l];f.push(o),p.push(k(h)),a+=o}for(e&&d.sort((function(t,n){return e(f[t],f[n])})),n&&p.forEach((function(t,e){t.sort((function(t,r){return n(i[e][t],i[e][r])}))})),c=(a=Ni(0,Di-t*h)/a)?t:Di/h,o=0,u=-1;++u<h;){for(s=o,l=-1;++l<h;){var m=d[u],b=p[m][l],x=i[m][b],_=o,w=o+=x*a;v[b*h+m]={index:m,subindex:b,startAngle:_,endAngle:w,value:x}}y[m]={index:m,startAngle:s,endAngle:o,value:f[m]},o+=c}for(u=-1;++u<h;)for(l=u-1;++l<h;){var E=v[l*h+u],T=v[u*h+l];(E.value||T.value)&&g.push(E.value<T.value?{source:T,target:E}:{source:E,target:T})}return r?g.sort(r):g}return i.padAngle=function(e){return arguments.length?(t=Ni(0,e),i):t},i.sortGroups=function(t){return arguments.length?(e=t,i):e},i.sortSubgroups=function(t){return arguments.length?(n=t,i):n},i.sortChords=function(t){return arguments.length?(null==t?r=null:(r=Bi(t))._=t,i):r&&r._},i},Fi=Array.prototype.slice,Pi=function(t){return function(){return t}},Ii=Math.PI,ji=2*Ii,Ri=ji-1e-6;function Yi(){this._x0=this._y0=this._x1=this._y1=null,this._=""}function zi(){return new Yi}Yi.prototype=zi.prototype={constructor:Yi,moveTo:function(t,e){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)},closePath:function(){null!==this._x1&&(this._x1=this._x0,this._y1=this._y0,this._+="Z")},lineTo:function(t,e){this._+="L"+(this._x1=+t)+","+(this._y1=+e)},quadraticCurveTo:function(t,e,n,r){this._+="Q"+ +t+","+ +e+","+(this._x1=+n)+","+(this._y1=+r)},bezierCurveTo:function(t,e,n,r,i,a){this._+="C"+ +t+","+ +e+","+ +n+","+ +r+","+(this._x1=+i)+","+(this._y1=+a)},arcTo:function(t,e,n,r,i){t=+t,e=+e,n=+n,r=+r,i=+i;var a=this._x1,o=this._y1,s=n-t,c=r-e,u=a-t,l=o-e,h=u*u+l*l;if(i<0)throw new Error("negative radius: "+i);if(null===this._x1)this._+="M"+(this._x1=t)+","+(this._y1=e);else if(h>1e-6)if(Math.abs(l*s-c*u)>1e-6&&i){var f=n-a,d=r-o,p=s*s+c*c,g=f*f+d*d,y=Math.sqrt(p),v=Math.sqrt(h),m=i*Math.tan((Ii-Math.acos((p+h-g)/(2*y*v)))/2),b=m/v,x=m/y;Math.abs(b-1)>1e-6&&(this._+="L"+(t+b*u)+","+(e+b*l)),this._+="A"+i+","+i+",0,0,"+ +(l*f>u*d)+","+(this._x1=t+x*s)+","+(this._y1=e+x*c)}else this._+="L"+(this._x1=t)+","+(this._y1=e);else;},arc:function(t,e,n,r,i,a){t=+t,e=+e,a=!!a;var o=(n=+n)*Math.cos(r),s=n*Math.sin(r),c=t+o,u=e+s,l=1^a,h=a?r-i:i-r;if(n<0)throw new Error("negative radius: "+n);null===this._x1?this._+="M"+c+","+u:(Math.abs(this._x1-c)>1e-6||Math.abs(this._y1-u)>1e-6)&&(this._+="L"+c+","+u),n&&(h<0&&(h=h%ji+ji),h>Ri?this._+="A"+n+","+n+",0,1,"+l+","+(t-o)+","+(e-s)+"A"+n+","+n+",0,1,"+l+","+(this._x1=c)+","+(this._y1=u):h>1e-6&&(this._+="A"+n+","+n+",0,"+ +(h>=Ii)+","+l+","+(this._x1=t+n*Math.cos(i))+","+(this._y1=e+n*Math.sin(i))))},rect:function(t,e,n,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)+"h"+ +n+"v"+ +r+"h"+-n+"Z"},toString:function(){return this._}};var Ui=zi;function $i(t){return t.source}function Wi(t){return t.target}function Hi(t){return t.radius}function Vi(t){return t.startAngle}function Gi(t){return t.endAngle}var qi=function(){var t=$i,e=Wi,n=Hi,r=Vi,i=Gi,a=null;function o(){var o,s=Fi.call(arguments),c=t.apply(this,s),u=e.apply(this,s),l=+n.apply(this,(s[0]=c,s)),h=r.apply(this,s)-Oi,f=i.apply(this,s)-Oi,d=l*Ai(h),p=l*Si(h),g=+n.apply(this,(s[0]=u,s)),y=r.apply(this,s)-Oi,v=i.apply(this,s)-Oi;if(a||(a=o=Ui()),a.moveTo(d,p),a.arc(0,0,l,h,f),h===y&&f===v||(a.quadraticCurveTo(0,0,g*Ai(y),g*Si(y)),a.arc(0,0,g,y,v)),a.quadraticCurveTo(0,0,d,p),a.closePath(),o)return a=null,o+""||null}return o.radius=function(t){return arguments.length?(n="function"==typeof t?t:Pi(+t),o):n},o.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:Pi(+t),o):r},o.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:Pi(+t),o):i},o.source=function(e){return arguments.length?(t=e,o):t},o.target=function(t){return arguments.length?(e=t,o):e},o.context=function(t){return arguments.length?(a=null==t?null:t,o):a},o};function Xi(){}function Zi(t,e){var n=new Xi;if(t instanceof Xi)t.each((function(t,e){n.set(e,t)}));else if(Array.isArray(t)){var r,i=-1,a=t.length;if(null==e)for(;++i<a;)n.set(i,t[i]);else for(;++i<a;)n.set(e(r=t[i],i,t),r)}else if(t)for(var o in t)n.set(o,t[o]);return n}Xi.prototype=Zi.prototype={constructor:Xi,has:function(t){return"$"+t in this},get:function(t){return this["$"+t]},set:function(t,e){return this["$"+t]=e,this},remove:function(t){var e="$"+t;return e in this&&delete this[e]},clear:function(){for(var t in this)"$"===t[0]&&delete this[t]},keys:function(){var t=[];for(var e in this)"$"===e[0]&&t.push(e.slice(1));return t},values:function(){var t=[];for(var e in this)"$"===e[0]&&t.push(this[e]);return t},entries:function(){var t=[];for(var e in this)"$"===e[0]&&t.push({key:e.slice(1),value:this[e]});return t},size:function(){var t=0;for(var e in this)"$"===e[0]&&++t;return t},empty:function(){for(var t in this)if("$"===t[0])return!1;return!0},each:function(t){for(var e in this)"$"===e[0]&&t(this[e],e.slice(1),this)}};var Ji=Zi,Ki=function(){var t,e,n,r=[],i=[];function a(n,i,o,s){if(i>=r.length)return null!=t&&n.sort(t),null!=e?e(n):n;for(var c,u,l,h=-1,f=n.length,d=r[i++],p=Ji(),g=o();++h<f;)(l=p.get(c=d(u=n[h])+""))?l.push(u):p.set(c,[u]);return p.each((function(t,e){s(g,e,a(t,i,o,s))})),g}return n={object:function(t){return a(t,0,Qi,ta)},map:function(t){return a(t,0,ea,na)},entries:function(t){return function t(n,a){if(++a>r.length)return n;var o,s=i[a-1];return null!=e&&a>=r.length?o=n.entries():(o=[],n.each((function(e,n){o.push({key:n,values:t(e,a)})}))),null!=s?o.sort((function(t,e){return s(t.key,e.key)})):o}(a(t,0,ea,na),0)},key:function(t){return r.push(t),n},sortKeys:function(t){return i[r.length-1]=t,n},sortValues:function(e){return t=e,n},rollup:function(t){return e=t,n}}};function Qi(){return{}}function ta(t,e,n){t[e]=n}function ea(){return Ji()}function na(t,e,n){t.set(e,n)}function ra(){}var ia=Ji.prototype;function aa(t,e){var n=new ra;if(t instanceof ra)t.each((function(t){n.add(t)}));else if(t){var r=-1,i=t.length;if(null==e)for(;++r<i;)n.add(t[r]);else for(;++r<i;)n.add(e(t[r],r,t))}return n}ra.prototype=aa.prototype={constructor:ra,has:ia.has,add:function(t){return this["$"+(t+="")]=t,this},remove:ia.remove,clear:ia.clear,values:ia.keys,size:ia.size,empty:ia.empty,each:ia.each};var oa=aa,sa=function(t){var e=[];for(var n in t)e.push(n);return e},ca=function(t){var e=[];for(var n in t)e.push(t[n]);return e},ua=function(t){var e=[];for(var n in t)e.push({key:n,value:t[n]});return e},la=Math.PI/180,ha=180/Math.PI;function fa(t){if(t instanceof ga)return new ga(t.l,t.a,t.b,t.opacity);if(t instanceof wa)return Ea(t);t instanceof qe||(t=Ve(t));var e,n,r=ba(t.r),i=ba(t.g),a=ba(t.b),o=ya((.2225045*r+.7168786*i+.0606169*a)/1);return r===i&&i===a?e=n=o:(e=ya((.4360747*r+.3850649*i+.1430804*a)/.96422),n=ya((.0139322*r+.0971045*i+.7141733*a)/.82521)),new ga(116*o-16,500*(e-o),200*(o-n),t.opacity)}function da(t,e){return new ga(t,0,0,null==e?1:e)}function pa(t,e,n,r){return 1===arguments.length?fa(t):new ga(t,e,n,null==r?1:r)}function ga(t,e,n,r){this.l=+t,this.a=+e,this.b=+n,this.opacity=+r}function ya(t){return t>6/29*(6/29)*(6/29)?Math.pow(t,1/3):t/(6/29*3*(6/29))+4/29}function va(t){return t>6/29?t*t*t:6/29*3*(6/29)*(t-4/29)}function ma(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function ba(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function xa(t){if(t instanceof wa)return new wa(t.h,t.c,t.l,t.opacity);if(t instanceof ga||(t=fa(t)),0===t.a&&0===t.b)return new wa(NaN,0<t.l&&t.l<100?0:NaN,t.l,t.opacity);var e=Math.atan2(t.b,t.a)*ha;return new wa(e<0?e+360:e,Math.sqrt(t.a*t.a+t.b*t.b),t.l,t.opacity)}function _a(t,e,n,r){return 1===arguments.length?xa(t):new wa(n,e,t,null==r?1:r)}function ka(t,e,n,r){return 1===arguments.length?xa(t):new wa(t,e,n,null==r?1:r)}function wa(t,e,n,r){this.h=+t,this.c=+e,this.l=+n,this.opacity=+r}function Ea(t){if(isNaN(t.h))return new ga(t.l,0,0,t.opacity);var e=t.h*la;return new ga(t.l,Math.cos(e)*t.c,Math.sin(e)*t.c,t.opacity)}Ae(ga,pa,Se(Me,{brighter:function(t){return new ga(this.l+18*(null==t?1:t),this.a,this.b,this.opacity)},darker:function(t){return new ga(this.l-18*(null==t?1:t),this.a,this.b,this.opacity)},rgb:function(){var t=(this.l+16)/116,e=isNaN(this.a)?t:t+this.a/500,n=isNaN(this.b)?t:t-this.b/200;return new qe(ma(3.1338561*(e=.96422*va(e))-1.6168667*(t=1*va(t))-.4906146*(n=.82521*va(n))),ma(-.9787684*e+1.9161415*t+.033454*n),ma(.0719453*e-.2289914*t+1.4052427*n),this.opacity)}})),Ae(wa,ka,Se(Me,{brighter:function(t){return new wa(this.h,this.c,this.l+18*(null==t?1:t),this.opacity)},darker:function(t){return new wa(this.h,this.c,this.l-18*(null==t?1:t),this.opacity)},rgb:function(){return Ea(this).rgb()}}));var Ta=-.29227,Ca=-1.7884503806,Aa=3.5172982438,Sa=-.6557636667999999;function Ma(t){if(t instanceof Da)return new Da(t.h,t.s,t.l,t.opacity);t instanceof qe||(t=Ve(t));var e=t.r/255,n=t.g/255,r=t.b/255,i=(Sa*r+Ca*e-Aa*n)/(Sa+Ca-Aa),a=r-i,o=(1.97294*(n-i)-Ta*a)/-.90649,s=Math.sqrt(o*o+a*a)/(1.97294*i*(1-i)),c=s?Math.atan2(o,a)*ha-120:NaN;return new Da(c<0?c+360:c,s,i,t.opacity)}function Oa(t,e,n,r){return 1===arguments.length?Ma(t):new Da(t,e,n,null==r?1:r)}function Da(t,e,n,r){this.h=+t,this.s=+e,this.l=+n,this.opacity=+r}Ae(Da,Oa,Se(Me,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new Da(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new Da(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=isNaN(this.h)?0:(this.h+120)*la,e=+this.l,n=isNaN(this.s)?0:this.s*e*(1-e),r=Math.cos(t),i=Math.sin(t);return new qe(255*(e+n*(-.14861*r+1.78277*i)),255*(e+n*(Ta*r+-.90649*i)),255*(e+n*(1.97294*r)),this.opacity)}}));var Na=Array.prototype.slice,Ba=function(t,e){return t-e},La=function(t){return function(){return t}},Fa=function(t,e){for(var n,r=-1,i=e.length;++r<i;)if(n=Pa(t,e[r]))return n;return 0};function Pa(t,e){for(var n=e[0],r=e[1],i=-1,a=0,o=t.length,s=o-1;a<o;s=a++){var c=t[a],u=c[0],l=c[1],h=t[s],f=h[0],d=h[1];if(Ia(c,h,e))return 0;l>r!=d>r&&n<(f-u)*(r-l)/(d-l)+u&&(i=-i)}return i}function Ia(t,e,n){var r,i,a,o;return function(t,e,n){return(e[0]-t[0])*(n[1]-t[1])==(n[0]-t[0])*(e[1]-t[1])}(t,e,n)&&(i=t[r=+(t[0]===e[0])],a=n[r],o=e[r],i<=a&&a<=o||o<=a&&a<=i)}var ja=function(){},Ra=[[],[[[1,1.5],[.5,1]]],[[[1.5,1],[1,1.5]]],[[[1.5,1],[.5,1]]],[[[1,.5],[1.5,1]]],[[[1,1.5],[.5,1]],[[1,.5],[1.5,1]]],[[[1,.5],[1,1.5]]],[[[1,.5],[.5,1]]],[[[.5,1],[1,.5]]],[[[1,1.5],[1,.5]]],[[[.5,1],[1,.5]],[[1.5,1],[1,1.5]]],[[[1.5,1],[1,.5]]],[[[.5,1],[1.5,1]]],[[[1,1.5],[1.5,1]]],[[[.5,1],[1,1.5]]],[]],Ya=function(){var t=1,e=1,n=M,r=s;function i(t){var e=n(t);if(Array.isArray(e))e=e.slice().sort(Ba);else{var r=y(t),i=r[0],o=r[1];e=S(i,o,e),e=k(Math.floor(i/e)*e,Math.floor(o/e)*e,e)}return e.map((function(e){return a(t,e)}))}function a(n,i){var a=[],s=[];return function(n,r,i){var a,s,c,u,l,h,f=new Array,d=new Array;a=s=-1,u=n[0]>=r,Ra[u<<1].forEach(p);for(;++a<t-1;)c=u,u=n[a+1]>=r,Ra[c|u<<1].forEach(p);Ra[u<<0].forEach(p);for(;++s<e-1;){for(a=-1,u=n[s*t+t]>=r,l=n[s*t]>=r,Ra[u<<1|l<<2].forEach(p);++a<t-1;)c=u,u=n[s*t+t+a+1]>=r,h=l,l=n[s*t+a+1]>=r,Ra[c|u<<1|l<<2|h<<3].forEach(p);Ra[u|l<<3].forEach(p)}a=-1,l=n[s*t]>=r,Ra[l<<2].forEach(p);for(;++a<t-1;)h=l,l=n[s*t+a+1]>=r,Ra[l<<2|h<<3].forEach(p);function p(t){var e,n,r=[t[0][0]+a,t[0][1]+s],c=[t[1][0]+a,t[1][1]+s],u=o(r),l=o(c);(e=d[u])?(n=f[l])?(delete d[e.end],delete f[n.start],e===n?(e.ring.push(c),i(e.ring)):f[e.start]=d[n.end]={start:e.start,end:n.end,ring:e.ring.concat(n.ring)}):(delete d[e.end],e.ring.push(c),d[e.end=l]=e):(e=f[l])?(n=d[u])?(delete f[e.start],delete d[n.end],e===n?(e.ring.push(c),i(e.ring)):f[n.start]=d[e.end]={start:n.start,end:e.end,ring:n.ring.concat(e.ring)}):(delete f[e.start],e.ring.unshift(r),f[e.start=u]=e):f[u]=d[l]={start:u,end:l,ring:[r,c]}}Ra[l<<3].forEach(p)}(n,i,(function(t){r(t,n,i),function(t){for(var e=0,n=t.length,r=t[n-1][1]*t[0][0]-t[n-1][0]*t[0][1];++e<n;)r+=t[e-1][1]*t[e][0]-t[e-1][0]*t[e][1];return r}(t)>0?a.push([t]):s.push(t)})),s.forEach((function(t){for(var e,n=0,r=a.length;n<r;++n)if(-1!==Fa((e=a[n])[0],t))return void e.push(t)})),{type:"MultiPolygon",value:i,coordinates:a}}function o(e){return 2*e[0]+e[1]*(t+1)*4}function s(n,r,i){n.forEach((function(n){var a,o=n[0],s=n[1],c=0|o,u=0|s,l=r[u*t+c];o>0&&o<t&&c===o&&(a=r[u*t+c-1],n[0]=o+(i-a)/(l-a)-.5),s>0&&s<e&&u===s&&(a=r[(u-1)*t+c],n[1]=s+(i-a)/(l-a)-.5)}))}return i.contour=a,i.size=function(n){if(!arguments.length)return[t,e];var r=Math.ceil(n[0]),a=Math.ceil(n[1]);if(!(r>0&&a>0))throw new Error("invalid size");return t=r,e=a,i},i.thresholds=function(t){return arguments.length?(n="function"==typeof t?t:Array.isArray(t)?La(Na.call(t)):La(t),i):n},i.smooth=function(t){return arguments.length?(r=t?s:ja,i):r===s},i};function za(t,e,n){for(var r=t.width,i=t.height,a=1+(n<<1),o=0;o<i;++o)for(var s=0,c=0;s<r+n;++s)s<r&&(c+=t.data[s+o*r]),s>=n&&(s>=a&&(c-=t.data[s-a+o*r]),e.data[s-n+o*r]=c/Math.min(s+1,r-1+a-s,a))}function Ua(t,e,n){for(var r=t.width,i=t.height,a=1+(n<<1),o=0;o<r;++o)for(var s=0,c=0;s<i+n;++s)s<i&&(c+=t.data[o+s*r]),s>=n&&(s>=a&&(c-=t.data[o+(s-a)*r]),e.data[o+(s-n)*r]=c/Math.min(s+1,i-1+a-s,a))}function $a(t){return t[0]}function Wa(t){return t[1]}function Ha(){return 1}var Va=function(){var t=$a,e=Wa,n=Ha,r=960,i=500,a=20,o=2,s=3*a,c=r+2*s>>o,u=i+2*s>>o,l=La(20);function h(r){var i=new Float32Array(c*u),h=new Float32Array(c*u);r.forEach((function(r,a,l){var h=+t(r,a,l)+s>>o,f=+e(r,a,l)+s>>o,d=+n(r,a,l);h>=0&&h<c&&f>=0&&f<u&&(i[h+f*c]+=d)})),za({width:c,height:u,data:i},{width:c,height:u,data:h},a>>o),Ua({width:c,height:u,data:h},{width:c,height:u,data:i},a>>o),za({width:c,height:u,data:i},{width:c,height:u,data:h},a>>o),Ua({width:c,height:u,data:h},{width:c,height:u,data:i},a>>o),za({width:c,height:u,data:i},{width:c,height:u,data:h},a>>o),Ua({width:c,height:u,data:h},{width:c,height:u,data:i},a>>o);var d=l(i);if(!Array.isArray(d)){var p=L(i);d=S(0,p,d),(d=k(0,Math.floor(p/d)*d,d)).shift()}return Ya().thresholds(d).size([c,u])(i).map(f)}function f(t){return t.value*=Math.pow(2,-2*o),t.coordinates.forEach(d),t}function d(t){t.forEach(p)}function p(t){t.forEach(g)}function g(t){t[0]=t[0]*Math.pow(2,o)-s,t[1]=t[1]*Math.pow(2,o)-s}function y(){return c=r+2*(s=3*a)>>o,u=i+2*s>>o,h}return h.x=function(e){return arguments.length?(t="function"==typeof e?e:La(+e),h):t},h.y=function(t){return arguments.length?(e="function"==typeof t?t:La(+t),h):e},h.weight=function(t){return arguments.length?(n="function"==typeof t?t:La(+t),h):n},h.size=function(t){if(!arguments.length)return[r,i];var e=Math.ceil(t[0]),n=Math.ceil(t[1]);if(!(e>=0||e>=0))throw new Error("invalid size");return r=e,i=n,y()},h.cellSize=function(t){if(!arguments.length)return 1<<o;if(!((t=+t)>=1))throw new Error("invalid cell size");return o=Math.floor(Math.log(t)/Math.LN2),y()},h.thresholds=function(t){return arguments.length?(l="function"==typeof t?t:Array.isArray(t)?La(Na.call(t)):La(t),h):l},h.bandwidth=function(t){if(!arguments.length)return Math.sqrt(a*(a+1));if(!((t=+t)>=0))throw new Error("invalid bandwidth");return a=Math.round((Math.sqrt(4*t*t+1)-1)/2),y()},h},Ga=function(t){return function(){return t}};function qa(t,e,n,r,i,a,o,s,c,u){this.target=t,this.type=e,this.subject=n,this.identifier=r,this.active=i,this.x=a,this.y=o,this.dx=s,this.dy=c,this._=u}function Xa(){return!ce.ctrlKey&&!ce.button}function Za(){return this.parentNode}function Ja(t){return null==t?{x:ce.x,y:ce.y}:t}function Ka(){return navigator.maxTouchPoints||"ontouchstart"in this}qa.prototype.on=function(){var t=this._.on.apply(this._,arguments);return t===this._?this:t};var Qa=function(){var t,e,n,r,i=Xa,a=Za,o=Ja,s=Ka,c={},u=lt("start","drag","end"),l=0,h=0;function f(t){t.on("mousedown.drag",d).filter(s).on("touchstart.drag",y).on("touchmove.drag",v).on("touchend.drag touchcancel.drag",m).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function d(){if(!r&&i.apply(this,arguments)){var o=b("mouse",a.apply(this,arguments),Nn,this,arguments);o&&(ke(ce.view).on("mousemove.drag",p,!0).on("mouseup.drag",g,!0),Te(ce.view),we(),n=!1,t=ce.clientX,e=ce.clientY,o("start"))}}function p(){if(Ee(),!n){var r=ce.clientX-t,i=ce.clientY-e;n=r*r+i*i>h}c.mouse("drag")}function g(){ke(ce.view).on("mousemove.drag mouseup.drag",null),Ce(ce.view,n),Ee(),c.mouse("end")}function y(){if(i.apply(this,arguments)){var t,e,n=ce.changedTouches,r=a.apply(this,arguments),o=n.length;for(t=0;t<o;++t)(e=b(n[t].identifier,r,Dn,this,arguments))&&(we(),e("start"))}}function v(){var t,e,n=ce.changedTouches,r=n.length;for(t=0;t<r;++t)(e=c[n[t].identifier])&&(Ee(),e("drag"))}function m(){var t,e,n=ce.changedTouches,i=n.length;for(r&&clearTimeout(r),r=setTimeout((function(){r=null}),500),t=0;t<i;++t)(e=c[n[t].identifier])&&(we(),e("end"))}function b(t,e,n,r,i){var a,s,h,d=n(e,t),p=u.copy();if(pe(new qa(f,"beforestart",a,t,l,d[0],d[1],0,0,p),(function(){return null!=(ce.subject=a=o.apply(r,i))&&(s=a.x-d[0]||0,h=a.y-d[1]||0,!0)})))return function o(u){var g,y=d;switch(u){case"start":c[t]=o,g=l++;break;case"end":delete c[t],--l;case"drag":d=n(e,t),g=l}pe(new qa(f,u,a,t,g,d[0]+s,d[1]+h,d[0]-y[0],d[1]-y[1],p),p.apply,p,[u,r,i])}}return f.filter=function(t){return arguments.length?(i="function"==typeof t?t:Ga(!!t),f):i},f.container=function(t){return arguments.length?(a="function"==typeof t?t:Ga(t),f):a},f.subject=function(t){return arguments.length?(o="function"==typeof t?t:Ga(t),f):o},f.touchable=function(t){return arguments.length?(s="function"==typeof t?t:Ga(!!t),f):s},f.on=function(){var t=u.on.apply(u,arguments);return t===u?f:t},f.clickDistance=function(t){return arguments.length?(h=(t=+t)*t,f):Math.sqrt(h)},f},to={},eo={};function no(t){return new Function("d","return {"+t.map((function(t,e){return JSON.stringify(t)+": d["+e+'] || ""'})).join(",")+"}")}function ro(t){var e=Object.create(null),n=[];return t.forEach((function(t){for(var r in t)r in e||n.push(e[r]=r)})),n}function io(t,e){var n=t+"",r=n.length;return r<e?new Array(e-r+1).join(0)+n:n}function ao(t){var e,n=t.getUTCHours(),r=t.getUTCMinutes(),i=t.getUTCSeconds(),a=t.getUTCMilliseconds();return isNaN(t)?"Invalid Date":((e=t.getUTCFullYear())<0?"-"+io(-e,6):e>9999?"+"+io(e,6):io(e,4))+"-"+io(t.getUTCMonth()+1,2)+"-"+io(t.getUTCDate(),2)+(a?"T"+io(n,2)+":"+io(r,2)+":"+io(i,2)+"."+io(a,3)+"Z":i?"T"+io(n,2)+":"+io(r,2)+":"+io(i,2)+"Z":r||n?"T"+io(n,2)+":"+io(r,2)+"Z":"")}var oo=function(t){var e=new RegExp('["'+t+"\n\r]"),n=t.charCodeAt(0);function r(t,e){var r,i=[],a=t.length,o=0,s=0,c=a<=0,u=!1;function l(){if(c)return eo;if(u)return u=!1,to;var e,r,i=o;if(34===t.charCodeAt(i)){for(;o++<a&&34!==t.charCodeAt(o)||34===t.charCodeAt(++o););return(e=o)>=a?c=!0:10===(r=t.charCodeAt(o++))?u=!0:13===r&&(u=!0,10===t.charCodeAt(o)&&++o),t.slice(i+1,e-1).replace(/""/g,'"')}for(;o<a;){if(10===(r=t.charCodeAt(e=o++)))u=!0;else if(13===r)u=!0,10===t.charCodeAt(o)&&++o;else if(r!==n)continue;return t.slice(i,e)}return c=!0,t.slice(i,a)}for(10===t.charCodeAt(a-1)&&--a,13===t.charCodeAt(a-1)&&--a;(r=l())!==eo;){for(var h=[];r!==to&&r!==eo;)h.push(r),r=l();e&&null==(h=e(h,s++))||i.push(h)}return i}function i(e,n){return e.map((function(e){return n.map((function(t){return o(e[t])})).join(t)}))}function a(e){return e.map(o).join(t)}function o(t){return null==t?"":t instanceof Date?ao(t):e.test(t+="")?'"'+t.replace(/"/g,'""')+'"':t}return{parse:function(t,e){var n,i,a=r(t,(function(t,r){if(n)return n(t,r-1);i=t,n=e?function(t,e){var n=no(t);return function(r,i){return e(n(r),i,t)}}(t,e):no(t)}));return a.columns=i||[],a},parseRows:r,format:function(e,n){return null==n&&(n=ro(e)),[n.map(o).join(t)].concat(i(e,n)).join("\n")},formatBody:function(t,e){return null==e&&(e=ro(t)),i(t,e).join("\n")},formatRows:function(t){return t.map(a).join("\n")},formatRow:a,formatValue:o}},so=oo(","),co=so.parse,uo=so.parseRows,lo=so.format,ho=so.formatBody,fo=so.formatRows,po=so.formatRow,go=so.formatValue,yo=oo("\t"),vo=yo.parse,mo=yo.parseRows,bo=yo.format,xo=yo.formatBody,_o=yo.formatRows,ko=yo.formatRow,wo=yo.formatValue;function Eo(t){for(var e in t){var n,r,i=t[e].trim();if(i)if("true"===i)i=!0;else if("false"===i)i=!1;else if("NaN"===i)i=NaN;else if(isNaN(n=+i)){if(!(r=i.match(/^([-+]\d{2})?\d{4}(-\d{2}(-\d{2})?)?(T\d{2}:\d{2}(:\d{2}(\.\d{3})?)?(Z|[-+]\d{2}:\d{2})?)?$/)))continue;To&&r[4]&&!r[7]&&(i=i.replace(/-/g,"/").replace(/T/," ")),i=new Date(i)}else i=n;else i=null;t[e]=i}return t}var To=new Date("2019-01-01T00:00").getHours()||new Date("2019-07-01T00:00").getHours();function Co(t){return+t}function Ao(t){return t*t}function So(t){return t*(2-t)}function Mo(t){return((t*=2)<=1?t*t:--t*(2-t)+1)/2}var Oo=function t(e){function n(t){return Math.pow(t,e)}return e=+e,n.exponent=t,n}(3),Do=function t(e){function n(t){return 1-Math.pow(1-t,e)}return e=+e,n.exponent=t,n}(3),No=function t(e){function n(t){return((t*=2)<=1?Math.pow(t,e):2-Math.pow(2-t,e))/2}return e=+e,n.exponent=t,n}(3),Bo=Math.PI,Lo=Bo/2;function Fo(t){return 1-Math.cos(t*Lo)}function Po(t){return Math.sin(t*Lo)}function Io(t){return(1-Math.cos(Bo*t))/2}function jo(t){return Math.pow(2,10*t-10)}function Ro(t){return 1-Math.pow(2,-10*t)}function Yo(t){return((t*=2)<=1?Math.pow(2,10*t-10):2-Math.pow(2,10-10*t))/2}function zo(t){return 1-Math.sqrt(1-t*t)}function Uo(t){return Math.sqrt(1- --t*t)}function $o(t){return((t*=2)<=1?1-Math.sqrt(1-t*t):Math.sqrt(1-(t-=2)*t)+1)/2}function Wo(t){return 1-Ho(1-t)}function Ho(t){return(t=+t)<4/11?7.5625*t*t:t<8/11?7.5625*(t-=6/11)*t+.75:t<10/11?7.5625*(t-=9/11)*t+.9375:7.5625*(t-=21/22)*t+63/64}function Vo(t){return((t*=2)<=1?1-Ho(1-t):Ho(t-1)+1)/2}var Go=function t(e){function n(t){return t*t*((e+1)*t-e)}return e=+e,n.overshoot=t,n}(1.70158),qo=function t(e){function n(t){return--t*t*((e+1)*t+e)+1}return e=+e,n.overshoot=t,n}(1.70158),Xo=function t(e){function n(t){return((t*=2)<1?t*t*((e+1)*t-e):(t-=2)*t*((e+1)*t+e)+2)/2}return e=+e,n.overshoot=t,n}(1.70158),Zo=2*Math.PI,Jo=function t(e,n){var r=Math.asin(1/(e=Math.max(1,e)))*(n/=Zo);function i(t){return e*Math.pow(2,10*--t)*Math.sin((r-t)/n)}return i.amplitude=function(e){return t(e,n*Zo)},i.period=function(n){return t(e,n)},i}(1,.3),Ko=function t(e,n){var r=Math.asin(1/(e=Math.max(1,e)))*(n/=Zo);function i(t){return 1-e*Math.pow(2,-10*(t=+t))*Math.sin((t+r)/n)}return i.amplitude=function(e){return t(e,n*Zo)},i.period=function(n){return t(e,n)},i}(1,.3),Qo=function t(e,n){var r=Math.asin(1/(e=Math.max(1,e)))*(n/=Zo);function i(t){return((t=2*t-1)<0?e*Math.pow(2,10*t)*Math.sin((r-t)/n):2-e*Math.pow(2,-10*t)*Math.sin((r+t)/n))/2}return i.amplitude=function(e){return t(e,n*Zo)},i.period=function(n){return t(e,n)},i}(1,.3);function ts(t){if(!t.ok)throw new Error(t.status+" "+t.statusText);return t.blob()}var es=function(t,e){return fetch(t,e).then(ts)};function ns(t){if(!t.ok)throw new Error(t.status+" "+t.statusText);return t.arrayBuffer()}var rs=function(t,e){return fetch(t,e).then(ns)};function is(t){if(!t.ok)throw new Error(t.status+" "+t.statusText);return t.text()}var as=function(t,e){return fetch(t,e).then(is)};function os(t){return function(e,n,r){return 2===arguments.length&&"function"==typeof n&&(r=n,n=void 0),as(e,n).then((function(e){return t(e,r)}))}}function ss(t,e,n,r){3===arguments.length&&"function"==typeof n&&(r=n,n=void 0);var i=oo(t);return as(e,n).then((function(t){return i.parse(t,r)}))}var cs=os(co),us=os(vo),ls=function(t,e){return new Promise((function(n,r){var i=new Image;for(var a in e)i[a]=e[a];i.onerror=r,i.onload=function(){n(i)},i.src=t}))};function hs(t){if(!t.ok)throw new Error(t.status+" "+t.statusText);return t.json()}var fs=function(t,e){return fetch(t,e).then(hs)};function ds(t){return function(e,n){return as(e,n).then((function(e){return(new DOMParser).parseFromString(e,t)}))}}var ps=ds("application/xml"),gs=ds("text/html"),ys=ds("image/svg+xml"),vs=function(t,e){var n;function r(){var r,i,a=n.length,o=0,s=0;for(r=0;r<a;++r)o+=(i=n[r]).x,s+=i.y;for(o=o/a-t,s=s/a-e,r=0;r<a;++r)(i=n[r]).x-=o,i.y-=s}return null==t&&(t=0),null==e&&(e=0),r.initialize=function(t){n=t},r.x=function(e){return arguments.length?(t=+e,r):t},r.y=function(t){return arguments.length?(e=+t,r):e},r},ms=function(t){return function(){return t}},bs=function(){return 1e-6*(Math.random()-.5)};function xs(t,e,n,r){if(isNaN(e)||isNaN(n))return t;var i,a,o,s,c,u,l,h,f,d=t._root,p={data:r},g=t._x0,y=t._y0,v=t._x1,m=t._y1;if(!d)return t._root=p,t;for(;d.length;)if((u=e>=(a=(g+v)/2))?g=a:v=a,(l=n>=(o=(y+m)/2))?y=o:m=o,i=d,!(d=d[h=l<<1|u]))return i[h]=p,t;if(s=+t._x.call(null,d.data),c=+t._y.call(null,d.data),e===s&&n===c)return p.next=d,i?i[h]=p:t._root=p,t;do{i=i?i[h]=new Array(4):t._root=new Array(4),(u=e>=(a=(g+v)/2))?g=a:v=a,(l=n>=(o=(y+m)/2))?y=o:m=o}while((h=l<<1|u)==(f=(c>=o)<<1|s>=a));return i[f]=d,i[h]=p,t}var _s=function(t,e,n,r,i){this.node=t,this.x0=e,this.y0=n,this.x1=r,this.y1=i};function ks(t){return t[0]}function ws(t){return t[1]}function Es(t,e,n){var r=new Ts(null==e?ks:e,null==n?ws:n,NaN,NaN,NaN,NaN);return null==t?r:r.addAll(t)}function Ts(t,e,n,r,i,a){this._x=t,this._y=e,this._x0=n,this._y0=r,this._x1=i,this._y1=a,this._root=void 0}function Cs(t){for(var e={data:t.data},n=e;t=t.next;)n=n.next={data:t.data};return e}var As=Es.prototype=Ts.prototype;function Ss(t){return t.x+t.vx}function Ms(t){return t.y+t.vy}As.copy=function(){var t,e,n=new Ts(this._x,this._y,this._x0,this._y0,this._x1,this._y1),r=this._root;if(!r)return n;if(!r.length)return n._root=Cs(r),n;for(t=[{source:r,target:n._root=new Array(4)}];r=t.pop();)for(var i=0;i<4;++i)(e=r.source[i])&&(e.length?t.push({source:e,target:r.target[i]=new Array(4)}):r.target[i]=Cs(e));return n},As.add=function(t){var e=+this._x.call(null,t),n=+this._y.call(null,t);return xs(this.cover(e,n),e,n,t)},As.addAll=function(t){var e,n,r,i,a=t.length,o=new Array(a),s=new Array(a),c=1/0,u=1/0,l=-1/0,h=-1/0;for(n=0;n<a;++n)isNaN(r=+this._x.call(null,e=t[n]))||isNaN(i=+this._y.call(null,e))||(o[n]=r,s[n]=i,r<c&&(c=r),r>l&&(l=r),i<u&&(u=i),i>h&&(h=i));if(c>l||u>h)return this;for(this.cover(c,u).cover(l,h),n=0;n<a;++n)xs(this,o[n],s[n],t[n]);return this},As.cover=function(t,e){if(isNaN(t=+t)||isNaN(e=+e))return this;var n=this._x0,r=this._y0,i=this._x1,a=this._y1;if(isNaN(n))i=(n=Math.floor(t))+1,a=(r=Math.floor(e))+1;else{for(var o,s,c=i-n,u=this._root;n>t||t>=i||r>e||e>=a;)switch(s=(e<r)<<1|t<n,(o=new Array(4))[s]=u,u=o,c*=2,s){case 0:i=n+c,a=r+c;break;case 1:n=i-c,a=r+c;break;case 2:i=n+c,r=a-c;break;case 3:n=i-c,r=a-c}this._root&&this._root.length&&(this._root=u)}return this._x0=n,this._y0=r,this._x1=i,this._y1=a,this},As.data=function(){var t=[];return this.visit((function(e){if(!e.length)do{t.push(e.data)}while(e=e.next)})),t},As.extent=function(t){return arguments.length?this.cover(+t[0][0],+t[0][1]).cover(+t[1][0],+t[1][1]):isNaN(this._x0)?void 0:[[this._x0,this._y0],[this._x1,this._y1]]},As.find=function(t,e,n){var r,i,a,o,s,c,u,l=this._x0,h=this._y0,f=this._x1,d=this._y1,p=[],g=this._root;for(g&&p.push(new _s(g,l,h,f,d)),null==n?n=1/0:(l=t-n,h=e-n,f=t+n,d=e+n,n*=n);c=p.pop();)if(!(!(g=c.node)||(i=c.x0)>f||(a=c.y0)>d||(o=c.x1)<l||(s=c.y1)<h))if(g.length){var y=(i+o)/2,v=(a+s)/2;p.push(new _s(g[3],y,v,o,s),new _s(g[2],i,v,y,s),new _s(g[1],y,a,o,v),new _s(g[0],i,a,y,v)),(u=(e>=v)<<1|t>=y)&&(c=p[p.length-1],p[p.length-1]=p[p.length-1-u],p[p.length-1-u]=c)}else{var m=t-+this._x.call(null,g.data),b=e-+this._y.call(null,g.data),x=m*m+b*b;if(x<n){var _=Math.sqrt(n=x);l=t-_,h=e-_,f=t+_,d=e+_,r=g.data}}return r},As.remove=function(t){if(isNaN(a=+this._x.call(null,t))||isNaN(o=+this._y.call(null,t)))return this;var e,n,r,i,a,o,s,c,u,l,h,f,d=this._root,p=this._x0,g=this._y0,y=this._x1,v=this._y1;if(!d)return this;if(d.length)for(;;){if((u=a>=(s=(p+y)/2))?p=s:y=s,(l=o>=(c=(g+v)/2))?g=c:v=c,e=d,!(d=d[h=l<<1|u]))return this;if(!d.length)break;(e[h+1&3]||e[h+2&3]||e[h+3&3])&&(n=e,f=h)}for(;d.data!==t;)if(r=d,!(d=d.next))return this;return(i=d.next)&&delete d.next,r?(i?r.next=i:delete r.next,this):e?(i?e[h]=i:delete e[h],(d=e[0]||e[1]||e[2]||e[3])&&d===(e[3]||e[2]||e[1]||e[0])&&!d.length&&(n?n[f]=d:this._root=d),this):(this._root=i,this)},As.removeAll=function(t){for(var e=0,n=t.length;e<n;++e)this.remove(t[e]);return this},As.root=function(){return this._root},As.size=function(){var t=0;return this.visit((function(e){if(!e.length)do{++t}while(e=e.next)})),t},As.visit=function(t){var e,n,r,i,a,o,s=[],c=this._root;for(c&&s.push(new _s(c,this._x0,this._y0,this._x1,this._y1));e=s.pop();)if(!t(c=e.node,r=e.x0,i=e.y0,a=e.x1,o=e.y1)&&c.length){var u=(r+a)/2,l=(i+o)/2;(n=c[3])&&s.push(new _s(n,u,l,a,o)),(n=c[2])&&s.push(new _s(n,r,l,u,o)),(n=c[1])&&s.push(new _s(n,u,i,a,l)),(n=c[0])&&s.push(new _s(n,r,i,u,l))}return this},As.visitAfter=function(t){var e,n=[],r=[];for(this._root&&n.push(new _s(this._root,this._x0,this._y0,this._x1,this._y1));e=n.pop();){var i=e.node;if(i.length){var a,o=e.x0,s=e.y0,c=e.x1,u=e.y1,l=(o+c)/2,h=(s+u)/2;(a=i[0])&&n.push(new _s(a,o,s,l,h)),(a=i[1])&&n.push(new _s(a,l,s,c,h)),(a=i[2])&&n.push(new _s(a,o,h,l,u)),(a=i[3])&&n.push(new _s(a,l,h,c,u))}r.push(e)}for(;e=r.pop();)t(e.node,e.x0,e.y0,e.x1,e.y1);return this},As.x=function(t){return arguments.length?(this._x=t,this):this._x},As.y=function(t){return arguments.length?(this._y=t,this):this._y};var Os=function(t){var e,n,r=1,i=1;function a(){for(var t,a,s,c,u,l,h,f=e.length,d=0;d<i;++d)for(a=Es(e,Ss,Ms).visitAfter(o),t=0;t<f;++t)s=e[t],l=n[s.index],h=l*l,c=s.x+s.vx,u=s.y+s.vy,a.visit(p);function p(t,e,n,i,a){var o=t.data,f=t.r,d=l+f;if(!o)return e>c+d||i<c-d||n>u+d||a<u-d;if(o.index>s.index){var p=c-o.x-o.vx,g=u-o.y-o.vy,y=p*p+g*g;y<d*d&&(0===p&&(y+=(p=bs())*p),0===g&&(y+=(g=bs())*g),y=(d-(y=Math.sqrt(y)))/y*r,s.vx+=(p*=y)*(d=(f*=f)/(h+f)),s.vy+=(g*=y)*d,o.vx-=p*(d=1-d),o.vy-=g*d)}}}function o(t){if(t.data)return t.r=n[t.data.index];for(var e=t.r=0;e<4;++e)t[e]&&t[e].r>t.r&&(t.r=t[e].r)}function s(){if(e){var r,i,a=e.length;for(n=new Array(a),r=0;r<a;++r)i=e[r],n[i.index]=+t(i,r,e)}}return"function"!=typeof t&&(t=ms(null==t?1:+t)),a.initialize=function(t){e=t,s()},a.iterations=function(t){return arguments.length?(i=+t,a):i},a.strength=function(t){return arguments.length?(r=+t,a):r},a.radius=function(e){return arguments.length?(t="function"==typeof e?e:ms(+e),s(),a):t},a};function Ds(t){return t.index}function Ns(t,e){var n=t.get(e);if(!n)throw new Error("missing: "+e);return n}var Bs=function(t){var e,n,r,i,a,o=Ds,s=function(t){return 1/Math.min(i[t.source.index],i[t.target.index])},c=ms(30),u=1;function l(r){for(var i=0,o=t.length;i<u;++i)for(var s,c,l,h,f,d,p,g=0;g<o;++g)c=(s=t[g]).source,h=(l=s.target).x+l.vx-c.x-c.vx||bs(),f=l.y+l.vy-c.y-c.vy||bs(),h*=d=((d=Math.sqrt(h*h+f*f))-n[g])/d*r*e[g],f*=d,l.vx-=h*(p=a[g]),l.vy-=f*p,c.vx+=h*(p=1-p),c.vy+=f*p}function h(){if(r){var s,c,u=r.length,l=t.length,h=Ji(r,o);for(s=0,i=new Array(u);s<l;++s)(c=t[s]).index=s,"object"!=typeof c.source&&(c.source=Ns(h,c.source)),"object"!=typeof c.target&&(c.target=Ns(h,c.target)),i[c.source.index]=(i[c.source.index]||0)+1,i[c.target.index]=(i[c.target.index]||0)+1;for(s=0,a=new Array(l);s<l;++s)c=t[s],a[s]=i[c.source.index]/(i[c.source.index]+i[c.target.index]);e=new Array(l),f(),n=new Array(l),d()}}function f(){if(r)for(var n=0,i=t.length;n<i;++n)e[n]=+s(t[n],n,t)}function d(){if(r)for(var e=0,i=t.length;e<i;++e)n[e]=+c(t[e],e,t)}return null==t&&(t=[]),l.initialize=function(t){r=t,h()},l.links=function(e){return arguments.length?(t=e,h(),l):t},l.id=function(t){return arguments.length?(o=t,l):o},l.iterations=function(t){return arguments.length?(u=+t,l):u},l.strength=function(t){return arguments.length?(s="function"==typeof t?t:ms(+t),f(),l):s},l.distance=function(t){return arguments.length?(c="function"==typeof t?t:ms(+t),d(),l):c},l};function Ls(t){return t.x}function Fs(t){return t.y}var Ps=Math.PI*(3-Math.sqrt(5)),Is=function(t){var e,n=1,r=.001,i=1-Math.pow(r,1/300),a=0,o=.6,s=Ji(),c=Wn(l),u=lt("tick","end");function l(){h(),u.call("tick",e),n<r&&(c.stop(),u.call("end",e))}function h(r){var c,u,l=t.length;void 0===r&&(r=1);for(var h=0;h<r;++h)for(n+=(a-n)*i,s.each((function(t){t(n)})),c=0;c<l;++c)null==(u=t[c]).fx?u.x+=u.vx*=o:(u.x=u.fx,u.vx=0),null==u.fy?u.y+=u.vy*=o:(u.y=u.fy,u.vy=0);return e}function f(){for(var e,n=0,r=t.length;n<r;++n){if((e=t[n]).index=n,null!=e.fx&&(e.x=e.fx),null!=e.fy&&(e.y=e.fy),isNaN(e.x)||isNaN(e.y)){var i=10*Math.sqrt(n),a=n*Ps;e.x=i*Math.cos(a),e.y=i*Math.sin(a)}(isNaN(e.vx)||isNaN(e.vy))&&(e.vx=e.vy=0)}}function d(e){return e.initialize&&e.initialize(t),e}return null==t&&(t=[]),f(),e={tick:h,restart:function(){return c.restart(l),e},stop:function(){return c.stop(),e},nodes:function(n){return arguments.length?(t=n,f(),s.each(d),e):t},alpha:function(t){return arguments.length?(n=+t,e):n},alphaMin:function(t){return arguments.length?(r=+t,e):r},alphaDecay:function(t){return arguments.length?(i=+t,e):+i},alphaTarget:function(t){return arguments.length?(a=+t,e):a},velocityDecay:function(t){return arguments.length?(o=1-t,e):1-o},force:function(t,n){return arguments.length>1?(null==n?s.remove(t):s.set(t,d(n)),e):s.get(t)},find:function(e,n,r){var i,a,o,s,c,u=0,l=t.length;for(null==r?r=1/0:r*=r,u=0;u<l;++u)(o=(i=e-(s=t[u]).x)*i+(a=n-s.y)*a)<r&&(c=s,r=o);return c},on:function(t,n){return arguments.length>1?(u.on(t,n),e):u.on(t)}}},js=function(){var t,e,n,r,i=ms(-30),a=1,o=1/0,s=.81;function c(r){var i,a=t.length,o=Es(t,Ls,Fs).visitAfter(l);for(n=r,i=0;i<a;++i)e=t[i],o.visit(h)}function u(){if(t){var e,n,a=t.length;for(r=new Array(a),e=0;e<a;++e)n=t[e],r[n.index]=+i(n,e,t)}}function l(t){var e,n,i,a,o,s=0,c=0;if(t.length){for(i=a=o=0;o<4;++o)(e=t[o])&&(n=Math.abs(e.value))&&(s+=e.value,c+=n,i+=n*e.x,a+=n*e.y);t.x=i/c,t.y=a/c}else{(e=t).x=e.data.x,e.y=e.data.y;do{s+=r[e.data.index]}while(e=e.next)}t.value=s}function h(t,i,c,u){if(!t.value)return!0;var l=t.x-e.x,h=t.y-e.y,f=u-i,d=l*l+h*h;if(f*f/s<d)return d<o&&(0===l&&(d+=(l=bs())*l),0===h&&(d+=(h=bs())*h),d<a&&(d=Math.sqrt(a*d)),e.vx+=l*t.value*n/d,e.vy+=h*t.value*n/d),!0;if(!(t.length||d>=o)){(t.data!==e||t.next)&&(0===l&&(d+=(l=bs())*l),0===h&&(d+=(h=bs())*h),d<a&&(d=Math.sqrt(a*d)));do{t.data!==e&&(f=r[t.data.index]*n/d,e.vx+=l*f,e.vy+=h*f)}while(t=t.next)}}return c.initialize=function(e){t=e,u()},c.strength=function(t){return arguments.length?(i="function"==typeof t?t:ms(+t),u(),c):i},c.distanceMin=function(t){return arguments.length?(a=t*t,c):Math.sqrt(a)},c.distanceMax=function(t){return arguments.length?(o=t*t,c):Math.sqrt(o)},c.theta=function(t){return arguments.length?(s=t*t,c):Math.sqrt(s)},c},Rs=function(t,e,n){var r,i,a,o=ms(.1);function s(t){for(var o=0,s=r.length;o<s;++o){var c=r[o],u=c.x-e||1e-6,l=c.y-n||1e-6,h=Math.sqrt(u*u+l*l),f=(a[o]-h)*i[o]*t/h;c.vx+=u*f,c.vy+=l*f}}function c(){if(r){var e,n=r.length;for(i=new Array(n),a=new Array(n),e=0;e<n;++e)a[e]=+t(r[e],e,r),i[e]=isNaN(a[e])?0:+o(r[e],e,r)}}return"function"!=typeof t&&(t=ms(+t)),null==e&&(e=0),null==n&&(n=0),s.initialize=function(t){r=t,c()},s.strength=function(t){return arguments.length?(o="function"==typeof t?t:ms(+t),c(),s):o},s.radius=function(e){return arguments.length?(t="function"==typeof e?e:ms(+e),c(),s):t},s.x=function(t){return arguments.length?(e=+t,s):e},s.y=function(t){return arguments.length?(n=+t,s):n},s},Ys=function(t){var e,n,r,i=ms(.1);function a(t){for(var i,a=0,o=e.length;a<o;++a)(i=e[a]).vx+=(r[a]-i.x)*n[a]*t}function o(){if(e){var a,o=e.length;for(n=new Array(o),r=new Array(o),a=0;a<o;++a)n[a]=isNaN(r[a]=+t(e[a],a,e))?0:+i(e[a],a,e)}}return"function"!=typeof t&&(t=ms(null==t?0:+t)),a.initialize=function(t){e=t,o()},a.strength=function(t){return arguments.length?(i="function"==typeof t?t:ms(+t),o(),a):i},a.x=function(e){return arguments.length?(t="function"==typeof e?e:ms(+e),o(),a):t},a},zs=function(t){var e,n,r,i=ms(.1);function a(t){for(var i,a=0,o=e.length;a<o;++a)(i=e[a]).vy+=(r[a]-i.y)*n[a]*t}function o(){if(e){var a,o=e.length;for(n=new Array(o),r=new Array(o),a=0;a<o;++a)n[a]=isNaN(r[a]=+t(e[a],a,e))?0:+i(e[a],a,e)}}return"function"!=typeof t&&(t=ms(null==t?0:+t)),a.initialize=function(t){e=t,o()},a.strength=function(t){return arguments.length?(i="function"==typeof t?t:ms(+t),o(),a):i},a.y=function(e){return arguments.length?(t="function"==typeof e?e:ms(+e),o(),a):t},a},Us=function(t,e){if((n=(t=e?t.toExponential(e-1):t.toExponential()).indexOf("e"))<0)return null;var n,r=t.slice(0,n);return[r.length>1?r[0]+r.slice(2):r,+t.slice(n+1)]},$s=function(t){return(t=Us(Math.abs(t)))?t[1]:NaN},Ws=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Hs(t){if(!(e=Ws.exec(t)))throw new Error("invalid format: "+t);var e;return new Vs({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}function Vs(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}Hs.prototype=Vs.prototype,Vs.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var Gs,qs,Xs,Zs,Js=function(t,e){var n=Us(t,e);if(!n)return t+"";var r=n[0],i=n[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")},Ks={"%":function(t,e){return(100*t).toFixed(e)},b:function(t){return Math.round(t).toString(2)},c:function(t){return t+""},d:function(t){return Math.round(t).toString(10)},e:function(t,e){return t.toExponential(e)},f:function(t,e){return t.toFixed(e)},g:function(t,e){return t.toPrecision(e)},o:function(t){return Math.round(t).toString(8)},p:function(t,e){return Js(100*t,e)},r:Js,s:function(t,e){var n=Us(t,e);if(!n)return t+"";var r=n[0],i=n[1],a=i-(Gs=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,o=r.length;return a===o?r:a>o?r+new Array(a-o+1).join("0"):a>0?r.slice(0,a)+"."+r.slice(a):"0."+new Array(1-a).join("0")+Us(t,Math.max(0,e+a-1))[0]},X:function(t){return Math.round(t).toString(16).toUpperCase()},x:function(t){return Math.round(t).toString(16)}},Qs=function(t){return t},tc=Array.prototype.map,ec=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"],nc=function(t){var e,n,r=void 0===t.grouping||void 0===t.thousands?Qs:(e=tc.call(t.grouping,Number),n=t.thousands+"",function(t,r){for(var i=t.length,a=[],o=0,s=e[0],c=0;i>0&&s>0&&(c+s+1>r&&(s=Math.max(1,r-c)),a.push(t.substring(i-=s,i+s)),!((c+=s+1)>r));)s=e[o=(o+1)%e.length];return a.reverse().join(n)}),i=void 0===t.currency?"":t.currency[0]+"",a=void 0===t.currency?"":t.currency[1]+"",o=void 0===t.decimal?".":t.decimal+"",s=void 0===t.numerals?Qs:function(t){return function(e){return e.replace(/[0-9]/g,(function(e){return t[+e]}))}}(tc.call(t.numerals,String)),c=void 0===t.percent?"%":t.percent+"",u=void 0===t.minus?"-":t.minus+"",l=void 0===t.nan?"NaN":t.nan+"";function h(t){var e=(t=Hs(t)).fill,n=t.align,h=t.sign,f=t.symbol,d=t.zero,p=t.width,g=t.comma,y=t.precision,v=t.trim,m=t.type;"n"===m?(g=!0,m="g"):Ks[m]||(void 0===y&&(y=12),v=!0,m="g"),(d||"0"===e&&"="===n)&&(d=!0,e="0",n="=");var b="$"===f?i:"#"===f&&/[boxX]/.test(m)?"0"+m.toLowerCase():"",x="$"===f?a:/[%p]/.test(m)?c:"",_=Ks[m],k=/[defgprs%]/.test(m);function w(t){var i,a,c,f=b,w=x;if("c"===m)w=_(t)+w,t="";else{var E=(t=+t)<0;if(t=isNaN(t)?l:_(Math.abs(t),y),v&&(t=function(t){t:for(var e,n=t.length,r=1,i=-1;r<n;++r)switch(t[r]){case".":i=e=r;break;case"0":0===i&&(i=r),e=r;break;default:if(!+t[r])break t;i>0&&(i=0)}return i>0?t.slice(0,i)+t.slice(e+1):t}(t)),E&&0==+t&&(E=!1),f=(E?"("===h?h:u:"-"===h||"("===h?"":h)+f,w=("s"===m?ec[8+Gs/3]:"")+w+(E&&"("===h?")":""),k)for(i=-1,a=t.length;++i<a;)if(48>(c=t.charCodeAt(i))||c>57){w=(46===c?o+t.slice(i+1):t.slice(i))+w,t=t.slice(0,i);break}}g&&!d&&(t=r(t,1/0));var T=f.length+t.length+w.length,C=T<p?new Array(p-T+1).join(e):"";switch(g&&d&&(t=r(C+t,C.length?p-w.length:1/0),C=""),n){case"<":t=f+t+w+C;break;case"=":t=f+C+t+w;break;case"^":t=C.slice(0,T=C.length>>1)+f+t+w+C.slice(T);break;default:t=C+f+t+w}return s(t)}return y=void 0===y?6:/[gprs]/.test(m)?Math.max(1,Math.min(21,y)):Math.max(0,Math.min(20,y)),w.toString=function(){return t+""},w}return{format:h,formatPrefix:function(t,e){var n=h(((t=Hs(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor($s(e)/3))),i=Math.pow(10,-r),a=ec[8+r/3];return function(t){return n(i*t)+a}}}};function rc(t){return qs=nc(t),Xs=qs.format,Zs=qs.formatPrefix,qs}rc({decimal:".",thousands:",",grouping:[3],currency:["$",""],minus:"-"});var ic=function(t){return Math.max(0,-$s(Math.abs(t)))},ac=function(t,e){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor($s(e)/3)))-$s(Math.abs(t)))},oc=function(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,$s(e)-$s(t))+1},sc=function(){return new cc};function cc(){this.reset()}cc.prototype={constructor:cc,reset:function(){this.s=this.t=0},add:function(t){lc(uc,t,this.t),lc(this,uc.s,this.s),this.s?this.t+=uc.t:this.s=uc.t},valueOf:function(){return this.s}};var uc=new cc;function lc(t,e,n){var r=t.s=e+n,i=r-e,a=r-i;t.t=e-a+(n-i)}var hc=Math.PI,fc=hc/2,dc=hc/4,pc=2*hc,gc=180/hc,yc=hc/180,vc=Math.abs,mc=Math.atan,bc=Math.atan2,xc=Math.cos,_c=Math.ceil,kc=Math.exp,wc=(Math.floor,Math.log),Ec=Math.pow,Tc=Math.sin,Cc=Math.sign||function(t){return t>0?1:t<0?-1:0},Ac=Math.sqrt,Sc=Math.tan;function Mc(t){return t>1?0:t<-1?hc:Math.acos(t)}function Oc(t){return t>1?fc:t<-1?-fc:Math.asin(t)}function Dc(t){return(t=Tc(t/2))*t}function Nc(){}function Bc(t,e){t&&Fc.hasOwnProperty(t.type)&&Fc[t.type](t,e)}var Lc={Feature:function(t,e){Bc(t.geometry,e)},FeatureCollection:function(t,e){for(var n=t.features,r=-1,i=n.length;++r<i;)Bc(n[r].geometry,e)}},Fc={Sphere:function(t,e){e.sphere()},Point:function(t,e){t=t.coordinates,e.point(t[0],t[1],t[2])},MultiPoint:function(t,e){for(var n=t.coordinates,r=-1,i=n.length;++r<i;)t=n[r],e.point(t[0],t[1],t[2])},LineString:function(t,e){Pc(t.coordinates,e,0)},MultiLineString:function(t,e){for(var n=t.coordinates,r=-1,i=n.length;++r<i;)Pc(n[r],e,0)},Polygon:function(t,e){Ic(t.coordinates,e)},MultiPolygon:function(t,e){for(var n=t.coordinates,r=-1,i=n.length;++r<i;)Ic(n[r],e)},GeometryCollection:function(t,e){for(var n=t.geometries,r=-1,i=n.length;++r<i;)Bc(n[r],e)}};function Pc(t,e,n){var r,i=-1,a=t.length-n;for(e.lineStart();++i<a;)r=t[i],e.point(r[0],r[1],r[2]);e.lineEnd()}function Ic(t,e){var n=-1,r=t.length;for(e.polygonStart();++n<r;)Pc(t[n],e,1);e.polygonEnd()}var jc,Rc,Yc,zc,Uc,$c=function(t,e){t&&Lc.hasOwnProperty(t.type)?Lc[t.type](t,e):Bc(t,e)},Wc=sc(),Hc=sc(),Vc={point:Nc,lineStart:Nc,lineEnd:Nc,polygonStart:function(){Wc.reset(),Vc.lineStart=Gc,Vc.lineEnd=qc},polygonEnd:function(){var t=+Wc;Hc.add(t<0?pc+t:t),this.lineStart=this.lineEnd=this.point=Nc},sphere:function(){Hc.add(pc)}};function Gc(){Vc.point=Xc}function qc(){Zc(jc,Rc)}function Xc(t,e){Vc.point=Zc,jc=t,Rc=e,Yc=t*=yc,zc=xc(e=(e*=yc)/2+dc),Uc=Tc(e)}function Zc(t,e){var n=(t*=yc)-Yc,r=n>=0?1:-1,i=r*n,a=xc(e=(e*=yc)/2+dc),o=Tc(e),s=Uc*o,c=zc*a+s*xc(i),u=s*r*Tc(i);Wc.add(bc(u,c)),Yc=t,zc=a,Uc=o}var Jc=function(t){return Hc.reset(),$c(t,Vc),2*Hc};function Kc(t){return[bc(t[1],t[0]),Oc(t[2])]}function Qc(t){var e=t[0],n=t[1],r=xc(n);return[r*xc(e),r*Tc(e),Tc(n)]}function tu(t,e){return t[0]*e[0]+t[1]*e[1]+t[2]*e[2]}function eu(t,e){return[t[1]*e[2]-t[2]*e[1],t[2]*e[0]-t[0]*e[2],t[0]*e[1]-t[1]*e[0]]}function nu(t,e){t[0]+=e[0],t[1]+=e[1],t[2]+=e[2]}function ru(t,e){return[t[0]*e,t[1]*e,t[2]*e]}function iu(t){var e=Ac(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=e,t[1]/=e,t[2]/=e}var au,ou,su,cu,uu,lu,hu,fu,du,pu,gu=sc(),yu={point:vu,lineStart:bu,lineEnd:xu,polygonStart:function(){yu.point=_u,yu.lineStart=ku,yu.lineEnd=wu,gu.reset(),Vc.polygonStart()},polygonEnd:function(){Vc.polygonEnd(),yu.point=vu,yu.lineStart=bu,yu.lineEnd=xu,Wc<0?(au=-(su=180),ou=-(cu=90)):gu>1e-6?cu=90:gu<-1e-6&&(ou=-90),pu[0]=au,pu[1]=su},sphere:function(){au=-(su=180),ou=-(cu=90)}};function vu(t,e){du.push(pu=[au=t,su=t]),e<ou&&(ou=e),e>cu&&(cu=e)}function mu(t,e){var n=Qc([t*yc,e*yc]);if(fu){var r=eu(fu,n),i=eu([r[1],-r[0],0],r);iu(i),i=Kc(i);var a,o=t-uu,s=o>0?1:-1,c=i[0]*gc*s,u=vc(o)>180;u^(s*uu<c&&c<s*t)?(a=i[1]*gc)>cu&&(cu=a):u^(s*uu<(c=(c+360)%360-180)&&c<s*t)?(a=-i[1]*gc)<ou&&(ou=a):(e<ou&&(ou=e),e>cu&&(cu=e)),u?t<uu?Eu(au,t)>Eu(au,su)&&(su=t):Eu(t,su)>Eu(au,su)&&(au=t):su>=au?(t<au&&(au=t),t>su&&(su=t)):t>uu?Eu(au,t)>Eu(au,su)&&(su=t):Eu(t,su)>Eu(au,su)&&(au=t)}else du.push(pu=[au=t,su=t]);e<ou&&(ou=e),e>cu&&(cu=e),fu=n,uu=t}function bu(){yu.point=mu}function xu(){pu[0]=au,pu[1]=su,yu.point=vu,fu=null}function _u(t,e){if(fu){var n=t-uu;gu.add(vc(n)>180?n+(n>0?360:-360):n)}else lu=t,hu=e;Vc.point(t,e),mu(t,e)}function ku(){Vc.lineStart()}function wu(){_u(lu,hu),Vc.lineEnd(),vc(gu)>1e-6&&(au=-(su=180)),pu[0]=au,pu[1]=su,fu=null}function Eu(t,e){return(e-=t)<0?e+360:e}function Tu(t,e){return t[0]-e[0]}function Cu(t,e){return t[0]<=t[1]?t[0]<=e&&e<=t[1]:e<t[0]||t[1]<e}var Au,Su,Mu,Ou,Du,Nu,Bu,Lu,Fu,Pu,Iu,ju,Ru,Yu,zu,Uu,$u=function(t){var e,n,r,i,a,o,s;if(cu=su=-(au=ou=1/0),du=[],$c(t,yu),n=du.length){for(du.sort(Tu),e=1,a=[r=du[0]];e<n;++e)Cu(r,(i=du[e])[0])||Cu(r,i[1])?(Eu(r[0],i[1])>Eu(r[0],r[1])&&(r[1]=i[1]),Eu(i[0],r[1])>Eu(r[0],r[1])&&(r[0]=i[0])):a.push(r=i);for(o=-1/0,e=0,r=a[n=a.length-1];e<=n;r=i,++e)i=a[e],(s=Eu(r[1],i[0]))>o&&(o=s,au=i[0],su=r[1])}return du=pu=null,au===1/0||ou===1/0?[[NaN,NaN],[NaN,NaN]]:[[au,ou],[su,cu]]},Wu={sphere:Nc,point:Hu,lineStart:Gu,lineEnd:Zu,polygonStart:function(){Wu.lineStart=Ju,Wu.lineEnd=Ku},polygonEnd:function(){Wu.lineStart=Gu,Wu.lineEnd=Zu}};function Hu(t,e){t*=yc;var n=xc(e*=yc);Vu(n*xc(t),n*Tc(t),Tc(e))}function Vu(t,e,n){++Au,Mu+=(t-Mu)/Au,Ou+=(e-Ou)/Au,Du+=(n-Du)/Au}function Gu(){Wu.point=qu}function qu(t,e){t*=yc;var n=xc(e*=yc);Yu=n*xc(t),zu=n*Tc(t),Uu=Tc(e),Wu.point=Xu,Vu(Yu,zu,Uu)}function Xu(t,e){t*=yc;var n=xc(e*=yc),r=n*xc(t),i=n*Tc(t),a=Tc(e),o=bc(Ac((o=zu*a-Uu*i)*o+(o=Uu*r-Yu*a)*o+(o=Yu*i-zu*r)*o),Yu*r+zu*i+Uu*a);Su+=o,Nu+=o*(Yu+(Yu=r)),Bu+=o*(zu+(zu=i)),Lu+=o*(Uu+(Uu=a)),Vu(Yu,zu,Uu)}function Zu(){Wu.point=Hu}function Ju(){Wu.point=Qu}function Ku(){tl(ju,Ru),Wu.point=Hu}function Qu(t,e){ju=t,Ru=e,t*=yc,e*=yc,Wu.point=tl;var n=xc(e);Yu=n*xc(t),zu=n*Tc(t),Uu=Tc(e),Vu(Yu,zu,Uu)}function tl(t,e){t*=yc;var n=xc(e*=yc),r=n*xc(t),i=n*Tc(t),a=Tc(e),o=zu*a-Uu*i,s=Uu*r-Yu*a,c=Yu*i-zu*r,u=Ac(o*o+s*s+c*c),l=Oc(u),h=u&&-l/u;Fu+=h*o,Pu+=h*s,Iu+=h*c,Su+=l,Nu+=l*(Yu+(Yu=r)),Bu+=l*(zu+(zu=i)),Lu+=l*(Uu+(Uu=a)),Vu(Yu,zu,Uu)}var el=function(t){Au=Su=Mu=Ou=Du=Nu=Bu=Lu=Fu=Pu=Iu=0,$c(t,Wu);var e=Fu,n=Pu,r=Iu,i=e*e+n*n+r*r;return i<1e-12&&(e=Nu,n=Bu,r=Lu,Su<1e-6&&(e=Mu,n=Ou,r=Du),(i=e*e+n*n+r*r)<1e-12)?[NaN,NaN]:[bc(n,e)*gc,Oc(r/Ac(i))*gc]},nl=function(t){return function(){return t}},rl=function(t,e){function n(n,r){return n=t(n,r),e(n[0],n[1])}return t.invert&&e.invert&&(n.invert=function(n,r){return(n=e.invert(n,r))&&t.invert(n[0],n[1])}),n};function il(t,e){return[vc(t)>hc?t+Math.round(-t/pc)*pc:t,e]}function al(t,e,n){return(t%=pc)?e||n?rl(sl(t),cl(e,n)):sl(t):e||n?cl(e,n):il}function ol(t){return function(e,n){return[(e+=t)>hc?e-pc:e<-hc?e+pc:e,n]}}function sl(t){var e=ol(t);return e.invert=ol(-t),e}function cl(t,e){var n=xc(t),r=Tc(t),i=xc(e),a=Tc(e);function o(t,e){var o=xc(e),s=xc(t)*o,c=Tc(t)*o,u=Tc(e),l=u*n+s*r;return[bc(c*i-l*a,s*n-u*r),Oc(l*i+c*a)]}return o.invert=function(t,e){var o=xc(e),s=xc(t)*o,c=Tc(t)*o,u=Tc(e),l=u*i-c*a;return[bc(c*i+u*a,s*n+l*r),Oc(l*n-s*r)]},o}il.invert=il;var ul=function(t){function e(e){return(e=t(e[0]*yc,e[1]*yc))[0]*=gc,e[1]*=gc,e}return t=al(t[0]*yc,t[1]*yc,t.length>2?t[2]*yc:0),e.invert=function(e){return(e=t.invert(e[0]*yc,e[1]*yc))[0]*=gc,e[1]*=gc,e},e};function ll(t,e,n,r,i,a){if(n){var o=xc(e),s=Tc(e),c=r*n;null==i?(i=e+r*pc,a=e-c/2):(i=hl(o,i),a=hl(o,a),(r>0?i<a:i>a)&&(i+=r*pc));for(var u,l=i;r>0?l>a:l<a;l-=c)u=Kc([o,-s*xc(l),-s*Tc(l)]),t.point(u[0],u[1])}}function hl(t,e){(e=Qc(e))[0]-=t,iu(e);var n=Mc(-e[1]);return((-e[2]<0?-n:n)+pc-1e-6)%pc}var fl=function(){var t,e,n=nl([0,0]),r=nl(90),i=nl(6),a={point:function(n,r){t.push(n=e(n,r)),n[0]*=gc,n[1]*=gc}};function o(){var o=n.apply(this,arguments),s=r.apply(this,arguments)*yc,c=i.apply(this,arguments)*yc;return t=[],e=al(-o[0]*yc,-o[1]*yc,0).invert,ll(a,s,c,1),o={type:"Polygon",coordinates:[t]},t=e=null,o}return o.center=function(t){return arguments.length?(n="function"==typeof t?t:nl([+t[0],+t[1]]),o):n},o.radius=function(t){return arguments.length?(r="function"==typeof t?t:nl(+t),o):r},o.precision=function(t){return arguments.length?(i="function"==typeof t?t:nl(+t),o):i},o},dl=function(){var t,e=[];return{point:function(e,n){t.push([e,n])},lineStart:function(){e.push(t=[])},lineEnd:Nc,rejoin:function(){e.length>1&&e.push(e.pop().concat(e.shift()))},result:function(){var n=e;return e=[],t=null,n}}},pl=function(t,e){return vc(t[0]-e[0])<1e-6&&vc(t[1]-e[1])<1e-6};function gl(t,e,n,r){this.x=t,this.z=e,this.o=n,this.e=r,this.v=!1,this.n=this.p=null}var yl=function(t,e,n,r,i){var a,o,s=[],c=[];if(t.forEach((function(t){if(!((e=t.length-1)<=0)){var e,n,r=t[0],o=t[e];if(pl(r,o)){for(i.lineStart(),a=0;a<e;++a)i.point((r=t[a])[0],r[1]);i.lineEnd()}else s.push(n=new gl(r,t,null,!0)),c.push(n.o=new gl(r,null,n,!1)),s.push(n=new gl(o,t,null,!1)),c.push(n.o=new gl(o,null,n,!0))}})),s.length){for(c.sort(e),vl(s),vl(c),a=0,o=c.length;a<o;++a)c[a].e=n=!n;for(var u,l,h=s[0];;){for(var f=h,d=!0;f.v;)if((f=f.n)===h)return;u=f.z,i.lineStart();do{if(f.v=f.o.v=!0,f.e){if(d)for(a=0,o=u.length;a<o;++a)i.point((l=u[a])[0],l[1]);else r(f.x,f.n.x,1,i);f=f.n}else{if(d)for(u=f.p.z,a=u.length-1;a>=0;--a)i.point((l=u[a])[0],l[1]);else r(f.x,f.p.x,-1,i);f=f.p}u=(f=f.o).z,d=!d}while(!f.v);i.lineEnd()}}};function vl(t){if(e=t.length){for(var e,n,r=0,i=t[0];++r<e;)i.n=n=t[r],n.p=i,i=n;i.n=n=t[0],n.p=i}}var ml=sc();function bl(t){return vc(t[0])<=hc?t[0]:Cc(t[0])*((vc(t[0])+hc)%pc-hc)}var xl=function(t,e){var n=bl(e),r=e[1],i=Tc(r),a=[Tc(n),-xc(n),0],o=0,s=0;ml.reset(),1===i?r=fc+1e-6:-1===i&&(r=-fc-1e-6);for(var c=0,u=t.length;c<u;++c)if(h=(l=t[c]).length)for(var l,h,f=l[h-1],d=bl(f),p=f[1]/2+dc,g=Tc(p),y=xc(p),v=0;v<h;++v,d=b,g=_,y=k,f=m){var m=l[v],b=bl(m),x=m[1]/2+dc,_=Tc(x),k=xc(x),w=b-d,E=w>=0?1:-1,T=E*w,C=T>hc,A=g*_;if(ml.add(bc(A*E*Tc(T),y*k+A*xc(T))),o+=C?w+E*pc:w,C^d>=n^b>=n){var S=eu(Qc(f),Qc(m));iu(S);var M=eu(a,S);iu(M);var O=(C^w>=0?-1:1)*Oc(M[2]);(r>O||r===O&&(S[0]||S[1]))&&(s+=C^w>=0?1:-1)}}return(o<-1e-6||o<1e-6&&ml<-1e-6)^1&s},_l=function(t,e,n,r){return function(i){var a,o,s,c=e(i),u=dl(),l=e(u),h=!1,f={point:d,lineStart:g,lineEnd:y,polygonStart:function(){f.point=v,f.lineStart=m,f.lineEnd=b,o=[],a=[]},polygonEnd:function(){f.point=d,f.lineStart=g,f.lineEnd=y,o=I(o);var t=xl(a,r);o.length?(h||(i.polygonStart(),h=!0),yl(o,wl,t,n,i)):t&&(h||(i.polygonStart(),h=!0),i.lineStart(),n(null,null,1,i),i.lineEnd()),h&&(i.polygonEnd(),h=!1),o=a=null},sphere:function(){i.polygonStart(),i.lineStart(),n(null,null,1,i),i.lineEnd(),i.polygonEnd()}};function d(e,n){t(e,n)&&i.point(e,n)}function p(t,e){c.point(t,e)}function g(){f.point=p,c.lineStart()}function y(){f.point=d,c.lineEnd()}function v(t,e){s.push([t,e]),l.point(t,e)}function m(){l.lineStart(),s=[]}function b(){v(s[0][0],s[0][1]),l.lineEnd();var t,e,n,r,c=l.clean(),f=u.result(),d=f.length;if(s.pop(),a.push(s),s=null,d)if(1&c){if((e=(n=f[0]).length-1)>0){for(h||(i.polygonStart(),h=!0),i.lineStart(),t=0;t<e;++t)i.point((r=n[t])[0],r[1]);i.lineEnd()}}else d>1&&2&c&&f.push(f.pop().concat(f.shift())),o.push(f.filter(kl))}return f}};function kl(t){return t.length>1}function wl(t,e){return((t=t.x)[0]<0?t[1]-fc-1e-6:fc-t[1])-((e=e.x)[0]<0?e[1]-fc-1e-6:fc-e[1])}var El=_l((function(){return!0}),(function(t){var e,n=NaN,r=NaN,i=NaN;return{lineStart:function(){t.lineStart(),e=1},point:function(a,o){var s=a>0?hc:-hc,c=vc(a-n);vc(c-hc)<1e-6?(t.point(n,r=(r+o)/2>0?fc:-fc),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(s,r),t.point(a,r),e=0):i!==s&&c>=hc&&(vc(n-i)<1e-6&&(n-=1e-6*i),vc(a-s)<1e-6&&(a-=1e-6*s),r=function(t,e,n,r){var i,a,o=Tc(t-n);return vc(o)>1e-6?mc((Tc(e)*(a=xc(r))*Tc(n)-Tc(r)*(i=xc(e))*Tc(t))/(i*a*o)):(e+r)/2}(n,r,a,o),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(s,r),e=0),t.point(n=a,r=o),i=s},lineEnd:function(){t.lineEnd(),n=r=NaN},clean:function(){return 2-e}}}),(function(t,e,n,r){var i;if(null==t)i=n*fc,r.point(-hc,i),r.point(0,i),r.point(hc,i),r.point(hc,0),r.point(hc,-i),r.point(0,-i),r.point(-hc,-i),r.point(-hc,0),r.point(-hc,i);else if(vc(t[0]-e[0])>1e-6){var a=t[0]<e[0]?hc:-hc;i=n*a/2,r.point(-a,i),r.point(0,i),r.point(a,i)}else r.point(e[0],e[1])}),[-hc,-fc]);var Tl=function(t){var e=xc(t),n=6*yc,r=e>0,i=vc(e)>1e-6;function a(t,n){return xc(t)*xc(n)>e}function o(t,n,r){var i=[1,0,0],a=eu(Qc(t),Qc(n)),o=tu(a,a),s=a[0],c=o-s*s;if(!c)return!r&&t;var u=e*o/c,l=-e*s/c,h=eu(i,a),f=ru(i,u);nu(f,ru(a,l));var d=h,p=tu(f,d),g=tu(d,d),y=p*p-g*(tu(f,f)-1);if(!(y<0)){var v=Ac(y),m=ru(d,(-p-v)/g);if(nu(m,f),m=Kc(m),!r)return m;var b,x=t[0],_=n[0],k=t[1],w=n[1];_<x&&(b=x,x=_,_=b);var E=_-x,T=vc(E-hc)<1e-6;if(!T&&w<k&&(b=k,k=w,w=b),T||E<1e-6?T?k+w>0^m[1]<(vc(m[0]-x)<1e-6?k:w):k<=m[1]&&m[1]<=w:E>hc^(x<=m[0]&&m[0]<=_)){var C=ru(d,(-p+v)/g);return nu(C,f),[m,Kc(C)]}}}function s(e,n){var i=r?t:hc-t,a=0;return e<-i?a|=1:e>i&&(a|=2),n<-i?a|=4:n>i&&(a|=8),a}return _l(a,(function(t){var e,n,c,u,l;return{lineStart:function(){u=c=!1,l=1},point:function(h,f){var d,p=[h,f],g=a(h,f),y=r?g?0:s(h,f):g?s(h+(h<0?hc:-hc),f):0;if(!e&&(u=c=g)&&t.lineStart(),g!==c&&(!(d=o(e,p))||pl(e,d)||pl(p,d))&&(p[0]+=1e-6,p[1]+=1e-6,g=a(p[0],p[1])),g!==c)l=0,g?(t.lineStart(),d=o(p,e),t.point(d[0],d[1])):(d=o(e,p),t.point(d[0],d[1]),t.lineEnd()),e=d;else if(i&&e&&r^g){var v;y&n||!(v=o(p,e,!0))||(l=0,r?(t.lineStart(),t.point(v[0][0],v[0][1]),t.point(v[1][0],v[1][1]),t.lineEnd()):(t.point(v[1][0],v[1][1]),t.lineEnd(),t.lineStart(),t.point(v[0][0],v[0][1])))}!g||e&&pl(e,p)||t.point(p[0],p[1]),e=p,c=g,n=y},lineEnd:function(){c&&t.lineEnd(),e=null},clean:function(){return l|(u&&c)<<1}}}),(function(e,r,i,a){ll(a,t,n,i,e,r)}),r?[0,-t]:[-hc,t-hc])};function Cl(t,e,n,r){function i(i,a){return t<=i&&i<=n&&e<=a&&a<=r}function a(i,a,s,u){var l=0,h=0;if(null==i||(l=o(i,s))!==(h=o(a,s))||c(i,a)<0^s>0)do{u.point(0===l||3===l?t:n,l>1?r:e)}while((l=(l+s+4)%4)!==h);else u.point(a[0],a[1])}function o(r,i){return vc(r[0]-t)<1e-6?i>0?0:3:vc(r[0]-n)<1e-6?i>0?2:1:vc(r[1]-e)<1e-6?i>0?1:0:i>0?3:2}function s(t,e){return c(t.x,e.x)}function c(t,e){var n=o(t,1),r=o(e,1);return n!==r?n-r:0===n?e[1]-t[1]:1===n?t[0]-e[0]:2===n?t[1]-e[1]:e[0]-t[0]}return function(o){var c,u,l,h,f,d,p,g,y,v,m,b=o,x=dl(),_={point:k,lineStart:function(){_.point=w,u&&u.push(l=[]);v=!0,y=!1,p=g=NaN},lineEnd:function(){c&&(w(h,f),d&&y&&x.rejoin(),c.push(x.result()));_.point=k,y&&b.lineEnd()},polygonStart:function(){b=x,c=[],u=[],m=!0},polygonEnd:function(){var e=function(){for(var e=0,n=0,i=u.length;n<i;++n)for(var a,o,s=u[n],c=1,l=s.length,h=s[0],f=h[0],d=h[1];c<l;++c)a=f,o=d,h=s[c],f=h[0],d=h[1],o<=r?d>r&&(f-a)*(r-o)>(d-o)*(t-a)&&++e:d<=r&&(f-a)*(r-o)<(d-o)*(t-a)&&--e;return e}(),n=m&&e,i=(c=I(c)).length;(n||i)&&(o.polygonStart(),n&&(o.lineStart(),a(null,null,1,o),o.lineEnd()),i&&yl(c,s,e,a,o),o.polygonEnd());b=o,c=u=l=null}};function k(t,e){i(t,e)&&b.point(t,e)}function w(a,o){var s=i(a,o);if(u&&l.push([a,o]),v)h=a,f=o,d=s,v=!1,s&&(b.lineStart(),b.point(a,o));else if(s&&y)b.point(a,o);else{var c=[p=Math.max(-1e9,Math.min(1e9,p)),g=Math.max(-1e9,Math.min(1e9,g))],x=[a=Math.max(-1e9,Math.min(1e9,a)),o=Math.max(-1e9,Math.min(1e9,o))];!function(t,e,n,r,i,a){var o,s=t[0],c=t[1],u=0,l=1,h=e[0]-s,f=e[1]-c;if(o=n-s,h||!(o>0)){if(o/=h,h<0){if(o<u)return;o<l&&(l=o)}else if(h>0){if(o>l)return;o>u&&(u=o)}if(o=i-s,h||!(o<0)){if(o/=h,h<0){if(o>l)return;o>u&&(u=o)}else if(h>0){if(o<u)return;o<l&&(l=o)}if(o=r-c,f||!(o>0)){if(o/=f,f<0){if(o<u)return;o<l&&(l=o)}else if(f>0){if(o>l)return;o>u&&(u=o)}if(o=a-c,f||!(o<0)){if(o/=f,f<0){if(o>l)return;o>u&&(u=o)}else if(f>0){if(o<u)return;o<l&&(l=o)}return u>0&&(t[0]=s+u*h,t[1]=c+u*f),l<1&&(e[0]=s+l*h,e[1]=c+l*f),!0}}}}}(c,x,t,e,n,r)?s&&(b.lineStart(),b.point(a,o),m=!1):(y||(b.lineStart(),b.point(c[0],c[1])),b.point(x[0],x[1]),s||b.lineEnd(),m=!1)}p=a,g=o,y=s}return _}}var Al,Sl,Ml,Ol=function(){var t,e,n,r=0,i=0,a=960,o=500;return n={stream:function(n){return t&&e===n?t:t=Cl(r,i,a,o)(e=n)},extent:function(s){return arguments.length?(r=+s[0][0],i=+s[0][1],a=+s[1][0],o=+s[1][1],t=e=null,n):[[r,i],[a,o]]}}},Dl=sc(),Nl={sphere:Nc,point:Nc,lineStart:function(){Nl.point=Ll,Nl.lineEnd=Bl},lineEnd:Nc,polygonStart:Nc,polygonEnd:Nc};function Bl(){Nl.point=Nl.lineEnd=Nc}function Ll(t,e){Al=t*=yc,Sl=Tc(e*=yc),Ml=xc(e),Nl.point=Fl}function Fl(t,e){t*=yc;var n=Tc(e*=yc),r=xc(e),i=vc(t-Al),a=xc(i),o=r*Tc(i),s=Ml*n-Sl*r*a,c=Sl*n+Ml*r*a;Dl.add(bc(Ac(o*o+s*s),c)),Al=t,Sl=n,Ml=r}var Pl=function(t){return Dl.reset(),$c(t,Nl),+Dl},Il=[null,null],jl={type:"LineString",coordinates:Il},Rl=function(t,e){return Il[0]=t,Il[1]=e,Pl(jl)},Yl={Feature:function(t,e){return Ul(t.geometry,e)},FeatureCollection:function(t,e){for(var n=t.features,r=-1,i=n.length;++r<i;)if(Ul(n[r].geometry,e))return!0;return!1}},zl={Sphere:function(){return!0},Point:function(t,e){return $l(t.coordinates,e)},MultiPoint:function(t,e){for(var n=t.coordinates,r=-1,i=n.length;++r<i;)if($l(n[r],e))return!0;return!1},LineString:function(t,e){return Wl(t.coordinates,e)},MultiLineString:function(t,e){for(var n=t.coordinates,r=-1,i=n.length;++r<i;)if(Wl(n[r],e))return!0;return!1},Polygon:function(t,e){return Hl(t.coordinates,e)},MultiPolygon:function(t,e){for(var n=t.coordinates,r=-1,i=n.length;++r<i;)if(Hl(n[r],e))return!0;return!1},GeometryCollection:function(t,e){for(var n=t.geometries,r=-1,i=n.length;++r<i;)if(Ul(n[r],e))return!0;return!1}};function Ul(t,e){return!(!t||!zl.hasOwnProperty(t.type))&&zl[t.type](t,e)}function $l(t,e){return 0===Rl(t,e)}function Wl(t,e){for(var n,r,i,a=0,o=t.length;a<o;a++){if(0===(r=Rl(t[a],e)))return!0;if(a>0&&(i=Rl(t[a],t[a-1]))>0&&n<=i&&r<=i&&(n+r-i)*(1-Math.pow((n-r)/i,2))<1e-12*i)return!0;n=r}return!1}function Hl(t,e){return!!xl(t.map(Vl),Gl(e))}function Vl(t){return(t=t.map(Gl)).pop(),t}function Gl(t){return[t[0]*yc,t[1]*yc]}var ql=function(t,e){return(t&&Yl.hasOwnProperty(t.type)?Yl[t.type]:Ul)(t,e)};function Xl(t,e,n){var r=k(t,e-1e-6,n).concat(e);return function(t){return r.map((function(e){return[t,e]}))}}function Zl(t,e,n){var r=k(t,e-1e-6,n).concat(e);return function(t){return r.map((function(e){return[e,t]}))}}function Jl(){var t,e,n,r,i,a,o,s,c,u,l,h,f=10,d=f,p=90,g=360,y=2.5;function v(){return{type:"MultiLineString",coordinates:m()}}function m(){return k(_c(r/p)*p,n,p).map(l).concat(k(_c(s/g)*g,o,g).map(h)).concat(k(_c(e/f)*f,t,f).filter((function(t){return vc(t%p)>1e-6})).map(c)).concat(k(_c(a/d)*d,i,d).filter((function(t){return vc(t%g)>1e-6})).map(u))}return v.lines=function(){return m().map((function(t){return{type:"LineString",coordinates:t}}))},v.outline=function(){return{type:"Polygon",coordinates:[l(r).concat(h(o).slice(1),l(n).reverse().slice(1),h(s).reverse().slice(1))]}},v.extent=function(t){return arguments.length?v.extentMajor(t).extentMinor(t):v.extentMinor()},v.extentMajor=function(t){return arguments.length?(r=+t[0][0],n=+t[1][0],s=+t[0][1],o=+t[1][1],r>n&&(t=r,r=n,n=t),s>o&&(t=s,s=o,o=t),v.precision(y)):[[r,s],[n,o]]},v.extentMinor=function(n){return arguments.length?(e=+n[0][0],t=+n[1][0],a=+n[0][1],i=+n[1][1],e>t&&(n=e,e=t,t=n),a>i&&(n=a,a=i,i=n),v.precision(y)):[[e,a],[t,i]]},v.step=function(t){return arguments.length?v.stepMajor(t).stepMinor(t):v.stepMinor()},v.stepMajor=function(t){return arguments.length?(p=+t[0],g=+t[1],v):[p,g]},v.stepMinor=function(t){return arguments.length?(f=+t[0],d=+t[1],v):[f,d]},v.precision=function(f){return arguments.length?(y=+f,c=Xl(a,i,90),u=Zl(e,t,y),l=Xl(s,o,90),h=Zl(r,n,y),v):y},v.extentMajor([[-180,1e-6-90],[180,90-1e-6]]).extentMinor([[-180,-80-1e-6],[180,80+1e-6]])}function Kl(){return Jl()()}var Ql,th,eh,nh,rh=function(t,e){var n=t[0]*yc,r=t[1]*yc,i=e[0]*yc,a=e[1]*yc,o=xc(r),s=Tc(r),c=xc(a),u=Tc(a),l=o*xc(n),h=o*Tc(n),f=c*xc(i),d=c*Tc(i),p=2*Oc(Ac(Dc(a-r)+o*c*Dc(i-n))),g=Tc(p),y=p?function(t){var e=Tc(t*=p)/g,n=Tc(p-t)/g,r=n*l+e*f,i=n*h+e*d,a=n*s+e*u;return[bc(i,r)*gc,bc(a,Ac(r*r+i*i))*gc]}:function(){return[n*gc,r*gc]};return y.distance=p,y},ih=function(t){return t},ah=sc(),oh=sc(),sh={point:Nc,lineStart:Nc,lineEnd:Nc,polygonStart:function(){sh.lineStart=ch,sh.lineEnd=hh},polygonEnd:function(){sh.lineStart=sh.lineEnd=sh.point=Nc,ah.add(vc(oh)),oh.reset()},result:function(){var t=ah/2;return ah.reset(),t}};function ch(){sh.point=uh}function uh(t,e){sh.point=lh,Ql=eh=t,th=nh=e}function lh(t,e){oh.add(nh*t-eh*e),eh=t,nh=e}function hh(){lh(Ql,th)}var fh=sh,dh=1/0,ph=dh,gh=-dh,yh=gh;var vh,mh,bh,xh,_h={point:function(t,e){t<dh&&(dh=t);t>gh&&(gh=t);e<ph&&(ph=e);e>yh&&(yh=e)},lineStart:Nc,lineEnd:Nc,polygonStart:Nc,polygonEnd:Nc,result:function(){var t=[[dh,ph],[gh,yh]];return gh=yh=-(ph=dh=1/0),t}},kh=0,wh=0,Eh=0,Th=0,Ch=0,Ah=0,Sh=0,Mh=0,Oh=0,Dh={point:Nh,lineStart:Bh,lineEnd:Ph,polygonStart:function(){Dh.lineStart=Ih,Dh.lineEnd=jh},polygonEnd:function(){Dh.point=Nh,Dh.lineStart=Bh,Dh.lineEnd=Ph},result:function(){var t=Oh?[Sh/Oh,Mh/Oh]:Ah?[Th/Ah,Ch/Ah]:Eh?[kh/Eh,wh/Eh]:[NaN,NaN];return kh=wh=Eh=Th=Ch=Ah=Sh=Mh=Oh=0,t}};function Nh(t,e){kh+=t,wh+=e,++Eh}function Bh(){Dh.point=Lh}function Lh(t,e){Dh.point=Fh,Nh(bh=t,xh=e)}function Fh(t,e){var n=t-bh,r=e-xh,i=Ac(n*n+r*r);Th+=i*(bh+t)/2,Ch+=i*(xh+e)/2,Ah+=i,Nh(bh=t,xh=e)}function Ph(){Dh.point=Nh}function Ih(){Dh.point=Rh}function jh(){Yh(vh,mh)}function Rh(t,e){Dh.point=Yh,Nh(vh=bh=t,mh=xh=e)}function Yh(t,e){var n=t-bh,r=e-xh,i=Ac(n*n+r*r);Th+=i*(bh+t)/2,Ch+=i*(xh+e)/2,Ah+=i,Sh+=(i=xh*t-bh*e)*(bh+t),Mh+=i*(xh+e),Oh+=3*i,Nh(bh=t,xh=e)}var zh=Dh;function Uh(t){this._context=t}Uh.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(t,e){switch(this._point){case 0:this._context.moveTo(t,e),this._point=1;break;case 1:this._context.lineTo(t,e);break;default:this._context.moveTo(t+this._radius,e),this._context.arc(t,e,this._radius,0,pc)}},result:Nc};var $h,Wh,Hh,Vh,Gh,qh=sc(),Xh={point:Nc,lineStart:function(){Xh.point=Zh},lineEnd:function(){$h&&Jh(Wh,Hh),Xh.point=Nc},polygonStart:function(){$h=!0},polygonEnd:function(){$h=null},result:function(){var t=+qh;return qh.reset(),t}};function Zh(t,e){Xh.point=Jh,Wh=Vh=t,Hh=Gh=e}function Jh(t,e){Vh-=t,Gh-=e,qh.add(Ac(Vh*Vh+Gh*Gh)),Vh=t,Gh=e}var Kh=Xh;function Qh(){this._string=[]}function tf(t){return"m0,"+t+"a"+t+","+t+" 0 1,1 0,"+-2*t+"a"+t+","+t+" 0 1,1 0,"+2*t+"z"}Qh.prototype={_radius:4.5,_circle:tf(4.5),pointRadius:function(t){return(t=+t)!==this._radius&&(this._radius=t,this._circle=null),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._string.push("Z"),this._point=NaN},point:function(t,e){switch(this._point){case 0:this._string.push("M",t,",",e),this._point=1;break;case 1:this._string.push("L",t,",",e);break;default:null==this._circle&&(this._circle=tf(this._radius)),this._string.push("M",t,",",e,this._circle)}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}return null}};var ef=function(t,e){var n,r,i=4.5;function a(t){return t&&("function"==typeof i&&r.pointRadius(+i.apply(this,arguments)),$c(t,n(r))),r.result()}return a.area=function(t){return $c(t,n(fh)),fh.result()},a.measure=function(t){return $c(t,n(Kh)),Kh.result()},a.bounds=function(t){return $c(t,n(_h)),_h.result()},a.centroid=function(t){return $c(t,n(zh)),zh.result()},a.projection=function(e){return arguments.length?(n=null==e?(t=null,ih):(t=e).stream,a):t},a.context=function(t){return arguments.length?(r=null==t?(e=null,new Qh):new Uh(e=t),"function"!=typeof i&&r.pointRadius(i),a):e},a.pointRadius=function(t){return arguments.length?(i="function"==typeof t?t:(r.pointRadius(+t),+t),a):i},a.projection(t).context(e)},nf=function(t){return{stream:rf(t)}};function rf(t){return function(e){var n=new af;for(var r in t)n[r]=t[r];return n.stream=e,n}}function af(){}function of(t,e,n){var r=t.clipExtent&&t.clipExtent();return t.scale(150).translate([0,0]),null!=r&&t.clipExtent(null),$c(n,t.stream(_h)),e(_h.result()),null!=r&&t.clipExtent(r),t}function sf(t,e,n){return of(t,(function(n){var r=e[1][0]-e[0][0],i=e[1][1]-e[0][1],a=Math.min(r/(n[1][0]-n[0][0]),i/(n[1][1]-n[0][1])),o=+e[0][0]+(r-a*(n[1][0]+n[0][0]))/2,s=+e[0][1]+(i-a*(n[1][1]+n[0][1]))/2;t.scale(150*a).translate([o,s])}),n)}function cf(t,e,n){return sf(t,[[0,0],e],n)}function uf(t,e,n){return of(t,(function(n){var r=+e,i=r/(n[1][0]-n[0][0]),a=(r-i*(n[1][0]+n[0][0]))/2,o=-i*n[0][1];t.scale(150*i).translate([a,o])}),n)}function lf(t,e,n){return of(t,(function(n){var r=+e,i=r/(n[1][1]-n[0][1]),a=-i*n[0][0],o=(r-i*(n[1][1]+n[0][1]))/2;t.scale(150*i).translate([a,o])}),n)}af.prototype={constructor:af,point:function(t,e){this.stream.point(t,e)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};var hf=xc(30*yc),ff=function(t,e){return+e?function(t,e){function n(r,i,a,o,s,c,u,l,h,f,d,p,g,y){var v=u-r,m=l-i,b=v*v+m*m;if(b>4*e&&g--){var x=o+f,_=s+d,k=c+p,w=Ac(x*x+_*_+k*k),E=Oc(k/=w),T=vc(vc(k)-1)<1e-6||vc(a-h)<1e-6?(a+h)/2:bc(_,x),C=t(T,E),A=C[0],S=C[1],M=A-r,O=S-i,D=m*M-v*O;(D*D/b>e||vc((v*M+m*O)/b-.5)>.3||o*f+s*d+c*p<hf)&&(n(r,i,a,o,s,c,A,S,T,x/=w,_/=w,k,g,y),y.point(A,S),n(A,S,T,x,_,k,u,l,h,f,d,p,g,y))}}return function(e){var r,i,a,o,s,c,u,l,h,f,d,p,g={point:y,lineStart:v,lineEnd:b,polygonStart:function(){e.polygonStart(),g.lineStart=x},polygonEnd:function(){e.polygonEnd(),g.lineStart=v}};function y(n,r){n=t(n,r),e.point(n[0],n[1])}function v(){l=NaN,g.point=m,e.lineStart()}function m(r,i){var a=Qc([r,i]),o=t(r,i);n(l,h,u,f,d,p,l=o[0],h=o[1],u=r,f=a[0],d=a[1],p=a[2],16,e),e.point(l,h)}function b(){g.point=y,e.lineEnd()}function x(){v(),g.point=_,g.lineEnd=k}function _(t,e){m(r=t,e),i=l,a=h,o=f,s=d,c=p,g.point=m}function k(){n(l,h,u,f,d,p,i,a,r,o,s,c,16,e),g.lineEnd=b,b()}return g}}(t,e):function(t){return rf({point:function(e,n){e=t(e,n),this.stream.point(e[0],e[1])}})}(t)};var df=rf({point:function(t,e){this.stream.point(t*yc,e*yc)}});function pf(t,e,n){function r(r,i){return[e+t*r,n-t*i]}return r.invert=function(r,i){return[(r-e)/t,(n-i)/t]},r}function gf(t,e,n,r){var i=xc(r),a=Tc(r),o=i*t,s=a*t,c=i/t,u=a/t,l=(a*n-i*e)/t,h=(a*e+i*n)/t;function f(t,r){return[o*t-s*r+e,n-s*t-o*r]}return f.invert=function(t,e){return[c*t-u*e+l,h-u*t-c*e]},f}function yf(t){return vf((function(){return t}))()}function vf(t){var e,n,r,i,a,o,s,c,u,l,h=150,f=480,d=250,p=0,g=0,y=0,v=0,m=0,b=0,x=null,_=El,k=null,w=ih,E=.5;function T(t){return c(t[0]*yc,t[1]*yc)}function C(t){return(t=c.invert(t[0],t[1]))&&[t[0]*gc,t[1]*gc]}function A(){var t=gf(h,0,0,b).apply(null,e(p,g)),r=(b?gf:pf)(h,f-t[0],d-t[1],b);return n=al(y,v,m),s=rl(e,r),c=rl(n,s),o=ff(s,E),S()}function S(){return u=l=null,T}return T.stream=function(t){return u&&l===t?u:u=df(function(t){return rf({point:function(e,n){var r=t(e,n);return this.stream.point(r[0],r[1])}})}(n)(_(o(w(l=t)))))},T.preclip=function(t){return arguments.length?(_=t,x=void 0,S()):_},T.postclip=function(t){return arguments.length?(w=t,k=r=i=a=null,S()):w},T.clipAngle=function(t){return arguments.length?(_=+t?Tl(x=t*yc):(x=null,El),S()):x*gc},T.clipExtent=function(t){return arguments.length?(w=null==t?(k=r=i=a=null,ih):Cl(k=+t[0][0],r=+t[0][1],i=+t[1][0],a=+t[1][1]),S()):null==k?null:[[k,r],[i,a]]},T.scale=function(t){return arguments.length?(h=+t,A()):h},T.translate=function(t){return arguments.length?(f=+t[0],d=+t[1],A()):[f,d]},T.center=function(t){return arguments.length?(p=t[0]%360*yc,g=t[1]%360*yc,A()):[p*gc,g*gc]},T.rotate=function(t){return arguments.length?(y=t[0]%360*yc,v=t[1]%360*yc,m=t.length>2?t[2]%360*yc:0,A()):[y*gc,v*gc,m*gc]},T.angle=function(t){return arguments.length?(b=t%360*yc,A()):b*gc},T.precision=function(t){return arguments.length?(o=ff(s,E=t*t),S()):Ac(E)},T.fitExtent=function(t,e){return sf(T,t,e)},T.fitSize=function(t,e){return cf(T,t,e)},T.fitWidth=function(t,e){return uf(T,t,e)},T.fitHeight=function(t,e){return lf(T,t,e)},function(){return e=t.apply(this,arguments),T.invert=e.invert&&C,A()}}function mf(t){var e=0,n=hc/3,r=vf(t),i=r(e,n);return i.parallels=function(t){return arguments.length?r(e=t[0]*yc,n=t[1]*yc):[e*gc,n*gc]},i}function bf(t,e){var n=Tc(t),r=(n+Tc(e))/2;if(vc(r)<1e-6)return function(t){var e=xc(t);function n(t,n){return[t*e,Tc(n)/e]}return n.invert=function(t,n){return[t/e,Oc(n*e)]},n}(t);var i=1+n*(2*r-n),a=Ac(i)/r;function o(t,e){var n=Ac(i-2*r*Tc(e))/r;return[n*Tc(t*=r),a-n*xc(t)]}return o.invert=function(t,e){var n=a-e;return[bc(t,vc(n))/r*Cc(n),Oc((i-(t*t+n*n)*r*r)/(2*r))]},o}var xf=function(){return mf(bf).scale(155.424).center([0,33.6442])},_f=function(){return xf().parallels([29.5,45.5]).scale(1070).translate([480,250]).rotate([96,0]).center([-.6,38.7])};var kf=function(){var t,e,n,r,i,a,o=_f(),s=xf().rotate([154,0]).center([-2,58.5]).parallels([55,65]),c=xf().rotate([157,0]).center([-3,19.9]).parallels([8,18]),u={point:function(t,e){a=[t,e]}};function l(t){var e=t[0],o=t[1];return a=null,n.point(e,o),a||(r.point(e,o),a)||(i.point(e,o),a)}function h(){return t=e=null,l}return l.invert=function(t){var e=o.scale(),n=o.translate(),r=(t[0]-n[0])/e,i=(t[1]-n[1])/e;return(i>=.12&&i<.234&&r>=-.425&&r<-.214?s:i>=.166&&i<.234&&r>=-.214&&r<-.115?c:o).invert(t)},l.stream=function(n){return t&&e===n?t:(r=[o.stream(e=n),s.stream(n),c.stream(n)],i=r.length,t={point:function(t,e){for(var n=-1;++n<i;)r[n].point(t,e)},sphere:function(){for(var t=-1;++t<i;)r[t].sphere()},lineStart:function(){for(var t=-1;++t<i;)r[t].lineStart()},lineEnd:function(){for(var t=-1;++t<i;)r[t].lineEnd()},polygonStart:function(){for(var t=-1;++t<i;)r[t].polygonStart()},polygonEnd:function(){for(var t=-1;++t<i;)r[t].polygonEnd()}});var r,i},l.precision=function(t){return arguments.length?(o.precision(t),s.precision(t),c.precision(t),h()):o.precision()},l.scale=function(t){return arguments.length?(o.scale(t),s.scale(.35*t),c.scale(t),l.translate(o.translate())):o.scale()},l.translate=function(t){if(!arguments.length)return o.translate();var e=o.scale(),a=+t[0],l=+t[1];return n=o.translate(t).clipExtent([[a-.455*e,l-.238*e],[a+.455*e,l+.238*e]]).stream(u),r=s.translate([a-.307*e,l+.201*e]).clipExtent([[a-.425*e+1e-6,l+.12*e+1e-6],[a-.214*e-1e-6,l+.234*e-1e-6]]).stream(u),i=c.translate([a-.205*e,l+.212*e]).clipExtent([[a-.214*e+1e-6,l+.166*e+1e-6],[a-.115*e-1e-6,l+.234*e-1e-6]]).stream(u),h()},l.fitExtent=function(t,e){return sf(l,t,e)},l.fitSize=function(t,e){return cf(l,t,e)},l.fitWidth=function(t,e){return uf(l,t,e)},l.fitHeight=function(t,e){return lf(l,t,e)},l.scale(1070)};function wf(t){return function(e,n){var r=xc(e),i=xc(n),a=t(r*i);return[a*i*Tc(e),a*Tc(n)]}}function Ef(t){return function(e,n){var r=Ac(e*e+n*n),i=t(r),a=Tc(i),o=xc(i);return[bc(e*a,r*o),Oc(r&&n*a/r)]}}var Tf=wf((function(t){return Ac(2/(1+t))}));Tf.invert=Ef((function(t){return 2*Oc(t/2)}));var Cf=function(){return yf(Tf).scale(124.75).clipAngle(179.999)},Af=wf((function(t){return(t=Mc(t))&&t/Tc(t)}));Af.invert=Ef((function(t){return t}));var Sf=function(){return yf(Af).scale(79.4188).clipAngle(179.999)};function Mf(t,e){return[t,wc(Sc((fc+e)/2))]}Mf.invert=function(t,e){return[t,2*mc(kc(e))-fc]};var Of=function(){return Df(Mf).scale(961/pc)};function Df(t){var e,n,r,i=yf(t),a=i.center,o=i.scale,s=i.translate,c=i.clipExtent,u=null;function l(){var a=hc*o(),s=i(ul(i.rotate()).invert([0,0]));return c(null==u?[[s[0]-a,s[1]-a],[s[0]+a,s[1]+a]]:t===Mf?[[Math.max(s[0]-a,u),e],[Math.min(s[0]+a,n),r]]:[[u,Math.max(s[1]-a,e)],[n,Math.min(s[1]+a,r)]])}return i.scale=function(t){return arguments.length?(o(t),l()):o()},i.translate=function(t){return arguments.length?(s(t),l()):s()},i.center=function(t){return arguments.length?(a(t),l()):a()},i.clipExtent=function(t){return arguments.length?(null==t?u=e=n=r=null:(u=+t[0][0],e=+t[0][1],n=+t[1][0],r=+t[1][1]),l()):null==u?null:[[u,e],[n,r]]},l()}function Nf(t){return Sc((fc+t)/2)}function Bf(t,e){var n=xc(t),r=t===e?Tc(t):wc(n/xc(e))/wc(Nf(e)/Nf(t)),i=n*Ec(Nf(t),r)/r;if(!r)return Mf;function a(t,e){i>0?e<1e-6-fc&&(e=1e-6-fc):e>fc-1e-6&&(e=fc-1e-6);var n=i/Ec(Nf(e),r);return[n*Tc(r*t),i-n*xc(r*t)]}return a.invert=function(t,e){var n=i-e,a=Cc(r)*Ac(t*t+n*n);return[bc(t,vc(n))/r*Cc(n),2*mc(Ec(i/a,1/r))-fc]},a}var Lf=function(){return mf(Bf).scale(109.5).parallels([30,30])};function Ff(t,e){return[t,e]}Ff.invert=Ff;var Pf=function(){return yf(Ff).scale(152.63)};function If(t,e){var n=xc(t),r=t===e?Tc(t):(n-xc(e))/(e-t),i=n/r+t;if(vc(r)<1e-6)return Ff;function a(t,e){var n=i-e,a=r*t;return[n*Tc(a),i-n*xc(a)]}return a.invert=function(t,e){var n=i-e;return[bc(t,vc(n))/r*Cc(n),i-Cc(r)*Ac(t*t+n*n)]},a}var jf=function(){return mf(If).scale(131.154).center([0,13.9389])},Rf=1.340264,Yf=-.081106,zf=893e-6,Uf=.003796,$f=Ac(3)/2;function Wf(t,e){var n=Oc($f*Tc(e)),r=n*n,i=r*r*r;return[t*xc(n)/($f*(Rf+3*Yf*r+i*(7*zf+9*Uf*r))),n*(Rf+Yf*r+i*(zf+Uf*r))]}Wf.invert=function(t,e){for(var n,r=e,i=r*r,a=i*i*i,o=0;o<12&&(a=(i=(r-=n=(r*(Rf+Yf*i+a*(zf+Uf*i))-e)/(Rf+3*Yf*i+a*(7*zf+9*Uf*i)))*r)*i*i,!(vc(n)<1e-12));++o);return[$f*t*(Rf+3*Yf*i+a*(7*zf+9*Uf*i))/xc(r),Oc(Tc(r)/$f)]};var Hf=function(){return yf(Wf).scale(177.158)};function Vf(t,e){var n=xc(e),r=xc(t)*n;return[n*Tc(t)/r,Tc(e)/r]}Vf.invert=Ef(mc);var Gf=function(){return yf(Vf).scale(144.049).clipAngle(60)};function qf(t,e,n,r){return 1===t&&1===e&&0===n&&0===r?ih:rf({point:function(i,a){this.stream.point(i*t+n,a*e+r)}})}var Xf=function(){var t,e,n,r,i,a,o=1,s=0,c=0,u=1,l=1,h=ih,f=null,d=ih;function p(){return r=i=null,a}return a={stream:function(t){return r&&i===t?r:r=h(d(i=t))},postclip:function(r){return arguments.length?(d=r,f=t=e=n=null,p()):d},clipExtent:function(r){return arguments.length?(d=null==r?(f=t=e=n=null,ih):Cl(f=+r[0][0],t=+r[0][1],e=+r[1][0],n=+r[1][1]),p()):null==f?null:[[f,t],[e,n]]},scale:function(t){return arguments.length?(h=qf((o=+t)*u,o*l,s,c),p()):o},translate:function(t){return arguments.length?(h=qf(o*u,o*l,s=+t[0],c=+t[1]),p()):[s,c]},reflectX:function(t){return arguments.length?(h=qf(o*(u=t?-1:1),o*l,s,c),p()):u<0},reflectY:function(t){return arguments.length?(h=qf(o*u,o*(l=t?-1:1),s,c),p()):l<0},fitExtent:function(t,e){return sf(a,t,e)},fitSize:function(t,e){return cf(a,t,e)},fitWidth:function(t,e){return uf(a,t,e)},fitHeight:function(t,e){return lf(a,t,e)}}};function Zf(t,e){var n=e*e,r=n*n;return[t*(.8707-.131979*n+r*(r*(.003971*n-.001529*r)-.013791)),e*(1.007226+n*(.015085+r*(.028874*n-.044475-.005916*r)))]}Zf.invert=function(t,e){var n,r=e,i=25;do{var a=r*r,o=a*a;r-=n=(r*(1.007226+a*(.015085+o*(.028874*a-.044475-.005916*o)))-e)/(1.007226+a*(.045255+o*(.259866*a-.311325-.005916*11*o)))}while(vc(n)>1e-6&&--i>0);return[t/(.8707+(a=r*r)*(a*(a*a*a*(.003971-.001529*a)-.013791)-.131979)),r]};var Jf=function(){return yf(Zf).scale(175.295)};function Kf(t,e){return[xc(e)*Tc(t),Tc(e)]}Kf.invert=Ef(Oc);var Qf=function(){return yf(Kf).scale(249.5).clipAngle(90+1e-6)};function td(t,e){var n=xc(e),r=1+xc(t)*n;return[n*Tc(t)/r,Tc(e)/r]}td.invert=Ef((function(t){return 2*mc(t)}));var ed=function(){return yf(td).scale(250).clipAngle(142)};function nd(t,e){return[wc(Sc((fc+e)/2)),-t]}nd.invert=function(t,e){return[-e,2*mc(kc(t))-fc]};var rd=function(){var t=Df(nd),e=t.center,n=t.rotate;return t.center=function(t){return arguments.length?e([-t[1],t[0]]):[(t=e())[1],-t[0]]},t.rotate=function(t){return arguments.length?n([t[0],t[1],t.length>2?t[2]+90:90]):[(t=n())[0],t[1],t[2]-90]},n([0,0,90]).scale(159.155)};function id(t,e){return t.parent===e.parent?1:2}function ad(t,e){return t+e.x}function od(t,e){return Math.max(t,e.y)}var sd=function(){var t=id,e=1,n=1,r=!1;function i(i){var a,o=0;i.eachAfter((function(e){var n=e.children;n?(e.x=function(t){return t.reduce(ad,0)/t.length}(n),e.y=function(t){return 1+t.reduce(od,0)}(n)):(e.x=a?o+=t(e,a):0,e.y=0,a=e)}));var s=function(t){for(var e;e=t.children;)t=e[0];return t}(i),c=function(t){for(var e;e=t.children;)t=e[e.length-1];return t}(i),u=s.x-t(s,c)/2,l=c.x+t(c,s)/2;return i.eachAfter(r?function(t){t.x=(t.x-i.x)*e,t.y=(i.y-t.y)*n}:function(t){t.x=(t.x-u)/(l-u)*e,t.y=(1-(i.y?t.y/i.y:1))*n})}return i.separation=function(e){return arguments.length?(t=e,i):t},i.size=function(t){return arguments.length?(r=!1,e=+t[0],n=+t[1],i):r?null:[e,n]},i.nodeSize=function(t){return arguments.length?(r=!0,e=+t[0],n=+t[1],i):r?[e,n]:null},i};function cd(t){var e=0,n=t.children,r=n&&n.length;if(r)for(;--r>=0;)e+=n[r].value;else e=1;t.value=e}function ud(t,e){var n,r,i,a,o,s=new dd(t),c=+t.value&&(s.value=t.value),u=[s];for(null==e&&(e=ld);n=u.pop();)if(c&&(n.value=+n.data.value),(i=e(n.data))&&(o=i.length))for(n.children=new Array(o),a=o-1;a>=0;--a)u.push(r=n.children[a]=new dd(i[a])),r.parent=n,r.depth=n.depth+1;return s.eachBefore(fd)}function ld(t){return t.children}function hd(t){t.data=t.data.data}function fd(t){var e=0;do{t.height=e}while((t=t.parent)&&t.height<++e)}function dd(t){this.data=t,this.depth=this.height=0,this.parent=null}dd.prototype=ud.prototype={constructor:dd,count:function(){return this.eachAfter(cd)},each:function(t){var e,n,r,i,a=this,o=[a];do{for(e=o.reverse(),o=[];a=e.pop();)if(t(a),n=a.children)for(r=0,i=n.length;r<i;++r)o.push(n[r])}while(o.length);return this},eachAfter:function(t){for(var e,n,r,i=this,a=[i],o=[];i=a.pop();)if(o.push(i),e=i.children)for(n=0,r=e.length;n<r;++n)a.push(e[n]);for(;i=o.pop();)t(i);return this},eachBefore:function(t){for(var e,n,r=this,i=[r];r=i.pop();)if(t(r),e=r.children)for(n=e.length-1;n>=0;--n)i.push(e[n]);return this},sum:function(t){return this.eachAfter((function(e){for(var n=+t(e.data)||0,r=e.children,i=r&&r.length;--i>=0;)n+=r[i].value;e.value=n}))},sort:function(t){return this.eachBefore((function(e){e.children&&e.children.sort(t)}))},path:function(t){for(var e=this,n=function(t,e){if(t===e)return t;var n=t.ancestors(),r=e.ancestors(),i=null;t=n.pop(),e=r.pop();for(;t===e;)i=t,t=n.pop(),e=r.pop();return i}(e,t),r=[e];e!==n;)e=e.parent,r.push(e);for(var i=r.length;t!==n;)r.splice(i,0,t),t=t.parent;return r},ancestors:function(){for(var t=this,e=[t];t=t.parent;)e.push(t);return e},descendants:function(){var t=[];return this.each((function(e){t.push(e)})),t},leaves:function(){var t=[];return this.eachBefore((function(e){e.children||t.push(e)})),t},links:function(){var t=this,e=[];return t.each((function(n){n!==t&&e.push({source:n.parent,target:n})})),e},copy:function(){return ud(this).eachBefore(hd)}};var pd=Array.prototype.slice;var gd=function(t){for(var e,n,r=0,i=(t=function(t){for(var e,n,r=t.length;r;)n=Math.random()*r--|0,e=t[r],t[r]=t[n],t[n]=e;return t}(pd.call(t))).length,a=[];r<i;)e=t[r],n&&md(n,e)?++r:(n=xd(a=yd(a,e)),r=0);return n};function yd(t,e){var n,r;if(bd(e,t))return[e];for(n=0;n<t.length;++n)if(vd(e,t[n])&&bd(_d(t[n],e),t))return[t[n],e];for(n=0;n<t.length-1;++n)for(r=n+1;r<t.length;++r)if(vd(_d(t[n],t[r]),e)&&vd(_d(t[n],e),t[r])&&vd(_d(t[r],e),t[n])&&bd(kd(t[n],t[r],e),t))return[t[n],t[r],e];throw new Error}function vd(t,e){var n=t.r-e.r,r=e.x-t.x,i=e.y-t.y;return n<0||n*n<r*r+i*i}function md(t,e){var n=t.r-e.r+1e-6,r=e.x-t.x,i=e.y-t.y;return n>0&&n*n>r*r+i*i}function bd(t,e){for(var n=0;n<e.length;++n)if(!md(t,e[n]))return!1;return!0}function xd(t){switch(t.length){case 1:return{x:(e=t[0]).x,y:e.y,r:e.r};case 2:return _d(t[0],t[1]);case 3:return kd(t[0],t[1],t[2])}var e}function _d(t,e){var n=t.x,r=t.y,i=t.r,a=e.x,o=e.y,s=e.r,c=a-n,u=o-r,l=s-i,h=Math.sqrt(c*c+u*u);return{x:(n+a+c/h*l)/2,y:(r+o+u/h*l)/2,r:(h+i+s)/2}}function kd(t,e,n){var r=t.x,i=t.y,a=t.r,o=e.x,s=e.y,c=e.r,u=n.x,l=n.y,h=n.r,f=r-o,d=r-u,p=i-s,g=i-l,y=c-a,v=h-a,m=r*r+i*i-a*a,b=m-o*o-s*s+c*c,x=m-u*u-l*l+h*h,_=d*p-f*g,k=(p*x-g*b)/(2*_)-r,w=(g*y-p*v)/_,E=(d*b-f*x)/(2*_)-i,T=(f*v-d*y)/_,C=w*w+T*T-1,A=2*(a+k*w+E*T),S=k*k+E*E-a*a,M=-(C?(A+Math.sqrt(A*A-4*C*S))/(2*C):S/A);return{x:r+k+w*M,y:i+E+T*M,r:M}}function wd(t,e,n){var r,i,a,o,s=t.x-e.x,c=t.y-e.y,u=s*s+c*c;u?(i=e.r+n.r,i*=i,o=t.r+n.r,i>(o*=o)?(r=(u+o-i)/(2*u),a=Math.sqrt(Math.max(0,o/u-r*r)),n.x=t.x-r*s-a*c,n.y=t.y-r*c+a*s):(r=(u+i-o)/(2*u),a=Math.sqrt(Math.max(0,i/u-r*r)),n.x=e.x+r*s-a*c,n.y=e.y+r*c+a*s)):(n.x=e.x+n.r,n.y=e.y)}function Ed(t,e){var n=t.r+e.r-1e-6,r=e.x-t.x,i=e.y-t.y;return n>0&&n*n>r*r+i*i}function Td(t){var e=t._,n=t.next._,r=e.r+n.r,i=(e.x*n.r+n.x*e.r)/r,a=(e.y*n.r+n.y*e.r)/r;return i*i+a*a}function Cd(t){this._=t,this.next=null,this.previous=null}function Ad(t){if(!(i=t.length))return 0;var e,n,r,i,a,o,s,c,u,l,h;if((e=t[0]).x=0,e.y=0,!(i>1))return e.r;if(n=t[1],e.x=-n.r,n.x=e.r,n.y=0,!(i>2))return e.r+n.r;wd(n,e,r=t[2]),e=new Cd(e),n=new Cd(n),r=new Cd(r),e.next=r.previous=n,n.next=e.previous=r,r.next=n.previous=e;t:for(s=3;s<i;++s){wd(e._,n._,r=t[s]),r=new Cd(r),c=n.next,u=e.previous,l=n._.r,h=e._.r;do{if(l<=h){if(Ed(c._,r._)){n=c,e.next=n,n.previous=e,--s;continue t}l+=c._.r,c=c.next}else{if(Ed(u._,r._)){(e=u).next=n,n.previous=e,--s;continue t}h+=u._.r,u=u.previous}}while(c!==u.next);for(r.previous=e,r.next=n,e.next=n.previous=n=r,a=Td(e);(r=r.next)!==n;)(o=Td(r))<a&&(e=r,a=o);n=e.next}for(e=[n._],r=n;(r=r.next)!==n;)e.push(r._);for(r=gd(e),s=0;s<i;++s)(e=t[s]).x-=r.x,e.y-=r.y;return r.r}var Sd=function(t){return Ad(t),t};function Md(t){return null==t?null:Od(t)}function Od(t){if("function"!=typeof t)throw new Error;return t}function Dd(){return 0}var Nd=function(t){return function(){return t}};function Bd(t){return Math.sqrt(t.value)}var Ld=function(){var t=null,e=1,n=1,r=Dd;function i(i){return i.x=e/2,i.y=n/2,t?i.eachBefore(Fd(t)).eachAfter(Pd(r,.5)).eachBefore(Id(1)):i.eachBefore(Fd(Bd)).eachAfter(Pd(Dd,1)).eachAfter(Pd(r,i.r/Math.min(e,n))).eachBefore(Id(Math.min(e,n)/(2*i.r))),i}return i.radius=function(e){return arguments.length?(t=Md(e),i):t},i.size=function(t){return arguments.length?(e=+t[0],n=+t[1],i):[e,n]},i.padding=function(t){return arguments.length?(r="function"==typeof t?t:Nd(+t),i):r},i};function Fd(t){return function(e){e.children||(e.r=Math.max(0,+t(e)||0))}}function Pd(t,e){return function(n){if(r=n.children){var r,i,a,o=r.length,s=t(n)*e||0;if(s)for(i=0;i<o;++i)r[i].r+=s;if(a=Ad(r),s)for(i=0;i<o;++i)r[i].r-=s;n.r=a+s}}}function Id(t){return function(e){var n=e.parent;e.r*=t,n&&(e.x=n.x+t*e.x,e.y=n.y+t*e.y)}}var jd=function(t){t.x0=Math.round(t.x0),t.y0=Math.round(t.y0),t.x1=Math.round(t.x1),t.y1=Math.round(t.y1)},Rd=function(t,e,n,r,i){for(var a,o=t.children,s=-1,c=o.length,u=t.value&&(r-e)/t.value;++s<c;)(a=o[s]).y0=n,a.y1=i,a.x0=e,a.x1=e+=a.value*u},Yd=function(){var t=1,e=1,n=0,r=!1;function i(i){var a=i.height+1;return i.x0=i.y0=n,i.x1=t,i.y1=e/a,i.eachBefore(function(t,e){return function(r){r.children&&Rd(r,r.x0,t*(r.depth+1)/e,r.x1,t*(r.depth+2)/e);var i=r.x0,a=r.y0,o=r.x1-n,s=r.y1-n;o<i&&(i=o=(i+o)/2),s<a&&(a=s=(a+s)/2),r.x0=i,r.y0=a,r.x1=o,r.y1=s}}(e,a)),r&&i.eachBefore(jd),i}return i.round=function(t){return arguments.length?(r=!!t,i):r},i.size=function(n){return arguments.length?(t=+n[0],e=+n[1],i):[t,e]},i.padding=function(t){return arguments.length?(n=+t,i):n},i},zd={depth:-1},Ud={};function $d(t){return t.id}function Wd(t){return t.parentId}var Hd=function(){var t=$d,e=Wd;function n(n){var r,i,a,o,s,c,u,l=n.length,h=new Array(l),f={};for(i=0;i<l;++i)r=n[i],s=h[i]=new dd(r),null!=(c=t(r,i,n))&&(c+="")&&(f[u="$"+(s.id=c)]=u in f?Ud:s);for(i=0;i<l;++i)if(s=h[i],null!=(c=e(n[i],i,n))&&(c+="")){if(!(o=f["$"+c]))throw new Error("missing: "+c);if(o===Ud)throw new Error("ambiguous: "+c);o.children?o.children.push(s):o.children=[s],s.parent=o}else{if(a)throw new Error("multiple roots");a=s}if(!a)throw new Error("no root");if(a.parent=zd,a.eachBefore((function(t){t.depth=t.parent.depth+1,--l})).eachBefore(fd),a.parent=null,l>0)throw new Error("cycle");return a}return n.id=function(e){return arguments.length?(t=Od(e),n):t},n.parentId=function(t){return arguments.length?(e=Od(t),n):e},n};function Vd(t,e){return t.parent===e.parent?1:2}function Gd(t){var e=t.children;return e?e[0]:t.t}function qd(t){var e=t.children;return e?e[e.length-1]:t.t}function Xd(t,e,n){var r=n/(e.i-t.i);e.c-=r,e.s+=n,t.c+=r,e.z+=n,e.m+=n}function Zd(t,e,n){return t.a.parent===e.parent?t.a:n}function Jd(t,e){this._=t,this.parent=null,this.children=null,this.A=null,this.a=this,this.z=0,this.m=0,this.c=0,this.s=0,this.t=null,this.i=e}Jd.prototype=Object.create(dd.prototype);var Kd=function(){var t=Vd,e=1,n=1,r=null;function i(i){var c=function(t){for(var e,n,r,i,a,o=new Jd(t,0),s=[o];e=s.pop();)if(r=e._.children)for(e.children=new Array(a=r.length),i=a-1;i>=0;--i)s.push(n=e.children[i]=new Jd(r[i],i)),n.parent=e;return(o.parent=new Jd(null,0)).children=[o],o}(i);if(c.eachAfter(a),c.parent.m=-c.z,c.eachBefore(o),r)i.eachBefore(s);else{var u=i,l=i,h=i;i.eachBefore((function(t){t.x<u.x&&(u=t),t.x>l.x&&(l=t),t.depth>h.depth&&(h=t)}));var f=u===l?1:t(u,l)/2,d=f-u.x,p=e/(l.x+f+d),g=n/(h.depth||1);i.eachBefore((function(t){t.x=(t.x+d)*p,t.y=t.depth*g}))}return i}function a(e){var n=e.children,r=e.parent.children,i=e.i?r[e.i-1]:null;if(n){!function(t){for(var e,n=0,r=0,i=t.children,a=i.length;--a>=0;)(e=i[a]).z+=n,e.m+=n,n+=e.s+(r+=e.c)}(e);var a=(n[0].z+n[n.length-1].z)/2;i?(e.z=i.z+t(e._,i._),e.m=e.z-a):e.z=a}else i&&(e.z=i.z+t(e._,i._));e.parent.A=function(e,n,r){if(n){for(var i,a=e,o=e,s=n,c=a.parent.children[0],u=a.m,l=o.m,h=s.m,f=c.m;s=qd(s),a=Gd(a),s&&a;)c=Gd(c),(o=qd(o)).a=e,(i=s.z+h-a.z-u+t(s._,a._))>0&&(Xd(Zd(s,e,r),e,i),u+=i,l+=i),h+=s.m,u+=a.m,f+=c.m,l+=o.m;s&&!qd(o)&&(o.t=s,o.m+=h-l),a&&!Gd(c)&&(c.t=a,c.m+=u-f,r=e)}return r}(e,i,e.parent.A||r[0])}function o(t){t._.x=t.z+t.parent.m,t.m+=t.parent.m}function s(t){t.x*=e,t.y=t.depth*n}return i.separation=function(e){return arguments.length?(t=e,i):t},i.size=function(t){return arguments.length?(r=!1,e=+t[0],n=+t[1],i):r?null:[e,n]},i.nodeSize=function(t){return arguments.length?(r=!0,e=+t[0],n=+t[1],i):r?[e,n]:null},i},Qd=function(t,e,n,r,i){for(var a,o=t.children,s=-1,c=o.length,u=t.value&&(i-n)/t.value;++s<c;)(a=o[s]).x0=e,a.x1=r,a.y0=n,a.y1=n+=a.value*u},tp=(1+Math.sqrt(5))/2;function ep(t,e,n,r,i,a){for(var o,s,c,u,l,h,f,d,p,g,y,v=[],m=e.children,b=0,x=0,_=m.length,k=e.value;b<_;){c=i-n,u=a-r;do{l=m[x++].value}while(!l&&x<_);for(h=f=l,y=l*l*(g=Math.max(u/c,c/u)/(k*t)),p=Math.max(f/y,y/h);x<_;++x){if(l+=s=m[x].value,s<h&&(h=s),s>f&&(f=s),y=l*l*g,(d=Math.max(f/y,y/h))>p){l-=s;break}p=d}v.push(o={value:l,dice:c<u,children:m.slice(b,x)}),o.dice?Rd(o,n,r,i,k?r+=u*l/k:a):Qd(o,n,r,k?n+=c*l/k:i,a),k-=l,b=x}return v}var np=function t(e){function n(t,n,r,i,a){ep(e,t,n,r,i,a)}return n.ratio=function(e){return t((e=+e)>1?e:1)},n}(tp),rp=function(){var t=np,e=!1,n=1,r=1,i=[0],a=Dd,o=Dd,s=Dd,c=Dd,u=Dd;function l(t){return t.x0=t.y0=0,t.x1=n,t.y1=r,t.eachBefore(h),i=[0],e&&t.eachBefore(jd),t}function h(e){var n=i[e.depth],r=e.x0+n,l=e.y0+n,h=e.x1-n,f=e.y1-n;h<r&&(r=h=(r+h)/2),f<l&&(l=f=(l+f)/2),e.x0=r,e.y0=l,e.x1=h,e.y1=f,e.children&&(n=i[e.depth+1]=a(e)/2,r+=u(e)-n,l+=o(e)-n,(h-=s(e)-n)<r&&(r=h=(r+h)/2),(f-=c(e)-n)<l&&(l=f=(l+f)/2),t(e,r,l,h,f))}return l.round=function(t){return arguments.length?(e=!!t,l):e},l.size=function(t){return arguments.length?(n=+t[0],r=+t[1],l):[n,r]},l.tile=function(e){return arguments.length?(t=Od(e),l):t},l.padding=function(t){return arguments.length?l.paddingInner(t).paddingOuter(t):l.paddingInner()},l.paddingInner=function(t){return arguments.length?(a="function"==typeof t?t:Nd(+t),l):a},l.paddingOuter=function(t){return arguments.length?l.paddingTop(t).paddingRight(t).paddingBottom(t).paddingLeft(t):l.paddingTop()},l.paddingTop=function(t){return arguments.length?(o="function"==typeof t?t:Nd(+t),l):o},l.paddingRight=function(t){return arguments.length?(s="function"==typeof t?t:Nd(+t),l):s},l.paddingBottom=function(t){return arguments.length?(c="function"==typeof t?t:Nd(+t),l):c},l.paddingLeft=function(t){return arguments.length?(u="function"==typeof t?t:Nd(+t),l):u},l},ip=function(t,e,n,r,i){var a,o,s=t.children,c=s.length,u=new Array(c+1);for(u[0]=o=a=0;a<c;++a)u[a+1]=o+=s[a].value;!function t(e,n,r,i,a,o,c){if(e>=n-1){var l=s[e];return l.x0=i,l.y0=a,l.x1=o,void(l.y1=c)}var h=u[e],f=r/2+h,d=e+1,p=n-1;for(;d<p;){var g=d+p>>>1;u[g]<f?d=g+1:p=g}f-u[d-1]<u[d]-f&&e+1<d&&--d;var y=u[d]-h,v=r-y;if(o-i>c-a){var m=(i*v+o*y)/r;t(e,d,y,i,a,m,c),t(d,n,v,m,a,o,c)}else{var b=(a*v+c*y)/r;t(e,d,y,i,a,o,b),t(d,n,v,i,b,o,c)}}(0,c,t.value,e,n,r,i)},ap=function(t,e,n,r,i){(1&t.depth?Qd:Rd)(t,e,n,r,i)},op=function t(e){function n(t,n,r,i,a){if((o=t._squarify)&&o.ratio===e)for(var o,s,c,u,l,h=-1,f=o.length,d=t.value;++h<f;){for(c=(s=o[h]).children,u=s.value=0,l=c.length;u<l;++u)s.value+=c[u].value;s.dice?Rd(s,n,r,i,r+=(a-r)*s.value/d):Qd(s,n,r,n+=(i-n)*s.value/d,a),d-=s.value}else t._squarify=o=ep(e,t,n,r,i,a),o.ratio=e}return n.ratio=function(e){return t((e=+e)>1?e:1)},n}(tp),sp=function(t){var e=t.length;return function(n){return t[Math.max(0,Math.min(e-1,Math.floor(n*e)))]}},cp=function(t,e){var n=un(+t,+e);return function(t){var e=n(t);return e-360*Math.floor(e/360)}},up=function(t,e){return t=+t,e=+e,function(n){return Math.round(t*(1-n)+e*n)}},lp=Math.SQRT2;function hp(t){return((t=Math.exp(t))+1/t)/2}var fp=function(t,e){var n,r,i=t[0],a=t[1],o=t[2],s=e[0],c=e[1],u=e[2],l=s-i,h=c-a,f=l*l+h*h;if(f<1e-12)r=Math.log(u/o)/lp,n=function(t){return[i+t*l,a+t*h,o*Math.exp(lp*t*r)]};else{var d=Math.sqrt(f),p=(u*u-o*o+4*f)/(2*o*2*d),g=(u*u-o*o-4*f)/(2*u*2*d),y=Math.log(Math.sqrt(p*p+1)-p),v=Math.log(Math.sqrt(g*g+1)-g);r=(v-y)/lp,n=function(t){var e,n=t*r,s=hp(y),c=o/(2*d)*(s*(e=lp*n+y,((e=Math.exp(2*e))-1)/(e+1))-function(t){return((t=Math.exp(t))-1/t)/2}(y));return[i+c*l,a+c*h,o*s/hp(lp*n+y)]}}return n.duration=1e3*r,n};function dp(t){return function(e,n){var r=t((e=tn(e)).h,(n=tn(n)).h),i=hn(e.s,n.s),a=hn(e.l,n.l),o=hn(e.opacity,n.opacity);return function(t){return e.h=r(t),e.s=i(t),e.l=a(t),e.opacity=o(t),e+""}}}var pp=dp(un),gp=dp(hn);function yp(t,e){var n=hn((t=pa(t)).l,(e=pa(e)).l),r=hn(t.a,e.a),i=hn(t.b,e.b),a=hn(t.opacity,e.opacity);return function(e){return t.l=n(e),t.a=r(e),t.b=i(e),t.opacity=a(e),t+""}}function vp(t){return function(e,n){var r=t((e=ka(e)).h,(n=ka(n)).h),i=hn(e.c,n.c),a=hn(e.l,n.l),o=hn(e.opacity,n.opacity);return function(t){return e.h=r(t),e.c=i(t),e.l=a(t),e.opacity=o(t),e+""}}}var mp=vp(un),bp=vp(hn);function xp(t){return function e(n){function r(e,r){var i=t((e=Oa(e)).h,(r=Oa(r)).h),a=hn(e.s,r.s),o=hn(e.l,r.l),s=hn(e.opacity,r.opacity);return function(t){return e.h=i(t),e.s=a(t),e.l=o(Math.pow(t,n)),e.opacity=s(t),e+""}}return n=+n,r.gamma=e,r}(1)}var _p=xp(un),kp=xp(hn);function wp(t,e){for(var n=0,r=e.length-1,i=e[0],a=new Array(r<0?0:r);n<r;)a[n]=t(i,i=e[++n]);return function(t){var e=Math.max(0,Math.min(r-1,Math.floor(t*=r)));return a[e](t-e)}}var Ep=function(t,e){for(var n=new Array(e),r=0;r<e;++r)n[r]=t(r/(e-1));return n},Tp=function(t){for(var e,n=-1,r=t.length,i=t[r-1],a=0;++n<r;)e=i,i=t[n],a+=e[1]*i[0]-e[0]*i[1];return a/2},Cp=function(t){for(var e,n,r=-1,i=t.length,a=0,o=0,s=t[i-1],c=0;++r<i;)e=s,s=t[r],c+=n=e[0]*s[1]-s[0]*e[1],a+=(e[0]+s[0])*n,o+=(e[1]+s[1])*n;return[a/(c*=3),o/c]};function Ap(t,e){return t[0]-e[0]||t[1]-e[1]}function Sp(t){for(var e,n,r,i=t.length,a=[0,1],o=2,s=2;s<i;++s){for(;o>1&&(e=t[a[o-2]],n=t[a[o-1]],r=t[s],(n[0]-e[0])*(r[1]-e[1])-(n[1]-e[1])*(r[0]-e[0])<=0);)--o;a[o++]=s}return a.slice(0,o)}var Mp=function(t){if((n=t.length)<3)return null;var e,n,r=new Array(n),i=new Array(n);for(e=0;e<n;++e)r[e]=[+t[e][0],+t[e][1],e];for(r.sort(Ap),e=0;e<n;++e)i[e]=[r[e][0],-r[e][1]];var a=Sp(r),o=Sp(i),s=o[0]===a[0],c=o[o.length-1]===a[a.length-1],u=[];for(e=a.length-1;e>=0;--e)u.push(t[r[a[e]][2]]);for(e=+s;e<o.length-c;++e)u.push(t[r[o[e]][2]]);return u},Op=function(t,e){for(var n,r,i=t.length,a=t[i-1],o=e[0],s=e[1],c=a[0],u=a[1],l=!1,h=0;h<i;++h)n=(a=t[h])[0],(r=a[1])>s!=u>s&&o<(c-n)*(s-r)/(u-r)+n&&(l=!l),c=n,u=r;return l},Dp=function(t){for(var e,n,r=-1,i=t.length,a=t[i-1],o=a[0],s=a[1],c=0;++r<i;)e=o,n=s,e-=o=(a=t[r])[0],n-=s=a[1],c+=Math.sqrt(e*e+n*n);return c},Np=function(){return Math.random()},Bp=function t(e){function n(t,n){return t=null==t?0:+t,n=null==n?1:+n,1===arguments.length?(n=t,t=0):n-=t,function(){return e()*n+t}}return n.source=t,n}(Np),Lp=function t(e){function n(t,n){var r,i;return t=null==t?0:+t,n=null==n?1:+n,function(){var a;if(null!=r)a=r,r=null;else do{r=2*e()-1,a=2*e()-1,i=r*r+a*a}while(!i||i>1);return t+n*a*Math.sqrt(-2*Math.log(i)/i)}}return n.source=t,n}(Np),Fp=function t(e){function n(){var t=Lp.source(e).apply(this,arguments);return function(){return Math.exp(t())}}return n.source=t,n}(Np),Pp=function t(e){function n(t){return function(){for(var n=0,r=0;r<t;++r)n+=e();return n}}return n.source=t,n}(Np),Ip=function t(e){function n(t){var n=Pp.source(e)(t);return function(){return n()/t}}return n.source=t,n}(Np),jp=function t(e){function n(t){return function(){return-Math.log(1-e())/t}}return n.source=t,n}(Np);function Rp(t,e){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(e).domain(t)}return this}function Yp(t,e){switch(arguments.length){case 0:break;case 1:this.interpolator(t);break;default:this.interpolator(e).domain(t)}return this}var zp=Array.prototype,Up=zp.map,$p=zp.slice,Wp={name:"implicit"};function Hp(){var t=Ji(),e=[],n=[],r=Wp;function i(i){var a=i+"",o=t.get(a);if(!o){if(r!==Wp)return r;t.set(a,o=e.push(i))}return n[(o-1)%n.length]}return i.domain=function(n){if(!arguments.length)return e.slice();e=[],t=Ji();for(var r,a,o=-1,s=n.length;++o<s;)t.has(a=(r=n[o])+"")||t.set(a,e.push(r));return i},i.range=function(t){return arguments.length?(n=$p.call(t),i):n.slice()},i.unknown=function(t){return arguments.length?(r=t,i):r},i.copy=function(){return Hp(e,n).unknown(r)},Rp.apply(i,arguments),i}function Vp(){var t,e,n=Hp().unknown(void 0),r=n.domain,i=n.range,a=[0,1],o=!1,s=0,c=0,u=.5;function l(){var n=r().length,l=a[1]<a[0],h=a[l-0],f=a[1-l];t=(f-h)/Math.max(1,n-s+2*c),o&&(t=Math.floor(t)),h+=(f-h-t*(n-s))*u,e=t*(1-s),o&&(h=Math.round(h),e=Math.round(e));var d=k(n).map((function(e){return h+t*e}));return i(l?d.reverse():d)}return delete n.unknown,n.domain=function(t){return arguments.length?(r(t),l()):r()},n.range=function(t){return arguments.length?(a=[+t[0],+t[1]],l()):a.slice()},n.rangeRound=function(t){return a=[+t[0],+t[1]],o=!0,l()},n.bandwidth=function(){return e},n.step=function(){return t},n.round=function(t){return arguments.length?(o=!!t,l()):o},n.padding=function(t){return arguments.length?(s=Math.min(1,c=+t),l()):s},n.paddingInner=function(t){return arguments.length?(s=Math.min(1,t),l()):s},n.paddingOuter=function(t){return arguments.length?(c=+t,l()):c},n.align=function(t){return arguments.length?(u=Math.max(0,Math.min(1,t)),l()):u},n.copy=function(){return Vp(r(),a).round(o).paddingInner(s).paddingOuter(c).align(u)},Rp.apply(l(),arguments)}function Gp(t){var e=t.copy;return t.padding=t.paddingOuter,delete t.paddingInner,delete t.paddingOuter,t.copy=function(){return Gp(e())},t}function qp(){return Gp(Vp.apply(null,arguments).paddingInner(1))}var Xp=function(t){return+t},Zp=[0,1];function Jp(t){return t}function Kp(t,e){return(e-=t=+t)?function(n){return(n-t)/e}:(n=isNaN(e)?NaN:.5,function(){return n});var n}function Qp(t){var e,n=t[0],r=t[t.length-1];return n>r&&(e=n,n=r,r=e),function(t){return Math.max(n,Math.min(r,t))}}function tg(t,e,n){var r=t[0],i=t[1],a=e[0],o=e[1];return i<r?(r=Kp(i,r),a=n(o,a)):(r=Kp(r,i),a=n(a,o)),function(t){return a(r(t))}}function eg(t,e,n){var r=Math.min(t.length,e.length)-1,i=new Array(r),a=new Array(r),o=-1;for(t[r]<t[0]&&(t=t.slice().reverse(),e=e.slice().reverse());++o<r;)i[o]=Kp(t[o],t[o+1]),a[o]=n(e[o],e[o+1]);return function(e){var n=c(t,e,1,r)-1;return a[n](i[n](e))}}function ng(t,e){return e.domain(t.domain()).range(t.range()).interpolate(t.interpolate()).clamp(t.clamp()).unknown(t.unknown())}function rg(){var t,e,n,r,i,a,o=Zp,s=Zp,c=Sn,u=Jp;function l(){return r=Math.min(o.length,s.length)>2?eg:tg,i=a=null,h}function h(e){return isNaN(e=+e)?n:(i||(i=r(o.map(t),s,c)))(t(u(e)))}return h.invert=function(n){return u(e((a||(a=r(s,o.map(t),_n)))(n)))},h.domain=function(t){return arguments.length?(o=Up.call(t,Xp),u===Jp||(u=Qp(o)),l()):o.slice()},h.range=function(t){return arguments.length?(s=$p.call(t),l()):s.slice()},h.rangeRound=function(t){return s=$p.call(t),c=up,l()},h.clamp=function(t){return arguments.length?(u=t?Qp(o):Jp,h):u!==Jp},h.interpolate=function(t){return arguments.length?(c=t,l()):c},h.unknown=function(t){return arguments.length?(n=t,h):n},function(n,r){return t=n,e=r,l()}}function ig(t,e){return rg()(t,e)}var ag=function(t,e,n,r){var i,a=S(t,e,n);switch((r=Hs(null==r?",f":r)).type){case"s":var o=Math.max(Math.abs(t),Math.abs(e));return null!=r.precision||isNaN(i=ac(a,o))||(r.precision=i),Zs(r,o);case"":case"e":case"g":case"p":case"r":null!=r.precision||isNaN(i=oc(a,Math.max(Math.abs(t),Math.abs(e))))||(r.precision=i-("e"===r.type));break;case"f":case"%":null!=r.precision||isNaN(i=ic(a))||(r.precision=i-2*("%"===r.type))}return Xs(r)};function og(t){var e=t.domain;return t.ticks=function(t){var n=e();return C(n[0],n[n.length-1],null==t?10:t)},t.tickFormat=function(t,n){var r=e();return ag(r[0],r[r.length-1],null==t?10:t,n)},t.nice=function(n){null==n&&(n=10);var r,i=e(),a=0,o=i.length-1,s=i[a],c=i[o];return c<s&&(r=s,s=c,c=r,r=a,a=o,o=r),(r=A(s,c,n))>0?r=A(s=Math.floor(s/r)*r,c=Math.ceil(c/r)*r,n):r<0&&(r=A(s=Math.ceil(s*r)/r,c=Math.floor(c*r)/r,n)),r>0?(i[a]=Math.floor(s/r)*r,i[o]=Math.ceil(c/r)*r,e(i)):r<0&&(i[a]=Math.ceil(s*r)/r,i[o]=Math.floor(c*r)/r,e(i)),t},t}function sg(){var t=ig(Jp,Jp);return t.copy=function(){return ng(t,sg())},Rp.apply(t,arguments),og(t)}function cg(t){var e;function n(t){return isNaN(t=+t)?e:t}return n.invert=n,n.domain=n.range=function(e){return arguments.length?(t=Up.call(e,Xp),n):t.slice()},n.unknown=function(t){return arguments.length?(e=t,n):e},n.copy=function(){return cg(t).unknown(e)},t=arguments.length?Up.call(t,Xp):[0,1],og(n)}var ug=function(t,e){var n,r=0,i=(t=t.slice()).length-1,a=t[r],o=t[i];return o<a&&(n=r,r=i,i=n,n=a,a=o,o=n),t[r]=e.floor(a),t[i]=e.ceil(o),t};function lg(t){return Math.log(t)}function hg(t){return Math.exp(t)}function fg(t){return-Math.log(-t)}function dg(t){return-Math.exp(-t)}function pg(t){return isFinite(t)?+("1e"+t):t<0?0:t}function gg(t){return function(e){return-t(-e)}}function yg(t){var e,n,r=t(lg,hg),i=r.domain,a=10;function o(){return e=function(t){return t===Math.E?Math.log:10===t&&Math.log10||2===t&&Math.log2||(t=Math.log(t),function(e){return Math.log(e)/t})}(a),n=function(t){return 10===t?pg:t===Math.E?Math.exp:function(e){return Math.pow(t,e)}}(a),i()[0]<0?(e=gg(e),n=gg(n),t(fg,dg)):t(lg,hg),r}return r.base=function(t){return arguments.length?(a=+t,o()):a},r.domain=function(t){return arguments.length?(i(t),o()):i()},r.ticks=function(t){var r,o=i(),s=o[0],c=o[o.length-1];(r=c<s)&&(f=s,s=c,c=f);var u,l,h,f=e(s),d=e(c),p=null==t?10:+t,g=[];if(!(a%1)&&d-f<p){if(f=Math.round(f)-1,d=Math.round(d)+1,s>0){for(;f<d;++f)for(l=1,u=n(f);l<a;++l)if(!((h=u*l)<s)){if(h>c)break;g.push(h)}}else for(;f<d;++f)for(l=a-1,u=n(f);l>=1;--l)if(!((h=u*l)<s)){if(h>c)break;g.push(h)}}else g=C(f,d,Math.min(d-f,p)).map(n);return r?g.reverse():g},r.tickFormat=function(t,i){if(null==i&&(i=10===a?".0e":","),"function"!=typeof i&&(i=Xs(i)),t===1/0)return i;null==t&&(t=10);var o=Math.max(1,a*t/r.ticks().length);return function(t){var r=t/n(Math.round(e(t)));return r*a<a-.5&&(r*=a),r<=o?i(t):""}},r.nice=function(){return i(ug(i(),{floor:function(t){return n(Math.floor(e(t)))},ceil:function(t){return n(Math.ceil(e(t)))}}))},r}function vg(){var t=yg(rg()).domain([1,10]);return t.copy=function(){return ng(t,vg()).base(t.base())},Rp.apply(t,arguments),t}function mg(t){return function(e){return Math.sign(e)*Math.log1p(Math.abs(e/t))}}function bg(t){return function(e){return Math.sign(e)*Math.expm1(Math.abs(e))*t}}function xg(t){var e=1,n=t(mg(e),bg(e));return n.constant=function(n){return arguments.length?t(mg(e=+n),bg(e)):e},og(n)}function _g(){var t=xg(rg());return t.copy=function(){return ng(t,_g()).constant(t.constant())},Rp.apply(t,arguments)}function kg(t){return function(e){return e<0?-Math.pow(-e,t):Math.pow(e,t)}}function wg(t){return t<0?-Math.sqrt(-t):Math.sqrt(t)}function Eg(t){return t<0?-t*t:t*t}function Tg(t){var e=t(Jp,Jp),n=1;function r(){return 1===n?t(Jp,Jp):.5===n?t(wg,Eg):t(kg(n),kg(1/n))}return e.exponent=function(t){return arguments.length?(n=+t,r()):n},og(e)}function Cg(){var t=Tg(rg());return t.copy=function(){return ng(t,Cg()).exponent(t.exponent())},Rp.apply(t,arguments),t}function Ag(){return Cg.apply(null,arguments).exponent(.5)}function Sg(){var t,e=[],n=[],i=[];function a(){var t=0,r=Math.max(1,n.length);for(i=new Array(r-1);++t<r;)i[t-1]=D(e,t/r);return o}function o(e){return isNaN(e=+e)?t:n[c(i,e)]}return o.invertExtent=function(t){var r=n.indexOf(t);return r<0?[NaN,NaN]:[r>0?i[r-1]:e[0],r<i.length?i[r]:e[e.length-1]]},o.domain=function(t){if(!arguments.length)return e.slice();e=[];for(var n,i=0,o=t.length;i<o;++i)null==(n=t[i])||isNaN(n=+n)||e.push(n);return e.sort(r),a()},o.range=function(t){return arguments.length?(n=$p.call(t),a()):n.slice()},o.unknown=function(e){return arguments.length?(t=e,o):t},o.quantiles=function(){return i.slice()},o.copy=function(){return Sg().domain(e).range(n).unknown(t)},Rp.apply(o,arguments)}function Mg(){var t,e=0,n=1,r=1,i=[.5],a=[0,1];function o(e){return e<=e?a[c(i,e,0,r)]:t}function s(){var t=-1;for(i=new Array(r);++t<r;)i[t]=((t+1)*n-(t-r)*e)/(r+1);return o}return o.domain=function(t){return arguments.length?(e=+t[0],n=+t[1],s()):[e,n]},o.range=function(t){return arguments.length?(r=(a=$p.call(t)).length-1,s()):a.slice()},o.invertExtent=function(t){var o=a.indexOf(t);return o<0?[NaN,NaN]:o<1?[e,i[0]]:o>=r?[i[r-1],n]:[i[o-1],i[o]]},o.unknown=function(e){return arguments.length?(t=e,o):o},o.thresholds=function(){return i.slice()},o.copy=function(){return Mg().domain([e,n]).range(a).unknown(t)},Rp.apply(og(o),arguments)}function Og(){var t,e=[.5],n=[0,1],r=1;function i(i){return i<=i?n[c(e,i,0,r)]:t}return i.domain=function(t){return arguments.length?(e=$p.call(t),r=Math.min(e.length,n.length-1),i):e.slice()},i.range=function(t){return arguments.length?(n=$p.call(t),r=Math.min(e.length,n.length-1),i):n.slice()},i.invertExtent=function(t){var r=n.indexOf(t);return[e[r-1],e[r]]},i.unknown=function(e){return arguments.length?(t=e,i):t},i.copy=function(){return Og().domain(e).range(n).unknown(t)},Rp.apply(i,arguments)}var Dg=new Date,Ng=new Date;function Bg(t,e,n,r){function i(e){return t(e=0===arguments.length?new Date:new Date(+e)),e}return i.floor=function(e){return t(e=new Date(+e)),e},i.ceil=function(n){return t(n=new Date(n-1)),e(n,1),t(n),n},i.round=function(t){var e=i(t),n=i.ceil(t);return t-e<n-t?e:n},i.offset=function(t,n){return e(t=new Date(+t),null==n?1:Math.floor(n)),t},i.range=function(n,r,a){var o,s=[];if(n=i.ceil(n),a=null==a?1:Math.floor(a),!(n<r&&a>0))return s;do{s.push(o=new Date(+n)),e(n,a),t(n)}while(o<n&&n<r);return s},i.filter=function(n){return Bg((function(e){if(e>=e)for(;t(e),!n(e);)e.setTime(e-1)}),(function(t,r){if(t>=t)if(r<0)for(;++r<=0;)for(;e(t,-1),!n(t););else for(;--r>=0;)for(;e(t,1),!n(t););}))},n&&(i.count=function(e,r){return Dg.setTime(+e),Ng.setTime(+r),t(Dg),t(Ng),Math.floor(n(Dg,Ng))},i.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?function(e){return r(e)%t==0}:function(e){return i.count(0,e)%t==0}):i:null}),i}var Lg=Bg((function(t){t.setMonth(0,1),t.setHours(0,0,0,0)}),(function(t,e){t.setFullYear(t.getFullYear()+e)}),(function(t,e){return e.getFullYear()-t.getFullYear()}),(function(t){return t.getFullYear()}));Lg.every=function(t){return isFinite(t=Math.floor(t))&&t>0?Bg((function(e){e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)}),(function(e,n){e.setFullYear(e.getFullYear()+n*t)})):null};var Fg=Lg,Pg=Lg.range,Ig=Bg((function(t){t.setDate(1),t.setHours(0,0,0,0)}),(function(t,e){t.setMonth(t.getMonth()+e)}),(function(t,e){return e.getMonth()-t.getMonth()+12*(e.getFullYear()-t.getFullYear())}),(function(t){return t.getMonth()})),jg=Ig,Rg=Ig.range;function Yg(t){return Bg((function(e){e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)}),(function(t,e){t.setDate(t.getDate()+7*e)}),(function(t,e){return(e-t-6e4*(e.getTimezoneOffset()-t.getTimezoneOffset()))/6048e5}))}var zg=Yg(0),Ug=Yg(1),$g=Yg(2),Wg=Yg(3),Hg=Yg(4),Vg=Yg(5),Gg=Yg(6),qg=zg.range,Xg=Ug.range,Zg=$g.range,Jg=Wg.range,Kg=Hg.range,Qg=Vg.range,ty=Gg.range,ey=Bg((function(t){t.setHours(0,0,0,0)}),(function(t,e){t.setDate(t.getDate()+e)}),(function(t,e){return(e-t-6e4*(e.getTimezoneOffset()-t.getTimezoneOffset()))/864e5}),(function(t){return t.getDate()-1})),ny=ey,ry=ey.range,iy=Bg((function(t){t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds()-6e4*t.getMinutes())}),(function(t,e){t.setTime(+t+36e5*e)}),(function(t,e){return(e-t)/36e5}),(function(t){return t.getHours()})),ay=iy,oy=iy.range,sy=Bg((function(t){t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds())}),(function(t,e){t.setTime(+t+6e4*e)}),(function(t,e){return(e-t)/6e4}),(function(t){return t.getMinutes()})),cy=sy,uy=sy.range,ly=Bg((function(t){t.setTime(t-t.getMilliseconds())}),(function(t,e){t.setTime(+t+1e3*e)}),(function(t,e){return(e-t)/1e3}),(function(t){return t.getUTCSeconds()})),hy=ly,fy=ly.range,dy=Bg((function(){}),(function(t,e){t.setTime(+t+e)}),(function(t,e){return e-t}));dy.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?Bg((function(e){e.setTime(Math.floor(e/t)*t)}),(function(e,n){e.setTime(+e+n*t)}),(function(e,n){return(n-e)/t})):dy:null};var py=dy,gy=dy.range;function yy(t){return Bg((function(e){e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+7*e)}),(function(t,e){return(e-t)/6048e5}))}var vy=yy(0),my=yy(1),by=yy(2),xy=yy(3),_y=yy(4),ky=yy(5),wy=yy(6),Ey=vy.range,Ty=my.range,Cy=by.range,Ay=xy.range,Sy=_y.range,My=ky.range,Oy=wy.range,Dy=Bg((function(t){t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+e)}),(function(t,e){return(e-t)/864e5}),(function(t){return t.getUTCDate()-1})),Ny=Dy,By=Dy.range,Ly=Bg((function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCFullYear(t.getUTCFullYear()+e)}),(function(t,e){return e.getUTCFullYear()-t.getUTCFullYear()}),(function(t){return t.getUTCFullYear()}));Ly.every=function(t){return isFinite(t=Math.floor(t))&&t>0?Bg((function(e){e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)}),(function(e,n){e.setUTCFullYear(e.getUTCFullYear()+n*t)})):null};var Fy=Ly,Py=Ly.range;function Iy(t){if(0<=t.y&&t.y<100){var e=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return e.setFullYear(t.y),e}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function jy(t){if(0<=t.y&&t.y<100){var e=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return e.setUTCFullYear(t.y),e}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function Ry(t,e,n){return{y:t,m:e,d:n,H:0,M:0,S:0,L:0}}function Yy(t){var e=t.dateTime,n=t.date,r=t.time,i=t.periods,a=t.days,o=t.shortDays,s=t.months,c=t.shortMonths,u=Ky(i),l=Qy(i),h=Ky(a),f=Qy(a),d=Ky(o),p=Qy(o),g=Ky(s),y=Qy(s),v=Ky(c),m=Qy(c),b={a:function(t){return o[t.getDay()]},A:function(t){return a[t.getDay()]},b:function(t){return c[t.getMonth()]},B:function(t){return s[t.getMonth()]},c:null,d:xv,e:xv,f:Tv,H:_v,I:kv,j:wv,L:Ev,m:Cv,M:Av,p:function(t){return i[+(t.getHours()>=12)]},q:function(t){return 1+~~(t.getMonth()/3)},Q:em,s:nm,S:Sv,u:Mv,U:Ov,V:Dv,w:Nv,W:Bv,x:null,X:null,y:Lv,Y:Fv,Z:Pv,"%":tm},x={a:function(t){return o[t.getUTCDay()]},A:function(t){return a[t.getUTCDay()]},b:function(t){return c[t.getUTCMonth()]},B:function(t){return s[t.getUTCMonth()]},c:null,d:Iv,e:Iv,f:Uv,H:jv,I:Rv,j:Yv,L:zv,m:$v,M:Wv,p:function(t){return i[+(t.getUTCHours()>=12)]},q:function(t){return 1+~~(t.getUTCMonth()/3)},Q:em,s:nm,S:Hv,u:Vv,U:Gv,V:qv,w:Xv,W:Zv,x:null,X:null,y:Jv,Y:Kv,Z:Qv,"%":tm},_={a:function(t,e,n){var r=d.exec(e.slice(n));return r?(t.w=p[r[0].toLowerCase()],n+r[0].length):-1},A:function(t,e,n){var r=h.exec(e.slice(n));return r?(t.w=f[r[0].toLowerCase()],n+r[0].length):-1},b:function(t,e,n){var r=v.exec(e.slice(n));return r?(t.m=m[r[0].toLowerCase()],n+r[0].length):-1},B:function(t,e,n){var r=g.exec(e.slice(n));return r?(t.m=y[r[0].toLowerCase()],n+r[0].length):-1},c:function(t,n,r){return E(t,e,n,r)},d:lv,e:lv,f:yv,H:fv,I:fv,j:hv,L:gv,m:uv,M:dv,p:function(t,e,n){var r=u.exec(e.slice(n));return r?(t.p=l[r[0].toLowerCase()],n+r[0].length):-1},q:cv,Q:mv,s:bv,S:pv,u:ev,U:nv,V:rv,w:tv,W:iv,x:function(t,e,r){return E(t,n,e,r)},X:function(t,e,n){return E(t,r,e,n)},y:ov,Y:av,Z:sv,"%":vv};function k(t,e){return function(n){var r,i,a,o=[],s=-1,c=0,u=t.length;for(n instanceof Date||(n=new Date(+n));++s<u;)37===t.charCodeAt(s)&&(o.push(t.slice(c,s)),null!=(i=Vy[r=t.charAt(++s)])?r=t.charAt(++s):i="e"===r?" ":"0",(a=e[r])&&(r=a(n,i)),o.push(r),c=s+1);return o.push(t.slice(c,s)),o.join("")}}function w(t,e){return function(n){var r,i,a=Ry(1900,void 0,1);if(E(a,t,n+="",0)!=n.length)return null;if("Q"in a)return new Date(a.Q);if("s"in a)return new Date(1e3*a.s+("L"in a?a.L:0));if(!e||"Z"in a||(a.Z=0),"p"in a&&(a.H=a.H%12+12*a.p),void 0===a.m&&(a.m="q"in a?a.q:0),"V"in a){if(a.V<1||a.V>53)return null;"w"in a||(a.w=1),"Z"in a?(i=(r=jy(Ry(a.y,0,1))).getUTCDay(),r=i>4||0===i?my.ceil(r):my(r),r=Ny.offset(r,7*(a.V-1)),a.y=r.getUTCFullYear(),a.m=r.getUTCMonth(),a.d=r.getUTCDate()+(a.w+6)%7):(i=(r=Iy(Ry(a.y,0,1))).getDay(),r=i>4||0===i?Ug.ceil(r):Ug(r),r=ny.offset(r,7*(a.V-1)),a.y=r.getFullYear(),a.m=r.getMonth(),a.d=r.getDate()+(a.w+6)%7)}else("W"in a||"U"in a)&&("w"in a||(a.w="u"in a?a.u%7:"W"in a?1:0),i="Z"in a?jy(Ry(a.y,0,1)).getUTCDay():Iy(Ry(a.y,0,1)).getDay(),a.m=0,a.d="W"in a?(a.w+6)%7+7*a.W-(i+5)%7:a.w+7*a.U-(i+6)%7);return"Z"in a?(a.H+=a.Z/100|0,a.M+=a.Z%100,jy(a)):Iy(a)}}function E(t,e,n,r){for(var i,a,o=0,s=e.length,c=n.length;o<s;){if(r>=c)return-1;if(37===(i=e.charCodeAt(o++))){if(i=e.charAt(o++),!(a=_[i in Vy?e.charAt(o++):i])||(r=a(t,n,r))<0)return-1}else if(i!=n.charCodeAt(r++))return-1}return r}return(b.x=k(n,b),b.X=k(r,b),b.c=k(e,b),x.x=k(n,x),x.X=k(r,x),x.c=k(e,x),{format:function(t){var e=k(t+="",b);return e.toString=function(){return t},e},parse:function(t){var e=w(t+="",!1);return e.toString=function(){return t},e},utcFormat:function(t){var e=k(t+="",x);return e.toString=function(){return t},e},utcParse:function(t){var e=w(t+="",!0);return e.toString=function(){return t},e}})}var zy,Uy,$y,Wy,Hy,Vy={"-":"",_:" ",0:"0"},Gy=/^\s*\d+/,qy=/^%/,Xy=/[\\^$*+?|[\]().{}]/g;function Zy(t,e,n){var r=t<0?"-":"",i=(r?-t:t)+"",a=i.length;return r+(a<n?new Array(n-a+1).join(e)+i:i)}function Jy(t){return t.replace(Xy,"\\$&")}function Ky(t){return new RegExp("^(?:"+t.map(Jy).join("|")+")","i")}function Qy(t){for(var e={},n=-1,r=t.length;++n<r;)e[t[n].toLowerCase()]=n;return e}function tv(t,e,n){var r=Gy.exec(e.slice(n,n+1));return r?(t.w=+r[0],n+r[0].length):-1}function ev(t,e,n){var r=Gy.exec(e.slice(n,n+1));return r?(t.u=+r[0],n+r[0].length):-1}function nv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.U=+r[0],n+r[0].length):-1}function rv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.V=+r[0],n+r[0].length):-1}function iv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.W=+r[0],n+r[0].length):-1}function av(t,e,n){var r=Gy.exec(e.slice(n,n+4));return r?(t.y=+r[0],n+r[0].length):-1}function ov(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.y=+r[0]+(+r[0]>68?1900:2e3),n+r[0].length):-1}function sv(t,e,n){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(n,n+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),n+r[0].length):-1}function cv(t,e,n){var r=Gy.exec(e.slice(n,n+1));return r?(t.q=3*r[0]-3,n+r[0].length):-1}function uv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.m=r[0]-1,n+r[0].length):-1}function lv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.d=+r[0],n+r[0].length):-1}function hv(t,e,n){var r=Gy.exec(e.slice(n,n+3));return r?(t.m=0,t.d=+r[0],n+r[0].length):-1}function fv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.H=+r[0],n+r[0].length):-1}function dv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.M=+r[0],n+r[0].length):-1}function pv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.S=+r[0],n+r[0].length):-1}function gv(t,e,n){var r=Gy.exec(e.slice(n,n+3));return r?(t.L=+r[0],n+r[0].length):-1}function yv(t,e,n){var r=Gy.exec(e.slice(n,n+6));return r?(t.L=Math.floor(r[0]/1e3),n+r[0].length):-1}function vv(t,e,n){var r=qy.exec(e.slice(n,n+1));return r?n+r[0].length:-1}function mv(t,e,n){var r=Gy.exec(e.slice(n));return r?(t.Q=+r[0],n+r[0].length):-1}function bv(t,e,n){var r=Gy.exec(e.slice(n));return r?(t.s=+r[0],n+r[0].length):-1}function xv(t,e){return Zy(t.getDate(),e,2)}function _v(t,e){return Zy(t.getHours(),e,2)}function kv(t,e){return Zy(t.getHours()%12||12,e,2)}function wv(t,e){return Zy(1+ny.count(Fg(t),t),e,3)}function Ev(t,e){return Zy(t.getMilliseconds(),e,3)}function Tv(t,e){return Ev(t,e)+"000"}function Cv(t,e){return Zy(t.getMonth()+1,e,2)}function Av(t,e){return Zy(t.getMinutes(),e,2)}function Sv(t,e){return Zy(t.getSeconds(),e,2)}function Mv(t){var e=t.getDay();return 0===e?7:e}function Ov(t,e){return Zy(zg.count(Fg(t)-1,t),e,2)}function Dv(t,e){var n=t.getDay();return t=n>=4||0===n?Hg(t):Hg.ceil(t),Zy(Hg.count(Fg(t),t)+(4===Fg(t).getDay()),e,2)}function Nv(t){return t.getDay()}function Bv(t,e){return Zy(Ug.count(Fg(t)-1,t),e,2)}function Lv(t,e){return Zy(t.getFullYear()%100,e,2)}function Fv(t,e){return Zy(t.getFullYear()%1e4,e,4)}function Pv(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+Zy(e/60|0,"0",2)+Zy(e%60,"0",2)}function Iv(t,e){return Zy(t.getUTCDate(),e,2)}function jv(t,e){return Zy(t.getUTCHours(),e,2)}function Rv(t,e){return Zy(t.getUTCHours()%12||12,e,2)}function Yv(t,e){return Zy(1+Ny.count(Fy(t),t),e,3)}function zv(t,e){return Zy(t.getUTCMilliseconds(),e,3)}function Uv(t,e){return zv(t,e)+"000"}function $v(t,e){return Zy(t.getUTCMonth()+1,e,2)}function Wv(t,e){return Zy(t.getUTCMinutes(),e,2)}function Hv(t,e){return Zy(t.getUTCSeconds(),e,2)}function Vv(t){var e=t.getUTCDay();return 0===e?7:e}function Gv(t,e){return Zy(vy.count(Fy(t)-1,t),e,2)}function qv(t,e){var n=t.getUTCDay();return t=n>=4||0===n?_y(t):_y.ceil(t),Zy(_y.count(Fy(t),t)+(4===Fy(t).getUTCDay()),e,2)}function Xv(t){return t.getUTCDay()}function Zv(t,e){return Zy(my.count(Fy(t)-1,t),e,2)}function Jv(t,e){return Zy(t.getUTCFullYear()%100,e,2)}function Kv(t,e){return Zy(t.getUTCFullYear()%1e4,e,4)}function Qv(){return"+0000"}function tm(){return"%"}function em(t){return+t}function nm(t){return Math.floor(+t/1e3)}function rm(t){return zy=Yy(t),Uy=zy.format,$y=zy.parse,Wy=zy.utcFormat,Hy=zy.utcParse,zy}rm({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function im(t){return new Date(t)}function am(t){return t instanceof Date?+t:+new Date(+t)}function om(t,e,n,r,a,o,s,c,u){var l=ig(Jp,Jp),h=l.invert,f=l.domain,d=u(".%L"),p=u(":%S"),g=u("%I:%M"),y=u("%I %p"),v=u("%a %d"),m=u("%b %d"),b=u("%B"),x=u("%Y"),_=[[s,1,1e3],[s,5,5e3],[s,15,15e3],[s,30,3e4],[o,1,6e4],[o,5,3e5],[o,15,9e5],[o,30,18e5],[a,1,36e5],[a,3,108e5],[a,6,216e5],[a,12,432e5],[r,1,864e5],[r,2,1728e5],[n,1,6048e5],[e,1,2592e6],[e,3,7776e6],[t,1,31536e6]];function k(i){return(s(i)<i?d:o(i)<i?p:a(i)<i?g:r(i)<i?y:e(i)<i?n(i)<i?v:m:t(i)<i?b:x)(i)}function w(e,n,r,a){if(null==e&&(e=10),"number"==typeof e){var o=Math.abs(r-n)/e,s=i((function(t){return t[2]})).right(_,o);s===_.length?(a=S(n/31536e6,r/31536e6,e),e=t):s?(a=(s=_[o/_[s-1][2]<_[s][2]/o?s-1:s])[1],e=s[0]):(a=Math.max(S(n,r,e),1),e=c)}return null==a?e:e.every(a)}return l.invert=function(t){return new Date(h(t))},l.domain=function(t){return arguments.length?f(Up.call(t,am)):f().map(im)},l.ticks=function(t,e){var n,r=f(),i=r[0],a=r[r.length-1],o=a<i;return o&&(n=i,i=a,a=n),n=(n=w(t,i,a,e))?n.range(i,a+1):[],o?n.reverse():n},l.tickFormat=function(t,e){return null==e?k:u(e)},l.nice=function(t,e){var n=f();return(t=w(t,n[0],n[n.length-1],e))?f(ug(n,t)):l},l.copy=function(){return ng(l,om(t,e,n,r,a,o,s,c,u))},l}var sm=function(){return Rp.apply(om(Fg,jg,zg,ny,ay,cy,hy,py,Uy).domain([new Date(2e3,0,1),new Date(2e3,0,2)]),arguments)},cm=Bg((function(t){t.setUTCDate(1),t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCMonth(t.getUTCMonth()+e)}),(function(t,e){return e.getUTCMonth()-t.getUTCMonth()+12*(e.getUTCFullYear()-t.getUTCFullYear())}),(function(t){return t.getUTCMonth()})),um=cm,lm=cm.range,hm=Bg((function(t){t.setUTCMinutes(0,0,0)}),(function(t,e){t.setTime(+t+36e5*e)}),(function(t,e){return(e-t)/36e5}),(function(t){return t.getUTCHours()})),fm=hm,dm=hm.range,pm=Bg((function(t){t.setUTCSeconds(0,0)}),(function(t,e){t.setTime(+t+6e4*e)}),(function(t,e){return(e-t)/6e4}),(function(t){return t.getUTCMinutes()})),gm=pm,ym=pm.range,vm=function(){return Rp.apply(om(Fy,um,vy,Ny,fm,gm,hy,py,Wy).domain([Date.UTC(2e3,0,1),Date.UTC(2e3,0,2)]),arguments)};function mm(){var t,e,n,r,i,a=0,o=1,s=Jp,c=!1;function u(e){return isNaN(e=+e)?i:s(0===n?.5:(e=(r(e)-t)*n,c?Math.max(0,Math.min(1,e)):e))}return u.domain=function(i){return arguments.length?(t=r(a=+i[0]),e=r(o=+i[1]),n=t===e?0:1/(e-t),u):[a,o]},u.clamp=function(t){return arguments.length?(c=!!t,u):c},u.interpolator=function(t){return arguments.length?(s=t,u):s},u.unknown=function(t){return arguments.length?(i=t,u):i},function(i){return r=i,t=i(a),e=i(o),n=t===e?0:1/(e-t),u}}function bm(t,e){return e.domain(t.domain()).interpolator(t.interpolator()).clamp(t.clamp()).unknown(t.unknown())}function xm(){var t=og(mm()(Jp));return t.copy=function(){return bm(t,xm())},Yp.apply(t,arguments)}function _m(){var t=yg(mm()).domain([1,10]);return t.copy=function(){return bm(t,_m()).base(t.base())},Yp.apply(t,arguments)}function km(){var t=xg(mm());return t.copy=function(){return bm(t,km()).constant(t.constant())},Yp.apply(t,arguments)}function wm(){var t=Tg(mm());return t.copy=function(){return bm(t,wm()).exponent(t.exponent())},Yp.apply(t,arguments)}function Em(){return wm.apply(null,arguments).exponent(.5)}function Tm(){var t=[],e=Jp;function n(n){if(!isNaN(n=+n))return e((c(t,n)-1)/(t.length-1))}return n.domain=function(e){if(!arguments.length)return t.slice();t=[];for(var i,a=0,o=e.length;a<o;++a)null==(i=e[a])||isNaN(i=+i)||t.push(i);return t.sort(r),n},n.interpolator=function(t){return arguments.length?(e=t,n):e},n.copy=function(){return Tm(e).domain(t)},Yp.apply(n,arguments)}function Cm(){var t,e,n,r,i,a,o,s=0,c=.5,u=1,l=Jp,h=!1;function f(t){return isNaN(t=+t)?o:(t=.5+((t=+a(t))-e)*(t<e?r:i),l(h?Math.max(0,Math.min(1,t)):t))}return f.domain=function(o){return arguments.length?(t=a(s=+o[0]),e=a(c=+o[1]),n=a(u=+o[2]),r=t===e?0:.5/(e-t),i=e===n?0:.5/(n-e),f):[s,c,u]},f.clamp=function(t){return arguments.length?(h=!!t,f):h},f.interpolator=function(t){return arguments.length?(l=t,f):l},f.unknown=function(t){return arguments.length?(o=t,f):o},function(o){return a=o,t=o(s),e=o(c),n=o(u),r=t===e?0:.5/(e-t),i=e===n?0:.5/(n-e),f}}function Am(){var t=og(Cm()(Jp));return t.copy=function(){return bm(t,Am())},Yp.apply(t,arguments)}function Sm(){var t=yg(Cm()).domain([.1,1,10]);return t.copy=function(){return bm(t,Sm()).base(t.base())},Yp.apply(t,arguments)}function Mm(){var t=xg(Cm());return t.copy=function(){return bm(t,Mm()).constant(t.constant())},Yp.apply(t,arguments)}function Om(){var t=Tg(Cm());return t.copy=function(){return bm(t,Om()).exponent(t.exponent())},Yp.apply(t,arguments)}function Dm(){return Om.apply(null,arguments).exponent(.5)}var Nm=function(t){for(var e=t.length/6|0,n=new Array(e),r=0;r<e;)n[r]="#"+t.slice(6*r,6*++r);return n},Bm=Nm("1f77b4ff7f0e2ca02cd627289467bd8c564be377c27f7f7fbcbd2217becf"),Lm=Nm("7fc97fbeaed4fdc086ffff99386cb0f0027fbf5b17666666"),Fm=Nm("1b9e77d95f027570b3e7298a66a61ee6ab02a6761d666666"),Pm=Nm("a6cee31f78b4b2df8a33a02cfb9a99e31a1cfdbf6fff7f00cab2d66a3d9affff99b15928"),Im=Nm("fbb4aeb3cde3ccebc5decbe4fed9a6ffffcce5d8bdfddaecf2f2f2"),jm=Nm("b3e2cdfdcdaccbd5e8f4cae4e6f5c9fff2aef1e2cccccccc"),Rm=Nm("e41a1c377eb84daf4a984ea3ff7f00ffff33a65628f781bf999999"),Ym=Nm("66c2a5fc8d628da0cbe78ac3a6d854ffd92fe5c494b3b3b3"),zm=Nm("8dd3c7ffffb3bebadafb807280b1d3fdb462b3de69fccde5d9d9d9bc80bdccebc5ffed6f"),Um=Nm("4e79a7f28e2ce1575976b7b259a14fedc949af7aa1ff9da79c755fbab0ab"),$m=function(t){return pn(t[t.length-1])},Wm=new Array(3).concat("d8b365f5f5f55ab4ac","a6611adfc27d80cdc1018571","a6611adfc27df5f5f580cdc1018571","8c510ad8b365f6e8c3c7eae55ab4ac01665e","8c510ad8b365f6e8c3f5f5f5c7eae55ab4ac01665e","8c510abf812ddfc27df6e8c3c7eae580cdc135978f01665e","8c510abf812ddfc27df6e8c3f5f5f5c7eae580cdc135978f01665e","5430058c510abf812ddfc27df6e8c3c7eae580cdc135978f01665e003c30","5430058c510abf812ddfc27df6e8c3f5f5f5c7eae580cdc135978f01665e003c30").map(Nm),Hm=$m(Wm),Vm=new Array(3).concat("af8dc3f7f7f77fbf7b","7b3294c2a5cfa6dba0008837","7b3294c2a5cff7f7f7a6dba0008837","762a83af8dc3e7d4e8d9f0d37fbf7b1b7837","762a83af8dc3e7d4e8f7f7f7d9f0d37fbf7b1b7837","762a839970abc2a5cfe7d4e8d9f0d3a6dba05aae611b7837","762a839970abc2a5cfe7d4e8f7f7f7d9f0d3a6dba05aae611b7837","40004b762a839970abc2a5cfe7d4e8d9f0d3a6dba05aae611b783700441b","40004b762a839970abc2a5cfe7d4e8f7f7f7d9f0d3a6dba05aae611b783700441b").map(Nm),Gm=$m(Vm),qm=new Array(3).concat("e9a3c9f7f7f7a1d76a","d01c8bf1b6dab8e1864dac26","d01c8bf1b6daf7f7f7b8e1864dac26","c51b7de9a3c9fde0efe6f5d0a1d76a4d9221","c51b7de9a3c9fde0eff7f7f7e6f5d0a1d76a4d9221","c51b7dde77aef1b6dafde0efe6f5d0b8e1867fbc414d9221","c51b7dde77aef1b6dafde0eff7f7f7e6f5d0b8e1867fbc414d9221","8e0152c51b7dde77aef1b6dafde0efe6f5d0b8e1867fbc414d9221276419","8e0152c51b7dde77aef1b6dafde0eff7f7f7e6f5d0b8e1867fbc414d9221276419").map(Nm),Xm=$m(qm),Zm=new Array(3).concat("998ec3f7f7f7f1a340","5e3c99b2abd2fdb863e66101","5e3c99b2abd2f7f7f7fdb863e66101","542788998ec3d8daebfee0b6f1a340b35806","542788998ec3d8daebf7f7f7fee0b6f1a340b35806","5427888073acb2abd2d8daebfee0b6fdb863e08214b35806","5427888073acb2abd2d8daebf7f7f7fee0b6fdb863e08214b35806","2d004b5427888073acb2abd2d8daebfee0b6fdb863e08214b358067f3b08","2d004b5427888073acb2abd2d8daebf7f7f7fee0b6fdb863e08214b358067f3b08").map(Nm),Jm=$m(Zm),Km=new Array(3).concat("ef8a62f7f7f767a9cf","ca0020f4a58292c5de0571b0","ca0020f4a582f7f7f792c5de0571b0","b2182bef8a62fddbc7d1e5f067a9cf2166ac","b2182bef8a62fddbc7f7f7f7d1e5f067a9cf2166ac","b2182bd6604df4a582fddbc7d1e5f092c5de4393c32166ac","b2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac","67001fb2182bd6604df4a582fddbc7d1e5f092c5de4393c32166ac053061","67001fb2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac053061").map(Nm),Qm=$m(Km),tb=new Array(3).concat("ef8a62ffffff999999","ca0020f4a582bababa404040","ca0020f4a582ffffffbababa404040","b2182bef8a62fddbc7e0e0e09999994d4d4d","b2182bef8a62fddbc7ffffffe0e0e09999994d4d4d","b2182bd6604df4a582fddbc7e0e0e0bababa8787874d4d4d","b2182bd6604df4a582fddbc7ffffffe0e0e0bababa8787874d4d4d","67001fb2182bd6604df4a582fddbc7e0e0e0bababa8787874d4d4d1a1a1a","67001fb2182bd6604df4a582fddbc7ffffffe0e0e0bababa8787874d4d4d1a1a1a").map(Nm),eb=$m(tb),nb=new Array(3).concat("fc8d59ffffbf91bfdb","d7191cfdae61abd9e92c7bb6","d7191cfdae61ffffbfabd9e92c7bb6","d73027fc8d59fee090e0f3f891bfdb4575b4","d73027fc8d59fee090ffffbfe0f3f891bfdb4575b4","d73027f46d43fdae61fee090e0f3f8abd9e974add14575b4","d73027f46d43fdae61fee090ffffbfe0f3f8abd9e974add14575b4","a50026d73027f46d43fdae61fee090e0f3f8abd9e974add14575b4313695","a50026d73027f46d43fdae61fee090ffffbfe0f3f8abd9e974add14575b4313695").map(Nm),rb=$m(nb),ib=new Array(3).concat("fc8d59ffffbf91cf60","d7191cfdae61a6d96a1a9641","d7191cfdae61ffffbfa6d96a1a9641","d73027fc8d59fee08bd9ef8b91cf601a9850","d73027fc8d59fee08bffffbfd9ef8b91cf601a9850","d73027f46d43fdae61fee08bd9ef8ba6d96a66bd631a9850","d73027f46d43fdae61fee08bffffbfd9ef8ba6d96a66bd631a9850","a50026d73027f46d43fdae61fee08bd9ef8ba6d96a66bd631a9850006837","a50026d73027f46d43fdae61fee08bffffbfd9ef8ba6d96a66bd631a9850006837").map(Nm),ab=$m(ib),ob=new Array(3).concat("fc8d59ffffbf99d594","d7191cfdae61abdda42b83ba","d7191cfdae61ffffbfabdda42b83ba","d53e4ffc8d59fee08be6f59899d5943288bd","d53e4ffc8d59fee08bffffbfe6f59899d5943288bd","d53e4ff46d43fdae61fee08be6f598abdda466c2a53288bd","d53e4ff46d43fdae61fee08bffffbfe6f598abdda466c2a53288bd","9e0142d53e4ff46d43fdae61fee08be6f598abdda466c2a53288bd5e4fa2","9e0142d53e4ff46d43fdae61fee08bffffbfe6f598abdda466c2a53288bd5e4fa2").map(Nm),sb=$m(ob),cb=new Array(3).concat("e5f5f999d8c92ca25f","edf8fbb2e2e266c2a4238b45","edf8fbb2e2e266c2a42ca25f006d2c","edf8fbccece699d8c966c2a42ca25f006d2c","edf8fbccece699d8c966c2a441ae76238b45005824","f7fcfde5f5f9ccece699d8c966c2a441ae76238b45005824","f7fcfde5f5f9ccece699d8c966c2a441ae76238b45006d2c00441b").map(Nm),ub=$m(cb),lb=new Array(3).concat("e0ecf49ebcda8856a7","edf8fbb3cde38c96c688419d","edf8fbb3cde38c96c68856a7810f7c","edf8fbbfd3e69ebcda8c96c68856a7810f7c","edf8fbbfd3e69ebcda8c96c68c6bb188419d6e016b","f7fcfde0ecf4bfd3e69ebcda8c96c68c6bb188419d6e016b","f7fcfde0ecf4bfd3e69ebcda8c96c68c6bb188419d810f7c4d004b").map(Nm),hb=$m(lb),fb=new Array(3).concat("e0f3dba8ddb543a2ca","f0f9e8bae4bc7bccc42b8cbe","f0f9e8bae4bc7bccc443a2ca0868ac","f0f9e8ccebc5a8ddb57bccc443a2ca0868ac","f0f9e8ccebc5a8ddb57bccc44eb3d32b8cbe08589e","f7fcf0e0f3dbccebc5a8ddb57bccc44eb3d32b8cbe08589e","f7fcf0e0f3dbccebc5a8ddb57bccc44eb3d32b8cbe0868ac084081").map(Nm),db=$m(fb),pb=new Array(3).concat("fee8c8fdbb84e34a33","fef0d9fdcc8afc8d59d7301f","fef0d9fdcc8afc8d59e34a33b30000","fef0d9fdd49efdbb84fc8d59e34a33b30000","fef0d9fdd49efdbb84fc8d59ef6548d7301f990000","fff7ecfee8c8fdd49efdbb84fc8d59ef6548d7301f990000","fff7ecfee8c8fdd49efdbb84fc8d59ef6548d7301fb300007f0000").map(Nm),gb=$m(pb),yb=new Array(3).concat("ece2f0a6bddb1c9099","f6eff7bdc9e167a9cf02818a","f6eff7bdc9e167a9cf1c9099016c59","f6eff7d0d1e6a6bddb67a9cf1c9099016c59","f6eff7d0d1e6a6bddb67a9cf3690c002818a016450","fff7fbece2f0d0d1e6a6bddb67a9cf3690c002818a016450","fff7fbece2f0d0d1e6a6bddb67a9cf3690c002818a016c59014636").map(Nm),vb=$m(yb),mb=new Array(3).concat("ece7f2a6bddb2b8cbe","f1eef6bdc9e174a9cf0570b0","f1eef6bdc9e174a9cf2b8cbe045a8d","f1eef6d0d1e6a6bddb74a9cf2b8cbe045a8d","f1eef6d0d1e6a6bddb74a9cf3690c00570b0034e7b","fff7fbece7f2d0d1e6a6bddb74a9cf3690c00570b0034e7b","fff7fbece7f2d0d1e6a6bddb74a9cf3690c00570b0045a8d023858").map(Nm),bb=$m(mb),xb=new Array(3).concat("e7e1efc994c7dd1c77","f1eef6d7b5d8df65b0ce1256","f1eef6d7b5d8df65b0dd1c77980043","f1eef6d4b9dac994c7df65b0dd1c77980043","f1eef6d4b9dac994c7df65b0e7298ace125691003f","f7f4f9e7e1efd4b9dac994c7df65b0e7298ace125691003f","f7f4f9e7e1efd4b9dac994c7df65b0e7298ace125698004367001f").map(Nm),_b=$m(xb),kb=new Array(3).concat("fde0ddfa9fb5c51b8a","feebe2fbb4b9f768a1ae017e","feebe2fbb4b9f768a1c51b8a7a0177","feebe2fcc5c0fa9fb5f768a1c51b8a7a0177","feebe2fcc5c0fa9fb5f768a1dd3497ae017e7a0177","fff7f3fde0ddfcc5c0fa9fb5f768a1dd3497ae017e7a0177","fff7f3fde0ddfcc5c0fa9fb5f768a1dd3497ae017e7a017749006a").map(Nm),wb=$m(kb),Eb=new Array(3).concat("edf8b17fcdbb2c7fb8","ffffcca1dab441b6c4225ea8","ffffcca1dab441b6c42c7fb8253494","ffffccc7e9b47fcdbb41b6c42c7fb8253494","ffffccc7e9b47fcdbb41b6c41d91c0225ea80c2c84","ffffd9edf8b1c7e9b47fcdbb41b6c41d91c0225ea80c2c84","ffffd9edf8b1c7e9b47fcdbb41b6c41d91c0225ea8253494081d58").map(Nm),Tb=$m(Eb),Cb=new Array(3).concat("f7fcb9addd8e31a354","ffffccc2e69978c679238443","ffffccc2e69978c67931a354006837","ffffccd9f0a3addd8e78c67931a354006837","ffffccd9f0a3addd8e78c67941ab5d238443005a32","ffffe5f7fcb9d9f0a3addd8e78c67941ab5d238443005a32","ffffe5f7fcb9d9f0a3addd8e78c67941ab5d238443006837004529").map(Nm),Ab=$m(Cb),Sb=new Array(3).concat("fff7bcfec44fd95f0e","ffffd4fed98efe9929cc4c02","ffffd4fed98efe9929d95f0e993404","ffffd4fee391fec44ffe9929d95f0e993404","ffffd4fee391fec44ffe9929ec7014cc4c028c2d04","ffffe5fff7bcfee391fec44ffe9929ec7014cc4c028c2d04","ffffe5fff7bcfee391fec44ffe9929ec7014cc4c02993404662506").map(Nm),Mb=$m(Sb),Ob=new Array(3).concat("ffeda0feb24cf03b20","ffffb2fecc5cfd8d3ce31a1c","ffffb2fecc5cfd8d3cf03b20bd0026","ffffb2fed976feb24cfd8d3cf03b20bd0026","ffffb2fed976feb24cfd8d3cfc4e2ae31a1cb10026","ffffccffeda0fed976feb24cfd8d3cfc4e2ae31a1cb10026","ffffccffeda0fed976feb24cfd8d3cfc4e2ae31a1cbd0026800026").map(Nm),Db=$m(Ob),Nb=new Array(3).concat("deebf79ecae13182bd","eff3ffbdd7e76baed62171b5","eff3ffbdd7e76baed63182bd08519c","eff3ffc6dbef9ecae16baed63182bd08519c","eff3ffc6dbef9ecae16baed64292c62171b5084594","f7fbffdeebf7c6dbef9ecae16baed64292c62171b5084594","f7fbffdeebf7c6dbef9ecae16baed64292c62171b508519c08306b").map(Nm),Bb=$m(Nb),Lb=new Array(3).concat("e5f5e0a1d99b31a354","edf8e9bae4b374c476238b45","edf8e9bae4b374c47631a354006d2c","edf8e9c7e9c0a1d99b74c47631a354006d2c","edf8e9c7e9c0a1d99b74c47641ab5d238b45005a32","f7fcf5e5f5e0c7e9c0a1d99b74c47641ab5d238b45005a32","f7fcf5e5f5e0c7e9c0a1d99b74c47641ab5d238b45006d2c00441b").map(Nm),Fb=$m(Lb),Pb=new Array(3).concat("f0f0f0bdbdbd636363","f7f7f7cccccc969696525252","f7f7f7cccccc969696636363252525","f7f7f7d9d9d9bdbdbd969696636363252525","f7f7f7d9d9d9bdbdbd969696737373525252252525","fffffff0f0f0d9d9d9bdbdbd969696737373525252252525","fffffff0f0f0d9d9d9bdbdbd969696737373525252252525000000").map(Nm),Ib=$m(Pb),jb=new Array(3).concat("efedf5bcbddc756bb1","f2f0f7cbc9e29e9ac86a51a3","f2f0f7cbc9e29e9ac8756bb154278f","f2f0f7dadaebbcbddc9e9ac8756bb154278f","f2f0f7dadaebbcbddc9e9ac8807dba6a51a34a1486","fcfbfdefedf5dadaebbcbddc9e9ac8807dba6a51a34a1486","fcfbfdefedf5dadaebbcbddc9e9ac8807dba6a51a354278f3f007d").map(Nm),Rb=$m(jb),Yb=new Array(3).concat("fee0d2fc9272de2d26","fee5d9fcae91fb6a4acb181d","fee5d9fcae91fb6a4ade2d26a50f15","fee5d9fcbba1fc9272fb6a4ade2d26a50f15","fee5d9fcbba1fc9272fb6a4aef3b2ccb181d99000d","fff5f0fee0d2fcbba1fc9272fb6a4aef3b2ccb181d99000d","fff5f0fee0d2fcbba1fc9272fb6a4aef3b2ccb181da50f1567000d").map(Nm),zb=$m(Yb),Ub=new Array(3).concat("fee6cefdae6be6550d","feeddefdbe85fd8d3cd94701","feeddefdbe85fd8d3ce6550da63603","feeddefdd0a2fdae6bfd8d3ce6550da63603","feeddefdd0a2fdae6bfd8d3cf16913d948018c2d04","fff5ebfee6cefdd0a2fdae6bfd8d3cf16913d948018c2d04","fff5ebfee6cefdd0a2fdae6bfd8d3cf16913d94801a636037f2704").map(Nm),$b=$m(Ub),Wb=function(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(-4.54-t*(35.34-t*(2381.73-t*(6402.7-t*(7024.72-2710.57*t)))))))+", "+Math.max(0,Math.min(255,Math.round(32.49+t*(170.73+t*(52.82-t*(131.46-t*(176.58-67.37*t)))))))+", "+Math.max(0,Math.min(255,Math.round(81.24+t*(442.36-t*(2482.43-t*(6167.24-t*(6614.94-2475.67*t)))))))+")"},Hb=kp(Oa(300,.5,0),Oa(-240,.5,1)),Vb=kp(Oa(-100,.75,.35),Oa(80,1.5,.8)),Gb=kp(Oa(260,.75,.35),Oa(80,1.5,.8)),qb=Oa(),Xb=function(t){(t<0||t>1)&&(t-=Math.floor(t));var e=Math.abs(t-.5);return qb.h=360*t-100,qb.s=1.5-1.5*e,qb.l=.8-.9*e,qb+""},Zb=Ge(),Jb=Math.PI/3,Kb=2*Math.PI/3,Qb=function(t){var e;return t=(.5-t)*Math.PI,Zb.r=255*(e=Math.sin(t))*e,Zb.g=255*(e=Math.sin(t+Jb))*e,Zb.b=255*(e=Math.sin(t+Kb))*e,Zb+""},tx=function(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(34.61+t*(1172.33-t*(10793.56-t*(33300.12-t*(38394.49-14825.05*t)))))))+", "+Math.max(0,Math.min(255,Math.round(23.31+t*(557.33+t*(1225.33-t*(3574.96-t*(1073.77+707.56*t)))))))+", "+Math.max(0,Math.min(255,Math.round(27.2+t*(3211.1-t*(15327.97-t*(27814-t*(22569.18-6838.66*t)))))))+")"};function ex(t){var e=t.length;return function(n){return t[Math.max(0,Math.min(e-1,Math.floor(n*e)))]}}var nx=ex(Nm("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725")),rx=ex(Nm("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")),ix=ex(Nm("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")),ax=ex(Nm("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921")),ox=function(t){return ke(ne(t).call(document.documentElement))},sx=0;function cx(){return new ux}function ux(){this._="@"+(++sx).toString(36)}ux.prototype=cx.prototype={constructor:ux,get:function(t){for(var e=this._;!(e in t);)if(!(t=t.parentNode))return;return t[e]},set:function(t,e){return t[this._]=e},remove:function(t){return this._ in t&&delete t[this._]},toString:function(){return this._}};var lx=function(t){return"string"==typeof t?new be([document.querySelectorAll(t)],[document.documentElement]):new be([null==t?[]:t],me)},hx=function(t,e){null==e&&(e=Mn().touches);for(var n=0,r=e?e.length:0,i=new Array(r);n<r;++n)i[n]=On(t,e[n]);return i},fx=function(t){return function(){return t}},dx=Math.abs,px=Math.atan2,gx=Math.cos,yx=Math.max,vx=Math.min,mx=Math.sin,bx=Math.sqrt,xx=Math.PI,_x=xx/2,kx=2*xx;function wx(t){return t>1?0:t<-1?xx:Math.acos(t)}function Ex(t){return t>=1?_x:t<=-1?-_x:Math.asin(t)}function Tx(t){return t.innerRadius}function Cx(t){return t.outerRadius}function Ax(t){return t.startAngle}function Sx(t){return t.endAngle}function Mx(t){return t&&t.padAngle}function Ox(t,e,n,r,i,a,o,s){var c=n-t,u=r-e,l=o-i,h=s-a,f=h*c-l*u;if(!(f*f<1e-12))return[t+(f=(l*(e-a)-h*(t-i))/f)*c,e+f*u]}function Dx(t,e,n,r,i,a,o){var s=t-n,c=e-r,u=(o?a:-a)/bx(s*s+c*c),l=u*c,h=-u*s,f=t+l,d=e+h,p=n+l,g=r+h,y=(f+p)/2,v=(d+g)/2,m=p-f,b=g-d,x=m*m+b*b,_=i-a,k=f*g-p*d,w=(b<0?-1:1)*bx(yx(0,_*_*x-k*k)),E=(k*b-m*w)/x,T=(-k*m-b*w)/x,C=(k*b+m*w)/x,A=(-k*m+b*w)/x,S=E-y,M=T-v,O=C-y,D=A-v;return S*S+M*M>O*O+D*D&&(E=C,T=A),{cx:E,cy:T,x01:-l,y01:-h,x11:E*(i/_-1),y11:T*(i/_-1)}}var Nx=function(){var t=Tx,e=Cx,n=fx(0),r=null,i=Ax,a=Sx,o=Mx,s=null;function c(){var c,u,l=+t.apply(this,arguments),h=+e.apply(this,arguments),f=i.apply(this,arguments)-_x,d=a.apply(this,arguments)-_x,p=dx(d-f),g=d>f;if(s||(s=c=Ui()),h<l&&(u=h,h=l,l=u),h>1e-12)if(p>kx-1e-12)s.moveTo(h*gx(f),h*mx(f)),s.arc(0,0,h,f,d,!g),l>1e-12&&(s.moveTo(l*gx(d),l*mx(d)),s.arc(0,0,l,d,f,g));else{var y,v,m=f,b=d,x=f,_=d,k=p,w=p,E=o.apply(this,arguments)/2,T=E>1e-12&&(r?+r.apply(this,arguments):bx(l*l+h*h)),C=vx(dx(h-l)/2,+n.apply(this,arguments)),A=C,S=C;if(T>1e-12){var M=Ex(T/l*mx(E)),O=Ex(T/h*mx(E));(k-=2*M)>1e-12?(x+=M*=g?1:-1,_-=M):(k=0,x=_=(f+d)/2),(w-=2*O)>1e-12?(m+=O*=g?1:-1,b-=O):(w=0,m=b=(f+d)/2)}var D=h*gx(m),N=h*mx(m),B=l*gx(_),L=l*mx(_);if(C>1e-12){var F,P=h*gx(b),I=h*mx(b),j=l*gx(x),R=l*mx(x);if(p<xx&&(F=Ox(D,N,j,R,P,I,B,L))){var Y=D-F[0],z=N-F[1],U=P-F[0],$=I-F[1],W=1/mx(wx((Y*U+z*$)/(bx(Y*Y+z*z)*bx(U*U+$*$)))/2),H=bx(F[0]*F[0]+F[1]*F[1]);A=vx(C,(l-H)/(W-1)),S=vx(C,(h-H)/(W+1))}}w>1e-12?S>1e-12?(y=Dx(j,R,D,N,h,S,g),v=Dx(P,I,B,L,h,S,g),s.moveTo(y.cx+y.x01,y.cy+y.y01),S<C?s.arc(y.cx,y.cy,S,px(y.y01,y.x01),px(v.y01,v.x01),!g):(s.arc(y.cx,y.cy,S,px(y.y01,y.x01),px(y.y11,y.x11),!g),s.arc(0,0,h,px(y.cy+y.y11,y.cx+y.x11),px(v.cy+v.y11,v.cx+v.x11),!g),s.arc(v.cx,v.cy,S,px(v.y11,v.x11),px(v.y01,v.x01),!g))):(s.moveTo(D,N),s.arc(0,0,h,m,b,!g)):s.moveTo(D,N),l>1e-12&&k>1e-12?A>1e-12?(y=Dx(B,L,P,I,l,-A,g),v=Dx(D,N,j,R,l,-A,g),s.lineTo(y.cx+y.x01,y.cy+y.y01),A<C?s.arc(y.cx,y.cy,A,px(y.y01,y.x01),px(v.y01,v.x01),!g):(s.arc(y.cx,y.cy,A,px(y.y01,y.x01),px(y.y11,y.x11),!g),s.arc(0,0,l,px(y.cy+y.y11,y.cx+y.x11),px(v.cy+v.y11,v.cx+v.x11),g),s.arc(v.cx,v.cy,A,px(v.y11,v.x11),px(v.y01,v.x01),!g))):s.arc(0,0,l,_,x,g):s.lineTo(B,L)}else s.moveTo(0,0);if(s.closePath(),c)return s=null,c+""||null}return c.centroid=function(){var n=(+t.apply(this,arguments)+ +e.apply(this,arguments))/2,r=(+i.apply(this,arguments)+ +a.apply(this,arguments))/2-xx/2;return[gx(r)*n,mx(r)*n]},c.innerRadius=function(e){return arguments.length?(t="function"==typeof e?e:fx(+e),c):t},c.outerRadius=function(t){return arguments.length?(e="function"==typeof t?t:fx(+t),c):e},c.cornerRadius=function(t){return arguments.length?(n="function"==typeof t?t:fx(+t),c):n},c.padRadius=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:fx(+t),c):r},c.startAngle=function(t){return arguments.length?(i="function"==typeof t?t:fx(+t),c):i},c.endAngle=function(t){return arguments.length?(a="function"==typeof t?t:fx(+t),c):a},c.padAngle=function(t){return arguments.length?(o="function"==typeof t?t:fx(+t),c):o},c.context=function(t){return arguments.length?(s=null==t?null:t,c):s},c};function Bx(t){this._context=t}Bx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:this._context.lineTo(t,e)}}};var Lx=function(t){return new Bx(t)};function Fx(t){return t[0]}function Px(t){return t[1]}var Ix=function(){var t=Fx,e=Px,n=fx(!0),r=null,i=Lx,a=null;function o(o){var s,c,u,l=o.length,h=!1;for(null==r&&(a=i(u=Ui())),s=0;s<=l;++s)!(s<l&&n(c=o[s],s,o))===h&&((h=!h)?a.lineStart():a.lineEnd()),h&&a.point(+t(c,s,o),+e(c,s,o));if(u)return a=null,u+""||null}return o.x=function(e){return arguments.length?(t="function"==typeof e?e:fx(+e),o):t},o.y=function(t){return arguments.length?(e="function"==typeof t?t:fx(+t),o):e},o.defined=function(t){return arguments.length?(n="function"==typeof t?t:fx(!!t),o):n},o.curve=function(t){return arguments.length?(i=t,null!=r&&(a=i(r)),o):i},o.context=function(t){return arguments.length?(null==t?r=a=null:a=i(r=t),o):r},o},jx=function(){var t=Fx,e=null,n=fx(0),r=Px,i=fx(!0),a=null,o=Lx,s=null;function c(c){var u,l,h,f,d,p=c.length,g=!1,y=new Array(p),v=new Array(p);for(null==a&&(s=o(d=Ui())),u=0;u<=p;++u){if(!(u<p&&i(f=c[u],u,c))===g)if(g=!g)l=u,s.areaStart(),s.lineStart();else{for(s.lineEnd(),s.lineStart(),h=u-1;h>=l;--h)s.point(y[h],v[h]);s.lineEnd(),s.areaEnd()}g&&(y[u]=+t(f,u,c),v[u]=+n(f,u,c),s.point(e?+e(f,u,c):y[u],r?+r(f,u,c):v[u]))}if(d)return s=null,d+""||null}function u(){return Ix().defined(i).curve(o).context(a)}return c.x=function(n){return arguments.length?(t="function"==typeof n?n:fx(+n),e=null,c):t},c.x0=function(e){return arguments.length?(t="function"==typeof e?e:fx(+e),c):t},c.x1=function(t){return arguments.length?(e=null==t?null:"function"==typeof t?t:fx(+t),c):e},c.y=function(t){return arguments.length?(n="function"==typeof t?t:fx(+t),r=null,c):n},c.y0=function(t){return arguments.length?(n="function"==typeof t?t:fx(+t),c):n},c.y1=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:fx(+t),c):r},c.lineX0=c.lineY0=function(){return u().x(t).y(n)},c.lineY1=function(){return u().x(t).y(r)},c.lineX1=function(){return u().x(e).y(n)},c.defined=function(t){return arguments.length?(i="function"==typeof t?t:fx(!!t),c):i},c.curve=function(t){return arguments.length?(o=t,null!=a&&(s=o(a)),c):o},c.context=function(t){return arguments.length?(null==t?a=s=null:s=o(a=t),c):a},c},Rx=function(t,e){return e<t?-1:e>t?1:e>=t?0:NaN},Yx=function(t){return t},zx=function(){var t=Yx,e=Rx,n=null,r=fx(0),i=fx(kx),a=fx(0);function o(o){var s,c,u,l,h,f=o.length,d=0,p=new Array(f),g=new Array(f),y=+r.apply(this,arguments),v=Math.min(kx,Math.max(-kx,i.apply(this,arguments)-y)),m=Math.min(Math.abs(v)/f,a.apply(this,arguments)),b=m*(v<0?-1:1);for(s=0;s<f;++s)(h=g[p[s]=s]=+t(o[s],s,o))>0&&(d+=h);for(null!=e?p.sort((function(t,n){return e(g[t],g[n])})):null!=n&&p.sort((function(t,e){return n(o[t],o[e])})),s=0,u=d?(v-f*b)/d:0;s<f;++s,y=l)c=p[s],l=y+((h=g[c])>0?h*u:0)+b,g[c]={data:o[c],index:s,value:h,startAngle:y,endAngle:l,padAngle:m};return g}return o.value=function(e){return arguments.length?(t="function"==typeof e?e:fx(+e),o):t},o.sortValues=function(t){return arguments.length?(e=t,n=null,o):e},o.sort=function(t){return arguments.length?(n=t,e=null,o):n},o.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:fx(+t),o):r},o.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:fx(+t),o):i},o.padAngle=function(t){return arguments.length?(a="function"==typeof t?t:fx(+t),o):a},o},Ux=Wx(Lx);function $x(t){this._curve=t}function Wx(t){function e(e){return new $x(t(e))}return e._curve=t,e}function Hx(t){var e=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(t){return arguments.length?e(Wx(t)):e()._curve},t}$x.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,e){this._curve.point(e*Math.sin(t),e*-Math.cos(t))}};var Vx=function(){return Hx(Ix().curve(Ux))},Gx=function(){var t=jx().curve(Ux),e=t.curve,n=t.lineX0,r=t.lineX1,i=t.lineY0,a=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return Hx(n())},delete t.lineX0,t.lineEndAngle=function(){return Hx(r())},delete t.lineX1,t.lineInnerRadius=function(){return Hx(i())},delete t.lineY0,t.lineOuterRadius=function(){return Hx(a())},delete t.lineY1,t.curve=function(t){return arguments.length?e(Wx(t)):e()._curve},t},qx=function(t,e){return[(e=+e)*Math.cos(t-=Math.PI/2),e*Math.sin(t)]},Xx=Array.prototype.slice;function Zx(t){return t.source}function Jx(t){return t.target}function Kx(t){var e=Zx,n=Jx,r=Fx,i=Px,a=null;function o(){var o,s=Xx.call(arguments),c=e.apply(this,s),u=n.apply(this,s);if(a||(a=o=Ui()),t(a,+r.apply(this,(s[0]=c,s)),+i.apply(this,s),+r.apply(this,(s[0]=u,s)),+i.apply(this,s)),o)return a=null,o+""||null}return o.source=function(t){return arguments.length?(e=t,o):e},o.target=function(t){return arguments.length?(n=t,o):n},o.x=function(t){return arguments.length?(r="function"==typeof t?t:fx(+t),o):r},o.y=function(t){return arguments.length?(i="function"==typeof t?t:fx(+t),o):i},o.context=function(t){return arguments.length?(a=null==t?null:t,o):a},o}function Qx(t,e,n,r,i){t.moveTo(e,n),t.bezierCurveTo(e=(e+r)/2,n,e,i,r,i)}function t_(t,e,n,r,i){t.moveTo(e,n),t.bezierCurveTo(e,n=(n+i)/2,r,n,r,i)}function e_(t,e,n,r,i){var a=qx(e,n),o=qx(e,n=(n+i)/2),s=qx(r,n),c=qx(r,i);t.moveTo(a[0],a[1]),t.bezierCurveTo(o[0],o[1],s[0],s[1],c[0],c[1])}function n_(){return Kx(Qx)}function r_(){return Kx(t_)}function i_(){var t=Kx(e_);return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t}var a_={draw:function(t,e){var n=Math.sqrt(e/xx);t.moveTo(n,0),t.arc(0,0,n,0,kx)}},o_={draw:function(t,e){var n=Math.sqrt(e/5)/2;t.moveTo(-3*n,-n),t.lineTo(-n,-n),t.lineTo(-n,-3*n),t.lineTo(n,-3*n),t.lineTo(n,-n),t.lineTo(3*n,-n),t.lineTo(3*n,n),t.lineTo(n,n),t.lineTo(n,3*n),t.lineTo(-n,3*n),t.lineTo(-n,n),t.lineTo(-3*n,n),t.closePath()}},s_=Math.sqrt(1/3),c_=2*s_,u_={draw:function(t,e){var n=Math.sqrt(e/c_),r=n*s_;t.moveTo(0,-n),t.lineTo(r,0),t.lineTo(0,n),t.lineTo(-r,0),t.closePath()}},l_=Math.sin(xx/10)/Math.sin(7*xx/10),h_=Math.sin(kx/10)*l_,f_=-Math.cos(kx/10)*l_,d_={draw:function(t,e){var n=Math.sqrt(.8908130915292852*e),r=h_*n,i=f_*n;t.moveTo(0,-n),t.lineTo(r,i);for(var a=1;a<5;++a){var o=kx*a/5,s=Math.cos(o),c=Math.sin(o);t.lineTo(c*n,-s*n),t.lineTo(s*r-c*i,c*r+s*i)}t.closePath()}},p_={draw:function(t,e){var n=Math.sqrt(e),r=-n/2;t.rect(r,r,n,n)}},g_=Math.sqrt(3),y_={draw:function(t,e){var n=-Math.sqrt(e/(3*g_));t.moveTo(0,2*n),t.lineTo(-g_*n,-n),t.lineTo(g_*n,-n),t.closePath()}},v_=Math.sqrt(3)/2,m_=1/Math.sqrt(12),b_=3*(m_/2+1),x_={draw:function(t,e){var n=Math.sqrt(e/b_),r=n/2,i=n*m_,a=r,o=n*m_+n,s=-a,c=o;t.moveTo(r,i),t.lineTo(a,o),t.lineTo(s,c),t.lineTo(-.5*r-v_*i,v_*r+-.5*i),t.lineTo(-.5*a-v_*o,v_*a+-.5*o),t.lineTo(-.5*s-v_*c,v_*s+-.5*c),t.lineTo(-.5*r+v_*i,-.5*i-v_*r),t.lineTo(-.5*a+v_*o,-.5*o-v_*a),t.lineTo(-.5*s+v_*c,-.5*c-v_*s),t.closePath()}},__=[a_,o_,u_,p_,d_,y_,x_],k_=function(){var t=fx(a_),e=fx(64),n=null;function r(){var r;if(n||(n=r=Ui()),t.apply(this,arguments).draw(n,+e.apply(this,arguments)),r)return n=null,r+""||null}return r.type=function(e){return arguments.length?(t="function"==typeof e?e:fx(e),r):t},r.size=function(t){return arguments.length?(e="function"==typeof t?t:fx(+t),r):e},r.context=function(t){return arguments.length?(n=null==t?null:t,r):n},r},w_=function(){};function E_(t,e,n){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+e)/6,(t._y0+4*t._y1+n)/6)}function T_(t){this._context=t}T_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:E_(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:E_(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var C_=function(t){return new T_(t)};function A_(t){this._context=t}A_.prototype={areaStart:w_,areaEnd:w_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x2=t,this._y2=e;break;case 1:this._point=2,this._x3=t,this._y3=e;break;case 2:this._point=3,this._x4=t,this._y4=e,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+e)/6);break;default:E_(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var S_=function(t){return new A_(t)};function M_(t){this._context=t}M_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var n=(this._x0+4*this._x1+t)/6,r=(this._y0+4*this._y1+e)/6;this._line?this._context.lineTo(n,r):this._context.moveTo(n,r);break;case 3:this._point=4;default:E_(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var O_=function(t){return new M_(t)};function D_(t,e){this._basis=new T_(t),this._beta=e}D_.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,e=this._y,n=t.length-1;if(n>0)for(var r,i=t[0],a=e[0],o=t[n]-i,s=e[n]-a,c=-1;++c<=n;)r=c/n,this._basis.point(this._beta*t[c]+(1-this._beta)*(i+r*o),this._beta*e[c]+(1-this._beta)*(a+r*s));this._x=this._y=null,this._basis.lineEnd()},point:function(t,e){this._x.push(+t),this._y.push(+e)}};var N_=function t(e){function n(t){return 1===e?new T_(t):new D_(t,e)}return n.beta=function(e){return t(+e)},n}(.85);function B_(t,e,n){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-e),t._y2+t._k*(t._y1-n),t._x2,t._y2)}function L_(t,e){this._context=t,this._k=(1-e)/6}L_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:B_(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2,this._x1=t,this._y1=e;break;case 2:this._point=3;default:B_(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var F_=function t(e){function n(t){return new L_(t,e)}return n.tension=function(e){return t(+e)},n}(0);function P_(t,e){this._context=t,this._k=(1-e)/6}P_.prototype={areaStart:w_,areaEnd:w_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:B_(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var I_=function t(e){function n(t){return new P_(t,e)}return n.tension=function(e){return t(+e)},n}(0);function j_(t,e){this._context=t,this._k=(1-e)/6}j_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:B_(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var R_=function t(e){function n(t){return new j_(t,e)}return n.tension=function(e){return t(+e)},n}(0);function Y_(t,e,n){var r=t._x1,i=t._y1,a=t._x2,o=t._y2;if(t._l01_a>1e-12){var s=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,c=3*t._l01_a*(t._l01_a+t._l12_a);r=(r*s-t._x0*t._l12_2a+t._x2*t._l01_2a)/c,i=(i*s-t._y0*t._l12_2a+t._y2*t._l01_2a)/c}if(t._l23_a>1e-12){var u=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,l=3*t._l23_a*(t._l23_a+t._l12_a);a=(a*u+t._x1*t._l23_2a-e*t._l12_2a)/l,o=(o*u+t._y1*t._l23_2a-n*t._l12_2a)/l}t._context.bezierCurveTo(r,i,a,o,t._x2,t._y2)}function z_(t,e){this._context=t,this._alpha=e}z_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3;default:Y_(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var U_=function t(e){function n(t){return e?new z_(t,e):new L_(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function $_(t,e){this._context=t,this._alpha=e}$_.prototype={areaStart:w_,areaEnd:w_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:Y_(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var W_=function t(e){function n(t){return e?new $_(t,e):new P_(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function H_(t,e){this._context=t,this._alpha=e}H_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Y_(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var V_=function t(e){function n(t){return e?new H_(t,e):new j_(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function G_(t){this._context=t}G_.prototype={areaStart:w_,areaEnd:w_,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(t,e){t=+t,e=+e,this._point?this._context.lineTo(t,e):(this._point=1,this._context.moveTo(t,e))}};var q_=function(t){return new G_(t)};function X_(t){return t<0?-1:1}function Z_(t,e,n){var r=t._x1-t._x0,i=e-t._x1,a=(t._y1-t._y0)/(r||i<0&&-0),o=(n-t._y1)/(i||r<0&&-0),s=(a*i+o*r)/(r+i);return(X_(a)+X_(o))*Math.min(Math.abs(a),Math.abs(o),.5*Math.abs(s))||0}function J_(t,e){var n=t._x1-t._x0;return n?(3*(t._y1-t._y0)/n-e)/2:e}function K_(t,e,n){var r=t._x0,i=t._y0,a=t._x1,o=t._y1,s=(a-r)/3;t._context.bezierCurveTo(r+s,i+s*e,a-s,o-s*n,a,o)}function Q_(t){this._context=t}function tk(t){this._context=new ek(t)}function ek(t){this._context=t}function nk(t){return new Q_(t)}function rk(t){return new tk(t)}function ik(t){this._context=t}function ak(t){var e,n,r=t.length-1,i=new Array(r),a=new Array(r),o=new Array(r);for(i[0]=0,a[0]=2,o[0]=t[0]+2*t[1],e=1;e<r-1;++e)i[e]=1,a[e]=4,o[e]=4*t[e]+2*t[e+1];for(i[r-1]=2,a[r-1]=7,o[r-1]=8*t[r-1]+t[r],e=1;e<r;++e)n=i[e]/a[e-1],a[e]-=n,o[e]-=n*o[e-1];for(i[r-1]=o[r-1]/a[r-1],e=r-2;e>=0;--e)i[e]=(o[e]-i[e+1])/a[e];for(a[r-1]=(t[r]+i[r-1])/2,e=0;e<r-1;++e)a[e]=2*t[e+1]-i[e+1];return[i,a]}Q_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:K_(this,this._t0,J_(this,this._t0))}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){var n=NaN;if(e=+e,(t=+t)!==this._x1||e!==this._y1){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,K_(this,J_(this,n=Z_(this,t,e)),n);break;default:K_(this,this._t0,n=Z_(this,t,e))}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e,this._t0=n}}},(tk.prototype=Object.create(Q_.prototype)).point=function(t,e){Q_.prototype.point.call(this,e,t)},ek.prototype={moveTo:function(t,e){this._context.moveTo(e,t)},closePath:function(){this._context.closePath()},lineTo:function(t,e){this._context.lineTo(e,t)},bezierCurveTo:function(t,e,n,r,i,a){this._context.bezierCurveTo(e,t,r,n,a,i)}},ik.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x=[],this._y=[]},lineEnd:function(){var t=this._x,e=this._y,n=t.length;if(n)if(this._line?this._context.lineTo(t[0],e[0]):this._context.moveTo(t[0],e[0]),2===n)this._context.lineTo(t[1],e[1]);else for(var r=ak(t),i=ak(e),a=0,o=1;o<n;++a,++o)this._context.bezierCurveTo(r[0][a],i[0][a],r[1][a],i[1][a],t[o],e[o]);(this._line||0!==this._line&&1===n)&&this._context.closePath(),this._line=1-this._line,this._x=this._y=null},point:function(t,e){this._x.push(+t),this._y.push(+e)}};var ok=function(t){return new ik(t)};function sk(t,e){this._context=t,this._t=e}sk.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x=this._y=NaN,this._point=0},lineEnd:function(){0<this._t&&this._t<1&&2===this._point&&this._context.lineTo(this._x,this._y),(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line>=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,e),this._context.lineTo(t,e);else{var n=this._x*(1-this._t)+t*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,e)}}this._x=t,this._y=e}};var ck=function(t){return new sk(t,.5)};function uk(t){return new sk(t,0)}function lk(t){return new sk(t,1)}var hk=function(t,e){if((i=t.length)>1)for(var n,r,i,a=1,o=t[e[0]],s=o.length;a<i;++a)for(r=o,o=t[e[a]],n=0;n<s;++n)o[n][1]+=o[n][0]=isNaN(r[n][1])?r[n][0]:r[n][1]},fk=function(t){for(var e=t.length,n=new Array(e);--e>=0;)n[e]=e;return n};function dk(t,e){return t[e]}var pk=function(){var t=fx([]),e=fk,n=hk,r=dk;function i(i){var a,o,s=t.apply(this,arguments),c=i.length,u=s.length,l=new Array(u);for(a=0;a<u;++a){for(var h,f=s[a],d=l[a]=new Array(c),p=0;p<c;++p)d[p]=h=[0,+r(i[p],f,p,i)],h.data=i[p];d.key=f}for(a=0,o=e(l);a<u;++a)l[o[a]].index=a;return n(l,o),l}return i.keys=function(e){return arguments.length?(t="function"==typeof e?e:fx(Xx.call(e)),i):t},i.value=function(t){return arguments.length?(r="function"==typeof t?t:fx(+t),i):r},i.order=function(t){return arguments.length?(e=null==t?fk:"function"==typeof t?t:fx(Xx.call(t)),i):e},i.offset=function(t){return arguments.length?(n=null==t?hk:t,i):n},i},gk=function(t,e){if((r=t.length)>0){for(var n,r,i,a=0,o=t[0].length;a<o;++a){for(i=n=0;n<r;++n)i+=t[n][a][1]||0;if(i)for(n=0;n<r;++n)t[n][a][1]/=i}hk(t,e)}},yk=function(t,e){if((s=t.length)>0)for(var n,r,i,a,o,s,c=0,u=t[e[0]].length;c<u;++c)for(a=o=0,n=0;n<s;++n)(i=(r=t[e[n]][c])[1]-r[0])>0?(r[0]=a,r[1]=a+=i):i<0?(r[1]=o,r[0]=o+=i):(r[0]=0,r[1]=i)},vk=function(t,e){if((n=t.length)>0){for(var n,r=0,i=t[e[0]],a=i.length;r<a;++r){for(var o=0,s=0;o<n;++o)s+=t[o][r][1]||0;i[r][1]+=i[r][0]=-s/2}hk(t,e)}},mk=function(t,e){if((i=t.length)>0&&(r=(n=t[e[0]]).length)>0){for(var n,r,i,a=0,o=1;o<r;++o){for(var s=0,c=0,u=0;s<i;++s){for(var l=t[e[s]],h=l[o][1]||0,f=(h-(l[o-1][1]||0))/2,d=0;d<s;++d){var p=t[e[d]];f+=(p[o][1]||0)-(p[o-1][1]||0)}c+=h,u+=f*h}n[o-1][1]+=n[o-1][0]=a,c&&(a-=u/c)}n[o-1][1]+=n[o-1][0]=a,hk(t,e)}},bk=function(t){var e=t.map(xk);return fk(t).sort((function(t,n){return e[t]-e[n]}))};function xk(t){for(var e,n=-1,r=0,i=t.length,a=-1/0;++n<i;)(e=+t[n][1])>a&&(a=e,r=n);return r}var _k=function(t){var e=t.map(kk);return fk(t).sort((function(t,n){return e[t]-e[n]}))};function kk(t){for(var e,n=0,r=-1,i=t.length;++r<i;)(e=+t[r][1])&&(n+=e);return n}var wk=function(t){return _k(t).reverse()},Ek=function(t){var e,n,r=t.length,i=t.map(kk),a=bk(t),o=0,s=0,c=[],u=[];for(e=0;e<r;++e)n=a[e],o<s?(o+=i[n],c.push(n)):(s+=i[n],u.push(n));return u.reverse().concat(c)},Tk=function(t){return fk(t).reverse()};var Ck=Date.prototype.toISOString?function(t){return t.toISOString()}:Wy("%Y-%m-%dT%H:%M:%S.%LZ");var Ak=+new Date("2000-01-01T00:00:00.000Z")?function(t){var e=new Date(t);return isNaN(e)?null:e}:Hy("%Y-%m-%dT%H:%M:%S.%LZ"),Sk=function(t,e,n){var r=new $n,i=e;return null==e?(r.restart(t,e,n),r):(e=+e,n=null==n?zn():+n,r.restart((function a(o){o+=i,r.restart(a,i+=e,n),t(o)}),e,n),r)},Mk=function(t){return function(){return t}};function Ok(t){return t[0]}function Dk(t){return t[1]}function Nk(){this._=null}function Bk(t){t.U=t.C=t.L=t.R=t.P=t.N=null}function Lk(t,e){var n=e,r=e.R,i=n.U;i?i.L===n?i.L=r:i.R=r:t._=r,r.U=i,n.U=r,n.R=r.L,n.R&&(n.R.U=n),r.L=n}function Fk(t,e){var n=e,r=e.L,i=n.U;i?i.L===n?i.L=r:i.R=r:t._=r,r.U=i,n.U=r,n.L=r.R,n.L&&(n.L.U=n),r.R=n}function Pk(t){for(;t.L;)t=t.L;return t}Nk.prototype={constructor:Nk,insert:function(t,e){var n,r,i;if(t){if(e.P=t,e.N=t.N,t.N&&(t.N.P=e),t.N=e,t.R){for(t=t.R;t.L;)t=t.L;t.L=e}else t.R=e;n=t}else this._?(t=Pk(this._),e.P=null,e.N=t,t.P=t.L=e,n=t):(e.P=e.N=null,this._=e,n=null);for(e.L=e.R=null,e.U=n,e.C=!0,t=e;n&&n.C;)n===(r=n.U).L?(i=r.R)&&i.C?(n.C=i.C=!1,r.C=!0,t=r):(t===n.R&&(Lk(this,n),n=(t=n).U),n.C=!1,r.C=!0,Fk(this,r)):(i=r.L)&&i.C?(n.C=i.C=!1,r.C=!0,t=r):(t===n.L&&(Fk(this,n),n=(t=n).U),n.C=!1,r.C=!0,Lk(this,r)),n=t.U;this._.C=!1},remove:function(t){t.N&&(t.N.P=t.P),t.P&&(t.P.N=t.N),t.N=t.P=null;var e,n,r,i=t.U,a=t.L,o=t.R;if(n=a?o?Pk(o):a:o,i?i.L===t?i.L=n:i.R=n:this._=n,a&&o?(r=n.C,n.C=t.C,n.L=a,a.U=n,n!==o?(i=n.U,n.U=t.U,t=n.R,i.L=t,n.R=o,o.U=n):(n.U=i,i=n,t=n.R)):(r=t.C,t=n),t&&(t.U=i),!r)if(t&&t.C)t.C=!1;else{do{if(t===this._)break;if(t===i.L){if((e=i.R).C&&(e.C=!1,i.C=!0,Lk(this,i),e=i.R),e.L&&e.L.C||e.R&&e.R.C){e.R&&e.R.C||(e.L.C=!1,e.C=!0,Fk(this,e),e=i.R),e.C=i.C,i.C=e.R.C=!1,Lk(this,i),t=this._;break}}else if((e=i.L).C&&(e.C=!1,i.C=!0,Fk(this,i),e=i.L),e.L&&e.L.C||e.R&&e.R.C){e.L&&e.L.C||(e.R.C=!1,e.C=!0,Lk(this,e),e=i.L),e.C=i.C,i.C=e.L.C=!1,Fk(this,i),t=this._;break}e.C=!0,t=i,i=i.U}while(!t.C);t&&(t.C=!1)}}};var Ik=Nk;function jk(t,e,n,r){var i=[null,null],a=cw.push(i)-1;return i.left=t,i.right=e,n&&Yk(i,t,e,n),r&&Yk(i,e,t,r),ow[t.index].halfedges.push(a),ow[e.index].halfedges.push(a),i}function Rk(t,e,n){var r=[e,n];return r.left=t,r}function Yk(t,e,n,r){t[0]||t[1]?t.left===n?t[1]=r:t[0]=r:(t[0]=r,t.left=e,t.right=n)}function zk(t,e,n,r,i){var a,o=t[0],s=t[1],c=o[0],u=o[1],l=0,h=1,f=s[0]-c,d=s[1]-u;if(a=e-c,f||!(a>0)){if(a/=f,f<0){if(a<l)return;a<h&&(h=a)}else if(f>0){if(a>h)return;a>l&&(l=a)}if(a=r-c,f||!(a<0)){if(a/=f,f<0){if(a>h)return;a>l&&(l=a)}else if(f>0){if(a<l)return;a<h&&(h=a)}if(a=n-u,d||!(a>0)){if(a/=d,d<0){if(a<l)return;a<h&&(h=a)}else if(d>0){if(a>h)return;a>l&&(l=a)}if(a=i-u,d||!(a<0)){if(a/=d,d<0){if(a>h)return;a>l&&(l=a)}else if(d>0){if(a<l)return;a<h&&(h=a)}return!(l>0||h<1)||(l>0&&(t[0]=[c+l*f,u+l*d]),h<1&&(t[1]=[c+h*f,u+h*d]),!0)}}}}}function Uk(t,e,n,r,i){var a=t[1];if(a)return!0;var o,s,c=t[0],u=t.left,l=t.right,h=u[0],f=u[1],d=l[0],p=l[1],g=(h+d)/2,y=(f+p)/2;if(p===f){if(g<e||g>=r)return;if(h>d){if(c){if(c[1]>=i)return}else c=[g,n];a=[g,i]}else{if(c){if(c[1]<n)return}else c=[g,i];a=[g,n]}}else if(s=y-(o=(h-d)/(p-f))*g,o<-1||o>1)if(h>d){if(c){if(c[1]>=i)return}else c=[(n-s)/o,n];a=[(i-s)/o,i]}else{if(c){if(c[1]<n)return}else c=[(i-s)/o,i];a=[(n-s)/o,n]}else if(f<p){if(c){if(c[0]>=r)return}else c=[e,o*e+s];a=[r,o*r+s]}else{if(c){if(c[0]<e)return}else c=[r,o*r+s];a=[e,o*e+s]}return t[0]=c,t[1]=a,!0}function $k(t,e){var n=t.site,r=e.left,i=e.right;return n===i&&(i=r,r=n),i?Math.atan2(i[1]-r[1],i[0]-r[0]):(n===r?(r=e[1],i=e[0]):(r=e[0],i=e[1]),Math.atan2(r[0]-i[0],i[1]-r[1]))}function Wk(t,e){return e[+(e.left!==t.site)]}function Hk(t,e){return e[+(e.left===t.site)]}var Vk,Gk=[];function qk(){Bk(this),this.x=this.y=this.arc=this.site=this.cy=null}function Xk(t){var e=t.P,n=t.N;if(e&&n){var r=e.site,i=t.site,a=n.site;if(r!==a){var o=i[0],s=i[1],c=r[0]-o,u=r[1]-s,l=a[0]-o,h=a[1]-s,f=2*(c*h-u*l);if(!(f>=-lw)){var d=c*c+u*u,p=l*l+h*h,g=(h*d-u*p)/f,y=(c*p-l*d)/f,v=Gk.pop()||new qk;v.arc=t,v.site=i,v.x=g+o,v.y=(v.cy=y+s)+Math.sqrt(g*g+y*y),t.circle=v;for(var m=null,b=sw._;b;)if(v.y<b.y||v.y===b.y&&v.x<=b.x){if(!b.L){m=b.P;break}b=b.L}else{if(!b.R){m=b;break}b=b.R}sw.insert(m,v),m||(Vk=v)}}}}function Zk(t){var e=t.circle;e&&(e.P||(Vk=e.N),sw.remove(e),Gk.push(e),Bk(e),t.circle=null)}var Jk=[];function Kk(){Bk(this),this.edge=this.site=this.circle=null}function Qk(t){var e=Jk.pop()||new Kk;return e.site=t,e}function tw(t){Zk(t),aw.remove(t),Jk.push(t),Bk(t)}function ew(t){var e=t.circle,n=e.x,r=e.cy,i=[n,r],a=t.P,o=t.N,s=[t];tw(t);for(var c=a;c.circle&&Math.abs(n-c.circle.x)<uw&&Math.abs(r-c.circle.cy)<uw;)a=c.P,s.unshift(c),tw(c),c=a;s.unshift(c),Zk(c);for(var u=o;u.circle&&Math.abs(n-u.circle.x)<uw&&Math.abs(r-u.circle.cy)<uw;)o=u.N,s.push(u),tw(u),u=o;s.push(u),Zk(u);var l,h=s.length;for(l=1;l<h;++l)u=s[l],c=s[l-1],Yk(u.edge,c.site,u.site,i);c=s[0],(u=s[h-1]).edge=jk(c.site,u.site,null,i),Xk(c),Xk(u)}function nw(t){for(var e,n,r,i,a=t[0],o=t[1],s=aw._;s;)if((r=rw(s,o)-a)>uw)s=s.L;else{if(!((i=a-iw(s,o))>uw)){r>-uw?(e=s.P,n=s):i>-uw?(e=s,n=s.N):e=n=s;break}if(!s.R){e=s;break}s=s.R}!function(t){ow[t.index]={site:t,halfedges:[]}}(t);var c=Qk(t);if(aw.insert(e,c),e||n){if(e===n)return Zk(e),n=Qk(e.site),aw.insert(c,n),c.edge=n.edge=jk(e.site,c.site),Xk(e),void Xk(n);if(n){Zk(e),Zk(n);var u=e.site,l=u[0],h=u[1],f=t[0]-l,d=t[1]-h,p=n.site,g=p[0]-l,y=p[1]-h,v=2*(f*y-d*g),m=f*f+d*d,b=g*g+y*y,x=[(y*m-d*b)/v+l,(f*b-g*m)/v+h];Yk(n.edge,u,p,x),c.edge=jk(u,t,null,x),n.edge=jk(t,p,null,x),Xk(e),Xk(n)}else c.edge=jk(e.site,c.site)}}function rw(t,e){var n=t.site,r=n[0],i=n[1],a=i-e;if(!a)return r;var o=t.P;if(!o)return-1/0;var s=(n=o.site)[0],c=n[1],u=c-e;if(!u)return s;var l=s-r,h=1/a-1/u,f=l/u;return h?(-f+Math.sqrt(f*f-2*h*(l*l/(-2*u)-c+u/2+i-a/2)))/h+r:(r+s)/2}function iw(t,e){var n=t.N;if(n)return rw(n,e);var r=t.site;return r[1]===e?r[0]:1/0}var aw,ow,sw,cw,uw=1e-6,lw=1e-12;function hw(t,e){return e[1]-t[1]||e[0]-t[0]}function fw(t,e){var n,r,i,a=t.sort(hw).pop();for(cw=[],ow=new Array(t.length),aw=new Ik,sw=new Ik;;)if(i=Vk,a&&(!i||a[1]<i.y||a[1]===i.y&&a[0]<i.x))a[0]===n&&a[1]===r||(nw(a),n=a[0],r=a[1]),a=t.pop();else{if(!i)break;ew(i.arc)}if(function(){for(var t,e,n,r,i=0,a=ow.length;i<a;++i)if((t=ow[i])&&(r=(e=t.halfedges).length)){var o=new Array(r),s=new Array(r);for(n=0;n<r;++n)o[n]=n,s[n]=$k(t,cw[e[n]]);for(o.sort((function(t,e){return s[e]-s[t]})),n=0;n<r;++n)s[n]=e[o[n]];for(n=0;n<r;++n)e[n]=s[n]}}(),e){var o=+e[0][0],s=+e[0][1],c=+e[1][0],u=+e[1][1];!function(t,e,n,r){for(var i,a=cw.length;a--;)Uk(i=cw[a],t,e,n,r)&&zk(i,t,e,n,r)&&(Math.abs(i[0][0]-i[1][0])>uw||Math.abs(i[0][1]-i[1][1])>uw)||delete cw[a]}(o,s,c,u),function(t,e,n,r){var i,a,o,s,c,u,l,h,f,d,p,g,y=ow.length,v=!0;for(i=0;i<y;++i)if(a=ow[i]){for(o=a.site,s=(c=a.halfedges).length;s--;)cw[c[s]]||c.splice(s,1);for(s=0,u=c.length;s<u;)p=(d=Hk(a,cw[c[s]]))[0],g=d[1],h=(l=Wk(a,cw[c[++s%u]]))[0],f=l[1],(Math.abs(p-h)>uw||Math.abs(g-f)>uw)&&(c.splice(s,0,cw.push(Rk(o,d,Math.abs(p-t)<uw&&r-g>uw?[t,Math.abs(h-t)<uw?f:r]:Math.abs(g-r)<uw&&n-p>uw?[Math.abs(f-r)<uw?h:n,r]:Math.abs(p-n)<uw&&g-e>uw?[n,Math.abs(h-n)<uw?f:e]:Math.abs(g-e)<uw&&p-t>uw?[Math.abs(f-e)<uw?h:t,e]:null))-1),++u);u&&(v=!1)}if(v){var m,b,x,_=1/0;for(i=0,v=null;i<y;++i)(a=ow[i])&&(x=(m=(o=a.site)[0]-t)*m+(b=o[1]-e)*b)<_&&(_=x,v=a);if(v){var k=[t,e],w=[t,r],E=[n,r],T=[n,e];v.halfedges.push(cw.push(Rk(o=v.site,k,w))-1,cw.push(Rk(o,w,E))-1,cw.push(Rk(o,E,T))-1,cw.push(Rk(o,T,k))-1)}}for(i=0;i<y;++i)(a=ow[i])&&(a.halfedges.length||delete ow[i])}(o,s,c,u)}this.edges=cw,this.cells=ow,aw=sw=cw=ow=null}fw.prototype={constructor:fw,polygons:function(){var t=this.edges;return this.cells.map((function(e){var n=e.halfedges.map((function(n){return Wk(e,t[n])}));return n.data=e.site.data,n}))},triangles:function(){var t=[],e=this.edges;return this.cells.forEach((function(n,r){if(a=(i=n.halfedges).length)for(var i,a,o,s,c,u,l=n.site,h=-1,f=e[i[a-1]],d=f.left===l?f.right:f.left;++h<a;)o=d,d=(f=e[i[h]]).left===l?f.right:f.left,o&&d&&r<o.index&&r<d.index&&(c=o,u=d,((s=l)[0]-u[0])*(c[1]-s[1])-(s[0]-c[0])*(u[1]-s[1])<0)&&t.push([l.data,o.data,d.data])})),t},links:function(){return this.edges.filter((function(t){return t.right})).map((function(t){return{source:t.left.data,target:t.right.data}}))},find:function(t,e,n){for(var r,i,a=this,o=a._found||0,s=a.cells.length;!(i=a.cells[o]);)if(++o>=s)return null;var c=t-i.site[0],u=e-i.site[1],l=c*c+u*u;do{i=a.cells[r=o],o=null,i.halfedges.forEach((function(n){var r=a.edges[n],s=r.left;if(s!==i.site&&s||(s=r.right)){var c=t-s[0],u=e-s[1],h=c*c+u*u;h<l&&(l=h,o=s.index)}}))}while(null!==o);return a._found=r,null==n||l<=n*n?i.site:null}};var dw=function(){var t=Ok,e=Dk,n=null;function r(r){return new fw(r.map((function(n,i){var a=[Math.round(t(n,i,r)/uw)*uw,Math.round(e(n,i,r)/uw)*uw];return a.index=i,a.data=n,a})),n)}return r.polygons=function(t){return r(t).polygons()},r.links=function(t){return r(t).links()},r.triangles=function(t){return r(t).triangles()},r.x=function(e){return arguments.length?(t="function"==typeof e?e:Mk(+e),r):t},r.y=function(t){return arguments.length?(e="function"==typeof t?t:Mk(+t),r):e},r.extent=function(t){return arguments.length?(n=null==t?null:[[+t[0][0],+t[0][1]],[+t[1][0],+t[1][1]]],r):n&&[[n[0][0],n[0][1]],[n[1][0],n[1][1]]]},r.size=function(t){return arguments.length?(n=null==t?null:[[0,0],[+t[0],+t[1]]],r):n&&[n[1][0]-n[0][0],n[1][1]-n[0][1]]},r},pw=function(t){return function(){return t}};function gw(t,e,n){this.target=t,this.type=e,this.transform=n}function yw(t,e,n){this.k=t,this.x=e,this.y=n}yw.prototype={constructor:yw,scale:function(t){return 1===t?this:new yw(this.k*t,this.x,this.y)},translate:function(t,e){return 0===t&0===e?this:new yw(this.k,this.x+this.k*t,this.y+this.k*e)},apply:function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},applyX:function(t){return t*this.k+this.x},applyY:function(t){return t*this.k+this.y},invert:function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},invertX:function(t){return(t-this.x)/this.k},invertY:function(t){return(t-this.y)/this.k},rescaleX:function(t){return t.copy().domain(t.range().map(this.invertX,this).map(t.invert,t))},rescaleY:function(t){return t.copy().domain(t.range().map(this.invertY,this).map(t.invert,t))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var vw=new yw(1,0,0);function mw(t){for(;!t.__zoom;)if(!(t=t.parentNode))return vw;return t.__zoom}function bw(){ce.stopImmediatePropagation()}mw.prototype=yw.prototype;var xw=function(){ce.preventDefault(),ce.stopImmediatePropagation()};function _w(){return!ce.ctrlKey&&!ce.button}function kw(){var t=this;return t instanceof SVGElement?(t=t.ownerSVGElement||t).hasAttribute("viewBox")?[[(t=t.viewBox.baseVal).x,t.y],[t.x+t.width,t.y+t.height]]:[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]:[[0,0],[t.clientWidth,t.clientHeight]]}function ww(){return this.__zoom||vw}function Ew(){return-ce.deltaY*(1===ce.deltaMode?.05:ce.deltaMode?1:.002)}function Tw(){return navigator.maxTouchPoints||"ontouchstart"in this}function Cw(t,e,n){var r=t.invertX(e[0][0])-n[0][0],i=t.invertX(e[1][0])-n[1][0],a=t.invertY(e[0][1])-n[0][1],o=t.invertY(e[1][1])-n[1][1];return t.translate(i>r?(r+i)/2:Math.min(0,r)||Math.max(0,i),o>a?(a+o)/2:Math.min(0,a)||Math.max(0,o))}var Aw=function(){var t,e,n=_w,r=kw,i=Cw,a=Ew,o=Tw,s=[0,1/0],c=[[-1/0,-1/0],[1/0,1/0]],u=250,l=fp,h=lt("start","zoom","end"),f=0;function d(t){t.property("__zoom",ww).on("wheel.zoom",x).on("mousedown.zoom",_).on("dblclick.zoom",k).filter(o).on("touchstart.zoom",w).on("touchmove.zoom",E).on("touchend.zoom touchcancel.zoom",T).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function p(t,e){return(e=Math.max(s[0],Math.min(s[1],e)))===t.k?t:new yw(e,t.x,t.y)}function g(t,e,n){var r=e[0]-n[0]*t.k,i=e[1]-n[1]*t.k;return r===t.x&&i===t.y?t:new yw(t.k,r,i)}function y(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function v(t,e,n){t.on("start.zoom",(function(){m(this,arguments).start()})).on("interrupt.zoom end.zoom",(function(){m(this,arguments).end()})).tween("zoom",(function(){var t=this,i=arguments,a=m(t,i),o=r.apply(t,i),s=null==n?y(o):"function"==typeof n?n.apply(t,i):n,c=Math.max(o[1][0]-o[0][0],o[1][1]-o[0][1]),u=t.__zoom,h="function"==typeof e?e.apply(t,i):e,f=l(u.invert(s).concat(c/u.k),h.invert(s).concat(c/h.k));return function(t){if(1===t)t=h;else{var e=f(t),n=c/e[2];t=new yw(n,s[0]-e[0]*n,s[1]-e[1]*n)}a.zoom(null,t)}}))}function m(t,e,n){return!n&&t.__zooming||new b(t,e)}function b(t,e){this.that=t,this.args=e,this.active=0,this.extent=r.apply(t,e),this.taps=0}function x(){if(n.apply(this,arguments)){var t=m(this,arguments),e=this.__zoom,r=Math.max(s[0],Math.min(s[1],e.k*Math.pow(2,a.apply(this,arguments)))),o=Nn(this);if(t.wheel)t.mouse[0][0]===o[0]&&t.mouse[0][1]===o[1]||(t.mouse[1]=e.invert(t.mouse[0]=o)),clearTimeout(t.wheel);else{if(e.k===r)return;t.mouse=[o,e.invert(o)],or(this),t.start()}xw(),t.wheel=setTimeout(u,150),t.zoom("mouse",i(g(p(e,r),t.mouse[0],t.mouse[1]),t.extent,c))}function u(){t.wheel=null,t.end()}}function _(){if(!e&&n.apply(this,arguments)){var t=m(this,arguments,!0),r=ke(ce.view).on("mousemove.zoom",u,!0).on("mouseup.zoom",l,!0),a=Nn(this),o=ce.clientX,s=ce.clientY;Te(ce.view),bw(),t.mouse=[a,this.__zoom.invert(a)],or(this),t.start()}function u(){if(xw(),!t.moved){var e=ce.clientX-o,n=ce.clientY-s;t.moved=e*e+n*n>f}t.zoom("mouse",i(g(t.that.__zoom,t.mouse[0]=Nn(t.that),t.mouse[1]),t.extent,c))}function l(){r.on("mousemove.zoom mouseup.zoom",null),Ce(ce.view,t.moved),xw(),t.end()}}function k(){if(n.apply(this,arguments)){var t=this.__zoom,e=Nn(this),a=t.invert(e),o=t.k*(ce.shiftKey?.5:2),s=i(g(p(t,o),e,a),r.apply(this,arguments),c);xw(),u>0?ke(this).transition().duration(u).call(v,s,e):ke(this).call(d.transform,s)}}function w(){if(n.apply(this,arguments)){var e,r,i,a,o=ce.touches,s=o.length,c=m(this,arguments,ce.changedTouches.length===s);for(bw(),r=0;r<s;++r)i=o[r],a=[a=Dn(this,o,i.identifier),this.__zoom.invert(a),i.identifier],c.touch0?c.touch1||c.touch0[2]===a[2]||(c.touch1=a,c.taps=0):(c.touch0=a,e=!0,c.taps=1+!!t);t&&(t=clearTimeout(t)),e&&(c.taps<2&&(t=setTimeout((function(){t=null}),500)),or(this),c.start())}}function E(){if(this.__zooming){var e,n,r,a,o=m(this,arguments),s=ce.changedTouches,u=s.length;for(xw(),t&&(t=clearTimeout(t)),o.taps=0,e=0;e<u;++e)n=s[e],r=Dn(this,s,n.identifier),o.touch0&&o.touch0[2]===n.identifier?o.touch0[0]=r:o.touch1&&o.touch1[2]===n.identifier&&(o.touch1[0]=r);if(n=o.that.__zoom,o.touch1){var l=o.touch0[0],h=o.touch0[1],f=o.touch1[0],d=o.touch1[1],y=(y=f[0]-l[0])*y+(y=f[1]-l[1])*y,v=(v=d[0]-h[0])*v+(v=d[1]-h[1])*v;n=p(n,Math.sqrt(y/v)),r=[(l[0]+f[0])/2,(l[1]+f[1])/2],a=[(h[0]+d[0])/2,(h[1]+d[1])/2]}else{if(!o.touch0)return;r=o.touch0[0],a=o.touch0[1]}o.zoom("touch",i(g(n,r,a),o.extent,c))}}function T(){if(this.__zooming){var t,n,r=m(this,arguments),i=ce.changedTouches,a=i.length;for(bw(),e&&clearTimeout(e),e=setTimeout((function(){e=null}),500),t=0;t<a;++t)n=i[t],r.touch0&&r.touch0[2]===n.identifier?delete r.touch0:r.touch1&&r.touch1[2]===n.identifier&&delete r.touch1;if(r.touch1&&!r.touch0&&(r.touch0=r.touch1,delete r.touch1),r.touch0)r.touch0[1]=this.__zoom.invert(r.touch0[0]);else if(r.end(),2===r.taps){var o=ke(this).on("dblclick.zoom");o&&o.apply(this,arguments)}}}return d.transform=function(t,e,n){var r=t.selection?t.selection():t;r.property("__zoom",ww),t!==r?v(t,e,n):r.interrupt().each((function(){m(this,arguments).start().zoom(null,"function"==typeof e?e.apply(this,arguments):e).end()}))},d.scaleBy=function(t,e,n){d.scaleTo(t,(function(){var t=this.__zoom.k,n="function"==typeof e?e.apply(this,arguments):e;return t*n}),n)},d.scaleTo=function(t,e,n){d.transform(t,(function(){var t=r.apply(this,arguments),a=this.__zoom,o=null==n?y(t):"function"==typeof n?n.apply(this,arguments):n,s=a.invert(o),u="function"==typeof e?e.apply(this,arguments):e;return i(g(p(a,u),o,s),t,c)}),n)},d.translateBy=function(t,e,n){d.transform(t,(function(){return i(this.__zoom.translate("function"==typeof e?e.apply(this,arguments):e,"function"==typeof n?n.apply(this,arguments):n),r.apply(this,arguments),c)}))},d.translateTo=function(t,e,n,a){d.transform(t,(function(){var t=r.apply(this,arguments),o=this.__zoom,s=null==a?y(t):"function"==typeof a?a.apply(this,arguments):a;return i(vw.translate(s[0],s[1]).scale(o.k).translate("function"==typeof e?-e.apply(this,arguments):-e,"function"==typeof n?-n.apply(this,arguments):-n),t,c)}),a)},b.prototype={start:function(){return 1==++this.active&&(this.that.__zooming=this,this.emit("start")),this},zoom:function(t,e){return this.mouse&&"mouse"!==t&&(this.mouse[1]=e.invert(this.mouse[0])),this.touch0&&"touch"!==t&&(this.touch0[1]=e.invert(this.touch0[0])),this.touch1&&"touch"!==t&&(this.touch1[1]=e.invert(this.touch1[0])),this.that.__zoom=e,this.emit("zoom"),this},end:function(){return 0==--this.active&&(delete this.that.__zooming,this.emit("end")),this},emit:function(t){pe(new gw(d,t,this.that.__zoom),h.apply,h,[t,this.that,this.args])}},d.wheelDelta=function(t){return arguments.length?(a="function"==typeof t?t:pw(+t),d):a},d.filter=function(t){return arguments.length?(n="function"==typeof t?t:pw(!!t),d):n},d.touchable=function(t){return arguments.length?(o="function"==typeof t?t:pw(!!t),d):o},d.extent=function(t){return arguments.length?(r="function"==typeof t?t:pw([[+t[0][0],+t[0][1]],[+t[1][0],+t[1][1]]]),d):r},d.scaleExtent=function(t){return arguments.length?(s[0]=+t[0],s[1]=+t[1],d):[s[0],s[1]]},d.translateExtent=function(t){return arguments.length?(c[0][0]=+t[0][0],c[1][0]=+t[1][0],c[0][1]=+t[0][1],c[1][1]=+t[1][1],d):[[c[0][0],c[0][1]],[c[1][0],c[1][1]]]},d.constrain=function(t){return arguments.length?(i=t,d):i},d.duration=function(t){return arguments.length?(u=+t,d):u},d.interpolate=function(t){return arguments.length?(l=t,d):l},d.on=function(){var t=h.on.apply(h,arguments);return t===h?d:t},d.clickDistance=function(t){return arguments.length?(f=(t=+t)*t,d):Math.sqrt(f)},d};n.d(e,"version",(function(){return"5.15.0"})),n.d(e,"bisect",(function(){return c})),n.d(e,"bisectRight",(function(){return o})),n.d(e,"bisectLeft",(function(){return s})),n.d(e,"ascending",(function(){return r})),n.d(e,"bisector",(function(){return i})),n.d(e,"cross",(function(){return h})),n.d(e,"descending",(function(){return f})),n.d(e,"deviation",(function(){return g})),n.d(e,"extent",(function(){return y})),n.d(e,"histogram",(function(){return O})),n.d(e,"thresholdFreedmanDiaconis",(function(){return N})),n.d(e,"thresholdScott",(function(){return B})),n.d(e,"thresholdSturges",(function(){return M})),n.d(e,"max",(function(){return L})),n.d(e,"mean",(function(){return F})),n.d(e,"median",(function(){return P})),n.d(e,"merge",(function(){return I})),n.d(e,"min",(function(){return j})),n.d(e,"pairs",(function(){return u})),n.d(e,"permute",(function(){return R})),n.d(e,"quantile",(function(){return D})),n.d(e,"range",(function(){return k})),n.d(e,"scan",(function(){return Y})),n.d(e,"shuffle",(function(){return z})),n.d(e,"sum",(function(){return U})),n.d(e,"ticks",(function(){return C})),n.d(e,"tickIncrement",(function(){return A})),n.d(e,"tickStep",(function(){return S})),n.d(e,"transpose",(function(){return $})),n.d(e,"variance",(function(){return p})),n.d(e,"zip",(function(){return H})),n.d(e,"axisTop",(function(){return tt})),n.d(e,"axisRight",(function(){return et})),n.d(e,"axisBottom",(function(){return nt})),n.d(e,"axisLeft",(function(){return rt})),n.d(e,"brush",(function(){return Ti})),n.d(e,"brushX",(function(){return wi})),n.d(e,"brushY",(function(){return Ei})),n.d(e,"brushSelection",(function(){return ki})),n.d(e,"chord",(function(){return Li})),n.d(e,"ribbon",(function(){return qi})),n.d(e,"nest",(function(){return Ki})),n.d(e,"set",(function(){return oa})),n.d(e,"map",(function(){return Ji})),n.d(e,"keys",(function(){return sa})),n.d(e,"values",(function(){return ca})),n.d(e,"entries",(function(){return ua})),n.d(e,"color",(function(){return $e})),n.d(e,"rgb",(function(){return Ge})),n.d(e,"hsl",(function(){return tn})),n.d(e,"lab",(function(){return pa})),n.d(e,"hcl",(function(){return ka})),n.d(e,"lch",(function(){return _a})),n.d(e,"gray",(function(){return da})),n.d(e,"cubehelix",(function(){return Oa})),n.d(e,"contours",(function(){return Ya})),n.d(e,"contourDensity",(function(){return Va})),n.d(e,"dispatch",(function(){return lt})),n.d(e,"drag",(function(){return Qa})),n.d(e,"dragDisable",(function(){return Te})),n.d(e,"dragEnable",(function(){return Ce})),n.d(e,"dsvFormat",(function(){return oo})),n.d(e,"csvParse",(function(){return co})),n.d(e,"csvParseRows",(function(){return uo})),n.d(e,"csvFormat",(function(){return lo})),n.d(e,"csvFormatBody",(function(){return ho})),n.d(e,"csvFormatRows",(function(){return fo})),n.d(e,"csvFormatRow",(function(){return po})),n.d(e,"csvFormatValue",(function(){return go})),n.d(e,"tsvParse",(function(){return vo})),n.d(e,"tsvParseRows",(function(){return mo})),n.d(e,"tsvFormat",(function(){return bo})),n.d(e,"tsvFormatBody",(function(){return xo})),n.d(e,"tsvFormatRows",(function(){return _o})),n.d(e,"tsvFormatRow",(function(){return ko})),n.d(e,"tsvFormatValue",(function(){return wo})),n.d(e,"autoType",(function(){return Eo})),n.d(e,"easeLinear",(function(){return Co})),n.d(e,"easeQuad",(function(){return Mo})),n.d(e,"easeQuadIn",(function(){return Ao})),n.d(e,"easeQuadOut",(function(){return So})),n.d(e,"easeQuadInOut",(function(){return Mo})),n.d(e,"easeCubic",(function(){return Vr})),n.d(e,"easeCubicIn",(function(){return Wr})),n.d(e,"easeCubicOut",(function(){return Hr})),n.d(e,"easeCubicInOut",(function(){return Vr})),n.d(e,"easePoly",(function(){return No})),n.d(e,"easePolyIn",(function(){return Oo})),n.d(e,"easePolyOut",(function(){return Do})),n.d(e,"easePolyInOut",(function(){return No})),n.d(e,"easeSin",(function(){return Io})),n.d(e,"easeSinIn",(function(){return Fo})),n.d(e,"easeSinOut",(function(){return Po})),n.d(e,"easeSinInOut",(function(){return Io})),n.d(e,"easeExp",(function(){return Yo})),n.d(e,"easeExpIn",(function(){return jo})),n.d(e,"easeExpOut",(function(){return Ro})),n.d(e,"easeExpInOut",(function(){return Yo})),n.d(e,"easeCircle",(function(){return $o})),n.d(e,"easeCircleIn",(function(){return zo})),n.d(e,"easeCircleOut",(function(){return Uo})),n.d(e,"easeCircleInOut",(function(){return $o})),n.d(e,"easeBounce",(function(){return Ho})),n.d(e,"easeBounceIn",(function(){return Wo})),n.d(e,"easeBounceOut",(function(){return Ho})),n.d(e,"easeBounceInOut",(function(){return Vo})),n.d(e,"easeBack",(function(){return Xo})),n.d(e,"easeBackIn",(function(){return Go})),n.d(e,"easeBackOut",(function(){return qo})),n.d(e,"easeBackInOut",(function(){return Xo})),n.d(e,"easeElastic",(function(){return Ko})),n.d(e,"easeElasticIn",(function(){return Jo})),n.d(e,"easeElasticOut",(function(){return Ko})),n.d(e,"easeElasticInOut",(function(){return Qo})),n.d(e,"blob",(function(){return es})),n.d(e,"buffer",(function(){return rs})),n.d(e,"dsv",(function(){return ss})),n.d(e,"csv",(function(){return cs})),n.d(e,"tsv",(function(){return us})),n.d(e,"image",(function(){return ls})),n.d(e,"json",(function(){return fs})),n.d(e,"text",(function(){return as})),n.d(e,"xml",(function(){return ps})),n.d(e,"html",(function(){return gs})),n.d(e,"svg",(function(){return ys})),n.d(e,"forceCenter",(function(){return vs})),n.d(e,"forceCollide",(function(){return Os})),n.d(e,"forceLink",(function(){return Bs})),n.d(e,"forceManyBody",(function(){return js})),n.d(e,"forceRadial",(function(){return Rs})),n.d(e,"forceSimulation",(function(){return Is})),n.d(e,"forceX",(function(){return Ys})),n.d(e,"forceY",(function(){return zs})),n.d(e,"formatDefaultLocale",(function(){return rc})),n.d(e,"format",(function(){return Xs})),n.d(e,"formatPrefix",(function(){return Zs})),n.d(e,"formatLocale",(function(){return nc})),n.d(e,"formatSpecifier",(function(){return Hs})),n.d(e,"FormatSpecifier",(function(){return Vs})),n.d(e,"precisionFixed",(function(){return ic})),n.d(e,"precisionPrefix",(function(){return ac})),n.d(e,"precisionRound",(function(){return oc})),n.d(e,"geoArea",(function(){return Jc})),n.d(e,"geoBounds",(function(){return $u})),n.d(e,"geoCentroid",(function(){return el})),n.d(e,"geoCircle",(function(){return fl})),n.d(e,"geoClipAntimeridian",(function(){return El})),n.d(e,"geoClipCircle",(function(){return Tl})),n.d(e,"geoClipExtent",(function(){return Ol})),n.d(e,"geoClipRectangle",(function(){return Cl})),n.d(e,"geoContains",(function(){return ql})),n.d(e,"geoDistance",(function(){return Rl})),n.d(e,"geoGraticule",(function(){return Jl})),n.d(e,"geoGraticule10",(function(){return Kl})),n.d(e,"geoInterpolate",(function(){return rh})),n.d(e,"geoLength",(function(){return Pl})),n.d(e,"geoPath",(function(){return ef})),n.d(e,"geoAlbers",(function(){return _f})),n.d(e,"geoAlbersUsa",(function(){return kf})),n.d(e,"geoAzimuthalEqualArea",(function(){return Cf})),n.d(e,"geoAzimuthalEqualAreaRaw",(function(){return Tf})),n.d(e,"geoAzimuthalEquidistant",(function(){return Sf})),n.d(e,"geoAzimuthalEquidistantRaw",(function(){return Af})),n.d(e,"geoConicConformal",(function(){return Lf})),n.d(e,"geoConicConformalRaw",(function(){return Bf})),n.d(e,"geoConicEqualArea",(function(){return xf})),n.d(e,"geoConicEqualAreaRaw",(function(){return bf})),n.d(e,"geoConicEquidistant",(function(){return jf})),n.d(e,"geoConicEquidistantRaw",(function(){return If})),n.d(e,"geoEqualEarth",(function(){return Hf})),n.d(e,"geoEqualEarthRaw",(function(){return Wf})),n.d(e,"geoEquirectangular",(function(){return Pf})),n.d(e,"geoEquirectangularRaw",(function(){return Ff})),n.d(e,"geoGnomonic",(function(){return Gf})),n.d(e,"geoGnomonicRaw",(function(){return Vf})),n.d(e,"geoIdentity",(function(){return Xf})),n.d(e,"geoProjection",(function(){return yf})),n.d(e,"geoProjectionMutator",(function(){return vf})),n.d(e,"geoMercator",(function(){return Of})),n.d(e,"geoMercatorRaw",(function(){return Mf})),n.d(e,"geoNaturalEarth1",(function(){return Jf})),n.d(e,"geoNaturalEarth1Raw",(function(){return Zf})),n.d(e,"geoOrthographic",(function(){return Qf})),n.d(e,"geoOrthographicRaw",(function(){return Kf})),n.d(e,"geoStereographic",(function(){return ed})),n.d(e,"geoStereographicRaw",(function(){return td})),n.d(e,"geoTransverseMercator",(function(){return rd})),n.d(e,"geoTransverseMercatorRaw",(function(){return nd})),n.d(e,"geoRotation",(function(){return ul})),n.d(e,"geoStream",(function(){return $c})),n.d(e,"geoTransform",(function(){return nf})),n.d(e,"cluster",(function(){return sd})),n.d(e,"hierarchy",(function(){return ud})),n.d(e,"pack",(function(){return Ld})),n.d(e,"packSiblings",(function(){return Sd})),n.d(e,"packEnclose",(function(){return gd})),n.d(e,"partition",(function(){return Yd})),n.d(e,"stratify",(function(){return Hd})),n.d(e,"tree",(function(){return Kd})),n.d(e,"treemap",(function(){return rp})),n.d(e,"treemapBinary",(function(){return ip})),n.d(e,"treemapDice",(function(){return Rd})),n.d(e,"treemapSlice",(function(){return Qd})),n.d(e,"treemapSliceDice",(function(){return ap})),n.d(e,"treemapSquarify",(function(){return np})),n.d(e,"treemapResquarify",(function(){return op})),n.d(e,"interpolate",(function(){return Sn})),n.d(e,"interpolateArray",(function(){return mn})),n.d(e,"interpolateBasis",(function(){return an})),n.d(e,"interpolateBasisClosed",(function(){return on})),n.d(e,"interpolateDate",(function(){return xn})),n.d(e,"interpolateDiscrete",(function(){return sp})),n.d(e,"interpolateHue",(function(){return cp})),n.d(e,"interpolateNumber",(function(){return _n})),n.d(e,"interpolateNumberArray",(function(){return yn})),n.d(e,"interpolateObject",(function(){return kn})),n.d(e,"interpolateRound",(function(){return up})),n.d(e,"interpolateString",(function(){return An})),n.d(e,"interpolateTransformCss",(function(){return hr})),n.d(e,"interpolateTransformSvg",(function(){return fr})),n.d(e,"interpolateZoom",(function(){return fp})),n.d(e,"interpolateRgb",(function(){return fn})),n.d(e,"interpolateRgbBasis",(function(){return pn})),n.d(e,"interpolateRgbBasisClosed",(function(){return gn})),n.d(e,"interpolateHsl",(function(){return pp})),n.d(e,"interpolateHslLong",(function(){return gp})),n.d(e,"interpolateLab",(function(){return yp})),n.d(e,"interpolateHcl",(function(){return mp})),n.d(e,"interpolateHclLong",(function(){return bp})),n.d(e,"interpolateCubehelix",(function(){return _p})),n.d(e,"interpolateCubehelixLong",(function(){return kp})),n.d(e,"piecewise",(function(){return wp})),n.d(e,"quantize",(function(){return Ep})),n.d(e,"path",(function(){return Ui})),n.d(e,"polygonArea",(function(){return Tp})),n.d(e,"polygonCentroid",(function(){return Cp})),n.d(e,"polygonHull",(function(){return Mp})),n.d(e,"polygonContains",(function(){return Op})),n.d(e,"polygonLength",(function(){return Dp})),n.d(e,"quadtree",(function(){return Es})),n.d(e,"randomUniform",(function(){return Bp})),n.d(e,"randomNormal",(function(){return Lp})),n.d(e,"randomLogNormal",(function(){return Fp})),n.d(e,"randomBates",(function(){return Ip})),n.d(e,"randomIrwinHall",(function(){return Pp})),n.d(e,"randomExponential",(function(){return jp})),n.d(e,"scaleBand",(function(){return Vp})),n.d(e,"scalePoint",(function(){return qp})),n.d(e,"scaleIdentity",(function(){return cg})),n.d(e,"scaleLinear",(function(){return sg})),n.d(e,"scaleLog",(function(){return vg})),n.d(e,"scaleSymlog",(function(){return _g})),n.d(e,"scaleOrdinal",(function(){return Hp})),n.d(e,"scaleImplicit",(function(){return Wp})),n.d(e,"scalePow",(function(){return Cg})),n.d(e,"scaleSqrt",(function(){return Ag})),n.d(e,"scaleQuantile",(function(){return Sg})),n.d(e,"scaleQuantize",(function(){return Mg})),n.d(e,"scaleThreshold",(function(){return Og})),n.d(e,"scaleTime",(function(){return sm})),n.d(e,"scaleUtc",(function(){return vm})),n.d(e,"scaleSequential",(function(){return xm})),n.d(e,"scaleSequentialLog",(function(){return _m})),n.d(e,"scaleSequentialPow",(function(){return wm})),n.d(e,"scaleSequentialSqrt",(function(){return Em})),n.d(e,"scaleSequentialSymlog",(function(){return km})),n.d(e,"scaleSequentialQuantile",(function(){return Tm})),n.d(e,"scaleDiverging",(function(){return Am})),n.d(e,"scaleDivergingLog",(function(){return Sm})),n.d(e,"scaleDivergingPow",(function(){return Om})),n.d(e,"scaleDivergingSqrt",(function(){return Dm})),n.d(e,"scaleDivergingSymlog",(function(){return Mm})),n.d(e,"tickFormat",(function(){return ag})),n.d(e,"schemeCategory10",(function(){return Bm})),n.d(e,"schemeAccent",(function(){return Lm})),n.d(e,"schemeDark2",(function(){return Fm})),n.d(e,"schemePaired",(function(){return Pm})),n.d(e,"schemePastel1",(function(){return Im})),n.d(e,"schemePastel2",(function(){return jm})),n.d(e,"schemeSet1",(function(){return Rm})),n.d(e,"schemeSet2",(function(){return Ym})),n.d(e,"schemeSet3",(function(){return zm})),n.d(e,"schemeTableau10",(function(){return Um})),n.d(e,"interpolateBrBG",(function(){return Hm})),n.d(e,"schemeBrBG",(function(){return Wm})),n.d(e,"interpolatePRGn",(function(){return Gm})),n.d(e,"schemePRGn",(function(){return Vm})),n.d(e,"interpolatePiYG",(function(){return Xm})),n.d(e,"schemePiYG",(function(){return qm})),n.d(e,"interpolatePuOr",(function(){return Jm})),n.d(e,"schemePuOr",(function(){return Zm})),n.d(e,"interpolateRdBu",(function(){return Qm})),n.d(e,"schemeRdBu",(function(){return Km})),n.d(e,"interpolateRdGy",(function(){return eb})),n.d(e,"schemeRdGy",(function(){return tb})),n.d(e,"interpolateRdYlBu",(function(){return rb})),n.d(e,"schemeRdYlBu",(function(){return nb})),n.d(e,"interpolateRdYlGn",(function(){return ab})),n.d(e,"schemeRdYlGn",(function(){return ib})),n.d(e,"interpolateSpectral",(function(){return sb})),n.d(e,"schemeSpectral",(function(){return ob})),n.d(e,"interpolateBuGn",(function(){return ub})),n.d(e,"schemeBuGn",(function(){return cb})),n.d(e,"interpolateBuPu",(function(){return hb})),n.d(e,"schemeBuPu",(function(){return lb})),n.d(e,"interpolateGnBu",(function(){return db})),n.d(e,"schemeGnBu",(function(){return fb})),n.d(e,"interpolateOrRd",(function(){return gb})),n.d(e,"schemeOrRd",(function(){return pb})),n.d(e,"interpolatePuBuGn",(function(){return vb})),n.d(e,"schemePuBuGn",(function(){return yb})),n.d(e,"interpolatePuBu",(function(){return bb})),n.d(e,"schemePuBu",(function(){return mb})),n.d(e,"interpolatePuRd",(function(){return _b})),n.d(e,"schemePuRd",(function(){return xb})),n.d(e,"interpolateRdPu",(function(){return wb})),n.d(e,"schemeRdPu",(function(){return kb})),n.d(e,"interpolateYlGnBu",(function(){return Tb})),n.d(e,"schemeYlGnBu",(function(){return Eb})),n.d(e,"interpolateYlGn",(function(){return Ab})),n.d(e,"schemeYlGn",(function(){return Cb})),n.d(e,"interpolateYlOrBr",(function(){return Mb})),n.d(e,"schemeYlOrBr",(function(){return Sb})),n.d(e,"interpolateYlOrRd",(function(){return Db})),n.d(e,"schemeYlOrRd",(function(){return Ob})),n.d(e,"interpolateBlues",(function(){return Bb})),n.d(e,"schemeBlues",(function(){return Nb})),n.d(e,"interpolateGreens",(function(){return Fb})),n.d(e,"schemeGreens",(function(){return Lb})),n.d(e,"interpolateGreys",(function(){return Ib})),n.d(e,"schemeGreys",(function(){return Pb})),n.d(e,"interpolatePurples",(function(){return Rb})),n.d(e,"schemePurples",(function(){return jb})),n.d(e,"interpolateReds",(function(){return zb})),n.d(e,"schemeReds",(function(){return Yb})),n.d(e,"interpolateOranges",(function(){return $b})),n.d(e,"schemeOranges",(function(){return Ub})),n.d(e,"interpolateCividis",(function(){return Wb})),n.d(e,"interpolateCubehelixDefault",(function(){return Hb})),n.d(e,"interpolateRainbow",(function(){return Xb})),n.d(e,"interpolateWarm",(function(){return Vb})),n.d(e,"interpolateCool",(function(){return Gb})),n.d(e,"interpolateSinebow",(function(){return Qb})),n.d(e,"interpolateTurbo",(function(){return tx})),n.d(e,"interpolateViridis",(function(){return nx})),n.d(e,"interpolateMagma",(function(){return rx})),n.d(e,"interpolateInferno",(function(){return ix})),n.d(e,"interpolatePlasma",(function(){return ax})),n.d(e,"create",(function(){return ox})),n.d(e,"creator",(function(){return ne})),n.d(e,"local",(function(){return cx})),n.d(e,"matcher",(function(){return gt})),n.d(e,"mouse",(function(){return Nn})),n.d(e,"namespace",(function(){return wt})),n.d(e,"namespaces",(function(){return kt})),n.d(e,"clientPoint",(function(){return On})),n.d(e,"select",(function(){return ke})),n.d(e,"selectAll",(function(){return lx})),n.d(e,"selection",(function(){return _e})),n.d(e,"selector",(function(){return ft})),n.d(e,"selectorAll",(function(){return pt})),n.d(e,"style",(function(){return Lt})),n.d(e,"touch",(function(){return Dn})),n.d(e,"touches",(function(){return hx})),n.d(e,"window",(function(){return Ot})),n.d(e,"event",(function(){return ce})),n.d(e,"customEvent",(function(){return pe})),n.d(e,"arc",(function(){return Nx})),n.d(e,"area",(function(){return jx})),n.d(e,"line",(function(){return Ix})),n.d(e,"pie",(function(){return zx})),n.d(e,"areaRadial",(function(){return Gx})),n.d(e,"radialArea",(function(){return Gx})),n.d(e,"lineRadial",(function(){return Vx})),n.d(e,"radialLine",(function(){return Vx})),n.d(e,"pointRadial",(function(){return qx})),n.d(e,"linkHorizontal",(function(){return n_})),n.d(e,"linkVertical",(function(){return r_})),n.d(e,"linkRadial",(function(){return i_})),n.d(e,"symbol",(function(){return k_})),n.d(e,"symbols",(function(){return __})),n.d(e,"symbolCircle",(function(){return a_})),n.d(e,"symbolCross",(function(){return o_})),n.d(e,"symbolDiamond",(function(){return u_})),n.d(e,"symbolSquare",(function(){return p_})),n.d(e,"symbolStar",(function(){return d_})),n.d(e,"symbolTriangle",(function(){return y_})),n.d(e,"symbolWye",(function(){return x_})),n.d(e,"curveBasisClosed",(function(){return S_})),n.d(e,"curveBasisOpen",(function(){return O_})),n.d(e,"curveBasis",(function(){return C_})),n.d(e,"curveBundle",(function(){return N_})),n.d(e,"curveCardinalClosed",(function(){return I_})),n.d(e,"curveCardinalOpen",(function(){return R_})),n.d(e,"curveCardinal",(function(){return F_})),n.d(e,"curveCatmullRomClosed",(function(){return W_})),n.d(e,"curveCatmullRomOpen",(function(){return V_})),n.d(e,"curveCatmullRom",(function(){return U_})),n.d(e,"curveLinearClosed",(function(){return q_})),n.d(e,"curveLinear",(function(){return Lx})),n.d(e,"curveMonotoneX",(function(){return nk})),n.d(e,"curveMonotoneY",(function(){return rk})),n.d(e,"curveNatural",(function(){return ok})),n.d(e,"curveStep",(function(){return ck})),n.d(e,"curveStepAfter",(function(){return lk})),n.d(e,"curveStepBefore",(function(){return uk})),n.d(e,"stack",(function(){return pk})),n.d(e,"stackOffsetExpand",(function(){return gk})),n.d(e,"stackOffsetDiverging",(function(){return yk})),n.d(e,"stackOffsetNone",(function(){return hk})),n.d(e,"stackOffsetSilhouette",(function(){return vk})),n.d(e,"stackOffsetWiggle",(function(){return mk})),n.d(e,"stackOrderAppearance",(function(){return bk})),n.d(e,"stackOrderAscending",(function(){return _k})),n.d(e,"stackOrderDescending",(function(){return wk})),n.d(e,"stackOrderInsideOut",(function(){return Ek})),n.d(e,"stackOrderNone",(function(){return fk})),n.d(e,"stackOrderReverse",(function(){return Tk})),n.d(e,"timeInterval",(function(){return Bg})),n.d(e,"timeMillisecond",(function(){return py})),n.d(e,"timeMilliseconds",(function(){return gy})),n.d(e,"utcMillisecond",(function(){return py})),n.d(e,"utcMilliseconds",(function(){return gy})),n.d(e,"timeSecond",(function(){return hy})),n.d(e,"timeSeconds",(function(){return fy})),n.d(e,"utcSecond",(function(){return hy})),n.d(e,"utcSeconds",(function(){return fy})),n.d(e,"timeMinute",(function(){return cy})),n.d(e,"timeMinutes",(function(){return uy})),n.d(e,"timeHour",(function(){return ay})),n.d(e,"timeHours",(function(){return oy})),n.d(e,"timeDay",(function(){return ny})),n.d(e,"timeDays",(function(){return ry})),n.d(e,"timeWeek",(function(){return zg})),n.d(e,"timeWeeks",(function(){return qg})),n.d(e,"timeSunday",(function(){return zg})),n.d(e,"timeSundays",(function(){return qg})),n.d(e,"timeMonday",(function(){return Ug})),n.d(e,"timeMondays",(function(){return Xg})),n.d(e,"timeTuesday",(function(){return $g})),n.d(e,"timeTuesdays",(function(){return Zg})),n.d(e,"timeWednesday",(function(){return Wg})),n.d(e,"timeWednesdays",(function(){return Jg})),n.d(e,"timeThursday",(function(){return Hg})),n.d(e,"timeThursdays",(function(){return Kg})),n.d(e,"timeFriday",(function(){return Vg})),n.d(e,"timeFridays",(function(){return Qg})),n.d(e,"timeSaturday",(function(){return Gg})),n.d(e,"timeSaturdays",(function(){return ty})),n.d(e,"timeMonth",(function(){return jg})),n.d(e,"timeMonths",(function(){return Rg})),n.d(e,"timeYear",(function(){return Fg})),n.d(e,"timeYears",(function(){return Pg})),n.d(e,"utcMinute",(function(){return gm})),n.d(e,"utcMinutes",(function(){return ym})),n.d(e,"utcHour",(function(){return fm})),n.d(e,"utcHours",(function(){return dm})),n.d(e,"utcDay",(function(){return Ny})),n.d(e,"utcDays",(function(){return By})),n.d(e,"utcWeek",(function(){return vy})),n.d(e,"utcWeeks",(function(){return Ey})),n.d(e,"utcSunday",(function(){return vy})),n.d(e,"utcSundays",(function(){return Ey})),n.d(e,"utcMonday",(function(){return my})),n.d(e,"utcMondays",(function(){return Ty})),n.d(e,"utcTuesday",(function(){return by})),n.d(e,"utcTuesdays",(function(){return Cy})),n.d(e,"utcWednesday",(function(){return xy})),n.d(e,"utcWednesdays",(function(){return Ay})),n.d(e,"utcThursday",(function(){return _y})),n.d(e,"utcThursdays",(function(){return Sy})),n.d(e,"utcFriday",(function(){return ky})),n.d(e,"utcFridays",(function(){return My})),n.d(e,"utcSaturday",(function(){return wy})),n.d(e,"utcSaturdays",(function(){return Oy})),n.d(e,"utcMonth",(function(){return um})),n.d(e,"utcMonths",(function(){return lm})),n.d(e,"utcYear",(function(){return Fy})),n.d(e,"utcYears",(function(){return Py})),n.d(e,"timeFormatDefaultLocale",(function(){return rm})),n.d(e,"timeFormat",(function(){return Uy})),n.d(e,"timeParse",(function(){return $y})),n.d(e,"utcFormat",(function(){return Wy})),n.d(e,"utcParse",(function(){return Hy})),n.d(e,"timeFormatLocale",(function(){return Yy})),n.d(e,"isoFormat",(function(){return Ck})),n.d(e,"isoParse",(function(){return Ak})),n.d(e,"now",(function(){return zn})),n.d(e,"timer",(function(){return Wn})),n.d(e,"timerFlush",(function(){return Hn})),n.d(e,"timeout",(function(){return Xn})),n.d(e,"interval",(function(){return Sk})),n.d(e,"transition",(function(){return zr})),n.d(e,"active",(function(){return Zr})),n.d(e,"interrupt",(function(){return or})),n.d(e,"voronoi",(function(){return dw})),n.d(e,"zoom",(function(){return Aw})),n.d(e,"zoomTransform",(function(){return mw})),n.d(e,"zoomIdentity",(function(){return vw}))},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),function(t){for(var n in t)e.hasOwnProperty(n)||(e[n]=t[n])}(n(172))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,3],r=[1,5],i=[1,7],a=[2,5],o=[1,15],s=[1,17],c=[1,18],u=[1,20],l=[1,21],h=[1,22],f=[1,24],d=[1,25],p=[1,26],g=[1,27],y=[1,28],v=[1,29],m=[1,32],b=[1,33],x=[1,36],_=[1,4,5,16,21,22,23,25,27,28,29,30,31,33,35,36,37,48,56],k=[1,44],w=[4,5,16,21,22,23,25,27,28,29,30,31,33,37,48,56],E=[4,5,16,21,22,23,25,27,28,29,30,31,33,36,37,48,56],T=[4,5,16,21,22,23,25,27,28,29,30,31,33,35,37,48,56],C=[46,47,48],A=[1,4,5,7,16,21,22,23,25,27,28,29,30,31,33,35,36,37,48,56],S={trace:function(){},yy:{},symbols_:{error:2,start:3,SPACE:4,NEWLINE:5,directive:6,SD:7,document:8,line:9,statement:10,openDirective:11,typeDirective:12,closeDirective:13,":":14,argDirective:15,participant:16,actor:17,AS:18,restOfLine:19,signal:20,autonumber:21,activate:22,deactivate:23,note_statement:24,title:25,text2:26,loop:27,end:28,rect:29,opt:30,alt:31,else_sections:32,par:33,par_sections:34,and:35,else:36,note:37,placement:38,over:39,actor_pair:40,spaceList:41,",":42,left_of:43,right_of:44,signaltype:45,"+":46,"-":47,ACTOR:48,SOLID_OPEN_ARROW:49,DOTTED_OPEN_ARROW:50,SOLID_ARROW:51,DOTTED_ARROW:52,SOLID_CROSS:53,DOTTED_CROSS:54,TXT:55,open_directive:56,type_directive:57,arg_directive:58,close_directive:59,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NEWLINE",7:"SD",14:":",16:"participant",18:"AS",19:"restOfLine",21:"autonumber",22:"activate",23:"deactivate",25:"title",27:"loop",28:"end",29:"rect",30:"opt",31:"alt",33:"par",35:"and",36:"else",37:"note",39:"over",42:",",43:"left_of",44:"right_of",46:"+",47:"-",48:"ACTOR",49:"SOLID_OPEN_ARROW",50:"DOTTED_OPEN_ARROW",51:"SOLID_ARROW",52:"DOTTED_ARROW",53:"SOLID_CROSS",54:"DOTTED_CROSS",55:"TXT",56:"open_directive",57:"type_directive",58:"arg_directive",59:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[3,2],[8,0],[8,2],[9,2],[9,1],[9,1],[6,4],[6,6],[10,5],[10,3],[10,2],[10,1],[10,3],[10,3],[10,2],[10,3],[10,4],[10,4],[10,4],[10,4],[10,4],[10,1],[34,1],[34,4],[32,1],[32,4],[24,4],[24,4],[41,2],[41,1],[40,3],[40,1],[38,1],[38,1],[20,5],[20,5],[20,4],[17,1],[45,1],[45,1],[45,1],[45,1],[45,1],[45,1],[26,1],[11,1],[12,1],[15,1],[13,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:return r.apply(a[s]),a[s];case 5:this.$=[];break;case 6:a[s-1].push(a[s]),this.$=a[s-1];break;case 7:case 8:this.$=a[s];break;case 9:this.$=[];break;case 12:a[s-3].description=r.parseMessage(a[s-1]),this.$=a[s-3];break;case 13:this.$=a[s-1];break;case 15:r.enableSequenceNumbers();break;case 16:this.$={type:"activeStart",signalType:r.LINETYPE.ACTIVE_START,actor:a[s-1]};break;case 17:this.$={type:"activeEnd",signalType:r.LINETYPE.ACTIVE_END,actor:a[s-1]};break;case 19:this.$=[{type:"setTitle",text:a[s-1]}];break;case 20:a[s-1].unshift({type:"loopStart",loopText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.LOOP_START}),a[s-1].push({type:"loopEnd",loopText:a[s-2],signalType:r.LINETYPE.LOOP_END}),this.$=a[s-1];break;case 21:a[s-1].unshift({type:"rectStart",color:r.parseMessage(a[s-2]),signalType:r.LINETYPE.RECT_START}),a[s-1].push({type:"rectEnd",color:r.parseMessage(a[s-2]),signalType:r.LINETYPE.RECT_END}),this.$=a[s-1];break;case 22:a[s-1].unshift({type:"optStart",optText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.OPT_START}),a[s-1].push({type:"optEnd",optText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.OPT_END}),this.$=a[s-1];break;case 23:a[s-1].unshift({type:"altStart",altText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.ALT_START}),a[s-1].push({type:"altEnd",signalType:r.LINETYPE.ALT_END}),this.$=a[s-1];break;case 24:a[s-1].unshift({type:"parStart",parText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.PAR_START}),a[s-1].push({type:"parEnd",signalType:r.LINETYPE.PAR_END}),this.$=a[s-1];break;case 27:this.$=a[s-3].concat([{type:"and",parText:r.parseMessage(a[s-1]),signalType:r.LINETYPE.PAR_AND},a[s]]);break;case 29:this.$=a[s-3].concat([{type:"else",altText:r.parseMessage(a[s-1]),signalType:r.LINETYPE.ALT_ELSE},a[s]]);break;case 30:this.$=[a[s-1],{type:"addNote",placement:a[s-2],actor:a[s-1].actor,text:a[s]}];break;case 31:a[s-2]=[].concat(a[s-1],a[s-1]).slice(0,2),a[s-2][0]=a[s-2][0].actor,a[s-2][1]=a[s-2][1].actor,this.$=[a[s-1],{type:"addNote",placement:r.PLACEMENT.OVER,actor:a[s-2].slice(0,2),text:a[s]}];break;case 34:this.$=[a[s-2],a[s]];break;case 35:this.$=a[s];break;case 36:this.$=r.PLACEMENT.LEFTOF;break;case 37:this.$=r.PLACEMENT.RIGHTOF;break;case 38:this.$=[a[s-4],a[s-1],{type:"addMessage",from:a[s-4].actor,to:a[s-1].actor,signalType:a[s-3],msg:a[s]},{type:"activeStart",signalType:r.LINETYPE.ACTIVE_START,actor:a[s-1]}];break;case 39:this.$=[a[s-4],a[s-1],{type:"addMessage",from:a[s-4].actor,to:a[s-1].actor,signalType:a[s-3],msg:a[s]},{type:"activeEnd",signalType:r.LINETYPE.ACTIVE_END,actor:a[s-4]}];break;case 40:this.$=[a[s-3],a[s-1],{type:"addMessage",from:a[s-3].actor,to:a[s-1].actor,signalType:a[s-2],msg:a[s]}];break;case 41:this.$={type:"addActor",actor:a[s]};break;case 42:this.$=r.LINETYPE.SOLID_OPEN;break;case 43:this.$=r.LINETYPE.DOTTED_OPEN;break;case 44:this.$=r.LINETYPE.SOLID;break;case 45:this.$=r.LINETYPE.DOTTED;break;case 46:this.$=r.LINETYPE.SOLID_CROSS;break;case 47:this.$=r.LINETYPE.DOTTED_CROSS;break;case 48:this.$=r.parseMessage(a[s].trim().substring(1));break;case 49:r.parseDirective("%%{","open_directive");break;case 50:r.parseDirective(a[s],"type_directive");break;case 51:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 52:r.parseDirective("}%%","close_directive","sequence")}},table:[{3:1,4:e,5:n,6:4,7:r,11:6,56:i},{1:[3]},{3:8,4:e,5:n,6:4,7:r,11:6,56:i},{3:9,4:e,5:n,6:4,7:r,11:6,56:i},{3:10,4:e,5:n,6:4,7:r,11:6,56:i},t([1,4,5,16,21,22,23,25,27,29,30,31,33,37,48,56],a,{8:11}),{12:12,57:[1,13]},{57:[2,49]},{1:[2,1]},{1:[2,2]},{1:[2,3]},{1:[2,4],4:o,5:s,6:30,9:14,10:16,11:6,16:c,17:31,20:19,21:u,22:l,23:h,24:23,25:f,27:d,29:p,30:g,31:y,33:v,37:m,48:b,56:i},{13:34,14:[1,35],59:x},t([14,59],[2,50]),t(_,[2,6]),{6:30,10:37,11:6,16:c,17:31,20:19,21:u,22:l,23:h,24:23,25:f,27:d,29:p,30:g,31:y,33:v,37:m,48:b,56:i},t(_,[2,8]),t(_,[2,9]),{17:38,48:b},{5:[1,39]},t(_,[2,15]),{17:40,48:b},{17:41,48:b},{5:[1,42]},{26:43,55:k},{19:[1,45]},{19:[1,46]},{19:[1,47]},{19:[1,48]},{19:[1,49]},t(_,[2,25]),{45:50,49:[1,51],50:[1,52],51:[1,53],52:[1,54],53:[1,55],54:[1,56]},{38:57,39:[1,58],43:[1,59],44:[1,60]},t([5,18,42,49,50,51,52,53,54,55],[2,41]),{5:[1,61]},{15:62,58:[1,63]},{5:[2,52]},t(_,[2,7]),{5:[1,65],18:[1,64]},t(_,[2,14]),{5:[1,66]},{5:[1,67]},t(_,[2,18]),{5:[1,68]},{5:[2,48]},t(w,a,{8:69}),t(w,a,{8:70}),t(w,a,{8:71}),t(E,a,{32:72,8:73}),t(T,a,{34:74,8:75}),{17:78,46:[1,76],47:[1,77],48:b},t(C,[2,42]),t(C,[2,43]),t(C,[2,44]),t(C,[2,45]),t(C,[2,46]),t(C,[2,47]),{17:79,48:b},{17:81,40:80,48:b},{48:[2,36]},{48:[2,37]},t(A,[2,10]),{13:82,59:x},{59:[2,51]},{19:[1,83]},t(_,[2,13]),t(_,[2,16]),t(_,[2,17]),t(_,[2,19]),{4:o,5:s,6:30,9:14,10:16,11:6,16:c,17:31,20:19,21:u,22:l,23:h,24:23,25:f,27:d,28:[1,84],29:p,30:g,31:y,33:v,37:m,48:b,56:i},{4:o,5:s,6:30,9:14,10:16,11:6,16:c,17:31,20:19,21:u,22:l,23:h,24:23,25:f,27:d,28:[1,85],29:p,30:g,31:y,33:v,37:m,48:b,56:i},{4:o,5:s,6:30,9:14,10:16,11:6,16:c,17:31,20:19,21:u,22:l,23:h,24:23,25:f,27:d,28:[1,86],29:p,30:g,31:y,33:v,37:m,48:b,56:i},{28:[1,87]},{4:o,5:s,6:30,9:14,10:16,11:6,16:c,17:31,20:19,21:u,22:l,23:h,24:23,25:f,27:d,28:[2,28],29:p,30:g,31:y,33:v,36:[1,88],37:m,48:b,56:i},{28:[1,89]},{4:o,5:s,6:30,9:14,10:16,11:6,16:c,17:31,20:19,21:u,22:l,23:h,24:23,25:f,27:d,28:[2,26],29:p,30:g,31:y,33:v,35:[1,90],37:m,48:b,56:i},{17:91,48:b},{17:92,48:b},{26:93,55:k},{26:94,55:k},{26:95,55:k},{42:[1,96],55:[2,35]},{5:[1,97]},{5:[1,98]},t(_,[2,20]),t(_,[2,21]),t(_,[2,22]),t(_,[2,23]),{19:[1,99]},t(_,[2,24]),{19:[1,100]},{26:101,55:k},{26:102,55:k},{5:[2,40]},{5:[2,30]},{5:[2,31]},{17:103,48:b},t(A,[2,11]),t(_,[2,12]),t(E,a,{8:73,32:104}),t(T,a,{8:75,34:105}),{5:[2,38]},{5:[2,39]},{55:[2,34]},{28:[2,29]},{28:[2,27]}],defaultActions:{7:[2,49],8:[2,1],9:[2,2],10:[2,3],36:[2,52],44:[2,48],59:[2,36],60:[2,37],63:[2,51],93:[2,40],94:[2,30],95:[2,31],101:[2,38],102:[2,39],103:[2,34],104:[2,29],105:[2,27]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},M={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;a<i.length;a++)if((n=this._input.match(this.rules[i[a]]))&&(!e||n[0].length>e[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),56;case 1:return this.begin("type_directive"),57;case 2:return this.popState(),this.begin("arg_directive"),14;case 3:return this.popState(),this.popState(),59;case 4:return 58;case 5:return 5;case 6:case 7:case 8:case 9:case 10:break;case 11:return this.begin("ID"),16;case 12:return e.yytext=e.yytext.trim(),this.begin("ALIAS"),48;case 13:return this.popState(),this.popState(),this.begin("LINE"),18;case 14:return this.popState(),this.popState(),5;case 15:return this.begin("LINE"),27;case 16:return this.begin("LINE"),29;case 17:return this.begin("LINE"),30;case 18:return this.begin("LINE"),31;case 19:return this.begin("LINE"),36;case 20:return this.begin("LINE"),33;case 21:return this.begin("LINE"),35;case 22:return this.popState(),19;case 23:return 28;case 24:return 43;case 25:return 44;case 26:return 39;case 27:return 37;case 28:return this.begin("ID"),22;case 29:return this.begin("ID"),23;case 30:return 25;case 31:return 7;case 32:return 21;case 33:return 42;case 34:return 5;case 35:return e.yytext=e.yytext.trim(),48;case 36:return 51;case 37:return 52;case 38:return 49;case 39:return 50;case 40:return 53;case 41:return 54;case 42:return 55;case 43:return 46;case 44:return 47;case 45:return 5;case 46:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:participant\b)/i,/^(?:[^\->:\n,;]+?(?=((?!\n)\s)+as(?!\n)\s|[#\n;]|$))/i,/^(?:as\b)/i,/^(?:(?:))/i,/^(?:loop\b)/i,/^(?:rect\b)/i,/^(?:opt\b)/i,/^(?:alt\b)/i,/^(?:else\b)/i,/^(?:par\b)/i,/^(?:and\b)/i,/^(?:(?:[:]?(?:no)?wrap)?[^#\n;]*)/i,/^(?:end\b)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:over\b)/i,/^(?:note\b)/i,/^(?:activate\b)/i,/^(?:deactivate\b)/i,/^(?:title\b)/i,/^(?:sequenceDiagram\b)/i,/^(?:autonumber\b)/i,/^(?:,)/i,/^(?:;)/i,/^(?:[^\+\->:\n,;]+((?!(-x|--x))[\-]*[^\+\->:\n,;]+)*)/i,/^(?:->>)/i,/^(?:-->>)/i,/^(?:->)/i,/^(?:-->)/i,/^(?:-[x])/i,/^(?:--[x])/i,/^(?::(?:(?:no)?wrap)?[^#\n;]+)/i,/^(?:\+)/i,/^(?:-)/i,/^(?:$)/i,/^(?:.)/i],conditions:{open_directive:{rules:[1,8],inclusive:!1},type_directive:{rules:[2,3,8],inclusive:!1},arg_directive:{rules:[3,4,8],inclusive:!1},ID:{rules:[7,8,12],inclusive:!1},ALIAS:{rules:[7,8,13,14],inclusive:!1},LINE:{rules:[7,8,22],inclusive:!1},INITIAL:{rules:[0,5,6,8,9,10,11,15,16,17,18,19,20,21,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],inclusive:!0}}};function O(){this.yy={}}return S.lexer=M,O.prototype=S,S.Parser=O,new O}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){var r=n(198);t.exports={Graph:r.Graph,json:n(301),alg:n(302),version:r.version}},function(t,e,n){var r;try{r={cloneDeep:n(313),constant:n(86),defaults:n(154),each:n(87),filter:n(128),find:n(314),flatten:n(156),forEach:n(126),forIn:n(319),has:n(93),isUndefined:n(139),last:n(320),map:n(140),mapValues:n(321),max:n(322),merge:n(324),min:n(329),minBy:n(330),now:n(331),pick:n(161),range:n(162),reduce:n(142),sortBy:n(338),uniqueId:n(163),values:n(147),zipObject:n(343)}}catch(t){}r||(r=window._),t.exports=r},function(t,e){var n=Array.isArray;t.exports=n},function(t,e,n){
+/**
+ * @license
+ * Copyright (c) 2012-2013 Chris Pettitt
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+t.exports={graphlib:n(311),dagre:n(153),intersect:n(368),render:n(370),util:n(12),version:n(382)}},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},function(t,e,n){"use strict";var r=n(4),i=n(17).Graph;function a(t,e,n,i){var a;do{a=r.uniqueId(i)}while(t.hasNode(a));return n.dummy=e,t.setNode(a,n),a}function o(t){return r.max(r.map(t.nodes(),(function(e){var n=t.node(e).rank;if(!r.isUndefined(n))return n})))}t.exports={addDummyNode:a,simplify:function(t){var e=(new i).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){var r=e.edge(n.v,n.w)||{weight:0,minlen:1},i=t.edge(n);e.setEdge(n.v,n.w,{weight:r.weight+i.weight,minlen:Math.max(r.minlen,i.minlen)})})),e},asNonCompoundGraph:function(t){var e=new i({multigraph:t.isMultigraph()}).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){t.children(n).length||e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){e.setEdge(n,t.edge(n))})),e},successorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.outEdges(e),(function(e){n[e.w]=(n[e.w]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},predecessorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.inEdges(e),(function(e){n[e.v]=(n[e.v]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},intersectRect:function(t,e){var n,r,i=t.x,a=t.y,o=e.x-i,s=e.y-a,c=t.width/2,u=t.height/2;if(!o&&!s)throw new Error("Not possible to find intersection inside of the rectangle");Math.abs(s)*c>Math.abs(o)*u?(s<0&&(u=-u),n=u*o/s,r=u):(o<0&&(c=-c),n=c,r=c*s/o);return{x:i+n,y:a+r}},buildLayerMatrix:function(t){var e=r.map(r.range(o(t)+1),(function(){return[]}));return r.forEach(t.nodes(),(function(n){var i=t.node(n),a=i.rank;r.isUndefined(a)||(e[a][i.order]=n)})),e},normalizeRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank})));r.forEach(t.nodes(),(function(n){var i=t.node(n);r.has(i,"rank")&&(i.rank-=e)}))},removeEmptyRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank}))),n=[];r.forEach(t.nodes(),(function(r){var i=t.node(r).rank-e;n[i]||(n[i]=[]),n[i].push(r)}));var i=0,a=t.graph().nodeRankFactor;r.forEach(n,(function(e,n){r.isUndefined(e)&&n%a!=0?--i:i&&r.forEach(e,(function(e){t.node(e).rank+=i}))}))},addBorderNode:function(t,e,n,r){var i={width:0,height:0};arguments.length>=4&&(i.rank=n,i.order=r);return a(t,"border",i,e)},maxRank:o,partition:function(t,e){var n={lhs:[],rhs:[]};return r.forEach(t,(function(t){e(t)?n.lhs.push(t):n.rhs.push(t)})),n},time:function(t,e){var n=r.now();try{return e()}finally{console.log(t+" time: "+(r.now()-n)+"ms")}},notime:function(t,e){return e()}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(173),i=n(174),a=n(175),o={channel:r.default,lang:i.default,unit:a.default};e.default=o},function(t,e,n){var r;try{r={clone:n(199),constant:n(86),each:n(87),filter:n(128),has:n(93),isArray:n(5),isEmpty:n(276),isFunction:n(37),isUndefined:n(139),keys:n(30),map:n(140),reduce:n(142),size:n(279),transform:n(285),union:n(286),values:n(147)}}catch(t){}r||(r=window._),t.exports=r},function(t,e){t.exports=function(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}},function(t,e,n){var r=n(43);t.exports={isSubgraph:function(t,e){return!!t.children(e).length},edgeToId:function(t){return a(t.v)+":"+a(t.w)+":"+a(t.name)},applyStyle:function(t,e){e&&t.attr("style",e)},applyClass:function(t,e,n){e&&t.attr("class",e).attr("class",n+" "+t.attr("class"))},applyTransition:function(t,e){var n=e.graph();if(r.isPlainObject(n)){var i=n.transition;if(r.isFunction(i))return i(t)}return t}};var i=/:/g;function a(t){return t?String(t).replace(i,"\\:"):""}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,7],n=[1,6],r=[1,14],i=[1,25],a=[1,28],o=[1,26],s=[1,27],c=[1,29],u=[1,30],l=[1,31],h=[1,32],f=[1,34],d=[1,35],p=[1,36],g=[10,19],y=[1,48],v=[1,49],m=[1,50],b=[1,51],x=[1,52],_=[1,53],k=[10,19,25,32,33,41,44,45,46,47,48,49,54,56],w=[10,19,23,25,32,33,37,41,44,45,46,47,48,49,54,56,71,72,73],E=[10,13,17,19],T=[41,71,72,73],C=[41,48,49,71,72,73],A=[41,44,45,46,47,71,72,73],S=[10,19,25],M=[1,85],O={trace:function(){},yy:{},symbols_:{error:2,start:3,mermaidDoc:4,directive:5,graphConfig:6,openDirective:7,typeDirective:8,closeDirective:9,NEWLINE:10,":":11,argDirective:12,open_directive:13,type_directive:14,arg_directive:15,close_directive:16,CLASS_DIAGRAM:17,statements:18,EOF:19,statement:20,className:21,alphaNumToken:22,GENERICTYPE:23,relationStatement:24,LABEL:25,classStatement:26,methodStatement:27,annotationStatement:28,clickStatement:29,cssClassStatement:30,CLASS:31,STYLE_SEPARATOR:32,STRUCT_START:33,members:34,STRUCT_STOP:35,ANNOTATION_START:36,ANNOTATION_END:37,MEMBER:38,SEPARATOR:39,relation:40,STR:41,relationType:42,lineType:43,AGGREGATION:44,EXTENSION:45,COMPOSITION:46,DEPENDENCY:47,LINE:48,DOTTED_LINE:49,CALLBACK:50,LINK:51,LINK_TARGET:52,CLICK:53,CALLBACK_NAME:54,CALLBACK_ARGS:55,HREF:56,CSSCLASS:57,commentToken:58,textToken:59,graphCodeTokens:60,textNoTagsToken:61,TAGSTART:62,TAGEND:63,"==":64,"--":65,PCT:66,DEFAULT:67,SPACE:68,MINUS:69,keywords:70,UNICODE_TEXT:71,NUM:72,ALPHA:73,$accept:0,$end:1},terminals_:{2:"error",10:"NEWLINE",11:":",13:"open_directive",14:"type_directive",15:"arg_directive",16:"close_directive",17:"CLASS_DIAGRAM",19:"EOF",23:"GENERICTYPE",25:"LABEL",31:"CLASS",32:"STYLE_SEPARATOR",33:"STRUCT_START",35:"STRUCT_STOP",36:"ANNOTATION_START",37:"ANNOTATION_END",38:"MEMBER",39:"SEPARATOR",41:"STR",44:"AGGREGATION",45:"EXTENSION",46:"COMPOSITION",47:"DEPENDENCY",48:"LINE",49:"DOTTED_LINE",50:"CALLBACK",51:"LINK",52:"LINK_TARGET",53:"CLICK",54:"CALLBACK_NAME",55:"CALLBACK_ARGS",56:"HREF",57:"CSSCLASS",60:"graphCodeTokens",62:"TAGSTART",63:"TAGEND",64:"==",65:"--",66:"PCT",67:"DEFAULT",68:"SPACE",69:"MINUS",70:"keywords",71:"UNICODE_TEXT",72:"NUM",73:"ALPHA"},productions_:[0,[3,1],[3,2],[4,1],[5,4],[5,6],[7,1],[8,1],[12,1],[9,1],[6,4],[18,1],[18,2],[18,3],[21,1],[21,2],[21,3],[21,2],[20,1],[20,2],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[26,2],[26,4],[26,5],[26,7],[28,4],[34,1],[34,2],[27,1],[27,2],[27,1],[27,1],[24,3],[24,4],[24,4],[24,5],[40,3],[40,2],[40,2],[40,1],[42,1],[42,1],[42,1],[42,1],[43,1],[43,1],[29,3],[29,4],[29,3],[29,4],[29,4],[29,5],[29,3],[29,4],[29,4],[29,5],[29,3],[29,4],[29,4],[29,5],[30,3],[58,1],[58,1],[59,1],[59,1],[59,1],[59,1],[59,1],[59,1],[59,1],[61,1],[61,1],[61,1],[61,1],[22,1],[22,1],[22,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 6:r.parseDirective("%%{","open_directive");break;case 7:r.parseDirective(a[s],"type_directive");break;case 8:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 9:r.parseDirective("}%%","close_directive","class");break;case 14:this.$=a[s];break;case 15:this.$=a[s-1]+a[s];break;case 16:this.$=a[s-2]+"~"+a[s-1]+a[s];break;case 17:this.$=a[s-1]+"~"+a[s];break;case 18:r.addRelation(a[s]);break;case 19:a[s-1].title=r.cleanupLabel(a[s]),r.addRelation(a[s-1]);break;case 26:r.addClass(a[s]);break;case 27:r.addClass(a[s-2]),r.setCssClass(a[s-2],a[s]);break;case 28:r.addClass(a[s-3]),r.addMembers(a[s-3],a[s-1]);break;case 29:r.addClass(a[s-5]),r.setCssClass(a[s-5],a[s-3]),r.addMembers(a[s-5],a[s-1]);break;case 30:r.addAnnotation(a[s],a[s-2]);break;case 31:this.$=[a[s]];break;case 32:a[s].push(a[s-1]),this.$=a[s];break;case 33:break;case 34:r.addMember(a[s-1],r.cleanupLabel(a[s]));break;case 35:case 36:break;case 37:this.$={id1:a[s-2],id2:a[s],relation:a[s-1],relationTitle1:"none",relationTitle2:"none"};break;case 38:this.$={id1:a[s-3],id2:a[s],relation:a[s-1],relationTitle1:a[s-2],relationTitle2:"none"};break;case 39:this.$={id1:a[s-3],id2:a[s],relation:a[s-2],relationTitle1:"none",relationTitle2:a[s-1]};break;case 40:this.$={id1:a[s-4],id2:a[s],relation:a[s-2],relationTitle1:a[s-3],relationTitle2:a[s-1]};break;case 41:this.$={type1:a[s-2],type2:a[s],lineType:a[s-1]};break;case 42:this.$={type1:"none",type2:a[s],lineType:a[s-1]};break;case 43:this.$={type1:a[s-1],type2:"none",lineType:a[s]};break;case 44:this.$={type1:"none",type2:"none",lineType:a[s]};break;case 45:this.$=r.relationType.AGGREGATION;break;case 46:this.$=r.relationType.EXTENSION;break;case 47:this.$=r.relationType.COMPOSITION;break;case 48:this.$=r.relationType.DEPENDENCY;break;case 49:this.$=r.lineType.LINE;break;case 50:this.$=r.lineType.DOTTED_LINE;break;case 51:case 57:this.$=a[s-2],r.setClickEvent(a[s-1],a[s]);break;case 52:case 58:this.$=a[s-3],r.setClickEvent(a[s-2],a[s-1]),r.setTooltip(a[s-2],a[s]);break;case 53:case 61:this.$=a[s-2],r.setLink(a[s-1],a[s]);break;case 54:this.$=a[s-3],r.setLink(a[s-2],a[s-1],a[s]);break;case 55:case 63:this.$=a[s-3],r.setLink(a[s-2],a[s-1]),r.setTooltip(a[s-2],a[s]);break;case 56:case 64:this.$=a[s-4],r.setLink(a[s-3],a[s-2],a[s]),r.setTooltip(a[s-3],a[s-1]);break;case 59:this.$=a[s-3],r.setClickEvent(a[s-2],a[s-1],a[s]);break;case 60:this.$=a[s-4],r.setClickEvent(a[s-3],a[s-2],a[s-1]),r.setTooltip(a[s-3],a[s]);break;case 62:this.$=a[s-3],r.setLink(a[s-2],a[s-1],a[s]);break;case 65:r.setCssClass(a[s-1],a[s])}},table:[{3:1,4:2,5:3,6:4,7:5,13:e,17:n},{1:[3]},{1:[2,1]},{3:8,4:2,5:3,6:4,7:5,13:e,17:n},{1:[2,3]},{8:9,14:[1,10]},{10:[1,11]},{14:[2,6]},{1:[2,2]},{9:12,11:[1,13],16:r},t([11,16],[2,7]),{5:23,7:5,13:e,18:15,20:16,21:24,22:33,24:17,26:18,27:19,28:20,29:21,30:22,31:i,36:a,38:o,39:s,50:c,51:u,53:l,57:h,71:f,72:d,73:p},{10:[1,37]},{12:38,15:[1,39]},{10:[2,9]},{19:[1,40]},{10:[1,41],19:[2,11]},t(g,[2,18],{25:[1,42]}),t(g,[2,20]),t(g,[2,21]),t(g,[2,22]),t(g,[2,23]),t(g,[2,24]),t(g,[2,25]),t(g,[2,33],{40:43,42:46,43:47,25:[1,45],41:[1,44],44:y,45:v,46:m,47:b,48:x,49:_}),{21:54,22:33,71:f,72:d,73:p},t(g,[2,35]),t(g,[2,36]),{22:55,71:f,72:d,73:p},{21:56,22:33,71:f,72:d,73:p},{21:57,22:33,71:f,72:d,73:p},{21:58,22:33,71:f,72:d,73:p},{41:[1,59]},t(k,[2,14],{22:33,21:60,23:[1,61],71:f,72:d,73:p}),t(w,[2,79]),t(w,[2,80]),t(w,[2,81]),t(E,[2,4]),{9:62,16:r},{16:[2,8]},{1:[2,10]},{5:23,7:5,13:e,18:63,19:[2,12],20:16,21:24,22:33,24:17,26:18,27:19,28:20,29:21,30:22,31:i,36:a,38:o,39:s,50:c,51:u,53:l,57:h,71:f,72:d,73:p},t(g,[2,19]),{21:64,22:33,41:[1,65],71:f,72:d,73:p},{40:66,42:46,43:47,44:y,45:v,46:m,47:b,48:x,49:_},t(g,[2,34]),{43:67,48:x,49:_},t(T,[2,44],{42:68,44:y,45:v,46:m,47:b}),t(C,[2,45]),t(C,[2,46]),t(C,[2,47]),t(C,[2,48]),t(A,[2,49]),t(A,[2,50]),t(g,[2,26],{32:[1,69],33:[1,70]}),{37:[1,71]},{41:[1,72]},{41:[1,73]},{54:[1,74],56:[1,75]},{22:76,71:f,72:d,73:p},t(k,[2,15]),t(k,[2,17],{22:33,21:77,71:f,72:d,73:p}),{10:[1,78]},{19:[2,13]},t(S,[2,37]),{21:79,22:33,71:f,72:d,73:p},{21:80,22:33,41:[1,81],71:f,72:d,73:p},t(T,[2,43],{42:82,44:y,45:v,46:m,47:b}),t(T,[2,42]),{22:83,71:f,72:d,73:p},{34:84,38:M},{21:86,22:33,71:f,72:d,73:p},t(g,[2,51],{41:[1,87]}),t(g,[2,53],{41:[1,89],52:[1,88]}),t(g,[2,57],{41:[1,90],55:[1,91]}),t(g,[2,61],{41:[1,93],52:[1,92]}),t(g,[2,65]),t(k,[2,16]),t(E,[2,5]),t(S,[2,39]),t(S,[2,38]),{21:94,22:33,71:f,72:d,73:p},t(T,[2,41]),t(g,[2,27],{33:[1,95]}),{35:[1,96]},{34:97,35:[2,31],38:M},t(g,[2,30]),t(g,[2,52]),t(g,[2,54]),t(g,[2,55],{52:[1,98]}),t(g,[2,58]),t(g,[2,59],{41:[1,99]}),t(g,[2,62]),t(g,[2,63],{52:[1,100]}),t(S,[2,40]),{34:101,38:M},t(g,[2,28]),{35:[2,32]},t(g,[2,56]),t(g,[2,60]),t(g,[2,64]),{35:[1,102]},t(g,[2,29])],defaultActions:{2:[2,1],4:[2,3],7:[2,6],8:[2,2],14:[2,9],39:[2,8],40:[2,10],63:[2,13],97:[2,32]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},D={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;a<i.length;a++)if((n=this._input.match(this.rules[i[a]]))&&(!e||n[0].length>e[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),13;case 1:return this.begin("type_directive"),14;case 2:return this.popState(),this.begin("arg_directive"),11;case 3:return this.popState(),this.popState(),16;case 4:return 15;case 5:case 6:break;case 7:return 10;case 8:break;case 9:case 10:return 17;case 11:return this.begin("struct"),33;case 12:return"EOF_IN_STRUCT";case 13:return"OPEN_IN_STRUCT";case 14:return this.popState(),35;case 15:break;case 16:return"MEMBER";case 17:return 31;case 18:return 57;case 19:return 50;case 20:return 51;case 21:return 53;case 22:return 36;case 23:return 37;case 24:this.begin("generic");break;case 25:this.popState();break;case 26:return"GENERICTYPE";case 27:this.begin("string");break;case 28:this.popState();break;case 29:return"STR";case 30:this.begin("href");break;case 31:this.popState();break;case 32:return 56;case 33:this.begin("callback_name");break;case 34:this.popState();break;case 35:this.popState(),this.begin("callback_args");break;case 36:return 54;case 37:this.popState();break;case 38:return 55;case 39:case 40:case 41:case 42:return 52;case 43:case 44:return 45;case 45:case 46:return 47;case 47:return 46;case 48:return 44;case 49:return 48;case 50:return 49;case 51:return 25;case 52:return 32;case 53:return 69;case 54:return"DOT";case 55:return"PLUS";case 56:return 66;case 57:case 58:return"EQUALS";case 59:return 73;case 60:return"PUNCTUATION";case 61:return 72;case 62:return 71;case 63:return 68;case 64:return 19}},rules:[/^(?:%%\{)/,/^(?:((?:(?!\}%%)[^:.])*))/,/^(?::)/,/^(?:\}%%)/,/^(?:((?:(?!\}%%).|\n)*))/,/^(?:%%(?!\{)*[^\n]*(\r?\n?)+)/,/^(?:%%[^\n]*(\r?\n)*)/,/^(?:(\r?\n)+)/,/^(?:\s+)/,/^(?:classDiagram-v2\b)/,/^(?:classDiagram\b)/,/^(?:[{])/,/^(?:$)/,/^(?:[{])/,/^(?:[}])/,/^(?:[\n])/,/^(?:[^{}\n]*)/,/^(?:class\b)/,/^(?:cssClass\b)/,/^(?:callback\b)/,/^(?:link\b)/,/^(?:click\b)/,/^(?:<<)/,/^(?:>>)/,/^(?:[~])/,/^(?:[~])/,/^(?:[^~]*)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:href[\s]+["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:\s*<\|)/,/^(?:\s*\|>)/,/^(?:\s*>)/,/^(?:\s*<)/,/^(?:\s*\*)/,/^(?:\s*o\b)/,/^(?:--)/,/^(?:\.\.)/,/^(?::{1}[^:\n;]+)/,/^(?::{3})/,/^(?:-)/,/^(?:\.)/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:\w+)/,/^(?:[!"#$%&'*+,-.`?\\/])/,/^(?:[0-9]+)/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\s)/,/^(?:$)/],conditions:{arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callback_args:{rules:[37,38],inclusive:!1},callback_name:{rules:[34,35,36],inclusive:!1},href:{rules:[31,32],inclusive:!1},struct:{rules:[12,13,14,15,16],inclusive:!1},generic:{rules:[25,26],inclusive:!1},string:{rules:[28,29],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,17,18,19,20,21,22,23,24,27,30,33,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64],inclusive:!0}}};function N(){this.yy={}}return O.lexer=D,N.prototype=O,O.Parser=N,new N}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e){var n,r,i=t.exports={};function a(){throw new Error("setTimeout has not been defined")}function o(){throw new Error("clearTimeout has not been defined")}function s(t){if(n===setTimeout)return setTimeout(t,0);if((n===a||!n)&&setTimeout)return n=setTimeout,setTimeout(t,0);try{return n(t,0)}catch(e){try{return n.call(null,t,0)}catch(e){return n.call(this,t,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:a}catch(t){n=a}try{r="function"==typeof clearTimeout?clearTimeout:o}catch(t){r=o}}();var c,u=[],l=!1,h=-1;function f(){l&&c&&(l=!1,c.length?u=c.concat(u):h=-1,u.length&&d())}function d(){if(!l){var t=s(f);l=!0;for(var e=u.length;e;){for(c=u,u=[];++h<e;)c&&c[h].run();h=-1,e=u.length}c=null,l=!1,function(t){if(r===clearTimeout)return clearTimeout(t);if((r===o||!r)&&clearTimeout)return r=clearTimeout,clearTimeout(t);try{r(t)}catch(e){try{return r.call(null,t)}catch(e){return r.call(this,t)}}}(t)}}function p(t,e){this.fun=t,this.array=e}function g(){}i.nextTick=function(t){var e=new Array(arguments.length-1);if(arguments.length>1)for(var n=1;n<arguments.length;n++)e[n-1]=arguments[n];u.push(new p(t,e)),1!==u.length||l||s(d)},p.prototype.run=function(){this.fun.apply(null,this.array)},i.title="browser",i.browser=!0,i.env={},i.argv=[],i.version="",i.versions={},i.on=g,i.addListener=g,i.once=g,i.off=g,i.removeListener=g,i.removeAllListeners=g,i.emit=g,i.prependListener=g,i.prependOnceListener=g,i.listeners=function(t){return[]},i.binding=function(t){throw new Error("process.binding is not supported")},i.cwd=function(){return"/"},i.chdir=function(t){throw new Error("process.chdir is not supported")},i.umask=function(){return 0}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(75),i=n(99),a=n(179),o=n(180),s=n(181),c={format:{keyword:a.default,hex:i.default,rgb:o.default,rgba:o.default,hsl:s.default,hsla:s.default},parse:function(t){if("string"!=typeof t)return t;var e=i.default.parse(t)||o.default.parse(t)||s.default.parse(t)||a.default.parse(t);if(e)return e;throw new Error('Unsupported color format: "'+t+'"')},stringify:function(t){return!t.changed&&t.color?t.color:t.type.is(r.TYPE.HSL)||void 0===t.data.r?s.default.stringify(t):t.a<1||!Number.isInteger(t.r)||!Number.isInteger(t.g)||!Number.isInteger(t.b)?o.default.stringify(t):i.default.stringify(t)}};e.default=c},function(t,e,n){var r=n(109),i="object"==typeof self&&self&&self.Object===Object&&self,a=r||i||Function("return this")();t.exports=a},function(t,e,n){var r;try{r=n(3)}catch(t){}r||(r=window.graphlib),t.exports=r},function(t,e,n){t.exports={graphlib:n(17),layout:n(312),debug:n(366),util:{time:n(8).time,notime:n(8).notime},version:n(367)}},function(t,e){},function(t,e,n){(function(t){function n(t,e){for(var n=0,r=t.length-1;r>=0;r--){var i=t[r];"."===i?t.splice(r,1):".."===i?(t.splice(r,1),n++):n&&(t.splice(r,1),n--)}if(e)for(;n--;n)t.unshift("..");return t}function r(t,e){if(t.filter)return t.filter(e);for(var n=[],r=0;r<t.length;r++)e(t[r],r,t)&&n.push(t[r]);return n}e.resolve=function(){for(var e="",i=!1,a=arguments.length-1;a>=-1&&!i;a--){var o=a>=0?arguments[a]:t.cwd();if("string"!=typeof o)throw new TypeError("Arguments to path.resolve must be strings");o&&(e=o+"/"+e,i="/"===o.charAt(0))}return(i?"/":"")+(e=n(r(e.split("/"),(function(t){return!!t})),!i).join("/"))||"."},e.normalize=function(t){var a=e.isAbsolute(t),o="/"===i(t,-1);return(t=n(r(t.split("/"),(function(t){return!!t})),!a).join("/"))||a||(t="."),t&&o&&(t+="/"),(a?"/":"")+t},e.isAbsolute=function(t){return"/"===t.charAt(0)},e.join=function(){var t=Array.prototype.slice.call(arguments,0);return e.normalize(r(t,(function(t,e){if("string"!=typeof t)throw new TypeError("Arguments to path.join must be strings");return t})).join("/"))},e.relative=function(t,n){function r(t){for(var e=0;e<t.length&&""===t[e];e++);for(var n=t.length-1;n>=0&&""===t[n];n--);return e>n?[]:t.slice(e,n-e+1)}t=e.resolve(t).substr(1),n=e.resolve(n).substr(1);for(var i=r(t.split("/")),a=r(n.split("/")),o=Math.min(i.length,a.length),s=o,c=0;c<o;c++)if(i[c]!==a[c]){s=c;break}var u=[];for(c=s;c<i.length;c++)u.push("..");return(u=u.concat(a.slice(s))).join("/")},e.sep="/",e.delimiter=":",e.dirname=function(t){if("string"!=typeof t&&(t+=""),0===t.length)return".";for(var e=t.charCodeAt(0),n=47===e,r=-1,i=!0,a=t.length-1;a>=1;--a)if(47===(e=t.charCodeAt(a))){if(!i){r=a;break}}else i=!1;return-1===r?n?"/":".":n&&1===r?"/":t.slice(0,r)},e.basename=function(t,e){var n=function(t){"string"!=typeof t&&(t+="");var e,n=0,r=-1,i=!0;for(e=t.length-1;e>=0;--e)if(47===t.charCodeAt(e)){if(!i){n=e+1;break}}else-1===r&&(i=!1,r=e+1);return-1===r?"":t.slice(n,r)}(t);return e&&n.substr(-1*e.length)===e&&(n=n.substr(0,n.length-e.length)),n},e.extname=function(t){"string"!=typeof t&&(t+="");for(var e=-1,n=0,r=-1,i=!0,a=0,o=t.length-1;o>=0;--o){var s=t.charCodeAt(o);if(47!==s)-1===r&&(i=!1,r=o+1),46===s?-1===e?e=o:1!==a&&(a=1):-1!==e&&(a=-1);else if(!i){n=o+1;break}}return-1===e||-1===r||0===a||1===a&&e===r-1&&e===n+1?"":t.slice(e,r)};var i="b"==="ab".substr(-1)?function(t,e,n){return t.substr(e,n)}:function(t,e,n){return e<0&&(e=t.length+e),t.substr(e,n)}}).call(this,n(14))},function(t,e){t.exports=function(t){return null!=t&&"object"==typeof t}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,3],r=[1,5],i=[1,7],a=[2,5],o=[1,15],s=[1,17],c=[1,19],u=[1,20],l=[1,21],h=[1,22],f=[1,28],d=[1,23],p=[1,24],g=[1,25],y=[1,26],v=[1,29],m=[1,32],b=[1,4,5,14,15,17,19,20,22,23,24,25,26,36,39],x=[1,4,5,12,13,14,15,17,19,20,22,23,24,25,26,36,39],_=[1,4,5,7,14,15,17,19,20,22,23,24,25,26,36,39],k=[4,5,14,15,17,19,20,22,23,24,25,26,36,39],w={trace:function(){},yy:{},symbols_:{error:2,start:3,SPACE:4,NL:5,directive:6,SD:7,document:8,line:9,statement:10,idStatement:11,DESCR:12,"--\x3e":13,HIDE_EMPTY:14,scale:15,WIDTH:16,COMPOSIT_STATE:17,STRUCT_START:18,STRUCT_STOP:19,STATE_DESCR:20,AS:21,ID:22,FORK:23,JOIN:24,CONCURRENT:25,note:26,notePosition:27,NOTE_TEXT:28,openDirective:29,typeDirective:30,closeDirective:31,":":32,argDirective:33,eol:34,";":35,EDGE_STATE:36,left_of:37,right_of:38,open_directive:39,type_directive:40,arg_directive:41,close_directive:42,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NL",7:"SD",12:"DESCR",13:"--\x3e",14:"HIDE_EMPTY",15:"scale",16:"WIDTH",17:"COMPOSIT_STATE",18:"STRUCT_START",19:"STRUCT_STOP",20:"STATE_DESCR",21:"AS",22:"ID",23:"FORK",24:"JOIN",25:"CONCURRENT",26:"note",28:"NOTE_TEXT",32:":",35:";",36:"EDGE_STATE",37:"left_of",38:"right_of",39:"open_directive",40:"type_directive",41:"arg_directive",42:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[3,2],[8,0],[8,2],[9,2],[9,1],[9,1],[10,1],[10,2],[10,3],[10,4],[10,1],[10,2],[10,1],[10,4],[10,3],[10,6],[10,1],[10,1],[10,1],[10,4],[10,4],[10,1],[6,3],[6,5],[34,1],[34,1],[11,1],[11,1],[27,1],[27,1],[29,1],[30,1],[33,1],[31,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:return r.setRootDoc(a[s]),a[s];case 5:this.$=[];break;case 6:"nl"!=a[s]&&(a[s-1].push(a[s]),this.$=a[s-1]);break;case 7:case 8:this.$=a[s];break;case 9:this.$="nl";break;case 10:this.$={stmt:"state",id:a[s],type:"default",description:""};break;case 11:this.$={stmt:"state",id:a[s-1],type:"default",description:r.trimColon(a[s])};break;case 12:this.$={stmt:"relation",state1:{stmt:"state",id:a[s-2],type:"default",description:""},state2:{stmt:"state",id:a[s],type:"default",description:""}};break;case 13:this.$={stmt:"relation",state1:{stmt:"state",id:a[s-3],type:"default",description:""},state2:{stmt:"state",id:a[s-1],type:"default",description:""},description:a[s].substr(1).trim()};break;case 17:this.$={stmt:"state",id:a[s-3],type:"default",description:"",doc:a[s-1]};break;case 18:var c=a[s],u=a[s-2].trim();if(a[s].match(":")){var l=a[s].split(":");c=l[0],u=[u,l[1]]}this.$={stmt:"state",id:c,type:"default",description:u};break;case 19:this.$={stmt:"state",id:a[s-3],type:"default",description:a[s-5],doc:a[s-1]};break;case 20:this.$={stmt:"state",id:a[s],type:"fork"};break;case 21:this.$={stmt:"state",id:a[s],type:"join"};break;case 22:this.$={stmt:"state",id:r.getDividerId(),type:"divider"};break;case 23:this.$={stmt:"state",id:a[s-1].trim(),note:{position:a[s-2].trim(),text:a[s].trim()}};break;case 30:case 31:this.$=a[s];break;case 34:r.parseDirective("%%{","open_directive");break;case 35:r.parseDirective(a[s],"type_directive");break;case 36:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 37:r.parseDirective("}%%","close_directive","state")}},table:[{3:1,4:e,5:n,6:4,7:r,29:6,39:i},{1:[3]},{3:8,4:e,5:n,6:4,7:r,29:6,39:i},{3:9,4:e,5:n,6:4,7:r,29:6,39:i},{3:10,4:e,5:n,6:4,7:r,29:6,39:i},t([1,4,5,14,15,17,20,22,23,24,25,26,36,39],a,{8:11}),{30:12,40:[1,13]},{40:[2,34]},{1:[2,1]},{1:[2,2]},{1:[2,3]},{1:[2,4],4:o,5:s,6:27,9:14,10:16,11:18,14:c,15:u,17:l,20:h,22:f,23:d,24:p,25:g,26:y,29:6,36:v,39:i},{31:30,32:[1,31],42:m},t([32,42],[2,35]),t(b,[2,6]),{6:27,10:33,11:18,14:c,15:u,17:l,20:h,22:f,23:d,24:p,25:g,26:y,29:6,36:v,39:i},t(b,[2,8]),t(b,[2,9]),t(b,[2,10],{12:[1,34],13:[1,35]}),t(b,[2,14]),{16:[1,36]},t(b,[2,16],{18:[1,37]}),{21:[1,38]},t(b,[2,20]),t(b,[2,21]),t(b,[2,22]),{27:39,28:[1,40],37:[1,41],38:[1,42]},t(b,[2,25]),t(x,[2,30]),t(x,[2,31]),t(_,[2,26]),{33:43,41:[1,44]},t(_,[2,37]),t(b,[2,7]),t(b,[2,11]),{11:45,22:f,36:v},t(b,[2,15]),t(k,a,{8:46}),{22:[1,47]},{22:[1,48]},{21:[1,49]},{22:[2,32]},{22:[2,33]},{31:50,42:m},{42:[2,36]},t(b,[2,12],{12:[1,51]}),{4:o,5:s,6:27,9:14,10:16,11:18,14:c,15:u,17:l,19:[1,52],20:h,22:f,23:d,24:p,25:g,26:y,29:6,36:v,39:i},t(b,[2,18],{18:[1,53]}),{28:[1,54]},{22:[1,55]},t(_,[2,27]),t(b,[2,13]),t(b,[2,17]),t(k,a,{8:56}),t(b,[2,23]),t(b,[2,24]),{4:o,5:s,6:27,9:14,10:16,11:18,14:c,15:u,17:l,19:[1,57],20:h,22:f,23:d,24:p,25:g,26:y,29:6,36:v,39:i},t(b,[2,19])],defaultActions:{7:[2,34],8:[2,1],9:[2,2],10:[2,3],41:[2,32],42:[2,33],44:[2,36]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},E={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;a<i.length;a++)if((n=this._input.match(this.rules[i[a]]))&&(!e||n[0].length>e[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),39;case 1:return this.begin("type_directive"),40;case 2:return this.popState(),this.begin("arg_directive"),32;case 3:return this.popState(),this.popState(),42;case 4:return 41;case 5:break;case 6:console.log("Crap after close");break;case 7:return 5;case 8:case 9:case 10:case 11:break;case 12:return this.pushState("SCALE"),15;case 13:return 16;case 14:this.popState();break;case 15:this.pushState("STATE");break;case 16:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),23;case 17:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),24;case 18:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),23;case 19:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),24;case 20:this.begin("STATE_STRING");break;case 21:return this.popState(),this.pushState("STATE_ID"),"AS";case 22:return this.popState(),"ID";case 23:this.popState();break;case 24:return"STATE_DESCR";case 25:return 17;case 26:this.popState();break;case 27:return this.popState(),this.pushState("struct"),18;case 28:return this.popState(),19;case 29:break;case 30:return this.begin("NOTE"),26;case 31:return this.popState(),this.pushState("NOTE_ID"),37;case 32:return this.popState(),this.pushState("NOTE_ID"),38;case 33:this.popState(),this.pushState("FLOATING_NOTE");break;case 34:return this.popState(),this.pushState("FLOATING_NOTE_ID"),"AS";case 35:break;case 36:return"NOTE_TEXT";case 37:return this.popState(),"ID";case 38:return this.popState(),this.pushState("NOTE_TEXT"),22;case 39:return this.popState(),e.yytext=e.yytext.substr(2).trim(),28;case 40:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),28;case 41:case 42:return 7;case 43:return 14;case 44:return 36;case 45:return 22;case 46:return e.yytext=e.yytext.trim(),12;case 47:return 13;case 48:return 25;case 49:return 5;case 50:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:[\s]+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:scale\s+)/i,/^(?:\d+)/i,/^(?:\s+width\b)/i,/^(?:state\s+)/i,/^(?:.*<<fork>>)/i,/^(?:.*<<join>>)/i,/^(?:.*\[\[fork\]\])/i,/^(?:.*\[\[join\]\])/i,/^(?:["])/i,/^(?:\s*as\s+)/i,/^(?:[^\n\{]*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n\s\{]+)/i,/^(?:\n)/i,/^(?:\{)/i,/^(?:\})/i,/^(?:[\n])/i,/^(?:note\s+)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:")/i,/^(?:\s*as\s*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n]*)/i,/^(?:\s*[^:\n\s\-]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:[\s\S]*?end note\b)/i,/^(?:stateDiagram\s+)/i,/^(?:stateDiagram-v2\s+)/i,/^(?:hide empty description\b)/i,/^(?:\[\*\])/i,/^(?:[^:\n\s\-\{]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:-->)/i,/^(?:--)/i,/^(?:$)/i,/^(?:.)/i],conditions:{LINE:{rules:[9,10],inclusive:!1},close_directive:{rules:[9,10],inclusive:!1},arg_directive:{rules:[3,4,9,10],inclusive:!1},type_directive:{rules:[2,3,9,10],inclusive:!1},open_directive:{rules:[1,9,10],inclusive:!1},struct:{rules:[9,10,15,28,29,30,44,45,46,47,48],inclusive:!1},FLOATING_NOTE_ID:{rules:[37],inclusive:!1},FLOATING_NOTE:{rules:[34,35,36],inclusive:!1},NOTE_TEXT:{rules:[39,40],inclusive:!1},NOTE_ID:{rules:[38],inclusive:!1},NOTE:{rules:[31,32,33],inclusive:!1},SCALE:{rules:[13,14],inclusive:!1},ALIAS:{rules:[],inclusive:!1},STATE_ID:{rules:[22],inclusive:!1},STATE_STRING:{rules:[23,24],inclusive:!1},FORK_STATE:{rules:[],inclusive:!1},STATE:{rules:[9,10,16,17,18,19,20,21,25,26,27],inclusive:!1},ID:{rules:[9,10],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,10,11,12,15,27,30,41,42,43,44,45,46,47,49,50],inclusive:!0}}};function T(){this.yy={}}return w.lexer=E,T.prototype=w,w.Parser=T,new T}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t){t.exports=function(){"use strict";var e,r;function i(){return e.apply(null,arguments)}function a(t){return t instanceof Array||"[object Array]"===Object.prototype.toString.call(t)}function o(t){return null!=t&&"[object Object]"===Object.prototype.toString.call(t)}function s(t){return void 0===t}function c(t){return"number"==typeof t||"[object Number]"===Object.prototype.toString.call(t)}function u(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function l(t,e){var n,r=[];for(n=0;n<t.length;++n)r.push(e(t[n],n));return r}function h(t,e){return Object.prototype.hasOwnProperty.call(t,e)}function f(t,e){for(var n in e)h(e,n)&&(t[n]=e[n]);return h(e,"toString")&&(t.toString=e.toString),h(e,"valueOf")&&(t.valueOf=e.valueOf),t}function d(t,e,n,r){return be(t,e,n,r,!0).utc()}function p(t){return null==t._pf&&(t._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],meridiem:null,rfc2822:!1,weekdayMismatch:!1}),t._pf}function g(t){if(null==t._isValid){var e=p(t),n=r.call(e.parsedDateParts,(function(t){return null!=t})),i=!isNaN(t._d.getTime())&&e.overflow<0&&!e.empty&&!e.invalidMonth&&!e.invalidWeekday&&!e.weekdayMismatch&&!e.nullInput&&!e.invalidFormat&&!e.userInvalidated&&(!e.meridiem||e.meridiem&&n);if(t._strict&&(i=i&&0===e.charsLeftOver&&0===e.unusedTokens.length&&void 0===e.bigHour),null!=Object.isFrozen&&Object.isFrozen(t))return i;t._isValid=i}return t._isValid}function y(t){var e=d(NaN);return null!=t?f(p(e),t):p(e).userInvalidated=!0,e}r=Array.prototype.some?Array.prototype.some:function(t){for(var e=Object(this),n=e.length>>>0,r=0;r<n;r++)if(r in e&&t.call(this,e[r],r,e))return!0;return!1};var v=i.momentProperties=[];function m(t,e){var n,r,i;if(s(e._isAMomentObject)||(t._isAMomentObject=e._isAMomentObject),s(e._i)||(t._i=e._i),s(e._f)||(t._f=e._f),s(e._l)||(t._l=e._l),s(e._strict)||(t._strict=e._strict),s(e._tzm)||(t._tzm=e._tzm),s(e._isUTC)||(t._isUTC=e._isUTC),s(e._offset)||(t._offset=e._offset),s(e._pf)||(t._pf=p(e)),s(e._locale)||(t._locale=e._locale),0<v.length)for(n=0;n<v.length;n++)s(i=e[r=v[n]])||(t[r]=i);return t}var b=!1;function x(t){m(this,t),this._d=new Date(null!=t._d?t._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===b&&(b=!0,i.updateOffset(this),b=!1)}function _(t){return t instanceof x||null!=t&&null!=t._isAMomentObject}function k(t){return t<0?Math.ceil(t)||0:Math.floor(t)}function w(t){var e=+t,n=0;return 0!==e&&isFinite(e)&&(n=k(e)),n}function E(t,e,n){var r,i=Math.min(t.length,e.length),a=Math.abs(t.length-e.length),o=0;for(r=0;r<i;r++)(n&&t[r]!==e[r]||!n&&w(t[r])!==w(e[r]))&&o++;return o+a}function T(t){!1===i.suppressDeprecationWarnings&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function C(t,e){var n=!0;return f((function(){if(null!=i.deprecationHandler&&i.deprecationHandler(null,t),n){for(var r,a=[],o=0;o<arguments.length;o++){if(r="","object"==typeof arguments[o]){for(var s in r+="\n["+o+"] ",arguments[0])r+=s+": "+arguments[0][s]+", ";r=r.slice(0,-2)}else r=arguments[o];a.push(r)}T(t+"\nArguments: "+Array.prototype.slice.call(a).join("")+"\n"+(new Error).stack),n=!1}return e.apply(this,arguments)}),e)}var A,S={};function M(t,e){null!=i.deprecationHandler&&i.deprecationHandler(t,e),S[t]||(T(e),S[t]=!0)}function O(t){return t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)}function D(t,e){var n,r=f({},t);for(n in e)h(e,n)&&(o(t[n])&&o(e[n])?(r[n]={},f(r[n],t[n]),f(r[n],e[n])):null!=e[n]?r[n]=e[n]:delete r[n]);for(n in t)h(t,n)&&!h(e,n)&&o(t[n])&&(r[n]=f({},r[n]));return r}function N(t){null!=t&&this.set(t)}i.suppressDeprecationWarnings=!1,i.deprecationHandler=null,A=Object.keys?Object.keys:function(t){var e,n=[];for(e in t)h(t,e)&&n.push(e);return n};var B={};function L(t,e){var n=t.toLowerCase();B[n]=B[n+"s"]=B[e]=t}function F(t){return"string"==typeof t?B[t]||B[t.toLowerCase()]:void 0}function P(t){var e,n,r={};for(n in t)h(t,n)&&(e=F(n))&&(r[e]=t[n]);return r}var I={};function j(t,e){I[t]=e}function R(t,e,n){var r=""+Math.abs(t),i=e-r.length;return(0<=t?n?"+":"":"-")+Math.pow(10,Math.max(0,i)).toString().substr(1)+r}var Y=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,z=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,U={},$={};function W(t,e,n,r){var i=r;"string"==typeof r&&(i=function(){return this[r]()}),t&&($[t]=i),e&&($[e[0]]=function(){return R(i.apply(this,arguments),e[1],e[2])}),n&&($[n]=function(){return this.localeData().ordinal(i.apply(this,arguments),t)})}function H(t,e){return t.isValid()?(e=V(e,t.localeData()),U[e]=U[e]||function(t){var e,n,r,i=t.match(Y);for(e=0,n=i.length;e<n;e++)$[i[e]]?i[e]=$[i[e]]:i[e]=(r=i[e]).match(/\[[\s\S]/)?r.replace(/^\[|\]$/g,""):r.replace(/\\/g,"");return function(e){var r,a="";for(r=0;r<n;r++)a+=O(i[r])?i[r].call(e,t):i[r];return a}}(e),U[e](t)):t.localeData().invalidDate()}function V(t,e){var n=5;function r(t){return e.longDateFormat(t)||t}for(z.lastIndex=0;0<=n&&z.test(t);)t=t.replace(z,r),z.lastIndex=0,n-=1;return t}var G=/\d/,q=/\d\d/,X=/\d{3}/,Z=/\d{4}/,J=/[+-]?\d{6}/,K=/\d\d?/,Q=/\d\d\d\d?/,tt=/\d\d\d\d\d\d?/,et=/\d{1,3}/,nt=/\d{1,4}/,rt=/[+-]?\d{1,6}/,it=/\d+/,at=/[+-]?\d+/,ot=/Z|[+-]\d\d:?\d\d/gi,st=/Z|[+-]\d\d(?::?\d\d)?/gi,ct=/[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i,ut={};function lt(t,e,n){ut[t]=O(e)?e:function(t,r){return t&&n?n:e}}function ht(t,e){return h(ut,t)?ut[t](e._strict,e._locale):new RegExp(ft(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,(function(t,e,n,r,i){return e||n||r||i}))))}function ft(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}var dt={};function pt(t,e){var n,r=e;for("string"==typeof t&&(t=[t]),c(e)&&(r=function(t,n){n[e]=w(t)}),n=0;n<t.length;n++)dt[t[n]]=r}function gt(t,e){pt(t,(function(t,n,r,i){r._w=r._w||{},e(t,r._w,r,i)}))}function yt(t){return vt(t)?366:365}function vt(t){return t%4==0&&t%100!=0||t%400==0}W("Y",0,0,(function(){var t=this.year();return t<=9999?""+t:"+"+t})),W(0,["YY",2],0,(function(){return this.year()%100})),W(0,["YYYY",4],0,"year"),W(0,["YYYYY",5],0,"year"),W(0,["YYYYYY",6,!0],0,"year"),L("year","y"),j("year",1),lt("Y",at),lt("YY",K,q),lt("YYYY",nt,Z),lt("YYYYY",rt,J),lt("YYYYYY",rt,J),pt(["YYYYY","YYYYYY"],0),pt("YYYY",(function(t,e){e[0]=2===t.length?i.parseTwoDigitYear(t):w(t)})),pt("YY",(function(t,e){e[0]=i.parseTwoDigitYear(t)})),pt("Y",(function(t,e){e[0]=parseInt(t,10)})),i.parseTwoDigitYear=function(t){return w(t)+(68<w(t)?1900:2e3)};var mt,bt=xt("FullYear",!0);function xt(t,e){return function(n){return null!=n?(kt(this,t,n),i.updateOffset(this,e),this):_t(this,t)}}function _t(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function kt(t,e,n){t.isValid()&&!isNaN(n)&&("FullYear"===e&&vt(t.year())&&1===t.month()&&29===t.date()?t._d["set"+(t._isUTC?"UTC":"")+e](n,t.month(),wt(n,t.month())):t._d["set"+(t._isUTC?"UTC":"")+e](n))}function wt(t,e){if(isNaN(t)||isNaN(e))return NaN;var n=(e%12+12)%12;return t+=(e-n)/12,1===n?vt(t)?29:28:31-n%7%2}mt=Array.prototype.indexOf?Array.prototype.indexOf:function(t){var e;for(e=0;e<this.length;++e)if(this[e]===t)return e;return-1},W("M",["MM",2],"Mo",(function(){return this.month()+1})),W("MMM",0,0,(function(t){return this.localeData().monthsShort(this,t)})),W("MMMM",0,0,(function(t){return this.localeData().months(this,t)})),L("month","M"),j("month",8),lt("M",K),lt("MM",K,q),lt("MMM",(function(t,e){return e.monthsShortRegex(t)})),lt("MMMM",(function(t,e){return e.monthsRegex(t)})),pt(["M","MM"],(function(t,e){e[1]=w(t)-1})),pt(["MMM","MMMM"],(function(t,e,n,r){var i=n._locale.monthsParse(t,r,n._strict);null!=i?e[1]=i:p(n).invalidMonth=t}));var Et=/D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/,Tt="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Ct="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_");function At(t,e){var n;if(!t.isValid())return t;if("string"==typeof e)if(/^\d+$/.test(e))e=w(e);else if(!c(e=t.localeData().monthsParse(e)))return t;return n=Math.min(t.date(),wt(t.year(),e)),t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,n),t}function St(t){return null!=t?(At(this,t),i.updateOffset(this,!0),this):_t(this,"Month")}var Mt=ct,Ot=ct;function Dt(){function t(t,e){return e.length-t.length}var e,n,r=[],i=[],a=[];for(e=0;e<12;e++)n=d([2e3,e]),r.push(this.monthsShort(n,"")),i.push(this.months(n,"")),a.push(this.months(n,"")),a.push(this.monthsShort(n,""));for(r.sort(t),i.sort(t),a.sort(t),e=0;e<12;e++)r[e]=ft(r[e]),i[e]=ft(i[e]);for(e=0;e<24;e++)a[e]=ft(a[e]);this._monthsRegex=new RegExp("^("+a.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+i.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+r.join("|")+")","i")}function Nt(t){var e;if(t<100&&0<=t){var n=Array.prototype.slice.call(arguments);n[0]=t+400,e=new Date(Date.UTC.apply(null,n)),isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t)}else e=new Date(Date.UTC.apply(null,arguments));return e}function Bt(t,e,n){var r=7+e-n;return-(7+Nt(t,0,r).getUTCDay()-e)%7+r-1}function Lt(t,e,n,r,i){var a,o,s=1+7*(e-1)+(7+n-r)%7+Bt(t,r,i);return o=s<=0?yt(a=t-1)+s:s>yt(t)?(a=t+1,s-yt(t)):(a=t,s),{year:a,dayOfYear:o}}function Ft(t,e,n){var r,i,a=Bt(t.year(),e,n),o=Math.floor((t.dayOfYear()-a-1)/7)+1;return o<1?r=o+Pt(i=t.year()-1,e,n):o>Pt(t.year(),e,n)?(r=o-Pt(t.year(),e,n),i=t.year()+1):(i=t.year(),r=o),{week:r,year:i}}function Pt(t,e,n){var r=Bt(t,e,n),i=Bt(t+1,e,n);return(yt(t)-r+i)/7}function It(t,e){return t.slice(e,7).concat(t.slice(0,e))}W("w",["ww",2],"wo","week"),W("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),j("week",5),j("isoWeek",5),lt("w",K),lt("ww",K,q),lt("W",K),lt("WW",K,q),gt(["w","ww","W","WW"],(function(t,e,n,r){e[r.substr(0,1)]=w(t)})),W("d",0,"do","day"),W("dd",0,0,(function(t){return this.localeData().weekdaysMin(this,t)})),W("ddd",0,0,(function(t){return this.localeData().weekdaysShort(this,t)})),W("dddd",0,0,(function(t){return this.localeData().weekdays(this,t)})),W("e",0,0,"weekday"),W("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),j("day",11),j("weekday",11),j("isoWeekday",11),lt("d",K),lt("e",K),lt("E",K),lt("dd",(function(t,e){return e.weekdaysMinRegex(t)})),lt("ddd",(function(t,e){return e.weekdaysShortRegex(t)})),lt("dddd",(function(t,e){return e.weekdaysRegex(t)})),gt(["dd","ddd","dddd"],(function(t,e,n,r){var i=n._locale.weekdaysParse(t,r,n._strict);null!=i?e.d=i:p(n).invalidWeekday=t})),gt(["d","e","E"],(function(t,e,n,r){e[r]=w(t)}));var jt="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Rt="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Yt="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),zt=ct,Ut=ct,$t=ct;function Wt(){function t(t,e){return e.length-t.length}var e,n,r,i,a,o=[],s=[],c=[],u=[];for(e=0;e<7;e++)n=d([2e3,1]).day(e),r=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),a=this.weekdays(n,""),o.push(r),s.push(i),c.push(a),u.push(r),u.push(i),u.push(a);for(o.sort(t),s.sort(t),c.sort(t),u.sort(t),e=0;e<7;e++)s[e]=ft(s[e]),c[e]=ft(c[e]),u[e]=ft(u[e]);this._weekdaysRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+c.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+o.join("|")+")","i")}function Ht(){return this.hours()%12||12}function Vt(t,e){W(t,0,0,(function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)}))}function Gt(t,e){return e._meridiemParse}W("H",["HH",2],0,"hour"),W("h",["hh",2],0,Ht),W("k",["kk",2],0,(function(){return this.hours()||24})),W("hmm",0,0,(function(){return""+Ht.apply(this)+R(this.minutes(),2)})),W("hmmss",0,0,(function(){return""+Ht.apply(this)+R(this.minutes(),2)+R(this.seconds(),2)})),W("Hmm",0,0,(function(){return""+this.hours()+R(this.minutes(),2)})),W("Hmmss",0,0,(function(){return""+this.hours()+R(this.minutes(),2)+R(this.seconds(),2)})),Vt("a",!0),Vt("A",!1),L("hour","h"),j("hour",13),lt("a",Gt),lt("A",Gt),lt("H",K),lt("h",K),lt("k",K),lt("HH",K,q),lt("hh",K,q),lt("kk",K,q),lt("hmm",Q),lt("hmmss",tt),lt("Hmm",Q),lt("Hmmss",tt),pt(["H","HH"],3),pt(["k","kk"],(function(t,e,n){var r=w(t);e[3]=24===r?0:r})),pt(["a","A"],(function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t})),pt(["h","hh"],(function(t,e,n){e[3]=w(t),p(n).bigHour=!0})),pt("hmm",(function(t,e,n){var r=t.length-2;e[3]=w(t.substr(0,r)),e[4]=w(t.substr(r)),p(n).bigHour=!0})),pt("hmmss",(function(t,e,n){var r=t.length-4,i=t.length-2;e[3]=w(t.substr(0,r)),e[4]=w(t.substr(r,2)),e[5]=w(t.substr(i)),p(n).bigHour=!0})),pt("Hmm",(function(t,e,n){var r=t.length-2;e[3]=w(t.substr(0,r)),e[4]=w(t.substr(r))})),pt("Hmmss",(function(t,e,n){var r=t.length-4,i=t.length-2;e[3]=w(t.substr(0,r)),e[4]=w(t.substr(r,2)),e[5]=w(t.substr(i))}));var qt,Xt=xt("Hours",!0),Zt={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Tt,monthsShort:Ct,week:{dow:0,doy:6},weekdays:jt,weekdaysMin:Yt,weekdaysShort:Rt,meridiemParse:/[ap]\.?m?\.?/i},Jt={},Kt={};function Qt(t){return t?t.toLowerCase().replace("_","-"):t}function te(e){var r=null;if(!Jt[e]&&void 0!==t&&t&&t.exports)try{r=qt._abbr,n(171)("./"+e),ee(r)}catch(e){}return Jt[e]}function ee(t,e){var n;return t&&((n=s(e)?re(t):ne(t,e))?qt=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+t+" not found. Did you forget to load it?")),qt._abbr}function ne(t,e){if(null===e)return delete Jt[t],null;var n,r=Zt;if(e.abbr=t,null!=Jt[t])M("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),r=Jt[t]._config;else if(null!=e.parentLocale)if(null!=Jt[e.parentLocale])r=Jt[e.parentLocale]._config;else{if(null==(n=te(e.parentLocale)))return Kt[e.parentLocale]||(Kt[e.parentLocale]=[]),Kt[e.parentLocale].push({name:t,config:e}),null;r=n._config}return Jt[t]=new N(D(r,e)),Kt[t]&&Kt[t].forEach((function(t){ne(t.name,t.config)})),ee(t),Jt[t]}function re(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return qt;if(!a(t)){if(e=te(t))return e;t=[t]}return function(t){for(var e,n,r,i,a=0;a<t.length;){for(e=(i=Qt(t[a]).split("-")).length,n=(n=Qt(t[a+1]))?n.split("-"):null;0<e;){if(r=te(i.slice(0,e).join("-")))return r;if(n&&n.length>=e&&E(i,n,!0)>=e-1)break;e--}a++}return qt}(t)}function ie(t){var e,n=t._a;return n&&-2===p(t).overflow&&(e=n[1]<0||11<n[1]?1:n[2]<1||n[2]>wt(n[0],n[1])?2:n[3]<0||24<n[3]||24===n[3]&&(0!==n[4]||0!==n[5]||0!==n[6])?3:n[4]<0||59<n[4]?4:n[5]<0||59<n[5]?5:n[6]<0||999<n[6]?6:-1,p(t)._overflowDayOfYear&&(e<0||2<e)&&(e=2),p(t)._overflowWeeks&&-1===e&&(e=7),p(t)._overflowWeekday&&-1===e&&(e=8),p(t).overflow=e),t}function ae(t,e,n){return null!=t?t:null!=e?e:n}function oe(t){var e,n,r,a,o,s=[];if(!t._d){var c,u;for(c=t,u=new Date(i.now()),r=c._useUTC?[u.getUTCFullYear(),u.getUTCMonth(),u.getUTCDate()]:[u.getFullYear(),u.getMonth(),u.getDate()],t._w&&null==t._a[2]&&null==t._a[1]&&function(t){var e,n,r,i,a,o,s,c;if(null!=(e=t._w).GG||null!=e.W||null!=e.E)a=1,o=4,n=ae(e.GG,t._a[0],Ft(xe(),1,4).year),r=ae(e.W,1),((i=ae(e.E,1))<1||7<i)&&(c=!0);else{a=t._locale._week.dow,o=t._locale._week.doy;var u=Ft(xe(),a,o);n=ae(e.gg,t._a[0],u.year),r=ae(e.w,u.week),null!=e.d?((i=e.d)<0||6<i)&&(c=!0):null!=e.e?(i=e.e+a,(e.e<0||6<e.e)&&(c=!0)):i=a}r<1||r>Pt(n,a,o)?p(t)._overflowWeeks=!0:null!=c?p(t)._overflowWeekday=!0:(s=Lt(n,r,i,a,o),t._a[0]=s.year,t._dayOfYear=s.dayOfYear)}(t),null!=t._dayOfYear&&(o=ae(t._a[0],r[0]),(t._dayOfYear>yt(o)||0===t._dayOfYear)&&(p(t)._overflowDayOfYear=!0),n=Nt(o,0,t._dayOfYear),t._a[1]=n.getUTCMonth(),t._a[2]=n.getUTCDate()),e=0;e<3&&null==t._a[e];++e)t._a[e]=s[e]=r[e];for(;e<7;e++)t._a[e]=s[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[3]&&0===t._a[4]&&0===t._a[5]&&0===t._a[6]&&(t._nextDay=!0,t._a[3]=0),t._d=(t._useUTC?Nt:function(t,e,n,r,i,a,o){var s;return t<100&&0<=t?(s=new Date(t+400,e,n,r,i,a,o),isFinite(s.getFullYear())&&s.setFullYear(t)):s=new Date(t,e,n,r,i,a,o),s}).apply(null,s),a=t._useUTC?t._d.getUTCDay():t._d.getDay(),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[3]=24),t._w&&void 0!==t._w.d&&t._w.d!==a&&(p(t).weekdayMismatch=!0)}}var se=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ce=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ue=/Z|[+-]\d\d(?::?\d\d)?/,le=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],he=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],fe=/^\/?Date\((\-?\d+)/i;function de(t){var e,n,r,i,a,o,s=t._i,c=se.exec(s)||ce.exec(s);if(c){for(p(t).iso=!0,e=0,n=le.length;e<n;e++)if(le[e][1].exec(c[1])){i=le[e][0],r=!1!==le[e][2];break}if(null==i)return void(t._isValid=!1);if(c[3]){for(e=0,n=he.length;e<n;e++)if(he[e][1].exec(c[3])){a=(c[2]||" ")+he[e][0];break}if(null==a)return void(t._isValid=!1)}if(!r&&null!=a)return void(t._isValid=!1);if(c[4]){if(!ue.exec(c[4]))return void(t._isValid=!1);o="Z"}t._f=i+(a||"")+(o||""),ve(t)}else t._isValid=!1}var pe=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,ge={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function ye(t){var e,n,r,i=pe.exec(t._i.replace(/\([^)]*\)|[\n\t]/g," ").replace(/(\s\s+)/g," ").replace(/^\s\s*/,"").replace(/\s\s*$/,""));if(i){var a=function(t,e,n,r,i,a){var o=[function(t){var e=parseInt(t,10);return e<=49?2e3+e:e<=999?1900+e:e}(t),Ct.indexOf(e),parseInt(n,10),parseInt(r,10),parseInt(i,10)];return a&&o.push(parseInt(a,10)),o}(i[4],i[3],i[2],i[5],i[6],i[7]);if(n=a,r=t,(e=i[1])&&Rt.indexOf(e)!==new Date(n[0],n[1],n[2]).getDay()&&(p(r).weekdayMismatch=!0,!(r._isValid=!1)))return;t._a=a,t._tzm=function(t,e,n){if(t)return ge[t];if(e)return 0;var r=parseInt(n,10),i=r%100;return(r-i)/100*60+i}(i[8],i[9],i[10]),t._d=Nt.apply(null,t._a),t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),p(t).rfc2822=!0}else t._isValid=!1}function ve(t){if(t._f!==i.ISO_8601)if(t._f!==i.RFC_2822){t._a=[],p(t).empty=!0;var e,n,r,a,o,s,c,u,l=""+t._i,f=l.length,d=0;for(r=V(t._f,t._locale).match(Y)||[],e=0;e<r.length;e++)a=r[e],(n=(l.match(ht(a,t))||[])[0])&&(0<(o=l.substr(0,l.indexOf(n))).length&&p(t).unusedInput.push(o),l=l.slice(l.indexOf(n)+n.length),d+=n.length),$[a]?(n?p(t).empty=!1:p(t).unusedTokens.push(a),s=a,u=t,null!=(c=n)&&h(dt,s)&&dt[s](c,u._a,u,s)):t._strict&&!n&&p(t).unusedTokens.push(a);p(t).charsLeftOver=f-d,0<l.length&&p(t).unusedInput.push(l),t._a[3]<=12&&!0===p(t).bigHour&&0<t._a[3]&&(p(t).bigHour=void 0),p(t).parsedDateParts=t._a.slice(0),p(t).meridiem=t._meridiem,t._a[3]=function(t,e,n){var r;return null==n?e:null!=t.meridiemHour?t.meridiemHour(e,n):(null!=t.isPM&&((r=t.isPM(n))&&e<12&&(e+=12),r||12!==e||(e=0)),e)}(t._locale,t._a[3],t._meridiem),oe(t),ie(t)}else ye(t);else de(t)}function me(t){var e,n,r,h,d=t._i,v=t._f;return t._locale=t._locale||re(t._l),null===d||void 0===v&&""===d?y({nullInput:!0}):("string"==typeof d&&(t._i=d=t._locale.preparse(d)),_(d)?new x(ie(d)):(u(d)?t._d=d:a(v)?function(t){var e,n,r,i,a;if(0===t._f.length)return p(t).invalidFormat=!0,t._d=new Date(NaN);for(i=0;i<t._f.length;i++)a=0,e=m({},t),null!=t._useUTC&&(e._useUTC=t._useUTC),e._f=t._f[i],ve(e),g(e)&&(a+=p(e).charsLeftOver,a+=10*p(e).unusedTokens.length,p(e).score=a,(null==r||a<r)&&(r=a,n=e));f(t,n||e)}(t):v?ve(t):s(n=(e=t)._i)?e._d=new Date(i.now()):u(n)?e._d=new Date(n.valueOf()):"string"==typeof n?(r=e,null===(h=fe.exec(r._i))?(de(r),!1===r._isValid&&(delete r._isValid,ye(r),!1===r._isValid&&(delete r._isValid,i.createFromInputFallback(r)))):r._d=new Date(+h[1])):a(n)?(e._a=l(n.slice(0),(function(t){return parseInt(t,10)})),oe(e)):o(n)?function(t){if(!t._d){var e=P(t._i);t._a=l([e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],(function(t){return t&&parseInt(t,10)})),oe(t)}}(e):c(n)?e._d=new Date(n):i.createFromInputFallback(e),g(t)||(t._d=null),t))}function be(t,e,n,r,i){var s,c={};return!0!==n&&!1!==n||(r=n,n=void 0),(o(t)&&function(t){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(t).length;var e;for(e in t)if(t.hasOwnProperty(e))return!1;return!0}(t)||a(t)&&0===t.length)&&(t=void 0),c._isAMomentObject=!0,c._useUTC=c._isUTC=i,c._l=n,c._i=t,c._f=e,c._strict=r,(s=new x(ie(me(c))))._nextDay&&(s.add(1,"d"),s._nextDay=void 0),s}function xe(t,e,n,r){return be(t,e,n,r,!1)}i.createFromInputFallback=C("value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",(function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))})),i.ISO_8601=function(){},i.RFC_2822=function(){};var _e=C("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",(function(){var t=xe.apply(null,arguments);return this.isValid()&&t.isValid()?t<this?this:t:y()})),ke=C("moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/",(function(){var t=xe.apply(null,arguments);return this.isValid()&&t.isValid()?this<t?this:t:y()}));function we(t,e){var n,r;if(1===e.length&&a(e[0])&&(e=e[0]),!e.length)return xe();for(n=e[0],r=1;r<e.length;++r)e[r].isValid()&&!e[r][t](n)||(n=e[r]);return n}var Ee=["year","quarter","month","week","day","hour","minute","second","millisecond"];function Te(t){var e=P(t),n=e.year||0,r=e.quarter||0,i=e.month||0,a=e.week||e.isoWeek||0,o=e.day||0,s=e.hour||0,c=e.minute||0,u=e.second||0,l=e.millisecond||0;this._isValid=function(t){for(var e in t)if(-1===mt.call(Ee,e)||null!=t[e]&&isNaN(t[e]))return!1;for(var n=!1,r=0;r<Ee.length;++r)if(t[Ee[r]]){if(n)return!1;parseFloat(t[Ee[r]])!==w(t[Ee[r]])&&(n=!0)}return!0}(e),this._milliseconds=+l+1e3*u+6e4*c+1e3*s*60*60,this._days=+o+7*a,this._months=+i+3*r+12*n,this._data={},this._locale=re(),this._bubble()}function Ce(t){return t instanceof Te}function Ae(t){return t<0?-1*Math.round(-1*t):Math.round(t)}function Se(t,e){W(t,0,0,(function(){var t=this.utcOffset(),n="+";return t<0&&(t=-t,n="-"),n+R(~~(t/60),2)+e+R(~~t%60,2)}))}Se("Z",":"),Se("ZZ",""),lt("Z",st),lt("ZZ",st),pt(["Z","ZZ"],(function(t,e,n){n._useUTC=!0,n._tzm=Oe(st,t)}));var Me=/([\+\-]|\d\d)/gi;function Oe(t,e){var n=(e||"").match(t);if(null===n)return null;var r=((n[n.length-1]||[])+"").match(Me)||["-",0,0],i=60*r[1]+w(r[2]);return 0===i?0:"+"===r[0]?i:-i}function De(t,e){var n,r;return e._isUTC?(n=e.clone(),r=(_(t)||u(t)?t.valueOf():xe(t).valueOf())-n.valueOf(),n._d.setTime(n._d.valueOf()+r),i.updateOffset(n,!1),n):xe(t).local()}function Ne(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function Be(){return!!this.isValid()&&this._isUTC&&0===this._offset}i.updateOffset=function(){};var Le=/^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,Fe=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;function Pe(t,e){var n,r,i,a=t,o=null;return Ce(t)?a={ms:t._milliseconds,d:t._days,M:t._months}:c(t)?(a={},e?a[e]=t:a.milliseconds=t):(o=Le.exec(t))?(n="-"===o[1]?-1:1,a={y:0,d:w(o[2])*n,h:w(o[3])*n,m:w(o[4])*n,s:w(o[5])*n,ms:w(Ae(1e3*o[6]))*n}):(o=Fe.exec(t))?(n="-"===o[1]?-1:1,a={y:Ie(o[2],n),M:Ie(o[3],n),w:Ie(o[4],n),d:Ie(o[5],n),h:Ie(o[6],n),m:Ie(o[7],n),s:Ie(o[8],n)}):null==a?a={}:"object"==typeof a&&("from"in a||"to"in a)&&(i=function(t,e){var n;return t.isValid()&&e.isValid()?(e=De(e,t),t.isBefore(e)?n=je(t,e):((n=je(e,t)).milliseconds=-n.milliseconds,n.months=-n.months),n):{milliseconds:0,months:0}}(xe(a.from),xe(a.to)),(a={}).ms=i.milliseconds,a.M=i.months),r=new Te(a),Ce(t)&&h(t,"_locale")&&(r._locale=t._locale),r}function Ie(t,e){var n=t&&parseFloat(t.replace(",","."));return(isNaN(n)?0:n)*e}function je(t,e){var n={};return n.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(n.months,"M").isAfter(e)&&--n.months,n.milliseconds=+e-+t.clone().add(n.months,"M"),n}function Re(t,e){return function(n,r){var i;return null===r||isNaN(+r)||(M(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),i=n,n=r,r=i),Ye(this,Pe(n="string"==typeof n?+n:n,r),t),this}}function Ye(t,e,n,r){var a=e._milliseconds,o=Ae(e._days),s=Ae(e._months);t.isValid()&&(r=null==r||r,s&&At(t,_t(t,"Month")+s*n),o&&kt(t,"Date",_t(t,"Date")+o*n),a&&t._d.setTime(t._d.valueOf()+a*n),r&&i.updateOffset(t,o||s))}Pe.fn=Te.prototype,Pe.invalid=function(){return Pe(NaN)};var ze=Re(1,"add"),Ue=Re(-1,"subtract");function $e(t,e){var n=12*(e.year()-t.year())+(e.month()-t.month()),r=t.clone().add(n,"months");return-(n+(e-r<0?(e-r)/(r-t.clone().add(n-1,"months")):(e-r)/(t.clone().add(n+1,"months")-r)))||0}function We(t){var e;return void 0===t?this._locale._abbr:(null!=(e=re(t))&&(this._locale=e),this)}i.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",i.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var He=C("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",(function(t){return void 0===t?this.localeData():this.locale(t)}));function Ve(){return this._locale}var Ge=126227808e5;function qe(t,e){return(t%e+e)%e}function Xe(t,e,n){return t<100&&0<=t?new Date(t+400,e,n)-Ge:new Date(t,e,n).valueOf()}function Ze(t,e,n){return t<100&&0<=t?Date.UTC(t+400,e,n)-Ge:Date.UTC(t,e,n)}function Je(t,e){W(0,[t,t.length],0,e)}function Ke(t,e,n,r,i){var a;return null==t?Ft(this,r,i).year:((a=Pt(t,r,i))<e&&(e=a),function(t,e,n,r,i){var a=Lt(t,e,n,r,i),o=Nt(a.year,0,a.dayOfYear);return this.year(o.getUTCFullYear()),this.month(o.getUTCMonth()),this.date(o.getUTCDate()),this}.call(this,t,e,n,r,i))}W(0,["gg",2],0,(function(){return this.weekYear()%100})),W(0,["GG",2],0,(function(){return this.isoWeekYear()%100})),Je("gggg","weekYear"),Je("ggggg","weekYear"),Je("GGGG","isoWeekYear"),Je("GGGGG","isoWeekYear"),L("weekYear","gg"),L("isoWeekYear","GG"),j("weekYear",1),j("isoWeekYear",1),lt("G",at),lt("g",at),lt("GG",K,q),lt("gg",K,q),lt("GGGG",nt,Z),lt("gggg",nt,Z),lt("GGGGG",rt,J),lt("ggggg",rt,J),gt(["gggg","ggggg","GGGG","GGGGG"],(function(t,e,n,r){e[r.substr(0,2)]=w(t)})),gt(["gg","GG"],(function(t,e,n,r){e[r]=i.parseTwoDigitYear(t)})),W("Q",0,"Qo","quarter"),L("quarter","Q"),j("quarter",7),lt("Q",G),pt("Q",(function(t,e){e[1]=3*(w(t)-1)})),W("D",["DD",2],"Do","date"),L("date","D"),j("date",9),lt("D",K),lt("DD",K,q),lt("Do",(function(t,e){return t?e._dayOfMonthOrdinalParse||e._ordinalParse:e._dayOfMonthOrdinalParseLenient})),pt(["D","DD"],2),pt("Do",(function(t,e){e[2]=w(t.match(K)[0])}));var Qe=xt("Date",!0);W("DDD",["DDDD",3],"DDDo","dayOfYear"),L("dayOfYear","DDD"),j("dayOfYear",4),lt("DDD",et),lt("DDDD",X),pt(["DDD","DDDD"],(function(t,e,n){n._dayOfYear=w(t)})),W("m",["mm",2],0,"minute"),L("minute","m"),j("minute",14),lt("m",K),lt("mm",K,q),pt(["m","mm"],4);var tn=xt("Minutes",!1);W("s",["ss",2],0,"second"),L("second","s"),j("second",15),lt("s",K),lt("ss",K,q),pt(["s","ss"],5);var en,nn=xt("Seconds",!1);for(W("S",0,0,(function(){return~~(this.millisecond()/100)})),W(0,["SS",2],0,(function(){return~~(this.millisecond()/10)})),W(0,["SSS",3],0,"millisecond"),W(0,["SSSS",4],0,(function(){return 10*this.millisecond()})),W(0,["SSSSS",5],0,(function(){return 100*this.millisecond()})),W(0,["SSSSSS",6],0,(function(){return 1e3*this.millisecond()})),W(0,["SSSSSSS",7],0,(function(){return 1e4*this.millisecond()})),W(0,["SSSSSSSS",8],0,(function(){return 1e5*this.millisecond()})),W(0,["SSSSSSSSS",9],0,(function(){return 1e6*this.millisecond()})),L("millisecond","ms"),j("millisecond",16),lt("S",et,G),lt("SS",et,q),lt("SSS",et,X),en="SSSS";en.length<=9;en+="S")lt(en,it);function rn(t,e){e[6]=w(1e3*("0."+t))}for(en="S";en.length<=9;en+="S")pt(en,rn);var an=xt("Milliseconds",!1);W("z",0,0,"zoneAbbr"),W("zz",0,0,"zoneName");var on=x.prototype;function sn(t){return t}on.add=ze,on.calendar=function(t,e){var n=t||xe(),r=De(n,this).startOf("day"),a=i.calendarFormat(this,r)||"sameElse",o=e&&(O(e[a])?e[a].call(this,n):e[a]);return this.format(o||this.localeData().calendar(a,this,xe(n)))},on.clone=function(){return new x(this)},on.diff=function(t,e,n){var r,i,a;if(!this.isValid())return NaN;if(!(r=De(t,this)).isValid())return NaN;switch(i=6e4*(r.utcOffset()-this.utcOffset()),e=F(e)){case"year":a=$e(this,r)/12;break;case"month":a=$e(this,r);break;case"quarter":a=$e(this,r)/3;break;case"second":a=(this-r)/1e3;break;case"minute":a=(this-r)/6e4;break;case"hour":a=(this-r)/36e5;break;case"day":a=(this-r-i)/864e5;break;case"week":a=(this-r-i)/6048e5;break;default:a=this-r}return n?a:k(a)},on.endOf=function(t){var e;if(void 0===(t=F(t))||"millisecond"===t||!this.isValid())return this;var n=this._isUTC?Ze:Xe;switch(t){case"year":e=n(this.year()+1,0,1)-1;break;case"quarter":e=n(this.year(),this.month()-this.month()%3+3,1)-1;break;case"month":e=n(this.year(),this.month()+1,1)-1;break;case"week":e=n(this.year(),this.month(),this.date()-this.weekday()+7)-1;break;case"isoWeek":e=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1)+7)-1;break;case"day":case"date":e=n(this.year(),this.month(),this.date()+1)-1;break;case"hour":e=this._d.valueOf(),e+=36e5-qe(e+(this._isUTC?0:6e4*this.utcOffset()),36e5)-1;break;case"minute":e=this._d.valueOf(),e+=6e4-qe(e,6e4)-1;break;case"second":e=this._d.valueOf(),e+=1e3-qe(e,1e3)-1}return this._d.setTime(e),i.updateOffset(this,!0),this},on.format=function(t){t||(t=this.isUtc()?i.defaultFormatUtc:i.defaultFormat);var e=H(this,t);return this.localeData().postformat(e)},on.from=function(t,e){return this.isValid()&&(_(t)&&t.isValid()||xe(t).isValid())?Pe({to:this,from:t}).locale(this.locale()).humanize(!e):this.localeData().invalidDate()},on.fromNow=function(t){return this.from(xe(),t)},on.to=function(t,e){return this.isValid()&&(_(t)&&t.isValid()||xe(t).isValid())?Pe({from:this,to:t}).locale(this.locale()).humanize(!e):this.localeData().invalidDate()},on.toNow=function(t){return this.to(xe(),t)},on.get=function(t){return O(this[t=F(t)])?this[t]():this},on.invalidAt=function(){return p(this).overflow},on.isAfter=function(t,e){var n=_(t)?t:xe(t);return!(!this.isValid()||!n.isValid())&&("millisecond"===(e=F(e)||"millisecond")?this.valueOf()>n.valueOf():n.valueOf()<this.clone().startOf(e).valueOf())},on.isBefore=function(t,e){var n=_(t)?t:xe(t);return!(!this.isValid()||!n.isValid())&&("millisecond"===(e=F(e)||"millisecond")?this.valueOf()<n.valueOf():this.clone().endOf(e).valueOf()<n.valueOf())},on.isBetween=function(t,e,n,r){var i=_(t)?t:xe(t),a=_(e)?e:xe(e);return!!(this.isValid()&&i.isValid()&&a.isValid())&&("("===(r=r||"()")[0]?this.isAfter(i,n):!this.isBefore(i,n))&&(")"===r[1]?this.isBefore(a,n):!this.isAfter(a,n))},on.isSame=function(t,e){var n,r=_(t)?t:xe(t);return!(!this.isValid()||!r.isValid())&&("millisecond"===(e=F(e)||"millisecond")?this.valueOf()===r.valueOf():(n=r.valueOf(),this.clone().startOf(e).valueOf()<=n&&n<=this.clone().endOf(e).valueOf()))},on.isSameOrAfter=function(t,e){return this.isSame(t,e)||this.isAfter(t,e)},on.isSameOrBefore=function(t,e){return this.isSame(t,e)||this.isBefore(t,e)},on.isValid=function(){return g(this)},on.lang=He,on.locale=We,on.localeData=Ve,on.max=ke,on.min=_e,on.parsingFlags=function(){return f({},p(this))},on.set=function(t,e){if("object"==typeof t)for(var n=function(t){var e=[];for(var n in t)e.push({unit:n,priority:I[n]});return e.sort((function(t,e){return t.priority-e.priority})),e}(t=P(t)),r=0;r<n.length;r++)this[n[r].unit](t[n[r].unit]);else if(O(this[t=F(t)]))return this[t](e);return this},on.startOf=function(t){var e;if(void 0===(t=F(t))||"millisecond"===t||!this.isValid())return this;var n=this._isUTC?Ze:Xe;switch(t){case"year":e=n(this.year(),0,1);break;case"quarter":e=n(this.year(),this.month()-this.month()%3,1);break;case"month":e=n(this.year(),this.month(),1);break;case"week":e=n(this.year(),this.month(),this.date()-this.weekday());break;case"isoWeek":e=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1));break;case"day":case"date":e=n(this.year(),this.month(),this.date());break;case"hour":e=this._d.valueOf(),e-=qe(e+(this._isUTC?0:6e4*this.utcOffset()),36e5);break;case"minute":e=this._d.valueOf(),e-=qe(e,6e4);break;case"second":e=this._d.valueOf(),e-=qe(e,1e3)}return this._d.setTime(e),i.updateOffset(this,!0),this},on.subtract=Ue,on.toArray=function(){var t=this;return[t.year(),t.month(),t.date(),t.hour(),t.minute(),t.second(),t.millisecond()]},on.toObject=function(){var t=this;return{years:t.year(),months:t.month(),date:t.date(),hours:t.hours(),minutes:t.minutes(),seconds:t.seconds(),milliseconds:t.milliseconds()}},on.toDate=function(){return new Date(this.valueOf())},on.toISOString=function(t){if(!this.isValid())return null;var e=!0!==t,n=e?this.clone().utc():this;return n.year()<0||9999<n.year()?H(n,e?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ"):O(Date.prototype.toISOString)?e?this.toDate().toISOString():new Date(this.valueOf()+60*this.utcOffset()*1e3).toISOString().replace("Z",H(n,"Z")):H(n,e?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")},on.inspect=function(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var t="moment",e="";this.isLocal()||(t=0===this.utcOffset()?"moment.utc":"moment.parseZone",e="Z");var n="["+t+'("]',r=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",i=e+'[")]';return this.format(n+r+"-MM-DD[T]HH:mm:ss.SSS"+i)},on.toJSON=function(){return this.isValid()?this.toISOString():null},on.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},on.unix=function(){return Math.floor(this.valueOf()/1e3)},on.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},on.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},on.year=bt,on.isLeapYear=function(){return vt(this.year())},on.weekYear=function(t){return Ke.call(this,t,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)},on.isoWeekYear=function(t){return Ke.call(this,t,this.isoWeek(),this.isoWeekday(),1,4)},on.quarter=on.quarters=function(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)},on.month=St,on.daysInMonth=function(){return wt(this.year(),this.month())},on.week=on.weeks=function(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")},on.isoWeek=on.isoWeeks=function(t){var e=Ft(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")},on.weeksInYear=function(){var t=this.localeData()._week;return Pt(this.year(),t.dow,t.doy)},on.isoWeeksInYear=function(){return Pt(this.year(),1,4)},on.date=Qe,on.day=on.days=function(t){if(!this.isValid())return null!=t?this:NaN;var e,n,r=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(e=t,n=this.localeData(),t="string"!=typeof e?e:isNaN(e)?"number"==typeof(e=n.weekdaysParse(e))?e:null:parseInt(e,10),this.add(t-r,"d")):r},on.weekday=function(t){if(!this.isValid())return null!=t?this:NaN;var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")},on.isoWeekday=function(t){if(!this.isValid())return null!=t?this:NaN;if(null==t)return this.day()||7;var e,n,r=(e=t,n=this.localeData(),"string"==typeof e?n.weekdaysParse(e)%7||7:isNaN(e)?null:e);return this.day(this.day()%7?r:r-7)},on.dayOfYear=function(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")},on.hour=on.hours=Xt,on.minute=on.minutes=tn,on.second=on.seconds=nn,on.millisecond=on.milliseconds=an,on.utcOffset=function(t,e,n){var r,a=this._offset||0;if(!this.isValid())return null!=t?this:NaN;if(null==t)return this._isUTC?a:Ne(this);if("string"==typeof t){if(null===(t=Oe(st,t)))return this}else Math.abs(t)<16&&!n&&(t*=60);return!this._isUTC&&e&&(r=Ne(this)),this._offset=t,this._isUTC=!0,null!=r&&this.add(r,"m"),a!==t&&(!e||this._changeInProgress?Ye(this,Pe(t-a,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,i.updateOffset(this,!0),this._changeInProgress=null)),this},on.utc=function(t){return this.utcOffset(0,t)},on.local=function(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(Ne(this),"m")),this},on.parseZone=function(){if(null!=this._tzm)this.utcOffset(this._tzm,!1,!0);else if("string"==typeof this._i){var t=Oe(ot,this._i);null!=t?this.utcOffset(t):this.utcOffset(0,!0)}return this},on.hasAlignedHourOffset=function(t){return!!this.isValid()&&(t=t?xe(t).utcOffset():0,(this.utcOffset()-t)%60==0)},on.isDST=function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},on.isLocal=function(){return!!this.isValid()&&!this._isUTC},on.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},on.isUtc=Be,on.isUTC=Be,on.zoneAbbr=function(){return this._isUTC?"UTC":""},on.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},on.dates=C("dates accessor is deprecated. Use date instead.",Qe),on.months=C("months accessor is deprecated. Use month instead",St),on.years=C("years accessor is deprecated. Use year instead",bt),on.zone=C("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",(function(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()})),on.isDSTShifted=C("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",(function(){if(!s(this._isDSTShifted))return this._isDSTShifted;var t={};if(m(t,this),(t=me(t))._a){var e=t._isUTC?d(t._a):xe(t._a);this._isDSTShifted=this.isValid()&&0<E(t._a,e.toArray())}else this._isDSTShifted=!1;return this._isDSTShifted}));var cn=N.prototype;function un(t,e,n,r){var i=re(),a=d().set(r,e);return i[n](a,t)}function ln(t,e,n){if(c(t)&&(e=t,t=void 0),t=t||"",null!=e)return un(t,e,n,"month");var r,i=[];for(r=0;r<12;r++)i[r]=un(t,r,n,"month");return i}function hn(t,e,n,r){"boolean"==typeof t?c(e)&&(n=e,e=void 0):(e=t,t=!1,c(n=e)&&(n=e,e=void 0)),e=e||"";var i,a=re(),o=t?a._week.dow:0;if(null!=n)return un(e,(n+o)%7,r,"day");var s=[];for(i=0;i<7;i++)s[i]=un(e,(i+o)%7,r,"day");return s}cn.calendar=function(t,e,n){var r=this._calendar[t]||this._calendar.sameElse;return O(r)?r.call(e,n):r},cn.longDateFormat=function(t){var e=this._longDateFormat[t],n=this._longDateFormat[t.toUpperCase()];return e||!n?e:(this._longDateFormat[t]=n.replace(/MMMM|MM|DD|dddd/g,(function(t){return t.slice(1)})),this._longDateFormat[t])},cn.invalidDate=function(){return this._invalidDate},cn.ordinal=function(t){return this._ordinal.replace("%d",t)},cn.preparse=sn,cn.postformat=sn,cn.relativeTime=function(t,e,n,r){var i=this._relativeTime[n];return O(i)?i(t,e,n,r):i.replace(/%d/i,t)},cn.pastFuture=function(t,e){var n=this._relativeTime[0<t?"future":"past"];return O(n)?n(e):n.replace(/%s/i,e)},cn.set=function(t){var e,n;for(n in t)O(e=t[n])?this[n]=e:this["_"+n]=e;this._config=t,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)},cn.months=function(t,e){return t?a(this._months)?this._months[t.month()]:this._months[(this._months.isFormat||Et).test(e)?"format":"standalone"][t.month()]:a(this._months)?this._months:this._months.standalone},cn.monthsShort=function(t,e){return t?a(this._monthsShort)?this._monthsShort[t.month()]:this._monthsShort[Et.test(e)?"format":"standalone"][t.month()]:a(this._monthsShort)?this._monthsShort:this._monthsShort.standalone},cn.monthsParse=function(t,e,n){var r,i,a;if(this._monthsParseExact)return function(t,e,n){var r,i,a,o=t.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],r=0;r<12;++r)a=d([2e3,r]),this._shortMonthsParse[r]=this.monthsShort(a,"").toLocaleLowerCase(),this._longMonthsParse[r]=this.months(a,"").toLocaleLowerCase();return n?"MMM"===e?-1!==(i=mt.call(this._shortMonthsParse,o))?i:null:-1!==(i=mt.call(this._longMonthsParse,o))?i:null:"MMM"===e?-1!==(i=mt.call(this._shortMonthsParse,o))?i:-1!==(i=mt.call(this._longMonthsParse,o))?i:null:-1!==(i=mt.call(this._longMonthsParse,o))?i:-1!==(i=mt.call(this._shortMonthsParse,o))?i:null}.call(this,t,e,n);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),r=0;r<12;r++){if(i=d([2e3,r]),n&&!this._longMonthsParse[r]&&(this._longMonthsParse[r]=new RegExp("^"+this.months(i,"").replace(".","")+"$","i"),this._shortMonthsParse[r]=new RegExp("^"+this.monthsShort(i,"").replace(".","")+"$","i")),n||this._monthsParse[r]||(a="^"+this.months(i,"")+"|^"+this.monthsShort(i,""),this._monthsParse[r]=new RegExp(a.replace(".",""),"i")),n&&"MMMM"===e&&this._longMonthsParse[r].test(t))return r;if(n&&"MMM"===e&&this._shortMonthsParse[r].test(t))return r;if(!n&&this._monthsParse[r].test(t))return r}},cn.monthsRegex=function(t){return this._monthsParseExact?(h(this,"_monthsRegex")||Dt.call(this),t?this._monthsStrictRegex:this._monthsRegex):(h(this,"_monthsRegex")||(this._monthsRegex=Ot),this._monthsStrictRegex&&t?this._monthsStrictRegex:this._monthsRegex)},cn.monthsShortRegex=function(t){return this._monthsParseExact?(h(this,"_monthsRegex")||Dt.call(this),t?this._monthsShortStrictRegex:this._monthsShortRegex):(h(this,"_monthsShortRegex")||(this._monthsShortRegex=Mt),this._monthsShortStrictRegex&&t?this._monthsShortStrictRegex:this._monthsShortRegex)},cn.week=function(t){return Ft(t,this._week.dow,this._week.doy).week},cn.firstDayOfYear=function(){return this._week.doy},cn.firstDayOfWeek=function(){return this._week.dow},cn.weekdays=function(t,e){var n=a(this._weekdays)?this._weekdays:this._weekdays[t&&!0!==t&&this._weekdays.isFormat.test(e)?"format":"standalone"];return!0===t?It(n,this._week.dow):t?n[t.day()]:n},cn.weekdaysMin=function(t){return!0===t?It(this._weekdaysMin,this._week.dow):t?this._weekdaysMin[t.day()]:this._weekdaysMin},cn.weekdaysShort=function(t){return!0===t?It(this._weekdaysShort,this._week.dow):t?this._weekdaysShort[t.day()]:this._weekdaysShort},cn.weekdaysParse=function(t,e,n){var r,i,a;if(this._weekdaysParseExact)return function(t,e,n){var r,i,a,o=t.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],r=0;r<7;++r)a=d([2e3,1]).day(r),this._minWeekdaysParse[r]=this.weekdaysMin(a,"").toLocaleLowerCase(),this._shortWeekdaysParse[r]=this.weekdaysShort(a,"").toLocaleLowerCase(),this._weekdaysParse[r]=this.weekdays(a,"").toLocaleLowerCase();return n?"dddd"===e?-1!==(i=mt.call(this._weekdaysParse,o))?i:null:"ddd"===e?-1!==(i=mt.call(this._shortWeekdaysParse,o))?i:null:-1!==(i=mt.call(this._minWeekdaysParse,o))?i:null:"dddd"===e?-1!==(i=mt.call(this._weekdaysParse,o))?i:-1!==(i=mt.call(this._shortWeekdaysParse,o))?i:-1!==(i=mt.call(this._minWeekdaysParse,o))?i:null:"ddd"===e?-1!==(i=mt.call(this._shortWeekdaysParse,o))?i:-1!==(i=mt.call(this._weekdaysParse,o))?i:-1!==(i=mt.call(this._minWeekdaysParse,o))?i:null:-1!==(i=mt.call(this._minWeekdaysParse,o))?i:-1!==(i=mt.call(this._weekdaysParse,o))?i:-1!==(i=mt.call(this._shortWeekdaysParse,o))?i:null}.call(this,t,e,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),r=0;r<7;r++){if(i=d([2e3,1]).day(r),n&&!this._fullWeekdaysParse[r]&&(this._fullWeekdaysParse[r]=new RegExp("^"+this.weekdays(i,"").replace(".","\\.?")+"$","i"),this._shortWeekdaysParse[r]=new RegExp("^"+this.weekdaysShort(i,"").replace(".","\\.?")+"$","i"),this._minWeekdaysParse[r]=new RegExp("^"+this.weekdaysMin(i,"").replace(".","\\.?")+"$","i")),this._weekdaysParse[r]||(a="^"+this.weekdays(i,"")+"|^"+this.weekdaysShort(i,"")+"|^"+this.weekdaysMin(i,""),this._weekdaysParse[r]=new RegExp(a.replace(".",""),"i")),n&&"dddd"===e&&this._fullWeekdaysParse[r].test(t))return r;if(n&&"ddd"===e&&this._shortWeekdaysParse[r].test(t))return r;if(n&&"dd"===e&&this._minWeekdaysParse[r].test(t))return r;if(!n&&this._weekdaysParse[r].test(t))return r}},cn.weekdaysRegex=function(t){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||Wt.call(this),t?this._weekdaysStrictRegex:this._weekdaysRegex):(h(this,"_weekdaysRegex")||(this._weekdaysRegex=zt),this._weekdaysStrictRegex&&t?this._weekdaysStrictRegex:this._weekdaysRegex)},cn.weekdaysShortRegex=function(t){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||Wt.call(this),t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(h(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=Ut),this._weekdaysShortStrictRegex&&t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)},cn.weekdaysMinRegex=function(t){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||Wt.call(this),t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(h(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=$t),this._weekdaysMinStrictRegex&&t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)},cn.isPM=function(t){return"p"===(t+"").toLowerCase().charAt(0)},cn.meridiem=function(t,e,n){return 11<t?n?"pm":"PM":n?"am":"AM"},ee("en",{dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10;return t+(1===w(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th")}}),i.lang=C("moment.lang is deprecated. Use moment.locale instead.",ee),i.langData=C("moment.langData is deprecated. Use moment.localeData instead.",re);var fn=Math.abs;function dn(t,e,n,r){var i=Pe(e,n);return t._milliseconds+=r*i._milliseconds,t._days+=r*i._days,t._months+=r*i._months,t._bubble()}function pn(t){return t<0?Math.floor(t):Math.ceil(t)}function gn(t){return 4800*t/146097}function yn(t){return 146097*t/4800}function vn(t){return function(){return this.as(t)}}var mn=vn("ms"),bn=vn("s"),xn=vn("m"),_n=vn("h"),kn=vn("d"),wn=vn("w"),En=vn("M"),Tn=vn("Q"),Cn=vn("y");function An(t){return function(){return this.isValid()?this._data[t]:NaN}}var Sn=An("milliseconds"),Mn=An("seconds"),On=An("minutes"),Dn=An("hours"),Nn=An("days"),Bn=An("months"),Ln=An("years"),Fn=Math.round,Pn={ss:44,s:45,m:45,h:22,d:26,M:11},In=Math.abs;function jn(t){return(0<t)-(t<0)||+t}function Rn(){if(!this.isValid())return this.localeData().invalidDate();var t,e,n=In(this._milliseconds)/1e3,r=In(this._days),i=In(this._months);e=k((t=k(n/60))/60),n%=60,t%=60;var a=k(i/12),o=i%=12,s=r,c=e,u=t,l=n?n.toFixed(3).replace(/\.?0+$/,""):"",h=this.asSeconds();if(!h)return"P0D";var f=h<0?"-":"",d=jn(this._months)!==jn(h)?"-":"",p=jn(this._days)!==jn(h)?"-":"",g=jn(this._milliseconds)!==jn(h)?"-":"";return f+"P"+(a?d+a+"Y":"")+(o?d+o+"M":"")+(s?p+s+"D":"")+(c||u||l?"T":"")+(c?g+c+"H":"")+(u?g+u+"M":"")+(l?g+l+"S":"")}var Yn=Te.prototype;return Yn.isValid=function(){return this._isValid},Yn.abs=function(){var t=this._data;return this._milliseconds=fn(this._milliseconds),this._days=fn(this._days),this._months=fn(this._months),t.milliseconds=fn(t.milliseconds),t.seconds=fn(t.seconds),t.minutes=fn(t.minutes),t.hours=fn(t.hours),t.months=fn(t.months),t.years=fn(t.years),this},Yn.add=function(t,e){return dn(this,t,e,1)},Yn.subtract=function(t,e){return dn(this,t,e,-1)},Yn.as=function(t){if(!this.isValid())return NaN;var e,n,r=this._milliseconds;if("month"===(t=F(t))||"quarter"===t||"year"===t)switch(e=this._days+r/864e5,n=this._months+gn(e),t){case"month":return n;case"quarter":return n/3;case"year":return n/12}else switch(e=this._days+Math.round(yn(this._months)),t){case"week":return e/7+r/6048e5;case"day":return e+r/864e5;case"hour":return 24*e+r/36e5;case"minute":return 1440*e+r/6e4;case"second":return 86400*e+r/1e3;case"millisecond":return Math.floor(864e5*e)+r;default:throw new Error("Unknown unit "+t)}},Yn.asMilliseconds=mn,Yn.asSeconds=bn,Yn.asMinutes=xn,Yn.asHours=_n,Yn.asDays=kn,Yn.asWeeks=wn,Yn.asMonths=En,Yn.asQuarters=Tn,Yn.asYears=Cn,Yn.valueOf=function(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*w(this._months/12):NaN},Yn._bubble=function(){var t,e,n,r,i,a=this._milliseconds,o=this._days,s=this._months,c=this._data;return 0<=a&&0<=o&&0<=s||a<=0&&o<=0&&s<=0||(a+=864e5*pn(yn(s)+o),s=o=0),c.milliseconds=a%1e3,t=k(a/1e3),c.seconds=t%60,e=k(t/60),c.minutes=e%60,n=k(e/60),c.hours=n%24,s+=i=k(gn(o+=k(n/24))),o-=pn(yn(i)),r=k(s/12),s%=12,c.days=o,c.months=s,c.years=r,this},Yn.clone=function(){return Pe(this)},Yn.get=function(t){return t=F(t),this.isValid()?this[t+"s"]():NaN},Yn.milliseconds=Sn,Yn.seconds=Mn,Yn.minutes=On,Yn.hours=Dn,Yn.days=Nn,Yn.weeks=function(){return k(this.days()/7)},Yn.months=Bn,Yn.years=Ln,Yn.humanize=function(t){if(!this.isValid())return this.localeData().invalidDate();var e,n,r,i,a,o,s,c,u,l,h=this.localeData(),f=(e=!t,n=h,r=Pe(this).abs(),i=Fn(r.as("s")),a=Fn(r.as("m")),o=Fn(r.as("h")),s=Fn(r.as("d")),c=Fn(r.as("M")),u=Fn(r.as("y")),(l=i<=Pn.ss&&["s",i]||i<Pn.s&&["ss",i]||a<=1&&["m"]||a<Pn.m&&["mm",a]||o<=1&&["h"]||o<Pn.h&&["hh",o]||s<=1&&["d"]||s<Pn.d&&["dd",s]||c<=1&&["M"]||c<Pn.M&&["MM",c]||u<=1&&["y"]||["yy",u])[2]=e,l[3]=0<+this,l[4]=n,function(t,e,n,r,i){return i.relativeTime(e||1,!!n,t,r)}.apply(null,l));return t&&(f=h.pastFuture(+this,f)),h.postformat(f)},Yn.toISOString=Rn,Yn.toString=Rn,Yn.toJSON=Rn,Yn.locale=We,Yn.localeData=Ve,Yn.toIsoString=C("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Rn),Yn.lang=He,W("X",0,0,"unix"),W("x",0,0,"valueOf"),lt("x",at),lt("X",/[+-]?\d+(\.\d{1,3})?/),pt("X",(function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))})),pt("x",(function(t,e,n){n._d=new Date(w(t))})),i.version="2.24.0",e=xe,i.fn=on,i.min=function(){return we("isBefore",[].slice.call(arguments,0))},i.max=function(){return we("isAfter",[].slice.call(arguments,0))},i.now=function(){return Date.now?Date.now():+new Date},i.utc=d,i.unix=function(t){return xe(1e3*t)},i.months=function(t,e){return ln(t,e,"months")},i.isDate=u,i.locale=ee,i.invalid=y,i.duration=Pe,i.isMoment=_,i.weekdays=function(t,e,n){return hn(t,e,n,"weekdays")},i.parseZone=function(){return xe.apply(null,arguments).parseZone()},i.localeData=re,i.isDuration=Ce,i.monthsShort=function(t,e){return ln(t,e,"monthsShort")},i.weekdaysMin=function(t,e,n){return hn(t,e,n,"weekdaysMin")},i.defineLocale=ne,i.updateLocale=function(t,e){if(null!=e){var n,r,i=Zt;null!=(r=te(t))&&(i=r._config),(n=new N(e=D(i,e))).parentLocale=Jt[t],Jt[t]=n,ee(t)}else null!=Jt[t]&&(null!=Jt[t].parentLocale?Jt[t]=Jt[t].parentLocale:null!=Jt[t]&&delete Jt[t]);return Jt[t]},i.locales=function(){return A(Jt)},i.weekdaysShort=function(t,e,n){return hn(t,e,n,"weekdaysShort")},i.normalizeUnits=F,i.relativeTimeRounding=function(t){return void 0===t?Fn:"function"==typeof t&&(Fn=t,!0)},i.relativeTimeThreshold=function(t,e){return void 0!==Pn[t]&&(void 0===e?Pn[t]:(Pn[t]=e,"s"===t&&(Pn.ss=e-1),!0))},i.calendarFormat=function(t,e){var n=t.diff(e,"days",!0);return n<-6?"sameElse":n<-1?"lastWeek":n<0?"lastDay":n<1?"sameDay":n<2?"nextDay":n<7?"nextWeek":"sameElse"},i.prototype=on,i.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},i}()}).call(this,n(7)(t))},function(t,e,n){var r=n(37),i=n(80);t.exports=function(t){return null!=t&&i(t.length)&&!r(t)}},function(t,e,n){var r=n(256),i=n(266),a=n(35),o=n(5),s=n(273);t.exports=function(t){return"function"==typeof t?t:null==t?a:"object"==typeof t?o(t)?i(t[0],t[1]):r(t):s(t)}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,9],n=[1,7],r=[1,6],i=[1,8],a=[1,20,21,22,23,38,46,75,76,77,78,79,80,94,95,98,99,100,102,103,109,110,111,112,113,114],o=[2,10],s=[1,20],c=[1,21],u=[1,22],l=[1,23],h=[1,30],f=[1,54],d=[1,32],p=[1,33],g=[1,34],y=[1,35],v=[1,36],m=[1,48],b=[1,43],x=[1,45],_=[1,40],k=[1,44],w=[1,47],E=[1,51],T=[1,52],C=[1,53],A=[1,42],S=[1,46],M=[1,49],O=[1,50],D=[1,41],N=[1,57],B=[1,62],L=[1,20,21,22,23,38,42,46,75,76,77,78,79,80,94,95,98,99,100,102,103,109,110,111,112,113,114],F=[1,66],P=[1,65],I=[1,67],j=[20,21,23,69,70],R=[1,88],Y=[1,93],z=[1,90],U=[1,95],$=[1,98],W=[1,96],H=[1,97],V=[1,91],G=[1,103],q=[1,102],X=[1,92],Z=[1,94],J=[1,99],K=[1,100],Q=[1,101],tt=[1,104],et=[20,21,22,23,69,70],nt=[20,21,22,23,47,69,70],rt=[20,21,22,23,40,46,47,49,51,53,55,57,59,61,62,64,69,70,80,94,95,98,99,100,102,103,109,110,111,112,113,114],it=[20,21,23],at=[20,21,23,46,69,70,80,94,95,98,99,100,102,103,109,110,111,112,113,114],ot=[1,12,20,21,22,23,24,38,42,46,75,76,77,78,79,80,94,95,98,99,100,102,103,109,110,111,112,113,114],st=[46,80,94,95,98,99,100,102,103,109,110,111,112,113,114],ct=[1,136],ut=[1,144],lt=[1,145],ht=[1,146],ft=[1,147],dt=[1,131],pt=[1,132],gt=[1,128],yt=[1,139],vt=[1,140],mt=[1,141],bt=[1,142],xt=[1,143],_t=[1,148],kt=[1,149],wt=[1,134],Et=[1,137],Tt=[1,133],Ct=[1,130],At=[20,21,22,23,38,42,46,75,76,77,78,79,80,94,95,98,99,100,102,103,109,110,111,112,113,114],St=[1,152],Mt=[20,21,22,23,26,46,80,94,95,98,99,100,102,103,109,110,111,112,113,114],Ot=[20,21,22,23,24,26,38,40,41,42,46,50,52,54,56,58,60,61,63,65,69,70,71,75,76,77,78,79,80,81,84,94,95,98,99,100,102,103,104,105,109,110,111,112,113,114],Dt=[12,21,22,24],Nt=[22,95],Bt=[1,233],Lt=[1,237],Ft=[1,234],Pt=[1,231],It=[1,228],jt=[1,229],Rt=[1,230],Yt=[1,232],zt=[1,235],Ut=[1,236],$t=[1,238],Wt=[1,255],Ht=[20,21,23,95],Vt=[20,21,22,23,75,91,94,95,98,99,100,101,102,103,104],Gt={trace:function(){},yy:{},symbols_:{error:2,start:3,mermaidDoc:4,directive:5,openDirective:6,typeDirective:7,closeDirective:8,separator:9,":":10,argDirective:11,open_directive:12,type_directive:13,arg_directive:14,close_directive:15,graphConfig:16,document:17,line:18,statement:19,SEMI:20,NEWLINE:21,SPACE:22,EOF:23,GRAPH:24,NODIR:25,DIR:26,FirstStmtSeperator:27,ending:28,endToken:29,spaceList:30,spaceListNewline:31,verticeStatement:32,styleStatement:33,linkStyleStatement:34,classDefStatement:35,classStatement:36,clickStatement:37,subgraph:38,text:39,SQS:40,SQE:41,end:42,link:43,node:44,vertex:45,AMP:46,STYLE_SEPARATOR:47,idString:48,PS:49,PE:50,"(-":51,"-)":52,STADIUMSTART:53,STADIUMEND:54,SUBROUTINESTART:55,SUBROUTINEEND:56,CYLINDERSTART:57,CYLINDEREND:58,DIAMOND_START:59,DIAMOND_STOP:60,TAGEND:61,TRAPSTART:62,TRAPEND:63,INVTRAPSTART:64,INVTRAPEND:65,linkStatement:66,arrowText:67,TESTSTR:68,START_LINK:69,LINK:70,PIPE:71,textToken:72,STR:73,keywords:74,STYLE:75,LINKSTYLE:76,CLASSDEF:77,CLASS:78,CLICK:79,DOWN:80,UP:81,textNoTags:82,textNoTagsToken:83,DEFAULT:84,stylesOpt:85,alphaNum:86,CALLBACKNAME:87,CALLBACKARGS:88,HREF:89,LINK_TARGET:90,HEX:91,numList:92,INTERPOLATE:93,NUM:94,COMMA:95,style:96,styleComponent:97,ALPHA:98,COLON:99,MINUS:100,UNIT:101,BRKT:102,DOT:103,PCT:104,TAGSTART:105,alphaNumToken:106,idStringToken:107,alphaNumStatement:108,PUNCTUATION:109,UNICODE_TEXT:110,PLUS:111,EQUALS:112,MULT:113,UNDERSCORE:114,graphCodeTokens:115,ARROW_CROSS:116,ARROW_POINT:117,ARROW_CIRCLE:118,ARROW_OPEN:119,QUOTE:120,$accept:0,$end:1},terminals_:{2:"error",10:":",12:"open_directive",13:"type_directive",14:"arg_directive",15:"close_directive",20:"SEMI",21:"NEWLINE",22:"SPACE",23:"EOF",24:"GRAPH",25:"NODIR",26:"DIR",38:"subgraph",40:"SQS",41:"SQE",42:"end",46:"AMP",47:"STYLE_SEPARATOR",49:"PS",50:"PE",51:"(-",52:"-)",53:"STADIUMSTART",54:"STADIUMEND",55:"SUBROUTINESTART",56:"SUBROUTINEEND",57:"CYLINDERSTART",58:"CYLINDEREND",59:"DIAMOND_START",60:"DIAMOND_STOP",61:"TAGEND",62:"TRAPSTART",63:"TRAPEND",64:"INVTRAPSTART",65:"INVTRAPEND",68:"TESTSTR",69:"START_LINK",70:"LINK",71:"PIPE",73:"STR",75:"STYLE",76:"LINKSTYLE",77:"CLASSDEF",78:"CLASS",79:"CLICK",80:"DOWN",81:"UP",84:"DEFAULT",87:"CALLBACKNAME",88:"CALLBACKARGS",89:"HREF",90:"LINK_TARGET",91:"HEX",93:"INTERPOLATE",94:"NUM",95:"COMMA",98:"ALPHA",99:"COLON",100:"MINUS",101:"UNIT",102:"BRKT",103:"DOT",104:"PCT",105:"TAGSTART",109:"PUNCTUATION",110:"UNICODE_TEXT",111:"PLUS",112:"EQUALS",113:"MULT",114:"UNDERSCORE",116:"ARROW_CROSS",117:"ARROW_POINT",118:"ARROW_CIRCLE",119:"ARROW_OPEN",120:"QUOTE"},productions_:[0,[3,1],[3,2],[5,4],[5,6],[6,1],[7,1],[11,1],[8,1],[4,2],[17,0],[17,2],[18,1],[18,1],[18,1],[18,1],[18,1],[16,2],[16,2],[16,2],[16,3],[28,2],[28,1],[29,1],[29,1],[29,1],[27,1],[27,1],[27,2],[31,2],[31,2],[31,1],[31,1],[30,2],[30,1],[19,2],[19,2],[19,2],[19,2],[19,2],[19,2],[19,9],[19,6],[19,4],[9,1],[9,1],[9,1],[32,3],[32,4],[32,2],[32,1],[44,1],[44,5],[44,3],[45,4],[45,6],[45,4],[45,4],[45,4],[45,4],[45,4],[45,4],[45,6],[45,4],[45,4],[45,4],[45,4],[45,4],[45,1],[43,2],[43,3],[43,3],[43,1],[43,3],[66,1],[67,3],[39,1],[39,2],[39,1],[74,1],[74,1],[74,1],[74,1],[74,1],[74,1],[74,1],[74,1],[74,1],[74,1],[74,1],[82,1],[82,2],[35,5],[35,5],[36,5],[37,2],[37,4],[37,3],[37,5],[37,2],[37,4],[37,4],[37,6],[37,2],[37,4],[37,2],[37,4],[37,4],[37,6],[33,5],[33,5],[34,5],[34,5],[34,9],[34,9],[34,7],[34,7],[92,1],[92,3],[85,1],[85,3],[96,1],[96,2],[97,1],[97,1],[97,1],[97,1],[97,1],[97,1],[97,1],[97,1],[97,1],[97,1],[97,1],[72,1],[72,1],[72,1],[72,1],[72,1],[72,1],[83,1],[83,1],[83,1],[83,1],[48,1],[48,2],[86,1],[86,2],[108,1],[108,1],[108,1],[108,1],[106,1],[106,1],[106,1],[106,1],[106,1],[106,1],[106,1],[106,1],[106,1],[106,1],[106,1],[106,1],[106,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[107,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 5:r.parseDirective("%%{","open_directive");break;case 6:r.parseDirective(a[s],"type_directive");break;case 7:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 8:r.parseDirective("}%%","close_directive","flowchart");break;case 10:this.$=[];break;case 11:a[s]!==[]&&a[s-1].push(a[s]),this.$=a[s-1];break;case 12:case 76:case 78:case 90:case 146:case 148:case 149:this.$=a[s];break;case 19:r.setDirection("TB"),this.$="TB";break;case 20:r.setDirection(a[s-1]),this.$=a[s-1];break;case 35:this.$=a[s-1].nodes;break;case 36:case 37:case 38:case 39:case 40:this.$=[];break;case 41:this.$=r.addSubGraph(a[s-6],a[s-1],a[s-4]);break;case 42:this.$=r.addSubGraph(a[s-3],a[s-1],a[s-3]);break;case 43:this.$=r.addSubGraph(void 0,a[s-1],void 0);break;case 47:r.addLink(a[s-2].stmt,a[s],a[s-1]),this.$={stmt:a[s],nodes:a[s].concat(a[s-2].nodes)};break;case 48:r.addLink(a[s-3].stmt,a[s-1],a[s-2]),this.$={stmt:a[s-1],nodes:a[s-1].concat(a[s-3].nodes)};break;case 49:this.$={stmt:a[s-1],nodes:a[s-1]};break;case 50:this.$={stmt:a[s],nodes:a[s]};break;case 51:this.$=[a[s]];break;case 52:this.$=a[s-4].concat(a[s]);break;case 53:this.$=[a[s-2]],r.setClass(a[s-2],a[s]);break;case 54:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"square");break;case 55:this.$=a[s-5],r.addVertex(a[s-5],a[s-2],"circle");break;case 56:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"ellipse");break;case 57:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"stadium");break;case 58:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"subroutine");break;case 59:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"cylinder");break;case 60:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"round");break;case 61:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"diamond");break;case 62:this.$=a[s-5],r.addVertex(a[s-5],a[s-2],"hexagon");break;case 63:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"odd");break;case 64:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"trapezoid");break;case 65:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"inv_trapezoid");break;case 66:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"lean_right");break;case 67:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"lean_left");break;case 68:this.$=a[s],r.addVertex(a[s]);break;case 69:a[s-1].text=a[s],this.$=a[s-1];break;case 70:case 71:a[s-2].text=a[s-1],this.$=a[s-2];break;case 72:this.$=a[s];break;case 73:var c=r.destructLink(a[s],a[s-2]);this.$={type:c.type,stroke:c.stroke,length:c.length,text:a[s-1]};break;case 74:c=r.destructLink(a[s]);this.$={type:c.type,stroke:c.stroke,length:c.length};break;case 75:this.$=a[s-1];break;case 77:case 91:case 147:this.$=a[s-1]+""+a[s];break;case 92:case 93:this.$=a[s-4],r.addClass(a[s-2],a[s]);break;case 94:this.$=a[s-4],r.setClass(a[s-2],a[s]);break;case 95:case 103:this.$=a[s-1],r.setClickEvent(a[s-1],a[s]);break;case 96:case 104:this.$=a[s-3],r.setClickEvent(a[s-3],a[s-2]),r.setTooltip(a[s-3],a[s]);break;case 97:this.$=a[s-2],r.setClickEvent(a[s-2],a[s-1],a[s]);break;case 98:this.$=a[s-4],r.setClickEvent(a[s-4],a[s-3],a[s-2]),r.setTooltip(a[s-4],a[s]);break;case 99:case 105:this.$=a[s-1],r.setLink(a[s-1],a[s]);break;case 100:case 106:this.$=a[s-3],r.setLink(a[s-3],a[s-2]),r.setTooltip(a[s-3],a[s]);break;case 101:case 107:this.$=a[s-3],r.setLink(a[s-3],a[s-2],a[s]);break;case 102:case 108:this.$=a[s-5],r.setLink(a[s-5],a[s-4],a[s]),r.setTooltip(a[s-5],a[s-2]);break;case 109:this.$=a[s-4],r.addVertex(a[s-2],void 0,void 0,a[s]);break;case 110:case 112:this.$=a[s-4],r.updateLink(a[s-2],a[s]);break;case 111:this.$=a[s-4],r.updateLink([a[s-2]],a[s]);break;case 113:this.$=a[s-8],r.updateLinkInterpolate([a[s-6]],a[s-2]),r.updateLink([a[s-6]],a[s]);break;case 114:this.$=a[s-8],r.updateLinkInterpolate(a[s-6],a[s-2]),r.updateLink(a[s-6],a[s]);break;case 115:this.$=a[s-6],r.updateLinkInterpolate([a[s-4]],a[s]);break;case 116:this.$=a[s-6],r.updateLinkInterpolate(a[s-4],a[s]);break;case 117:case 119:this.$=[a[s]];break;case 118:case 120:a[s-2].push(a[s]),this.$=a[s-2];break;case 122:this.$=a[s-1]+a[s];break;case 144:this.$=a[s];break;case 145:this.$=a[s-1]+""+a[s];break;case 150:this.$="v";break;case 151:this.$="-"}},table:[{3:1,4:2,5:3,6:5,12:e,16:4,21:n,22:r,24:i},{1:[3]},{1:[2,1]},{3:10,4:2,5:3,6:5,12:e,16:4,21:n,22:r,24:i},t(a,o,{17:11}),{7:12,13:[1,13]},{16:14,21:n,22:r,24:i},{16:15,21:n,22:r,24:i},{25:[1,16],26:[1,17]},{13:[2,5]},{1:[2,2]},{1:[2,9],18:18,19:19,20:s,21:c,22:u,23:l,32:24,33:25,34:26,35:27,36:28,37:29,38:h,44:31,45:37,46:f,48:38,75:d,76:p,77:g,78:y,79:v,80:m,94:b,95:x,98:_,99:k,100:w,102:E,103:T,107:39,109:C,110:A,111:S,112:M,113:O,114:D},{8:55,10:[1,56],15:N},t([10,15],[2,6]),t(a,[2,17]),t(a,[2,18]),t(a,[2,19]),{20:[1,59],21:[1,60],22:B,27:58,30:61},t(L,[2,11]),t(L,[2,12]),t(L,[2,13]),t(L,[2,14]),t(L,[2,15]),t(L,[2,16]),{9:63,20:F,21:P,23:I,43:64,66:68,69:[1,69],70:[1,70]},{9:71,20:F,21:P,23:I},{9:72,20:F,21:P,23:I},{9:73,20:F,21:P,23:I},{9:74,20:F,21:P,23:I},{9:75,20:F,21:P,23:I},{9:77,20:F,21:P,22:[1,76],23:I},t(j,[2,50],{30:78,22:B}),{22:[1,79]},{22:[1,80]},{22:[1,81]},{22:[1,82]},{26:R,46:Y,73:[1,86],80:z,86:85,87:[1,83],89:[1,84],94:U,95:$,98:W,99:H,100:V,102:G,103:q,106:89,108:87,109:X,110:Z,111:J,112:K,113:Q,114:tt},t(et,[2,51],{47:[1,105]}),t(nt,[2,68],{107:116,40:[1,106],46:f,49:[1,107],51:[1,108],53:[1,109],55:[1,110],57:[1,111],59:[1,112],61:[1,113],62:[1,114],64:[1,115],80:m,94:b,95:x,98:_,99:k,100:w,102:E,103:T,109:C,110:A,111:S,112:M,113:O,114:D}),t(rt,[2,144]),t(rt,[2,165]),t(rt,[2,166]),t(rt,[2,167]),t(rt,[2,168]),t(rt,[2,169]),t(rt,[2,170]),t(rt,[2,171]),t(rt,[2,172]),t(rt,[2,173]),t(rt,[2,174]),t(rt,[2,175]),t(rt,[2,176]),t(rt,[2,177]),t(rt,[2,178]),t(rt,[2,179]),{9:117,20:F,21:P,23:I},{11:118,14:[1,119]},t(it,[2,8]),t(a,[2,20]),t(a,[2,26]),t(a,[2,27]),{21:[1,120]},t(at,[2,34],{30:121,22:B}),t(L,[2,35]),{44:122,45:37,46:f,48:38,80:m,94:b,95:x,98:_,99:k,100:w,102:E,103:T,107:39,109:C,110:A,111:S,112:M,113:O,114:D},t(ot,[2,44]),t(ot,[2,45]),t(ot,[2,46]),t(st,[2,72],{67:123,68:[1,124],71:[1,125]}),{22:ct,24:ut,26:lt,38:ht,39:126,42:ft,46:Y,61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},t([46,68,71,80,94,95,98,99,100,102,103,109,110,111,112,113,114],[2,74]),t(L,[2,36]),t(L,[2,37]),t(L,[2,38]),t(L,[2,39]),t(L,[2,40]),{22:ct,24:ut,26:lt,38:ht,39:150,42:ft,46:Y,61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},t(At,o,{17:151}),t(j,[2,49],{46:St}),{26:R,46:Y,80:z,86:153,91:[1,154],94:U,95:$,98:W,99:H,100:V,102:G,103:q,106:89,108:87,109:X,110:Z,111:J,112:K,113:Q,114:tt},{84:[1,155],92:156,94:[1,157]},{26:R,46:Y,80:z,84:[1,158],86:159,94:U,95:$,98:W,99:H,100:V,102:G,103:q,106:89,108:87,109:X,110:Z,111:J,112:K,113:Q,114:tt},{26:R,46:Y,80:z,86:160,94:U,95:$,98:W,99:H,100:V,102:G,103:q,106:89,108:87,109:X,110:Z,111:J,112:K,113:Q,114:tt},t(it,[2,95],{22:[1,161],88:[1,162]}),t(it,[2,99],{22:[1,163]}),t(it,[2,103],{106:89,108:165,22:[1,164],26:R,46:Y,80:z,94:U,95:$,98:W,99:H,100:V,102:G,103:q,109:X,110:Z,111:J,112:K,113:Q,114:tt}),t(it,[2,105],{22:[1,166]}),t(Mt,[2,146]),t(Mt,[2,148]),t(Mt,[2,149]),t(Mt,[2,150]),t(Mt,[2,151]),t(Ot,[2,152]),t(Ot,[2,153]),t(Ot,[2,154]),t(Ot,[2,155]),t(Ot,[2,156]),t(Ot,[2,157]),t(Ot,[2,158]),t(Ot,[2,159]),t(Ot,[2,160]),t(Ot,[2,161]),t(Ot,[2,162]),t(Ot,[2,163]),t(Ot,[2,164]),{46:f,48:167,80:m,94:b,95:x,98:_,99:k,100:w,102:E,103:T,107:39,109:C,110:A,111:S,112:M,113:O,114:D},{22:ct,24:ut,26:lt,38:ht,39:168,42:ft,46:Y,61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,39:170,42:ft,46:Y,49:[1,169],61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,39:171,42:ft,46:Y,61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,39:172,42:ft,46:Y,61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,39:173,42:ft,46:Y,61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,39:174,42:ft,46:Y,61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,39:175,42:ft,46:Y,59:[1,176],61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,39:177,42:ft,46:Y,61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,39:178,42:ft,46:Y,61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,39:179,42:ft,46:Y,61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},t(rt,[2,145]),t(Dt,[2,3]),{8:180,15:N},{15:[2,7]},t(a,[2,28]),t(at,[2,33]),t(j,[2,47],{30:181,22:B}),t(st,[2,69],{22:[1,182]}),{22:[1,183]},{22:ct,24:ut,26:lt,38:ht,39:184,42:ft,46:Y,61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,42:ft,46:Y,61:dt,69:pt,70:[1,185],72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},t(Ot,[2,76]),t(Ot,[2,78]),t(Ot,[2,134]),t(Ot,[2,135]),t(Ot,[2,136]),t(Ot,[2,137]),t(Ot,[2,138]),t(Ot,[2,139]),t(Ot,[2,140]),t(Ot,[2,141]),t(Ot,[2,142]),t(Ot,[2,143]),t(Ot,[2,79]),t(Ot,[2,80]),t(Ot,[2,81]),t(Ot,[2,82]),t(Ot,[2,83]),t(Ot,[2,84]),t(Ot,[2,85]),t(Ot,[2,86]),t(Ot,[2,87]),t(Ot,[2,88]),t(Ot,[2,89]),{9:188,20:F,21:P,22:ct,23:I,24:ut,26:lt,38:ht,40:[1,187],42:ft,46:Y,61:dt,69:pt,72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{18:18,19:19,20:s,21:c,22:u,23:l,32:24,33:25,34:26,35:27,36:28,37:29,38:h,42:[1,189],44:31,45:37,46:f,48:38,75:d,76:p,77:g,78:y,79:v,80:m,94:b,95:x,98:_,99:k,100:w,102:E,103:T,107:39,109:C,110:A,111:S,112:M,113:O,114:D},{22:B,30:190},{22:[1,191],26:R,46:Y,80:z,94:U,95:$,98:W,99:H,100:V,102:G,103:q,106:89,108:165,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:[1,192]},{22:[1,193]},{22:[1,194],95:[1,195]},t(Nt,[2,117]),{22:[1,196]},{22:[1,197],26:R,46:Y,80:z,94:U,95:$,98:W,99:H,100:V,102:G,103:q,106:89,108:165,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:[1,198],26:R,46:Y,80:z,94:U,95:$,98:W,99:H,100:V,102:G,103:q,106:89,108:165,109:X,110:Z,111:J,112:K,113:Q,114:tt},{73:[1,199]},t(it,[2,97],{22:[1,200]}),{73:[1,201],90:[1,202]},{73:[1,203]},t(Mt,[2,147]),{73:[1,204],90:[1,205]},t(et,[2,53],{107:116,46:f,80:m,94:b,95:x,98:_,99:k,100:w,102:E,103:T,109:C,110:A,111:S,112:M,113:O,114:D}),{22:ct,24:ut,26:lt,38:ht,41:[1,206],42:ft,46:Y,61:dt,69:pt,72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,39:207,42:ft,46:Y,61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,42:ft,46:Y,50:[1,208],61:dt,69:pt,72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,42:ft,46:Y,52:[1,209],61:dt,69:pt,72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,42:ft,46:Y,54:[1,210],61:dt,69:pt,72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,42:ft,46:Y,56:[1,211],61:dt,69:pt,72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,42:ft,46:Y,58:[1,212],61:dt,69:pt,72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,42:ft,46:Y,60:[1,213],61:dt,69:pt,72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,39:214,42:ft,46:Y,61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,41:[1,215],42:ft,46:Y,61:dt,69:pt,72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,42:ft,46:Y,61:dt,63:[1,216],65:[1,217],69:pt,72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{22:ct,24:ut,26:lt,38:ht,42:ft,46:Y,61:dt,63:[1,219],65:[1,218],69:pt,72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{9:220,20:F,21:P,23:I},t(j,[2,48],{46:St}),t(st,[2,71]),t(st,[2,70]),{22:ct,24:ut,26:lt,38:ht,42:ft,46:Y,61:dt,69:pt,71:[1,221],72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},t(st,[2,73]),t(Ot,[2,77]),{22:ct,24:ut,26:lt,38:ht,39:222,42:ft,46:Y,61:dt,69:pt,72:127,73:gt,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},t(At,o,{17:223}),t(L,[2,43]),{45:224,46:f,48:38,80:m,94:b,95:x,98:_,99:k,100:w,102:E,103:T,107:39,109:C,110:A,111:S,112:M,113:O,114:D},{22:Bt,75:Lt,85:225,91:Ft,94:Pt,96:226,97:227,98:It,99:jt,100:Rt,101:Yt,102:zt,103:Ut,104:$t},{22:Bt,75:Lt,85:239,91:Ft,94:Pt,96:226,97:227,98:It,99:jt,100:Rt,101:Yt,102:zt,103:Ut,104:$t},{22:Bt,75:Lt,85:240,91:Ft,93:[1,241],94:Pt,96:226,97:227,98:It,99:jt,100:Rt,101:Yt,102:zt,103:Ut,104:$t},{22:Bt,75:Lt,85:242,91:Ft,93:[1,243],94:Pt,96:226,97:227,98:It,99:jt,100:Rt,101:Yt,102:zt,103:Ut,104:$t},{94:[1,244]},{22:Bt,75:Lt,85:245,91:Ft,94:Pt,96:226,97:227,98:It,99:jt,100:Rt,101:Yt,102:zt,103:Ut,104:$t},{22:Bt,75:Lt,85:246,91:Ft,94:Pt,96:226,97:227,98:It,99:jt,100:Rt,101:Yt,102:zt,103:Ut,104:$t},{26:R,46:Y,80:z,86:247,94:U,95:$,98:W,99:H,100:V,102:G,103:q,106:89,108:87,109:X,110:Z,111:J,112:K,113:Q,114:tt},t(it,[2,96]),{73:[1,248]},t(it,[2,100],{22:[1,249]}),t(it,[2,101]),t(it,[2,104]),t(it,[2,106],{22:[1,250]}),t(it,[2,107]),t(nt,[2,54]),{22:ct,24:ut,26:lt,38:ht,42:ft,46:Y,50:[1,251],61:dt,69:pt,72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},t(nt,[2,60]),t(nt,[2,56]),t(nt,[2,57]),t(nt,[2,58]),t(nt,[2,59]),t(nt,[2,61]),{22:ct,24:ut,26:lt,38:ht,42:ft,46:Y,60:[1,252],61:dt,69:pt,72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},t(nt,[2,63]),t(nt,[2,64]),t(nt,[2,66]),t(nt,[2,65]),t(nt,[2,67]),t(Dt,[2,4]),t([22,46,80,94,95,98,99,100,102,103,109,110,111,112,113,114],[2,75]),{22:ct,24:ut,26:lt,38:ht,41:[1,253],42:ft,46:Y,61:dt,69:pt,72:186,74:138,75:yt,76:vt,77:mt,78:bt,79:xt,80:_t,81:kt,83:129,84:wt,94:U,95:$,98:W,99:H,100:Et,102:G,103:q,104:Tt,105:Ct,106:135,109:X,110:Z,111:J,112:K,113:Q,114:tt},{18:18,19:19,20:s,21:c,22:u,23:l,32:24,33:25,34:26,35:27,36:28,37:29,38:h,42:[1,254],44:31,45:37,46:f,48:38,75:d,76:p,77:g,78:y,79:v,80:m,94:b,95:x,98:_,99:k,100:w,102:E,103:T,107:39,109:C,110:A,111:S,112:M,113:O,114:D},t(et,[2,52]),t(it,[2,109],{95:Wt}),t(Ht,[2,119],{97:256,22:Bt,75:Lt,91:Ft,94:Pt,98:It,99:jt,100:Rt,101:Yt,102:zt,103:Ut,104:$t}),t(Vt,[2,121]),t(Vt,[2,123]),t(Vt,[2,124]),t(Vt,[2,125]),t(Vt,[2,126]),t(Vt,[2,127]),t(Vt,[2,128]),t(Vt,[2,129]),t(Vt,[2,130]),t(Vt,[2,131]),t(Vt,[2,132]),t(Vt,[2,133]),t(it,[2,110],{95:Wt}),t(it,[2,111],{95:Wt}),{22:[1,257]},t(it,[2,112],{95:Wt}),{22:[1,258]},t(Nt,[2,118]),t(it,[2,92],{95:Wt}),t(it,[2,93],{95:Wt}),t(it,[2,94],{106:89,108:165,26:R,46:Y,80:z,94:U,95:$,98:W,99:H,100:V,102:G,103:q,109:X,110:Z,111:J,112:K,113:Q,114:tt}),t(it,[2,98]),{90:[1,259]},{90:[1,260]},{50:[1,261]},{60:[1,262]},{9:263,20:F,21:P,23:I},t(L,[2,42]),{22:Bt,75:Lt,91:Ft,94:Pt,96:264,97:227,98:It,99:jt,100:Rt,101:Yt,102:zt,103:Ut,104:$t},t(Vt,[2,122]),{26:R,46:Y,80:z,86:265,94:U,95:$,98:W,99:H,100:V,102:G,103:q,106:89,108:87,109:X,110:Z,111:J,112:K,113:Q,114:tt},{26:R,46:Y,80:z,86:266,94:U,95:$,98:W,99:H,100:V,102:G,103:q,106:89,108:87,109:X,110:Z,111:J,112:K,113:Q,114:tt},t(it,[2,102]),t(it,[2,108]),t(nt,[2,55]),t(nt,[2,62]),t(At,o,{17:267}),t(Ht,[2,120],{97:256,22:Bt,75:Lt,91:Ft,94:Pt,98:It,99:jt,100:Rt,101:Yt,102:zt,103:Ut,104:$t}),t(it,[2,115],{106:89,108:165,22:[1,268],26:R,46:Y,80:z,94:U,95:$,98:W,99:H,100:V,102:G,103:q,109:X,110:Z,111:J,112:K,113:Q,114:tt}),t(it,[2,116],{106:89,108:165,22:[1,269],26:R,46:Y,80:z,94:U,95:$,98:W,99:H,100:V,102:G,103:q,109:X,110:Z,111:J,112:K,113:Q,114:tt}),{18:18,19:19,20:s,21:c,22:u,23:l,32:24,33:25,34:26,35:27,36:28,37:29,38:h,42:[1,270],44:31,45:37,46:f,48:38,75:d,76:p,77:g,78:y,79:v,80:m,94:b,95:x,98:_,99:k,100:w,102:E,103:T,107:39,109:C,110:A,111:S,112:M,113:O,114:D},{22:Bt,75:Lt,85:271,91:Ft,94:Pt,96:226,97:227,98:It,99:jt,100:Rt,101:Yt,102:zt,103:Ut,104:$t},{22:Bt,75:Lt,85:272,91:Ft,94:Pt,96:226,97:227,98:It,99:jt,100:Rt,101:Yt,102:zt,103:Ut,104:$t},t(L,[2,41]),t(it,[2,113],{95:Wt}),t(it,[2,114],{95:Wt})],defaultActions:{2:[2,1],9:[2,5],10:[2,2],119:[2,7]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},qt={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;a<i.length;a++)if((n=this._input.match(this.rules[i[a]]))&&(!e||n[0].length>e[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),12;case 1:return this.begin("type_directive"),13;case 2:return this.popState(),this.begin("arg_directive"),10;case 3:return this.popState(),this.popState(),15;case 4:return 14;case 5:case 6:break;case 7:this.begin("string");break;case 8:this.popState();break;case 9:return"STR";case 10:return 75;case 11:return 84;case 12:return 76;case 13:return 93;case 14:return 77;case 15:return 78;case 16:this.begin("href");break;case 17:this.popState();break;case 18:return 89;case 19:this.begin("callbackname");break;case 20:this.popState();break;case 21:this.popState(),this.begin("callbackargs");break;case 22:return 87;case 23:this.popState();break;case 24:return 88;case 25:this.begin("click");break;case 26:this.popState();break;case 27:return 79;case 28:case 29:return t.lex.firstGraph()&&this.begin("dir"),24;case 30:return 38;case 31:return 42;case 32:case 33:case 34:case 35:return 90;case 36:return this.popState(),25;case 37:case 38:case 39:case 40:case 41:case 42:case 43:case 44:case 45:case 46:return this.popState(),26;case 47:return 94;case 48:return 102;case 49:return 47;case 50:return 99;case 51:return 46;case 52:return 20;case 53:return 95;case 54:return 113;case 55:case 56:case 57:return 70;case 58:case 59:case 60:return 69;case 61:return 51;case 62:return 52;case 63:return 53;case 64:return 54;case 65:return 55;case 66:return 56;case 67:return 57;case 68:return 58;case 69:return 100;case 70:return 103;case 71:return 114;case 72:return 111;case 73:return 104;case 74:case 75:return 112;case 76:return 105;case 77:return 61;case 78:return 81;case 79:return"SEP";case 80:return 80;case 81:return 98;case 82:return 63;case 83:return 62;case 84:return 65;case 85:return 64;case 86:return 109;case 87:return 110;case 88:return 71;case 89:return 49;case 90:return 50;case 91:return 40;case 92:return 41;case 93:return 59;case 94:return 60;case 95:return 120;case 96:return 21;case 97:return 22;case 98:return 23}},rules:[/^(?:%%\{)/,/^(?:((?:(?!\}%%)[^:.])*))/,/^(?::)/,/^(?:\}%%)/,/^(?:((?:(?!\}%%).|\n)*))/,/^(?:%%(?!\{)[^\n]*)/,/^(?:[^\}]%%[^\n]*)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:style\b)/,/^(?:default\b)/,/^(?:linkStyle\b)/,/^(?:interpolate\b)/,/^(?:classDef\b)/,/^(?:class\b)/,/^(?:href[\s]+["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:click[\s]+)/,/^(?:[\s\n])/,/^(?:[^\s\n]*)/,/^(?:graph\b)/,/^(?:flowchart\b)/,/^(?:subgraph\b)/,/^(?:end\b\s*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:(\r?\n)*\s*\n)/,/^(?:\s*LR\b)/,/^(?:\s*RL\b)/,/^(?:\s*TB\b)/,/^(?:\s*BT\b)/,/^(?:\s*TD\b)/,/^(?:\s*BR\b)/,/^(?:\s*<)/,/^(?:\s*>)/,/^(?:\s*\^)/,/^(?:\s*v\b)/,/^(?:[0-9]+)/,/^(?:#)/,/^(?::::)/,/^(?::)/,/^(?:&)/,/^(?:;)/,/^(?:,)/,/^(?:\*)/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?:\s*[xo<]?--\s*)/,/^(?:\s*[xo<]?==\s*)/,/^(?:\s*[xo<]?-\.\s*)/,/^(?:\(-)/,/^(?:-\))/,/^(?:\(\[)/,/^(?:\]\))/,/^(?:\[\[)/,/^(?:\]\])/,/^(?:\[\()/,/^(?:\)\])/,/^(?:-)/,/^(?:\.)/,/^(?:[\_])/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:<)/,/^(?:>)/,/^(?:\^)/,/^(?:\\\|)/,/^(?:v\b)/,/^(?:[A-Za-z]+)/,/^(?:\\\])/,/^(?:\[\/)/,/^(?:\/\])/,/^(?:\[\\)/,/^(?:[!"#$%&'*+,-.`?\\_/])/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\|)/,/^(?:\()/,/^(?:\))/,/^(?:\[)/,/^(?:\])/,/^(?:\{)/,/^(?:\})/,/^(?:")/,/^(?:(\r?\n)+)/,/^(?:\s)/,/^(?:$)/],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callbackargs:{rules:[23,24],inclusive:!1},callbackname:{rules:[20,21,22],inclusive:!1},href:{rules:[17,18],inclusive:!1},click:{rules:[26,27],inclusive:!1},vertex:{rules:[],inclusive:!1},dir:{rules:[36,37,38,39,40,41,42,43,44,45,46],inclusive:!1},string:{rules:[8,9],inclusive:!1},INITIAL:{rules:[0,5,6,7,10,11,12,13,14,15,16,19,25,28,29,30,31,32,33,34,35,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98],inclusive:!0}}};function Xt(){this.yy={}}return Gt.lexer=qt,Xt.prototype=Gt,Gt.Parser=Xt,new Xt}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,3],n=[1,5],r=[7,9,11,12,13,14,15,16,17,18,20,27,32],i=[1,15],a=[1,16],o=[1,17],s=[1,18],c=[1,19],u=[1,20],l=[1,21],h=[1,23],f=[1,25],d=[1,28],p=[5,7,9,11,12,13,14,15,16,17,18,20,27,32],g={trace:function(){},yy:{},symbols_:{error:2,start:3,directive:4,gantt:5,document:6,EOF:7,line:8,SPACE:9,statement:10,NL:11,dateFormat:12,inclusiveEndDates:13,axisFormat:14,excludes:15,todayMarker:16,title:17,section:18,clickStatement:19,taskTxt:20,taskData:21,openDirective:22,typeDirective:23,closeDirective:24,":":25,argDirective:26,click:27,callbackname:28,callbackargs:29,href:30,clickStatementDebug:31,open_directive:32,type_directive:33,arg_directive:34,close_directive:35,$accept:0,$end:1},terminals_:{2:"error",5:"gantt",7:"EOF",9:"SPACE",11:"NL",12:"dateFormat",13:"inclusiveEndDates",14:"axisFormat",15:"excludes",16:"todayMarker",17:"title",18:"section",20:"taskTxt",21:"taskData",25:":",27:"click",28:"callbackname",29:"callbackargs",30:"href",32:"open_directive",33:"type_directive",34:"arg_directive",35:"close_directive"},productions_:[0,[3,2],[3,3],[6,0],[6,2],[8,2],[8,1],[8,1],[8,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,2],[10,1],[4,4],[4,6],[19,2],[19,3],[19,3],[19,4],[19,3],[19,4],[19,2],[31,2],[31,3],[31,3],[31,4],[31,3],[31,4],[31,2],[22,1],[23,1],[26,1],[24,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 2:return a[s-1];case 3:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 7:case 8:this.$=[];break;case 9:r.setDateFormat(a[s].substr(11)),this.$=a[s].substr(11);break;case 10:r.enableInclusiveEndDates(),this.$=a[s].substr(18);break;case 11:r.setAxisFormat(a[s].substr(11)),this.$=a[s].substr(11);break;case 12:r.setExcludes(a[s].substr(9)),this.$=a[s].substr(9);break;case 13:r.setTodayMarker(a[s].substr(12)),this.$=a[s].substr(12);break;case 14:r.setTitle(a[s].substr(6)),this.$=a[s].substr(6);break;case 15:r.addSection(a[s].substr(8)),this.$=a[s].substr(8);break;case 17:r.addTask(a[s-1],a[s]),this.$="task";break;case 21:this.$=a[s-1],r.setClickEvent(a[s-1],a[s],null);break;case 22:this.$=a[s-2],r.setClickEvent(a[s-2],a[s-1],a[s]);break;case 23:this.$=a[s-2],r.setClickEvent(a[s-2],a[s-1],null),r.setLink(a[s-2],a[s]);break;case 24:this.$=a[s-3],r.setClickEvent(a[s-3],a[s-2],a[s-1]),r.setLink(a[s-3],a[s]);break;case 25:this.$=a[s-2],r.setClickEvent(a[s-2],a[s],null),r.setLink(a[s-2],a[s-1]);break;case 26:this.$=a[s-3],r.setClickEvent(a[s-3],a[s-1],a[s]),r.setLink(a[s-3],a[s-2]);break;case 27:this.$=a[s-1],r.setLink(a[s-1],a[s]);break;case 28:case 34:this.$=a[s-1]+" "+a[s];break;case 29:case 30:case 32:this.$=a[s-2]+" "+a[s-1]+" "+a[s];break;case 31:case 33:this.$=a[s-3]+" "+a[s-2]+" "+a[s-1]+" "+a[s];break;case 35:r.parseDirective("%%{","open_directive");break;case 36:r.parseDirective(a[s],"type_directive");break;case 37:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 38:r.parseDirective("}%%","close_directive","gantt")}},table:[{3:1,4:2,5:e,22:4,32:n},{1:[3]},{3:6,4:2,5:e,22:4,32:n},t(r,[2,3],{6:7}),{23:8,33:[1,9]},{33:[2,35]},{1:[2,1]},{4:24,7:[1,10],8:11,9:[1,12],10:13,11:[1,14],12:i,13:a,14:o,15:s,16:c,17:u,18:l,19:22,20:h,22:4,27:f,32:n},{24:26,25:[1,27],35:d},t([25,35],[2,36]),t(r,[2,8],{1:[2,2]}),t(r,[2,4]),{4:24,10:29,12:i,13:a,14:o,15:s,16:c,17:u,18:l,19:22,20:h,22:4,27:f,32:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,9]),t(r,[2,10]),t(r,[2,11]),t(r,[2,12]),t(r,[2,13]),t(r,[2,14]),t(r,[2,15]),t(r,[2,16]),{21:[1,30]},t(r,[2,18]),{28:[1,31],30:[1,32]},{11:[1,33]},{26:34,34:[1,35]},{11:[2,38]},t(r,[2,5]),t(r,[2,17]),t(r,[2,21],{29:[1,36],30:[1,37]}),t(r,[2,27],{28:[1,38]}),t(p,[2,19]),{24:39,35:d},{35:[2,37]},t(r,[2,22],{30:[1,40]}),t(r,[2,23]),t(r,[2,25],{29:[1,41]}),{11:[1,42]},t(r,[2,24]),t(r,[2,26]),t(p,[2,20])],defaultActions:{5:[2,35],6:[2,1],28:[2,38],35:[2,37]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},y={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;a<i.length;a++)if((n=this._input.match(this.rules[i[a]]))&&(!e||n[0].length>e[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),32;case 1:return this.begin("type_directive"),33;case 2:return this.popState(),this.begin("arg_directive"),25;case 3:return this.popState(),this.popState(),35;case 4:return 34;case 5:case 6:case 7:break;case 8:return 11;case 9:case 10:case 11:break;case 12:this.begin("href");break;case 13:this.popState();break;case 14:return 30;case 15:this.begin("callbackname");break;case 16:this.popState();break;case 17:this.popState(),this.begin("callbackargs");break;case 18:return 28;case 19:this.popState();break;case 20:return 29;case 21:this.begin("click");break;case 22:this.popState();break;case 23:return 27;case 24:return 5;case 25:return 12;case 26:return 13;case 27:return 14;case 28:return 15;case 29:return 16;case 30:return"date";case 31:return 17;case 32:return 18;case 33:return 20;case 34:return 21;case 35:return 25;case 36:return 7;case 37:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)*[^\n]*)/i,/^(?:[^\}]%%*[^\n]*)/i,/^(?:%%*[^\n]*[\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:href[\s]+["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:call[\s]+)/i,/^(?:\([\s]*\))/i,/^(?:\()/i,/^(?:[^(]*)/i,/^(?:\))/i,/^(?:[^)]*)/i,/^(?:click[\s]+)/i,/^(?:[\s\n])/i,/^(?:[^\s\n]*)/i,/^(?:gantt\b)/i,/^(?:dateFormat\s[^#\n;]+)/i,/^(?:inclusiveEndDates\b)/i,/^(?:axisFormat\s[^#\n;]+)/i,/^(?:excludes\s[^#\n;]+)/i,/^(?:todayMarker\s[^\n;]+)/i,/^(?:\d\d\d\d-\d\d-\d\d\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callbackargs:{rules:[19,20],inclusive:!1},callbackname:{rules:[16,17,18],inclusive:!1},href:{rules:[13,14],inclusive:!1},click:{rules:[22,23],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,15,21,24,25,26,27,28,29,30,31,32,33,34,35,36,37],inclusive:!0}}};function v(){this.yy={}}return g.lexer=y,v.prototype=g,g.Parser=v,new v}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,5],r=[6,9,11,17,18,19,21],i=[1,15],a=[1,16],o=[1,17],s=[1,21],c=[4,6,9,11,17,18,19,21],u={trace:function(){},yy:{},symbols_:{error:2,start:3,journey:4,document:5,EOF:6,directive:7,line:8,SPACE:9,statement:10,NEWLINE:11,openDirective:12,typeDirective:13,closeDirective:14,":":15,argDirective:16,title:17,section:18,taskName:19,taskData:20,open_directive:21,type_directive:22,arg_directive:23,close_directive:24,$accept:0,$end:1},terminals_:{2:"error",4:"journey",6:"EOF",9:"SPACE",11:"NEWLINE",15:":",17:"title",18:"section",19:"taskName",20:"taskData",21:"open_directive",22:"type_directive",23:"arg_directive",24:"close_directive"},productions_:[0,[3,3],[3,2],[5,0],[5,2],[8,2],[8,1],[8,1],[8,1],[7,4],[7,6],[10,1],[10,1],[10,2],[10,1],[12,1],[13,1],[16,1],[14,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:return a[s-1];case 3:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 7:case 8:this.$=[];break;case 11:r.setTitle(a[s].substr(6)),this.$=a[s].substr(6);break;case 12:r.addSection(a[s].substr(8)),this.$=a[s].substr(8);break;case 13:r.addTask(a[s-1],a[s]),this.$="task";break;case 15:r.parseDirective("%%{","open_directive");break;case 16:r.parseDirective(a[s],"type_directive");break;case 17:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 18:r.parseDirective("}%%","close_directive","journey")}},table:[{3:1,4:e,7:3,12:4,21:n},{1:[3]},t(r,[2,3],{5:6}),{3:7,4:e,7:3,12:4,21:n},{13:8,22:[1,9]},{22:[2,15]},{6:[1,10],7:18,8:11,9:[1,12],10:13,11:[1,14],12:4,17:i,18:a,19:o,21:n},{1:[2,2]},{14:19,15:[1,20],24:s},t([15,24],[2,16]),t(r,[2,8],{1:[2,1]}),t(r,[2,4]),{7:18,10:22,12:4,17:i,18:a,19:o,21:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,11]),t(r,[2,12]),{20:[1,23]},t(r,[2,14]),{11:[1,24]},{16:25,23:[1,26]},{11:[2,18]},t(r,[2,5]),t(r,[2,13]),t(c,[2,9]),{14:27,24:s},{24:[2,17]},{11:[1,28]},t(c,[2,10])],defaultActions:{5:[2,15],7:[2,2],21:[2,18],26:[2,17]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},l={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;a<i.length;a++)if((n=this._input.match(this.rules[i[a]]))&&(!e||n[0].length>e[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),21;case 1:return this.begin("type_directive"),22;case 2:return this.popState(),this.begin("arg_directive"),15;case 3:return this.popState(),this.popState(),24;case 4:return 23;case 5:case 6:break;case 7:return 11;case 8:case 9:break;case 10:return 4;case 11:return 17;case 12:return 18;case 13:return 19;case 14:return 20;case 15:return 15;case 16:return 6;case 17:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:journey\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{open_directive:{rules:[1],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,13,14,15,16,17],inclusive:!0}}};function h(){this.yy={}}return u.lexer=l,h.prototype=u,u.Parser=h,new h}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(15);e.default=function(t,e){return r.default.lang.round(i.default.parse(t)[e])}},function(t,e,n){var r=n(112),i=n(82),a=n(24);t.exports=function(t){return a(t)?r(t):i(t)}},function(t,e,n){var r;if(!r)try{r=n(0)}catch(t){}r||(r=window.d3),t.exports=r},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(15);e.default=function(t,e,n){var a=i.default.parse(t),o=a[e],s=r.default.channel.clamp[e](o+n);return o!==s&&(a[e]=s),i.default.stringify(a)}},function(t,e,n){var r=n(210),i=n(216);t.exports=function(t,e){var n=i(t,e);return r(n)?n:void 0}},function(t,e,n){var r=n(38),i=n(212),a=n(213),o=r?r.toStringTag:void 0;t.exports=function(t){return null==t?void 0===t?"[object Undefined]":"[object Null]":o&&o in Object(t)?i(t):a(t)}},function(t,e){t.exports=function(t){return t}},function(t,e){t.exports=function(t,e){return t===e||t!=t&&e!=e}},function(t,e,n){var r=n(34),i=n(11);t.exports=function(t){if(!i(t))return!1;var e=r(t);return"[object Function]"==e||"[object GeneratorFunction]"==e||"[object AsyncFunction]"==e||"[object Proxy]"==e}},function(t,e,n){var r=n(16).Symbol;t.exports=r},function(t,e,n){(function(t){var r=n(16),i=n(232),a=e&&!e.nodeType&&e,o=a&&"object"==typeof t&&t&&!t.nodeType&&t,s=o&&o.exports===a?r.Buffer:void 0,c=(s?s.isBuffer:void 0)||i;t.exports=c}).call(this,n(7)(t))},function(t,e,n){var r=n(112),i=n(236),a=n(24);t.exports=function(t){return a(t)?r(t,!0):i(t)}},function(t,e,n){var r=n(241),i=n(77),a=n(242),o=n(121),s=n(243),c=n(34),u=n(110),l=u(r),h=u(i),f=u(a),d=u(o),p=u(s),g=c;(r&&"[object DataView]"!=g(new r(new ArrayBuffer(1)))||i&&"[object Map]"!=g(new i)||a&&"[object Promise]"!=g(a.resolve())||o&&"[object Set]"!=g(new o)||s&&"[object WeakMap]"!=g(new s))&&(g=function(t){var e=c(t),n="[object Object]"==e?t.constructor:void 0,r=n?u(n):"";if(r)switch(r){case l:return"[object DataView]";case h:return"[object Map]";case f:return"[object Promise]";case d:return"[object Set]";case p:return"[object WeakMap]"}return e}),t.exports=g},function(t,e,n){var r=n(34),i=n(21);t.exports=function(t){return"symbol"==typeof t||i(t)&&"[object Symbol]"==r(t)}},function(t,e,n){var r;try{r={defaults:n(154),each:n(87),isFunction:n(37),isPlainObject:n(158),pick:n(161),has:n(93),range:n(162),uniqueId:n(163)}}catch(t){}r||(r=window._),t.exports=r},function(t){t.exports=JSON.parse('{"name":"mermaid","version":"8.8.4","description":"Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.","main":"dist/mermaid.core.js","keywords":["diagram","markdown","flowchart","sequence diagram","gantt","class diagram","git graph"],"scripts":{"build:development":"webpack --progress --colors","build:production":"yarn build:development -p --config webpack.config.prod.babel.js","build":"yarn build:development && yarn build:production","postbuild":"documentation build src/mermaidAPI.js src/config.js --shallow -f md --markdown-toc false > docs/Setup.md","build:watch":"yarn build --watch","minify":"minify ./dist/mermaid.js > ./dist/mermaid.min.js","release":"yarn build","lint":"eslint src","e2e:depr":"yarn lint && jest e2e --config e2e/jest.config.js","cypress":"percy exec -- cypress run","e2e":"start-server-and-test dev http://localhost:9000/ cypress","e2e-upd":"yarn lint && jest e2e -u --config e2e/jest.config.js","dev":"webpack-dev-server --config webpack.config.e2e.js","test":"yarn lint && jest src/.*","test:watch":"jest --watch src","prepublishOnly":"yarn build && yarn test","prepare":"yarn build"},"repository":{"type":"git","url":"https://github.com/knsv/mermaid"},"author":"Knut Sveidqvist","license":"MIT","standard":{"ignore":["**/parser/*.js","dist/**/*.js","cypress/**/*.js"],"globals":["page"]},"dependencies":{"@braintree/sanitize-url":"^3.1.0","d3":"^5.7.0","dagre":"^0.8.4","dagre-d3":"^0.6.4","entity-decode":"^2.0.2","graphlib":"^2.1.7","he":"^1.2.0","khroma":"^1.1.0","minify":"^4.1.1","moment-mini":"^2.22.1","stylis":"^3.5.2"},"devDependencies":{"@babel/core":"^7.2.2","@babel/preset-env":"^7.8.4","@babel/register":"^7.0.0","@percy/cypress":"*","babel-core":"7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^24.9.0","babel-loader":"^8.0.4","coveralls":"^3.0.2","css-loader":"^2.0.1","css-to-string-loader":"^0.1.3","cypress":"4.0.1","documentation":"^12.0.1","eslint":"^6.3.0","eslint-config-prettier":"^6.3.0","eslint-plugin-prettier":"^3.1.0","husky":"^1.2.1","identity-obj-proxy":"^3.0.0","jest":"^24.9.0","jison":"^0.4.18","moment":"^2.23.0","node-sass":"^4.12.0","prettier":"^1.18.2","puppeteer":"^1.17.0","sass-loader":"^7.1.0","start-server-and-test":"^1.10.6","terser-webpack-plugin":"^2.2.2","webpack":"^4.41.2","webpack-bundle-analyzer":"^3.7.0","webpack-cli":"^3.1.2","webpack-dev-server":"^3.4.1","webpack-node-externals":"^1.7.2","yarn-upgrade-all":"^0.5.0"},"files":["dist"],"yarn-upgrade-all":{"ignore":["babel-core"]},"sideEffects":["**/*.css","**/*.scss"],"husky":{"hooks":{"pre-push":"yarn test"}}}')},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=new(n(176).default)({r:0,g:0,b:0,a:0},"transparent");e.default=r},function(t,e,n){var r=n(58),i=n(59);t.exports=function(t,e,n,a){var o=!n;n||(n={});for(var s=-1,c=e.length;++s<c;){var u=e[s],l=a?a(n[u],t[u],u,n,t):void 0;void 0===l&&(l=t[u]),o?i(n,u,l):r(n,u,l)}return n}},function(t,e,n){var r=n(231),i=n(21),a=Object.prototype,o=a.hasOwnProperty,s=a.propertyIsEnumerable,c=r(function(){return arguments}())?r:function(t){return i(t)&&o.call(t,"callee")&&!s.call(t,"callee")};t.exports=c},function(t,e,n){var r=n(233),i=n(61),a=n(81),o=a&&a.isTypedArray,s=o?i(o):r;t.exports=s},function(t,e,n){var r=n(42);t.exports=function(t){if("string"==typeof t||r(t))return t;var e=t+"";return"0"==e&&1/t==-1/0?"-0":e}},function(t,e,n){var r=n(12);t.exports=function(t,e){var n=t.append("foreignObject").attr("width","100000"),i=n.append("xhtml:div");i.attr("xmlns","http://www.w3.org/1999/xhtml");var a=e.label;switch(typeof a){case"function":i.insert(a);break;case"object":i.insert((function(){return a}));break;default:i.html(a)}r.applyStyle(i,e.labelStyle),i.style("display","inline-block"),i.style("white-space","nowrap");var o=i.node().getBoundingClientRect();return n.attr("width",o.width).attr("height",o.height),n}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(45),a=n(15),o=n(52);e.default=function(t,e,n,s){if(void 0===n&&(n=0),void 0===s&&(s=1),"number"!=typeof t)return o.default(t,{a:e});var c=i.default.set({r:r.default.channel.clamp.r(t),g:r.default.channel.clamp.g(e),b:r.default.channel.clamp.b(n),a:r.default.channel.clamp.a(s)});return a.default.stringify(c)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(15);e.default=function(t,e){var n=i.default.parse(t);for(var a in e)n[a]=r.default.channel.clamp[a](e[a]);return i.default.stringify(n)}},function(t,e,n){var r=n(54),i=n(205),a=n(206),o=n(207),s=n(208),c=n(209);function u(t){var e=this.__data__=new r(t);this.size=e.size}u.prototype.clear=i,u.prototype.delete=a,u.prototype.get=o,u.prototype.has=s,u.prototype.set=c,t.exports=u},function(t,e,n){var r=n(200),i=n(201),a=n(202),o=n(203),s=n(204);function c(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e<n;){var r=t[e];this.set(r[0],r[1])}}c.prototype.clear=r,c.prototype.delete=i,c.prototype.get=a,c.prototype.has=o,c.prototype.set=s,t.exports=c},function(t,e,n){var r=n(36);t.exports=function(t,e){for(var n=t.length;n--;)if(r(t[n][0],e))return n;return-1}},function(t,e,n){var r=n(33)(Object,"create");t.exports=r},function(t,e,n){var r=n(225);t.exports=function(t,e){var n=t.__data__;return r(e)?n["string"==typeof e?"string":"hash"]:n.map}},function(t,e,n){var r=n(59),i=n(36),a=Object.prototype.hasOwnProperty;t.exports=function(t,e,n){var o=t[e];a.call(t,e)&&i(o,n)&&(void 0!==n||e in t)||r(t,e,n)}},function(t,e,n){var r=n(111);t.exports=function(t,e,n){"__proto__"==e&&r?r(t,e,{configurable:!0,enumerable:!0,value:n,writable:!0}):t[e]=n}},function(t,e){var n=/^(?:0|[1-9]\d*)$/;t.exports=function(t,e){var r=typeof t;return!!(e=null==e?9007199254740991:e)&&("number"==r||"symbol"!=r&&n.test(t))&&t>-1&&t%1==0&&t<e}},function(t,e){t.exports=function(t){return function(e){return t(e)}}},function(t,e){var n=Object.prototype;t.exports=function(t){var e=t&&t.constructor;return t===("function"==typeof e&&e.prototype||n)}},function(t,e,n){var r=n(113)(Object.getPrototypeOf,Object);t.exports=r},function(t,e,n){var r=n(88),i=n(254)(r);t.exports=i},function(t,e,n){var r=n(5),i=n(92),a=n(268),o=n(135);t.exports=function(t,e){return r(t)?t:i(t,e)?[t]:a(o(t))}},function(t,e){t.exports=function(t,e){for(var n=-1,r=null==t?0:t.length,i=Array(r);++n<r;)i[n]=e(t[n],n,t);return i}},function(t,e,n){var r=n(35),i=n(143),a=n(144);t.exports=function(t,e){return a(i(t,e,r),t+"")}},function(t,e,n){var r=n(36),i=n(24),a=n(60),o=n(11);t.exports=function(t,e,n){if(!o(n))return!1;var s=typeof e;return!!("number"==s?i(n)&&a(e,n.length):"string"==s&&e in n)&&r(n[e],t)}},function(t,e,n){"use strict";var r=n(4);t.exports={longestPath:function(t){var e={};r.forEach(t.sources(),(function n(i){var a=t.node(i);if(r.has(e,i))return a.rank;e[i]=!0;var o=r.min(r.map(t.outEdges(i),(function(e){return n(e.w)-t.edge(e).minlen})));return o!==Number.POSITIVE_INFINITY&&null!=o||(o=0),a.rank=o}))},slack:function(t,e){return t.node(e.w).rank-t.node(e.v).rank-t.edge(e).minlen}}},function(t,e,n){"use strict";var r=/^(%20|\s)*(javascript|data)/im,i=/[^\x20-\x7E]/gim,a=/^([^:]+):/gm,o=[".","/"];t.exports={sanitizeUrl:function(t){if(!t)return"about:blank";var e,n,s=t.replace(i,"").trim();return function(t){return o.indexOf(t[0])>-1}(s)?s:(n=s.match(a))?(e=n[0],r.test(e)?"about:blank":s):"about:blank"}}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[2,3],n=[1,7],r=[7,12,15,17,19,20,21],i=[7,11,12,15,17,19,20,21],a=[2,20],o=[1,32],s={trace:function(){},yy:{},symbols_:{error:2,start:3,GG:4,":":5,document:6,EOF:7,DIR:8,options:9,body:10,OPT:11,NL:12,line:13,statement:14,COMMIT:15,commit_arg:16,BRANCH:17,ID:18,CHECKOUT:19,MERGE:20,RESET:21,reset_arg:22,STR:23,HEAD:24,reset_parents:25,CARET:26,$accept:0,$end:1},terminals_:{2:"error",4:"GG",5:":",7:"EOF",8:"DIR",11:"OPT",12:"NL",15:"COMMIT",17:"BRANCH",18:"ID",19:"CHECKOUT",20:"MERGE",21:"RESET",23:"STR",24:"HEAD",26:"CARET"},productions_:[0,[3,4],[3,5],[6,0],[6,2],[9,2],[9,1],[10,0],[10,2],[13,2],[13,1],[14,2],[14,2],[14,2],[14,2],[14,2],[16,0],[16,1],[22,2],[22,2],[25,0],[25,2]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:return a[s-1];case 2:return r.setDirection(a[s-3]),a[s-1];case 4:r.setOptions(a[s-1]),this.$=a[s];break;case 5:a[s-1]+=a[s],this.$=a[s-1];break;case 7:this.$=[];break;case 8:a[s-1].push(a[s]),this.$=a[s-1];break;case 9:this.$=a[s-1];break;case 11:r.commit(a[s]);break;case 12:r.branch(a[s]);break;case 13:r.checkout(a[s]);break;case 14:r.merge(a[s]);break;case 15:r.reset(a[s]);break;case 16:this.$="";break;case 17:this.$=a[s];break;case 18:this.$=a[s-1]+":"+a[s];break;case 19:this.$=a[s-1]+":"+r.count,r.count=0;break;case 20:r.count=0;break;case 21:r.count+=1}},table:[{3:1,4:[1,2]},{1:[3]},{5:[1,3],8:[1,4]},{6:5,7:e,9:6,12:n},{5:[1,8]},{7:[1,9]},t(r,[2,7],{10:10,11:[1,11]}),t(i,[2,6]),{6:12,7:e,9:6,12:n},{1:[2,1]},{7:[2,4],12:[1,15],13:13,14:14,15:[1,16],17:[1,17],19:[1,18],20:[1,19],21:[1,20]},t(i,[2,5]),{7:[1,21]},t(r,[2,8]),{12:[1,22]},t(r,[2,10]),{12:[2,16],16:23,23:[1,24]},{18:[1,25]},{18:[1,26]},{18:[1,27]},{18:[1,30],22:28,24:[1,29]},{1:[2,2]},t(r,[2,9]),{12:[2,11]},{12:[2,17]},{12:[2,12]},{12:[2,13]},{12:[2,14]},{12:[2,15]},{12:a,25:31,26:o},{12:a,25:33,26:o},{12:[2,18]},{12:a,25:34,26:o},{12:[2,19]},{12:[2,21]}],defaultActions:{9:[2,1],21:[2,2],23:[2,11],24:[2,17],25:[2,12],26:[2,13],27:[2,14],28:[2,15],31:[2,18],33:[2,19],34:[2,21]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},c={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;a<i.length;a++)if((n=this._input.match(this.rules[i[a]]))&&(!e||n[0].length>e[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 12;case 1:case 2:case 3:break;case 4:return 4;case 5:return 15;case 6:return 17;case 7:return 20;case 8:return 21;case 9:return 19;case 10:case 11:return 8;case 12:return 5;case 13:return 26;case 14:this.begin("options");break;case 15:this.popState();break;case 16:return 11;case 17:this.begin("string");break;case 18:this.popState();break;case 19:return 23;case 20:return 18;case 21:return 7}},rules:[/^(?:(\r?\n)+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:gitGraph\b)/i,/^(?:commit\b)/i,/^(?:branch\b)/i,/^(?:merge\b)/i,/^(?:reset\b)/i,/^(?:checkout\b)/i,/^(?:LR\b)/i,/^(?:BT\b)/i,/^(?::)/i,/^(?:\^)/i,/^(?:options\r?\n)/i,/^(?:end\r?\n)/i,/^(?:[^\n]+\r?\n)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[a-zA-Z][-_\.a-zA-Z0-9]*[-_a-zA-Z0-9])/i,/^(?:$)/i],conditions:{options:{rules:[15,16],inclusive:!1},string:{rules:[18,19],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,17,20,21],inclusive:!0}}};function u(){this.yy={}}return s.lexer=c,u.prototype=s,s.Parser=u,new u}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[6,9,10],n={trace:function(){},yy:{},symbols_:{error:2,start:3,info:4,document:5,EOF:6,line:7,statement:8,NL:9,showInfo:10,$accept:0,$end:1},terminals_:{2:"error",4:"info",6:"EOF",9:"NL",10:"showInfo"},productions_:[0,[3,3],[5,0],[5,2],[7,1],[7,1],[8,1]],performAction:function(t,e,n,r,i,a,o){a.length;switch(i){case 1:return r;case 4:break;case 6:r.setInfo(!0)}},table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:6,9:[1,7],10:[1,8]},{1:[2,1]},t(e,[2,3]),t(e,[2,4]),t(e,[2,5]),t(e,[2,6])],defaultActions:{4:[2,1]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},r={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;a<i.length;a++)if((n=this._input.match(this.rules[i[a]]))&&(!e||n[0].length>e[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 4;case 1:return 9;case 2:return"space";case 3:return 10;case 4:return 6;case 5:return"TXT"}},rules:[/^(?:info\b)/i,/^(?:[\s\n\r]+)/i,/^(?:[\s]+)/i,/^(?:showInfo\b)/i,/^(?:$)/i,/^(?:.)/i],conditions:{INITIAL:{rules:[0,1,2,3,4,5],inclusive:!0}}};function i(){this.yy={}}return n.lexer=r,i.prototype=n,n.Parser=i,new i}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,4],n=[1,5],r=[1,6],i=[1,7],a=[1,9],o=[1,10,12,19,20,21,22],s=[1,6,10,12,19,20,21,22],c=[19,20,21],u=[1,22],l=[6,19,20,21,22],h={trace:function(){},yy:{},symbols_:{error:2,start:3,eol:4,directive:5,PIE:6,document:7,line:8,statement:9,txt:10,value:11,title:12,title_value:13,openDirective:14,typeDirective:15,closeDirective:16,":":17,argDirective:18,NEWLINE:19,";":20,EOF:21,open_directive:22,type_directive:23,arg_directive:24,close_directive:25,$accept:0,$end:1},terminals_:{2:"error",6:"PIE",10:"txt",11:"value",12:"title",13:"title_value",17:":",19:"NEWLINE",20:";",21:"EOF",22:"open_directive",23:"type_directive",24:"arg_directive",25:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[7,0],[7,2],[8,2],[9,0],[9,2],[9,2],[9,1],[5,3],[5,5],[4,1],[4,1],[4,1],[14,1],[15,1],[18,1],[16,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 6:this.$=a[s-1];break;case 8:r.addSection(a[s-1],r.cleanupValue(a[s]));break;case 9:this.$=a[s].trim(),r.setTitle(this.$);break;case 16:r.parseDirective("%%{","open_directive");break;case 17:r.parseDirective(a[s],"type_directive");break;case 18:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 19:r.parseDirective("}%%","close_directive","pie")}},table:[{3:1,4:2,5:3,6:e,14:8,19:n,20:r,21:i,22:a},{1:[3]},{3:10,4:2,5:3,6:e,14:8,19:n,20:r,21:i,22:a},{3:11,4:2,5:3,6:e,14:8,19:n,20:r,21:i,22:a},t(o,[2,4],{7:12}),t(s,[2,13]),t(s,[2,14]),t(s,[2,15]),{15:13,23:[1,14]},{23:[2,16]},{1:[2,1]},{1:[2,2]},t(c,[2,7],{14:8,8:15,9:16,5:19,1:[2,3],10:[1,17],12:[1,18],22:a}),{16:20,17:[1,21],25:u},t([17,25],[2,17]),t(o,[2,5]),{4:23,19:n,20:r,21:i},{11:[1,24]},{13:[1,25]},t(c,[2,10]),t(l,[2,11]),{18:26,24:[1,27]},t(l,[2,19]),t(o,[2,6]),t(c,[2,8]),t(c,[2,9]),{16:28,25:u},{25:[2,18]},t(l,[2,12])],defaultActions:{9:[2,16],10:[2,1],11:[2,2],27:[2,18]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},f={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;a<i.length;a++)if((n=this._input.match(this.rules[i[a]]))&&(!e||n[0].length>e[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),22;case 1:return this.begin("type_directive"),23;case 2:return this.popState(),this.begin("arg_directive"),17;case 3:return this.popState(),this.popState(),25;case 4:return 24;case 5:case 6:break;case 7:return 19;case 8:case 9:break;case 10:return this.begin("title"),12;case 11:return this.popState(),"title_value";case 12:this.begin("string");break;case 13:this.popState();break;case 14:return"txt";case 15:return 6;case 16:return"value";case 17:return 21}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n\r]+)/i,/^(?:%%[^\n]*)/i,/^(?:[\s]+)/i,/^(?:title\b)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:pie\b)/i,/^(?::[\s]*[\d]+(?:\.[\d]+)?)/i,/^(?:$)/i],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},title:{rules:[11],inclusive:!1},string:{rules:[13,14],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,12,15,16,17],inclusive:!0}}};function d(){this.yy={}}return h.lexer=f,d.prototype=h,h.Parser=d,new d}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,5],r=[6,9,11,23,37],i=[1,17],a=[1,20],o=[1,25],s=[1,26],c=[1,27],u=[1,28],l=[1,37],h=[23,34,35],f=[4,6,9,11,23,37],d=[30,31,32,33],p=[22,27],g={trace:function(){},yy:{},symbols_:{error:2,start:3,ER_DIAGRAM:4,document:5,EOF:6,directive:7,line:8,SPACE:9,statement:10,NEWLINE:11,openDirective:12,typeDirective:13,closeDirective:14,":":15,argDirective:16,entityName:17,relSpec:18,role:19,BLOCK_START:20,attributes:21,BLOCK_STOP:22,ALPHANUM:23,attribute:24,attributeType:25,attributeName:26,ATTRIBUTE_WORD:27,cardinality:28,relType:29,ZERO_OR_ONE:30,ZERO_OR_MORE:31,ONE_OR_MORE:32,ONLY_ONE:33,NON_IDENTIFYING:34,IDENTIFYING:35,WORD:36,open_directive:37,type_directive:38,arg_directive:39,close_directive:40,$accept:0,$end:1},terminals_:{2:"error",4:"ER_DIAGRAM",6:"EOF",9:"SPACE",11:"NEWLINE",15:":",20:"BLOCK_START",22:"BLOCK_STOP",23:"ALPHANUM",27:"ATTRIBUTE_WORD",30:"ZERO_OR_ONE",31:"ZERO_OR_MORE",32:"ONE_OR_MORE",33:"ONLY_ONE",34:"NON_IDENTIFYING",35:"IDENTIFYING",36:"WORD",37:"open_directive",38:"type_directive",39:"arg_directive",40:"close_directive"},productions_:[0,[3,3],[3,2],[5,0],[5,2],[8,2],[8,1],[8,1],[8,1],[7,4],[7,6],[10,1],[10,5],[10,4],[10,3],[10,1],[17,1],[21,1],[21,2],[24,2],[25,1],[26,1],[18,3],[28,1],[28,1],[28,1],[28,1],[29,1],[29,1],[19,1],[19,1],[12,1],[13,1],[16,1],[14,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:break;case 3:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 7:case 8:this.$=[];break;case 12:r.addEntity(a[s-4]),r.addEntity(a[s-2]),r.addRelationship(a[s-4],a[s],a[s-2],a[s-3]);break;case 13:r.addEntity(a[s-3]),r.addAttributes(a[s-3],a[s-1]);break;case 14:r.addEntity(a[s-2]);break;case 15:r.addEntity(a[s]);break;case 16:this.$=a[s];break;case 17:this.$=[a[s]];break;case 18:a[s].push(a[s-1]),this.$=a[s];break;case 19:this.$={attributeType:a[s-1],attributeName:a[s]};break;case 20:case 21:this.$=a[s];break;case 22:this.$={cardA:a[s],relType:a[s-1],cardB:a[s-2]};break;case 23:this.$=r.Cardinality.ZERO_OR_ONE;break;case 24:this.$=r.Cardinality.ZERO_OR_MORE;break;case 25:this.$=r.Cardinality.ONE_OR_MORE;break;case 26:this.$=r.Cardinality.ONLY_ONE;break;case 27:this.$=r.Identification.NON_IDENTIFYING;break;case 28:this.$=r.Identification.IDENTIFYING;break;case 29:this.$=a[s].replace(/"/g,"");break;case 30:this.$=a[s];break;case 31:r.parseDirective("%%{","open_directive");break;case 32:r.parseDirective(a[s],"type_directive");break;case 33:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 34:r.parseDirective("}%%","close_directive","er")}},table:[{3:1,4:e,7:3,12:4,37:n},{1:[3]},t(r,[2,3],{5:6}),{3:7,4:e,7:3,12:4,37:n},{13:8,38:[1,9]},{38:[2,31]},{6:[1,10],7:15,8:11,9:[1,12],10:13,11:[1,14],12:4,17:16,23:i,37:n},{1:[2,2]},{14:18,15:[1,19],40:a},t([15,40],[2,32]),t(r,[2,8],{1:[2,1]}),t(r,[2,4]),{7:15,10:21,12:4,17:16,23:i,37:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,11]),t(r,[2,15],{18:22,28:24,20:[1,23],30:o,31:s,32:c,33:u}),t([6,9,11,15,20,23,30,31,32,33,37],[2,16]),{11:[1,29]},{16:30,39:[1,31]},{11:[2,34]},t(r,[2,5]),{17:32,23:i},{21:33,22:[1,34],24:35,25:36,27:l},{29:38,34:[1,39],35:[1,40]},t(h,[2,23]),t(h,[2,24]),t(h,[2,25]),t(h,[2,26]),t(f,[2,9]),{14:41,40:a},{40:[2,33]},{15:[1,42]},{22:[1,43]},t(r,[2,14]),{21:44,22:[2,17],24:35,25:36,27:l},{26:45,27:[1,46]},{27:[2,20]},{28:47,30:o,31:s,32:c,33:u},t(d,[2,27]),t(d,[2,28]),{11:[1,48]},{19:49,23:[1,51],36:[1,50]},t(r,[2,13]),{22:[2,18]},t(p,[2,19]),t(p,[2,21]),{23:[2,22]},t(f,[2,10]),t(r,[2,12]),t(r,[2,29]),t(r,[2,30])],defaultActions:{5:[2,31],7:[2,2],20:[2,34],31:[2,33],37:[2,20],44:[2,18],47:[2,22]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},y={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;a<i.length;a++)if((n=this._input.match(this.rules[i[a]]))&&(!e||n[0].length>e[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),37;case 1:return this.begin("type_directive"),38;case 2:return this.popState(),this.begin("arg_directive"),15;case 3:return this.popState(),this.popState(),40;case 4:return 39;case 5:case 6:break;case 7:return 11;case 8:break;case 9:return 9;case 10:return 36;case 11:return 4;case 12:return this.begin("block"),20;case 13:break;case 14:return 27;case 15:break;case 16:return this.popState(),22;case 17:return e.yytext[0];case 18:return 30;case 19:return 31;case 20:return 32;case 21:return 33;case 22:return 30;case 23:return 31;case 24:return 32;case 25:return 34;case 26:return 35;case 27:case 28:return 34;case 29:return 23;case 30:return e.yytext[0];case 31:return 6}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:[\s]+)/i,/^(?:"[^"]*")/i,/^(?:erDiagram\b)/i,/^(?:\{)/i,/^(?:\s+)/i,/^(?:[A-Za-z][A-Za-z0-9\-_]*)/i,/^(?:[\n]+)/i,/^(?:\})/i,/^(?:.)/i,/^(?:\|o\b)/i,/^(?:\}o\b)/i,/^(?:\}\|)/i,/^(?:\|\|)/i,/^(?:o\|)/i,/^(?:o\{)/i,/^(?:\|\{)/i,/^(?:\.\.)/i,/^(?:--)/i,/^(?:\.-)/i,/^(?:-\.)/i,/^(?:[A-Za-z][A-Za-z0-9\-_]*)/i,/^(?:.)/i,/^(?:$)/i],conditions:{open_directive:{rules:[1],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},block:{rules:[13,14,15,16,17],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,18,19,20,21,22,23,24,25,26,27,28,29,30,31],inclusive:!0}}};function v(){this.yy={}}return g.lexer=y,v.prototype=g,g.Parser=v,new v}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){"use strict";var r;Object.defineProperty(e,"__esModule",{value:!0}),function(t){t[t.ALL=0]="ALL",t[t.RGB=1]="RGB",t[t.HSL=2]="HSL"}(r||(r={})),e.TYPE=r},function(t,e,n){"use strict";var r=n(10);t.exports=i;function i(t){this._isDirected=!r.has(t,"directed")||t.directed,this._isMultigraph=!!r.has(t,"multigraph")&&t.multigraph,this._isCompound=!!r.has(t,"compound")&&t.compound,this._label=void 0,this._defaultNodeLabelFn=r.constant(void 0),this._defaultEdgeLabelFn=r.constant(void 0),this._nodes={},this._isCompound&&(this._parent={},this._children={},this._children["\0"]={}),this._in={},this._preds={},this._out={},this._sucs={},this._edgeObjs={},this._edgeLabels={}}function a(t,e){t[e]?t[e]++:t[e]=1}function o(t,e){--t[e]||delete t[e]}function s(t,e,n,i){var a=""+e,o=""+n;if(!t&&a>o){var s=a;a=o,o=s}return a+""+o+""+(r.isUndefined(i)?"\0":i)}function c(t,e,n,r){var i=""+e,a=""+n;if(!t&&i>a){var o=i;i=a,a=o}var s={v:i,w:a};return r&&(s.name=r),s}function u(t,e){return s(t,e.v,e.w,e.name)}i.prototype._nodeCount=0,i.prototype._edgeCount=0,i.prototype.isDirected=function(){return this._isDirected},i.prototype.isMultigraph=function(){return this._isMultigraph},i.prototype.isCompound=function(){return this._isCompound},i.prototype.setGraph=function(t){return this._label=t,this},i.prototype.graph=function(){return this._label},i.prototype.setDefaultNodeLabel=function(t){return r.isFunction(t)||(t=r.constant(t)),this._defaultNodeLabelFn=t,this},i.prototype.nodeCount=function(){return this._nodeCount},i.prototype.nodes=function(){return r.keys(this._nodes)},i.prototype.sources=function(){var t=this;return r.filter(this.nodes(),(function(e){return r.isEmpty(t._in[e])}))},i.prototype.sinks=function(){var t=this;return r.filter(this.nodes(),(function(e){return r.isEmpty(t._out[e])}))},i.prototype.setNodes=function(t,e){var n=arguments,i=this;return r.each(t,(function(t){n.length>1?i.setNode(t,e):i.setNode(t)})),this},i.prototype.setNode=function(t,e){return r.has(this._nodes,t)?(arguments.length>1&&(this._nodes[t]=e),this):(this._nodes[t]=arguments.length>1?e:this._defaultNodeLabelFn(t),this._isCompound&&(this._parent[t]="\0",this._children[t]={},this._children["\0"][t]=!0),this._in[t]={},this._preds[t]={},this._out[t]={},this._sucs[t]={},++this._nodeCount,this)},i.prototype.node=function(t){return this._nodes[t]},i.prototype.hasNode=function(t){return r.has(this._nodes,t)},i.prototype.removeNode=function(t){var e=this;if(r.has(this._nodes,t)){var n=function(t){e.removeEdge(e._edgeObjs[t])};delete this._nodes[t],this._isCompound&&(this._removeFromParentsChildList(t),delete this._parent[t],r.each(this.children(t),(function(t){e.setParent(t)})),delete this._children[t]),r.each(r.keys(this._in[t]),n),delete this._in[t],delete this._preds[t],r.each(r.keys(this._out[t]),n),delete this._out[t],delete this._sucs[t],--this._nodeCount}return this},i.prototype.setParent=function(t,e){if(!this._isCompound)throw new Error("Cannot set parent in a non-compound graph");if(r.isUndefined(e))e="\0";else{for(var n=e+="";!r.isUndefined(n);n=this.parent(n))if(n===t)throw new Error("Setting "+e+" as parent of "+t+" would create a cycle");this.setNode(e)}return this.setNode(t),this._removeFromParentsChildList(t),this._parent[t]=e,this._children[e][t]=!0,this},i.prototype._removeFromParentsChildList=function(t){delete this._children[this._parent[t]][t]},i.prototype.parent=function(t){if(this._isCompound){var e=this._parent[t];if("\0"!==e)return e}},i.prototype.children=function(t){if(r.isUndefined(t)&&(t="\0"),this._isCompound){var e=this._children[t];if(e)return r.keys(e)}else{if("\0"===t)return this.nodes();if(this.hasNode(t))return[]}},i.prototype.predecessors=function(t){var e=this._preds[t];if(e)return r.keys(e)},i.prototype.successors=function(t){var e=this._sucs[t];if(e)return r.keys(e)},i.prototype.neighbors=function(t){var e=this.predecessors(t);if(e)return r.union(e,this.successors(t))},i.prototype.isLeaf=function(t){return 0===(this.isDirected()?this.successors(t):this.neighbors(t)).length},i.prototype.filterNodes=function(t){var e=new this.constructor({directed:this._isDirected,multigraph:this._isMultigraph,compound:this._isCompound});e.setGraph(this.graph());var n=this;r.each(this._nodes,(function(n,r){t(r)&&e.setNode(r,n)})),r.each(this._edgeObjs,(function(t){e.hasNode(t.v)&&e.hasNode(t.w)&&e.setEdge(t,n.edge(t))}));var i={};return this._isCompound&&r.each(e.nodes(),(function(t){e.setParent(t,function t(r){var a=n.parent(r);return void 0===a||e.hasNode(a)?(i[r]=a,a):a in i?i[a]:t(a)}(t))})),e},i.prototype.setDefaultEdgeLabel=function(t){return r.isFunction(t)||(t=r.constant(t)),this._defaultEdgeLabelFn=t,this},i.prototype.edgeCount=function(){return this._edgeCount},i.prototype.edges=function(){return r.values(this._edgeObjs)},i.prototype.setPath=function(t,e){var n=this,i=arguments;return r.reduce(t,(function(t,r){return i.length>1?n.setEdge(t,r,e):n.setEdge(t,r),r})),this},i.prototype.setEdge=function(){var t,e,n,i,o=!1,u=arguments[0];"object"==typeof u&&null!==u&&"v"in u?(t=u.v,e=u.w,n=u.name,2===arguments.length&&(i=arguments[1],o=!0)):(t=u,e=arguments[1],n=arguments[3],arguments.length>2&&(i=arguments[2],o=!0)),t=""+t,e=""+e,r.isUndefined(n)||(n=""+n);var l=s(this._isDirected,t,e,n);if(r.has(this._edgeLabels,l))return o&&(this._edgeLabels[l]=i),this;if(!r.isUndefined(n)&&!this._isMultigraph)throw new Error("Cannot set a named edge when isMultigraph = false");this.setNode(t),this.setNode(e),this._edgeLabels[l]=o?i:this._defaultEdgeLabelFn(t,e,n);var h=c(this._isDirected,t,e,n);return t=h.v,e=h.w,Object.freeze(h),this._edgeObjs[l]=h,a(this._preds[e],t),a(this._sucs[t],e),this._in[e][l]=h,this._out[t][l]=h,this._edgeCount++,this},i.prototype.edge=function(t,e,n){var r=1===arguments.length?u(this._isDirected,arguments[0]):s(this._isDirected,t,e,n);return this._edgeLabels[r]},i.prototype.hasEdge=function(t,e,n){var i=1===arguments.length?u(this._isDirected,arguments[0]):s(this._isDirected,t,e,n);return r.has(this._edgeLabels,i)},i.prototype.removeEdge=function(t,e,n){var r=1===arguments.length?u(this._isDirected,arguments[0]):s(this._isDirected,t,e,n),i=this._edgeObjs[r];return i&&(t=i.v,e=i.w,delete this._edgeLabels[r],delete this._edgeObjs[r],o(this._preds[e],t),o(this._sucs[t],e),delete this._in[e][r],delete this._out[t][r],this._edgeCount--),this},i.prototype.inEdges=function(t,e){var n=this._in[t];if(n){var i=r.values(n);return e?r.filter(i,(function(t){return t.v===e})):i}},i.prototype.outEdges=function(t,e){var n=this._out[t];if(n){var i=r.values(n);return e?r.filter(i,(function(t){return t.w===e})):i}},i.prototype.nodeEdges=function(t,e){var n=this.inEdges(t,e);if(n)return n.concat(this.outEdges(t,e))}},function(t,e,n){var r=n(33)(n(16),"Map");t.exports=r},function(t,e,n){var r=n(217),i=n(224),a=n(226),o=n(227),s=n(228);function c(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e<n;){var r=t[e];this.set(r[0],r[1])}}c.prototype.clear=r,c.prototype.delete=i,c.prototype.get=a,c.prototype.has=o,c.prototype.set=s,t.exports=c},function(t,e){t.exports=function(t,e){for(var n=-1,r=null==t?0:t.length;++n<r&&!1!==e(t[n],n,t););return t}},function(t,e){t.exports=function(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=9007199254740991}},function(t,e,n){(function(t){var r=n(109),i=e&&!e.nodeType&&e,a=i&&"object"==typeof t&&t&&!t.nodeType&&t,o=a&&a.exports===i&&r.process,s=function(){try{var t=a&&a.require&&a.require("util").types;return t||o&&o.binding&&o.binding("util")}catch(t){}}();t.exports=s}).call(this,n(7)(t))},function(t,e,n){var r=n(62),i=n(234),a=Object.prototype.hasOwnProperty;t.exports=function(t){if(!r(t))return i(t);var e=[];for(var n in Object(t))a.call(t,n)&&"constructor"!=n&&e.push(n);return e}},function(t,e,n){var r=n(116),i=n(117),a=Object.prototype.propertyIsEnumerable,o=Object.getOwnPropertySymbols,s=o?function(t){return null==t?[]:(t=Object(t),r(o(t),(function(e){return a.call(t,e)})))}:i;t.exports=s},function(t,e){t.exports=function(t,e){for(var n=-1,r=e.length,i=t.length;++n<r;)t[i+n]=e[n];return t}},function(t,e,n){var r=n(122);t.exports=function(t){var e=new t.constructor(t.byteLength);return new r(e).set(new r(t)),e}},function(t,e){t.exports=function(t){return function(){return t}}},function(t,e,n){t.exports=n(126)},function(t,e,n){var r=n(89),i=n(30);t.exports=function(t,e){return t&&r(t,e,i)}},function(t,e,n){var r=n(253)();t.exports=r},function(t,e){t.exports=function(t){var e=-1,n=Array(t.size);return t.forEach((function(t){n[++e]=t})),n}},function(t,e,n){var r=n(65),i=n(49);t.exports=function(t,e){for(var n=0,a=(e=r(e,t)).length;null!=t&&n<a;)t=t[i(e[n++])];return n&&n==a?t:void 0}},function(t,e,n){var r=n(5),i=n(42),a=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,o=/^\w*$/;t.exports=function(t,e){if(r(t))return!1;var n=typeof t;return!("number"!=n&&"symbol"!=n&&"boolean"!=n&&null!=t&&!i(t))||(o.test(t)||!a.test(t)||null!=e&&t in Object(e))}},function(t,e,n){var r=n(275),i=n(137);t.exports=function(t,e){return null!=t&&i(t,e,r)}},function(t,e,n){var r=n(84),i=n(287);t.exports=function t(e,n,a,o,s){var c=-1,u=e.length;for(a||(a=i),s||(s=[]);++c<u;){var l=e[c];n>0&&a(l)?n>1?t(l,n-1,a,o,s):r(s,l):o||(s[s.length]=l)}return s}},function(t,e,n){var r=n(42);t.exports=function(t,e,n){for(var i=-1,a=t.length;++i<a;){var o=t[i],s=e(o);if(null!=s&&(void 0===c?s==s&&!r(s):n(s,c)))var c=s,u=o}return u}},function(t,e){t.exports=function(t,e,n,r){var i=t.x,a=t.y,o=i-r.x,s=a-r.y,c=Math.sqrt(e*e*s*s+n*n*o*o),u=Math.abs(e*n*o/c);r.x<i&&(u=-u);var l=Math.abs(e*n*s/c);r.y<a&&(l=-l);return{x:i+u,y:a+l}}},function(t,e,n){var r=n(372),i=n(50),a=n(373);t.exports=function(t,e,n){var o=e.label,s=t.append("g");"svg"===e.labelType?a(s,e):"string"!=typeof o||"html"===e.labelType?i(s,e):r(s,e);var c,u=s.node().getBBox();switch(n){case"top":c=-e.height/2;break;case"bottom":c=e.height/2-u.height;break;default:c=-u.height/2}return s.attr("transform","translate("+-u.width/2+","+c+")"),s}},function(t,e){},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(45),a=n(178),o={re:/^#((?:[a-f0-9]{2}){2,4}|[a-f0-9]{3})$/i,parse:function(t){if(35===t.charCodeAt(0)){var e=t.match(o.re);if(e){var n=e[1],r=parseInt(n,16),a=n.length,s=a%4==0,c=a>4,u=c?1:17,l=c?8:4,h=s?0:-1,f=c?255:15;return i.default.set({r:(r>>l*(h+3)&f)*u,g:(r>>l*(h+2)&f)*u,b:(r>>l*(h+1)&f)*u,a:s?(r&f)*u/255:1},t)}}},stringify:function(t){return t.a<1?"#"+a.DEC2HEX[Math.round(t.r)]+a.DEC2HEX[Math.round(t.g)]+a.DEC2HEX[Math.round(t.b)]+r.default.unit.frac2hex(t.a):"#"+a.DEC2HEX[Math.round(t.r)]+a.DEC2HEX[Math.round(t.g)]+a.DEC2HEX[Math.round(t.b)]}};e.default=o},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(45),a=n(15);e.default=function(t,e,n,o){void 0===o&&(o=1);var s=i.default.set({h:r.default.channel.clamp.h(t),s:r.default.channel.clamp.s(e),l:r.default.channel.clamp.l(n),a:r.default.channel.clamp.a(o)});return a.default.stringify(s)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"a")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(15);e.default=function(t){var e=i.default.parse(t),n=e.r,a=e.g,o=e.b,s=.2126*r.default.channel.toLinear(n)+.7152*r.default.channel.toLinear(a)+.0722*r.default.channel.toLinear(o);return r.default.lang.round(s)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(102);e.default=function(t){return r.default(t)>=.5}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"a",e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"a",-e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(15),i=n(52);e.default=function(t,e){var n=r.default.parse(t),a={};for(var o in e)e[o]&&(a[o]=n[o]+e[o]);return i.default(t,a)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(15),i=n(51);e.default=function(t,e,n){void 0===n&&(n=50);var a=r.default.parse(t),o=a.r,s=a.g,c=a.b,u=a.a,l=r.default.parse(e),h=l.r,f=l.g,d=l.b,p=l.a,g=n/100,y=2*g-1,v=u-p,m=((y*v==-1?y:(y+v)/(1+y*v))+1)/2,b=1-m,x=o*m+h*b,_=s*m+f*b,k=c*m+d*b,w=u*g+p*(1-g);return i.default(x,_,k,w)}},function(t,e,n){var r=n(53),i=n(79),a=n(58),o=n(229),s=n(235),c=n(114),u=n(115),l=n(238),h=n(239),f=n(119),d=n(240),p=n(41),g=n(244),y=n(245),v=n(124),m=n(5),b=n(39),x=n(249),_=n(11),k=n(251),w=n(30),E={};E["[object Arguments]"]=E["[object Array]"]=E["[object ArrayBuffer]"]=E["[object DataView]"]=E["[object Boolean]"]=E["[object Date]"]=E["[object Float32Array]"]=E["[object Float64Array]"]=E["[object Int8Array]"]=E["[object Int16Array]"]=E["[object Int32Array]"]=E["[object Map]"]=E["[object Number]"]=E["[object Object]"]=E["[object RegExp]"]=E["[object Set]"]=E["[object String]"]=E["[object Symbol]"]=E["[object Uint8Array]"]=E["[object Uint8ClampedArray]"]=E["[object Uint16Array]"]=E["[object Uint32Array]"]=!0,E["[object Error]"]=E["[object Function]"]=E["[object WeakMap]"]=!1,t.exports=function t(e,n,T,C,A,S){var M,O=1&n,D=2&n,N=4&n;if(T&&(M=A?T(e,C,A,S):T(e)),void 0!==M)return M;if(!_(e))return e;var B=m(e);if(B){if(M=g(e),!O)return u(e,M)}else{var L=p(e),F="[object Function]"==L||"[object GeneratorFunction]"==L;if(b(e))return c(e,O);if("[object Object]"==L||"[object Arguments]"==L||F&&!A){if(M=D||F?{}:v(e),!O)return D?h(e,s(M,e)):l(e,o(M,e))}else{if(!E[L])return A?e:{};M=y(e,L,O)}}S||(S=new r);var P=S.get(e);if(P)return P;S.set(e,M),k(e)?e.forEach((function(r){M.add(t(r,n,T,r,e,S))})):x(e)&&e.forEach((function(r,i){M.set(i,t(r,n,T,i,e,S))}));var I=N?D?d:f:D?keysIn:w,j=B?void 0:I(e);return i(j||e,(function(r,i){j&&(r=e[i=r]),a(M,i,t(r,n,T,i,e,S))})),M}},function(t,e,n){(function(e){var n="object"==typeof e&&e&&e.Object===Object&&e;t.exports=n}).call(this,n(211))},function(t,e){var n=Function.prototype.toString;t.exports=function(t){if(null!=t){try{return n.call(t)}catch(t){}try{return t+""}catch(t){}}return""}},function(t,e,n){var r=n(33),i=function(){try{var t=r(Object,"defineProperty");return t({},"",{}),t}catch(t){}}();t.exports=i},function(t,e,n){var r=n(230),i=n(47),a=n(5),o=n(39),s=n(60),c=n(48),u=Object.prototype.hasOwnProperty;t.exports=function(t,e){var n=a(t),l=!n&&i(t),h=!n&&!l&&o(t),f=!n&&!l&&!h&&c(t),d=n||l||h||f,p=d?r(t.length,String):[],g=p.length;for(var y in t)!e&&!u.call(t,y)||d&&("length"==y||h&&("offset"==y||"parent"==y)||f&&("buffer"==y||"byteLength"==y||"byteOffset"==y)||s(y,g))||p.push(y);return p}},function(t,e){t.exports=function(t,e){return function(n){return t(e(n))}}},function(t,e,n){(function(t){var r=n(16),i=e&&!e.nodeType&&e,a=i&&"object"==typeof t&&t&&!t.nodeType&&t,o=a&&a.exports===i?r.Buffer:void 0,s=o?o.allocUnsafe:void 0;t.exports=function(t,e){if(e)return t.slice();var n=t.length,r=s?s(n):new t.constructor(n);return t.copy(r),r}}).call(this,n(7)(t))},function(t,e){t.exports=function(t,e){var n=-1,r=t.length;for(e||(e=Array(r));++n<r;)e[n]=t[n];return e}},function(t,e){t.exports=function(t,e){for(var n=-1,r=null==t?0:t.length,i=0,a=[];++n<r;){var o=t[n];e(o,n,t)&&(a[i++]=o)}return a}},function(t,e){t.exports=function(){return[]}},function(t,e,n){var r=n(84),i=n(63),a=n(83),o=n(117),s=Object.getOwnPropertySymbols?function(t){for(var e=[];t;)r(e,a(t)),t=i(t);return e}:o;t.exports=s},function(t,e,n){var r=n(120),i=n(83),a=n(30);t.exports=function(t){return r(t,a,i)}},function(t,e,n){var r=n(84),i=n(5);t.exports=function(t,e,n){var a=e(t);return i(t)?a:r(a,n(t))}},function(t,e,n){var r=n(33)(n(16),"Set");t.exports=r},function(t,e,n){var r=n(16).Uint8Array;t.exports=r},function(t,e,n){var r=n(85);t.exports=function(t,e){var n=e?r(t.buffer):t.buffer;return new t.constructor(n,t.byteOffset,t.length)}},function(t,e,n){var r=n(125),i=n(63),a=n(62);t.exports=function(t){return"function"!=typeof t.constructor||a(t)?{}:r(i(t))}},function(t,e,n){var r=n(11),i=Object.create,a=function(){function t(){}return function(e){if(!r(e))return{};if(i)return i(e);t.prototype=e;var n=new t;return t.prototype=void 0,n}}();t.exports=a},function(t,e,n){var r=n(79),i=n(64),a=n(127),o=n(5);t.exports=function(t,e){return(o(t)?r:i)(t,a(e))}},function(t,e,n){var r=n(35);t.exports=function(t){return"function"==typeof t?t:r}},function(t,e,n){var r=n(116),i=n(255),a=n(25),o=n(5);t.exports=function(t,e){return(o(t)?r:i)(t,a(e,3))}},function(t,e,n){var r=n(258),i=n(21);t.exports=function t(e,n,a,o,s){return e===n||(null==e||null==n||!i(e)&&!i(n)?e!=e&&n!=n:r(e,n,a,o,t,s))}},function(t,e,n){var r=n(131),i=n(261),a=n(132);t.exports=function(t,e,n,o,s,c){var u=1&n,l=t.length,h=e.length;if(l!=h&&!(u&&h>l))return!1;var f=c.get(t);if(f&&c.get(e))return f==e;var d=-1,p=!0,g=2&n?new r:void 0;for(c.set(t,e),c.set(e,t);++d<l;){var y=t[d],v=e[d];if(o)var m=u?o(v,y,d,e,t,c):o(y,v,d,t,e,c);if(void 0!==m){if(m)continue;p=!1;break}if(g){if(!i(e,(function(t,e){if(!a(g,e)&&(y===t||s(y,t,n,o,c)))return g.push(e)}))){p=!1;break}}else if(y!==v&&!s(y,v,n,o,c)){p=!1;break}}return c.delete(t),c.delete(e),p}},function(t,e,n){var r=n(78),i=n(259),a=n(260);function o(t){var e=-1,n=null==t?0:t.length;for(this.__data__=new r;++e<n;)this.add(t[e])}o.prototype.add=o.prototype.push=i,o.prototype.has=a,t.exports=o},function(t,e){t.exports=function(t,e){return t.has(e)}},function(t,e,n){var r=n(11);t.exports=function(t){return t==t&&!r(t)}},function(t,e){t.exports=function(t,e){return function(n){return null!=n&&(n[t]===e&&(void 0!==e||t in Object(n)))}}},function(t,e,n){var r=n(271);t.exports=function(t){return null==t?"":r(t)}},function(t,e,n){var r=n(272),i=n(137);t.exports=function(t,e){return null!=t&&i(t,e,r)}},function(t,e,n){var r=n(65),i=n(47),a=n(5),o=n(60),s=n(80),c=n(49);t.exports=function(t,e,n){for(var u=-1,l=(e=r(e,t)).length,h=!1;++u<l;){var f=c(e[u]);if(!(h=null!=t&&n(t,f)))break;t=t[f]}return h||++u!=l?h:!!(l=null==t?0:t.length)&&s(l)&&o(f,l)&&(a(t)||i(t))}},function(t,e){t.exports=function(t){return function(e){return null==e?void 0:e[t]}}},function(t,e){t.exports=function(t){return void 0===t}},function(t,e,n){var r=n(66),i=n(25),a=n(141),o=n(5);t.exports=function(t,e){return(o(t)?r:a)(t,i(e,3))}},function(t,e,n){var r=n(64),i=n(24);t.exports=function(t,e){var n=-1,a=i(t)?Array(t.length):[];return r(t,(function(t,r,i){a[++n]=e(t,r,i)})),a}},function(t,e,n){var r=n(277),i=n(64),a=n(25),o=n(278),s=n(5);t.exports=function(t,e,n){var c=s(t)?r:o,u=arguments.length<3;return c(t,a(e,4),n,u,i)}},function(t,e,n){var r=n(288),i=Math.max;t.exports=function(t,e,n){return e=i(void 0===e?t.length-1:e,0),function(){for(var a=arguments,o=-1,s=i(a.length-e,0),c=Array(s);++o<s;)c[o]=a[e+o];o=-1;for(var u=Array(e+1);++o<e;)u[o]=a[o];return u[e]=n(c),r(t,this,u)}}},function(t,e,n){var r=n(289),i=n(290)(r);t.exports=i},function(t,e){t.exports=function(t,e,n,r){for(var i=t.length,a=n+(r?1:-1);r?a--:++a<i;)if(e(t[a],a,t))return a;return-1}},function(t,e,n){var r=n(24),i=n(21);t.exports=function(t){return i(t)&&r(t)}},function(t,e,n){var r=n(299),i=n(30);t.exports=function(t){return null==t?[]:r(t,i(t))}},function(t,e,n){var r=n(10),i=n(149);t.exports=function(t,e,n,r){return function(t,e,n,r){var a,o,s={},c=new i,u=function(t){var e=t.v!==a?t.v:t.w,r=s[e],i=n(t),u=o.distance+i;if(i<0)throw new Error("dijkstra does not allow negative edge weights. Bad edge: "+t+" Weight: "+i);u<r.distance&&(r.distance=u,r.predecessor=a,c.decrease(e,u))};t.nodes().forEach((function(t){var n=t===e?0:Number.POSITIVE_INFINITY;s[t]={distance:n},c.add(t,n)}));for(;c.size()>0&&(a=c.removeMin(),(o=s[a]).distance!==Number.POSITIVE_INFINITY);)r(a).forEach(u);return s}(t,String(e),n||a,r||function(e){return t.outEdges(e)})};var a=r.constant(1)},function(t,e,n){var r=n(10);function i(){this._arr=[],this._keyIndices={}}t.exports=i,i.prototype.size=function(){return this._arr.length},i.prototype.keys=function(){return this._arr.map((function(t){return t.key}))},i.prototype.has=function(t){return r.has(this._keyIndices,t)},i.prototype.priority=function(t){var e=this._keyIndices[t];if(void 0!==e)return this._arr[e].priority},i.prototype.min=function(){if(0===this.size())throw new Error("Queue underflow");return this._arr[0].key},i.prototype.add=function(t,e){var n=this._keyIndices;if(t=String(t),!r.has(n,t)){var i=this._arr,a=i.length;return n[t]=a,i.push({key:t,priority:e}),this._decrease(a),!0}return!1},i.prototype.removeMin=function(){this._swap(0,this._arr.length-1);var t=this._arr.pop();return delete this._keyIndices[t.key],this._heapify(0),t.key},i.prototype.decrease=function(t,e){var n=this._keyIndices[t];if(e>this._arr[n].priority)throw new Error("New priority is greater than current priority. Key: "+t+" Old: "+this._arr[n].priority+" New: "+e);this._arr[n].priority=e,this._decrease(n)},i.prototype._heapify=function(t){var e=this._arr,n=2*t,r=n+1,i=t;n<e.length&&(i=e[n].priority<e[i].priority?n:i,r<e.length&&(i=e[r].priority<e[i].priority?r:i),i!==t&&(this._swap(t,i),this._heapify(i)))},i.prototype._decrease=function(t){for(var e,n=this._arr,r=n[t].priority;0!==t&&!(n[e=t>>1].priority<r);)this._swap(t,e),t=e},i.prototype._swap=function(t,e){var n=this._arr,r=this._keyIndices,i=n[t],a=n[e];n[t]=a,n[e]=i,r[a.key]=t,r[i.key]=e}},function(t,e,n){var r=n(10);t.exports=function(t){var e=0,n=[],i={},a=[];return t.nodes().forEach((function(o){r.has(i,o)||function o(s){var c=i[s]={onStack:!0,lowlink:e,index:e++};if(n.push(s),t.successors(s).forEach((function(t){r.has(i,t)?i[t].onStack&&(c.lowlink=Math.min(c.lowlink,i[t].index)):(o(t),c.lowlink=Math.min(c.lowlink,i[t].lowlink))})),c.lowlink===c.index){var u,l=[];do{u=n.pop(),i[u].onStack=!1,l.push(u)}while(s!==u);a.push(l)}}(o)})),a}},function(t,e,n){var r=n(10);function i(t){var e={},n={},i=[];if(r.each(t.sinks(),(function o(s){if(r.has(n,s))throw new a;r.has(e,s)||(n[s]=!0,e[s]=!0,r.each(t.predecessors(s),o),delete n[s],i.push(s))})),r.size(e)!==t.nodeCount())throw new a;return i}function a(){}t.exports=i,i.CycleException=a,a.prototype=new Error},function(t,e,n){var r=n(10);t.exports=function(t,e,n){r.isArray(e)||(e=[e]);var i=(t.isDirected()?t.successors:t.neighbors).bind(t),a=[],o={};return r.each(e,(function(e){if(!t.hasNode(e))throw new Error("Graph does not have node: "+e);!function t(e,n,i,a,o,s){r.has(a,n)||(a[n]=!0,i||s.push(n),r.each(o(n),(function(n){t(e,n,i,a,o,s)})),i&&s.push(n))}(t,e,"post"===n,o,i,a)})),a}},function(t,e,n){var r;try{r=n(18)}catch(t){}r||(r=window.dagre),t.exports=r},function(t,e,n){var r=n(67),i=n(36),a=n(68),o=n(40),s=Object.prototype,c=s.hasOwnProperty,u=r((function(t,e){t=Object(t);var n=-1,r=e.length,u=r>2?e[2]:void 0;for(u&&a(e[0],e[1],u)&&(r=1);++n<r;)for(var l=e[n],h=o(l),f=-1,d=h.length;++f<d;){var p=h[f],g=t[p];(void 0===g||i(g,s[p])&&!c.call(t,p))&&(t[p]=l[p])}return t}));t.exports=u},function(t,e,n){var r=n(318);t.exports=function(t){return t?(t=r(t))===1/0||t===-1/0?17976931348623157e292*(t<0?-1:1):t==t?t:0:0===t?t:0}},function(t,e,n){var r=n(94);t.exports=function(t){return(null==t?0:t.length)?r(t,1):[]}},function(t,e,n){var r=n(59),i=n(36);t.exports=function(t,e,n){(void 0===n||i(t[e],n))&&(void 0!==n||e in t)||r(t,e,n)}},function(t,e,n){var r=n(34),i=n(63),a=n(21),o=Function.prototype,s=Object.prototype,c=o.toString,u=s.hasOwnProperty,l=c.call(Object);t.exports=function(t){if(!a(t)||"[object Object]"!=r(t))return!1;var e=i(t);if(null===e)return!0;var n=u.call(e,"constructor")&&e.constructor;return"function"==typeof n&&n instanceof n&&c.call(n)==l}},function(t,e){t.exports=function(t,e){if(("constructor"!==e||"function"!=typeof t[e])&&"__proto__"!=e)return t[e]}},function(t,e){t.exports=function(t,e){return t<e}},function(t,e,n){var r=n(332),i=n(335)((function(t,e){return null==t?{}:r(t,e)}));t.exports=i},function(t,e,n){var r=n(336)();t.exports=r},function(t,e,n){var r=n(135),i=0;t.exports=function(t){var e=++i;return r(t)+e}},function(t,e,n){"use strict";var r=n(4),i=n(17).Graph,a=n(69).slack;function o(t,e){return r.forEach(t.nodes(),(function n(i){r.forEach(e.nodeEdges(i),(function(r){var o=r.v,s=i===o?r.w:o;t.hasNode(s)||a(e,r)||(t.setNode(s,{}),t.setEdge(i,s,{}),n(s))}))})),t.nodeCount()}function s(t,e){return r.minBy(e.edges(),(function(n){if(t.hasNode(n.v)!==t.hasNode(n.w))return a(e,n)}))}function c(t,e,n){r.forEach(t.nodes(),(function(t){e.node(t).rank+=n}))}t.exports=function(t){var e,n,r=new i({directed:!1}),u=t.nodes()[0],l=t.nodeCount();r.setNode(u,{});for(;o(r,t)<l;)e=s(r,t),n=r.hasNode(e.v)?a(t,e):-a(t,e),c(r,t,n);return r}},function(t,e){t.exports=function(t,e){return t.intersect(e)}},function(t,e,n){var r=n(96);t.exports=function(t,e,n){return r(t,e,e,n)}},function(t,e,n){var r=n(369);t.exports=function(t,e,n){var i=t.x,a=t.y,o=[],s=Number.POSITIVE_INFINITY,c=Number.POSITIVE_INFINITY;e.forEach((function(t){s=Math.min(s,t.x),c=Math.min(c,t.y)}));for(var u=i-t.width/2-s,l=a-t.height/2-c,h=0;h<e.length;h++){var f=e[h],d=e[h<e.length-1?h+1:0],p=r(t,n,{x:u+f.x,y:l+f.y},{x:u+d.x,y:l+d.y});p&&o.push(p)}if(!o.length)return console.log("NO INTERSECTION FOUND, RETURN NODE CENTER",t),t;o.length>1&&o.sort((function(t,e){var r=t.x-n.x,i=t.y-n.y,a=Math.sqrt(r*r+i*i),o=e.x-n.x,s=e.y-n.y,c=Math.sqrt(o*o+s*s);return a<c?-1:a===c?0:1}));return o[0]}},function(t,e){t.exports=function(t,e){var n,r,i=t.x,a=t.y,o=e.x-i,s=e.y-a,c=t.width/2,u=t.height/2;Math.abs(s)*c>Math.abs(o)*u?(s<0&&(u=-u),n=0===s?0:u*o/s,r=u):(o<0&&(c=-c),n=c,r=0===o?0:c*s/o);return{x:i+n,y:a+r}}},function(t,e,n){t.exports=function t(e){"use strict";var n=/^\0+/g,r=/[\0\r\f]/g,i=/: */g,a=/zoo|gra/,o=/([,: ])(transform)/g,s=/,+\s*(?![^(]*[)])/g,c=/ +\s*(?![^(]*[)])/g,u=/ *[\0] */g,l=/,\r+?/g,h=/([\t\r\n ])*\f?&/g,f=/:global\(((?:[^\(\)\[\]]*|\[.*\]|\([^\(\)]*\))*)\)/g,d=/\W+/g,p=/@(k\w+)\s*(\S*)\s*/,g=/::(place)/g,y=/:(read-only)/g,v=/\s+(?=[{\];=:>])/g,m=/([[}=:>])\s+/g,b=/(\{[^{]+?);(?=\})/g,x=/\s{2,}/g,_=/([^\(])(:+) */g,k=/[svh]\w+-[tblr]{2}/,w=/\(\s*(.*)\s*\)/g,E=/([\s\S]*?);/g,T=/-self|flex-/g,C=/[^]*?(:[rp][el]a[\w-]+)[^]*/,A=/stretch|:\s*\w+\-(?:conte|avail)/,S=/([^-])(image-set\()/,M="-webkit-",O="-moz-",D="-ms-",N=1,B=1,L=0,F=1,P=1,I=1,j=0,R=0,Y=0,z=[],U=[],$=0,W=null,H=0,V=1,G="",q="",X="";function Z(t,e,i,a,o){for(var s,c,l=0,h=0,f=0,d=0,v=0,m=0,b=0,x=0,k=0,E=0,T=0,C=0,A=0,S=0,O=0,D=0,j=0,U=0,W=0,K=i.length,it=K-1,at="",ot="",st="",ct="",ut="",lt="";O<K;){if(b=i.charCodeAt(O),O===it&&h+d+f+l!==0&&(0!==h&&(b=47===h?10:47),d=f=l=0,K++,it++),h+d+f+l===0){if(O===it&&(D>0&&(ot=ot.replace(r,"")),ot.trim().length>0)){switch(b){case 32:case 9:case 59:case 13:case 10:break;default:ot+=i.charAt(O)}b=59}if(1===j)switch(b){case 123:case 125:case 59:case 34:case 39:case 40:case 41:case 44:j=0;case 9:case 13:case 10:case 32:break;default:for(j=0,W=O,v=b,O--,b=59;W<K;)switch(i.charCodeAt(W++)){case 10:case 13:case 59:++O,b=v,W=K;break;case 58:D>0&&(++O,b=v);case 123:W=K}}switch(b){case 123:for(v=(ot=ot.trim()).charCodeAt(0),T=1,W=++O;O<K;){switch(b=i.charCodeAt(O)){case 123:T++;break;case 125:T--;break;case 47:switch(m=i.charCodeAt(O+1)){case 42:case 47:O=rt(m,O,it,i)}break;case 91:b++;case 40:b++;case 34:case 39:for(;O++<it&&i.charCodeAt(O)!==b;);}if(0===T)break;O++}switch(st=i.substring(W,O),0===v&&(v=(ot=ot.replace(n,"").trim()).charCodeAt(0)),v){case 64:switch(D>0&&(ot=ot.replace(r,"")),m=ot.charCodeAt(1)){case 100:case 109:case 115:case 45:s=e;break;default:s=z}if(W=(st=Z(e,s,st,m,o+1)).length,Y>0&&0===W&&(W=ot.length),$>0&&(c=nt(3,st,s=J(z,ot,U),e,B,N,W,m,o,a),ot=s.join(""),void 0!==c&&0===(W=(st=c.trim()).length)&&(m=0,st="")),W>0)switch(m){case 115:ot=ot.replace(w,et);case 100:case 109:case 45:st=ot+"{"+st+"}";break;case 107:st=(ot=ot.replace(p,"$1 $2"+(V>0?G:"")))+"{"+st+"}",st=1===P||2===P&&tt("@"+st,3)?"@"+M+st+"@"+st:"@"+st;break;default:st=ot+st,112===a&&(ct+=st,st="")}else st="";break;default:st=Z(e,J(e,ot,U),st,a,o+1)}ut+=st,C=0,j=0,S=0,D=0,U=0,A=0,ot="",st="",b=i.charCodeAt(++O);break;case 125:case 59:if((W=(ot=(D>0?ot.replace(r,""):ot).trim()).length)>1)switch(0===S&&(45===(v=ot.charCodeAt(0))||v>96&&v<123)&&(W=(ot=ot.replace(" ",":")).length),$>0&&void 0!==(c=nt(1,ot,e,t,B,N,ct.length,a,o,a))&&0===(W=(ot=c.trim()).length)&&(ot="\0\0"),v=ot.charCodeAt(0),m=ot.charCodeAt(1),v){case 0:break;case 64:if(105===m||99===m){lt+=ot+i.charAt(O);break}default:if(58===ot.charCodeAt(W-1))break;ct+=Q(ot,v,m,ot.charCodeAt(2))}C=0,j=0,S=0,D=0,U=0,ot="",b=i.charCodeAt(++O)}}switch(b){case 13:case 10:if(h+d+f+l+R===0)switch(E){case 41:case 39:case 34:case 64:case 126:case 62:case 42:case 43:case 47:case 45:case 58:case 44:case 59:case 123:case 125:break;default:S>0&&(j=1)}47===h?h=0:F+C===0&&107!==a&&ot.length>0&&(D=1,ot+="\0"),$*H>0&&nt(0,ot,e,t,B,N,ct.length,a,o,a),N=1,B++;break;case 59:case 125:if(h+d+f+l===0){N++;break}default:switch(N++,at=i.charAt(O),b){case 9:case 32:if(d+l+h===0)switch(x){case 44:case 58:case 9:case 32:at="";break;default:32!==b&&(at=" ")}break;case 0:at="\\0";break;case 12:at="\\f";break;case 11:at="\\v";break;case 38:d+h+l===0&&F>0&&(U=1,D=1,at="\f"+at);break;case 108:if(d+h+l+L===0&&S>0)switch(O-S){case 2:112===x&&58===i.charCodeAt(O-3)&&(L=x);case 8:111===k&&(L=k)}break;case 58:d+h+l===0&&(S=O);break;case 44:h+f+d+l===0&&(D=1,at+="\r");break;case 34:case 39:0===h&&(d=d===b?0:0===d?b:d);break;case 91:d+h+f===0&&l++;break;case 93:d+h+f===0&&l--;break;case 41:d+h+l===0&&f--;break;case 40:if(d+h+l===0){if(0===C)switch(2*x+3*k){case 533:break;default:T=0,C=1}f++}break;case 64:h+f+d+l+S+A===0&&(A=1);break;case 42:case 47:if(d+l+f>0)break;switch(h){case 0:switch(2*b+3*i.charCodeAt(O+1)){case 235:h=47;break;case 220:W=O,h=42}break;case 42:47===b&&42===x&&W+2!==O&&(33===i.charCodeAt(W+2)&&(ct+=i.substring(W,O+1)),at="",h=0)}}if(0===h){if(F+d+l+A===0&&107!==a&&59!==b)switch(b){case 44:case 126:case 62:case 43:case 41:case 40:if(0===C){switch(x){case 9:case 32:case 10:case 13:at+="\0";break;default:at="\0"+at+(44===b?"":"\0")}D=1}else switch(b){case 40:S+7===O&&108===x&&(S=0),C=++T;break;case 41:0==(C=--T)&&(D=1,at+="\0")}break;case 9:case 32:switch(x){case 0:case 123:case 125:case 59:case 44:case 12:case 9:case 32:case 10:case 13:break;default:0===C&&(D=1,at+="\0")}}ot+=at,32!==b&&9!==b&&(E=b)}}k=x,x=b,O++}if(W=ct.length,Y>0&&0===W&&0===ut.length&&0===e[0].length==0&&(109!==a||1===e.length&&(F>0?q:X)===e[0])&&(W=e.join(",").length+2),W>0){if(s=0===F&&107!==a?function(t){for(var e,n,i=0,a=t.length,o=Array(a);i<a;++i){for(var s=t[i].split(u),c="",l=0,h=0,f=0,d=0,p=s.length;l<p;++l)if(!(0===(h=(n=s[l]).length)&&p>1)){if(f=c.charCodeAt(c.length-1),d=n.charCodeAt(0),e="",0!==l)switch(f){case 42:case 126:case 62:case 43:case 32:case 40:break;default:e=" "}switch(d){case 38:n=e+q;case 126:case 62:case 43:case 32:case 41:case 40:break;case 91:n=e+n+q;break;case 58:switch(2*n.charCodeAt(1)+3*n.charCodeAt(2)){case 530:if(I>0){n=e+n.substring(8,h-1);break}default:(l<1||s[l-1].length<1)&&(n=e+q+n)}break;case 44:e="";default:n=h>1&&n.indexOf(":")>0?e+n.replace(_,"$1"+q+"$2"):e+n+q}c+=n}o[i]=c.replace(r,"").trim()}return o}(e):e,$>0&&void 0!==(c=nt(2,ct,s,t,B,N,W,a,o,a))&&0===(ct=c).length)return lt+ct+ut;if(ct=s.join(",")+"{"+ct+"}",P*L!=0){switch(2!==P||tt(ct,2)||(L=0),L){case 111:ct=ct.replace(y,":-moz-$1")+ct;break;case 112:ct=ct.replace(g,"::-webkit-input-$1")+ct.replace(g,"::-moz-$1")+ct.replace(g,":-ms-input-$1")+ct}L=0}}return lt+ct+ut}function J(t,e,n){var r=e.trim().split(l),i=r,a=r.length,o=t.length;switch(o){case 0:case 1:for(var s=0,c=0===o?"":t[0]+" ";s<a;++s)i[s]=K(c,i[s],n,o).trim();break;default:s=0;var u=0;for(i=[];s<a;++s)for(var h=0;h<o;++h)i[u++]=K(t[h]+" ",r[s],n,o).trim()}return i}function K(t,e,n,r){var i=e,a=i.charCodeAt(0);switch(a<33&&(a=(i=i.trim()).charCodeAt(0)),a){case 38:switch(F+r){case 0:case 1:if(0===t.trim().length)break;default:return i.replace(h,"$1"+t.trim())}break;case 58:switch(i.charCodeAt(1)){case 103:if(I>0&&F>0)return i.replace(f,"$1").replace(h,"$1"+X);break;default:return t.trim()+i.replace(h,"$1"+t.trim())}default:if(n*F>0&&i.indexOf("\f")>0)return i.replace(h,(58===t.charCodeAt(0)?"":"$1")+t.trim())}return t+i}function Q(t,e,n,r){var u,l=0,h=t+";",f=2*e+3*n+4*r;if(944===f)return function(t){var e=t.length,n=t.indexOf(":",9)+1,r=t.substring(0,n).trim(),i=t.substring(n,e-1).trim();switch(t.charCodeAt(9)*V){case 0:break;case 45:if(110!==t.charCodeAt(10))break;default:var a=i.split((i="",s)),o=0;for(n=0,e=a.length;o<e;n=0,++o){for(var u=a[o],l=u.split(c);u=l[n];){var h=u.charCodeAt(0);if(1===V&&(h>64&&h<90||h>96&&h<123||95===h||45===h&&45!==u.charCodeAt(1)))switch(isNaN(parseFloat(u))+(-1!==u.indexOf("("))){case 1:switch(u){case"infinite":case"alternate":case"backwards":case"running":case"normal":case"forwards":case"both":case"none":case"linear":case"ease":case"ease-in":case"ease-out":case"ease-in-out":case"paused":case"reverse":case"alternate-reverse":case"inherit":case"initial":case"unset":case"step-start":case"step-end":break;default:u+=G}}l[n++]=u}i+=(0===o?"":",")+l.join(" ")}}return i=r+i+";",1===P||2===P&&tt(i,1)?M+i+i:i}(h);if(0===P||2===P&&!tt(h,1))return h;switch(f){case 1015:return 97===h.charCodeAt(10)?M+h+h:h;case 951:return 116===h.charCodeAt(3)?M+h+h:h;case 963:return 110===h.charCodeAt(5)?M+h+h:h;case 1009:if(100!==h.charCodeAt(4))break;case 969:case 942:return M+h+h;case 978:return M+h+O+h+h;case 1019:case 983:return M+h+O+h+D+h+h;case 883:return 45===h.charCodeAt(8)?M+h+h:h.indexOf("image-set(",11)>0?h.replace(S,"$1-webkit-$2")+h:h;case 932:if(45===h.charCodeAt(4))switch(h.charCodeAt(5)){case 103:return M+"box-"+h.replace("-grow","")+M+h+D+h.replace("grow","positive")+h;case 115:return M+h+D+h.replace("shrink","negative")+h;case 98:return M+h+D+h.replace("basis","preferred-size")+h}return M+h+D+h+h;case 964:return M+h+D+"flex-"+h+h;case 1023:if(99!==h.charCodeAt(8))break;return u=h.substring(h.indexOf(":",15)).replace("flex-","").replace("space-between","justify"),M+"box-pack"+u+M+h+D+"flex-pack"+u+h;case 1005:return a.test(h)?h.replace(i,":"+M)+h.replace(i,":"+O)+h:h;case 1e3:switch(l=(u=h.substring(13).trim()).indexOf("-")+1,u.charCodeAt(0)+u.charCodeAt(l)){case 226:u=h.replace(k,"tb");break;case 232:u=h.replace(k,"tb-rl");break;case 220:u=h.replace(k,"lr");break;default:return h}return M+h+D+u+h;case 1017:if(-1===h.indexOf("sticky",9))return h;case 975:switch(l=(h=t).length-10,f=(u=(33===h.charCodeAt(l)?h.substring(0,l):h).substring(t.indexOf(":",7)+1).trim()).charCodeAt(0)+(0|u.charCodeAt(7))){case 203:if(u.charCodeAt(8)<111)break;case 115:h=h.replace(u,M+u)+";"+h;break;case 207:case 102:h=h.replace(u,M+(f>102?"inline-":"")+"box")+";"+h.replace(u,M+u)+";"+h.replace(u,D+u+"box")+";"+h}return h+";";case 938:if(45===h.charCodeAt(5))switch(h.charCodeAt(6)){case 105:return u=h.replace("-items",""),M+h+M+"box-"+u+D+"flex-"+u+h;case 115:return M+h+D+"flex-item-"+h.replace(T,"")+h;default:return M+h+D+"flex-line-pack"+h.replace("align-content","").replace(T,"")+h}break;case 973:case 989:if(45!==h.charCodeAt(3)||122===h.charCodeAt(4))break;case 931:case 953:if(!0===A.test(t))return 115===(u=t.substring(t.indexOf(":")+1)).charCodeAt(0)?Q(t.replace("stretch","fill-available"),e,n,r).replace(":fill-available",":stretch"):h.replace(u,M+u)+h.replace(u,O+u.replace("fill-",""))+h;break;case 962:if(h=M+h+(102===h.charCodeAt(5)?D+h:"")+h,n+r===211&&105===h.charCodeAt(13)&&h.indexOf("transform",10)>0)return h.substring(0,h.indexOf(";",27)+1).replace(o,"$1-webkit-$2")+h}return h}function tt(t,e){var n=t.indexOf(1===e?":":"{"),r=t.substring(0,3!==e?n:10),i=t.substring(n+1,t.length-1);return W(2!==e?r:r.replace(C,"$1"),i,e)}function et(t,e){var n=Q(e,e.charCodeAt(0),e.charCodeAt(1),e.charCodeAt(2));return n!==e+";"?n.replace(E," or ($1)").substring(4):"("+e+")"}function nt(t,e,n,r,i,a,o,s,c,u){for(var l,h=0,f=e;h<$;++h)switch(l=U[h].call(at,t,f,n,r,i,a,o,s,c,u)){case void 0:case!1:case!0:case null:break;default:f=l}if(f!==e)return f}function rt(t,e,n,r){for(var i=e+1;i<n;++i)switch(r.charCodeAt(i)){case 47:if(42===t&&42===r.charCodeAt(i-1)&&e+2!==i)return i+1;break;case 10:if(47===t)return i+1}return i}function it(t){for(var e in t){var n=t[e];switch(e){case"keyframe":V=0|n;break;case"global":I=0|n;break;case"cascade":F=0|n;break;case"compress":j=0|n;break;case"semicolon":R=0|n;break;case"preserve":Y=0|n;break;case"prefix":W=null,n?"function"!=typeof n?P=1:(P=2,W=n):P=0}}return it}function at(e,n){if(void 0!==this&&this.constructor===at)return t(e);var i=e,a=i.charCodeAt(0);a<33&&(a=(i=i.trim()).charCodeAt(0)),V>0&&(G=i.replace(d,91===a?"":"-")),a=1,1===F?X=i:q=i;var o,s=[X];$>0&&void 0!==(o=nt(-1,n,s,s,B,N,0,0,0,0))&&"string"==typeof o&&(n=o);var c=Z(z,s,n,0,0);return $>0&&void 0!==(o=nt(-2,c,s,s,B,N,c.length,0,0,0))&&"string"!=typeof(c=o)&&(a=0),G="",X="",q="",L=0,B=1,N=1,j*a==0?c:function(t){return t.replace(r,"").replace(v,"").replace(m,"$1").replace(b,"$1").replace(x," ")}(c)}return at.use=function t(e){switch(e){case void 0:case null:$=U.length=0;break;default:if("function"==typeof e)U[$++]=e;else if("object"==typeof e)for(var n=0,r=e.length;n<r;++n)t(e[n]);else H=0|!!e}return t},at.set=it,void 0!==e&&it(e),at}(null)},function(t,e){t.exports=function(t,e){return t.intersect(e)}},function(t,e,n){var r={"./locale":98,"./locale.js":98};function i(t){var e=a(t);return n(e)}function a(t){if(!n.o(r,t)){var e=new Error("Cannot find module '"+t+"'");throw e.code="MODULE_NOT_FOUND",e}return r[t]}i.keys=function(){return Object.keys(r)},i.resolve=a,t.exports=i,i.id=171},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(51);e.hex=r.default;var i=n(51);e.rgb=i.default;var a=n(51);e.rgba=a.default;var o=n(100);e.hsl=o.default;var s=n(100);e.hsla=s.default;var c=n(29);e.channel=c.default;var u=n(182);e.red=u.default;var l=n(183);e.green=l.default;var h=n(184);e.blue=h.default;var f=n(185);e.hue=f.default;var d=n(186);e.saturation=d.default;var p=n(187);e.lightness=p.default;var g=n(101);e.alpha=g.default;var y=n(101);e.opacity=y.default;var v=n(102);e.luminance=v.default;var m=n(188);e.isDark=m.default;var b=n(103);e.isLight=b.default;var x=n(189);e.isValid=x.default;var _=n(190);e.saturate=_.default;var k=n(191);e.desaturate=k.default;var w=n(192);e.lighten=w.default;var E=n(193);e.darken=E.default;var T=n(104);e.opacify=T.default;var C=n(104);e.fadeIn=C.default;var A=n(105);e.transparentize=A.default;var S=n(105);e.fadeOut=S.default;var M=n(194);e.complement=M.default;var O=n(195);e.grayscale=O.default;var D=n(106);e.adjust=D.default;var N=n(52);e.change=N.default;var B=n(196);e.invert=B.default;var L=n(107);e.mix=L.default;var F=n(197);e.scale=F.default},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r={min:{r:0,g:0,b:0,s:0,l:0,a:0},max:{r:255,g:255,b:255,h:360,s:100,l:100,a:1},clamp:{r:function(t){return t>=255?255:t<0?0:t},g:function(t){return t>=255?255:t<0?0:t},b:function(t){return t>=255?255:t<0?0:t},h:function(t){return t%360},s:function(t){return t>=100?100:t<0?0:t},l:function(t){return t>=100?100:t<0?0:t},a:function(t){return t>=1?1:t<0?0:t}},toLinear:function(t){var e=t/255;return t>.03928?Math.pow((e+.055)/1.055,2.4):e/12.92},hue2rgb:function(t,e,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+(e-t)*(2/3-n)*6:t},hsl2rgb:function(t,e){var n=t.h,i=t.s,a=t.l;if(100===i)return 2.55*a;n/=360,i/=100;var o=(a/=100)<.5?a*(1+i):a+i-a*i,s=2*a-o;switch(e){case"r":return 255*r.hue2rgb(s,o,n+1/3);case"g":return 255*r.hue2rgb(s,o,n);case"b":return 255*r.hue2rgb(s,o,n-1/3)}},rgb2hsl:function(t,e){var n=t.r,r=t.g,i=t.b;n/=255,r/=255,i/=255;var a=Math.max(n,r,i),o=Math.min(n,r,i),s=(a+o)/2;if("l"===e)return 100*s;if(a===o)return 0;var c=a-o;if("s"===e)return 100*(s>.5?c/(2-a-o):c/(a+o));switch(a){case n:return 60*((r-i)/c+(r<i?6:0));case r:return 60*((i-n)/c+2);case i:return 60*((n-r)/c+4);default:return-1}}};e.default=r},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r={round:function(t){return Math.round(1e10*t)/1e10}};e.default=r},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r={frac2hex:function(t){var e=Math.round(255*t).toString(16);return e.length>1?e:"0"+e},dec2hex:function(t){var e=Math.round(t).toString(16);return e.length>1?e:"0"+e}};e.default=r},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(75),a=n(177),o=function(){function t(t,e){this.color=e,this.changed=!1,this.data=t,this.type=new a.default}return t.prototype.set=function(t,e){return this.color=e,this.changed=!1,this.data=t,this.type.type=i.TYPE.ALL,this},t.prototype._ensureHSL=function(){void 0===this.data.h&&(this.data.h=r.default.channel.rgb2hsl(this.data,"h")),void 0===this.data.s&&(this.data.s=r.default.channel.rgb2hsl(this.data,"s")),void 0===this.data.l&&(this.data.l=r.default.channel.rgb2hsl(this.data,"l"))},t.prototype._ensureRGB=function(){void 0===this.data.r&&(this.data.r=r.default.channel.hsl2rgb(this.data,"r")),void 0===this.data.g&&(this.data.g=r.default.channel.hsl2rgb(this.data,"g")),void 0===this.data.b&&(this.data.b=r.default.channel.hsl2rgb(this.data,"b"))},Object.defineProperty(t.prototype,"r",{get:function(){return this.type.is(i.TYPE.HSL)||void 0===this.data.r?(this._ensureHSL(),r.default.channel.hsl2rgb(this.data,"r")):this.data.r},set:function(t){this.type.set(i.TYPE.RGB),this.changed=!0,this.data.r=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"g",{get:function(){return this.type.is(i.TYPE.HSL)||void 0===this.data.g?(this._ensureHSL(),r.default.channel.hsl2rgb(this.data,"g")):this.data.g},set:function(t){this.type.set(i.TYPE.RGB),this.changed=!0,this.data.g=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"b",{get:function(){return this.type.is(i.TYPE.HSL)||void 0===this.data.b?(this._ensureHSL(),r.default.channel.hsl2rgb(this.data,"b")):this.data.b},set:function(t){this.type.set(i.TYPE.RGB),this.changed=!0,this.data.b=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"h",{get:function(){return this.type.is(i.TYPE.RGB)||void 0===this.data.h?(this._ensureRGB(),r.default.channel.rgb2hsl(this.data,"h")):this.data.h},set:function(t){this.type.set(i.TYPE.HSL),this.changed=!0,this.data.h=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"s",{get:function(){return this.type.is(i.TYPE.RGB)||void 0===this.data.s?(this._ensureRGB(),r.default.channel.rgb2hsl(this.data,"s")):this.data.s},set:function(t){this.type.set(i.TYPE.HSL),this.changed=!0,this.data.s=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"l",{get:function(){return this.type.is(i.TYPE.RGB)||void 0===this.data.l?(this._ensureRGB(),r.default.channel.rgb2hsl(this.data,"l")):this.data.l},set:function(t){this.type.set(i.TYPE.HSL),this.changed=!0,this.data.l=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"a",{get:function(){return this.data.a},set:function(t){this.changed=!0,this.data.a=t},enumerable:!0,configurable:!0}),t}();e.default=o},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(75),i=function(){function t(){this.type=r.TYPE.ALL}return t.prototype.get=function(){return this.type},t.prototype.set=function(t){if(this.type&&this.type!==t)throw new Error("Cannot change both RGB and HSL channels at the same time");this.type=t},t.prototype.reset=function(){this.type=r.TYPE.ALL},t.prototype.is=function(t){return this.type===t},t}();e.default=i},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i={};e.DEC2HEX=i;for(var a=0;a<=255;a++)i[a]=r.default.unit.dec2hex(a)},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(99),i={colors:{aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyanaqua:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",transparent:"#00000000",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"},parse:function(t){t=t.toLowerCase();var e=i.colors[t];if(e)return r.default.parse(e)},stringify:function(t){var e=r.default.stringify(t);for(var n in i.colors)if(i.colors[n]===e)return n}};e.default=i},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(45),a={re:/^rgba?\(\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))(?:\s*?(?:,|\/)\s*?\+?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?)))?\s*?\)$/i,parse:function(t){var e=t.charCodeAt(0);if(114===e||82===e){var n=t.match(a.re);if(n){var o=n[1],s=n[2],c=n[3],u=n[4],l=n[5],h=n[6],f=n[7],d=n[8];return i.default.set({r:r.default.channel.clamp.r(s?2.55*parseFloat(o):parseFloat(o)),g:r.default.channel.clamp.g(u?2.55*parseFloat(c):parseFloat(c)),b:r.default.channel.clamp.b(h?2.55*parseFloat(l):parseFloat(l)),a:f?r.default.channel.clamp.a(d?parseFloat(f)/100:parseFloat(f)):1},t)}}},stringify:function(t){return t.a<1?"rgba("+r.default.lang.round(t.r)+", "+r.default.lang.round(t.g)+", "+r.default.lang.round(t.b)+", "+r.default.lang.round(t.a)+")":"rgb("+r.default.lang.round(t.r)+", "+r.default.lang.round(t.g)+", "+r.default.lang.round(t.b)+")"}};e.default=a},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(45),a={re:/^hsla?\(\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?(?:deg|grad|rad|turn)?)\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?%)\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?%)(?:\s*?(?:,|\/)\s*?\+?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?(%)?))?\s*?\)$/i,hueRe:/^(.+?)(deg|grad|rad|turn)$/i,_hue2deg:function(t){var e=t.match(a.hueRe);if(e){var n=e[1];switch(e[2]){case"grad":return r.default.channel.clamp.h(.9*parseFloat(n));case"rad":return r.default.channel.clamp.h(180*parseFloat(n)/Math.PI);case"turn":return r.default.channel.clamp.h(360*parseFloat(n))}}return r.default.channel.clamp.h(parseFloat(t))},parse:function(t){var e=t.charCodeAt(0);if(104===e||72===e){var n=t.match(a.re);if(n){var o=n[1],s=n[2],c=n[3],u=n[4],l=n[5];return i.default.set({h:a._hue2deg(o),s:r.default.channel.clamp.s(parseFloat(s)),l:r.default.channel.clamp.l(parseFloat(c)),a:u?r.default.channel.clamp.a(l?parseFloat(u)/100:parseFloat(u)):1},t)}}},stringify:function(t){return t.a<1?"hsla("+r.default.lang.round(t.h)+", "+r.default.lang.round(t.s)+"%, "+r.default.lang.round(t.l)+"%, "+t.a+")":"hsl("+r.default.lang.round(t.h)+", "+r.default.lang.round(t.s)+"%, "+r.default.lang.round(t.l)+"%)"}};e.default=a},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"r")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"g")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"b")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"h")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"s")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"l")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(103);e.default=function(t){return!r.default(t)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(15);e.default=function(t){try{return r.default.parse(t),!0}catch(t){return!1}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"s",e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"s",-e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"l",e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"l",-e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t){return r.default(t,"h",180)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(52);e.default=function(t){return r.default(t,{s:0})}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(15),i=n(107);e.default=function(t,e){void 0===e&&(e=100);var n=r.default.parse(t);return n.r=255-n.r,n.g=255-n.g,n.b=255-n.b,i.default(n,t,e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(15),a=n(106);e.default=function(t,e){var n,o,s,c=i.default.parse(t),u={};for(var l in e)u[l]=(n=c[l],o=e[l],s=r.default.channel.max[l],o>0?(s-n)*o/100:n*o/100);return a.default(t,u)}},function(t,e,n){t.exports={Graph:n(76),version:n(300)}},function(t,e,n){var r=n(108);t.exports=function(t){return r(t,4)}},function(t,e){t.exports=function(){this.__data__=[],this.size=0}},function(t,e,n){var r=n(55),i=Array.prototype.splice;t.exports=function(t){var e=this.__data__,n=r(e,t);return!(n<0)&&(n==e.length-1?e.pop():i.call(e,n,1),--this.size,!0)}},function(t,e,n){var r=n(55);t.exports=function(t){var e=this.__data__,n=r(e,t);return n<0?void 0:e[n][1]}},function(t,e,n){var r=n(55);t.exports=function(t){return r(this.__data__,t)>-1}},function(t,e,n){var r=n(55);t.exports=function(t,e){var n=this.__data__,i=r(n,t);return i<0?(++this.size,n.push([t,e])):n[i][1]=e,this}},function(t,e,n){var r=n(54);t.exports=function(){this.__data__=new r,this.size=0}},function(t,e){t.exports=function(t){var e=this.__data__,n=e.delete(t);return this.size=e.size,n}},function(t,e){t.exports=function(t){return this.__data__.get(t)}},function(t,e){t.exports=function(t){return this.__data__.has(t)}},function(t,e,n){var r=n(54),i=n(77),a=n(78);t.exports=function(t,e){var n=this.__data__;if(n instanceof r){var o=n.__data__;if(!i||o.length<199)return o.push([t,e]),this.size=++n.size,this;n=this.__data__=new a(o)}return n.set(t,e),this.size=n.size,this}},function(t,e,n){var r=n(37),i=n(214),a=n(11),o=n(110),s=/^\[object .+?Constructor\]$/,c=Function.prototype,u=Object.prototype,l=c.toString,h=u.hasOwnProperty,f=RegExp("^"+l.call(h).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");t.exports=function(t){return!(!a(t)||i(t))&&(r(t)?f:s).test(o(t))}},function(t,e){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(t){"object"==typeof window&&(n=window)}t.exports=n},function(t,e,n){var r=n(38),i=Object.prototype,a=i.hasOwnProperty,o=i.toString,s=r?r.toStringTag:void 0;t.exports=function(t){var e=a.call(t,s),n=t[s];try{t[s]=void 0;var r=!0}catch(t){}var i=o.call(t);return r&&(e?t[s]=n:delete t[s]),i}},function(t,e){var n=Object.prototype.toString;t.exports=function(t){return n.call(t)}},function(t,e,n){var r,i=n(215),a=(r=/[^.]+$/.exec(i&&i.keys&&i.keys.IE_PROTO||""))?"Symbol(src)_1."+r:"";t.exports=function(t){return!!a&&a in t}},function(t,e,n){var r=n(16)["__core-js_shared__"];t.exports=r},function(t,e){t.exports=function(t,e){return null==t?void 0:t[e]}},function(t,e,n){var r=n(218),i=n(54),a=n(77);t.exports=function(){this.size=0,this.__data__={hash:new r,map:new(a||i),string:new r}}},function(t,e,n){var r=n(219),i=n(220),a=n(221),o=n(222),s=n(223);function c(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e<n;){var r=t[e];this.set(r[0],r[1])}}c.prototype.clear=r,c.prototype.delete=i,c.prototype.get=a,c.prototype.has=o,c.prototype.set=s,t.exports=c},function(t,e,n){var r=n(56);t.exports=function(){this.__data__=r?r(null):{},this.size=0}},function(t,e){t.exports=function(t){var e=this.has(t)&&delete this.__data__[t];return this.size-=e?1:0,e}},function(t,e,n){var r=n(56),i=Object.prototype.hasOwnProperty;t.exports=function(t){var e=this.__data__;if(r){var n=e[t];return"__lodash_hash_undefined__"===n?void 0:n}return i.call(e,t)?e[t]:void 0}},function(t,e,n){var r=n(56),i=Object.prototype.hasOwnProperty;t.exports=function(t){var e=this.__data__;return r?void 0!==e[t]:i.call(e,t)}},function(t,e,n){var r=n(56);t.exports=function(t,e){var n=this.__data__;return this.size+=this.has(t)?0:1,n[t]=r&&void 0===e?"__lodash_hash_undefined__":e,this}},function(t,e,n){var r=n(57);t.exports=function(t){var e=r(this,t).delete(t);return this.size-=e?1:0,e}},function(t,e){t.exports=function(t){var e=typeof t;return"string"==e||"number"==e||"symbol"==e||"boolean"==e?"__proto__"!==t:null===t}},function(t,e,n){var r=n(57);t.exports=function(t){return r(this,t).get(t)}},function(t,e,n){var r=n(57);t.exports=function(t){return r(this,t).has(t)}},function(t,e,n){var r=n(57);t.exports=function(t,e){var n=r(this,t),i=n.size;return n.set(t,e),this.size+=n.size==i?0:1,this}},function(t,e,n){var r=n(46),i=n(30);t.exports=function(t,e){return t&&r(e,i(e),t)}},function(t,e){t.exports=function(t,e){for(var n=-1,r=Array(t);++n<t;)r[n]=e(n);return r}},function(t,e,n){var r=n(34),i=n(21);t.exports=function(t){return i(t)&&"[object Arguments]"==r(t)}},function(t,e){t.exports=function(){return!1}},function(t,e,n){var r=n(34),i=n(80),a=n(21),o={};o["[object Float32Array]"]=o["[object Float64Array]"]=o["[object Int8Array]"]=o["[object Int16Array]"]=o["[object Int32Array]"]=o["[object Uint8Array]"]=o["[object Uint8ClampedArray]"]=o["[object Uint16Array]"]=o["[object Uint32Array]"]=!0,o["[object Arguments]"]=o["[object Array]"]=o["[object ArrayBuffer]"]=o["[object Boolean]"]=o["[object DataView]"]=o["[object Date]"]=o["[object Error]"]=o["[object Function]"]=o["[object Map]"]=o["[object Number]"]=o["[object Object]"]=o["[object RegExp]"]=o["[object Set]"]=o["[object String]"]=o["[object WeakMap]"]=!1,t.exports=function(t){return a(t)&&i(t.length)&&!!o[r(t)]}},function(t,e,n){var r=n(113)(Object.keys,Object);t.exports=r},function(t,e,n){var r=n(46),i=n(40);t.exports=function(t,e){return t&&r(e,i(e),t)}},function(t,e,n){var r=n(11),i=n(62),a=n(237),o=Object.prototype.hasOwnProperty;t.exports=function(t){if(!r(t))return a(t);var e=i(t),n=[];for(var s in t)("constructor"!=s||!e&&o.call(t,s))&&n.push(s);return n}},function(t,e){t.exports=function(t){var e=[];if(null!=t)for(var n in Object(t))e.push(n);return e}},function(t,e,n){var r=n(46),i=n(83);t.exports=function(t,e){return r(t,i(t),e)}},function(t,e,n){var r=n(46),i=n(118);t.exports=function(t,e){return r(t,i(t),e)}},function(t,e,n){var r=n(120),i=n(118),a=n(40);t.exports=function(t){return r(t,a,i)}},function(t,e,n){var r=n(33)(n(16),"DataView");t.exports=r},function(t,e,n){var r=n(33)(n(16),"Promise");t.exports=r},function(t,e,n){var r=n(33)(n(16),"WeakMap");t.exports=r},function(t,e){var n=Object.prototype.hasOwnProperty;t.exports=function(t){var e=t.length,r=new t.constructor(e);return e&&"string"==typeof t[0]&&n.call(t,"index")&&(r.index=t.index,r.input=t.input),r}},function(t,e,n){var r=n(85),i=n(246),a=n(247),o=n(248),s=n(123);t.exports=function(t,e,n){var c=t.constructor;switch(e){case"[object ArrayBuffer]":return r(t);case"[object Boolean]":case"[object Date]":return new c(+t);case"[object DataView]":return i(t,n);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":case"[object Uint16Array]":case"[object Uint32Array]":return s(t,n);case"[object Map]":return new c;case"[object Number]":case"[object String]":return new c(t);case"[object RegExp]":return a(t);case"[object Set]":return new c;case"[object Symbol]":return o(t)}}},function(t,e,n){var r=n(85);t.exports=function(t,e){var n=e?r(t.buffer):t.buffer;return new t.constructor(n,t.byteOffset,t.byteLength)}},function(t,e){var n=/\w*$/;t.exports=function(t){var e=new t.constructor(t.source,n.exec(t));return e.lastIndex=t.lastIndex,e}},function(t,e,n){var r=n(38),i=r?r.prototype:void 0,a=i?i.valueOf:void 0;t.exports=function(t){return a?Object(a.call(t)):{}}},function(t,e,n){var r=n(250),i=n(61),a=n(81),o=a&&a.isMap,s=o?i(o):r;t.exports=s},function(t,e,n){var r=n(41),i=n(21);t.exports=function(t){return i(t)&&"[object Map]"==r(t)}},function(t,e,n){var r=n(252),i=n(61),a=n(81),o=a&&a.isSet,s=o?i(o):r;t.exports=s},function(t,e,n){var r=n(41),i=n(21);t.exports=function(t){return i(t)&&"[object Set]"==r(t)}},function(t,e){t.exports=function(t){return function(e,n,r){for(var i=-1,a=Object(e),o=r(e),s=o.length;s--;){var c=o[t?s:++i];if(!1===n(a[c],c,a))break}return e}}},function(t,e,n){var r=n(24);t.exports=function(t,e){return function(n,i){if(null==n)return n;if(!r(n))return t(n,i);for(var a=n.length,o=e?a:-1,s=Object(n);(e?o--:++o<a)&&!1!==i(s[o],o,s););return n}}},function(t,e,n){var r=n(64);t.exports=function(t,e){var n=[];return r(t,(function(t,r,i){e(t,r,i)&&n.push(t)})),n}},function(t,e,n){var r=n(257),i=n(265),a=n(134);t.exports=function(t){var e=i(t);return 1==e.length&&e[0][2]?a(e[0][0],e[0][1]):function(n){return n===t||r(n,t,e)}}},function(t,e,n){var r=n(53),i=n(129);t.exports=function(t,e,n,a){var o=n.length,s=o,c=!a;if(null==t)return!s;for(t=Object(t);o--;){var u=n[o];if(c&&u[2]?u[1]!==t[u[0]]:!(u[0]in t))return!1}for(;++o<s;){var l=(u=n[o])[0],h=t[l],f=u[1];if(c&&u[2]){if(void 0===h&&!(l in t))return!1}else{var d=new r;if(a)var p=a(h,f,l,t,e,d);if(!(void 0===p?i(f,h,3,a,d):p))return!1}}return!0}},function(t,e,n){var r=n(53),i=n(130),a=n(262),o=n(264),s=n(41),c=n(5),u=n(39),l=n(48),h="[object Object]",f=Object.prototype.hasOwnProperty;t.exports=function(t,e,n,d,p,g){var y=c(t),v=c(e),m=y?"[object Array]":s(t),b=v?"[object Array]":s(e),x=(m="[object Arguments]"==m?h:m)==h,_=(b="[object Arguments]"==b?h:b)==h,k=m==b;if(k&&u(t)){if(!u(e))return!1;y=!0,x=!1}if(k&&!x)return g||(g=new r),y||l(t)?i(t,e,n,d,p,g):a(t,e,m,n,d,p,g);if(!(1&n)){var w=x&&f.call(t,"__wrapped__"),E=_&&f.call(e,"__wrapped__");if(w||E){var T=w?t.value():t,C=E?e.value():e;return g||(g=new r),p(T,C,n,d,g)}}return!!k&&(g||(g=new r),o(t,e,n,d,p,g))}},function(t,e){t.exports=function(t){return this.__data__.set(t,"__lodash_hash_undefined__"),this}},function(t,e){t.exports=function(t){return this.__data__.has(t)}},function(t,e){t.exports=function(t,e){for(var n=-1,r=null==t?0:t.length;++n<r;)if(e(t[n],n,t))return!0;return!1}},function(t,e,n){var r=n(38),i=n(122),a=n(36),o=n(130),s=n(263),c=n(90),u=r?r.prototype:void 0,l=u?u.valueOf:void 0;t.exports=function(t,e,n,r,u,h,f){switch(n){case"[object DataView]":if(t.byteLength!=e.byteLength||t.byteOffset!=e.byteOffset)return!1;t=t.buffer,e=e.buffer;case"[object ArrayBuffer]":return!(t.byteLength!=e.byteLength||!h(new i(t),new i(e)));case"[object Boolean]":case"[object Date]":case"[object Number]":return a(+t,+e);case"[object Error]":return t.name==e.name&&t.message==e.message;case"[object RegExp]":case"[object String]":return t==e+"";case"[object Map]":var d=s;case"[object Set]":var p=1&r;if(d||(d=c),t.size!=e.size&&!p)return!1;var g=f.get(t);if(g)return g==e;r|=2,f.set(t,e);var y=o(d(t),d(e),r,u,h,f);return f.delete(t),y;case"[object Symbol]":if(l)return l.call(t)==l.call(e)}return!1}},function(t,e){t.exports=function(t){var e=-1,n=Array(t.size);return t.forEach((function(t,r){n[++e]=[r,t]})),n}},function(t,e,n){var r=n(119),i=Object.prototype.hasOwnProperty;t.exports=function(t,e,n,a,o,s){var c=1&n,u=r(t),l=u.length;if(l!=r(e).length&&!c)return!1;for(var h=l;h--;){var f=u[h];if(!(c?f in e:i.call(e,f)))return!1}var d=s.get(t);if(d&&s.get(e))return d==e;var p=!0;s.set(t,e),s.set(e,t);for(var g=c;++h<l;){var y=t[f=u[h]],v=e[f];if(a)var m=c?a(v,y,f,e,t,s):a(y,v,f,t,e,s);if(!(void 0===m?y===v||o(y,v,n,a,s):m)){p=!1;break}g||(g="constructor"==f)}if(p&&!g){var b=t.constructor,x=e.constructor;b!=x&&"constructor"in t&&"constructor"in e&&!("function"==typeof b&&b instanceof b&&"function"==typeof x&&x instanceof x)&&(p=!1)}return s.delete(t),s.delete(e),p}},function(t,e,n){var r=n(133),i=n(30);t.exports=function(t){for(var e=i(t),n=e.length;n--;){var a=e[n],o=t[a];e[n]=[a,o,r(o)]}return e}},function(t,e,n){var r=n(129),i=n(267),a=n(136),o=n(92),s=n(133),c=n(134),u=n(49);t.exports=function(t,e){return o(t)&&s(e)?c(u(t),e):function(n){var o=i(n,t);return void 0===o&&o===e?a(n,t):r(e,o,3)}}},function(t,e,n){var r=n(91);t.exports=function(t,e,n){var i=null==t?void 0:r(t,e);return void 0===i?n:i}},function(t,e,n){var r=n(269),i=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,a=/\\(\\)?/g,o=r((function(t){var e=[];return 46===t.charCodeAt(0)&&e.push(""),t.replace(i,(function(t,n,r,i){e.push(r?i.replace(a,"$1"):n||t)})),e}));t.exports=o},function(t,e,n){var r=n(270);t.exports=function(t){var e=r(t,(function(t){return 500===n.size&&n.clear(),t})),n=e.cache;return e}},function(t,e,n){var r=n(78);function i(t,e){if("function"!=typeof t||null!=e&&"function"!=typeof e)throw new TypeError("Expected a function");var n=function(){var r=arguments,i=e?e.apply(this,r):r[0],a=n.cache;if(a.has(i))return a.get(i);var o=t.apply(this,r);return n.cache=a.set(i,o)||a,o};return n.cache=new(i.Cache||r),n}i.Cache=r,t.exports=i},function(t,e,n){var r=n(38),i=n(66),a=n(5),o=n(42),s=r?r.prototype:void 0,c=s?s.toString:void 0;t.exports=function t(e){if("string"==typeof e)return e;if(a(e))return i(e,t)+"";if(o(e))return c?c.call(e):"";var n=e+"";return"0"==n&&1/e==-1/0?"-0":n}},function(t,e){t.exports=function(t,e){return null!=t&&e in Object(t)}},function(t,e,n){var r=n(138),i=n(274),a=n(92),o=n(49);t.exports=function(t){return a(t)?r(o(t)):i(t)}},function(t,e,n){var r=n(91);t.exports=function(t){return function(e){return r(e,t)}}},function(t,e){var n=Object.prototype.hasOwnProperty;t.exports=function(t,e){return null!=t&&n.call(t,e)}},function(t,e,n){var r=n(82),i=n(41),a=n(47),o=n(5),s=n(24),c=n(39),u=n(62),l=n(48),h=Object.prototype.hasOwnProperty;t.exports=function(t){if(null==t)return!0;if(s(t)&&(o(t)||"string"==typeof t||"function"==typeof t.splice||c(t)||l(t)||a(t)))return!t.length;var e=i(t);if("[object Map]"==e||"[object Set]"==e)return!t.size;if(u(t))return!r(t).length;for(var n in t)if(h.call(t,n))return!1;return!0}},function(t,e){t.exports=function(t,e,n,r){var i=-1,a=null==t?0:t.length;for(r&&a&&(n=t[++i]);++i<a;)n=e(n,t[i],i,t);return n}},function(t,e){t.exports=function(t,e,n,r,i){return i(t,(function(t,i,a){n=r?(r=!1,t):e(n,t,i,a)})),n}},function(t,e,n){var r=n(82),i=n(41),a=n(24),o=n(280),s=n(281);t.exports=function(t){if(null==t)return 0;if(a(t))return o(t)?s(t):t.length;var e=i(t);return"[object Map]"==e||"[object Set]"==e?t.size:r(t).length}},function(t,e,n){var r=n(34),i=n(5),a=n(21);t.exports=function(t){return"string"==typeof t||!i(t)&&a(t)&&"[object String]"==r(t)}},function(t,e,n){var r=n(282),i=n(283),a=n(284);t.exports=function(t){return i(t)?a(t):r(t)}},function(t,e,n){var r=n(138)("length");t.exports=r},function(t,e){var n=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]");t.exports=function(t){return n.test(t)}},function(t,e){var n="[\\ud800-\\udfff]",r="[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]",i="\\ud83c[\\udffb-\\udfff]",a="[^\\ud800-\\udfff]",o="(?:\\ud83c[\\udde6-\\uddff]){2}",s="[\\ud800-\\udbff][\\udc00-\\udfff]",c="(?:"+r+"|"+i+")"+"?",u="[\\ufe0e\\ufe0f]?"+c+("(?:\\u200d(?:"+[a,o,s].join("|")+")[\\ufe0e\\ufe0f]?"+c+")*"),l="(?:"+[a+r+"?",r,o,s,n].join("|")+")",h=RegExp(i+"(?="+i+")|"+l+u,"g");t.exports=function(t){for(var e=h.lastIndex=0;h.test(t);)++e;return e}},function(t,e,n){var r=n(79),i=n(125),a=n(88),o=n(25),s=n(63),c=n(5),u=n(39),l=n(37),h=n(11),f=n(48);t.exports=function(t,e,n){var d=c(t),p=d||u(t)||f(t);if(e=o(e,4),null==n){var g=t&&t.constructor;n=p?d?new g:[]:h(t)&&l(g)?i(s(t)):{}}return(p?r:a)(t,(function(t,r,i){return e(n,t,r,i)})),n}},function(t,e,n){var r=n(94),i=n(67),a=n(291),o=n(146),s=i((function(t){return a(r(t,1,o,!0))}));t.exports=s},function(t,e,n){var r=n(38),i=n(47),a=n(5),o=r?r.isConcatSpreadable:void 0;t.exports=function(t){return a(t)||i(t)||!!(o&&t&&t[o])}},function(t,e){t.exports=function(t,e,n){switch(n.length){case 0:return t.call(e);case 1:return t.call(e,n[0]);case 2:return t.call(e,n[0],n[1]);case 3:return t.call(e,n[0],n[1],n[2])}return t.apply(e,n)}},function(t,e,n){var r=n(86),i=n(111),a=n(35),o=i?function(t,e){return i(t,"toString",{configurable:!0,enumerable:!1,value:r(e),writable:!0})}:a;t.exports=o},function(t,e){var n=Date.now;t.exports=function(t){var e=0,r=0;return function(){var i=n(),a=16-(i-r);if(r=i,a>0){if(++e>=800)return arguments[0]}else e=0;return t.apply(void 0,arguments)}}},function(t,e,n){var r=n(131),i=n(292),a=n(296),o=n(132),s=n(297),c=n(90);t.exports=function(t,e,n){var u=-1,l=i,h=t.length,f=!0,d=[],p=d;if(n)f=!1,l=a;else if(h>=200){var g=e?null:s(t);if(g)return c(g);f=!1,l=o,p=new r}else p=e?[]:d;t:for(;++u<h;){var y=t[u],v=e?e(y):y;if(y=n||0!==y?y:0,f&&v==v){for(var m=p.length;m--;)if(p[m]===v)continue t;e&&p.push(v),d.push(y)}else l(p,v,n)||(p!==d&&p.push(v),d.push(y))}return d}},function(t,e,n){var r=n(293);t.exports=function(t,e){return!!(null==t?0:t.length)&&r(t,e,0)>-1}},function(t,e,n){var r=n(145),i=n(294),a=n(295);t.exports=function(t,e,n){return e==e?a(t,e,n):r(t,i,n)}},function(t,e){t.exports=function(t){return t!=t}},function(t,e){t.exports=function(t,e,n){for(var r=n-1,i=t.length;++r<i;)if(t[r]===e)return r;return-1}},function(t,e){t.exports=function(t,e,n){for(var r=-1,i=null==t?0:t.length;++r<i;)if(n(e,t[r]))return!0;return!1}},function(t,e,n){var r=n(121),i=n(298),a=n(90),o=r&&1/a(new r([,-0]))[1]==1/0?function(t){return new r(t)}:i;t.exports=o},function(t,e){t.exports=function(){}},function(t,e,n){var r=n(66);t.exports=function(t,e){return r(e,(function(e){return t[e]}))}},function(t,e){t.exports="2.1.8"},function(t,e,n){var r=n(10),i=n(76);function a(t){return r.map(t.nodes(),(function(e){var n=t.node(e),i=t.parent(e),a={v:e};return r.isUndefined(n)||(a.value=n),r.isUndefined(i)||(a.parent=i),a}))}function o(t){return r.map(t.edges(),(function(e){var n=t.edge(e),i={v:e.v,w:e.w};return r.isUndefined(e.name)||(i.name=e.name),r.isUndefined(n)||(i.value=n),i}))}t.exports={write:function(t){var e={options:{directed:t.isDirected(),multigraph:t.isMultigraph(),compound:t.isCompound()},nodes:a(t),edges:o(t)};r.isUndefined(t.graph())||(e.value=r.clone(t.graph()));return e},read:function(t){var e=new i(t.options).setGraph(t.value);return r.each(t.nodes,(function(t){e.setNode(t.v,t.value),t.parent&&e.setParent(t.v,t.parent)})),r.each(t.edges,(function(t){e.setEdge({v:t.v,w:t.w,name:t.name},t.value)})),e}}},function(t,e,n){t.exports={components:n(303),dijkstra:n(148),dijkstraAll:n(304),findCycles:n(305),floydWarshall:n(306),isAcyclic:n(307),postorder:n(308),preorder:n(309),prim:n(310),tarjan:n(150),topsort:n(151)}},function(t,e,n){var r=n(10);t.exports=function(t){var e,n={},i=[];function a(i){r.has(n,i)||(n[i]=!0,e.push(i),r.each(t.successors(i),a),r.each(t.predecessors(i),a))}return r.each(t.nodes(),(function(t){e=[],a(t),e.length&&i.push(e)})),i}},function(t,e,n){var r=n(148),i=n(10);t.exports=function(t,e,n){return i.transform(t.nodes(),(function(i,a){i[a]=r(t,a,e,n)}),{})}},function(t,e,n){var r=n(10),i=n(150);t.exports=function(t){return r.filter(i(t),(function(e){return e.length>1||1===e.length&&t.hasEdge(e[0],e[0])}))}},function(t,e,n){var r=n(10);t.exports=function(t,e,n){return function(t,e,n){var r={},i=t.nodes();return i.forEach((function(t){r[t]={},r[t][t]={distance:0},i.forEach((function(e){t!==e&&(r[t][e]={distance:Number.POSITIVE_INFINITY})})),n(t).forEach((function(n){var i=n.v===t?n.w:n.v,a=e(n);r[t][i]={distance:a,predecessor:t}}))})),i.forEach((function(t){var e=r[t];i.forEach((function(n){var a=r[n];i.forEach((function(n){var r=a[t],i=e[n],o=a[n],s=r.distance+i.distance;s<o.distance&&(o.distance=s,o.predecessor=i.predecessor)}))}))})),r}(t,e||i,n||function(e){return t.outEdges(e)})};var i=r.constant(1)},function(t,e,n){var r=n(151);t.exports=function(t){try{r(t)}catch(t){if(t instanceof r.CycleException)return!1;throw t}return!0}},function(t,e,n){var r=n(152);t.exports=function(t,e){return r(t,e,"post")}},function(t,e,n){var r=n(152);t.exports=function(t,e){return r(t,e,"pre")}},function(t,e,n){var r=n(10),i=n(76),a=n(149);t.exports=function(t,e){var n,o=new i,s={},c=new a;function u(t){var r=t.v===n?t.w:t.v,i=c.priority(r);if(void 0!==i){var a=e(t);a<i&&(s[r]=n,c.decrease(r,a))}}if(0===t.nodeCount())return o;r.each(t.nodes(),(function(t){c.add(t,Number.POSITIVE_INFINITY),o.setNode(t)})),c.decrease(t.nodes()[0],0);var l=!1;for(;c.size()>0;){if(n=c.removeMin(),r.has(s,n))o.setEdge(n,s[n]);else{if(l)throw new Error("Input graph is not connected: "+t);l=!0}t.nodeEdges(n).forEach(u)}return o}},function(t,e,n){var r;try{r=n(3)}catch(t){}r||(r=window.graphlib),t.exports=r},function(t,e,n){"use strict";var r=n(4),i=n(345),a=n(348),o=n(349),s=n(8).normalizeRanks,c=n(351),u=n(8).removeEmptyRanks,l=n(352),h=n(353),f=n(354),d=n(355),p=n(364),g=n(8),y=n(17).Graph;t.exports=function(t,e){var n=e&&e.debugTiming?g.time:g.notime;n("layout",(function(){var e=n(" buildLayoutGraph",(function(){return function(t){var e=new y({multigraph:!0,compound:!0}),n=C(t.graph());return e.setGraph(r.merge({},m,T(n,v),r.pick(n,b))),r.forEach(t.nodes(),(function(n){var i=C(t.node(n));e.setNode(n,r.defaults(T(i,x),_)),e.setParent(n,t.parent(n))})),r.forEach(t.edges(),(function(n){var i=C(t.edge(n));e.setEdge(n,r.merge({},w,T(i,k),r.pick(i,E)))})),e}(t)}));n(" runLayout",(function(){!function(t,e){e(" makeSpaceForEdgeLabels",(function(){!function(t){var e=t.graph();e.ranksep/=2,r.forEach(t.edges(),(function(n){var r=t.edge(n);r.minlen*=2,"c"!==r.labelpos.toLowerCase()&&("TB"===e.rankdir||"BT"===e.rankdir?r.width+=r.labeloffset:r.height+=r.labeloffset)}))}(t)})),e(" removeSelfEdges",(function(){!function(t){r.forEach(t.edges(),(function(e){if(e.v===e.w){var n=t.node(e.v);n.selfEdges||(n.selfEdges=[]),n.selfEdges.push({e:e,label:t.edge(e)}),t.removeEdge(e)}}))}(t)})),e(" acyclic",(function(){i.run(t)})),e(" nestingGraph.run",(function(){l.run(t)})),e(" rank",(function(){o(g.asNonCompoundGraph(t))})),e(" injectEdgeLabelProxies",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(n.width&&n.height){var r=t.node(e.v),i={rank:(t.node(e.w).rank-r.rank)/2+r.rank,e:e};g.addDummyNode(t,"edge-proxy",i,"_ep")}}))}(t)})),e(" removeEmptyRanks",(function(){u(t)})),e(" nestingGraph.cleanup",(function(){l.cleanup(t)})),e(" normalizeRanks",(function(){s(t)})),e(" assignRankMinMax",(function(){!function(t){var e=0;r.forEach(t.nodes(),(function(n){var i=t.node(n);i.borderTop&&(i.minRank=t.node(i.borderTop).rank,i.maxRank=t.node(i.borderBottom).rank,e=r.max(e,i.maxRank))})),t.graph().maxRank=e}(t)})),e(" removeEdgeLabelProxies",(function(){!function(t){r.forEach(t.nodes(),(function(e){var n=t.node(e);"edge-proxy"===n.dummy&&(t.edge(n.e).labelRank=n.rank,t.removeNode(e))}))}(t)})),e(" normalize.run",(function(){a.run(t)})),e(" parentDummyChains",(function(){c(t)})),e(" addBorderSegments",(function(){h(t)})),e(" order",(function(){d(t)})),e(" insertSelfEdges",(function(){!function(t){var e=g.buildLayerMatrix(t);r.forEach(e,(function(e){var n=0;r.forEach(e,(function(e,i){var a=t.node(e);a.order=i+n,r.forEach(a.selfEdges,(function(e){g.addDummyNode(t,"selfedge",{width:e.label.width,height:e.label.height,rank:a.rank,order:i+ ++n,e:e.e,label:e.label},"_se")})),delete a.selfEdges}))}))}(t)})),e(" adjustCoordinateSystem",(function(){f.adjust(t)})),e(" position",(function(){p(t)})),e(" positionSelfEdges",(function(){!function(t){r.forEach(t.nodes(),(function(e){var n=t.node(e);if("selfedge"===n.dummy){var r=t.node(n.e.v),i=r.x+r.width/2,a=r.y,o=n.x-i,s=r.height/2;t.setEdge(n.e,n.label),t.removeNode(e),n.label.points=[{x:i+2*o/3,y:a-s},{x:i+5*o/6,y:a-s},{x:i+o,y:a},{x:i+5*o/6,y:a+s},{x:i+2*o/3,y:a+s}],n.label.x=n.x,n.label.y=n.y}}))}(t)})),e(" removeBorderNodes",(function(){!function(t){r.forEach(t.nodes(),(function(e){if(t.children(e).length){var n=t.node(e),i=t.node(n.borderTop),a=t.node(n.borderBottom),o=t.node(r.last(n.borderLeft)),s=t.node(r.last(n.borderRight));n.width=Math.abs(s.x-o.x),n.height=Math.abs(a.y-i.y),n.x=o.x+n.width/2,n.y=i.y+n.height/2}})),r.forEach(t.nodes(),(function(e){"border"===t.node(e).dummy&&t.removeNode(e)}))}(t)})),e(" normalize.undo",(function(){a.undo(t)})),e(" fixupEdgeLabelCoords",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(r.has(n,"x"))switch("l"!==n.labelpos&&"r"!==n.labelpos||(n.width-=n.labeloffset),n.labelpos){case"l":n.x-=n.width/2+n.labeloffset;break;case"r":n.x+=n.width/2+n.labeloffset}}))}(t)})),e(" undoCoordinateSystem",(function(){f.undo(t)})),e(" translateGraph",(function(){!function(t){var e=Number.POSITIVE_INFINITY,n=0,i=Number.POSITIVE_INFINITY,a=0,o=t.graph(),s=o.marginx||0,c=o.marginy||0;function u(t){var r=t.x,o=t.y,s=t.width,c=t.height;e=Math.min(e,r-s/2),n=Math.max(n,r+s/2),i=Math.min(i,o-c/2),a=Math.max(a,o+c/2)}r.forEach(t.nodes(),(function(e){u(t.node(e))})),r.forEach(t.edges(),(function(e){var n=t.edge(e);r.has(n,"x")&&u(n)})),e-=s,i-=c,r.forEach(t.nodes(),(function(n){var r=t.node(n);r.x-=e,r.y-=i})),r.forEach(t.edges(),(function(n){var a=t.edge(n);r.forEach(a.points,(function(t){t.x-=e,t.y-=i})),r.has(a,"x")&&(a.x-=e),r.has(a,"y")&&(a.y-=i)})),o.width=n-e+s,o.height=a-i+c}(t)})),e(" assignNodeIntersects",(function(){!function(t){r.forEach(t.edges(),(function(e){var n,r,i=t.edge(e),a=t.node(e.v),o=t.node(e.w);i.points?(n=i.points[0],r=i.points[i.points.length-1]):(i.points=[],n=o,r=a),i.points.unshift(g.intersectRect(a,n)),i.points.push(g.intersectRect(o,r))}))}(t)})),e(" reversePoints",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);n.reversed&&n.points.reverse()}))}(t)})),e(" acyclic.undo",(function(){i.undo(t)}))}(e,n)})),n(" updateInputGraph",(function(){!function(t,e){r.forEach(t.nodes(),(function(n){var r=t.node(n),i=e.node(n);r&&(r.x=i.x,r.y=i.y,e.children(n).length&&(r.width=i.width,r.height=i.height))})),r.forEach(t.edges(),(function(n){var i=t.edge(n),a=e.edge(n);i.points=a.points,r.has(a,"x")&&(i.x=a.x,i.y=a.y)})),t.graph().width=e.graph().width,t.graph().height=e.graph().height}(t,e)}))}))};var v=["nodesep","edgesep","ranksep","marginx","marginy"],m={ranksep:50,edgesep:20,nodesep:50,rankdir:"tb"},b=["acyclicer","ranker","rankdir","align"],x=["width","height"],_={width:0,height:0},k=["minlen","weight","width","height","labeloffset"],w={minlen:1,weight:1,width:0,height:0,labeloffset:10,labelpos:"r"},E=["labelpos"];function T(t,e){return r.mapValues(r.pick(t,e),Number)}function C(t){var e={};return r.forEach(t,(function(t,n){e[n.toLowerCase()]=t})),e}},function(t,e,n){var r=n(108);t.exports=function(t){return r(t,5)}},function(t,e,n){var r=n(315)(n(316));t.exports=r},function(t,e,n){var r=n(25),i=n(24),a=n(30);t.exports=function(t){return function(e,n,o){var s=Object(e);if(!i(e)){var c=r(n,3);e=a(e),n=function(t){return c(s[t],t,s)}}var u=t(e,n,o);return u>-1?s[c?e[u]:u]:void 0}}},function(t,e,n){var r=n(145),i=n(25),a=n(317),o=Math.max;t.exports=function(t,e,n){var s=null==t?0:t.length;if(!s)return-1;var c=null==n?0:a(n);return c<0&&(c=o(s+c,0)),r(t,i(e,3),c)}},function(t,e,n){var r=n(155);t.exports=function(t){var e=r(t),n=e%1;return e==e?n?e-n:e:0}},function(t,e,n){var r=n(11),i=n(42),a=/^\s+|\s+$/g,o=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,c=/^0o[0-7]+$/i,u=parseInt;t.exports=function(t){if("number"==typeof t)return t;if(i(t))return NaN;if(r(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=r(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(a,"");var n=s.test(t);return n||c.test(t)?u(t.slice(2),n?2:8):o.test(t)?NaN:+t}},function(t,e,n){var r=n(89),i=n(127),a=n(40);t.exports=function(t,e){return null==t?t:r(t,i(e),a)}},function(t,e){t.exports=function(t){var e=null==t?0:t.length;return e?t[e-1]:void 0}},function(t,e,n){var r=n(59),i=n(88),a=n(25);t.exports=function(t,e){var n={};return e=a(e,3),i(t,(function(t,i,a){r(n,i,e(t,i,a))})),n}},function(t,e,n){var r=n(95),i=n(323),a=n(35);t.exports=function(t){return t&&t.length?r(t,a,i):void 0}},function(t,e){t.exports=function(t,e){return t>e}},function(t,e,n){var r=n(325),i=n(328)((function(t,e,n){r(t,e,n)}));t.exports=i},function(t,e,n){var r=n(53),i=n(157),a=n(89),o=n(326),s=n(11),c=n(40),u=n(159);t.exports=function t(e,n,l,h,f){e!==n&&a(n,(function(a,c){if(f||(f=new r),s(a))o(e,n,c,l,t,h,f);else{var d=h?h(u(e,c),a,c+"",e,n,f):void 0;void 0===d&&(d=a),i(e,c,d)}}),c)}},function(t,e,n){var r=n(157),i=n(114),a=n(123),o=n(115),s=n(124),c=n(47),u=n(5),l=n(146),h=n(39),f=n(37),d=n(11),p=n(158),g=n(48),y=n(159),v=n(327);t.exports=function(t,e,n,m,b,x,_){var k=y(t,n),w=y(e,n),E=_.get(w);if(E)r(t,n,E);else{var T=x?x(k,w,n+"",t,e,_):void 0,C=void 0===T;if(C){var A=u(w),S=!A&&h(w),M=!A&&!S&&g(w);T=w,A||S||M?u(k)?T=k:l(k)?T=o(k):S?(C=!1,T=i(w,!0)):M?(C=!1,T=a(w,!0)):T=[]:p(w)||c(w)?(T=k,c(k)?T=v(k):d(k)&&!f(k)||(T=s(w))):C=!1}C&&(_.set(w,T),b(T,w,m,x,_),_.delete(w)),r(t,n,T)}}},function(t,e,n){var r=n(46),i=n(40);t.exports=function(t){return r(t,i(t))}},function(t,e,n){var r=n(67),i=n(68);t.exports=function(t){return r((function(e,n){var r=-1,a=n.length,o=a>1?n[a-1]:void 0,s=a>2?n[2]:void 0;for(o=t.length>3&&"function"==typeof o?(a--,o):void 0,s&&i(n[0],n[1],s)&&(o=a<3?void 0:o,a=1),e=Object(e);++r<a;){var c=n[r];c&&t(e,c,r,o)}return e}))}},function(t,e,n){var r=n(95),i=n(160),a=n(35);t.exports=function(t){return t&&t.length?r(t,a,i):void 0}},function(t,e,n){var r=n(95),i=n(25),a=n(160);t.exports=function(t,e){return t&&t.length?r(t,i(e,2),a):void 0}},function(t,e,n){var r=n(16);t.exports=function(){return r.Date.now()}},function(t,e,n){var r=n(333),i=n(136);t.exports=function(t,e){return r(t,e,(function(e,n){return i(t,n)}))}},function(t,e,n){var r=n(91),i=n(334),a=n(65);t.exports=function(t,e,n){for(var o=-1,s=e.length,c={};++o<s;){var u=e[o],l=r(t,u);n(l,u)&&i(c,a(u,t),l)}return c}},function(t,e,n){var r=n(58),i=n(65),a=n(60),o=n(11),s=n(49);t.exports=function(t,e,n,c){if(!o(t))return t;for(var u=-1,l=(e=i(e,t)).length,h=l-1,f=t;null!=f&&++u<l;){var d=s(e[u]),p=n;if(u!=h){var g=f[d];void 0===(p=c?c(g,d,f):void 0)&&(p=o(g)?g:a(e[u+1])?[]:{})}r(f,d,p),f=f[d]}return t}},function(t,e,n){var r=n(156),i=n(143),a=n(144);t.exports=function(t){return a(i(t,void 0,r),t+"")}},function(t,e,n){var r=n(337),i=n(68),a=n(155);t.exports=function(t){return function(e,n,o){return o&&"number"!=typeof o&&i(e,n,o)&&(n=o=void 0),e=a(e),void 0===n?(n=e,e=0):n=a(n),o=void 0===o?e<n?1:-1:a(o),r(e,n,o,t)}}},function(t,e){var n=Math.ceil,r=Math.max;t.exports=function(t,e,i,a){for(var o=-1,s=r(n((e-t)/(i||1)),0),c=Array(s);s--;)c[a?s:++o]=t,t+=i;return c}},function(t,e,n){var r=n(94),i=n(339),a=n(67),o=n(68),s=a((function(t,e){if(null==t)return[];var n=e.length;return n>1&&o(t,e[0],e[1])?e=[]:n>2&&o(e[0],e[1],e[2])&&(e=[e[0]]),i(t,r(e,1),[])}));t.exports=s},function(t,e,n){var r=n(66),i=n(25),a=n(141),o=n(340),s=n(61),c=n(341),u=n(35);t.exports=function(t,e,n){var l=-1;e=r(e.length?e:[u],s(i));var h=a(t,(function(t,n,i){return{criteria:r(e,(function(e){return e(t)})),index:++l,value:t}}));return o(h,(function(t,e){return c(t,e,n)}))}},function(t,e){t.exports=function(t,e){var n=t.length;for(t.sort(e);n--;)t[n]=t[n].value;return t}},function(t,e,n){var r=n(342);t.exports=function(t,e,n){for(var i=-1,a=t.criteria,o=e.criteria,s=a.length,c=n.length;++i<s;){var u=r(a[i],o[i]);if(u)return i>=c?u:u*("desc"==n[i]?-1:1)}return t.index-e.index}},function(t,e,n){var r=n(42);t.exports=function(t,e){if(t!==e){var n=void 0!==t,i=null===t,a=t==t,o=r(t),s=void 0!==e,c=null===e,u=e==e,l=r(e);if(!c&&!l&&!o&&t>e||o&&s&&u&&!c&&!l||i&&s&&u||!n&&u||!a)return 1;if(!i&&!o&&!l&&t<e||l&&n&&a&&!i&&!o||c&&n&&a||!s&&a||!u)return-1}return 0}},function(t,e,n){var r=n(58),i=n(344);t.exports=function(t,e){return i(t||[],e||[],r)}},function(t,e){t.exports=function(t,e,n){for(var r=-1,i=t.length,a=e.length,o={};++r<i;){var s=r<a?e[r]:void 0;n(o,t[r],s)}return o}},function(t,e,n){"use strict";var r=n(4),i=n(346);t.exports={run:function(t){var e="greedy"===t.graph().acyclicer?i(t,function(t){return function(e){return t.edge(e).weight}}(t)):function(t){var e=[],n={},i={};function a(o){r.has(i,o)||(i[o]=!0,n[o]=!0,r.forEach(t.outEdges(o),(function(t){r.has(n,t.w)?e.push(t):a(t.w)})),delete n[o])}return r.forEach(t.nodes(),a),e}(t);r.forEach(e,(function(e){var n=t.edge(e);t.removeEdge(e),n.forwardName=e.name,n.reversed=!0,t.setEdge(e.w,e.v,n,r.uniqueId("rev"))}))},undo:function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(n.reversed){t.removeEdge(e);var r=n.forwardName;delete n.reversed,delete n.forwardName,t.setEdge(e.w,e.v,n,r)}}))}}},function(t,e,n){var r=n(4),i=n(17).Graph,a=n(347);t.exports=function(t,e){if(t.nodeCount()<=1)return[];var n=function(t,e){var n=new i,o=0,s=0;r.forEach(t.nodes(),(function(t){n.setNode(t,{v:t,in:0,out:0})})),r.forEach(t.edges(),(function(t){var r=n.edge(t.v,t.w)||0,i=e(t),a=r+i;n.setEdge(t.v,t.w,a),s=Math.max(s,n.node(t.v).out+=i),o=Math.max(o,n.node(t.w).in+=i)}));var u=r.range(s+o+3).map((function(){return new a})),l=o+1;return r.forEach(n.nodes(),(function(t){c(u,l,n.node(t))})),{graph:n,buckets:u,zeroIdx:l}}(t,e||o),u=function(t,e,n){var r,i=[],a=e[e.length-1],o=e[0];for(;t.nodeCount();){for(;r=o.dequeue();)s(t,e,n,r);for(;r=a.dequeue();)s(t,e,n,r);if(t.nodeCount())for(var c=e.length-2;c>0;--c)if(r=e[c].dequeue()){i=i.concat(s(t,e,n,r,!0));break}}return i}(n.graph,n.buckets,n.zeroIdx);return r.flatten(r.map(u,(function(e){return t.outEdges(e.v,e.w)})),!0)};var o=r.constant(1);function s(t,e,n,i,a){var o=a?[]:void 0;return r.forEach(t.inEdges(i.v),(function(r){var i=t.edge(r),s=t.node(r.v);a&&o.push({v:r.v,w:r.w}),s.out-=i,c(e,n,s)})),r.forEach(t.outEdges(i.v),(function(r){var i=t.edge(r),a=r.w,o=t.node(a);o.in-=i,c(e,n,o)})),t.removeNode(i.v),o}function c(t,e,n){n.out?n.in?t[n.out-n.in+e].enqueue(n):t[t.length-1].enqueue(n):t[0].enqueue(n)}},function(t,e){function n(){var t={};t._next=t._prev=t,this._sentinel=t}function r(t){t._prev._next=t._next,t._next._prev=t._prev,delete t._next,delete t._prev}function i(t,e){if("_next"!==t&&"_prev"!==t)return e}t.exports=n,n.prototype.dequeue=function(){var t=this._sentinel,e=t._prev;if(e!==t)return r(e),e},n.prototype.enqueue=function(t){var e=this._sentinel;t._prev&&t._next&&r(t),t._next=e._next,e._next._prev=t,e._next=t,t._prev=e},n.prototype.toString=function(){for(var t=[],e=this._sentinel,n=e._prev;n!==e;)t.push(JSON.stringify(n,i)),n=n._prev;return"["+t.join(", ")+"]"}},function(t,e,n){"use strict";var r=n(4),i=n(8);t.exports={run:function(t){t.graph().dummyChains=[],r.forEach(t.edges(),(function(e){!function(t,e){var n,r,a,o=e.v,s=t.node(o).rank,c=e.w,u=t.node(c).rank,l=e.name,h=t.edge(e),f=h.labelRank;if(u===s+1)return;for(t.removeEdge(e),a=0,++s;s<u;++a,++s)h.points=[],r={width:0,height:0,edgeLabel:h,edgeObj:e,rank:s},n=i.addDummyNode(t,"edge",r,"_d"),s===f&&(r.width=h.width,r.height=h.height,r.dummy="edge-label",r.labelpos=h.labelpos),t.setEdge(o,n,{weight:h.weight},l),0===a&&t.graph().dummyChains.push(n),o=n;t.setEdge(o,c,{weight:h.weight},l)}(t,e)}))},undo:function(t){r.forEach(t.graph().dummyChains,(function(e){var n,r=t.node(e),i=r.edgeLabel;for(t.setEdge(r.edgeObj,i);r.dummy;)n=t.successors(e)[0],t.removeNode(e),i.points.push({x:r.x,y:r.y}),"edge-label"===r.dummy&&(i.x=r.x,i.y=r.y,i.width=r.width,i.height=r.height),e=n,r=t.node(e)}))}}},function(t,e,n){"use strict";var r=n(69).longestPath,i=n(164),a=n(350);t.exports=function(t){switch(t.graph().ranker){case"network-simplex":s(t);break;case"tight-tree":!function(t){r(t),i(t)}(t);break;case"longest-path":o(t);break;default:s(t)}};var o=r;function s(t){a(t)}},function(t,e,n){"use strict";var r=n(4),i=n(164),a=n(69).slack,o=n(69).longestPath,s=n(17).alg.preorder,c=n(17).alg.postorder,u=n(8).simplify;function l(t){t=u(t),o(t);var e,n=i(t);for(d(n),h(n,t);e=g(n);)v(n,t,e,y(n,t,e))}function h(t,e){var n=c(t,t.nodes());n=n.slice(0,n.length-1),r.forEach(n,(function(n){!function(t,e,n){var r=t.node(n).parent;t.edge(n,r).cutvalue=f(t,e,n)}(t,e,n)}))}function f(t,e,n){var i=t.node(n).parent,a=!0,o=e.edge(n,i),s=0;return o||(a=!1,o=e.edge(i,n)),s=o.weight,r.forEach(e.nodeEdges(n),(function(r){var o,c,u=r.v===n,l=u?r.w:r.v;if(l!==i){var h=u===a,f=e.edge(r).weight;if(s+=h?f:-f,o=n,c=l,t.hasEdge(o,c)){var d=t.edge(n,l).cutvalue;s+=h?-d:d}}})),s}function d(t,e){arguments.length<2&&(e=t.nodes()[0]),p(t,{},1,e)}function p(t,e,n,i,a){var o=n,s=t.node(i);return e[i]=!0,r.forEach(t.neighbors(i),(function(a){r.has(e,a)||(n=p(t,e,n,a,i))})),s.low=o,s.lim=n++,a?s.parent=a:delete s.parent,n}function g(t){return r.find(t.edges(),(function(e){return t.edge(e).cutvalue<0}))}function y(t,e,n){var i=n.v,o=n.w;e.hasEdge(i,o)||(i=n.w,o=n.v);var s=t.node(i),c=t.node(o),u=s,l=!1;s.lim>c.lim&&(u=c,l=!0);var h=r.filter(e.edges(),(function(e){return l===m(t,t.node(e.v),u)&&l!==m(t,t.node(e.w),u)}));return r.minBy(h,(function(t){return a(e,t)}))}function v(t,e,n,i){var a=n.v,o=n.w;t.removeEdge(a,o),t.setEdge(i.v,i.w,{}),d(t),h(t,e),function(t,e){var n=r.find(t.nodes(),(function(t){return!e.node(t).parent})),i=s(t,n);i=i.slice(1),r.forEach(i,(function(n){var r=t.node(n).parent,i=e.edge(n,r),a=!1;i||(i=e.edge(r,n),a=!0),e.node(n).rank=e.node(r).rank+(a?i.minlen:-i.minlen)}))}(t,e)}function m(t,e,n){return n.low<=e.lim&&e.lim<=n.lim}t.exports=l,l.initLowLimValues=d,l.initCutValues=h,l.calcCutValue=f,l.leaveEdge=g,l.enterEdge=y,l.exchangeEdges=v},function(t,e,n){var r=n(4);t.exports=function(t){var e=function(t){var e={},n=0;function i(a){var o=n;r.forEach(t.children(a),i),e[a]={low:o,lim:n++}}return r.forEach(t.children(),i),e}(t);r.forEach(t.graph().dummyChains,(function(n){for(var r=t.node(n),i=r.edgeObj,a=function(t,e,n,r){var i,a,o=[],s=[],c=Math.min(e[n].low,e[r].low),u=Math.max(e[n].lim,e[r].lim);i=n;do{i=t.parent(i),o.push(i)}while(i&&(e[i].low>c||u>e[i].lim));a=i,i=r;for(;(i=t.parent(i))!==a;)s.push(i);return{path:o.concat(s.reverse()),lca:a}}(t,e,i.v,i.w),o=a.path,s=a.lca,c=0,u=o[c],l=!0;n!==i.w;){if(r=t.node(n),l){for(;(u=o[c])!==s&&t.node(u).maxRank<r.rank;)c++;u===s&&(l=!1)}if(!l){for(;c<o.length-1&&t.node(u=o[c+1]).minRank<=r.rank;)c++;u=o[c]}t.setParent(n,u),n=t.successors(n)[0]}}))}},function(t,e,n){var r=n(4),i=n(8);t.exports={run:function(t){var e=i.addDummyNode(t,"root",{},"_root"),n=function(t){var e={};return r.forEach(t.children(),(function(n){!function n(i,a){var o=t.children(i);o&&o.length&&r.forEach(o,(function(t){n(t,a+1)}));e[i]=a}(n,1)})),e}(t),a=r.max(r.values(n))-1,o=2*a+1;t.graph().nestingRoot=e,r.forEach(t.edges(),(function(e){t.edge(e).minlen*=o}));var s=function(t){return r.reduce(t.edges(),(function(e,n){return e+t.edge(n).weight}),0)}(t)+1;r.forEach(t.children(),(function(c){!function t(e,n,a,o,s,c,u){var l=e.children(u);if(!l.length)return void(u!==n&&e.setEdge(n,u,{weight:0,minlen:a}));var h=i.addBorderNode(e,"_bt"),f=i.addBorderNode(e,"_bb"),d=e.node(u);e.setParent(h,u),d.borderTop=h,e.setParent(f,u),d.borderBottom=f,r.forEach(l,(function(r){t(e,n,a,o,s,c,r);var i=e.node(r),l=i.borderTop?i.borderTop:r,d=i.borderBottom?i.borderBottom:r,p=i.borderTop?o:2*o,g=l!==d?1:s-c[u]+1;e.setEdge(h,l,{weight:p,minlen:g,nestingEdge:!0}),e.setEdge(d,f,{weight:p,minlen:g,nestingEdge:!0})})),e.parent(u)||e.setEdge(n,h,{weight:0,minlen:s+c[u]})}(t,e,o,s,a,n,c)})),t.graph().nodeRankFactor=o},cleanup:function(t){var e=t.graph();t.removeNode(e.nestingRoot),delete e.nestingRoot,r.forEach(t.edges(),(function(e){t.edge(e).nestingEdge&&t.removeEdge(e)}))}}},function(t,e,n){var r=n(4),i=n(8);function a(t,e,n,r,a,o){var s={width:0,height:0,rank:o,borderType:e},c=a[e][o-1],u=i.addDummyNode(t,"border",s,n);a[e][o]=u,t.setParent(u,r),c&&t.setEdge(c,u,{weight:1})}t.exports=function(t){r.forEach(t.children(),(function e(n){var i=t.children(n),o=t.node(n);if(i.length&&r.forEach(i,e),r.has(o,"minRank")){o.borderLeft=[],o.borderRight=[];for(var s=o.minRank,c=o.maxRank+1;s<c;++s)a(t,"borderLeft","_bl",n,o,s),a(t,"borderRight","_br",n,o,s)}}))}},function(t,e,n){"use strict";var r=n(4);function i(t){r.forEach(t.nodes(),(function(e){a(t.node(e))})),r.forEach(t.edges(),(function(e){a(t.edge(e))}))}function a(t){var e=t.width;t.width=t.height,t.height=e}function o(t){t.y=-t.y}function s(t){var e=t.x;t.x=t.y,t.y=e}t.exports={adjust:function(t){var e=t.graph().rankdir.toLowerCase();"lr"!==e&&"rl"!==e||i(t)},undo:function(t){var e=t.graph().rankdir.toLowerCase();"bt"!==e&&"rl"!==e||function(t){r.forEach(t.nodes(),(function(e){o(t.node(e))})),r.forEach(t.edges(),(function(e){var n=t.edge(e);r.forEach(n.points,o),r.has(n,"y")&&o(n)}))}(t);"lr"!==e&&"rl"!==e||(!function(t){r.forEach(t.nodes(),(function(e){s(t.node(e))})),r.forEach(t.edges(),(function(e){var n=t.edge(e);r.forEach(n.points,s),r.has(n,"x")&&s(n)}))}(t),i(t))}}},function(t,e,n){"use strict";var r=n(4),i=n(356),a=n(357),o=n(358),s=n(362),c=n(363),u=n(17).Graph,l=n(8);function h(t,e,n){return r.map(e,(function(e){return s(t,e,n)}))}function f(t,e){var n=new u;r.forEach(t,(function(t){var i=t.graph().root,a=o(t,i,n,e);r.forEach(a.vs,(function(e,n){t.node(e).order=n})),c(t,n,a.vs)}))}function d(t,e){r.forEach(e,(function(e){r.forEach(e,(function(e,n){t.node(e).order=n}))}))}t.exports=function(t){var e=l.maxRank(t),n=h(t,r.range(1,e+1),"inEdges"),o=h(t,r.range(e-1,-1,-1),"outEdges"),s=i(t);d(t,s);for(var c,u=Number.POSITIVE_INFINITY,p=0,g=0;g<4;++p,++g){f(p%2?n:o,p%4>=2),s=l.buildLayerMatrix(t);var y=a(t,s);y<u&&(g=0,c=r.cloneDeep(s),u=y)}d(t,c)}},function(t,e,n){"use strict";var r=n(4);t.exports=function(t){var e={},n=r.filter(t.nodes(),(function(e){return!t.children(e).length})),i=r.max(r.map(n,(function(e){return t.node(e).rank}))),a=r.map(r.range(i+1),(function(){return[]}));var o=r.sortBy(n,(function(e){return t.node(e).rank}));return r.forEach(o,(function n(i){if(r.has(e,i))return;e[i]=!0;var o=t.node(i);a[o.rank].push(i),r.forEach(t.successors(i),n)})),a}},function(t,e,n){"use strict";var r=n(4);function i(t,e,n){for(var i=r.zipObject(n,r.map(n,(function(t,e){return e}))),a=r.flatten(r.map(e,(function(e){return r.sortBy(r.map(t.outEdges(e),(function(e){return{pos:i[e.w],weight:t.edge(e).weight}})),"pos")})),!0),o=1;o<n.length;)o<<=1;var s=2*o-1;o-=1;var c=r.map(new Array(s),(function(){return 0})),u=0;return r.forEach(a.forEach((function(t){var e=t.pos+o;c[e]+=t.weight;for(var n=0;e>0;)e%2&&(n+=c[e+1]),c[e=e-1>>1]+=t.weight;u+=t.weight*n}))),u}t.exports=function(t,e){for(var n=0,r=1;r<e.length;++r)n+=i(t,e[r-1],e[r]);return n}},function(t,e,n){var r=n(4),i=n(359),a=n(360),o=n(361);t.exports=function t(e,n,s,c){var u=e.children(n),l=e.node(n),h=l?l.borderLeft:void 0,f=l?l.borderRight:void 0,d={};h&&(u=r.filter(u,(function(t){return t!==h&&t!==f})));var p=i(e,u);r.forEach(p,(function(n){if(e.children(n.v).length){var i=t(e,n.v,s,c);d[n.v]=i,r.has(i,"barycenter")&&(a=n,o=i,r.isUndefined(a.barycenter)?(a.barycenter=o.barycenter,a.weight=o.weight):(a.barycenter=(a.barycenter*a.weight+o.barycenter*o.weight)/(a.weight+o.weight),a.weight+=o.weight))}var a,o}));var g=a(p,s);!function(t,e){r.forEach(t,(function(t){t.vs=r.flatten(t.vs.map((function(t){return e[t]?e[t].vs:t})),!0)}))}(g,d);var y=o(g,c);if(h&&(y.vs=r.flatten([h,y.vs,f],!0),e.predecessors(h).length)){var v=e.node(e.predecessors(h)[0]),m=e.node(e.predecessors(f)[0]);r.has(y,"barycenter")||(y.barycenter=0,y.weight=0),y.barycenter=(y.barycenter*y.weight+v.order+m.order)/(y.weight+2),y.weight+=2}return y}},function(t,e,n){var r=n(4);t.exports=function(t,e){return r.map(e,(function(e){var n=t.inEdges(e);if(n.length){var i=r.reduce(n,(function(e,n){var r=t.edge(n),i=t.node(n.v);return{sum:e.sum+r.weight*i.order,weight:e.weight+r.weight}}),{sum:0,weight:0});return{v:e,barycenter:i.sum/i.weight,weight:i.weight}}return{v:e}}))}},function(t,e,n){"use strict";var r=n(4);t.exports=function(t,e){var n={};return r.forEach(t,(function(t,e){var i=n[t.v]={indegree:0,in:[],out:[],vs:[t.v],i:e};r.isUndefined(t.barycenter)||(i.barycenter=t.barycenter,i.weight=t.weight)})),r.forEach(e.edges(),(function(t){var e=n[t.v],i=n[t.w];r.isUndefined(e)||r.isUndefined(i)||(i.indegree++,e.out.push(n[t.w]))})),function(t){var e=[];function n(t){return function(e){e.merged||(r.isUndefined(e.barycenter)||r.isUndefined(t.barycenter)||e.barycenter>=t.barycenter)&&function(t,e){var n=0,r=0;t.weight&&(n+=t.barycenter*t.weight,r+=t.weight);e.weight&&(n+=e.barycenter*e.weight,r+=e.weight);t.vs=e.vs.concat(t.vs),t.barycenter=n/r,t.weight=r,t.i=Math.min(e.i,t.i),e.merged=!0}(t,e)}}function i(e){return function(n){n.in.push(e),0==--n.indegree&&t.push(n)}}for(;t.length;){var a=t.pop();e.push(a),r.forEach(a.in.reverse(),n(a)),r.forEach(a.out,i(a))}return r.map(r.filter(e,(function(t){return!t.merged})),(function(t){return r.pick(t,["vs","i","barycenter","weight"])}))}(r.filter(n,(function(t){return!t.indegree})))}},function(t,e,n){var r=n(4),i=n(8);function a(t,e,n){for(var i;e.length&&(i=r.last(e)).i<=n;)e.pop(),t.push(i.vs),n++;return n}t.exports=function(t,e){var n=i.partition(t,(function(t){return r.has(t,"barycenter")})),o=n.lhs,s=r.sortBy(n.rhs,(function(t){return-t.i})),c=[],u=0,l=0,h=0;o.sort((f=!!e,function(t,e){return t.barycenter<e.barycenter?-1:t.barycenter>e.barycenter?1:f?e.i-t.i:t.i-e.i})),h=a(c,s,h),r.forEach(o,(function(t){h+=t.vs.length,c.push(t.vs),u+=t.barycenter*t.weight,l+=t.weight,h=a(c,s,h)}));var f;var d={vs:r.flatten(c,!0)};l&&(d.barycenter=u/l,d.weight=l);return d}},function(t,e,n){var r=n(4),i=n(17).Graph;t.exports=function(t,e,n){var a=function(t){var e;for(;t.hasNode(e=r.uniqueId("_root")););return e}(t),o=new i({compound:!0}).setGraph({root:a}).setDefaultNodeLabel((function(e){return t.node(e)}));return r.forEach(t.nodes(),(function(i){var s=t.node(i),c=t.parent(i);(s.rank===e||s.minRank<=e&&e<=s.maxRank)&&(o.setNode(i),o.setParent(i,c||a),r.forEach(t[n](i),(function(e){var n=e.v===i?e.w:e.v,a=o.edge(n,i),s=r.isUndefined(a)?0:a.weight;o.setEdge(n,i,{weight:t.edge(e).weight+s})})),r.has(s,"minRank")&&o.setNode(i,{borderLeft:s.borderLeft[e],borderRight:s.borderRight[e]}))})),o}},function(t,e,n){var r=n(4);t.exports=function(t,e,n){var i,a={};r.forEach(n,(function(n){for(var r,o,s=t.parent(n);s;){if((r=t.parent(s))?(o=a[r],a[r]=s):(o=i,i=s),o&&o!==s)return void e.setEdge(o,s);s=r}}))}},function(t,e,n){"use strict";var r=n(4),i=n(8),a=n(365).positionX;t.exports=function(t){(function(t){var e=i.buildLayerMatrix(t),n=t.graph().ranksep,a=0;r.forEach(e,(function(e){var i=r.max(r.map(e,(function(e){return t.node(e).height})));r.forEach(e,(function(e){t.node(e).y=a+i/2})),a+=i+n}))})(t=i.asNonCompoundGraph(t)),r.forEach(a(t),(function(e,n){t.node(n).x=e}))}},function(t,e,n){"use strict";var r=n(4),i=n(17).Graph,a=n(8);function o(t,e){var n={};return r.reduce(e,(function(e,i){var a=0,o=0,s=e.length,u=r.last(i);return r.forEach(i,(function(e,l){var h=function(t,e){if(t.node(e).dummy)return r.find(t.predecessors(e),(function(e){return t.node(e).dummy}))}(t,e),f=h?t.node(h).order:s;(h||e===u)&&(r.forEach(i.slice(o,l+1),(function(e){r.forEach(t.predecessors(e),(function(r){var i=t.node(r),o=i.order;!(o<a||f<o)||i.dummy&&t.node(e).dummy||c(n,r,e)}))})),o=l+1,a=f)})),i})),n}function s(t,e){var n={};function i(e,i,a,o,s){var u;r.forEach(r.range(i,a),(function(i){u=e[i],t.node(u).dummy&&r.forEach(t.predecessors(u),(function(e){var r=t.node(e);r.dummy&&(r.order<o||r.order>s)&&c(n,e,u)}))}))}return r.reduce(e,(function(e,n){var a,o=-1,s=0;return r.forEach(n,(function(r,c){if("border"===t.node(r).dummy){var u=t.predecessors(r);u.length&&(a=t.node(u[0]).order,i(n,s,c,o,a),s=c,o=a)}i(n,s,n.length,a,e.length)})),n})),n}function c(t,e,n){if(e>n){var r=e;e=n,n=r}var i=t[e];i||(t[e]=i={}),i[n]=!0}function u(t,e,n){if(e>n){var i=e;e=n,n=i}return r.has(t[e],n)}function l(t,e,n,i){var a={},o={},s={};return r.forEach(e,(function(t){r.forEach(t,(function(t,e){a[t]=t,o[t]=t,s[t]=e}))})),r.forEach(e,(function(t){var e=-1;r.forEach(t,(function(t){var c=i(t);if(c.length)for(var l=((c=r.sortBy(c,(function(t){return s[t]}))).length-1)/2,h=Math.floor(l),f=Math.ceil(l);h<=f;++h){var d=c[h];o[t]===t&&e<s[d]&&!u(n,t,d)&&(o[d]=t,o[t]=a[t]=a[d],e=s[d])}}))})),{root:a,align:o}}function h(t,e,n,a,o){var s={},c=function(t,e,n,a){var o=new i,s=t.graph(),c=function(t,e,n){return function(i,a,o){var s,c=i.node(a),u=i.node(o),l=0;if(l+=c.width/2,r.has(c,"labelpos"))switch(c.labelpos.toLowerCase()){case"l":s=-c.width/2;break;case"r":s=c.width/2}if(s&&(l+=n?s:-s),s=0,l+=(c.dummy?e:t)/2,l+=(u.dummy?e:t)/2,l+=u.width/2,r.has(u,"labelpos"))switch(u.labelpos.toLowerCase()){case"l":s=u.width/2;break;case"r":s=-u.width/2}return s&&(l+=n?s:-s),s=0,l}}(s.nodesep,s.edgesep,a);return r.forEach(e,(function(e){var i;r.forEach(e,(function(e){var r=n[e];if(o.setNode(r),i){var a=n[i],s=o.edge(a,r);o.setEdge(a,r,Math.max(c(t,e,i),s||0))}i=e}))})),o}(t,e,n,o),u=o?"borderLeft":"borderRight";function l(t,e){for(var n=c.nodes(),r=n.pop(),i={};r;)i[r]?t(r):(i[r]=!0,n.push(r),n=n.concat(e(r))),r=n.pop()}return l((function(t){s[t]=c.inEdges(t).reduce((function(t,e){return Math.max(t,s[e.v]+c.edge(e))}),0)}),c.predecessors.bind(c)),l((function(e){var n=c.outEdges(e).reduce((function(t,e){return Math.min(t,s[e.w]-c.edge(e))}),Number.POSITIVE_INFINITY),r=t.node(e);n!==Number.POSITIVE_INFINITY&&r.borderType!==u&&(s[e]=Math.max(s[e],n))}),c.successors.bind(c)),r.forEach(a,(function(t){s[t]=s[n[t]]})),s}function f(t,e){return r.minBy(r.values(e),(function(e){var n=Number.NEGATIVE_INFINITY,i=Number.POSITIVE_INFINITY;return r.forIn(e,(function(e,r){var a=function(t,e){return t.node(e).width}(t,r)/2;n=Math.max(e+a,n),i=Math.min(e-a,i)})),n-i}))}function d(t,e){var n=r.values(e),i=r.min(n),a=r.max(n);r.forEach(["u","d"],(function(n){r.forEach(["l","r"],(function(o){var s,c=n+o,u=t[c];if(u!==e){var l=r.values(u);(s="l"===o?i-r.min(l):a-r.max(l))&&(t[c]=r.mapValues(u,(function(t){return t+s})))}}))}))}function p(t,e){return r.mapValues(t.ul,(function(n,i){if(e)return t[e.toLowerCase()][i];var a=r.sortBy(r.map(t,i));return(a[1]+a[2])/2}))}t.exports={positionX:function(t){var e,n=a.buildLayerMatrix(t),i=r.merge(o(t,n),s(t,n)),c={};r.forEach(["u","d"],(function(a){e="u"===a?n:r.values(n).reverse(),r.forEach(["l","r"],(function(n){"r"===n&&(e=r.map(e,(function(t){return r.values(t).reverse()})));var o=("u"===a?t.predecessors:t.successors).bind(t),s=l(t,e,i,o),u=h(t,e,s.root,s.align,"r"===n);"r"===n&&(u=r.mapValues(u,(function(t){return-t}))),c[a+n]=u}))}));var u=f(t,c);return d(c,u),p(c,t.graph().align)},findType1Conflicts:o,findType2Conflicts:s,addConflict:c,hasConflict:u,verticalAlignment:l,horizontalCompaction:h,alignCoordinates:d,findSmallestWidthAlignment:f,balance:p}},function(t,e,n){var r=n(4),i=n(8),a=n(17).Graph;t.exports={debugOrdering:function(t){var e=i.buildLayerMatrix(t),n=new a({compound:!0,multigraph:!0}).setGraph({});return r.forEach(t.nodes(),(function(e){n.setNode(e,{label:e}),n.setParent(e,"layer"+t.node(e).rank)})),r.forEach(t.edges(),(function(t){n.setEdge(t.v,t.w,{},t.name)})),r.forEach(e,(function(t,e){var i="layer"+e;n.setNode(i,{rank:"same"}),r.reduce(t,(function(t,e){return n.setEdge(t,e,{style:"invis"}),e}))})),n}}},function(t,e){t.exports="0.8.5"},function(t,e,n){t.exports={node:n(165),circle:n(166),ellipse:n(96),polygon:n(167),rect:n(168)}},function(t,e){function n(t,e){return t*e>0}t.exports=function(t,e,r,i){var a,o,s,c,u,l,h,f,d,p,g,y,v;if(a=e.y-t.y,s=t.x-e.x,u=e.x*t.y-t.x*e.y,d=a*r.x+s*r.y+u,p=a*i.x+s*i.y+u,0!==d&&0!==p&&n(d,p))return;if(o=i.y-r.y,c=r.x-i.x,l=i.x*r.y-r.x*i.y,h=o*t.x+c*t.y+l,f=o*e.x+c*e.y+l,0!==h&&0!==f&&n(h,f))return;if(0===(g=a*c-o*s))return;return y=Math.abs(g/2),{x:(v=s*l-c*u)<0?(v-y)/g:(v+y)/g,y:(v=o*u-a*l)<0?(v-y)/g:(v+y)/g}}},function(t,e,n){var r=n(43),i=n(31),a=n(153).layout;t.exports=function(){var t=n(371),e=n(374),i=n(375),u=n(376),l=n(377),h=n(378),f=n(379),d=n(380),p=n(381),g=function(n,g){!function(t){t.nodes().forEach((function(e){var n=t.node(e);r.has(n,"label")||t.children(e).length||(n.label=e),r.has(n,"paddingX")&&r.defaults(n,{paddingLeft:n.paddingX,paddingRight:n.paddingX}),r.has(n,"paddingY")&&r.defaults(n,{paddingTop:n.paddingY,paddingBottom:n.paddingY}),r.has(n,"padding")&&r.defaults(n,{paddingLeft:n.padding,paddingRight:n.padding,paddingTop:n.padding,paddingBottom:n.padding}),r.defaults(n,o),r.each(["paddingLeft","paddingRight","paddingTop","paddingBottom"],(function(t){n[t]=Number(n[t])})),r.has(n,"width")&&(n._prevWidth=n.width),r.has(n,"height")&&(n._prevHeight=n.height)})),t.edges().forEach((function(e){var n=t.edge(e);r.has(n,"label")||(n.label=""),r.defaults(n,s)}))}(g);var y=c(n,"output"),v=c(y,"clusters"),m=c(y,"edgePaths"),b=i(c(y,"edgeLabels"),g),x=t(c(y,"nodes"),g,d);a(g),l(x,g),h(b,g),u(m,g,p);var _=e(v,g);f(_,g),function(t){r.each(t.nodes(),(function(e){var n=t.node(e);r.has(n,"_prevWidth")?n.width=n._prevWidth:delete n.width,r.has(n,"_prevHeight")?n.height=n._prevHeight:delete n.height,delete n._prevWidth,delete n._prevHeight}))}(g)};return g.createNodes=function(e){return arguments.length?(t=e,g):t},g.createClusters=function(t){return arguments.length?(e=t,g):e},g.createEdgeLabels=function(t){return arguments.length?(i=t,g):i},g.createEdgePaths=function(t){return arguments.length?(u=t,g):u},g.shapes=function(t){return arguments.length?(d=t,g):d},g.arrows=function(t){return arguments.length?(p=t,g):p},g};var o={paddingLeft:10,paddingRight:10,paddingTop:10,paddingBottom:10,rx:0,ry:0,shape:"rect"},s={arrowhead:"normal",curve:i.curveLinear};function c(t,e){var n=t.select("g."+e);return n.empty()&&(n=t.append("g").attr("class",e)),n}},function(t,e,n){"use strict";var r=n(43),i=n(97),a=n(12),o=n(31);t.exports=function(t,e,n){var s,c=e.nodes().filter((function(t){return!a.isSubgraph(e,t)})),u=t.selectAll("g.node").data(c,(function(t){return t})).classed("update",!0);u.exit().remove(),u.enter().append("g").attr("class","node").style("opacity",0),(u=t.selectAll("g.node")).each((function(t){var s=e.node(t),c=o.select(this);a.applyClass(c,s.class,(c.classed("update")?"update ":"")+"node"),c.select("g.label").remove();var u=c.append("g").attr("class","label"),l=i(u,s),h=n[s.shape],f=r.pick(l.node().getBBox(),"width","height");s.elem=this,s.id&&c.attr("id",s.id),s.labelId&&u.attr("id",s.labelId),r.has(s,"width")&&(f.width=s.width),r.has(s,"height")&&(f.height=s.height),f.width+=s.paddingLeft+s.paddingRight,f.height+=s.paddingTop+s.paddingBottom,u.attr("transform","translate("+(s.paddingLeft-s.paddingRight)/2+","+(s.paddingTop-s.paddingBottom)/2+")");var d=o.select(this);d.select(".label-container").remove();var p=h(d,f,s).classed("label-container",!0);a.applyStyle(p,s.style);var g=p.node().getBBox();s.width=g.width,s.height=g.height})),s=u.exit?u.exit():u.selectAll(null);return a.applyTransition(s,e).style("opacity",0).remove(),u}},function(t,e,n){var r=n(12);t.exports=function(t,e){for(var n=t.append("text"),i=function(t){for(var e,n="",r=!1,i=0;i<t.length;++i)if(e=t[i],r){switch(e){case"n":n+="\n";break;default:n+=e}r=!1}else"\\"===e?r=!0:n+=e;return n}(e.label).split("\n"),a=0;a<i.length;a++)n.append("tspan").attr("xml:space","preserve").attr("dy","1em").attr("x","1").text(i[a]);return r.applyStyle(n,e.labelStyle),n}},function(t,e,n){var r=n(12);t.exports=function(t,e){var n=t;return n.node().appendChild(e.label),r.applyStyle(n,e.labelStyle),n}},function(t,e,n){var r=n(12),i=n(31),a=n(97);t.exports=function(t,e){var n,o=e.nodes().filter((function(t){return r.isSubgraph(e,t)})),s=t.selectAll("g.cluster").data(o,(function(t){return t}));s.selectAll("*").remove(),s.enter().append("g").attr("class","cluster").attr("id",(function(t){return e.node(t).id})).style("opacity",0),s=t.selectAll("g.cluster"),r.applyTransition(s,e).style("opacity",1),s.each((function(t){var n=e.node(t),r=i.select(this);i.select(this).append("rect");var o=r.append("g").attr("class","label");a(o,n,n.clusterLabelPos)})),s.selectAll("rect").each((function(t){var n=e.node(t),a=i.select(this);r.applyStyle(a,n.style)})),n=s.exit?s.exit():s.selectAll(null);return r.applyTransition(n,e).style("opacity",0).remove(),s}},function(t,e,n){"use strict";var r=n(43),i=n(97),a=n(12),o=n(31);t.exports=function(t,e){var n,s=t.selectAll("g.edgeLabel").data(e.edges(),(function(t){return a.edgeToId(t)})).classed("update",!0);s.exit().remove(),s.enter().append("g").classed("edgeLabel",!0).style("opacity",0),(s=t.selectAll("g.edgeLabel")).each((function(t){var n=o.select(this);n.select(".label").remove();var a=e.edge(t),s=i(n,e.edge(t),0,0).classed("label",!0),c=s.node().getBBox();a.labelId&&s.attr("id",a.labelId),r.has(a,"width")||(a.width=c.width),r.has(a,"height")||(a.height=c.height)})),n=s.exit?s.exit():s.selectAll(null);return a.applyTransition(n,e).style("opacity",0).remove(),s}},function(t,e,n){"use strict";var r=n(43),i=n(165),a=n(12),o=n(31);function s(t,e){var n=(o.line||o.svg.line)().x((function(t){return t.x})).y((function(t){return t.y}));return(n.curve||n.interpolate)(t.curve),n(e)}t.exports=function(t,e,n){var c=t.selectAll("g.edgePath").data(e.edges(),(function(t){return a.edgeToId(t)})).classed("update",!0),u=function(t,e){var n=t.enter().append("g").attr("class","edgePath").style("opacity",0);return n.append("path").attr("class","path").attr("d",(function(t){var n=e.edge(t),i=e.node(t.v).elem;return s(n,r.range(n.points.length).map((function(){return e=(t=i).getBBox(),{x:(n=t.ownerSVGElement.getScreenCTM().inverse().multiply(t.getScreenCTM()).translate(e.width/2,e.height/2)).e,y:n.f};var t,e,n})))})),n.append("defs"),n}(c,e);!function(t,e){var n=t.exit();a.applyTransition(n,e).style("opacity",0).remove()}(c,e);var l=void 0!==c.merge?c.merge(u):c;return a.applyTransition(l,e).style("opacity",1),l.each((function(t){var n=o.select(this),r=e.edge(t);r.elem=this,r.id&&n.attr("id",r.id),a.applyClass(n,r.class,(n.classed("update")?"update ":"")+"edgePath")})),l.selectAll("path.path").each((function(t){var n=e.edge(t);n.arrowheadId=r.uniqueId("arrowhead");var c=o.select(this).attr("marker-end",(function(){return"url("+(t=location.href,e=n.arrowheadId,t.split("#")[0]+"#"+e)+")";var t,e})).style("fill","none");a.applyTransition(c,e).attr("d",(function(t){return function(t,e){var n=t.edge(e),r=t.node(e.v),a=t.node(e.w),o=n.points.slice(1,n.points.length-1);return o.unshift(i(r,o[0])),o.push(i(a,o[o.length-1])),s(n,o)}(e,t)})),a.applyStyle(c,n.style)})),l.selectAll("defs *").remove(),l.selectAll("defs").each((function(t){var r=e.edge(t);(0,n[r.arrowhead])(o.select(this),r.arrowheadId,r,"arrowhead")})),l}},function(t,e,n){"use strict";var r=n(12),i=n(31);t.exports=function(t,e){function n(t){var n=e.node(t);return"translate("+n.x+","+n.y+")"}t.filter((function(){return!i.select(this).classed("update")})).attr("transform",n),r.applyTransition(t,e).style("opacity",1).attr("transform",n)}},function(t,e,n){"use strict";var r=n(12),i=n(31),a=n(43);t.exports=function(t,e){function n(t){var n=e.edge(t);return a.has(n,"x")?"translate("+n.x+","+n.y+")":""}t.filter((function(){return!i.select(this).classed("update")})).attr("transform",n),r.applyTransition(t,e).style("opacity",1).attr("transform",n)}},function(t,e,n){"use strict";var r=n(12),i=n(31);t.exports=function(t,e){var n=t.filter((function(){return!i.select(this).classed("update")}));function a(t){var n=e.node(t);return"translate("+n.x+","+n.y+")"}n.attr("transform",a),r.applyTransition(t,e).style("opacity",1).attr("transform",a),r.applyTransition(n.selectAll("rect"),e).attr("width",(function(t){return e.node(t).width})).attr("height",(function(t){return e.node(t).height})).attr("x",(function(t){return-e.node(t).width/2})).attr("y",(function(t){return-e.node(t).height/2}))}},function(t,e,n){"use strict";var r=n(168),i=n(96),a=n(166),o=n(167);t.exports={rect:function(t,e,n){var i=t.insert("rect",":first-child").attr("rx",n.rx).attr("ry",n.ry).attr("x",-e.width/2).attr("y",-e.height/2).attr("width",e.width).attr("height",e.height);return n.intersect=function(t){return r(n,t)},i},ellipse:function(t,e,n){var r=e.width/2,a=e.height/2,o=t.insert("ellipse",":first-child").attr("x",-e.width/2).attr("y",-e.height/2).attr("rx",r).attr("ry",a);return n.intersect=function(t){return i(n,r,a,t)},o},circle:function(t,e,n){var r=Math.max(e.width,e.height)/2,i=t.insert("circle",":first-child").attr("x",-e.width/2).attr("y",-e.height/2).attr("r",r);return n.intersect=function(t){return a(n,r,t)},i},diamond:function(t,e,n){var r=e.width*Math.SQRT2/2,i=e.height*Math.SQRT2/2,a=[{x:0,y:-i},{x:-r,y:0},{x:0,y:i},{x:r,y:0}],s=t.insert("polygon",":first-child").attr("points",a.map((function(t){return t.x+","+t.y})).join(" "));return n.intersect=function(t){return o(n,a,t)},s}}},function(t,e,n){var r=n(12);function i(t,e,n,i){var a=t.append("marker").attr("id",e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","strokeWidth").attr("markerWidth",8).attr("markerHeight",6).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z").style("stroke-width",1).style("stroke-dasharray","1,0");r.applyStyle(a,n[i+"Style"]),n[i+"Class"]&&a.attr("class",n[i+"Class"])}t.exports={default:i,normal:i,vee:function(t,e,n,i){var a=t.append("marker").attr("id",e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","strokeWidth").attr("markerWidth",8).attr("markerHeight",6).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 L 4 5 z").style("stroke-width",1).style("stroke-dasharray","1,0");r.applyStyle(a,n[i+"Style"]),n[i+"Class"]&&a.attr("class",n[i+"Class"])},undirected:function(t,e,n,i){var a=t.append("marker").attr("id",e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","strokeWidth").attr("markerWidth",8).attr("markerHeight",6).attr("orient","auto").append("path").attr("d","M 0 5 L 10 5").style("stroke-width",1).style("stroke-dasharray","1,0");r.applyStyle(a,n[i+"Style"]),n[i+"Class"]&&a.attr("class",n[i+"Class"])}}},function(t,e){t.exports="0.6.4"},function(t,e,n){"use strict";var r;function i(t){return r=r||document.createElement("div"),t=escape(t).replace(/%26/g,"&").replace(/%23/g,"#").replace(/%3B/g,";"),r.innerHTML=t,unescape(r.textContent)}n.r(e);var a=n(23),o=n.n(a),s={debug:1,info:2,warn:3,error:4,fatal:5},c={debug:function(){},info:function(){},warn:function(){},error:function(){},fatal:function(){}},u=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"fatal";isNaN(t)&&(t=t.toLowerCase(),void 0!==s[t]&&(t=s[t])),c.trace=function(){},c.debug=function(){},c.info=function(){},c.warn=function(){},c.error=function(){},c.fatal=function(){},t<=s.fatal&&(c.fatal=console.error?console.error.bind(console,l("FATAL"),"color: orange"):console.log.bind(console,"",l("FATAL"))),t<=s.error&&(c.error=console.error?console.error.bind(console,l("ERROR"),"color: orange"):console.log.bind(console,"",l("ERROR"))),t<=s.warn&&(c.warn=console.warn?console.warn.bind(console,l("WARN"),"color: orange"):console.log.bind(console,"",l("WARN"))),t<=s.info&&(c.info=console.info?console.info.bind(console,l("INFO"),"color: lightblue"):console.log.bind(console,"",l("INFO"))),t<=s.debug&&(c.debug=console.debug?console.debug.bind(console,l("DEBUG"),"color: lightgreen"):console.log.bind(console,"",l("DEBUG")))},l=function(t){var e=o()().format("ss.SSS");return"%c".concat(e," : ").concat(t," : ")},h=n(169),f=n.n(h),d=n(0),p=n(44),g=n(70),y=function(t){for(var e="",n=0;n>=0;){if(!((n=t.indexOf("<script"))>=0)){e+=t,n=-1;break}e+=t.substr(0,n),(n=(t=t.substr(n+1)).indexOf("<\/script>"))>=0&&(n+=9,t=t.substr(n))}return e},v=/<br\s*\/?>/gi,m=function(t){return t.replace(v,"#br#")},b=function(t){return t.replace(/#br#/g,"<br/>")},x={getRows:function(t){if(!t)return 1;var e=m(t);return(e=e.replace(/\\n/g,"#br#")).split("#br#")},sanitizeText:function(t,e){var n=t,r=!0;if(!e.flowchart||!1!==e.flowchart.htmlLabels&&"false"!==e.flowchart.htmlLabels||(r=!1),r){var i=e.securityLevel;"antiscript"===i?n=y(n):"loose"!==i&&(n=(n=(n=m(n)).replace(/</g,"&lt;").replace(/>/g,"&gt;")).replace(/=/g,"&equals;"),n=b(n))}return n},hasBreaks:function(t){return/<br\s*[/]?>/gi.test(t)},splitBreaks:function(t){return t.split(/<br\s*[/]?>/gi)},lineBreakRegex:v,removeScript:y};function _(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}function k(t){return(k="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function w(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e<t.length;e++)n[e]=t[e];return n}}(t)||function(t){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t))return Array.from(t)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance")}()}var E={curveBasis:d.curveBasis,curveBasisClosed:d.curveBasisClosed,curveBasisOpen:d.curveBasisOpen,curveLinear:d.curveLinear,curveLinearClosed:d.curveLinearClosed,curveMonotoneX:d.curveMonotoneX,curveMonotoneY:d.curveMonotoneY,curveNatural:d.curveNatural,curveStep:d.curveStep,curveStepAfter:d.curveStepAfter,curveStepBefore:d.curveStepBefore},T=/[%]{2}[{]\s*(?:(?:(\w+)\s*:|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi,C=/\s*(?:(?:(\w+)(?=:):|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi,A=/\s*%%.*\n/gm,S=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;try{var n=new RegExp("[%]{2}(?![{]".concat(C.source,")(?=[}][%]{2}).*\n"),"ig");t=t.trim().replace(n,"").replace(/'/gm,'"'),c.debug("Detecting diagram directive".concat(null!==e?" type:"+e:""," based on the text:").concat(t));for(var r,i=[];null!==(r=T.exec(t));)if(r.index===T.lastIndex&&T.lastIndex++,r&&!e||e&&r[1]&&r[1].match(e)||e&&r[2]&&r[2].match(e)){var a=r[1]?r[1]:r[2],o=r[3]?r[3].trim():r[4]?JSON.parse(r[4].trim()):null;i.push({type:a,args:o})}return 0===i.length&&i.push({type:t,args:null}),1===i.length?i[0]:i}catch(n){return c.error("ERROR: ".concat(n.message," - Unable to parse directive").concat(null!==e?" type:"+e:""," based on the text:").concat(t)),{type:null,args:null}}},M=function(t){return t=t.replace(T,"").replace(A,"\n"),c.debug("Detecting diagram type based on the text "+t),t.match(/^\s*sequenceDiagram/)?"sequence":t.match(/^\s*gantt/)?"gantt":t.match(/^\s*classDiagram-v2/)?"classDiagram":t.match(/^\s*classDiagram/)?"class":t.match(/^\s*stateDiagram-v2/)?"stateDiagram":t.match(/^\s*stateDiagram/)?"state":t.match(/^\s*gitGraph/)?"git":t.match(/^\s*flowchart/)?"flowchart-v2":t.match(/^\s*info/)?"info":t.match(/^\s*pie/)?"pie":t.match(/^\s*erDiagram/)?"er":t.match(/^\s*journey/)?"journey":"flowchart"},O=function(t,e){var n={};return function(){for(var r=arguments.length,i=new Array(r),a=0;a<r;a++)i[a]=arguments[a];var o=e?e.apply(void 0,i):i[0];if(o in n)return n[o];var s=t.apply(void 0,i);return n[o]=s,s}},D=function(t,e){if(!t)return e;var n="curve".concat(t.charAt(0).toUpperCase()+t.slice(1));return E[n]||e},N=function(t,e){return t&&e?Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2)):0},B=function(t){for(var e="",n="",r=0;r<t.length;r++)void 0!==t[r]&&(t[r].startsWith("color:")||t[r].startsWith("text-align:")?n=n+t[r]+";":e=e+t[r]+";");return{style:e,labelStyle:n}},L=0,F=function(){return L++,"id-"+Math.random().toString(36).substr(2,12)+"-"+L};var P=function(t){return function(t){for(var e="",n="0123456789abcdef".length,r=0;r<t;r++)e+="0123456789abcdef".charAt(Math.floor(Math.random()*n));return e}(t.length)},I=function t(e,n,r){var i=Object.assign({depth:2,clobber:!1},r),a=i.depth,o=i.clobber;return Array.isArray(n)&&!Array.isArray(e)?(n.forEach((function(n){return t(e,n,r)})),e):Array.isArray(n)&&Array.isArray(e)?(n.forEach((function(t){-1===e.indexOf(t)&&e.push(t)})),e):void 0===e||a<=0?null!=e&&"object"===k(e)&&"object"===k(n)?Object.assign(e,n):n:(void 0!==n&&"object"===k(e)&&"object"===k(n)&&Object.keys(n).forEach((function(r){"object"!==k(n[r])||void 0!==e[r]&&"object"!==k(e[r])?(o||"object"!==k(e[r])&&"object"!==k(n[r]))&&(e[r]=n[r]):(void 0===e[r]&&(e[r]=Array.isArray(n[r])?[]:{}),e[r]=t(e[r],n[r],{depth:a-1,clobber:o}))})),e)},j=function(t,e){var n=e.text.replace(x.lineBreakRegex," "),r=t.append("text");r.attr("x",e.x),r.attr("y",e.y),r.style("text-anchor",e.anchor),r.style("font-family",e.fontFamily),r.style("font-size",e.fontSize),r.style("font-weight",e.fontWeight),r.attr("fill",e.fill),void 0!==e.class&&r.attr("class",e.class);var i=r.append("tspan");return i.attr("x",e.x+2*e.textMargin),i.attr("fill",e.fill),i.text(n),r},R=O((function(t,e,n){if(!t)return t;if(n=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial",joinWith:"<br/>"},n),x.lineBreakRegex.test(t))return t;var r=t.split(" "),i=[],a="";return r.forEach((function(t,o){var s=z("".concat(t," "),n),c=z(a,n);if(s>e){var u=Y(t,e,"-",n),l=u.hyphenatedStrings,h=u.remainingWord;i.push.apply(i,[a].concat(w(l))),a=h}else c+s>=e?(i.push(a),a=t):a=[a,t].filter(Boolean).join(" ");o+1===r.length&&i.push(a)})),i.filter((function(t){return""!==t})).join(n.joinWith)}),(function(t,e,n){return"".concat(t,"-").concat(e,"-").concat(n.fontSize,"-").concat(n.fontWeight,"-").concat(n.fontFamily,"-").concat(n.joinWith)})),Y=O((function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"-",r=arguments.length>3?arguments[3]:void 0;r=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial",margin:0},r);var i=t.split(""),a=[],o="";return i.forEach((function(t,s){var c="".concat(o).concat(t);if(z(c,r)>=e){var u=s+1,l=i.length===u,h="".concat(c).concat(n);a.push(l?c:h),o=""}else o=c})),{hyphenatedStrings:a,remainingWord:o}}),(function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"-",r=arguments.length>3?arguments[3]:void 0;return"".concat(t,"-").concat(e,"-").concat(n,"-").concat(r.fontSize,"-").concat(r.fontWeight,"-").concat(r.fontFamily)})),z=function(t,e){return e=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial"},e),U(t,e).width},U=O((function(t,e){var n=e=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial"},e),r=n.fontSize,i=n.fontFamily,a=n.fontWeight;if(!t)return{width:0,height:0};var o=["sans-serif",i],s=t.split(x.lineBreakRegex),c=[],u=Object(d.select)("body");if(!u.remove)return{width:0,height:0,lineHeight:0};for(var l=u.append("svg"),h=0,f=o;h<f.length;h++){var p=f[h],g=0,y={width:0,height:0,lineHeight:0},v=!0,m=!1,b=void 0;try{for(var _,k=s[Symbol.iterator]();!(v=(_=k.next()).done);v=!0){var w=_.value,E={x:0,y:0,fill:void 0,anchor:"start",style:"#666",width:100,height:100,textMargin:0,rx:0,ry:0,valign:void 0};E.text=w;var T=j(l,E).style("font-size",r).style("font-weight",a).style("font-family",p),C=(T._groups||T)[0][0].getBBox();y.width=Math.round(Math.max(y.width,C.width)),g=Math.round(C.height),y.height+=g,y.lineHeight=Math.round(Math.max(y.lineHeight,g))}}catch(t){m=!0,b=t}finally{try{v||null==k.return||k.return()}finally{if(m)throw b}}c.push(y)}return l.remove(),c[isNaN(c[1].height)||isNaN(c[1].width)||isNaN(c[1].lineHeight)||c[0].height>c[1].height&&c[0].width>c[1].width&&c[0].lineHeight>c[1].lineHeight?0:1]}),(function(t,e){return"".concat(t,"-").concat(e.fontSize,"-").concat(e.fontWeight,"-").concat(e.fontFamily)})),$=function(t,e,n){var r=new Map;return r.set("height",t),n?(r.set("width","100%"),r.set("style","max-width: ".concat(e,"px;"))):r.set("width",e),r},W=function(t,e,n,r){!function(t,e){var n=!0,r=!1,i=void 0;try{for(var a,o=e[Symbol.iterator]();!(n=(a=o.next()).done);n=!0){var s=a.value;t.attr(s[0],s[1])}}catch(t){r=!0,i=t}finally{try{n||null==o.return||o.return()}finally{if(r)throw i}}}(t,$(e,n,r))},H={assignWithDepth:I,wrapLabel:R,calculateTextHeight:function(t,e){return e=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial",margin:15},e),U(t,e).height},calculateTextWidth:z,calculateTextDimensions:U,calculateSvgSizeAttrs:$,configureSvgSize:W,detectInit:function(t){var e=S(t,/(?:init\b)|(?:initialize\b)/),n={};if(Array.isArray(e)){var r=e.map((function(t){return t.args}));n=I(n,w(r))}else n=e.args;if(n){var i=M(t);["config"].forEach((function(t){void 0!==n[t]&&("flowchart-v2"===i&&(i="flowchart"),n[i]=n[t],delete n[t])}))}return n},detectDirective:S,detectType:M,isSubstringInArray:function(t,e){for(var n=0;n<e.length;n++)if(e[n].match(t))return n;return-1},interpolateToCurve:D,calcLabelPosition:function(t){return function(t){var e,n=0;t.forEach((function(t){n+=N(t,e),e=t}));var r=n/2,i=void 0;return e=void 0,t.forEach((function(t){if(e&&!i){var n=N(t,e);if(n<r)r-=n;else{var a=r/n;a<=0&&(i=e),a>=1&&(i={x:t.x,y:t.y}),a>0&&a<1&&(i={x:(1-a)*e.x+a*t.x,y:(1-a)*e.y+a*t.y})}}e=t})),i}(t)},calcCardinalityPosition:function(t,e,n){var r;c.info("our points",e),e[0]!==n&&(e=e.reverse()),e.forEach((function(t){N(t,r),r=t}));var i,a=25;r=void 0,e.forEach((function(t){if(r&&!i){var e=N(t,r);if(e<a)a-=e;else{var n=a/e;n<=0&&(i=r),n>=1&&(i={x:t.x,y:t.y}),n>0&&n<1&&(i={x:(1-n)*r.x+n*t.x,y:(1-n)*r.y+n*t.y})}}r=t}));var o=t?10:5,s=Math.atan2(e[0].y-i.y,e[0].x-i.x),u={x:0,y:0};return u.x=Math.sin(s)*o+(e[0].x+i.x)/2,u.y=-Math.cos(s)*o+(e[0].y+i.y)/2,u},calcTerminalLabelPosition:function(t,e,n){var r,i=JSON.parse(JSON.stringify(n));c.info("our points",i),"start_left"!==e&&"start_right"!==e&&(i=i.reverse()),i.forEach((function(t){N(t,r),r=t}));var a,o=25;r=void 0,i.forEach((function(t){if(r&&!a){var e=N(t,r);if(e<o)o-=e;else{var n=o/e;n<=0&&(a=r),n>=1&&(a={x:t.x,y:t.y}),n>0&&n<1&&(a={x:(1-n)*r.x+n*t.x,y:(1-n)*r.y+n*t.y})}}r=t}));var s=10,u=Math.atan2(i[0].y-a.y,i[0].x-a.x),l={x:0,y:0};return l.x=Math.sin(u)*s+(i[0].x+a.x)/2,l.y=-Math.cos(u)*s+(i[0].y+a.y)/2,"start_left"===e&&(l.x=Math.sin(u+Math.PI)*s+(i[0].x+a.x)/2,l.y=-Math.cos(u+Math.PI)*s+(i[0].y+a.y)/2),"end_right"===e&&(l.x=Math.sin(u-Math.PI)*s+(i[0].x+a.x)/2-5,l.y=-Math.cos(u-Math.PI)*s+(i[0].y+a.y)/2-5),"end_left"===e&&(l.x=Math.sin(u)*s+(i[0].x+a.x)/2-5,l.y=-Math.cos(u)*s+(i[0].y+a.y)/2-5),l},formatUrl:function(t,e){var n=t.trim();if(n)return"loose"!==e.securityLevel?Object(g.sanitizeUrl)(n):n},getStylesFromArray:B,generateId:F,random:P,memoize:O,runFunc:function(t){for(var e,n=t.split("."),r=n.length-1,i=n[r],a=window,o=0;o<r;o++)if(!(a=a[n[o]]))return;for(var s=arguments.length,c=new Array(s>1?s-1:0),u=1;u<s;u++)c[u-1]=arguments[u];(e=a)[i].apply(e,c)},initIdGeneratior:function(t,e){return t?new(function(){function t(){return function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.count=e?e.length:0}var n,r,i;return n=t,(r=[{key:"next",value:function(){return this.count++}}])&&_(n.prototype,r),i&&_(n,i),t}()):{next:function(){return Date.now()}}}},V=n(3),G=n.n(V),q=n(1),X=function(t,e){return e?Object(q.adjust)(t,{s:-40,l:10}):Object(q.adjust)(t,{s:-40,l:-10})};function Z(t){return(Z="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function J(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}var K=function(){function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.background="#f4f4f4",this.darkMode=!1,this.primaryColor="#fff4dd",this.noteBkgColor="#fff5ad",this.noteTextColor="#333",this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px"}var e,n,r;return e=t,(n=[{key:"updateColors",value:function(){this.primaryTextColor=this.primaryTextColor||(this.darkMode?"#ddd":"#333"),this.secondaryColor=this.secondaryColor||Object(q.adjust)(this.primaryColor,{h:-120}),this.tertiaryColor=this.tertiaryColor||Object(q.adjust)(this.primaryColor,{h:180,l:5}),this.primaryBorderColor=this.primaryBorderColor||X(this.primaryColor,this.darkMode),this.secondaryBorderColor=this.secondaryBorderColor||X(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=this.tertiaryBorderColor||X(this.tertiaryColor,this.darkMode),this.noteBorderColor=this.noteBorderColor||X(this.noteBkgColor,this.darkMode),this.secondaryTextColor=this.secondaryTextColor||Object(q.invert)(this.secondaryColor),this.tertiaryTextColor=this.tertiaryTextColor||Object(q.invert)(this.tertiaryColor),this.lineColor=this.lineColor||Object(q.invert)(this.background),this.textColor=this.textColor||this.primaryTextColor,this.nodeBkg=this.nodeBkg||this.primaryColor,this.mainBkg=this.mainBkg||this.primaryColor,this.nodeBorder=this.nodeBorder||this.primaryBorderColor,this.clusterBkg=this.clusterBkg||this.tertiaryColor,this.clusterBorder=this.clusterBorder||this.tertiaryBorderColor,this.defaultLinkColor=this.defaultLinkColor||this.lineColor,this.titleColor=this.titleColor||this.tertiaryTextColor,this.edgeLabelBackground=this.edgeLabelBackground||(this.darkMode?Object(q.darken)(this.secondaryColor,30):this.secondaryColor),this.nodeTextColor=this.nodeTextColor||this.primaryTextColor,this.actorBorder=this.actorBorder||this.primaryBorderColor,this.actorBkg=this.actorBkg||this.mainBkg,this.actorTextColor=this.actorTextColor||this.primaryTextColor,this.actorLineColor=this.actorLineColor||"grey",this.labelBoxBkgColor=this.labelBoxBkgColor||this.actorBkg,this.signalColor=this.signalColor||this.textColor,this.signalTextColor=this.signalTextColor||this.textColor,this.labelBoxBorderColor=this.labelBoxBorderColor||this.actorBorder,this.labelTextColor=this.labelTextColor||this.actorTextColor,this.loopTextColor=this.loopTextColor||this.actorTextColor,this.activationBorderColor=this.activationBorderColor||Object(q.darken)(this.secondaryColor,10),this.activationBkgColor=this.activationBkgColor||this.secondaryColor,this.sequenceNumberColor=this.sequenceNumberColor||Object(q.invert)(this.lineColor),this.sectionBkgColor=this.sectionBkgColor||this.tertiaryColor,this.altSectionBkgColor=this.altSectionBkgColor||"white",this.sectionBkgColor=this.sectionBkgColor||this.secondaryColor,this.sectionBkgColor2=this.sectionBkgColor2||this.primaryColor,this.taskBorderColor=this.taskBorderColor||this.primaryBorderColor,this.taskBkgColor=this.taskBkgColor||this.primaryColor,this.activeTaskBorderColor=this.activeTaskBorderColor||this.primaryColor,this.activeTaskBkgColor=this.activeTaskBkgColor||Object(q.lighten)(this.primaryColor,23),this.gridColor=this.gridColor||"lightgrey",this.doneTaskBkgColor=this.doneTaskBkgColor||"lightgrey",this.doneTaskBorderColor=this.doneTaskBorderColor||"grey",this.critBorderColor=this.critBorderColor||"#ff8888",this.critBkgColor=this.critBkgColor||"red",this.todayLineColor=this.todayLineColor||"red",this.taskTextColor=this.taskTextColor||this.textColor,this.taskTextOutsideColor=this.taskTextOutsideColor||this.textColor,this.taskTextLightColor=this.taskTextLightColor||this.textColor,this.taskTextColor=this.taskTextColor||this.primaryTextColor,this.taskTextDarkColor=this.taskTextDarkColor||this.textColor,this.taskTextClickableColor=this.taskTextClickableColor||"#003163",this.labelColor=this.labelColor||this.primaryTextColor,this.altBackground=this.altBackground||this.tertiaryColor,this.errorBkgColor=this.errorBkgColor||this.tertiaryColor,this.errorTextColor=this.errorTextColor||this.tertiaryTextColor,this.classText=this.classText||this.textColor,this.fillType0=this.fillType0||this.primaryColor,this.fillType1=this.fillType1||this.secondaryColor,this.fillType2=this.fillType2||Object(q.adjust)(this.primaryColor,{h:64}),this.fillType3=this.fillType3||Object(q.adjust)(this.secondaryColor,{h:64}),this.fillType4=this.fillType4||Object(q.adjust)(this.primaryColor,{h:-64}),this.fillType5=this.fillType5||Object(q.adjust)(this.secondaryColor,{h:-64}),this.fillType6=this.fillType6||Object(q.adjust)(this.primaryColor,{h:128}),this.fillType7=this.fillType7||Object(q.adjust)(this.secondaryColor,{h:128})}},{key:"calculate",value:function(t){var e=this;if("object"===Z(t)){var n=Object.keys(t);n.forEach((function(n){e[n]=t[n]})),this.updateColors(),n.forEach((function(n){e[n]=t[n]}))}else this.updateColors()}}])&&J(e.prototype,n),r&&J(e,r),t}();function Q(t){return(Q="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function tt(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}var et=function(){function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.background="#333",this.primaryColor="#1f2020",this.secondaryColor=Object(q.lighten)(this.primaryColor,16),this.tertiaryColor=Object(q.adjust)(this.primaryColor,{h:-160}),this.primaryBorderColor=X(this.primaryColor,this.darkMode),this.secondaryBorderColor=X(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=X(this.tertiaryColor,this.darkMode),this.primaryTextColor=Object(q.invert)(this.primaryColor),this.secondaryTextColor=Object(q.invert)(this.secondaryColor),this.tertiaryTextColor=Object(q.invert)(this.tertiaryColor),this.lineColor=Object(q.invert)(this.background),this.textColor=Object(q.invert)(this.background),this.mainBkg="#1f2020",this.secondBkg="calculated",this.mainContrastColor="lightgrey",this.darkTextColor=Object(q.lighten)(Object(q.invert)("#323D47"),10),this.lineColor="calculated",this.border1="#81B1DB",this.border2=Object(q.rgba)(255,255,255,.25),this.arrowheadColor="calculated",this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px",this.labelBackground="#181818",this.textColor="#ccc",this.nodeBkg="calculated",this.nodeBorder="calculated",this.clusterBkg="calculated",this.clusterBorder="calculated",this.defaultLinkColor="calculated",this.titleColor="#F9FFFE",this.edgeLabelBackground="calculated",this.actorBorder="calculated",this.actorBkg="calculated",this.actorTextColor="calculated",this.actorLineColor="calculated",this.signalColor="calculated",this.signalTextColor="calculated",this.labelBoxBkgColor="calculated",this.labelBoxBorderColor="calculated",this.labelTextColor="calculated",this.loopTextColor="calculated",this.noteBorderColor="calculated",this.noteBkgColor="#fff5ad",this.noteTextColor="calculated",this.activationBorderColor="calculated",this.activationBkgColor="calculated",this.sequenceNumberColor="black",this.sectionBkgColor=Object(q.darken)("#EAE8D9",30),this.altSectionBkgColor="calculated",this.sectionBkgColor2="#EAE8D9",this.taskBorderColor=Object(q.rgba)(255,255,255,70),this.taskBkgColor="calculated",this.taskTextColor="calculated",this.taskTextLightColor="calculated",this.taskTextOutsideColor="calculated",this.taskTextClickableColor="#003163",this.activeTaskBorderColor=Object(q.rgba)(255,255,255,50),this.activeTaskBkgColor="#81B1DB",this.gridColor="calculated",this.doneTaskBkgColor="calculated",this.doneTaskBorderColor="grey",this.critBorderColor="#E83737",this.critBkgColor="#E83737",this.taskTextDarkColor="calculated",this.todayLineColor="#DB5757",this.labelColor="calculated",this.errorBkgColor="#a44141",this.errorTextColor="#ddd"}var e,n,r;return e=t,(n=[{key:"updateColors",value:function(){this.secondBkg=Object(q.lighten)(this.mainBkg,16),this.lineColor=this.mainContrastColor,this.arrowheadColor=this.mainContrastColor,this.nodeBkg=this.mainBkg,this.nodeBorder=this.border1,this.clusterBkg=this.secondBkg,this.clusterBorder=this.border2,this.defaultLinkColor=this.lineColor,this.edgeLabelBackground=Object(q.lighten)(this.labelBackground,25),this.actorBorder=this.border1,this.actorBkg=this.mainBkg,this.actorTextColor=this.mainContrastColor,this.actorLineColor=this.mainContrastColor,this.signalColor=this.mainContrastColor,this.signalTextColor=this.mainContrastColor,this.labelBoxBkgColor=this.actorBkg,this.labelBoxBorderColor=this.actorBorder,this.labelTextColor=this.mainContrastColor,this.loopTextColor=this.mainContrastColor,this.noteBorderColor=this.border2,this.noteTextColor=this.mainBkg,this.activationBorderColor=this.border1,this.activationBkgColor=this.secondBkg,this.altSectionBkgColor=this.background,this.taskBkgColor=Object(q.lighten)(this.mainBkg,23),this.taskTextColor=this.darkTextColor,this.taskTextLightColor=this.mainContrastColor,this.taskTextOutsideColor=this.taskTextLightColor,this.gridColor=this.mainContrastColor,this.doneTaskBkgColor=this.mainContrastColor,this.taskTextDarkColor=this.darkTextColor,this.labelColor=this.textColor,this.altBackground=Object(q.lighten)(this.background,20),this.fillType0=this.primaryColor,this.fillType1=this.secondaryColor,this.fillType2=Object(q.adjust)(this.primaryColor,{h:64}),this.fillType3=Object(q.adjust)(this.secondaryColor,{h:64}),this.fillType4=Object(q.adjust)(this.primaryColor,{h:-64}),this.fillType5=Object(q.adjust)(this.secondaryColor,{h:-64}),this.fillType6=Object(q.adjust)(this.primaryColor,{h:128}),this.fillType7=Object(q.adjust)(this.secondaryColor,{h:128}),this.classText=this.primaryTextColor}},{key:"calculate",value:function(t){var e=this;if("object"===Q(t)){var n=Object.keys(t);n.forEach((function(n){e[n]=t[n]})),this.updateColors(),n.forEach((function(n){e[n]=t[n]}))}else this.updateColors()}}])&&tt(e.prototype,n),r&&tt(e,r),t}();function nt(t){return(nt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function rt(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}var it=function(){function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.background="#f4f4f4",this.primaryColor="#ECECFF",this.secondaryColor=Object(q.adjust)(this.primaryColor,{h:120}),this.secondaryColor="#ffffde",this.tertiaryColor=Object(q.adjust)(this.primaryColor,{h:-160}),this.primaryBorderColor=X(this.primaryColor,this.darkMode),this.secondaryBorderColor=X(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=X(this.tertiaryColor,this.darkMode),this.primaryTextColor=Object(q.invert)(this.primaryColor),this.secondaryTextColor=Object(q.invert)(this.secondaryColor),this.tertiaryTextColor=Object(q.invert)(this.tertiaryColor),this.lineColor=Object(q.invert)(this.background),this.textColor=Object(q.invert)(this.background),this.background="white",this.mainBkg="#ECECFF",this.secondBkg="#ffffde",this.lineColor="#333333",this.border1="#9370DB",this.border2="#aaaa33",this.arrowheadColor="#333333",this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px",this.labelBackground="#e8e8e8",this.textColor="#333",this.nodeBkg="calculated",this.nodeBorder="calculated",this.clusterBkg="calculated",this.clusterBorder="calculated",this.defaultLinkColor="calculated",this.titleColor="calculated",this.edgeLabelBackground="calculated",this.actorBorder="calculated",this.actorBkg="calculated",this.actorTextColor="black",this.actorLineColor="grey",this.signalColor="calculated",this.signalTextColor="calculated",this.labelBoxBkgColor="calculated",this.labelBoxBorderColor="calculated",this.labelTextColor="calculated",this.loopTextColor="calculated",this.noteBorderColor="calculated",this.noteBkgColor="#fff5ad",this.noteTextColor="calculated",this.activationBorderColor="#666",this.activationBkgColor="#f4f4f4",this.sequenceNumberColor="white",this.sectionBkgColor="calculated",this.altSectionBkgColor="calculated",this.sectionBkgColor2="calculated",this.taskBorderColor="calculated",this.taskBkgColor="calculated",this.taskTextLightColor="calculated",this.taskTextColor=this.taskTextLightColor,this.taskTextDarkColor="calculated",this.taskTextOutsideColor=this.taskTextDarkColor,this.taskTextClickableColor="calculated",this.activeTaskBorderColor="calculated",this.activeTaskBkgColor="calculated",this.gridColor="calculated",this.doneTaskBkgColor="calculated",this.doneTaskBorderColor="calculated",this.critBorderColor="calculated",this.critBkgColor="calculated",this.todayLineColor="calculated",this.sectionBkgColor=Object(q.rgba)(102,102,255,.49),this.altSectionBkgColor="white",this.sectionBkgColor2="#fff400",this.taskBorderColor="#534fbc",this.taskBkgColor="#8a90dd",this.taskTextLightColor="white",this.taskTextColor="calculated",this.taskTextDarkColor="black",this.taskTextOutsideColor="calculated",this.taskTextClickableColor="#003163",this.activeTaskBorderColor="#534fbc",this.activeTaskBkgColor="#bfc7ff",this.gridColor="lightgrey",this.doneTaskBkgColor="lightgrey",this.doneTaskBorderColor="grey",this.critBorderColor="#ff8888",this.critBkgColor="red",this.todayLineColor="red",this.labelColor="black",this.errorBkgColor="#552222",this.errorTextColor="#552222",this.updateColors()}var e,n,r;return e=t,(n=[{key:"updateColors",value:function(){this.nodeBkg=this.mainBkg,this.nodeBorder=this.border1,this.clusterBkg=this.secondBkg,this.clusterBorder=this.border2,this.defaultLinkColor=this.lineColor,this.titleColor=this.textColor,this.edgeLabelBackground=this.labelBackground,this.actorBorder=Object(q.lighten)(this.border1,23),this.actorBkg=this.mainBkg,this.labelBoxBkgColor=this.actorBkg,this.signalColor=this.textColor,this.signalTextColor=this.textColor,this.labelBoxBorderColor=this.actorBorder,this.labelTextColor=this.actorTextColor,this.loopTextColor=this.actorTextColor,this.noteBorderColor=this.border2,this.noteTextColor=this.actorTextColor,this.taskTextColor=this.taskTextLightColor,this.taskTextOutsideColor=this.taskTextDarkColor,this.classText=this.primaryTextColor,this.fillType0=this.primaryColor,this.fillType1=this.secondaryColor,this.fillType2=Object(q.adjust)(this.primaryColor,{h:64}),this.fillType3=Object(q.adjust)(this.secondaryColor,{h:64}),this.fillType4=Object(q.adjust)(this.primaryColor,{h:-64}),this.fillType5=Object(q.adjust)(this.secondaryColor,{h:-64}),this.fillType6=Object(q.adjust)(this.primaryColor,{h:128}),this.fillType7=Object(q.adjust)(this.secondaryColor,{h:128})}},{key:"calculate",value:function(t){var e=this;if("object"===nt(t)){var n=Object.keys(t);n.forEach((function(n){e[n]=t[n]})),this.updateColors(),n.forEach((function(n){e[n]=t[n]}))}else this.updateColors()}}])&&rt(e.prototype,n),r&&rt(e,r),t}();function at(t){return(at="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function ot(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}var st=function(){function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.background="#f4f4f4",this.primaryColor="#cde498",this.secondaryColor="#cdffb2",this.background="white",this.mainBkg="#cde498",this.secondBkg="#cdffb2",this.lineColor="green",this.border1="#13540c",this.border2="#6eaa49",this.arrowheadColor="green",this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px",this.tertiaryColor=Object(q.lighten)("#cde498",10),this.primaryBorderColor=X(this.primaryColor,this.darkMode),this.secondaryBorderColor=X(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=X(this.tertiaryColor,this.darkMode),this.primaryTextColor=Object(q.invert)(this.primaryColor),this.secondaryTextColor=Object(q.invert)(this.secondaryColor),this.tertiaryTextColor=Object(q.invert)(this.primaryColor),this.lineColor=Object(q.invert)(this.background),this.textColor=Object(q.invert)(this.background),this.nodeBkg="calculated",this.nodeBorder="calculated",this.clusterBkg="calculated",this.clusterBorder="calculated",this.defaultLinkColor="calculated",this.titleColor="#333",this.edgeLabelBackground="#e8e8e8",this.actorBorder="calculated",this.actorBkg="calculated",this.actorTextColor="black",this.actorLineColor="grey",this.signalColor="#333",this.signalTextColor="#333",this.labelBoxBkgColor="calculated",this.labelBoxBorderColor="#326932",this.labelTextColor="calculated",this.loopTextColor="calculated",this.noteBorderColor="calculated",this.noteBkgColor="#fff5ad",this.noteTextColor="calculated",this.activationBorderColor="#666",this.activationBkgColor="#f4f4f4",this.sequenceNumberColor="white",this.sectionBkgColor="#6eaa49",this.altSectionBkgColor="white",this.sectionBkgColor2="#6eaa49",this.taskBorderColor="calculated",this.taskBkgColor="#487e3a",this.taskTextLightColor="white",this.taskTextColor="calculated",this.taskTextDarkColor="black",this.taskTextOutsideColor="calculated",this.taskTextClickableColor="#003163",this.activeTaskBorderColor="calculated",this.activeTaskBkgColor="calculated",this.gridColor="lightgrey",this.doneTaskBkgColor="lightgrey",this.doneTaskBorderColor="grey",this.critBorderColor="#ff8888",this.critBkgColor="red",this.todayLineColor="red",this.labelColor="black",this.errorBkgColor="#552222",this.errorTextColor="#552222"}var e,n,r;return e=t,(n=[{key:"updateColors",value:function(){this.nodeBkg=this.mainBkg,this.nodeBorder=this.border1,this.clusterBkg=this.secondBkg,this.clusterBorder=this.border2,this.defaultLinkColor=this.lineColor,this.actorBorder=Object(q.darken)(this.mainBkg,20),this.actorBkg=this.mainBkg,this.labelBoxBkgColor=this.actorBkg,this.labelTextColor=this.actorTextColor,this.loopTextColor=this.actorTextColor,this.noteBorderColor=this.border2,this.noteTextColor=this.actorTextColor,this.taskBorderColor=this.border1,this.taskTextColor=this.taskTextLightColor,this.taskTextOutsideColor=this.taskTextDarkColor,this.activeTaskBorderColor=this.taskBorderColor,this.activeTaskBkgColor=this.mainBkg,this.classText=this.primaryTextColor,this.fillType0=this.primaryColor,this.fillType1=this.secondaryColor,this.fillType2=Object(q.adjust)(this.primaryColor,{h:64}),this.fillType3=Object(q.adjust)(this.secondaryColor,{h:64}),this.fillType4=Object(q.adjust)(this.primaryColor,{h:-64}),this.fillType5=Object(q.adjust)(this.secondaryColor,{h:-64}),this.fillType6=Object(q.adjust)(this.primaryColor,{h:128}),this.fillType7=Object(q.adjust)(this.secondaryColor,{h:128})}},{key:"calculate",value:function(t){var e=this;if("object"===at(t)){var n=Object.keys(t);n.forEach((function(n){e[n]=t[n]})),this.updateColors(),n.forEach((function(n){e[n]=t[n]}))}else this.updateColors()}}])&&ot(e.prototype,n),r&&ot(e,r),t}();function ct(t){return(ct="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function ut(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(t,r.key,r)}}var lt=function(){function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.primaryColor="#eee",this.contrast="#26a",this.secondaryColor=Object(q.lighten)(this.contrast,55),this.background="#ffffff",this.tertiaryColor=Object(q.adjust)(this.primaryColor,{h:-160}),this.primaryBorderColor=X(this.primaryColor,this.darkMode),this.secondaryBorderColor=X(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=X(this.tertiaryColor,this.darkMode),this.primaryTextColor=Object(q.invert)(this.primaryColor),this.secondaryTextColor=Object(q.invert)(this.secondaryColor),this.tertiaryTextColor=Object(q.invert)(this.tertiaryColor),this.lineColor=Object(q.invert)(this.background),this.textColor=Object(q.invert)(this.background),this.altBackground=Object(q.lighten)(this.contrast,55),this.mainBkg="#eee",this.secondBkg="calculated",this.lineColor="#666",this.border1="#999",this.border2="calculated",this.note="#ffa",this.text="#333",this.critical="#d42",this.done="#bbb",this.arrowheadColor="#333333",this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px",this.nodeBkg="calculated",this.nodeBorder="calculated",this.clusterBkg="calculated",this.clusterBorder="calculated",this.defaultLinkColor="calculated",this.titleColor="calculated",this.edgeLabelBackground="white",this.actorBorder="calculated",this.actorBkg="calculated",this.actorTextColor="calculated",this.actorLineColor="calculated",this.signalColor="calculated",this.signalTextColor="calculated",this.labelBoxBkgColor="calculated",this.labelBoxBorderColor="calculated",this.labelTextColor="calculated",this.loopTextColor="calculated",this.noteBorderColor="calculated",this.noteBkgColor="calculated",this.noteTextColor="calculated",this.activationBorderColor="#666",this.activationBkgColor="#f4f4f4",this.sequenceNumberColor="white",this.sectionBkgColor="calculated",this.altSectionBkgColor="white",this.sectionBkgColor2="calculated",this.taskBorderColor="calculated",this.taskBkgColor="calculated",this.taskTextLightColor="white",this.taskTextColor="calculated",this.taskTextDarkColor="calculated",this.taskTextOutsideColor="calculated",this.taskTextClickableColor="#003163",this.activeTaskBorderColor="calculated",this.activeTaskBkgColor="calculated",this.gridColor="calculated",this.doneTaskBkgColor="calculated",this.doneTaskBorderColor="calculated",this.critBkgColor="calculated",this.critBorderColor="calculated",this.todayLineColor="calculated",this.labelColor="black",this.errorBkgColor="#552222",this.errorTextColor="#552222"}var e,n,r;return e=t,(n=[{key:"updateColors",value:function(){this.secondBkg=Object(q.lighten)(this.contrast,55),this.border2=this.contrast,this.nodeBkg=this.mainBkg,this.nodeBorder=this.border1,this.clusterBkg=this.secondBkg,this.clusterBorder=this.border2,this.defaultLinkColor=this.lineColor,this.titleColor=this.text,this.actorBorder=Object(q.lighten)(this.border1,23),this.actorBkg=this.mainBkg,this.actorTextColor=this.text,this.actorLineColor=this.lineColor,this.signalColor=this.text,this.signalTextColor=this.text,this.labelBoxBkgColor=this.actorBkg,this.labelBoxBorderColor=this.actorBorder,this.labelTextColor=this.text,this.loopTextColor=this.text,this.noteBorderColor=Object(q.darken)(this.note,60),this.noteBkgColor=this.note,this.noteTextColor=this.actorTextColor,this.sectionBkgColor=Object(q.lighten)(this.contrast,30),this.sectionBkgColor2=Object(q.lighten)(this.contrast,30),this.taskBorderColor=Object(q.darken)(this.contrast,10),this.taskBkgColor=this.contrast,this.taskTextColor=this.taskTextLightColor,this.taskTextDarkColor=this.text,this.taskTextOutsideColor=this.taskTextDarkColor,this.activeTaskBorderColor=this.taskBorderColor,this.activeTaskBkgColor=this.mainBkg,this.gridColor=Object(q.lighten)(this.border1,30),this.doneTaskBkgColor=this.done,this.doneTaskBorderColor=this.lineColor,this.critBkgColor=this.critical,this.critBorderColor=Object(q.darken)(this.critBkgColor,10),this.todayLineColor=this.critBkgColor,this.classText=this.primaryTextColor,this.fillType0=this.primaryColor,this.fillType1=this.secondaryColor,this.fillType2=Object(q.adjust)(this.primaryColor,{h:64}),this.fillType3=Object(q.adjust)(this.secondaryColor,{h:64}),this.fillType4=Object(q.adjust)(this.primaryColor,{h:-64}),this.fillType5=Object(q.adjust)(this.secondaryColor,{h:-64}),this.fillType6=Object(q.adjust)(this.primaryColor,{h:128}),this.fillType7=Object(q.adjust)(this.secondaryColor,{h:128})}},{key:"calculate",value:function(t){var e=this;if("object"===ct(t)){var n=Object.keys(t);n.forEach((function(n){e[n]=t[n]})),this.updateColors(),n.forEach((function(n){e[n]=t[n]}))}else this.updateColors()}}])&&ut(e.prototype,n),r&&ut(e,r),t}(),ht={base:{getThemeVariables:function(t){var e=new K;return e.calculate(t),e}},dark:{getThemeVariables:function(t){var e=new et;return e.calculate(t),e}},default:{getThemeVariables:function(t){var e=new it;return e.calculate(t),e}},forest:{getThemeVariables:function(t){var e=new st;return e.calculate(t),e}},neutral:{getThemeVariables:function(t){var e=new lt;return e.calculate(t),e}}},ft={theme:"default",themeVariables:ht.default.getThemeVariables(),themeCSS:void 0,maxTextSize:5e4,fontFamily:'"trebuchet ms", verdana, arial, sans-serif;',logLevel:5,securityLevel:"strict",startOnLoad:!0,arrowMarkerAbsolute:!1,secure:["secure","securityLevel","startOnLoad","maxTextSize"],deterministicIds:!1,deterministicIDSeed:void 0,flowchart:{diagramPadding:8,htmlLabels:!0,nodeSpacing:50,rankSpacing:50,curve:"linear",padding:15,useMaxWidth:!0},sequence:{activationWidth:10,diagramMarginX:50,diagramMarginY:10,actorMargin:50,width:150,height:65,boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,messageAlign:"center",mirrorActors:!0,bottomMarginAdj:1,useMaxWidth:!0,rightAngles:!1,showSequenceNumbers:!1,actorFontSize:14,actorFontFamily:'"Open-Sans", "sans-serif"',actorFontWeight:400,noteFontSize:14,noteFontFamily:'"trebuchet ms", verdana, arial, sans-serif',noteFontWeight:400,noteAlign:"center",messageFontSize:16,messageFontFamily:'"trebuchet ms", verdana, arial, sans-serif',messageFontWeight:400,wrap:!1,wrapPadding:10,labelBoxWidth:50,labelBoxHeight:20,messageFont:function(){return{fontFamily:this.messageFontFamily,fontSize:this.messageFontSize,fontWeight:this.messageFontWeight}},noteFont:function(){return{fontFamily:this.noteFontFamily,fontSize:this.noteFontSize,fontWeight:this.noteFontWeight}},actorFont:function(){return{fontFamily:this.actorFontFamily,fontSize:this.actorFontSize,fontWeight:this.actorFontWeight}}},gantt:{titleTopMargin:25,barHeight:20,barGap:4,topPadding:50,leftPadding:75,gridLineStartPadding:35,fontSize:11,fontFamily:'"Open-Sans", "sans-serif"',numberSectionStyles:4,axisFormat:"%Y-%m-%d",useMaxWidth:!0,useWidth:void 0},journey:{diagramMarginX:50,diagramMarginY:10,actorMargin:50,width:150,height:65,boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,messageAlign:"center",bottomMarginAdj:1,useMaxWidth:!0,rightAngles:!1},class:{arrowMarkerAbsolute:!1,useMaxWidth:!0},git:{arrowMarkerAbsolute:!1,useWidth:void 0,useMaxWidth:!0},state:{dividerMargin:10,sizeUnit:5,padding:8,textHeight:10,titleShift:-15,noteMargin:10,forkWidth:70,forkHeight:7,miniPadding:2,fontSizeFactor:5.02,fontSize:24,labelHeight:16,edgeLengthFactor:"20",compositTitleSize:35,radius:5,useMaxWidth:!0},er:{diagramPadding:20,layoutDirection:"TB",minEntityWidth:100,minEntityHeight:75,entityPadding:15,stroke:"gray",fill:"honeydew",fontSize:12,useMaxWidth:!0},pie:{useWidth:void 0,useMaxWidth:!0}};ft.class.arrowMarkerAbsolute=ft.arrowMarkerAbsolute,ft.git.arrowMarkerAbsolute=ft.arrowMarkerAbsolute;var dt,pt=ft,gt=Object.freeze(pt),yt=I({},gt),vt=[],mt=I({},gt),bt=function(t,e){for(var n=I({},t),r={},i=0;i<e.length;i++){var a=e[i];kt(a),r=I(r,a)}if(n=I(n,r),r.theme){var o=I(dt.themeVariables||{},r.themeVariables);n.themeVariables=ht[n.theme].getThemeVariables(o)}return mt=n,n},xt=function(){return I({},yt)},_t=function(){return I({},mt)},kt=function(t){Object.keys(yt.secure).forEach((function(e){void 0!==t[yt.secure[e]]&&(c.debug("Denied attempt to modify a secure key ".concat(yt.secure[e]),t[yt.secure[e]]),delete t[yt.secure[e]])}))},wt=function(t){t.fontFamily&&(t.themeVariables&&t.themeVariables.fontFamily||(t.themeVariables={fontFamily:t.fontFamily})),vt.push(t),bt(yt,vt)},Et=function(){bt(yt,vt=[])};function Tt(t){return(Tt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function Ct(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e<t.length;e++)n[e]=t[e];return n}}(t)||function(t){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t))return Array.from(t)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance")}()}var At,St,Mt=0,Ot=_t(),Dt={},Nt=[],Bt=[],Lt=[],Ft={},Pt={},It=0,jt=!0,Rt=[],Yt=function(t){for(var e=Object.keys(Dt),n=0;n<e.length;n++)if(Dt[e[n]].id===t)return Dt[e[n]].domId;return t},zt=function(t,e,n,r){var i={start:t,end:e,type:void 0,text:""};void 0!==(r=n.text)&&(i.text=x.sanitizeText(r.trim(),Ot),'"'===i.text[0]&&'"'===i.text[i.text.length-1]&&(i.text=i.text.substring(1,i.text.length-1))),void 0!==n&&(i.type=n.type,i.stroke=n.stroke,i.length=n.length),Nt.push(i)},Ut=function(t,e){t.split(",").forEach((function(t){var n=t;void 0!==Dt[n]&&Dt[n].classes.push(e),void 0!==Ft[n]&&Ft[n].classes.push(e)}))},$t=function(t){var e=Object(d.select)(".mermaidTooltip");null===(e._groups||e)[0][0]&&(e=Object(d.select)("body").append("div").attr("class","mermaidTooltip").style("opacity",0)),Object(d.select)(t).select("svg").selectAll("g.node").on("mouseover",(function(){var t=Object(d.select)(this);if(null!==t.attr("title")){var n=this.getBoundingClientRect();e.transition().duration(200).style("opacity",".9"),e.html(t.attr("title")).style("left",window.scrollX+n.left+(n.right-n.left)/2+"px").style("top",window.scrollY+n.top-14+document.body.scrollTop+"px"),t.classed("hover",!0)}})).on("mouseout",(function(){e.transition().duration(500).style("opacity",0),Object(d.select)(this).classed("hover",!1)}))};Rt.push($t);var Wt=function(t){for(var e=0;e<Lt.length;e++)if(Lt[e].id===t)return e;return-1},Ht=-1,Vt=[],Gt=function(t,e){var n=!1;return t.forEach((function(t){t.nodes.indexOf(e)>=0&&(n=!0)})),n},qt=function(t,e){var n=[];return t.nodes.forEach((function(r,i){Gt(e,r)||n.push(t.nodes[i])})),{nodes:n}},Xt={parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},defaultConfig:function(){return gt.flowchart},addVertex:function(t,e,n,r,i){var a,o=t;void 0!==o&&0!==o.trim().length&&(void 0===Dt[o]&&(Dt[o]={id:o,domId:"flowchart-"+o+"-"+Mt,styles:[],classes:[]}),Mt++,void 0!==e?(Ot=_t(),'"'===(a=x.sanitizeText(e.trim(),Ot))[0]&&'"'===a[a.length-1]&&(a=a.substring(1,a.length-1)),Dt[o].text=a):void 0===Dt[o].text&&(Dt[o].text=t),void 0!==n&&(Dt[o].type=n),null!=r&&r.forEach((function(t){Dt[o].styles.push(t)})),null!=i&&i.forEach((function(t){Dt[o].classes.push(t)})))},lookUpDomId:Yt,addLink:function(t,e,n,r){var i,a;for(i=0;i<t.length;i++)for(a=0;a<e.length;a++)zt(t[i],e[a],n,r)},updateLinkInterpolate:function(t,e){t.forEach((function(t){"default"===t?Nt.defaultInterpolate=e:Nt[t].interpolate=e}))},updateLink:function(t,e){t.forEach((function(t){"default"===t?Nt.defaultStyle=e:(-1===H.isSubstringInArray("fill",e)&&e.push("fill:none"),Nt[t].style=e)}))},addClass:function(t,e){void 0===Bt[t]&&(Bt[t]={id:t,styles:[],textStyles:[]}),null!=e&&e.forEach((function(e){if(e.match("color")){var n=e.replace("fill","bgFill").replace("color","fill");Bt[t].textStyles.push(n)}Bt[t].styles.push(e)}))},setDirection:function(t){(At=t).match(/.*</)&&(At="RL"),At.match(/.*\^/)&&(At="BT"),At.match(/.*>/)&&(At="LR"),At.match(/.*v/)&&(At="TB")},setClass:Ut,setTooltip:function(t,e){t.split(",").forEach((function(t){void 0!==e&&(Pt["gen-1"===St?Yt(t):t]=x.sanitizeText(e,Ot))}))},getTooltip:function(t){return Pt[t]},setClickEvent:function(t,e,n){t.split(",").forEach((function(t){!function(t,e,n){var r=Yt(t);if("loose"===_t().securityLevel&&void 0!==e){var i=[];if("string"==typeof n){i=n.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);for(var a=0;a<i.length;a++){var o=i[a].trim();'"'===o.charAt(0)&&'"'===o.charAt(o.length-1)&&(o=o.substr(1,o.length-2)),i[a]=o}}0===i.length&&i.push(t),void 0!==Dt[t]&&(Dt[t].haveCallback=!0,Rt.push((function(){var t=document.querySelector('[id="'.concat(r,'"]'));null!==t&&t.addEventListener("click",(function(){H.runFunc.apply(H,[e].concat(Ct(i)))}),!1)})))}}(t,e,n)})),Ut(t,"clickable")},setLink:function(t,e,n){t.split(",").forEach((function(t){void 0!==Dt[t]&&(Dt[t].link=H.formatUrl(e,Ot),Dt[t].linkTarget=n)})),Ut(t,"clickable")},bindFunctions:function(t){Rt.forEach((function(e){e(t)}))},getDirection:function(){return At.trim()},getVertices:function(){return Dt},getEdges:function(){return Nt},getClasses:function(){return Bt},clear:function(t){Dt={},Bt={},Nt=[],(Rt=[]).push($t),Lt=[],Ft={},It=0,Pt=[],jt=!0,St=t||"gen-1"},setGen:function(t){St=t||"gen-1"},defaultStyle:function(){return"fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;"},addSubGraph:function(t,e,n){var r=t.trim(),i=n;t===n&&n.match(/\s/)&&(r=void 0);var a,o,s,u=[];if(a=u.concat.apply(u,e),o={boolean:{},number:{},string:{}},s=[],u=a.filter((function(t){var e=Tt(t);return""!==t.trim()&&(e in o?!o[e].hasOwnProperty(t)&&(o[e][t]=!0):!(s.indexOf(t)>=0)&&s.push(t))})),"gen-1"===St){c.warn("LOOKING UP");for(var l=0;l<u.length;l++)u[l]=Yt(u[l])}r=r||"subGraph"+It,i=i||"",i=x.sanitizeText(i,Ot),It+=1;var h={id:r,nodes:u,title:i.trim(),classes:[]};return c.info("Adding",h.id,h.nodes),h.nodes=qt(h,Lt).nodes,Lt.push(h),Ft[r]=h,r},getDepthFirstPos:function(t){return Vt[t]},indexNodes:function(){Ht=-1,Lt.length>0&&function t(e,n){var r=Lt[n].nodes;if(!((Ht+=1)>2e3)){if(Vt[Ht]=n,Lt[n].id===e)return{result:!0,count:0};for(var i=0,a=1;i<r.length;){var o=Wt(r[i]);if(o>=0){var s=t(e,o);if(s.result)return{result:!0,count:a+s.count};a+=s.count}i+=1}return{result:!1,count:a}}}("none",Lt.length-1)},getSubGraphs:function(){return Lt},destructLink:function(t,e){var n,r=function(t){var e=t.trim(),n=e.slice(0,-1),r="arrow_open";switch(e.slice(-1)){case"x":r="arrow_cross","x"===e[0]&&(r="double_"+r,n=n.slice(1));break;case">":r="arrow_point","<"===e[0]&&(r="double_"+r,n=n.slice(1));break;case"o":r="arrow_circle","o"===e[0]&&(r="double_"+r,n=n.slice(1))}var i="normal",a=n.length-1;"="===n[0]&&(i="thick");var o=function(t,e){for(var n=e.length,r=0,i=0;i<n;++i)e[i]===t&&++r;return r}(".",n);return o&&(i="dotted",a=o),{type:r,stroke:i,length:a}}(t);if(e){if((n=function(t){var e=t.trim(),n="arrow_open";switch(e[0]){case"<":n="arrow_point",e=e.slice(1);break;case"x":n="arrow_cross",e=e.slice(1);break;case"o":n="arrow_circle",e=e.slice(1)}var r="normal";return-1!==e.indexOf("=")&&(r="thick"),-1!==e.indexOf(".")&&(r="dotted"),{type:n,stroke:r}}(e)).stroke!==r.stroke)return{type:"INVALID",stroke:"INVALID"};if("arrow_open"===n.type)n.type=r.type;else{if(n.type!==r.type)return{type:"INVALID",stroke:"INVALID"};n.type="double_"+n.type}return"double_arrow"===n.type&&(n.type="double_arrow_point"),n.length=r.length,n}return r},lex:{firstGraph:function(){return!!jt&&(jt=!1,!0)}},exists:Gt,makeUniq:qt},Zt=n(26),Jt=n.n(Zt),Kt=n(6),Qt=n.n(Kt),te=n(50),ee=n.n(te);function ne(t,e,n){var r=.9*(e.width+e.height),i=[{x:r/2,y:0},{x:r,y:-r/2},{x:r/2,y:-r},{x:0,y:-r/2}],a=de(t,r,r,i);return n.intersect=function(t){return Qt.a.intersect.polygon(n,i,t)},a}function re(t,e,n){var r=e.height,i=r/4,a=e.width+2*i,o=[{x:i,y:0},{x:a-i,y:0},{x:a,y:-r/2},{x:a-i,y:-r},{x:i,y:-r},{x:0,y:-r/2}],s=de(t,a,r,o);return n.intersect=function(t){return Qt.a.intersect.polygon(n,o,t)},s}function ie(t,e,n){var r=e.width,i=e.height,a=[{x:-i/2,y:0},{x:r,y:0},{x:r,y:-i},{x:-i/2,y:-i},{x:0,y:-i/2}],o=de(t,r,i,a);return n.intersect=function(t){return Qt.a.intersect.polygon(n,a,t)},o}function ae(t,e,n){var r=e.width,i=e.height,a=[{x:-2*i/6,y:0},{x:r-i/6,y:0},{x:r+2*i/6,y:-i},{x:i/6,y:-i}],o=de(t,r,i,a);return n.intersect=function(t){return Qt.a.intersect.polygon(n,a,t)},o}function oe(t,e,n){var r=e.width,i=e.height,a=[{x:2*i/6,y:0},{x:r+i/6,y:0},{x:r-2*i/6,y:-i},{x:-i/6,y:-i}],o=de(t,r,i,a);return n.intersect=function(t){return Qt.a.intersect.polygon(n,a,t)},o}function se(t,e,n){var r=e.width,i=e.height,a=[{x:-2*i/6,y:0},{x:r+2*i/6,y:0},{x:r-i/6,y:-i},{x:i/6,y:-i}],o=de(t,r,i,a);return n.intersect=function(t){return Qt.a.intersect.polygon(n,a,t)},o}function ce(t,e,n){var r=e.width,i=e.height,a=[{x:i/6,y:0},{x:r-i/6,y:0},{x:r+2*i/6,y:-i},{x:-2*i/6,y:-i}],o=de(t,r,i,a);return n.intersect=function(t){return Qt.a.intersect.polygon(n,a,t)},o}function ue(t,e,n){var r=e.width,i=e.height,a=[{x:0,y:0},{x:r+i/2,y:0},{x:r,y:-i/2},{x:r+i/2,y:-i},{x:0,y:-i}],o=de(t,r,i,a);return n.intersect=function(t){return Qt.a.intersect.polygon(n,a,t)},o}function le(t,e,n){var r=e.height,i=e.width+r/4,a=t.insert("rect",":first-child").attr("rx",r/2).attr("ry",r/2).attr("x",-i/2).attr("y",-r/2).attr("width",i).attr("height",r);return n.intersect=function(t){return Qt.a.intersect.rect(n,t)},a}function he(t,e,n){var r=e.width,i=e.height,a=[{x:0,y:0},{x:r,y:0},{x:r,y:-i},{x:0,y:-i},{x:0,y:0},{x:-8,y:0},{x:r+8,y:0},{x:r+8,y:-i},{x:-8,y:-i},{x:-8,y:0}],o=de(t,r,i,a);return n.intersect=function(t){return Qt.a.intersect.polygon(n,a,t)},o}function fe(t,e,n){var r=e.width,i=r/2,a=i/(2.5+r/50),o=e.height+a,s="M 0,"+a+" a "+i+","+a+" 0,0,0 "+r+" 0 a "+i+","+a+" 0,0,0 "+-r+" 0 l 0,"+o+" a "+i+","+a+" 0,0,0 "+r+" 0 l 0,"+-o,c=t.attr("label-offset-y",a).insert("path",":first-child").attr("d",s).attr("transform","translate("+-r/2+","+-(o/2+a)+")");return n.intersect=function(t){var e=Qt.a.intersect.rect(n,t),r=e.x-n.x;if(0!=i&&(Math.abs(r)<n.width/2||Math.abs(r)==n.width/2&&Math.abs(e.y-n.y)>n.height/2-a)){var o=a*a*(1-r*r/(i*i));0!=o&&(o=Math.sqrt(o)),o=a-o,t.y-n.y>0&&(o=-o),e.y+=o}return e},c}function de(t,e,n,r){return t.insert("polygon",":first-child").attr("points",r.map((function(t){return t.x+","+t.y})).join(" ")).attr("transform","translate("+-e/2+","+n/2+")")}var pe={addToRender:function(t){t.shapes().question=ne,t.shapes().hexagon=re,t.shapes().stadium=le,t.shapes().subroutine=he,t.shapes().cylinder=fe,t.shapes().rect_left_inv_arrow=ie,t.shapes().lean_right=ae,t.shapes().lean_left=oe,t.shapes().trapezoid=se,t.shapes().inv_trapezoid=ce,t.shapes().rect_right_inv_arrow=ue},addToRenderV2:function(t){t({question:ne}),t({hexagon:re}),t({stadium:le}),t({subroutine:he}),t({cylinder:fe}),t({rect_left_inv_arrow:ie}),t({lean_right:ae}),t({lean_left:oe}),t({trapezoid:se}),t({inv_trapezoid:ce}),t({rect_right_inv_arrow:ue})}},ge={},ye=function(t,e,n){var r=Object(d.select)('[id="'.concat(n,'"]'));Object.keys(t).forEach((function(n){var i=t[n],a="default";i.classes.length>0&&(a=i.classes.join(" "));var o,s=B(i.styles),u=void 0!==i.text?i.text:i.id;if(_t().flowchart.htmlLabels){var l={label:u.replace(/fa[lrsb]?:fa-[\w-]+/g,(function(t){return"<i class='".concat(t.replace(":"," "),"'></i>")}))};(o=ee()(r,l).node()).parentNode.removeChild(o)}else{var h=document.createElementNS("http://www.w3.org/2000/svg","text");h.setAttribute("style",s.labelStyle.replace("color:","fill:"));for(var f=u.split(x.lineBreakRegex),d=0;d<f.length;d++){var p=document.createElementNS("http://www.w3.org/2000/svg","tspan");p.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),p.setAttribute("dy","1em"),p.setAttribute("x","1"),p.textContent=f[d],h.appendChild(p)}o=h}var g=0,y="";switch(i.type){case"round":g=5,y="rect";break;case"square":y="rect";break;case"diamond":y="question";break;case"hexagon":y="hexagon";break;case"odd":y="rect_left_inv_arrow";break;case"lean_right":y="lean_right";break;case"lean_left":y="lean_left";break;case"trapezoid":y="trapezoid";break;case"inv_trapezoid":y="inv_trapezoid";break;case"odd_right":y="rect_left_inv_arrow";break;case"circle":y="circle";break;case"ellipse":y="ellipse";break;case"stadium":y="stadium";break;case"subroutine":y="subroutine";break;case"cylinder":y="cylinder";break;case"group":y="rect";break;default:y="rect"}c.warn("Adding node",i.id,i.domId),e.setNode(Xt.lookUpDomId(i.id),{labelType:"svg",labelStyle:s.labelStyle,shape:y,label:o,rx:g,ry:g,class:a,style:s.style,id:Xt.lookUpDomId(i.id)})}))},ve=function(t,e){var n,r,i=0;if(void 0!==t.defaultStyle){var a=B(t.defaultStyle);n=a.style,r=a.labelStyle}t.forEach((function(a){i++;var o="L-"+a.start+"-"+a.end,s="LS-"+a.start,c="LE-"+a.end,u={};"arrow_open"===a.type?u.arrowhead="none":u.arrowhead="normal";var l="",h="";if(void 0!==a.style){var f=B(a.style);l=f.style,h=f.labelStyle}else switch(a.stroke){case"normal":l="fill:none",void 0!==n&&(l=n),void 0!==r&&(h=r);break;case"dotted":l="fill:none;stroke-width:2px;stroke-dasharray:3;";break;case"thick":l=" stroke-width: 3.5px;fill:none"}u.style=l,u.labelStyle=h,void 0!==a.interpolate?u.curve=D(a.interpolate,d.curveLinear):void 0!==t.defaultInterpolate?u.curve=D(t.defaultInterpolate,d.curveLinear):u.curve=D(ge.curve,d.curveLinear),void 0===a.text?void 0!==a.style&&(u.arrowheadStyle="fill: #333"):(u.arrowheadStyle="fill: #333",u.labelpos="c",_t().flowchart.htmlLabels?(u.labelType="html",u.label='<span id="L-'.concat(o,'" class="edgeLabel L-').concat(s,"' L-").concat(c,'">').concat(a.text.replace(/fa[lrsb]?:fa-[\w-]+/g,(function(t){return"<i class='".concat(t.replace(":"," "),"'></i>")})),"</span>")):(u.labelType="text",u.label=a.text.replace(x.lineBreakRegex,"\n"),void 0===a.style&&(u.style=u.style||"stroke: #333; stroke-width: 1.5px;fill:none"),u.labelStyle=u.labelStyle.replace("color:","fill:"))),u.id=o,u.class=s+" "+c,u.minlen=a.length||1,e.setEdge(Xt.lookUpDomId(a.start),Xt.lookUpDomId(a.end),u,i)}))},me=function(t){for(var e=Object.keys(t),n=0;n<e.length;n++)ge[e[n]]=t[e[n]]},be=function(t){c.info("Extracting classes"),Xt.clear();try{var e=Jt.a.parser;return e.yy=Xt,e.parse(t),Xt.getClasses()}catch(t){return}},xe=function(t,e){c.info("Drawing flowchart"),Xt.clear(),Xt.setGen("gen-1");var n=Jt.a.parser;n.yy=Xt,n.parse(t);var r=Xt.getDirection();void 0===r&&(r="TD");for(var i,a=_t().flowchart,o=a.nodeSpacing||50,s=a.rankSpacing||50,u=new G.a.Graph({multigraph:!0,compound:!0}).setGraph({rankdir:r,nodesep:o,ranksep:s,marginx:8,marginy:8}).setDefaultEdgeLabel((function(){return{}})),l=Xt.getSubGraphs(),h=l.length-1;h>=0;h--)i=l[h],Xt.addVertex(i.id,i.title,"group",void 0,i.classes);var f=Xt.getVertices();c.warn("Get vertices",f);var p=Xt.getEdges(),g=0;for(g=l.length-1;g>=0;g--){i=l[g],Object(d.selectAll)("cluster").append("text");for(var y=0;y<i.nodes.length;y++)c.warn("Setting subgraph",i.nodes[y],Xt.lookUpDomId(i.nodes[y]),Xt.lookUpDomId(i.id)),u.setParent(Xt.lookUpDomId(i.nodes[y]),Xt.lookUpDomId(i.id))}ye(f,u,e),ve(p,u);var v=new(0,Qt.a.render);pe.addToRender(v),v.arrows().none=function(t,e,n,r){var i=t.append("marker").attr("id",e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","strokeWidth").attr("markerWidth",8).attr("markerHeight",6).attr("orient","auto").append("path").attr("d","M 0 0 L 0 0 L 0 0 z");Qt.a.util.applyStyle(i,n[r+"Style"])},v.arrows().normal=function(t,e){t.append("marker").attr("id",e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","strokeWidth").attr("markerWidth",8).attr("markerHeight",6).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z").attr("class","arrowheadPath").style("stroke-width",1).style("stroke-dasharray","1,0")};var m=Object(d.select)('[id="'.concat(e,'"]'));m.attr("xmlns:xlink","http://www.w3.org/1999/xlink"),c.warn(u);var b=Object(d.select)("#"+e+" g");v(b,u),b.selectAll("g.node").attr("title",(function(){return Xt.getTooltip(this.id)}));var x=a.diagramPadding,_=m.node().getBBox(),k=_.width+2*x,w=_.height+2*x;W(m,w,k,a.useMaxWidth);var E="".concat(_.x-x," ").concat(_.y-x," ").concat(k," ").concat(w);for(c.debug("viewBox ".concat(E)),m.attr("viewBox",E),Xt.indexNodes("subGraph"+g),g=0;g<l.length;g++)if("undefined"!==(i=l[g]).title){var T=document.querySelectorAll("#"+e+' [id="'+Xt.lookUpDomId(i.id)+'"] rect'),C=document.querySelectorAll("#"+e+' [id="'+Xt.lookUpDomId(i.id)+'"]'),A=T[0].x.baseVal.value,S=T[0].y.baseVal.value,M=T[0].width.baseVal.value,O=Object(d.select)(C[0]).select(".label");O.attr("transform","translate(".concat(A+M/2,", ").concat(S+14,")")),O.attr("id",e+"Text");for(var D=0;D<i.classes.length;D++)C[0].classList.add(i.classes[D])}a.htmlLabels;for(var N=document.querySelectorAll('[id="'+e+'"] .edgeLabel .label'),B=0;B<N.length;B++){var L=N[B],F=L.getBBox(),P=document.createElementNS("http://www.w3.org/2000/svg","rect");P.setAttribute("rx",0),P.setAttribute("ry",0),P.setAttribute("width",F.width),P.setAttribute("height",F.height),L.insertBefore(P,L.firstChild)}Object.keys(f).forEach((function(t){var n=f[t];if(n.link){var r=Object(d.select)("#"+e+' [id="'+Xt.lookUpDomId(t)+'"]');if(r){var i=document.createElementNS("http://www.w3.org/2000/svg","a");i.setAttributeNS("http://www.w3.org/2000/svg","class",n.classes.join(" ")),i.setAttributeNS("http://www.w3.org/2000/svg","href",n.link),i.setAttributeNS("http://www.w3.org/2000/svg","rel","noopener"),n.linkTarget&&i.setAttributeNS("http://www.w3.org/2000/svg","target",n.linkTarget);var a=r.insert((function(){return i}),":first-child"),o=r.select(".label-container");o&&a.append((function(){return o.node()}));var s=r.select(".label");s&&a.append((function(){return s.node()}))}}}))},_e=n(18),ke=n.n(_e),we={extension:function(t,e,n){c.trace("Making markers for ",n),t.append("defs").append("marker").attr("id",e+"-extensionStart").attr("class","marker extension "+e).attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 1,7 L18,13 V 1 Z"),t.append("defs").append("marker").attr("id",e+"-extensionEnd").attr("class","marker extension "+e).attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 1,1 V 13 L18,7 Z")},composition:function(t,e){t.append("defs").append("marker").attr("id",e+"-compositionStart").attr("class","marker composition "+e).attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id",e+"-compositionEnd").attr("class","marker composition "+e).attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z")},aggregation:function(t,e){t.append("defs").append("marker").attr("id",e+"-aggregationStart").attr("class","marker aggregation "+e).attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id",e+"-aggregationEnd").attr("class","marker aggregation "+e).attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z")},dependency:function(t,e){t.append("defs").append("marker").attr("id",e+"-dependencyStart").attr("class","marker dependency "+e).attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 5,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id",e+"-dependencyEnd").attr("class","marker dependency "+e).attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z")},point:function(t,e){t.append("marker").attr("id",e+"-pointEnd").attr("class","marker "+e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0"),t.append("marker").attr("id",e+"-pointStart").attr("class","marker "+e).attr("viewBox","0 0 10 10").attr("refX",0).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 0 5 L 10 10 L 10 0 z").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0")},circle:function(t,e){t.append("marker").attr("id",e+"-circleEnd").attr("class","marker "+e).attr("viewBox","0 0 10 10").attr("refX",11).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("circle").attr("cx","5").attr("cy","5").attr("r","5").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0"),t.append("marker").attr("id",e+"-circleStart").attr("class","marker "+e).attr("viewBox","0 0 10 10").attr("refX",-1).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("circle").attr("cx","5").attr("cy","5").attr("r","5").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0")},cross:function(t,e){t.append("marker").attr("id",e+"-crossEnd").attr("class","marker cross "+e).attr("viewBox","0 0 11 11").attr("refX",12).attr("refY",5.2).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("path").attr("d","M 1,1 l 9,9 M 10,1 l -9,9").attr("class","arrowMarkerPath").style("stroke-width",2).style("stroke-dasharray","1,0"),t.append("marker").attr("id",e+"-crossStart").attr("class","marker cross "+e).attr("viewBox","0 0 11 11").attr("refX",-1).attr("refY",5.2).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("path").attr("d","M 1,1 l 9,9 M 10,1 l -9,9").attr("class","arrowMarkerPath").style("stroke-width",2).style("stroke-dasharray","1,0")},barb:function(t,e){t.append("defs").append("marker").attr("id",e+"-barbEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",14).attr("markerUnits","strokeWidth").attr("orient","auto").append("path").attr("d","M 19,7 L9,13 L14,7 L9,1 Z")}},Ee=function(t,e,n,r){e.forEach((function(e){we[e](t,n,r)}))};var Te=function(t,e,n,r){var i=t||"";if(_t().flowchart.htmlLabels)return i=i.replace(/\\n|\n/g,"<br />"),c.info("vertexText"+i),function(t){var e,n,r=Object(d.select)(document.createElementNS("http://www.w3.org/2000/svg","foreignObject")),i=r.append("xhtml:div"),a=t.label,o=t.isNode?"nodeLabel":"edgeLabel";return i.html('<span class="'+o+'">'+a+"</span>"),e=i,(n=t.labelStyle)&&e.attr("style",n),i.style("display","inline-block"),i.style("white-space","nowrap"),i.attr("xmlns","http://www.w3.org/1999/xhtml"),r.node()}({isNode:r,label:i.replace(/fa[lrsb]?:fa-[\w-]+/g,(function(t){return"<i class='".concat(t.replace(":"," "),"'></i>")})),labelStyle:e.replace("fill:","color:")});var a=document.createElementNS("http://www.w3.org/2000/svg","text");a.setAttribute("style",e.replace("color:","fill:"));var o=[];o="string"==typeof i?i.split(/\\n|\n|<br\s*\/?>/gi):Array.isArray(i)?i:[];for(var s=0;s<o.length;s++){var u=document.createElementNS("http://www.w3.org/2000/svg","tspan");u.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),u.setAttribute("dy","1em"),u.setAttribute("x","0"),n?u.setAttribute("class","title-row"):u.setAttribute("class","row"),u.textContent=o[s].trim(),a.appendChild(u)}return a},Ce=function(t,e,n,r){var i;i=n||"node default";var a=t.insert("g").attr("class",i).attr("id",e.domId||e.id),o=a.insert("g").attr("class","label").attr("style",e.labelStyle),s=o.node().appendChild(Te(e.labelText,e.labelStyle,!1,r)),c=s.getBBox();if(_t().flowchart.htmlLabels){var u=s.children[0],l=Object(d.select)(s);c=u.getBoundingClientRect(),l.attr("width",c.width),l.attr("height",c.height)}var h=e.padding/2;return o.attr("transform","translate("+-c.width/2+", "+-c.height/2+")"),{shapeSvg:a,bbox:c,halfPadding:h,label:o}},Ae=function(t,e){var n=e.node().getBBox();t.width=n.width,t.height=n.height};function Se(t,e,n,r){return t.insert("polygon",":first-child").attr("points",r.map((function(t){return t.x+","+t.y})).join(" ")).attr("class","label-container").attr("transform","translate("+-e/2+","+n/2+")")}var Me={},Oe={},De={},Ne=function(t,e){return c.debug("In isDecendant",e," ",t," = ",Oe[e].indexOf(t)>=0),Oe[e].indexOf(t)>=0},Be=function t(e,n,r,i){c.warn("Copying children of ",e,"root",i,"data",n.node(e),i);var a=n.children(e)||[];e!==i&&a.push(e),c.warn("Copying (nodes) clusterId",e,"nodes",a),a.forEach((function(a){if(n.children(a).length>0)t(a,n,r,i);else{var o=n.node(a);c.info("cp ",a," to ",i," with parent ",e),r.setNode(a,o),i!==n.parent(a)&&(c.warn("Setting parent",a,n.parent(a)),r.setParent(a,n.parent(a))),e!==i&&a!==e?(c.debug("Setting parent",a,e),r.setParent(a,e)):(c.info("In copy ",e,"root",i,"data",n.node(e),i),c.debug("Not Setting parent for node=",a,"cluster!==rootId",e!==i,"node!==clusterId",a!==e));var s=n.edges(a);c.debug("Copying Edges",s),s.forEach((function(t){c.info("Edge",t);var a=n.edge(t.v,t.w,t.name);c.info("Edge data",a,i);try{!function(t,e){return c.info("Decendants of ",e," is ",Oe[e]),c.info("Edge is ",t),t.v!==e&&(t.w!==e&&(Oe[e]?(c.info("Here "),Oe[e].indexOf(t.v)>=0||(!!Ne(t.v,e)||(!!Ne(t.w,e)||Oe[e].indexOf(t.w)>=0))):(c.debug("Tilt, ",e,",not in decendants"),!1)))}(t,i)?c.info("Skipping copy of edge ",t.v,"--\x3e",t.w," rootId: ",i," clusterId:",e):(c.info("Copying as ",t.v,t.w,a,t.name),r.setEdge(t.v,t.w,a,t.name),c.info("newGraph edges ",r.edges(),r.edge(r.edges()[0])))}catch(t){c.error(t)}}))}c.debug("Removing node",a),n.removeNode(a)}))},Le=function t(e,n){c.trace("Searching",e);var r=n.children(e);if(c.trace("Searching children of id ",e,r),r.length<1)return c.trace("This is a valid node",e),e;for(var i=0;i<r.length;i++){var a=t(r[i],n);if(a)return c.trace("Found replacement for",e," => ",a),a}},Fe=function(t){return Me[t]&&Me[t].externalConnections&&Me[t]?Me[t].id:t},Pe=function(t,e){!t||e>10?c.debug("Opting out, no graph "):(c.debug("Opting in, graph "),t.nodes().forEach((function(e){t.children(e).length>0&&(c.warn("Cluster identified",e," Replacement id in edges: ",Le(e,t)),Oe[e]=function t(e,n){for(var r=n.children(e),i=[].concat(r),a=0;a<r.length;a++)De[r[a]]=e,i=i.concat(t(r[a],n));return i}(e,t),Me[e]={id:Le(e,t),clusterData:t.node(e)})})),t.nodes().forEach((function(e){var n=t.children(e),r=t.edges();n.length>0?(c.debug("Cluster identified",e,Oe),r.forEach((function(t){t.v!==e&&t.w!==e&&(Ne(t.v,e)^Ne(t.w,e)&&(c.warn("Edge: ",t," leaves cluster ",e),c.warn("Decendants of XXX ",e,": ",Oe[e]),Me[e].externalConnections=!0))}))):c.debug("Not a cluster ",e,Oe)})),t.edges().forEach((function(e){var n=t.edge(e);c.warn("Edge "+e.v+" -> "+e.w+": "+JSON.stringify(e)),c.warn("Edge "+e.v+" -> "+e.w+": "+JSON.stringify(t.edge(e)));var r=e.v,i=e.w;c.warn("Fix XXX",Me,"ids:",e.v,e.w,"Translateing: ",Me[e.v]," --- ",Me[e.w]),(Me[e.v]||Me[e.w])&&(c.warn("Fixing and trixing - removing XXX",e.v,e.w,e.name),r=Fe(e.v),i=Fe(e.w),t.removeEdge(e.v,e.w,e.name),r!==e.v&&(n.fromCluster=e.v),i!==e.w&&(n.toCluster=e.w),c.warn("Fix Replacing with XXX",r,i,e.name),t.setEdge(r,i,n,e.name))})),c.warn("Adjusted Graph",G.a.json.write(t)),Ie(t,0),c.trace(Me))},Ie=function t(e,n){if(c.warn("extractor - ",n,G.a.json.write(e),e.children("D")),n>10)c.error("Bailing out");else{for(var r=e.nodes(),i=!1,a=0;a<r.length;a++){var o=r[a],s=e.children(o);i=i||s.length>0}if(i){c.debug("Nodes = ",r,n);for(var u=0;u<r.length;u++){var l=r[u];if(c.debug("Extracting node",l,Me,Me[l]&&!Me[l].externalConnections,!e.parent(l),e.node(l),e.children("D")," Depth ",n),Me[l])if(!Me[l].externalConnections&&e.children(l)&&e.children(l).length>0){c.warn("Cluster without external connections, without a parent and with children",l,n);var h=e.graph(),f=new G.a.Graph({multigraph:!0,compound:!0}).setGraph({rankdir:"TB"===h.rankdir?"LR":"TB",nodesep:50,ranksep:50,marginx:8,marginy:8}).setDefaultEdgeLabel((function(){return{}}));c.warn("Old graph before copy",G.a.json.write(e)),Be(l,e,f,l),e.setNode(l,{clusterNode:!0,id:l,clusterData:Me[l].clusterData,labelText:Me[l].labelText,graph:f}),c.warn("New graph after copy node: (",l,")",G.a.json.write(f)),c.debug("Old graph after copy",G.a.json.write(e))}else c.warn("Cluster ** ",l," **not meeting the criteria !externalConnections:",!Me[l].externalConnections," no parent: ",!e.parent(l)," children ",e.children(l)&&e.children(l).length>0,e.children("D"),n),c.debug(Me);else c.debug("Not a cluster",l,n)}r=e.nodes(),c.warn("New list of nodes",r);for(var d=0;d<r.length;d++){var p=r[d],g=e.node(p);c.warn(" Now next level",p,g),g.clusterNode&&t(g.graph,n+1)}}else c.debug("Done, no node has children",e.nodes())}},je=function(t){return function t(e,n){if(0===n.length)return[];var r=Object.assign(n);return n.forEach((function(n){var i=e.children(n),a=t(e,i);r=r.concat(a)})),r}(t,t.children())},Re=n(170);var Ye=function(t,e,n,r){var i=t.x,a=t.y,o=i-r.x,s=a-r.y,c=Math.sqrt(e*e*s*s+n*n*o*o),u=Math.abs(e*n*o/c);r.x<i&&(u=-u);var l=Math.abs(e*n*s/c);return r.y<a&&(l=-l),{x:i+u,y:a+l}};var ze=function(t,e,n){return Ye(t,e,e,n)};function Ue(t,e){return t*e>0}var $e=function(t,e,n,r){var i,a,o,s,c,u,l,h,f,d,p,g,y;if(i=e.y-t.y,o=t.x-e.x,c=e.x*t.y-t.x*e.y,f=i*n.x+o*n.y+c,d=i*r.x+o*r.y+c,!(0!==f&&0!==d&&Ue(f,d)||(a=r.y-n.y,s=n.x-r.x,u=r.x*n.y-n.x*r.y,l=a*t.x+s*t.y+u,h=a*e.x+s*e.y+u,0!==l&&0!==h&&Ue(l,h)||0==(p=i*s-a*o))))return g=Math.abs(p/2),{x:(y=o*u-s*c)<0?(y-g)/p:(y+g)/p,y:(y=a*c-i*u)<0?(y-g)/p:(y+g)/p}},We=function(t,e,n){var r=t.x,i=t.y,a=[],o=Number.POSITIVE_INFINITY,s=Number.POSITIVE_INFINITY;"function"==typeof e.forEach?e.forEach((function(t){o=Math.min(o,t.x),s=Math.min(s,t.y)})):(o=Math.min(o,e.x),s=Math.min(s,e.y));for(var c=r-t.width/2-o,u=i-t.height/2-s,l=0;l<e.length;l++){var h=e[l],f=e[l<e.length-1?l+1:0],d=$e(t,n,{x:c+h.x,y:u+h.y},{x:c+f.x,y:u+f.y});d&&a.push(d)}if(!a.length)return t;a.length>1&&a.sort((function(t,e){var r=t.x-n.x,i=t.y-n.y,a=Math.sqrt(r*r+i*i),o=e.x-n.x,s=e.y-n.y,c=Math.sqrt(o*o+s*s);return a<c?-1:a===c?0:1}));return a[0]};var He=function(t,e){var n,r,i=t.x,a=t.y,o=e.x-i,s=e.y-a,c=t.width/2,u=t.height/2;return Math.abs(s)*c>Math.abs(o)*u?(s<0&&(u=-u),n=0===s?0:u*o/s,r=u):(o<0&&(c=-c),n=c,r=0===o?0:c*s/o),{x:i+n,y:a+r}},Ve={node:n.n(Re).a,circle:ze,ellipse:Ye,polygon:We,rect:He},Ge=function(t,e){var n=Ce(t,e,"node "+e.classes,!0),r=n.shapeSvg,i=n.bbox,a=n.halfPadding;c.info("Classes = ",e.classes);var o=r.insert("rect",":first-child");return o.attr("rx",e.rx).attr("ry",e.ry).attr("x",-i.width/2-a).attr("y",-i.height/2-a).attr("width",i.width+e.padding).attr("height",i.height+e.padding),Ae(e,o),e.intersect=function(t){return Ve.rect(e,t)},r};function qe(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e<t.length;e++)n[e]=t[e];return n}}(t)||function(t){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t))return Array.from(t)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance")}()}var Xe=[],Ze={},Je=0,Ke=[],Qe=function(t){var e="",n=t;if(t.indexOf("~")>0){var r=t.split("~");n=r[0],e=r[1]}return{className:n,type:e}},tn=function(t){var e=Qe(t);void 0===Ze[e.className]&&(Ze[e.className]={id:e.className,type:e.type,cssClasses:[],methods:[],members:[],annotations:[],domId:"classid-"+e.className+"-"+Je},Je++)},en=function(t){for(var e=Object.keys(Ze),n=0;n<e.length;n++)if(Ze[e[n]].id===t)return Ze[e[n]].domId},nn=function(t,e){var n=Qe(t).className,r=Ze[n];if("string"==typeof e){var i=e.trim();i.startsWith("<<")&&i.endsWith(">>")?r.annotations.push(i.substring(2,i.length-2)):i.indexOf(")")>0?r.methods.push(i):i&&r.members.push(i)}},rn=function(t,e){t.split(",").forEach((function(t){var n=t;t[0].match(/\d/)&&(n="classid-"+n),void 0!==Ze[n]&&Ze[n].cssClasses.push(e)}))},an=function(t,e,n){var r=_t(),i=t,a=en(i);if("loose"===r.securityLevel&&void 0!==e&&void 0!==Ze[i]){var o=[];if("string"==typeof n){o=n.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);for(var s=0;s<o.length;s++){var c=o[s].trim();'"'===c.charAt(0)&&'"'===c.charAt(c.length-1)&&(c=c.substr(1,c.length-2)),o[s]=c}}0===o.length&&o.push(a),Ke.push((function(){var t=document.querySelector('[id="'.concat(a,'"]'));null!==t&&t.addEventListener("click",(function(){H.runFunc.apply(H,[e].concat(qe(o)))}),!1)}))}},on={AGGREGATION:0,EXTENSION:1,COMPOSITION:2,DEPENDENCY:3},sn=function(t){var e=Object(d.select)(".mermaidTooltip");null===(e._groups||e)[0][0]&&(e=Object(d.select)("body").append("div").attr("class","mermaidTooltip").style("opacity",0)),Object(d.select)(t).select("svg").selectAll("g.node").on("mouseover",(function(){var t=Object(d.select)(this);if(null!==t.attr("title")){var n=this.getBoundingClientRect();e.transition().duration(200).style("opacity",".9"),e.html(t.attr("title")).style("left",window.scrollX+n.left+(n.right-n.left)/2+"px").style("top",window.scrollY+n.top-14+document.body.scrollTop+"px"),t.classed("hover",!0)}})).on("mouseout",(function(){e.transition().duration(500).style("opacity",0),Object(d.select)(this).classed("hover",!1)}))};Ke.push(sn);var cn={parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},getConfig:function(){return _t().class},addClass:tn,bindFunctions:function(t){Ke.forEach((function(e){e(t)}))},clear:function(){Xe=[],Ze={},(Ke=[]).push(sn)},getClass:function(t){return Ze[t]},getClasses:function(){return Ze},addAnnotation:function(t,e){var n=Qe(t).className;Ze[n].annotations.push(e)},getRelations:function(){return Xe},addRelation:function(t){c.debug("Adding relation: "+JSON.stringify(t)),tn(t.id1),tn(t.id2),t.id1=Qe(t.id1).className,t.id2=Qe(t.id2).className,Xe.push(t)},addMember:nn,addMembers:function(t,e){Array.isArray(e)&&(e.reverse(),e.forEach((function(e){return nn(t,e)})))},cleanupLabel:function(t){return":"===t.substring(0,1)?t.substr(1).trim():t.trim()},lineType:{LINE:0,DOTTED_LINE:1},relationType:on,setClickEvent:function(t,e,n){t.split(",").forEach((function(t){an(t,e,n),Ze[t].haveCallback=!0})),rn(t,"clickable")},setCssClass:rn,setLink:function(t,e,n){var r=_t();t.split(",").forEach((function(t){var i=t;t[0].match(/\d/)&&(i="classid-"+i),void 0!==Ze[i]&&(Ze[i].link=H.formatUrl(e,r),Ze[i].linkTarget="string"==typeof n?n:"_blank")})),rn(t,"clickable")},setTooltip:function(t,e){var n=_t();t.split(",").forEach((function(t){void 0!==e&&(Ze[t].tooltip=x.sanitizeText(e,n))}))},lookUpDomId:en},un=0,ln=function(t){var e=t.match(/(\+|-|~|#)?(\w+)(~\w+~|\[\])?\s+(\w+)/),n=t.match(/^([+|\-|~|#])?(\w+) *\( *(.*)\) *(\*|\$)? *(\w*[~|[\]]*\s*\w*~?)$/);return e&&!n?hn(e):n?fn(n):dn(t)},hn=function(t){var e="";try{e=(t[1]?t[1].trim():"")+(t[2]?t[2].trim():"")+(t[3]?gn(t[3].trim()):"")+" "+(t[4]?t[4].trim():"")}catch(n){e=t}return{displayText:e,cssStyle:""}},fn=function(t){var e="",n="";try{var r=t[1]?t[1].trim():"",i=t[2]?t[2].trim():"",a=t[3]?gn(t[3].trim()):"",o=t[4]?t[4].trim():"";n=r+i+"("+a+")"+(t[5]?" : "+gn(t[5]).trim():""),e=yn(o)}catch(e){n=t}return{displayText:n,cssStyle:e}},dn=function(t){var e="",n="",r="",i=t.indexOf("("),a=t.indexOf(")");if(i>1&&a>i&&a<=t.length){var o="",s="",c=t.substring(0,1);c.match(/\w/)?s=t.substring(0,i).trim():(c.match(/\+|-|~|#/)&&(o=c),s=t.substring(1,i).trim());var u=t.substring(i+1,a),l=t.substring(a+1,1);n=yn(l),e=o+s+"("+gn(u.trim())+")",a<"".length&&""!==(r=t.substring(a+2).trim())&&(r=" : "+gn(r))}else e=gn(t);return{displayText:e,cssStyle:n}},pn=function(t,e,n,r){var i=ln(e),a=t.append("tspan").attr("x",r.padding).text(i.displayText);""!==i.cssStyle&&a.attr("style",i.cssStyle),n||a.attr("dy",r.textHeight)},gn=function t(e){var n=e;return-1!=e.indexOf("~")?t(n=(n=n.replace("~","<")).replace("~",">")):n},yn=function(t){switch(t){case"*":return"font-style:italic;";case"$":return"text-decoration:underline;";default:return""}},vn=function(t,e,n){c.info("Rendering class "+e);var r,i=e.id,a={id:i,label:e.id,width:0,height:0},o=t.append("g").attr("id",en(i)).attr("class","classGroup");r=e.link?o.append("svg:a").attr("xlink:href",e.link).attr("target",e.linkTarget).append("text").attr("y",n.textHeight+n.padding).attr("x",0):o.append("text").attr("y",n.textHeight+n.padding).attr("x",0);var s=!0;e.annotations.forEach((function(t){var e=r.append("tspan").text("«"+t+"»");s||e.attr("dy",n.textHeight),s=!1}));var u=e.id;void 0!==e.type&&""!==e.type&&(u+="<"+e.type+">");var l=r.append("tspan").text(u).attr("class","title");s||l.attr("dy",n.textHeight);var h=r.node().getBBox().height,f=o.append("line").attr("x1",0).attr("y1",n.padding+h+n.dividerMargin/2).attr("y2",n.padding+h+n.dividerMargin/2),d=o.append("text").attr("x",n.padding).attr("y",h+n.dividerMargin+n.textHeight).attr("fill","white").attr("class","classText");s=!0,e.members.forEach((function(t){pn(d,t,s,n),s=!1}));var p=d.node().getBBox(),g=o.append("line").attr("x1",0).attr("y1",n.padding+h+n.dividerMargin+p.height).attr("y2",n.padding+h+n.dividerMargin+p.height),y=o.append("text").attr("x",n.padding).attr("y",h+2*n.dividerMargin+p.height+n.textHeight).attr("fill","white").attr("class","classText");s=!0,e.methods.forEach((function(t){pn(y,t,s,n),s=!1}));var v=o.node().getBBox(),m=" ";e.cssClasses.length>0&&(m+=e.cssClasses.join(" "));var b=o.insert("rect",":first-child").attr("x",0).attr("y",0).attr("width",v.width+2*n.padding).attr("height",v.height+n.padding+.5*n.dividerMargin).attr("class",m).node().getBBox().width;return r.node().childNodes.forEach((function(t){t.setAttribute("x",(b-t.getBBox().width)/2)})),e.tooltip&&r.insert("title").text(e.tooltip),f.attr("x2",b),g.attr("x2",b),a.width=b,a.height=v.height+n.padding+.5*n.dividerMargin,a},mn=function(t,e,n,r){var i=function(t){switch(t){case on.AGGREGATION:return"aggregation";case on.EXTENSION:return"extension";case on.COMPOSITION:return"composition";case on.DEPENDENCY:return"dependency"}};e.points=e.points.filter((function(t){return!Number.isNaN(t.y)}));var a,o,s=e.points,u=Object(d.line)().x((function(t){return t.x})).y((function(t){return t.y})).curve(d.curveBasis),l=t.append("path").attr("d",u(s)).attr("id","edge"+un).attr("class","relation"),h="";r.arrowMarkerAbsolute&&(h=(h=(h=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),1==n.relation.lineType&&l.attr("class","relation dashed-line"),"none"!==n.relation.type1&&l.attr("marker-start","url("+h+"#"+i(n.relation.type1)+"Start)"),"none"!==n.relation.type2&&l.attr("marker-end","url("+h+"#"+i(n.relation.type2)+"End)");var f,p,g,y,v=e.points.length,m=H.calcLabelPosition(e.points);if(a=m.x,o=m.y,v%2!=0&&v>1){var b=H.calcCardinalityPosition("none"!==n.relation.type1,e.points,e.points[0]),x=H.calcCardinalityPosition("none"!==n.relation.type2,e.points,e.points[v-1]);c.debug("cardinality_1_point "+JSON.stringify(b)),c.debug("cardinality_2_point "+JSON.stringify(x)),f=b.x,p=b.y,g=x.x,y=x.y}if(void 0!==n.title){var _=t.append("g").attr("class","classLabel"),k=_.append("text").attr("class","label").attr("x",a).attr("y",o).attr("fill","red").attr("text-anchor","middle").text(n.title);window.label=k;var w=k.node().getBBox();_.insert("rect",":first-child").attr("class","box").attr("x",w.x-r.padding/2).attr("y",w.y-r.padding/2).attr("width",w.width+r.padding).attr("height",w.height+r.padding)}(c.info("Rendering relation "+JSON.stringify(n)),void 0!==n.relationTitle1&&"none"!==n.relationTitle1)&&t.append("g").attr("class","cardinality").append("text").attr("class","type1").attr("x",f).attr("y",p).attr("fill","black").attr("font-size","6").text(n.relationTitle1);void 0!==n.relationTitle2&&"none"!==n.relationTitle2&&t.append("g").attr("class","cardinality").append("text").attr("class","type2").attr("x",g).attr("y",y).attr("fill","black").attr("font-size","6").text(n.relationTitle2);un++},bn=function(t,e,n){var r=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),i=70,a=10;"LR"===n&&(i=10,a=70);var o=r.append("rect").style("stroke","black").style("fill","black").attr("x",-1*i/2).attr("y",-1*a/2).attr("width",i).attr("height",a).attr("class","fork-join");return Ae(e,o),e.height=e.height+e.padding/2,e.width=e.width+e.padding/2,e.intersect=function(t){return Ve.rect(e,t)},r},xn={question:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.width+e.padding+(i.height+e.padding),o=[{x:a/2,y:0},{x:a,y:-a/2},{x:a/2,y:-a},{x:0,y:-a/2}];c.info("Question main (Circle)");var s=Se(r,a,a,o);return Ae(e,s),e.intersect=function(t){return c.warn("Intersect called"),Ve.polygon(e,o,t)},r},rect:function(t,e){var n=Ce(t,e,"node "+e.classes,!0),r=n.shapeSvg,i=n.bbox,a=n.halfPadding;c.trace("Classes = ",e.classes);var o=r.insert("rect",":first-child");return o.attr("class","basic label-container").attr("style",e.style).attr("rx",e.rx).attr("ry",e.ry).attr("x",-i.width/2-a).attr("y",-i.height/2-a).attr("width",i.width+e.padding).attr("height",i.height+e.padding),Ae(e,o),e.intersect=function(t){return Ve.rect(e,t)},r},rectWithTitle:function(t,e){var n;n=e.classes?"node "+e.classes:"node default";var r=t.insert("g").attr("class",n).attr("id",e.domId||e.id),i=r.insert("rect",":first-child"),a=r.insert("line"),o=r.insert("g").attr("class","label"),s=e.labelText.flat();c.info("Label text",s[0]);var u,l=o.node().appendChild(Te(s[0],e.labelStyle,!0,!0));if(_t().flowchart.htmlLabels){var h=l.children[0],f=Object(d.select)(l);u=h.getBoundingClientRect(),f.attr("width",u.width),f.attr("height",u.height)}c.info("Text 2",s);var p=s.slice(1,s.length),g=l.getBBox(),y=o.node().appendChild(Te(p.join("<br/>"),e.labelStyle,!0,!0));if(_t().flowchart.htmlLabels){var v=y.children[0],m=Object(d.select)(y);u=v.getBoundingClientRect(),m.attr("width",u.width),m.attr("height",u.height)}var b=e.padding/2;return Object(d.select)(y).attr("transform","translate( "+(u.width>g.width?0:(g.width-u.width)/2)+", "+(g.height+b+5)+")"),Object(d.select)(l).attr("transform","translate( "+(u.width<g.width?0:-(g.width-u.width)/2)+", 0)"),u=o.node().getBBox(),o.attr("transform","translate("+-u.width/2+", "+(-u.height/2-b+3)+")"),i.attr("class","outer title-state").attr("x",-u.width/2-b).attr("y",-u.height/2-b).attr("width",u.width+e.padding).attr("height",u.height+e.padding),a.attr("class","divider").attr("x1",-u.width/2-b).attr("x2",u.width/2+b).attr("y1",-u.height/2-b+g.height+b).attr("y2",-u.height/2-b+g.height+b),Ae(e,i),e.intersect=function(t){return Ve.rect(e,t)},r},circle:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=n.halfPadding,o=r.insert("circle",":first-child");return o.attr("rx",e.rx).attr("ry",e.ry).attr("r",i.width/2+a).attr("width",i.width+e.padding).attr("height",i.height+e.padding),c.info("Circle main"),Ae(e,o),e.intersect=function(t){return c.info("Circle intersect",e,i.width/2+a,t),Ve.circle(e,i.width/2+a,t)},r},stadium:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.height+e.padding,o=i.width+a/4+e.padding,s=r.insert("rect",":first-child").attr("rx",a/2).attr("ry",a/2).attr("x",-o/2).attr("y",-a/2).attr("width",o).attr("height",a);return Ae(e,s),e.intersect=function(t){return Ve.rect(e,t)},r},hexagon:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.height+e.padding,o=a/4,s=i.width+2*o+e.padding,c=Se(r,s,a,[{x:o,y:0},{x:s-o,y:0},{x:s,y:-a/2},{x:s-o,y:-a},{x:o,y:-a},{x:0,y:-a/2}]);return Ae(e,c),e.intersect=function(t){return Ve.polygon(e,t)},r},rect_left_inv_arrow:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.width+e.padding,o=i.height+e.padding,s=Se(r,a,o,[{x:-o/2,y:0},{x:a,y:0},{x:a,y:-o},{x:-o/2,y:-o},{x:0,y:-o/2}]);return Ae(e,s),e.intersect=function(t){return Ve.polygon(e,t)},r},lean_right:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.width+e.padding,o=i.height+e.padding,s=Se(r,a,o,[{x:-2*o/6,y:0},{x:a-o/6,y:0},{x:a+2*o/6,y:-o},{x:o/6,y:-o}]);return Ae(e,s),e.intersect=function(t){return Ve.polygon(e,t)},r},lean_left:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.width+e.padding,o=i.height+e.padding,s=Se(r,a,o,[{x:2*o/6,y:0},{x:a+o/6,y:0},{x:a-2*o/6,y:-o},{x:-o/6,y:-o}]);return Ae(e,s),e.intersect=function(t){return Ve.polygon(e,t)},r},trapezoid:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.width+e.padding,o=i.height+e.padding,s=Se(r,a,o,[{x:-2*o/6,y:0},{x:a+2*o/6,y:0},{x:a-o/6,y:-o},{x:o/6,y:-o}]);return Ae(e,s),e.intersect=function(t){return Ve.polygon(e,t)},r},inv_trapezoid:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.width+e.padding,o=i.height+e.padding,s=Se(r,a,o,[{x:o/6,y:0},{x:a-o/6,y:0},{x:a+2*o/6,y:-o},{x:-2*o/6,y:-o}]);return Ae(e,s),e.intersect=function(t){return Ve.polygon(e,t)},r},rect_right_inv_arrow:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.width+e.padding,o=i.height+e.padding,s=Se(r,a,o,[{x:0,y:0},{x:a+o/2,y:0},{x:a,y:-o/2},{x:a+o/2,y:-o},{x:0,y:-o}]);return Ae(e,s),e.intersect=function(t){return Ve.polygon(e,t)},r},cylinder:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.width+e.padding,o=a/2,s=o/(2.5+a/50),c=i.height+s+e.padding,u="M 0,"+s+" a "+o+","+s+" 0,0,0 "+a+" 0 a "+o+","+s+" 0,0,0 "+-a+" 0 l 0,"+c+" a "+o+","+s+" 0,0,0 "+a+" 0 l 0,"+-c,l=r.attr("label-offset-y",s).insert("path",":first-child").attr("d",u).attr("transform","translate("+-a/2+","+-(c/2+s)+")");return Ae(e,l),e.intersect=function(t){var n=Ve.rect(e,t),r=n.x-e.x;if(0!=o&&(Math.abs(r)<e.width/2||Math.abs(r)==e.width/2&&Math.abs(n.y-e.y)>e.height/2-s)){var i=s*s*(1-r*r/(o*o));0!=i&&(i=Math.sqrt(i)),i=s-i,t.y-e.y>0&&(i=-i),n.y+=i}return n},r},start:function(t,e){var n=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),r=n.insert("circle",":first-child");return r.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14),Ae(e,r),e.intersect=function(t){return Ve.circle(e,7,t)},n},end:function(t,e){var n=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),r=n.insert("circle",":first-child"),i=n.insert("circle",":first-child");return i.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14),r.attr("class","state-end").attr("r",5).attr("width",10).attr("height",10),Ae(e,i),e.intersect=function(t){return Ve.circle(e,7,t)},n},note:Ge,subroutine:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.width+e.padding,o=i.height+e.padding,s=Se(r,a,o,[{x:0,y:0},{x:a,y:0},{x:a,y:-o},{x:0,y:-o},{x:0,y:0},{x:-8,y:0},{x:a+8,y:0},{x:a+8,y:-o},{x:-8,y:-o},{x:-8,y:0}]);return Ae(e,s),e.intersect=function(t){return Ve.polygon(e,t)},r},fork:bn,join:bn,class_box:function(t,e){var n,r=e.padding/2;n=e.classes?"node "+e.classes:"node default";var i=t.insert("g").attr("class",n).attr("id",e.domId||e.id),a=i.insert("rect",":first-child"),o=i.insert("line"),s=i.insert("line"),c=0,u=4,l=i.insert("g").attr("class","label"),h=0,f=e.classData.annotations&&e.classData.annotations[0],p=e.classData.annotations[0]?"«"+e.classData.annotations[0]+"»":"",g=l.node().appendChild(Te(p,e.labelStyle,!0,!0)),y=g.getBBox();if(_t().flowchart.htmlLabels){var v=g.children[0],m=Object(d.select)(g);y=v.getBoundingClientRect(),m.attr("width",y.width),m.attr("height",y.height)}e.classData.annotations[0]&&(u+=y.height+4,c+=y.width);var b=e.classData.id;void 0!==e.classData.type&&""!==e.classData.type&&(b+="<"+e.classData.type+">");var x=l.node().appendChild(Te(b,e.labelStyle,!0,!0));Object(d.select)(x).attr("class","classTitle");var _=x.getBBox();if(_t().flowchart.htmlLabels){var k=x.children[0],w=Object(d.select)(x);_=k.getBoundingClientRect(),w.attr("width",_.width),w.attr("height",_.height)}u+=_.height+4,_.width>c&&(c=_.width);var E=[];e.classData.members.forEach((function(t){var n=ln(t).displayText,r=l.node().appendChild(Te(n,e.labelStyle,!0,!0)),i=r.getBBox();if(_t().flowchart.htmlLabels){var a=r.children[0],o=Object(d.select)(r);i=a.getBoundingClientRect(),o.attr("width",i.width),o.attr("height",i.height)}i.width>c&&(c=i.width),u+=i.height+4,E.push(r)})),u+=8;var T=[];if(e.classData.methods.forEach((function(t){var n=ln(t).displayText,r=l.node().appendChild(Te(n,e.labelStyle,!0,!0)),i=r.getBBox();if(_t().flowchart.htmlLabels){var a=r.children[0],o=Object(d.select)(r);i=a.getBoundingClientRect(),o.attr("width",i.width),o.attr("height",i.height)}i.width>c&&(c=i.width),u+=i.height+4,T.push(r)})),u+=8,f){var C=(c-y.width)/2;Object(d.select)(g).attr("transform","translate( "+(-1*c/2+C)+", "+-1*u/2+")"),h=y.height+4}var A=(c-_.width)/2;return Object(d.select)(x).attr("transform","translate( "+(-1*c/2+A)+", "+(-1*u/2+h)+")"),h+=_.height+4,o.attr("class","divider").attr("x1",-c/2-r).attr("x2",c/2+r).attr("y1",-u/2-r+8+h).attr("y2",-u/2-r+8+h),h+=8,E.forEach((function(t){Object(d.select)(t).attr("transform","translate( "+-c/2+", "+(-1*u/2+h+4)+")"),h+=_.height+4})),h+=8,s.attr("class","divider").attr("x1",-c/2-r).attr("x2",c/2+r).attr("y1",-u/2-r+8+h).attr("y2",-u/2-r+8+h),h+=8,T.forEach((function(t){Object(d.select)(t).attr("transform","translate( "+-c/2+", "+(-1*u/2+h)+")"),h+=_.height+4})),a.attr("class","outer title-state").attr("x",-c/2-r).attr("y",-u/2-r).attr("width",c+e.padding).attr("height",u+e.padding),Ae(e,a),e.intersect=function(t){return Ve.rect(e,t)},i}},_n={},kn=function(t){var e=_n[t.id];c.trace("Transforming node",t,"translate("+(t.x-t.width/2-5)+", "+(t.y-t.height/2-5)+")");t.clusterNode?e.attr("transform","translate("+(t.x-t.width/2-8)+", "+(t.y-t.height/2-8)+")"):e.attr("transform","translate("+t.x+", "+t.y+")")},wn={rect:function(t,e){c.trace("Creating subgraph rect for ",e.id,e);var n=t.insert("g").attr("class","cluster"+(e.class?" "+e.class:"")).attr("id",e.id),r=n.insert("rect",":first-child"),i=n.insert("g").attr("class","cluster-label"),a=i.node().appendChild(Te(e.labelText,e.labelStyle,void 0,!0)),o=a.getBBox();if(_t().flowchart.htmlLabels){var s=a.children[0],u=Object(d.select)(a);o=s.getBoundingClientRect(),u.attr("width",o.width),u.attr("height",o.height)}var l=0*e.padding,h=l/2;c.trace("Data ",e,JSON.stringify(e)),r.attr("style",e.style).attr("rx",e.rx).attr("ry",e.ry).attr("x",e.x-e.width/2-h).attr("y",e.y-e.height/2-h).attr("width",e.width+l).attr("height",e.height+l),i.attr("transform","translate("+(e.x-o.width/2)+", "+(e.y-e.height/2+e.padding/3)+")");var f=r.node().getBBox();return e.width=f.width,e.height=f.height,e.intersect=function(t){return He(e,t)},n},roundedWithTitle:function(t,e){var n=t.insert("g").attr("class",e.classes).attr("id",e.id),r=n.insert("rect",":first-child"),i=n.insert("g").attr("class","cluster-label"),a=n.append("rect"),o=i.node().appendChild(Te(e.labelText,e.labelStyle,void 0,!0)),s=o.getBBox();if(_t().flowchart.htmlLabels){var c=o.children[0],u=Object(d.select)(o);s=c.getBoundingClientRect(),u.attr("width",s.width),u.attr("height",s.height)}s=o.getBBox();var l=0*e.padding,h=l/2;r.attr("class","outer").attr("x",e.x-e.width/2-h).attr("y",e.y-e.height/2-h).attr("width",e.width+l).attr("height",e.height+l),a.attr("class","inner").attr("x",e.x-e.width/2-h).attr("y",e.y-e.height/2-h+s.height-1).attr("width",e.width+l).attr("height",e.height+l-s.height-3),i.attr("transform","translate("+(e.x-s.width/2)+", "+(e.y-e.height/2-e.padding/3+(_t().flowchart.htmlLabels?5:3))+")");var f=r.node().getBBox();return e.width=f.width,e.height=f.height,e.intersect=function(t){return He(e,t)},n},noteGroup:function(t,e){var n=t.insert("g").attr("class","note-cluster").attr("id",e.id),r=n.insert("rect",":first-child"),i=0*e.padding,a=i/2;r.attr("rx",e.rx).attr("ry",e.ry).attr("x",e.x-e.width/2-a).attr("y",e.y-e.height/2-a).attr("width",e.width+i).attr("height",e.height+i).attr("fill","none");var o=r.node().getBBox();return e.width=o.width,e.height=o.height,e.intersect=function(t){return He(e,t)},n},divider:function(t,e){var n=t.insert("g").attr("class",e.classes).attr("id",e.id),r=n.insert("rect",":first-child"),i=0*e.padding,a=i/2;r.attr("class","divider").attr("x",e.x-e.width/2-a).attr("y",e.y-e.height/2).attr("width",e.width+i).attr("height",e.height+i);var o=r.node().getBBox();return e.width=o.width,e.height=o.height,e.intersect=function(t){return He(e,t)},n}},En={},Tn={},Cn={},An=function(t,e){var n=t.x,r=t.y,i=Math.abs(e.x-n),a=Math.abs(e.y-r),o=t.width/2,s=t.height/2;return i>=o||a>=s},Sn=function(t,e,n){c.warn("intersection calc o:",e," i:",n,t);var r=t.x,i=t.y,a=Math.abs(r-n.x),o=t.width/2,s=n.x<e.x?o-a:o+a,u=t.height/2,l=r-o,h=r+o,f=i-u,d=i+u;if(e.x===l||e.x===h||e.y===f||e.y===d)return c.warn("calc equals on edge"),e;var p=Math.abs(e.y-n.y),g=Math.abs(e.x-n.x);if(Math.abs(i-e.y)*o>Math.abs(r-e.x)*u){var y=n.y<e.y?e.y-u-i:i-u-e.y;s=g*y/p;var v={x:n.x<e.x?n.x+g-s:n.x-s,y:n.y<e.y?n.y+p-y:n.y-y};return c.warn("topp/bott calc, Q ".concat(p,", q ").concat(y,", R ").concat(g,", r ").concat(s),v),v}var m=m=p*(s=n.x<e.x?e.x-o-r:r-o-e.x)/g;return c.warn("sides calc, Q ".concat(p,", q ").concat(m,", R ").concat(g,", r ").concat(s),{x:n.x<e.x?n.x+g-s:n.x+a-o,y:n.y<e.y?n.y+m:n.y-m}),{x:n.x<e.x?n.x+g-s:n.x+a-o,y:n.y<e.y?n.y+m:n.y-m}},Mn=function t(e,n,r,i){c.info("Graph in recursive render: XXX",G.a.json.write(n),i);var a=n.graph().rankdir;c.warn("Dir in recursive render - dir:",a);var o=e.insert("g").attr("class","root");n.nodes()?c.info("Recursive render XXX",n.nodes()):c.info("No nodes found for",n),n.edges().length>0&&c.info("Recursive edges",n.edge(n.edges()[0]));var s=o.insert("g").attr("class","clusters"),u=o.insert("g").attr("class","edgePaths"),l=o.insert("g").attr("class","edgeLabels"),h=o.insert("g").attr("class","nodes");return n.nodes().forEach((function(e){var o=n.node(e);if(void 0!==i){var s=JSON.parse(JSON.stringify(i.clusterData));c.info("Setting data for cluster XXX (",e,") ",s,i),n.setNode(i.id,s),n.parent(e)||(c.warn("Setting parent",e,i.id),n.setParent(e,i.id,s))}if(c.info("(Insert) Node XXX"+e+": "+JSON.stringify(n.node(e))),o&&o.clusterNode){c.info("Cluster identified",e,o,n.node(e));var u=t(h,o.graph,r,n.node(e));Ae(o,u),function(t,e){_n[e.id]=t}(u,o),c.warn("Recursive render complete",u,o)}else n.children(e).length>0?(c.info("Cluster - the non recursive path XXX",e,o.id,o,n),c.info(Le(o.id,n)),Me[o.id]={id:Le(o.id,n),node:o}):(c.info("Node - the non recursive path",e,o.id,o),function(t,e,n){var r,i;e.link?(r=t.insert("svg:a").attr("xlink:href",e.link).attr("target",e.linkTarget||"_blank"),i=xn[e.shape](r,e,n)):r=i=xn[e.shape](t,e,n),e.tooltip&&i.attr("title",e.tooltip),e.class&&i.attr("class","node default "+e.class),_n[e.id]=r,e.haveCallback&&_n[e.id].attr("class",_n[e.id].attr("class")+" clickable")}(h,n.node(e),a))})),n.edges().forEach((function(t){var e=n.edge(t.v,t.w,t.name);c.info("Edge "+t.v+" -> "+t.w+": "+JSON.stringify(t)),c.info("Edge "+t.v+" -> "+t.w+": ",t," ",JSON.stringify(n.edge(t))),c.info("Fix",Me,"ids:",t.v,t.w,"Translateing: ",Me[t.v],Me[t.w]),function(t,e){var n=Te(e.label,e.labelStyle),r=t.insert("g").attr("class","edgeLabel"),i=r.insert("g").attr("class","label");i.node().appendChild(n);var a=n.getBBox();if(_t().flowchart.htmlLabels){var o=n.children[0],s=Object(d.select)(n);a=o.getBoundingClientRect(),s.attr("width",a.width),s.attr("height",a.height)}if(i.attr("transform","translate("+-a.width/2+", "+-a.height/2+")"),Tn[e.id]=r,e.width=a.width,e.height=a.height,e.startLabelLeft){var c=Te(e.startLabelLeft,e.labelStyle),u=t.insert("g").attr("class","edgeTerminals"),l=u.insert("g").attr("class","inner");l.node().appendChild(c);var h=c.getBBox();l.attr("transform","translate("+-h.width/2+", "+-h.height/2+")"),Cn[e.id]||(Cn[e.id]={}),Cn[e.id].startLeft=u}if(e.startLabelRight){var f=Te(e.startLabelRight,e.labelStyle),p=t.insert("g").attr("class","edgeTerminals"),g=p.insert("g").attr("class","inner");p.node().appendChild(f),g.node().appendChild(f);var y=f.getBBox();g.attr("transform","translate("+-y.width/2+", "+-y.height/2+")"),Cn[e.id]||(Cn[e.id]={}),Cn[e.id].startRight=p}if(e.endLabelLeft){var v=Te(e.endLabelLeft,e.labelStyle),m=t.insert("g").attr("class","edgeTerminals"),b=m.insert("g").attr("class","inner");b.node().appendChild(v);var x=v.getBBox();b.attr("transform","translate("+-x.width/2+", "+-x.height/2+")"),m.node().appendChild(v),Cn[e.id]||(Cn[e.id]={}),Cn[e.id].endLeft=m}if(e.endLabelRight){var _=Te(e.endLabelRight,e.labelStyle),k=t.insert("g").attr("class","edgeTerminals"),w=k.insert("g").attr("class","inner");w.node().appendChild(_);var E=_.getBBox();w.attr("transform","translate("+-E.width/2+", "+-E.height/2+")"),k.node().appendChild(_),Cn[e.id]||(Cn[e.id]={}),Cn[e.id].endRight=k}}(l,e)})),n.edges().forEach((function(t){c.info("Edge "+t.v+" -> "+t.w+": "+JSON.stringify(t))})),c.info("#############################################"),c.info("### Layout ###"),c.info("#############################################"),c.info(n),ke.a.layout(n),c.info("Graph after layout:",G.a.json.write(n)),je(n).forEach((function(t){var e=n.node(t);c.info("Position "+t+": "+JSON.stringify(n.node(t))),c.info("Position "+t+": ("+e.x,","+e.y,") width: ",e.width," height: ",e.height),e&&e.clusterNode?kn(e):n.children(t).length>0?(!function(t,e){c.trace("Inserting cluster");var n=e.shape||"rect";En[e.id]=wn[n](t,e)}(s,e),Me[e.id].node=e):kn(e)})),n.edges().forEach((function(t){var e=n.edge(t);c.info("Edge "+t.v+" -> "+t.w+": "+JSON.stringify(e),e);var i=function(t,e,n,r,i,a){var o=n.points,s=!1,u=a.node(e.v),l=a.node(e.w);if(l.intersect&&u.intersect&&((o=o.slice(1,n.points.length-1)).unshift(u.intersect(o[0])),c.info("Last point",o[o.length-1],l,l.intersect(o[o.length-1])),o.push(l.intersect(o[o.length-1]))),n.toCluster){var h;c.trace("edge",n),c.trace("to cluster",r[n.toCluster]),o=[];var f=!1;n.points.forEach((function(t){var e=r[n.toCluster].node;if(An(e,t)||f)f||o.push(t);else{c.trace("inside",n.toCluster,t,h);var i=Sn(e,h,t),a=!1;o.forEach((function(t){a=a||t.x===i.x&&t.y===i.y})),o.find((function(t){return t.x===i.x&&t.y===i.y}))?c.warn("no intersect",i,o):o.push(i),f=!0}h=t})),s=!0}if(n.fromCluster){c.trace("edge",n),c.warn("from cluster",r[n.fromCluster]);for(var p,g=[],y=!1,v=o.length-1;v>=0;v--){var m=o[v],b=r[n.fromCluster].node;if(An(b,m)||y)c.trace("Outside point",m),y||g.unshift(m);else{c.warn("inside",n.fromCluster,m,b);var x=Sn(b,p,m);g.unshift(x),y=!0}p=m}o=g,s=!0}var _,k=o.filter((function(t){return!Number.isNaN(t.y)})),w=Object(d.line)().x((function(t){return t.x})).y((function(t){return t.y})).curve(d.curveBasis);switch(n.thickness){case"normal":_="edge-thickness-normal";break;case"thick":_="edge-thickness-thick";break;default:_=""}switch(n.pattern){case"solid":_+=" edge-pattern-solid";break;case"dotted":_+=" edge-pattern-dotted";break;case"dashed":_+=" edge-pattern-dashed"}var E=t.append("path").attr("d",w(k)).attr("id",n.id).attr("class"," "+_+(n.classes?" "+n.classes:"")).attr("style",n.style),T="";switch(_t().state.arrowMarkerAbsolute&&(T=(T=(T=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),c.info("arrowTypeStart",n.arrowTypeStart),c.info("arrowTypeEnd",n.arrowTypeEnd),n.arrowTypeStart){case"arrow_cross":E.attr("marker-start","url("+T+"#"+i+"-crossStart)");break;case"arrow_point":E.attr("marker-start","url("+T+"#"+i+"-pointStart)");break;case"arrow_barb":E.attr("marker-start","url("+T+"#"+i+"-barbStart)");break;case"arrow_circle":E.attr("marker-start","url("+T+"#"+i+"-circleStart)");break;case"aggregation":E.attr("marker-start","url("+T+"#"+i+"-aggregationStart)");break;case"extension":E.attr("marker-start","url("+T+"#"+i+"-extensionStart)");break;case"composition":E.attr("marker-start","url("+T+"#"+i+"-compositionStart)");break;case"dependency":E.attr("marker-start","url("+T+"#"+i+"-dependencyStart)")}switch(n.arrowTypeEnd){case"arrow_cross":E.attr("marker-end","url("+T+"#"+i+"-crossEnd)");break;case"arrow_point":E.attr("marker-end","url("+T+"#"+i+"-pointEnd)");break;case"arrow_barb":E.attr("marker-end","url("+T+"#"+i+"-barbEnd)");break;case"arrow_circle":E.attr("marker-end","url("+T+"#"+i+"-circleEnd)");break;case"aggregation":E.attr("marker-end","url("+T+"#"+i+"-aggregationEnd)");break;case"extension":E.attr("marker-end","url("+T+"#"+i+"-extensionEnd)");break;case"composition":E.attr("marker-end","url("+T+"#"+i+"-compositionEnd)");break;case"dependency":E.attr("marker-end","url("+T+"#"+i+"-dependencyEnd)")}var C={};return s&&(C.updatedPath=o),C.originalPath=n.points,C}(u,t,e,Me,r,n);!function(t,e){c.info("Moving label",t.id,t.label,Tn[t.id]);var n=e.updatedPath?e.updatedPath:e.originalPath;if(t.label){var r=Tn[t.id],i=t.x,a=t.y;if(n){var o=H.calcLabelPosition(n);c.info("Moving label from (",i,",",a,") to (",o.x,",",o.y,")")}r.attr("transform","translate("+i+", "+a+")")}if(t.startLabelLeft){var s=Cn[t.id].startLeft,u=t.x,l=t.y;if(n){var h=H.calcTerminalLabelPosition(0,"start_left",n);u=h.x,l=h.y}s.attr("transform","translate("+u+", "+l+")")}if(t.startLabelRight){var f=Cn[t.id].startRight,d=t.x,p=t.y;if(n){var g=H.calcTerminalLabelPosition(0,"start_right",n);d=g.x,p=g.y}f.attr("transform","translate("+d+", "+p+")")}if(t.endLabelLeft){var y=Cn[t.id].endLeft,v=t.x,m=t.y;if(n){var b=H.calcTerminalLabelPosition(0,"end_left",n);v=b.x,m=b.y}y.attr("transform","translate("+v+", "+m+")")}if(t.endLabelRight){var x=Cn[t.id].endRight,_=t.x,k=t.y;if(n){var w=H.calcTerminalLabelPosition(0,"end_right",n);_=w.x,k=w.y}x.attr("transform","translate("+_+", "+k+")")}}(e,i)})),o},On=function(t,e,n,r,i){Ee(t,n,r,i),_n={},Tn={},Cn={},En={},Oe={},De={},Me={},c.warn("Graph at first:",G.a.json.write(e)),Pe(e),c.warn("Graph after:",G.a.json.write(e)),Mn(t,e,r)},Dn={},Nn=function(t,e,n){var r=Object(d.select)('[id="'.concat(n,'"]'));Object.keys(t).forEach((function(n){var i=t[n],a="default";i.classes.length>0&&(a=i.classes.join(" "));var o,s=B(i.styles),u=void 0!==i.text?i.text:i.id;if(_t().flowchart.htmlLabels){var l={label:u.replace(/fa[lrsb]?:fa-[\w-]+/g,(function(t){return"<i class='".concat(t.replace(":"," "),"'></i>")}))};(o=ee()(r,l).node()).parentNode.removeChild(o)}else{var h=document.createElementNS("http://www.w3.org/2000/svg","text");h.setAttribute("style",s.labelStyle.replace("color:","fill:"));for(var f=u.split(x.lineBreakRegex),d=0;d<f.length;d++){var p=document.createElementNS("http://www.w3.org/2000/svg","tspan");p.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),p.setAttribute("dy","1em"),p.setAttribute("x","1"),p.textContent=f[d],h.appendChild(p)}o=h}var g=0,y="";switch(i.type){case"round":g=5,y="rect";break;case"square":y="rect";break;case"diamond":y="question";break;case"hexagon":y="hexagon";break;case"odd":y="rect_left_inv_arrow";break;case"lean_right":y="lean_right";break;case"lean_left":y="lean_left";break;case"trapezoid":y="trapezoid";break;case"inv_trapezoid":y="inv_trapezoid";break;case"odd_right":y="rect_left_inv_arrow";break;case"circle":y="circle";break;case"ellipse":y="ellipse";break;case"stadium":y="stadium";break;case"subroutine":y="subroutine";break;case"cylinder":y="cylinder";break;case"group":y="rect";break;default:y="rect"}e.setNode(i.id,{labelStyle:s.labelStyle,shape:y,labelText:u,rx:g,ry:g,class:a,style:s.style,id:i.id,link:i.link,linkTarget:i.linkTarget,tooltip:Xt.getTooltip(i.id)||"",domId:Xt.lookUpDomId(i.id),haveCallback:i.haveCallback,width:"group"===i.type?500:void 0,type:i.type,padding:_t().flowchart.padding}),c.info("setNode",{labelStyle:s.labelStyle,shape:y,labelText:u,rx:g,ry:g,class:a,style:s.style,id:i.id,domId:Xt.lookUpDomId(i.id),width:"group"===i.type?500:void 0,type:i.type,padding:_t().flowchart.padding})}))},Bn=function(t,e){var n,r,i=0;if(void 0!==t.defaultStyle){var a=B(t.defaultStyle);n=a.style,r=a.labelStyle}t.forEach((function(a){i++;var o="L-"+a.start+"-"+a.end,s="LS-"+a.start,c="LE-"+a.end,u={style:"",labelStyle:""};switch(u.minlen=a.length||1,"arrow_open"===a.type?u.arrowhead="none":u.arrowhead="normal",u.arrowTypeStart="arrow_open",u.arrowTypeEnd="arrow_open",a.type){case"double_arrow_cross":u.arrowTypeStart="arrow_cross";case"arrow_cross":u.arrowTypeEnd="arrow_cross";break;case"double_arrow_point":u.arrowTypeStart="arrow_point";case"arrow_point":u.arrowTypeEnd="arrow_point";break;case"double_arrow_circle":u.arrowTypeStart="arrow_circle";case"arrow_circle":u.arrowTypeEnd="arrow_circle"}var l="",h="";switch(a.stroke){case"normal":l="fill:none;",void 0!==n&&(l=n),void 0!==r&&(h=r),u.thickness="normal",u.pattern="solid";break;case"dotted":u.thickness="normal",u.pattern="dotted",u.style="fill:none;stroke-width:2px;stroke-dasharray:3;";break;case"thick":u.thickness="thick",u.pattern="solid",u.style="stroke-width: 3.5px;fill:none;"}if(void 0!==a.style){var f=B(a.style);l=f.style,h=f.labelStyle}u.style=u.style+=l,u.labelStyle=u.labelStyle+=h,void 0!==a.interpolate?u.curve=D(a.interpolate,d.curveLinear):void 0!==t.defaultInterpolate?u.curve=D(t.defaultInterpolate,d.curveLinear):u.curve=D(Dn.curve,d.curveLinear),void 0===a.text?void 0!==a.style&&(u.arrowheadStyle="fill: #333"):(u.arrowheadStyle="fill: #333",u.labelpos="c"),u.labelType="text",u.label=a.text.replace(x.lineBreakRegex,"\n"),void 0===a.style&&(u.style=u.style||"stroke: #333; stroke-width: 1.5px;fill:none;"),u.labelStyle=u.labelStyle.replace("color:","fill:"),u.id=o,u.classes="flowchart-link "+s+" "+c,e.setEdge(a.start,a.end,u,i)}))},Ln=function(t){for(var e=Object.keys(t),n=0;n<e.length;n++)Dn[e[n]]=t[e[n]]},Fn=function(t,e){c.info("Drawing flowchart"),Xt.clear(),Xt.setGen("gen-2");var n=Jt.a.parser;n.yy=Xt,n.parse(t);var r=Xt.getDirection();void 0===r&&(r="TD");var i,a=_t().flowchart,o=a.nodeSpacing||50,s=a.rankSpacing||50,u=new G.a.Graph({multigraph:!0,compound:!0}).setGraph({rankdir:r,nodesep:o,ranksep:s,marginx:8,marginy:8}).setDefaultEdgeLabel((function(){return{}})),l=Xt.getSubGraphs();c.info("Subgraphs - ",l);for(var h=l.length-1;h>=0;h--)i=l[h],c.info("Subgraph - ",i),Xt.addVertex(i.id,i.title,"group",void 0,i.classes);var f=Xt.getVertices(),p=Xt.getEdges();c.info(p);var g=0;for(g=l.length-1;g>=0;g--){i=l[g],Object(d.selectAll)("cluster").append("text");for(var y=0;y<i.nodes.length;y++)c.info("Setting up subgraphs",i.nodes[y],i.id),u.setParent(i.nodes[y],i.id)}Nn(f,u,e),Bn(p,u);var v=Object(d.select)('[id="'.concat(e,'"]'));v.attr("xmlns:xlink","http://www.w3.org/1999/xlink");var m=Object(d.select)("#"+e+" g");On(m,u,["point","circle","cross"],"flowchart",e);var b=a.diagramPadding,x=v.node().getBBox(),_=x.width+2*b,k=x.height+2*b;if(c.debug("new ViewBox 0 0 ".concat(_," ").concat(k),"translate(".concat(b-u._label.marginx,", ").concat(b-u._label.marginy,")")),W(v,k,_,a.useMaxWidth),v.attr("viewBox","0 0 ".concat(_," ").concat(k)),v.select("g").attr("transform","translate(".concat(b-u._label.marginx,", ").concat(b-x.y,")")),Xt.indexNodes("subGraph"+g),!a.htmlLabels)for(var w=document.querySelectorAll('[id="'+e+'"] .edgeLabel .label'),E=0;E<w.length;E++){var T=w[E],C=T.getBBox(),A=document.createElementNS("http://www.w3.org/2000/svg","rect");A.setAttribute("rx",0),A.setAttribute("ry",0),A.setAttribute("width",C.width),A.setAttribute("height",C.height),T.insertBefore(A,T.firstChild)}Object.keys(f).forEach((function(t){var n=f[t];if(n.link){var r=Object(d.select)("#"+e+' [id="'+t+'"]');if(r){var i=document.createElementNS("http://www.w3.org/2000/svg","a");i.setAttributeNS("http://www.w3.org/2000/svg","class",n.classes.join(" ")),i.setAttributeNS("http://www.w3.org/2000/svg","href",n.link),i.setAttributeNS("http://www.w3.org/2000/svg","rel","noopener"),n.linkTarget&&i.setAttributeNS("http://www.w3.org/2000/svg","target",n.linkTarget);var a=r.insert((function(){return i}),":first-child"),o=r.select(".label-container");o&&a.append((function(){return o.node()}));var s=r.select(".label");s&&a.append((function(){return s.node()}))}}}))},Pn=function(t,e){var n=t.append("rect");return n.attr("x",e.x),n.attr("y",e.y),n.attr("fill",e.fill),n.attr("stroke",e.stroke),n.attr("width",e.width),n.attr("height",e.height),n.attr("rx",e.rx),n.attr("ry",e.ry),void 0!==e.class&&n.attr("class",e.class),n},In=function(t,e){var n=0,r=0,i=e.text.split(x.lineBreakRegex),a=[],o=0,s=function(){return e.y};if(void 0!==e.valign&&void 0!==e.textMargin&&e.textMargin>0)switch(e.valign){case"top":case"start":s=function(){return Math.round(e.y+e.textMargin)};break;case"middle":case"center":s=function(){return Math.round(e.y+(n+r+e.textMargin)/2)};break;case"bottom":case"end":s=function(){return Math.round(e.y+(n+r+2*e.textMargin)-e.textMargin)}}if(void 0!==e.anchor&&void 0!==e.textMargin&&void 0!==e.width)switch(e.anchor){case"left":case"start":e.x=Math.round(e.x+e.textMargin),e.anchor="start",e.dominantBaseline="text-after-edge",e.alignmentBaseline="middle";break;case"middle":case"center":e.x=Math.round(e.x+e.width/2),e.anchor="middle",e.dominantBaseline="middle",e.alignmentBaseline="middle";break;case"right":case"end":e.x=Math.round(e.x+e.width-e.textMargin),e.anchor="end",e.dominantBaseline="text-before-edge",e.alignmentBaseline="middle"}for(var c=0;c<i.length;c++){var u=i[c];void 0!==e.textMargin&&0===e.textMargin&&void 0!==e.fontSize&&(o=c*e.fontSize);var l=t.append("text");if(l.attr("x",e.x),l.attr("y",s()),void 0!==e.anchor&&l.attr("text-anchor",e.anchor).attr("dominant-baseline",e.dominantBaseline).attr("alignment-baseline",e.alignmentBaseline),void 0!==e.fontFamily&&l.style("font-family",e.fontFamily),void 0!==e.fontSize&&l.style("font-size",e.fontSize),void 0!==e.fontWeight&&l.style("font-weight",e.fontWeight),void 0!==e.fill&&l.attr("fill",e.fill),void 0!==e.class&&l.attr("class",e.class),void 0!==e.dy?l.attr("dy",e.dy):0!==o&&l.attr("dy",o),e.tspan){var h=l.append("tspan");h.attr("x",e.x),void 0!==e.fill&&h.attr("fill",e.fill),h.text(u)}else l.text(u);void 0!==e.valign&&void 0!==e.textMargin&&e.textMargin>0&&(r+=(l._groups||l)[0][0].getBBox().height,n=r),a.push(l)}return a},jn=function(t,e){var n,r,i,a,o,s=t.append("polygon");return s.attr("points",(n=e.x,r=e.y,i=e.width,a=e.height,n+","+r+" "+(n+i)+","+r+" "+(n+i)+","+(r+a-(o=7))+" "+(n+i-1.2*o)+","+(r+a)+" "+n+","+(r+a))),s.attr("class","labelBox"),e.y=e.y+e.height/2,In(t,e),s},Rn=-1,Yn=function(){return{x:0,y:0,fill:void 0,anchor:void 0,style:"#666",width:void 0,height:void 0,textMargin:0,rx:0,ry:0,tspan:!0,valign:void 0}},zn=function(){return{x:0,y:0,fill:"#EDF2AE",stroke:"#666",width:100,anchor:"start",height:100,rx:0,ry:0}},Un=function(){function t(t,e,n,i,a,o,s){r(e.append("text").attr("x",n+a/2).attr("y",i+o/2+5).style("text-anchor","middle").text(t),s)}function e(t,e,n,i,a,o,s,c){for(var u=c.actorFontSize,l=c.actorFontFamily,h=c.actorFontWeight,f=t.split(x.lineBreakRegex),d=0;d<f.length;d++){var p=d*u-u*(f.length-1)/2,g=e.append("text").attr("x",n+a/2).attr("y",i).style("text-anchor","middle").style("font-size",u).style("font-weight",h).style("font-family",l);g.append("tspan").attr("x",n+a/2).attr("dy",p).text(f[d]),g.attr("y",i+o/2).attr("dominant-baseline","central").attr("alignment-baseline","central"),r(g,s)}}function n(t,n,i,a,o,s,c,u){var l=n.append("switch"),h=l.append("foreignObject").attr("x",i).attr("y",a).attr("width",o).attr("height",s).append("div").style("display","table").style("height","100%").style("width","100%");h.append("div").style("display","table-cell").style("text-align","center").style("vertical-align","middle").text(t),e(t,l,i,a,o,s,c,u),r(h,c)}function r(t,e){for(var n in e)e.hasOwnProperty(n)&&t.attr(n,e[n])}return function(r){return"fo"===r.textPlacement?n:"old"===r.textPlacement?t:e}}(),$n={drawRect:Pn,drawText:In,drawLabel:jn,drawActor:function(t,e,n){var r=e.x+e.width/2,i=t.append("g");0===e.y&&(Rn++,i.append("line").attr("id","actor"+Rn).attr("x1",r).attr("y1",5).attr("x2",r).attr("y2",2e3).attr("class","actor-line").attr("stroke-width","0.5px").attr("stroke","#999"));var a=zn();a.x=e.x,a.y=e.y,a.fill="#eaeaea",a.width=e.width,a.height=e.height,a.class="actor",a.rx=3,a.ry=3,Pn(i,a),Un(n)(e.description,i,a.x,a.y,a.width,a.height,{class:"actor"},n)},anchorElement:function(t){return t.append("g")},drawActivation:function(t,e,n,r,i){var a=zn(),o=e.anchored;a.x=e.startx,a.y=e.starty,a.class="activation"+i%3,a.width=e.stopx-e.startx,a.height=n-e.starty,Pn(o,a)},drawLoop:function(t,e,n,r){var i=r.boxMargin,a=r.boxTextMargin,o=r.labelBoxHeight,s=r.labelBoxWidth,c=r.messageFontFamily,u=r.messageFontSize,l=r.messageFontWeight,h=t.append("g"),f=function(t,e,n,r){return h.append("line").attr("x1",t).attr("y1",e).attr("x2",n).attr("y2",r).attr("class","loopLine")};f(e.startx,e.starty,e.stopx,e.starty),f(e.stopx,e.starty,e.stopx,e.stopy),f(e.startx,e.stopy,e.stopx,e.stopy),f(e.startx,e.starty,e.startx,e.stopy),void 0!==e.sections&&e.sections.forEach((function(t){f(e.startx,t.y,e.stopx,t.y).style("stroke-dasharray","3, 3")}));var d=Yn();d.text=n,d.x=e.startx,d.y=e.starty,d.fontFamily=c,d.fontSize=u,d.fontWeight=l,d.anchor="middle",d.valign="middle",d.tspan=!1,d.width=s||50,d.height=o||20,d.textMargin=a,d.class="labelText",jn(h,d),(d=Yn()).text=e.title,d.x=e.startx+s/2+(e.stopx-e.startx)/2,d.y=e.starty+i+a,d.anchor="middle",d.valign="middle",d.textMargin=a,d.class="loopText",d.fontFamily=c,d.fontSize=u,d.fontWeight=l,d.wrap=!0;var p=In(h,d);return void 0!==e.sectionTitles&&e.sectionTitles.forEach((function(t,n){if(t.message){d.text=t.message,d.x=e.startx+(e.stopx-e.startx)/2,d.y=e.sections[n].y+i+a,d.class="loopText",d.anchor="middle",d.valign="middle",d.tspan=!1,d.fontFamily=c,d.fontSize=u,d.fontWeight=l,d.wrap=e.wrap,p=In(h,d);var r=Math.round(p.map((function(t){return(t._groups||t)[0][0].getBBox().height})).reduce((function(t,e){return t+e})));e.sections[n].height+=r-(i+a)}})),e.height=Math.round(e.stopy-e.starty),h},drawBackgroundRect:function(t,e){Pn(t,{x:e.startx,y:e.starty,width:e.stopx-e.startx,height:e.stopy-e.starty,fill:e.fill,class:"rect"}).lower()},insertArrowHead:function(t){t.append("defs").append("marker").attr("id","arrowhead").attr("refX",5).attr("refY",2).attr("markerWidth",6).attr("markerHeight",4).attr("orient","auto").append("path").attr("d","M 0,0 V 4 L6,2 Z")},insertSequenceNumber:function(t){t.append("defs").append("marker").attr("id","sequencenumber").attr("refX",15).attr("refY",15).attr("markerWidth",60).attr("markerHeight",40).attr("orient","auto").append("circle").attr("cx",15).attr("cy",15).attr("r",6)},insertArrowCrossHead:function(t){var e=t.append("defs").append("marker").attr("id","crosshead").attr("markerWidth",15).attr("markerHeight",8).attr("orient","auto").attr("refX",16).attr("refY",4);e.append("path").attr("fill","black").attr("stroke","#000000").style("stroke-dasharray","0, 0").attr("stroke-width","1px").attr("d","M 9,2 V 6 L16,4 Z"),e.append("path").attr("fill","none").attr("stroke","#000000").style("stroke-dasharray","0, 0").attr("stroke-width","1px").attr("d","M 0,1 L 6,7 M 6,1 L 0,7")},getTextObj:Yn,getNoteRect:zn},Wn=n(2),Hn=n.n(Wn),Vn=void 0,Gn={},qn=[],Xn=[],Zn="",Jn=!1,Kn=!1,Qn=!1,tr=function(t,e,n){var r=Gn[t];r&&e===r.name&&null==n||(null!=n&&null!=n.text||(n={text:e,wrap:null}),Gn[t]={name:e,description:n.text,wrap:void 0===n.wrap&&rr()||!!n.wrap,prevActor:Vn},Vn&&Gn[Vn]&&(Gn[Vn].nextActor=t),Vn=t)},er=function(t){var e,n=0;for(e=0;e<qn.length;e++)qn[e].type===ir.ACTIVE_START&&qn[e].from.actor===t&&n++,qn[e].type===ir.ACTIVE_END&&qn[e].from.actor===t&&n--;return n},nr=function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{text:void 0,wrap:void 0},r=arguments.length>3?arguments[3]:void 0;if(r===ir.ACTIVE_END){var i=er(t.actor);if(i<1){var a=new Error("Trying to inactivate an inactive participant ("+t.actor+")");throw a.hash={text:"->>-",token:"->>-",line:"1",loc:{first_line:1,last_line:1,first_column:1,last_column:1},expected:["'ACTIVE_PARTICIPANT'"]},a}}return qn.push({from:t,to:e,message:n.text,wrap:void 0===n.wrap&&rr()||!!n.wrap,type:r}),!0},rr=function(){return Qn},ir={SOLID:0,DOTTED:1,NOTE:2,SOLID_CROSS:3,DOTTED_CROSS:4,SOLID_OPEN:5,DOTTED_OPEN:6,LOOP_START:10,LOOP_END:11,ALT_START:12,ALT_ELSE:13,ALT_END:14,OPT_START:15,OPT_END:16,ACTIVE_START:17,ACTIVE_END:18,PAR_START:19,PAR_AND:20,PAR_END:21,RECT_START:22,RECT_END:23},ar=function(t,e,n){var r={actor:t,placement:e,message:n.text,wrap:void 0===n.wrap&&rr()||!!n.wrap},i=[].concat(t,t);Xn.push(r),qn.push({from:i[0],to:i[1],message:n.text,wrap:void 0===n.wrap&&rr()||!!n.wrap,type:ir.NOTE,placement:e})},or=function(t){Zn=t.text,Jn=void 0===t.wrap&&rr()||!!t.wrap},sr={addActor:tr,addMessage:function(t,e,n,r){qn.push({from:t,to:e,message:n.text,wrap:void 0===n.wrap&&rr()||!!n.wrap,answer:r})},addSignal:nr,autoWrap:rr,setWrap:function(t){Qn=t},enableSequenceNumbers:function(){Kn=!0},showSequenceNumbers:function(){return Kn},getMessages:function(){return qn},getActors:function(){return Gn},getActor:function(t){return Gn[t]},getActorKeys:function(){return Object.keys(Gn)},getTitle:function(){return Zn},parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},getConfig:function(){return _t().sequence},getTitleWrapped:function(){return Jn},clear:function(){Gn={},qn=[]},parseMessage:function(t){var e=t.trim(),n={text:e.replace(/^[:]?(?:no)?wrap:/,"").trim(),wrap:null!==e.match(/^[:]?wrap:/)||null===e.match(/^[:]?nowrap:/)&&void 0};return c.debug("parseMessage:",n),n},LINETYPE:ir,ARROWTYPE:{FILLED:0,OPEN:1},PLACEMENT:{LEFTOF:0,RIGHTOF:1,OVER:2},addNote:ar,setTitle:or,apply:function t(e){if(e instanceof Array)e.forEach((function(e){t(e)}));else switch(e.type){case"addActor":tr(e.actor,e.actor,e.description);break;case"activeStart":case"activeEnd":nr(e.actor,void 0,void 0,e.signalType);break;case"addNote":ar(e.actor,e.placement,e.text);break;case"addMessage":nr(e.from,e.to,e.msg,e.signalType);break;case"loopStart":nr(void 0,void 0,e.loopText,e.signalType);break;case"loopEnd":nr(void 0,void 0,void 0,e.signalType);break;case"rectStart":nr(void 0,void 0,e.color,e.signalType);break;case"rectEnd":nr(void 0,void 0,void 0,e.signalType);break;case"optStart":nr(void 0,void 0,e.optText,e.signalType);break;case"optEnd":nr(void 0,void 0,void 0,e.signalType);break;case"altStart":case"else":nr(void 0,void 0,e.altText,e.signalType);break;case"altEnd":nr(void 0,void 0,void 0,e.signalType);break;case"setTitle":or(e.text);break;case"parStart":case"and":nr(void 0,void 0,e.parText,e.signalType);break;case"parEnd":nr(void 0,void 0,void 0,e.signalType)}}};Wn.parser.yy=sr;var cr={},ur={data:{startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},verticalPos:0,sequenceItems:[],activations:[],models:{getHeight:function(){return Math.max.apply(null,0===this.actors.length?[0]:this.actors.map((function(t){return t.height||0})))+(0===this.loops.length?0:this.loops.map((function(t){return t.height||0})).reduce((function(t,e){return t+e})))+(0===this.messages.length?0:this.messages.map((function(t){return t.height||0})).reduce((function(t,e){return t+e})))+(0===this.notes.length?0:this.notes.map((function(t){return t.height||0})).reduce((function(t,e){return t+e})))},clear:function(){this.actors=[],this.loops=[],this.messages=[],this.notes=[]},addActor:function(t){this.actors.push(t)},addLoop:function(t){this.loops.push(t)},addMessage:function(t){this.messages.push(t)},addNote:function(t){this.notes.push(t)},lastActor:function(){return this.actors[this.actors.length-1]},lastLoop:function(){return this.loops[this.loops.length-1]},lastMessage:function(){return this.messages[this.messages.length-1]},lastNote:function(){return this.notes[this.notes.length-1]},actors:[],loops:[],messages:[],notes:[]},init:function(){this.sequenceItems=[],this.activations=[],this.models.clear(),this.data={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},this.verticalPos=0,pr(Wn.parser.yy.getConfig())},updateVal:function(t,e,n,r){void 0===t[e]?t[e]=n:t[e]=r(n,t[e])},updateBounds:function(t,e,n,r){var i=this,a=0;function o(o){return function(s){a++;var c=i.sequenceItems.length-a+1;i.updateVal(s,"starty",e-c*cr.boxMargin,Math.min),i.updateVal(s,"stopy",r+c*cr.boxMargin,Math.max),i.updateVal(ur.data,"startx",t-c*cr.boxMargin,Math.min),i.updateVal(ur.data,"stopx",n+c*cr.boxMargin,Math.max),"activation"!==o&&(i.updateVal(s,"startx",t-c*cr.boxMargin,Math.min),i.updateVal(s,"stopx",n+c*cr.boxMargin,Math.max),i.updateVal(ur.data,"starty",e-c*cr.boxMargin,Math.min),i.updateVal(ur.data,"stopy",r+c*cr.boxMargin,Math.max))}}this.sequenceItems.forEach(o()),this.activations.forEach(o("activation"))},insert:function(t,e,n,r){var i=Math.min(t,n),a=Math.max(t,n),o=Math.min(e,r),s=Math.max(e,r);this.updateVal(ur.data,"startx",i,Math.min),this.updateVal(ur.data,"starty",o,Math.min),this.updateVal(ur.data,"stopx",a,Math.max),this.updateVal(ur.data,"stopy",s,Math.max),this.updateBounds(i,o,a,s)},newActivation:function(t,e,n){var r=n[t.from.actor],i=gr(t.from.actor).length||0,a=r.x+r.width/2+(i-1)*cr.activationWidth/2;this.activations.push({startx:a,starty:this.verticalPos+2,stopx:a+cr.activationWidth,stopy:void 0,actor:t.from.actor,anchored:$n.anchorElement(e)})},endActivation:function(t){var e=this.activations.map((function(t){return t.actor})).lastIndexOf(t.from.actor);return this.activations.splice(e,1)[0]},createLoop:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{message:void 0,wrap:!1,width:void 0},e=arguments.length>1?arguments[1]:void 0;return{startx:void 0,starty:this.verticalPos,stopx:void 0,stopy:void 0,title:t.message,wrap:t.wrap,width:t.width,height:0,fill:e}},newLoop:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{message:void 0,wrap:!1,width:void 0},e=arguments.length>1?arguments[1]:void 0;this.sequenceItems.push(this.createLoop(t,e))},endLoop:function(){return this.sequenceItems.pop()},addSectionToLoop:function(t){var e=this.sequenceItems.pop();e.sections=e.sections||[],e.sectionTitles=e.sectionTitles||[],e.sections.push({y:ur.getVerticalPos(),height:0}),e.sectionTitles.push(t),this.sequenceItems.push(e)},bumpVerticalPos:function(t){this.verticalPos=this.verticalPos+t,this.data.stopy=this.verticalPos},getVerticalPos:function(){return this.verticalPos},getBounds:function(){return{bounds:this.data,models:this.models}}},lr=function(t){return{fontFamily:t.messageFontFamily,fontSize:t.messageFontSize,fontWeight:t.messageFontWeight}},hr=function(t){return{fontFamily:t.noteFontFamily,fontSize:t.noteFontSize,fontWeight:t.noteFontWeight}},fr=function(t){return{fontFamily:t.actorFontFamily,fontSize:t.actorFontSize,fontWeight:t.actorFontWeight}},dr=function(t,e,n,r){for(var i=0,a=0,o=0;o<n.length;o++){var s=e[n[o]];s.width=s.width||cr.width,s.height=Math.max(s.height||cr.height,cr.height),s.margin=s.margin||cr.actorMargin,s.x=i+a,s.y=r,$n.drawActor(t,s,cr),ur.insert(s.x,r,s.x+s.width,s.height),i+=s.width,a+=s.margin,ur.models.addActor(s)}ur.bumpVerticalPos(cr.height)},pr=function(t){I(cr,t),t.fontFamily&&(cr.actorFontFamily=cr.noteFontFamily=cr.messageFontFamily=t.fontFamily),t.fontSize&&(cr.actorFontSize=cr.noteFontSize=cr.messageFontSize=t.fontSize),t.fontWeight&&(cr.actorFontWeight=cr.noteFontWeight=cr.messageFontWeight=t.fontWeight)},gr=function(t){return ur.activations.filter((function(e){return e.actor===t}))},yr=function(t,e){var n=e[t],r=gr(t);return[r.reduce((function(t,e){return Math.min(t,e.startx)}),n.x+n.width/2),r.reduce((function(t,e){return Math.max(t,e.stopx)}),n.x+n.width/2)]};function vr(t,e,n,r,i){ur.bumpVerticalPos(n);var a=r;if(e.id&&e.message&&t[e.id]){var o=t[e.id].width,s=lr(cr);e.message=H.wrapLabel("[".concat(e.message,"]"),o-2*cr.wrapPadding,s),e.width=o,e.wrap=!0;var u=H.calculateTextDimensions(e.message,s),l=Math.max(u.height,cr.labelBoxHeight);a=r+l,c.debug("".concat(l," - ").concat(e.message))}i(e),ur.bumpVerticalPos(a)}var mr=function(t,e){var n={};return e.forEach((function(e){if(t[e.to]&&t[e.from]){var r=t[e.to];if(e.placement===Wn.parser.yy.PLACEMENT.LEFTOF&&!r.prevActor)return;if(e.placement===Wn.parser.yy.PLACEMENT.RIGHTOF&&!r.nextActor)return;var i=void 0!==e.placement,a=!i,o=i?hr(cr):lr(cr),s=e.wrap?H.wrapLabel(e.message,cr.width-2*cr.wrapPadding,o):e.message,c=H.calculateTextDimensions(s,o).width+2*cr.wrapPadding;a&&e.from===r.nextActor?n[e.to]=Math.max(n[e.to]||0,c):a&&e.from===r.prevActor?n[e.from]=Math.max(n[e.from]||0,c):a&&e.from===e.to?(n[e.from]=Math.max(n[e.from]||0,c/2),n[e.to]=Math.max(n[e.to]||0,c/2)):e.placement===Wn.parser.yy.PLACEMENT.RIGHTOF?n[e.from]=Math.max(n[e.from]||0,c):e.placement===Wn.parser.yy.PLACEMENT.LEFTOF?n[r.prevActor]=Math.max(n[r.prevActor]||0,c):e.placement===Wn.parser.yy.PLACEMENT.OVER&&(r.prevActor&&(n[r.prevActor]=Math.max(n[r.prevActor]||0,c/2)),r.nextActor&&(n[e.from]=Math.max(n[e.from]||0,c/2)))}})),c.debug("maxMessageWidthPerActor:",n),n},br=function(t,e){var n=0;for(var r in Object.keys(t).forEach((function(e){var r=t[e];r.wrap&&(r.description=H.wrapLabel(r.description,cr.width-2*cr.wrapPadding,fr(cr)));var i=H.calculateTextDimensions(r.description,fr(cr));r.width=r.wrap?cr.width:Math.max(cr.width,i.width+2*cr.wrapPadding),r.height=r.wrap?Math.max(i.height,cr.height):cr.height,n=Math.max(n,r.height)})),e){var i=t[r];if(i){var a=t[i.nextActor];if(a){var o=e[r]+cr.actorMargin-i.width/2-a.width/2;i.margin=Math.max(o,cr.actorMargin)}}}return Math.max(n,cr.height)},xr=function(t,e){var n,r,i,a={},o=[];return t.forEach((function(t){switch(t.id=H.random({length:10}),t.type){case Wn.parser.yy.LINETYPE.LOOP_START:case Wn.parser.yy.LINETYPE.ALT_START:case Wn.parser.yy.LINETYPE.OPT_START:case Wn.parser.yy.LINETYPE.PAR_START:o.push({id:t.id,msg:t.message,from:Number.MAX_SAFE_INTEGER,to:Number.MIN_SAFE_INTEGER,width:0});break;case Wn.parser.yy.LINETYPE.ALT_ELSE:case Wn.parser.yy.LINETYPE.PAR_AND:t.message&&(n=o.pop(),a[n.id]=n,a[t.id]=n,o.push(n));break;case Wn.parser.yy.LINETYPE.LOOP_END:case Wn.parser.yy.LINETYPE.ALT_END:case Wn.parser.yy.LINETYPE.OPT_END:case Wn.parser.yy.LINETYPE.PAR_END:n=o.pop(),a[n.id]=n;break;case Wn.parser.yy.LINETYPE.ACTIVE_START:var s=e[t.from?t.from.actor:t.to.actor],u=gr(t.from?t.from.actor:t.to.actor).length,l=s.x+s.width/2+(u-1)*cr.activationWidth/2,h={startx:l,stopx:l+cr.activationWidth,actor:t.from.actor,enabled:!0};ur.activations.push(h);break;case Wn.parser.yy.LINETYPE.ACTIVE_END:var f=ur.activations.map((function(t){return t.actor})).lastIndexOf(t.from.actor);delete ur.activations.splice(f,1)[0]}void 0!==t.placement?(r=function(t,e){var n=e[t.from].x,r=e[t.to].x,i=t.wrap&&t.message,a=H.calculateTextDimensions(i?H.wrapLabel(t.message,cr.width,hr(cr)):t.message,hr(cr)),o={width:i?cr.width:Math.max(cr.width,a.width+2*cr.noteMargin),height:0,startx:e[t.from].x,stopx:0,starty:0,stopy:0,message:t.message};return t.placement===Wn.parser.yy.PLACEMENT.RIGHTOF?(o.width=i?Math.max(cr.width,a.width):Math.max(e[t.from].width/2+e[t.to].width/2,a.width+2*cr.noteMargin),o.startx=n+(e[t.from].width+cr.actorMargin)/2):t.placement===Wn.parser.yy.PLACEMENT.LEFTOF?(o.width=i?Math.max(cr.width,a.width+2*cr.noteMargin):Math.max(e[t.from].width/2+e[t.to].width/2,a.width+2*cr.noteMargin),o.startx=n-o.width+(e[t.from].width-cr.actorMargin)/2):t.to===t.from?(a=H.calculateTextDimensions(i?H.wrapLabel(t.message,Math.max(cr.width,e[t.from].width),hr(cr)):t.message,hr(cr)),o.width=i?Math.max(cr.width,e[t.from].width):Math.max(e[t.from].width,cr.width,a.width+2*cr.noteMargin),o.startx=n+(e[t.from].width-o.width)/2):(o.width=Math.abs(n+e[t.from].width/2-(r+e[t.to].width/2))+cr.actorMargin,o.startx=n<r?n+e[t.from].width/2-cr.actorMargin/2:r+e[t.to].width/2-cr.actorMargin/2),i&&(o.message=H.wrapLabel(t.message,o.width-2*cr.wrapPadding,hr(cr))),c.debug("NM:[".concat(o.startx,",").concat(o.stopx,",").concat(o.starty,",").concat(o.stopy,":").concat(o.width,",").concat(o.height,"=").concat(t.message,"]")),o}(t,e),t.noteModel=r,o.forEach((function(t){(n=t).from=Math.min(n.from,r.startx),n.to=Math.max(n.to,r.startx+r.width),n.width=Math.max(n.width,Math.abs(n.from-n.to))-cr.labelBoxWidth}))):(i=function(t,e){var n=!1;if([Wn.parser.yy.LINETYPE.SOLID_OPEN,Wn.parser.yy.LINETYPE.DOTTED_OPEN,Wn.parser.yy.LINETYPE.SOLID,Wn.parser.yy.LINETYPE.DOTTED,Wn.parser.yy.LINETYPE.SOLID_CROSS,Wn.parser.yy.LINETYPE.DOTTED_CROSS].includes(t.type)&&(n=!0),!n)return{};var r=yr(t.from,e),i=yr(t.to,e),a=r[0]<=i[0]?1:0,o=r[0]<i[0]?0:1,s=r.concat(i),c=Math.abs(i[o]-r[a]);t.wrap&&t.message&&(t.message=H.wrapLabel(t.message,Math.max(c+2*cr.wrapPadding,cr.width),lr(cr)));var u=H.calculateTextDimensions(t.message,lr(cr));return{width:Math.max(t.wrap?0:u.width+2*cr.wrapPadding,c+2*cr.wrapPadding,cr.width),height:0,startx:r[a],stopx:i[o],starty:0,stopy:0,message:t.message,type:t.type,wrap:t.wrap,fromBounds:Math.min.apply(null,s),toBounds:Math.max.apply(null,s)}}(t,e),t.msgModel=i,i.startx&&i.stopx&&o.length>0&&o.forEach((function(r){if(n=r,i.startx===i.stopx){var a=e[t.from],o=e[t.to];n.from=Math.min(a.x-i.width/2,a.x-a.width/2,n.from),n.to=Math.max(o.x+i.width/2,o.x+a.width/2,n.to),n.width=Math.max(n.width,Math.abs(n.to-n.from))-cr.labelBoxWidth}else n.from=Math.min(i.startx,n.from),n.to=Math.max(i.stopx,n.to),n.width=Math.max(n.width,i.width)-cr.labelBoxWidth})))})),ur.activations=[],c.debug("Loop type widths:",a),a},_r={bounds:ur,drawActors:dr,setConf:pr,draw:function(t,e){cr=_t().sequence,Wn.parser.yy.clear(),Wn.parser.yy.setWrap(cr.wrap),Wn.parser.parse(t+"\n"),ur.init(),c.debug("C:".concat(JSON.stringify(cr,null,2)));var n=Object(d.select)('[id="'.concat(e,'"]')),r=Wn.parser.yy.getActors(),i=Wn.parser.yy.getActorKeys(),a=Wn.parser.yy.getMessages(),o=Wn.parser.yy.getTitle(),s=mr(r,a);cr.height=br(r,s),dr(n,r,i,0);var u=xr(a,r,s);$n.insertArrowHead(n),$n.insertArrowCrossHead(n),$n.insertSequenceNumber(n);var l=1;a.forEach((function(t){var e,i,a;switch(t.type){case Wn.parser.yy.LINETYPE.NOTE:i=t.noteModel,function(t,e){ur.bumpVerticalPos(cr.boxMargin),e.height=cr.boxMargin,e.starty=ur.getVerticalPos();var n=$n.getNoteRect();n.x=e.startx,n.y=e.starty,n.width=e.width||cr.width,n.class="note";var r=t.append("g"),i=$n.drawRect(r,n),a=$n.getTextObj();a.x=e.startx,a.y=e.starty,a.width=n.width,a.dy="1em",a.text=e.message,a.class="noteText",a.fontFamily=cr.noteFontFamily,a.fontSize=cr.noteFontSize,a.fontWeight=cr.noteFontWeight,a.anchor=cr.noteAlign,a.textMargin=cr.noteMargin,a.valign=cr.noteAlign;var o=In(r,a),s=Math.round(o.map((function(t){return(t._groups||t)[0][0].getBBox().height})).reduce((function(t,e){return t+e})));i.attr("height",s+2*cr.noteMargin),e.height+=s+2*cr.noteMargin,ur.bumpVerticalPos(s+2*cr.noteMargin),e.stopy=e.starty+s+2*cr.noteMargin,e.stopx=e.startx+n.width,ur.insert(e.startx,e.starty,e.stopx,e.stopy),ur.models.addNote(e)}(n,i);break;case Wn.parser.yy.LINETYPE.ACTIVE_START:ur.newActivation(t,n,r);break;case Wn.parser.yy.LINETYPE.ACTIVE_END:!function(t,e){var r=ur.endActivation(t);r.starty+18>e&&(r.starty=e-6,e+=12),$n.drawActivation(n,r,e,cr,gr(t.from.actor).length),ur.insert(r.startx,e-10,r.stopx,e)}(t,ur.getVerticalPos());break;case Wn.parser.yy.LINETYPE.LOOP_START:vr(u,t,cr.boxMargin,cr.boxMargin+cr.boxTextMargin,(function(t){return ur.newLoop(t)}));break;case Wn.parser.yy.LINETYPE.LOOP_END:e=ur.endLoop(),$n.drawLoop(n,e,"loop",cr),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos()),ur.models.addLoop(e);break;case Wn.parser.yy.LINETYPE.RECT_START:vr(u,t,cr.boxMargin,cr.boxMargin,(function(t){return ur.newLoop(void 0,t.message)}));break;case Wn.parser.yy.LINETYPE.RECT_END:e=ur.endLoop(),$n.drawBackgroundRect(n,e),ur.models.addLoop(e),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos());break;case Wn.parser.yy.LINETYPE.OPT_START:vr(u,t,cr.boxMargin,cr.boxMargin+cr.boxTextMargin,(function(t){return ur.newLoop(t)}));break;case Wn.parser.yy.LINETYPE.OPT_END:e=ur.endLoop(),$n.drawLoop(n,e,"opt",cr),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos()),ur.models.addLoop(e);break;case Wn.parser.yy.LINETYPE.ALT_START:vr(u,t,cr.boxMargin,cr.boxMargin+cr.boxTextMargin,(function(t){return ur.newLoop(t)}));break;case Wn.parser.yy.LINETYPE.ALT_ELSE:vr(u,t,cr.boxMargin+cr.boxTextMargin,cr.boxMargin,(function(t){return ur.addSectionToLoop(t)}));break;case Wn.parser.yy.LINETYPE.ALT_END:e=ur.endLoop(),$n.drawLoop(n,e,"alt",cr),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos()),ur.models.addLoop(e);break;case Wn.parser.yy.LINETYPE.PAR_START:vr(u,t,cr.boxMargin,cr.boxMargin+cr.boxTextMargin,(function(t){return ur.newLoop(t)}));break;case Wn.parser.yy.LINETYPE.PAR_AND:vr(u,t,cr.boxMargin+cr.boxTextMargin,cr.boxMargin,(function(t){return ur.addSectionToLoop(t)}));break;case Wn.parser.yy.LINETYPE.PAR_END:e=ur.endLoop(),$n.drawLoop(n,e,"par",cr),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos()),ur.models.addLoop(e);break;default:try{(a=t.msgModel).starty=ur.getVerticalPos(),a.sequenceIndex=l,function(t,e){ur.bumpVerticalPos(10);var n=e.startx,r=e.stopx,i=e.starty,a=e.message,o=e.type,s=e.sequenceIndex,c=x.splitBreaks(a).length,u=H.calculateTextDimensions(a,lr(cr)),l=u.height/c;e.height+=l,ur.bumpVerticalPos(l);var h=$n.getTextObj();h.x=n,h.y=i+10,h.width=r-n,h.class="messageText",h.dy="1em",h.text=a,h.fontFamily=cr.messageFontFamily,h.fontSize=cr.messageFontSize,h.fontWeight=cr.messageFontWeight,h.anchor=cr.messageAlign,h.valign=cr.messageAlign,h.textMargin=cr.wrapPadding,h.tspan=!1,In(t,h);var f,d,p=u.height-10,g=u.width;if(n===r){d=ur.getVerticalPos()+p,cr.rightAngles?f=t.append("path").attr("d","M ".concat(n,",").concat(d," H ").concat(n+Math.max(cr.width/2,g/2)," V ").concat(d+25," H ").concat(n)):(p+=cr.boxMargin,d=ur.getVerticalPos()+p,f=t.append("path").attr("d","M "+n+","+d+" C "+(n+60)+","+(d-10)+" "+(n+60)+","+(d+30)+" "+n+","+(d+20))),p+=30;var y=Math.max(g/2,cr.width/2);ur.insert(n-y,ur.getVerticalPos()-10+p,r+y,ur.getVerticalPos()+30+p)}else p+=cr.boxMargin,d=ur.getVerticalPos()+p,(f=t.append("line")).attr("x1",n),f.attr("y1",d),f.attr("x2",r),f.attr("y2",d),ur.insert(n,d-10,r,d);o===Wn.parser.yy.LINETYPE.DOTTED||o===Wn.parser.yy.LINETYPE.DOTTED_CROSS||o===Wn.parser.yy.LINETYPE.DOTTED_OPEN?(f.style("stroke-dasharray","3, 3"),f.attr("class","messageLine1")):f.attr("class","messageLine0");var v="";cr.arrowMarkerAbsolute&&(v=(v=(v=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),f.attr("stroke-width",2),f.attr("stroke","none"),f.style("fill","none"),o!==Wn.parser.yy.LINETYPE.SOLID&&o!==Wn.parser.yy.LINETYPE.DOTTED||f.attr("marker-end","url("+v+"#arrowhead)"),o!==Wn.parser.yy.LINETYPE.SOLID_CROSS&&o!==Wn.parser.yy.LINETYPE.DOTTED_CROSS||f.attr("marker-end","url("+v+"#crosshead)"),(sr.showSequenceNumbers()||cr.showSequenceNumbers)&&(f.attr("marker-start","url("+v+"#sequencenumber)"),t.append("text").attr("x",n).attr("y",d+4).attr("font-family","sans-serif").attr("font-size","12px").attr("text-anchor","middle").attr("textLength","16px").attr("class","sequenceNumber").text(s)),ur.bumpVerticalPos(p),e.height+=p,e.stopy=e.starty+e.height,ur.insert(e.fromBounds,e.starty,e.toBounds,e.stopy)}(n,a),ur.models.addMessage(a)}catch(t){c.error("error while drawing message",t)}}[Wn.parser.yy.LINETYPE.SOLID_OPEN,Wn.parser.yy.LINETYPE.DOTTED_OPEN,Wn.parser.yy.LINETYPE.SOLID,Wn.parser.yy.LINETYPE.DOTTED,Wn.parser.yy.LINETYPE.SOLID_CROSS,Wn.parser.yy.LINETYPE.DOTTED_CROSS].includes(t.type)&&l++})),cr.mirrorActors&&(ur.bumpVerticalPos(2*cr.boxMargin),dr(n,r,i,ur.getVerticalPos()));var h=ur.getBounds().bounds;c.debug("For line height fix Querying: #"+e+" .actor-line"),Object(d.selectAll)("#"+e+" .actor-line").attr("y2",h.stopy);var f=h.stopy-h.starty+2*cr.diagramMarginY;cr.mirrorActors&&(f=f-cr.boxMargin+cr.bottomMarginAdj);var p=h.stopx-h.startx+2*cr.diagramMarginX;o&&n.append("text").text(o).attr("x",(h.stopx-h.startx)/2-2*cr.diagramMarginX).attr("y",-25),W(n,f,p,cr.useMaxWidth);var g=o?40:0;n.attr("viewBox",h.startx-cr.diagramMarginX+" -"+(cr.diagramMarginY+g)+" "+p+" "+(f+g)),c.debug("models:",ur.models)}},kr=n(27),wr=n.n(kr);function Er(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e<t.length;e++)n[e]=t[e];return n}}(t)||function(t){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t))return Array.from(t)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance")}()}var Tr,Cr,Ar="",Sr="",Mr="",Or=[],Dr="",Nr=[],Br=[],Lr="",Fr=["active","done","crit","milestone"],Pr=[],Ir=!1,jr=0,Rr=function(t,e,n){return t.isoWeekday()>=6&&n.indexOf("weekends")>=0||(n.indexOf(t.format("dddd").toLowerCase())>=0||n.indexOf(t.format(e.trim()))>=0)},Yr=function(t,e,n){if(n.length&&!t.manualEndTime){var r=o()(t.startTime,e,!0);r.add(1,"d");var i=o()(t.endTime,e,!0),a=zr(r,i,e,n);t.endTime=i.toDate(),t.renderEndTime=a}},zr=function(t,e,n,r){for(var i=!1,a=null;t<=e;)i||(a=e.toDate()),(i=Rr(t,n,r))&&e.add(1,"d"),t.add(1,"d");return a},Ur=function(t,e,n){n=n.trim();var r=/^after\s+([\d\w- ]+)/.exec(n.trim());if(null!==r){var i=null;if(r[1].split(" ").forEach((function(t){var e=Xr(t);void 0!==e&&(i?e.endTime>i.endTime&&(i=e):i=e)})),i)return i.endTime;var a=new Date;return a.setHours(0,0,0,0),a}var s=o()(n,e.trim(),!0);return s.isValid()?s.toDate():(c.debug("Invalid date:"+n),c.debug("With date format:"+e.trim()),new Date)},$r=function(t,e){if(null!==t)switch(t[2]){case"s":e.add(t[1],"seconds");break;case"m":e.add(t[1],"minutes");break;case"h":e.add(t[1],"hours");break;case"d":e.add(t[1],"days");break;case"w":e.add(t[1],"weeks")}return e.toDate()},Wr=function(t,e,n,r){r=r||!1,n=n.trim();var i=o()(n,e.trim(),!0);return i.isValid()?(r&&i.add(1,"d"),i.toDate()):$r(/^([\d]+)([wdhms])/.exec(n.trim()),o()(t))},Hr=0,Vr=function(t){return void 0===t?"task"+(Hr+=1):t},Gr=[],qr={},Xr=function(t){var e=qr[t];return Gr[e]},Zr=function(){for(var t=function(t){var e=Gr[t],n="";switch(Gr[t].raw.startTime.type){case"prevTaskEnd":var r=Xr(e.prevTaskId);e.startTime=r.endTime;break;case"getStartDate":(n=Ur(0,Ar,Gr[t].raw.startTime.startData))&&(Gr[t].startTime=n)}return Gr[t].startTime&&(Gr[t].endTime=Wr(Gr[t].startTime,Ar,Gr[t].raw.endTime.data,Ir),Gr[t].endTime&&(Gr[t].processed=!0,Gr[t].manualEndTime=o()(Gr[t].raw.endTime.data,"YYYY-MM-DD",!0).isValid(),Yr(Gr[t],Ar,Or))),Gr[t].processed},e=!0,n=0;n<Gr.length;n++)t(n),e=e&&Gr[n].processed;return e},Jr=function(t,e){t.split(",").forEach((function(t){var n=Xr(t);void 0!==n&&n.classes.push(e)}))},Kr=function(t,e){Pr.push((function(){var n=document.querySelector('[id="'.concat(t,'"]'));null!==n&&n.addEventListener("click",(function(){e()}))})),Pr.push((function(){var n=document.querySelector('[id="'.concat(t,'-text"]'));null!==n&&n.addEventListener("click",(function(){e()}))}))},Qr={parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},getConfig:function(){return _t().gantt},clear:function(){Nr=[],Br=[],Lr="",Pr=[],Dr="",Hr=0,Tr=void 0,Cr=void 0,Gr=[],Ar="",Sr="",Mr="",Or=[],Ir=!1,jr=0},setDateFormat:function(t){Ar=t},getDateFormat:function(){return Ar},enableInclusiveEndDates:function(){Ir=!0},endDatesAreInclusive:function(){return Ir},setAxisFormat:function(t){Sr=t},getAxisFormat:function(){return Sr},setTodayMarker:function(t){Mr=t},getTodayMarker:function(){return Mr},setTitle:function(t){Dr=t},getTitle:function(){return Dr},addSection:function(t){Lr=t,Nr.push(t)},getSections:function(){return Nr},getTasks:function(){for(var t=Zr(),e=0;!t&&e<10;)t=Zr(),e++;return Br=Gr},addTask:function(t,e){var n={section:Lr,type:Lr,processed:!1,manualEndTime:!1,renderEndTime:null,raw:{data:e},task:t,classes:[]},r=function(t,e){var n=(":"===e.substr(0,1)?e.substr(1,e.length):e).split(","),r={};ti(n,r,Fr);for(var i=0;i<n.length;i++)n[i]=n[i].trim();switch(n.length){case 1:r.id=Vr(),r.startTime={type:"prevTaskEnd",id:t},r.endTime={data:n[0]};break;case 2:r.id=Vr(),r.startTime={type:"getStartDate",startData:n[0]},r.endTime={data:n[1]};break;case 3:r.id=Vr(n[0]),r.startTime={type:"getStartDate",startData:n[1]},r.endTime={data:n[2]}}return r}(Cr,e);n.raw.startTime=r.startTime,n.raw.endTime=r.endTime,n.id=r.id,n.prevTaskId=Cr,n.active=r.active,n.done=r.done,n.crit=r.crit,n.milestone=r.milestone,n.order=jr,jr++;var i=Gr.push(n);Cr=n.id,qr[n.id]=i-1},findTaskById:Xr,addTaskOrg:function(t,e){var n={section:Lr,type:Lr,description:t,task:t,classes:[]},r=function(t,e){var n=(":"===e.substr(0,1)?e.substr(1,e.length):e).split(","),r={};ti(n,r,Fr);for(var i=0;i<n.length;i++)n[i]=n[i].trim();var a="";switch(n.length){case 1:r.id=Vr(),r.startTime=t.endTime,a=n[0];break;case 2:r.id=Vr(),r.startTime=Ur(0,Ar,n[0]),a=n[1];break;case 3:r.id=Vr(n[0]),r.startTime=Ur(0,Ar,n[1]),a=n[2]}return a&&(r.endTime=Wr(r.startTime,Ar,a,Ir),r.manualEndTime=o()(a,"YYYY-MM-DD",!0).isValid(),Yr(r,Ar,Or)),r}(Tr,e);n.startTime=r.startTime,n.endTime=r.endTime,n.id=r.id,n.active=r.active,n.done=r.done,n.crit=r.crit,n.milestone=r.milestone,Tr=n,Br.push(n)},setExcludes:function(t){Or=t.toLowerCase().split(/[\s,]+/)},getExcludes:function(){return Or},setClickEvent:function(t,e,n){t.split(",").forEach((function(t){!function(t,e,n){if("loose"===_t().securityLevel&&void 0!==e){var r=[];if("string"==typeof n){r=n.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);for(var i=0;i<r.length;i++){var a=r[i].trim();'"'===a.charAt(0)&&'"'===a.charAt(a.length-1)&&(a=a.substr(1,a.length-2)),r[i]=a}}0===r.length&&r.push(t),void 0!==Xr(t)&&Kr(t,(function(){H.runFunc.apply(H,[e].concat(Er(r)))}))}}(t,e,n)})),Jr(t,"clickable")},setLink:function(t,e){var n=e;"loose"!==_t().securityLevel&&(n=Object(g.sanitizeUrl)(e)),t.split(",").forEach((function(t){void 0!==Xr(t)&&Kr(t,(function(){window.open(n,"_self")}))})),Jr(t,"clickable")},bindFunctions:function(t){Pr.forEach((function(e){e(t)}))},durationToDate:$r};function ti(t,e,n){for(var r=!0;r;)r=!1,n.forEach((function(n){var i=new RegExp("^\\s*"+n+"\\s*$");t[0].match(i)&&(e[n]=!0,t.shift(1),r=!0)}))}kr.parser.yy=Qr;var ei,ni={titleTopMargin:25,barHeight:20,barGap:4,topPadding:50,rightPadding:75,leftPadding:75,gridLineStartPadding:35,fontSize:11,fontFamily:'"Open-Sans", "sans-serif"'},ri=function(t){Object.keys(t).forEach((function(e){ni[e]=t[e]}))},ii=function(t,e){kr.parser.yy.clear(),kr.parser.parse(t);var n=document.getElementById(e);void 0===(ei=n.parentElement.offsetWidth)&&(ei=1200),void 0!==ni.useWidth&&(ei=ni.useWidth);var r=kr.parser.yy.getTasks(),i=r.length*(ni.barHeight+ni.barGap)+2*ni.topPadding;n.setAttribute("viewBox","0 0 "+ei+" "+i);for(var a=Object(d.select)('[id="'.concat(e,'"]')),o=Object(d.scaleTime)().domain([Object(d.min)(r,(function(t){return t.startTime})),Object(d.max)(r,(function(t){return t.endTime}))]).rangeRound([0,ei-ni.leftPadding-ni.rightPadding]),s=[],c=0;c<r.length;c++)s.push(r[c].type);var u=s;function l(t){for(var e=t.length,n={};e;)n[t[--e]]=(n[t[e]]||0)+1;return n}s=function(t){for(var e={},n=[],r=0,i=t.length;r<i;++r)e.hasOwnProperty(t[r])||(e[t[r]]=!0,n.push(t[r]));return n}(s),r.sort((function(t,e){var n=t.startTime,r=e.startTime,i=0;return n>r?i=1:n<r&&(i=-1),i})),function(t,e,n){var r=ni.barHeight,i=r+ni.barGap,c=ni.topPadding,h=ni.leftPadding;Object(d.scaleLinear)().domain([0,s.length]).range(["#00B9FA","#F95002"]).interpolate(d.interpolateHcl);(function(t,e,n,r){var i=Object(d.axisBottom)(o).tickSize(-r+e+ni.gridLineStartPadding).tickFormat(Object(d.timeFormat)(kr.parser.yy.getAxisFormat()||ni.axisFormat||"%Y-%m-%d"));a.append("g").attr("class","grid").attr("transform","translate("+t+", "+(r-50)+")").call(i).selectAll("text").style("text-anchor","middle").attr("fill","#000").attr("stroke","none").attr("font-size",10).attr("dy","1em")})(h,c,0,n),function(t,e,n,r,i,c,u){a.append("g").selectAll("rect").data(t).enter().append("rect").attr("x",0).attr("y",(function(t,r){return t.order*e+n-2})).attr("width",(function(){return u-ni.rightPadding/2})).attr("height",e).attr("class",(function(t){for(var e=0;e<s.length;e++)if(t.type===s[e])return"section section"+e%ni.numberSectionStyles;return"section section0"}));var l=a.append("g").selectAll("rect").data(t).enter();l.append("rect").attr("id",(function(t){return t.id})).attr("rx",3).attr("ry",3).attr("x",(function(t){return t.milestone?o(t.startTime)+r+.5*(o(t.endTime)-o(t.startTime))-.5*i:o(t.startTime)+r})).attr("y",(function(t,r){return t.order*e+n})).attr("width",(function(t){return t.milestone?i:o(t.renderEndTime||t.endTime)-o(t.startTime)})).attr("height",i).attr("transform-origin",(function(t,a){return(o(t.startTime)+r+.5*(o(t.endTime)-o(t.startTime))).toString()+"px "+(a*e+n+.5*i).toString()+"px"})).attr("class",(function(t){var e="";t.classes.length>0&&(e=t.classes.join(" "));for(var n=0,r=0;r<s.length;r++)t.type===s[r]&&(n=r%ni.numberSectionStyles);var i="";return t.active?t.crit?i+=" activeCrit":i=" active":t.done?i=t.crit?" doneCrit":" done":t.crit&&(i+=" crit"),0===i.length&&(i=" task"),t.milestone&&(i=" milestone "+i),i+=n,"task"+(i+=" "+e)})),l.append("text").attr("id",(function(t){return t.id+"-text"})).text((function(t){return t.task})).attr("font-size",ni.fontSize).attr("x",(function(t){var e=o(t.startTime),n=o(t.renderEndTime||t.endTime);t.milestone&&(e+=.5*(o(t.endTime)-o(t.startTime))-.5*i),t.milestone&&(n=e+i);var a=this.getBBox().width;return a>n-e?n+a+1.5*ni.leftPadding>u?e+r-5:n+r+5:(n-e)/2+e+r})).attr("y",(function(t,r){return t.order*e+ni.barHeight/2+(ni.fontSize/2-2)+n})).attr("text-height",i).attr("class",(function(t){var e=o(t.startTime),n=o(t.endTime);t.milestone&&(n=e+i);var r=this.getBBox().width,a="";t.classes.length>0&&(a=t.classes.join(" "));for(var c=0,l=0;l<s.length;l++)t.type===s[l]&&(c=l%ni.numberSectionStyles);var h="";return t.active&&(h=t.crit?"activeCritText"+c:"activeText"+c),t.done?h=t.crit?h+" doneCritText"+c:h+" doneText"+c:t.crit&&(h=h+" critText"+c),t.milestone&&(h+=" milestoneText"),r>n-e?n+r+1.5*ni.leftPadding>u?a+" taskTextOutsideLeft taskTextOutside"+c+" "+h:a+" taskTextOutsideRight taskTextOutside"+c+" "+h+" width-"+r:a+" taskText taskText"+c+" "+h+" width-"+r}))}(t,i,c,h,r,0,e),function(t,e){for(var n=[],r=0,i=0;i<s.length;i++)n[i]=[s[i],(o=s[i],c=u,l(c)[o]||0)];var o,c;a.append("g").selectAll("text").data(n).enter().append((function(t){var e=t[0].split(x.lineBreakRegex),n=-(e.length-1)/2,r=document.createElementNS("http://www.w3.org/2000/svg","text");r.setAttribute("dy",n+"em");for(var i=0;i<e.length;i++){var a=document.createElementNS("http://www.w3.org/2000/svg","tspan");a.setAttribute("alignment-baseline","central"),a.setAttribute("x","10"),i>0&&a.setAttribute("dy","1em"),a.textContent=e[i],r.appendChild(a)}return r})).attr("x",10).attr("y",(function(i,a){if(!(a>0))return i[1]*t/2+e;for(var o=0;o<a;o++)return r+=n[a-1][1],i[1]*t/2+r*t+e})).attr("class",(function(t){for(var e=0;e<s.length;e++)if(t[0]===s[e])return"sectionTitle sectionTitle"+e%ni.numberSectionStyles;return"sectionTitle"}))}(i,c),function(t,e,n,r){var i=Qr.getTodayMarker();if("off"===i)return;var s=a.append("g").attr("class","today"),c=new Date,u=s.append("line");u.attr("x1",o(c)+t).attr("x2",o(c)+t).attr("y1",ni.titleTopMargin).attr("y2",r-ni.titleTopMargin).attr("class","today"),""!==i&&u.attr("style",i.replace(/,/g,";"))}(h,0,0,n)}(r,ei,i),W(a,i,ei,ni.useMaxWidth),a.append("text").text(kr.parser.yy.getTitle()).attr("x",ei/2).attr("y",ni.titleTopMargin).attr("class","titleText")},ai=n(13),oi=n.n(ai);ai.parser.yy=cn;var si={},ci={dividerMargin:10,padding:5,textHeight:10},ui=function(t){for(var e=Object.keys(si),n=0;n<e.length;n++)if(si[e[n]].label===t)return e[n]},li=function(t){Object.keys(t).forEach((function(e){ci[e]=t[e]}))},hi=function(t,e){si={},ai.parser.yy.clear(),ai.parser.parse(t),c.info("Rendering diagram "+t);var n,r=Object(d.select)("[id='".concat(e,"']"));r.attr("xmlns:xlink","http://www.w3.org/1999/xlink"),(n=r).append("defs").append("marker").attr("id","extensionStart").attr("class","extension").attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 1,7 L18,13 V 1 Z"),n.append("defs").append("marker").attr("id","extensionEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 1,1 V 13 L18,7 Z"),n.append("defs").append("marker").attr("id","compositionStart").attr("class","extension").attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),n.append("defs").append("marker").attr("id","compositionEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),n.append("defs").append("marker").attr("id","aggregationStart").attr("class","extension").attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),n.append("defs").append("marker").attr("id","aggregationEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),n.append("defs").append("marker").attr("id","dependencyStart").attr("class","extension").attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 5,7 L9,13 L1,7 L9,1 Z"),n.append("defs").append("marker").attr("id","dependencyEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z");var i=new G.a.Graph({multigraph:!0});i.setGraph({isMultiGraph:!0}),i.setDefaultEdgeLabel((function(){return{}}));for(var a=cn.getClasses(),o=Object.keys(a),s=0;s<o.length;s++){var u=a[o[s]],l=vn(r,u,ci);si[l.id]=l,i.setNode(l.id,l),c.info("Org height: "+l.height)}cn.getRelations().forEach((function(t){c.info("tjoho"+ui(t.id1)+ui(t.id2)+JSON.stringify(t)),i.setEdge(ui(t.id1),ui(t.id2),{relation:t},t.title||"DEFAULT")})),ke.a.layout(i),i.nodes().forEach((function(t){void 0!==t&&void 0!==i.node(t)&&(c.debug("Node "+t+": "+JSON.stringify(i.node(t))),Object(d.select)("#"+en(t)).attr("transform","translate("+(i.node(t).x-i.node(t).width/2)+","+(i.node(t).y-i.node(t).height/2)+" )"))})),i.edges().forEach((function(t){void 0!==t&&void 0!==i.edge(t)&&(c.debug("Edge "+t.v+" -> "+t.w+": "+JSON.stringify(i.edge(t))),mn(r,i.edge(t),i.edge(t).relation,ci))}));var h=r.node().getBBox(),f=h.width+40,p=h.height+40;W(r,p,f,ci.useMaxWidth);var g="".concat(h.x-20," ").concat(h.y-20," ").concat(f," ").concat(p);c.debug("viewBox ".concat(g)),r.attr("viewBox",g)};ai.parser.yy=cn;var fi={dividerMargin:10,padding:5,textHeight:10},di=function(t){Object.keys(t).forEach((function(e){fi[e]=t[e]}))},pi=function(t,e){c.info("Drawing class"),cn.clear(),ai.parser.parse(t);var n=_t().flowchart;c.info("config:",n);var r=n.nodeSpacing||50,i=n.rankSpacing||50,a=new G.a.Graph({multigraph:!0,compound:!0}).setGraph({rankdir:"TD",nodesep:r,ranksep:i,marginx:8,marginy:8}).setDefaultEdgeLabel((function(){return{}})),o=cn.getClasses(),s=cn.getRelations();c.info(s),function(t,e){var n=Object.keys(t);c.info("keys:",n),c.info(t),n.forEach((function(n){var r=t[n],i="";r.cssClasses.length>0&&(i=i+" "+r.cssClasses.join(" "));var a={labelStyle:""},o=void 0!==r.text?r.text:r.id,s="";switch(r.type){case"class":s="class_box";break;default:s="class_box"}e.setNode(r.id,{labelStyle:a.labelStyle,shape:s,labelText:o,classData:r,rx:0,ry:0,class:i,style:a.style,id:r.id,domId:r.domId,haveCallback:r.haveCallback,link:r.link,width:"group"===r.type?500:void 0,type:r.type,padding:_t().flowchart.padding}),c.info("setNode",{labelStyle:a.labelStyle,shape:s,labelText:o,rx:0,ry:0,class:i,style:a.style,id:r.id,width:"group"===r.type?500:void 0,type:r.type,padding:_t().flowchart.padding})}))}(o,a),function(t,e){var n=0;t.forEach((function(r){n++;var i={classes:"relation"};i.pattern=1==r.relation.lineType?"dashed":"solid",i.id="id"+n,"arrow_open"===r.type?i.arrowhead="none":i.arrowhead="normal",c.info(i,r),i.startLabelRight="none"===r.relationTitle1?"":r.relationTitle1,i.endLabelLeft="none"===r.relationTitle2?"":r.relationTitle2,i.arrowTypeStart=gi(r.relation.type1),i.arrowTypeEnd=gi(r.relation.type2);var a="",o="";if(void 0!==r.style){var s=B(r.style);a=s.style,o=s.labelStyle}else a="fill:none";i.style=a,i.labelStyle=o,void 0!==r.interpolate?i.curve=D(r.interpolate,d.curveLinear):void 0!==t.defaultInterpolate?i.curve=D(t.defaultInterpolate,d.curveLinear):i.curve=D(fi.curve,d.curveLinear),r.text=r.title,void 0===r.text?void 0!==r.style&&(i.arrowheadStyle="fill: #333"):(i.arrowheadStyle="fill: #333",i.labelpos="c",_t().flowchart.htmlLabels,i.labelType="text",i.label=r.text.replace(x.lineBreakRegex,"\n"),void 0===r.style&&(i.style=i.style||"stroke: #333; stroke-width: 1.5px;fill:none"),i.labelStyle=i.labelStyle.replace("color:","fill:")),e.setEdge(r.id1,r.id2,i,n)}))}(s,a);var u=Object(d.select)('[id="'.concat(e,'"]'));u.attr("xmlns:xlink","http://www.w3.org/1999/xlink");var l=Object(d.select)("#"+e+" g");On(l,a,["aggregation","extension","composition","dependency"],"classDiagram",e);var h=u.node().getBBox(),f=h.width+16,p=h.height+16;if(c.debug("new ViewBox 0 0 ".concat(f," ").concat(p),"translate(".concat(8-a._label.marginx,", ").concat(8-a._label.marginy,")")),W(u,p,f,n.useMaxWidth),u.attr("viewBox","0 0 ".concat(f," ").concat(p)),u.select("g").attr("transform","translate(".concat(8-a._label.marginx,", ").concat(8-h.y,")")),!n.htmlLabels)for(var g=document.querySelectorAll('[id="'+e+'"] .edgeLabel .label'),y=0;y<g.length;y++){var v=g[y],m=v.getBBox(),b=document.createElementNS("http://www.w3.org/2000/svg","rect");b.setAttribute("rx",0),b.setAttribute("ry",0),b.setAttribute("width",m.width),b.setAttribute("height",m.height),b.setAttribute("style","fill:#e8e8e8;"),v.insertBefore(b,v.firstChild)}};function gi(t){var e;switch(t){case 0:e="aggregation";break;case 1:e="extension";break;case 2:e="composition";break;case 3:e="dependency";break;default:e="none"}return e}function yi(t){return(yi="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var vi,mi=function(t){return JSON.parse(JSON.stringify(t))},bi=[],xi={root:{relations:[],states:{},documents:{}}},_i=xi.root,ki=0,wi=function(t,e,n,r,i){void 0===_i.states[t]?_i.states[t]={id:t,descriptions:[],type:e,doc:n,note:i}:(_i.states[t].doc||(_i.states[t].doc=n),_i.states[t].type||(_i.states[t].type=e)),r&&(c.info("Adding state ",t,r),"string"==typeof r&&Ci(t,r.trim()),"object"===yi(r)&&r.forEach((function(e){return Ci(t,e.trim())}))),i&&(_i.states[t].note=i)},Ei=function(){_i=(xi={root:{relations:[],states:{},documents:{}}}).root,_i=xi.root,ki=0,0,Si=[]},Ti=function(t,e,n){var r=t,i=e,a="default",o="default";"[*]"===t&&(r="start"+ ++ki,a="start"),"[*]"===e&&(i="end"+ki,o="end"),wi(r,a),wi(i,o),_i.relations.push({id1:r,id2:i,title:n})},Ci=function(t,e){var n=_i.states[t],r=e;":"===r[0]&&(r=r.substr(1).trim()),n.descriptions.push(r)},Ai=0,Si=[],Mi={parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},getConfig:function(){return _t().state},addState:wi,clear:Ei,getState:function(t){return _i.states[t]},getStates:function(){return _i.states},getRelations:function(){return _i.relations},getClasses:function(){return Si},getDirection:function(){return"TB"},addRelation:Ti,getDividerId:function(){return"divider-id-"+ ++Ai},cleanupLabel:function(t){return":"===t.substring(0,1)?t.substr(2).trim():t.trim()},lineType:{LINE:0,DOTTED_LINE:1},relationType:{AGGREGATION:0,EXTENSION:1,COMPOSITION:2,DEPENDENCY:3},logDocuments:function(){c.info("Documents = ",xi)},getRootDoc:function(){return bi},setRootDoc:function(t){c.info("Setting root doc",t),bi=t},getRootDocV2:function(){return function t(e,n,r){if("relation"===n.stmt)t(e,n.state1,!0),t(e,n.state2,!1);else if("state"===n.stmt&&"[*]"===n.id&&(n.id=r?e.id+"_start":e.id+"_end",n.start=r),n.doc){var i=[],a=0,o=[];for(a=0;a<n.doc.length;a++)if("divider"===n.doc[a].type){var s=mi(n.doc[a]);s.doc=mi(o),i.push(s),o=[]}else o.push(n.doc[a]);if(i.length>0&&o.length>0){var c={stmt:"state",id:F(),type:"divider",doc:mi(o)};i.push(mi(c)),n.doc=i}n.doc.forEach((function(e){return t(n,e,!0)}))}}({id:"root"},{id:"root",doc:bi},!0),{id:"root",doc:bi}},extract:function(t){var e;e=t.doc?t.doc:t,c.info(e),Ei(),c.info("Extract",e),e.forEach((function(t){"state"===t.stmt&&wi(t.id,t.type,t.doc,t.description,t.note),"relation"===t.stmt&&Ti(t.state1.id,t.state2.id,t.description)}))},trimColon:function(t){return t&&":"===t[0]?t.substr(1).trim():t.trim()}},Oi=n(22),Di=n.n(Oi),Ni={},Bi=function(t,e){Ni[t]=e},Li=function(t,e){var n=t.append("text").attr("x",2*_t().state.padding).attr("y",_t().state.textHeight+1.3*_t().state.padding).attr("font-size",_t().state.fontSize).attr("class","state-title").text(e.descriptions[0]).node().getBBox(),r=n.height,i=t.append("text").attr("x",_t().state.padding).attr("y",r+.4*_t().state.padding+_t().state.dividerMargin+_t().state.textHeight).attr("class","state-description"),a=!0,o=!0;e.descriptions.forEach((function(t){a||(!function(t,e,n){var r=t.append("tspan").attr("x",2*_t().state.padding).text(e);n||r.attr("dy",_t().state.textHeight)}(i,t,o),o=!1),a=!1}));var s=t.append("line").attr("x1",_t().state.padding).attr("y1",_t().state.padding+r+_t().state.dividerMargin/2).attr("y2",_t().state.padding+r+_t().state.dividerMargin/2).attr("class","descr-divider"),c=i.node().getBBox(),u=Math.max(c.width,n.width);return s.attr("x2",u+3*_t().state.padding),t.insert("rect",":first-child").attr("x",_t().state.padding).attr("y",_t().state.padding).attr("width",u+2*_t().state.padding).attr("height",c.height+r+2*_t().state.padding).attr("rx",_t().state.radius),t},Fi=function(t,e,n){var r,i=_t().state.padding,a=2*_t().state.padding,o=t.node().getBBox(),s=o.width,c=o.x,u=t.append("text").attr("x",0).attr("y",_t().state.titleShift).attr("font-size",_t().state.fontSize).attr("class","state-title").text(e.id),l=u.node().getBBox().width+a,h=Math.max(l,s);h===s&&(h+=a);var f=t.node().getBBox();e.doc,r=c-i,l>s&&(r=(s-h)/2+i),Math.abs(c-f.x)<i&&l>s&&(r=c-(l-s)/2);var d=1-_t().state.textHeight;return t.insert("rect",":first-child").attr("x",r).attr("y",d).attr("class",n?"alt-composit":"composit").attr("width",h).attr("height",f.height+_t().state.textHeight+_t().state.titleShift+1).attr("rx","0"),u.attr("x",r+i),l<=s&&u.attr("x",c+(h-a)/2-l/2+i),t.insert("rect",":first-child").attr("x",r).attr("y",_t().state.titleShift-_t().state.textHeight-_t().state.padding).attr("width",h).attr("height",3*_t().state.textHeight).attr("rx",_t().state.radius),t.insert("rect",":first-child").attr("x",r).attr("y",_t().state.titleShift-_t().state.textHeight-_t().state.padding).attr("width",h).attr("height",f.height+3+2*_t().state.textHeight).attr("rx",_t().state.radius),t},Pi=function(t,e){e.attr("class","state-note");var n=e.append("rect").attr("x",0).attr("y",_t().state.padding),r=function(t,e,n,r){var i=0,a=r.append("text");a.style("text-anchor","start"),a.attr("class","noteText");var o=t.replace(/\r\n/g,"<br/>"),s=(o=o.replace(/\n/g,"<br/>")).split(x.lineBreakRegex),c=1.25*_t().state.noteMargin,u=!0,l=!1,h=void 0;try{for(var f,d=s[Symbol.iterator]();!(u=(f=d.next()).done);u=!0){var p=f.value.trim();if(p.length>0){var g=a.append("tspan");if(g.text(p),0===c)c+=g.node().getBBox().height;i+=c,g.attr("x",e+_t().state.noteMargin),g.attr("y",n+i+1.25*_t().state.noteMargin)}}}catch(t){l=!0,h=t}finally{try{u||null==d.return||d.return()}finally{if(l)throw h}}return{textWidth:a.node().getBBox().width,textHeight:i}}(t,0,0,e.append("g")),i=r.textWidth,a=r.textHeight;return n.attr("height",a+2*_t().state.noteMargin),n.attr("width",i+2*_t().state.noteMargin),n},Ii=function(t,e){var n=e.id,r={id:n,label:e.id,width:0,height:0},i=t.append("g").attr("id",n).attr("class","stateGroup");"start"===e.type&&function(t){t.append("circle").attr("class","start-state").attr("r",_t().state.sizeUnit).attr("cx",_t().state.padding+_t().state.sizeUnit).attr("cy",_t().state.padding+_t().state.sizeUnit)}(i),"end"===e.type&&function(t){t.append("circle").attr("class","end-state-outer").attr("r",_t().state.sizeUnit+_t().state.miniPadding).attr("cx",_t().state.padding+_t().state.sizeUnit+_t().state.miniPadding).attr("cy",_t().state.padding+_t().state.sizeUnit+_t().state.miniPadding),t.append("circle").attr("class","end-state-inner").attr("r",_t().state.sizeUnit).attr("cx",_t().state.padding+_t().state.sizeUnit+2).attr("cy",_t().state.padding+_t().state.sizeUnit+2)}(i),"fork"!==e.type&&"join"!==e.type||function(t,e){var n=_t().state.forkWidth,r=_t().state.forkHeight;if(e.parentId){var i=n;n=r,r=i}t.append("rect").style("stroke","black").style("fill","black").attr("width",n).attr("height",r).attr("x",_t().state.padding).attr("y",_t().state.padding)}(i,e),"note"===e.type&&Pi(e.note.text,i),"divider"===e.type&&function(t){t.append("line").style("stroke","grey").style("stroke-dasharray","3").attr("x1",_t().state.textHeight).attr("class","divider").attr("x2",2*_t().state.textHeight).attr("y1",0).attr("y2",0)}(i),"default"===e.type&&0===e.descriptions.length&&function(t,e){var n=t.append("text").attr("x",2*_t().state.padding).attr("y",_t().state.textHeight+2*_t().state.padding).attr("font-size",_t().state.fontSize).attr("class","state-title").text(e.id),r=n.node().getBBox();t.insert("rect",":first-child").attr("x",_t().state.padding).attr("y",_t().state.padding).attr("width",r.width+2*_t().state.padding).attr("height",r.height+2*_t().state.padding).attr("rx",_t().state.radius)}(i,e),"default"===e.type&&e.descriptions.length>0&&Li(i,e);var a=i.node().getBBox();return r.width=a.width+2*_t().state.padding,r.height=a.height+2*_t().state.padding,Bi(n,r),r},ji=0;Oi.parser.yy=Mi;var Ri={},Yi=function t(e,n,r,i){var a,o=new G.a.Graph({compound:!0,multigraph:!0}),s=!0;for(a=0;a<e.length;a++)if("relation"===e[a].stmt){s=!1;break}r?o.setGraph({rankdir:"LR",multigraph:!0,compound:!0,ranker:"tight-tree",ranksep:s?1:vi.edgeLengthFactor,nodeSep:s?1:50,isMultiGraph:!0}):o.setGraph({rankdir:"TB",multigraph:!0,compound:!0,ranksep:s?1:vi.edgeLengthFactor,nodeSep:s?1:50,ranker:"tight-tree",isMultiGraph:!0}),o.setDefaultEdgeLabel((function(){return{}})),Mi.extract(e);for(var u=Mi.getStates(),l=Mi.getRelations(),h=Object.keys(u),f=0;f<h.length;f++){var p=u[h[f]];r&&(p.parentId=r);var g=void 0;if(p.doc){var y=n.append("g").attr("id",p.id).attr("class","stateGroup");g=t(p.doc,y,p.id,!i);var v=(y=Fi(y,p,i)).node().getBBox();g.width=v.width,g.height=v.height+vi.padding/2,Ri[p.id]={y:vi.compositTitleSize}}else g=Ii(n,p);if(p.note){var m={descriptions:[],id:p.id+"-note",note:p.note,type:"note"},b=Ii(n,m);"left of"===p.note.position?(o.setNode(g.id+"-note",b),o.setNode(g.id,g)):(o.setNode(g.id,g),o.setNode(g.id+"-note",b)),o.setParent(g.id,g.id+"-group"),o.setParent(g.id+"-note",g.id+"-group")}else o.setNode(g.id,g)}c.debug("Count=",o.nodeCount(),o);var _=0;l.forEach((function(t){var e;_++,c.debug("Setting edge",t),o.setEdge(t.id1,t.id2,{relation:t,width:(e=t.title,e?e.length*vi.fontSizeFactor:1),height:vi.labelHeight*x.getRows(t.title).length,labelpos:"c"},"id"+_)})),ke.a.layout(o),c.debug("Graph after layout",o.nodes());var k=n.node();o.nodes().forEach((function(t){void 0!==t&&void 0!==o.node(t)?(c.warn("Node "+t+": "+JSON.stringify(o.node(t))),Object(d.select)("#"+k.id+" #"+t).attr("transform","translate("+(o.node(t).x-o.node(t).width/2)+","+(o.node(t).y+(Ri[t]?Ri[t].y:0)-o.node(t).height/2)+" )"),Object(d.select)("#"+k.id+" #"+t).attr("data-x-shift",o.node(t).x-o.node(t).width/2),document.querySelectorAll("#"+k.id+" #"+t+" .divider").forEach((function(t){var e=t.parentElement,n=0,r=0;e&&(e.parentElement&&(n=e.parentElement.getBBox().width),r=parseInt(e.getAttribute("data-x-shift"),10),Number.isNaN(r)&&(r=0)),t.setAttribute("x1",0-r+8),t.setAttribute("x2",n-r-8)}))):c.debug("No Node "+t+": "+JSON.stringify(o.node(t)))}));var w=k.getBBox();o.edges().forEach((function(t){void 0!==t&&void 0!==o.edge(t)&&(c.debug("Edge "+t.v+" -> "+t.w+": "+JSON.stringify(o.edge(t))),function(t,e,n){e.points=e.points.filter((function(t){return!Number.isNaN(t.y)}));var r=e.points,i=Object(d.line)().x((function(t){return t.x})).y((function(t){return t.y})).curve(d.curveBasis),a=t.append("path").attr("d",i(r)).attr("id","edge"+ji).attr("class","transition"),o="";if(_t().state.arrowMarkerAbsolute&&(o=(o=(o=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),a.attr("marker-end","url("+o+"#"+function(t){switch(t){case Mi.relationType.AGGREGATION:return"aggregation";case Mi.relationType.EXTENSION:return"extension";case Mi.relationType.COMPOSITION:return"composition";case Mi.relationType.DEPENDENCY:return"dependency"}}(Mi.relationType.DEPENDENCY)+"End)"),void 0!==n.title){for(var s=t.append("g").attr("class","stateLabel"),u=H.calcLabelPosition(e.points),l=u.x,h=u.y,f=x.getRows(n.title),p=0,g=[],y=0,v=0,m=0;m<=f.length;m++){var b=s.append("text").attr("text-anchor","middle").text(f[m]).attr("x",l).attr("y",h+p),_=b.node().getBBox();if(y=Math.max(y,_.width),v=Math.min(v,_.x),c.info(_.x,l,h+p),0===p){var k=b.node().getBBox();p=k.height,c.info("Title height",p,h)}g.push(b)}var w=p*f.length;if(f.length>1){var E=(f.length-1)*p*.5;g.forEach((function(t,e){return t.attr("y",h+e*p-E)})),w=p*f.length}var T=s.node().getBBox();s.insert("rect",":first-child").attr("class","box").attr("x",l-y/2-_t().state.padding/2).attr("y",h-w/2-_t().state.padding/2-3.5).attr("width",y+_t().state.padding).attr("height",w+_t().state.padding),c.info(T)}ji++}(n,o.edge(t),o.edge(t).relation))})),w=k.getBBox();var E={id:r||"root",label:r||"root",width:0,height:0};return E.width=w.width+2*vi.padding,E.height=w.height+2*vi.padding,c.debug("Doc rendered",E,o),E},zi=function(){},Ui=function(t,e){vi=_t().state,Oi.parser.yy.clear(),Oi.parser.parse(t),c.debug("Rendering diagram "+t);var n=Object(d.select)("[id='".concat(e,"']"));n.append("defs").append("marker").attr("id","dependencyEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 19,7 L9,13 L14,7 L9,1 Z"),new G.a.Graph({multigraph:!0,compound:!0,rankdir:"RL"}).setDefaultEdgeLabel((function(){return{}}));var r=Mi.getRootDoc();Yi(r,n,void 0,!1);var i=vi.padding,a=n.node().getBBox(),o=a.width+2*i,s=a.height+2*i;W(n,s,1.75*o,vi.useMaxWidth),n.attr("viewBox","".concat(a.x-vi.padding," ").concat(a.y-vi.padding," ")+o+" "+s)},$i={},Wi={},Hi=function(t,e,n,r){if("root"!==n.id){var i="rect";!0===n.start&&(i="start"),!1===n.start&&(i="end"),"default"!==n.type&&(i=n.type),Wi[n.id]||(Wi[n.id]={id:n.id,shape:i,description:n.id,classes:"statediagram-state"}),n.description&&(Array.isArray(Wi[n.id].description)?(Wi[n.id].shape="rectWithTitle",Wi[n.id].description.push(n.description)):Wi[n.id].description.length>0?(Wi[n.id].shape="rectWithTitle",Wi[n.id].description===n.id?Wi[n.id].description=[n.description]:Wi[n.id].description=[Wi[n.id].description,n.description]):(Wi[n.id].shape="rect",Wi[n.id].description=n.description)),!Wi[n.id].type&&n.doc&&(c.info("Setting cluser for ",n.id),Wi[n.id].type="group",Wi[n.id].shape="divider"===n.type?"divider":"roundedWithTitle",Wi[n.id].classes=Wi[n.id].classes+" "+(r?"statediagram-cluster statediagram-cluster-alt":"statediagram-cluster"));var a={labelStyle:"",shape:Wi[n.id].shape,labelText:Wi[n.id].description,classes:Wi[n.id].classes,style:"",id:n.id,domId:"state-"+n.id+"-"+Vi,type:Wi[n.id].type,padding:15};if(n.note){var o={labelStyle:"",shape:"note",labelText:n.note.text,classes:"statediagram-note",style:"",id:n.id+"----note",domId:"state-"+n.id+"----note-"+Vi,type:Wi[n.id].type,padding:15},s={labelStyle:"",shape:"noteGroup",labelText:n.note.text,classes:Wi[n.id].classes,style:"",id:n.id+"----parent",domId:"state-"+n.id+"----parent-"+Vi,type:"group",padding:0};Vi++,t.setNode(n.id+"----parent",s),t.setNode(o.id,o),t.setNode(n.id,a),t.setParent(n.id,n.id+"----parent"),t.setParent(o.id,n.id+"----parent");var u=n.id,l=o.id;"left of"===n.note.position&&(u=o.id,l=n.id),t.setEdge(u,l,{arrowhead:"none",arrowType:"",style:"fill:none",labelStyle:"",classes:"transition note-edge",arrowheadStyle:"fill: #333",labelpos:"c",labelType:"text",thickness:"normal"})}else t.setNode(n.id,a)}e&&"root"!==e.id&&(c.info("Setting node ",n.id," to be child of its parent ",e.id),t.setParent(n.id,e.id)),n.doc&&(c.info("Adding nodes children "),Gi(t,n,n.doc,!r))},Vi=0,Gi=function(t,e,n,r){Vi=0,c.trace("items",n),n.forEach((function(n){if("state"===n.stmt||"default"===n.stmt)Hi(t,e,n,r);else if("relation"===n.stmt){Hi(t,e,n.state1,r),Hi(t,e,n.state2,r);var i={id:"edge"+Vi,arrowhead:"normal",arrowTypeEnd:"arrow_barb",style:"fill:none",labelStyle:"",label:n.description,arrowheadStyle:"fill: #333",labelpos:"c",labelType:"text",thickness:"normal",classes:"transition"},a=n.state1.id,o=n.state2.id;t.setEdge(a,o,i,Vi),Vi++}}))},qi=function(t){for(var e=Object.keys(t),n=0;n<e.length;n++)$i[e[n]]=t[e[n]]},Xi=function(t,e){c.info("Drawing state diagram (v2)",e),Mi.clear(),Wi={};var n=Di.a.parser;n.yy=Mi,n.parse(t);var r=Mi.getDirection();void 0===r&&(r="LR");var i=_t().state,a=i.nodeSpacing||50,o=i.rankSpacing||50,s=new G.a.Graph({multigraph:!0,compound:!0}).setGraph({rankdir:"TB",nodesep:a,ranksep:o,marginx:8,marginy:8}).setDefaultEdgeLabel((function(){return{}}));c.info(Mi.getRootDocV2()),Mi.extract(Mi.getRootDocV2()),c.info(Mi.getRootDocV2()),Hi(s,void 0,Mi.getRootDocV2(),!0);var u=Object(d.select)('[id="'.concat(e,'"]')),l=Object(d.select)("#"+e+" g");On(l,s,["barb"],"statediagram",e);var h=u.node().getBBox(),f=h.width+16,p=h.height+16;u.attr("class","statediagram");var g=u.node().getBBox();W(u,p,1.75*f,i.useMaxWidth);var y="".concat(g.x-8," ").concat(g.y-8," ").concat(f," ").concat(p);if(c.debug("viewBox ".concat(y)),u.attr("viewBox",y),!i.htmlLabels)for(var v=document.querySelectorAll('[id="'+e+'"] .edgeLabel .label'),m=0;m<v.length;m++){var b=v[m],x=b.getBBox(),_=document.createElementNS("http://www.w3.org/2000/svg","rect");_.setAttribute("rx",0),_.setAttribute("ry",0),_.setAttribute("width",x.width),_.setAttribute("height",x.height),b.insertBefore(_,b.firstChild)}},Zi={},Ji=null,Ki={master:Ji},Qi="master",ta="LR",ea=0;function na(){return P({length:7})}function ra(t,e){for(c.debug("Entering isfastforwardable:",t.id,e.id);t.seq<=e.seq&&t!==e&&null!=e.parent;){if(Array.isArray(e.parent))return c.debug("In merge commit:",e.parent),ra(t,Zi[e.parent[0]])||ra(t,Zi[e.parent[1]]);e=Zi[e.parent]}return c.debug(t.id,e.id),t.id===e.id}var ia={};function aa(t,e,n){var r=t.indexOf(e);-1===r?t.push(n):t.splice(r,1,n)}function oa(t){var e=t.reduce((function(t,e){return t.seq>e.seq?t:e}),t[0]),n="";t.forEach((function(t){n+=t===e?"\t*":"\t|"}));var r,i,a,o=[n,e.id,e.seq];for(var s in Ki)Ki[s]===e.id&&o.push(s);if(c.debug(o.join(" ")),Array.isArray(e.parent)){var u=Zi[e.parent[0]];aa(t,e,u),t.push(Zi[e.parent[1]])}else{if(null==e.parent)return;var l=Zi[e.parent];aa(t,e,l)}r=t,i=function(t){return t.id},a=Object.create(null),oa(t=r.reduce((function(t,e){var n=i(e);return a[n]||(a[n]=!0,t.push(e)),t}),[]))}var sa,ca=function(){var t=Object.keys(Zi).map((function(t){return Zi[t]}));return t.forEach((function(t){c.debug(t.id)})),t.sort((function(t,e){return e.seq-t.seq})),t},ua={setDirection:function(t){ta=t},setOptions:function(t){c.debug("options str",t),t=(t=t&&t.trim())||"{}";try{ia=JSON.parse(t)}catch(t){c.error("error while parsing gitGraph options",t.message)}},getOptions:function(){return ia},commit:function(t){var e={id:na(),message:t,seq:ea++,parent:null==Ji?null:Ji.id};Ji=e,Zi[e.id]=e,Ki[Qi]=e.id,c.debug("in pushCommit "+e.id)},branch:function(t){Ki[t]=null!=Ji?Ji.id:null,c.debug("in createBranch")},merge:function(t){var e=Zi[Ki[Qi]],n=Zi[Ki[t]];if(function(t,e){return t.seq>e.seq&&ra(e,t)}(e,n))c.debug("Already merged");else{if(ra(e,n))Ki[Qi]=Ki[t],Ji=Zi[Ki[Qi]];else{var r={id:na(),message:"merged branch "+t+" into "+Qi,seq:ea++,parent:[null==Ji?null:Ji.id,Ki[t]]};Ji=r,Zi[r.id]=r,Ki[Qi]=r.id}c.debug(Ki),c.debug("in mergeBranch")}},checkout:function(t){c.debug("in checkout");var e=Ki[Qi=t];Ji=Zi[e]},reset:function(t){c.debug("in reset",t);var e=t.split(":")[0],n=parseInt(t.split(":")[1]),r="HEAD"===e?Ji:Zi[Ki[e]];for(c.debug(r,n);n>0;)if(n--,!(r=Zi[r.parent])){var i="Critical error - unique parent commit not found during reset";throw c.error(i),i}Ji=r,Ki[Qi]=r.id},prettyPrint:function(){c.debug(Zi),oa([ca()[0]])},clear:function(){Zi={},Ki={master:Ji=null},Qi="master",ea=0},getBranchesAsObjArray:function(){var t=[];for(var e in Ki)t.push({name:e,commit:Zi[Ki[e]]});return t},getBranches:function(){return Ki},getCommits:function(){return Zi},getCommitsArray:ca,getCurrentBranch:function(){return Qi},getDirection:function(){return ta},getHead:function(){return Ji}},la=n(71),ha=n.n(la),fa={},da={nodeSpacing:150,nodeFillColor:"yellow",nodeStrokeWidth:2,nodeStrokeColor:"grey",lineStrokeWidth:4,branchOffset:50,lineColor:"grey",leftMargin:50,branchColors:["#442f74","#983351","#609732","#AA9A39"],nodeRadius:10,nodeLabel:{width:75,height:100,x:-25,y:0}},pa={};function ga(t,e,n,r){var i=D(r,d.curveBasis),a=da.branchColors[n%da.branchColors.length],o=Object(d.line)().x((function(t){return Math.round(t.x)})).y((function(t){return Math.round(t.y)})).curve(i);t.append("svg:path").attr("d",o(e)).style("stroke",a).style("stroke-width",da.lineStrokeWidth).style("fill","none")}function ya(t,e){e=e||t.node().getBBox();var n=t.node().getCTM();return{left:n.e+e.x*n.a,top:n.f+e.y*n.d,width:e.width,height:e.height}}function va(t,e,n,r,i){c.debug("svgDrawLineForCommits: ",e,n);var a=ya(t.select("#node-"+e+" circle")),o=ya(t.select("#node-"+n+" circle"));switch(r){case"LR":if(a.left-o.left>da.nodeSpacing){var s={x:a.left-da.nodeSpacing,y:o.top+o.height/2};ga(t,[s,{x:o.left+o.width,y:o.top+o.height/2}],i,"linear"),ga(t,[{x:a.left,y:a.top+a.height/2},{x:a.left-da.nodeSpacing/2,y:a.top+a.height/2},{x:a.left-da.nodeSpacing/2,y:s.y},s],i)}else ga(t,[{x:a.left,y:a.top+a.height/2},{x:a.left-da.nodeSpacing/2,y:a.top+a.height/2},{x:a.left-da.nodeSpacing/2,y:o.top+o.height/2},{x:o.left+o.width,y:o.top+o.height/2}],i);break;case"BT":if(o.top-a.top>da.nodeSpacing){var u={x:o.left+o.width/2,y:a.top+a.height+da.nodeSpacing};ga(t,[u,{x:o.left+o.width/2,y:o.top}],i,"linear"),ga(t,[{x:a.left+a.width/2,y:a.top+a.height},{x:a.left+a.width/2,y:a.top+a.height+da.nodeSpacing/2},{x:o.left+o.width/2,y:u.y-da.nodeSpacing/2},u],i)}else ga(t,[{x:a.left+a.width/2,y:a.top+a.height},{x:a.left+a.width/2,y:a.top+da.nodeSpacing/2},{x:o.left+o.width/2,y:o.top-da.nodeSpacing/2},{x:o.left+o.width/2,y:o.top}],i)}}function ma(t,e){return t.select(e).node().cloneNode(!0)}function ba(t,e,n,r){var i,a=Object.keys(fa).length;if("string"==typeof e)do{if(i=fa[e],c.debug("in renderCommitHistory",i.id,i.seq),t.select("#node-"+e).size()>0)return;t.append((function(){return ma(t,"#def-commit")})).attr("class","commit").attr("id",(function(){return"node-"+i.id})).attr("transform",(function(){switch(r){case"LR":return"translate("+(i.seq*da.nodeSpacing+da.leftMargin)+", "+sa*da.branchOffset+")";case"BT":return"translate("+(sa*da.branchOffset+da.leftMargin)+", "+(a-i.seq)*da.nodeSpacing+")"}})).attr("fill",da.nodeFillColor).attr("stroke",da.nodeStrokeColor).attr("stroke-width",da.nodeStrokeWidth);var o=void 0;for(var s in n)if(n[s].commit===i){o=n[s];break}o&&(c.debug("found branch ",o.name),t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","branch-label").text(o.name+", ")),t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","commit-id").text(i.id),""!==i.message&&"BT"===r&&t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","commit-msg").text(", "+i.message),e=i.parent}while(e&&fa[e]);Array.isArray(e)&&(c.debug("found merge commmit",e),ba(t,e[0],n,r),sa++,ba(t,e[1],n,r),sa--)}function xa(t,e,n,r){for(r=r||0;e.seq>0&&!e.lineDrawn;)"string"==typeof e.parent?(va(t,e.id,e.parent,n,r),e.lineDrawn=!0,e=fa[e.parent]):Array.isArray(e.parent)&&(va(t,e.id,e.parent[0],n,r),va(t,e.id,e.parent[1],n,r+1),xa(t,fa[e.parent[1]],n,r+1),e.lineDrawn=!0,e=fa[e.parent[0]])}var _a,ka=function(t){pa=t},wa=function(t,e,n){try{var r=ha.a.parser;r.yy=ua,r.yy.clear(),c.debug("in gitgraph renderer",t+"\n","id:",e,n),r.parse(t+"\n"),da=Object.assign(da,pa,ua.getOptions()),c.debug("effective options",da);var i=ua.getDirection();fa=ua.getCommits();var a=ua.getBranchesAsObjArray();"BT"===i&&(da.nodeLabel.x=a.length*da.branchOffset,da.nodeLabel.width="100%",da.nodeLabel.y=-2*da.nodeRadius);var o=Object(d.select)('[id="'.concat(e,'"]'));for(var s in function(t){t.append("defs").append("g").attr("id","def-commit").append("circle").attr("r",da.nodeRadius).attr("cx",0).attr("cy",0),t.select("#def-commit").append("foreignObject").attr("width",da.nodeLabel.width).attr("height",da.nodeLabel.height).attr("x",da.nodeLabel.x).attr("y",da.nodeLabel.y).attr("class","node-label").attr("requiredFeatures","http://www.w3.org/TR/SVG11/feature#Extensibility").append("p").html("")}(o),sa=1,a){var u=a[s];ba(o,u.commit.id,a,i),xa(o,u.commit,i),sa++}o.attr("height",(function(){return"BT"===i?Object.keys(fa).length*da.nodeSpacing:(a.length+1)*da.branchOffset}))}catch(t){c.error("Error while rendering gitgraph"),c.error(t.message)}},Ea="",Ta=!1,Ca={setMessage:function(t){c.debug("Setting message to: "+t),Ea=t},getMessage:function(){return Ea},setInfo:function(t){Ta=t},getInfo:function(){return Ta}},Aa=n(72),Sa=n.n(Aa),Ma={},Oa=function(t){Object.keys(t).forEach((function(e){Ma[e]=t[e]}))},Da=function(t,e,n){try{var r=Sa.a.parser;r.yy=Ca,c.debug("Renering info diagram\n"+t),r.parse(t),c.debug("Parsed info diagram");var i=Object(d.select)("#"+e);i.append("g").append("text").attr("x",100).attr("y",40).attr("class","version").attr("font-size","32px").style("text-anchor","middle").text("v "+n),i.attr("height",100),i.attr("width",400)}catch(t){c.error("Error while rendering info diagram"),c.error(t.message)}},Na={},Ba=function(t){Object.keys(t).forEach((function(e){Na[e]=t[e]}))},La=function(t,e){try{c.debug("Renering svg for syntax error\n");var n=Object(d.select)("#"+t),r=n.append("g");r.append("path").attr("class","error-icon").attr("d","m411.313,123.313c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32-9.375,9.375-20.688-20.688c-12.484-12.5-32.766-12.5-45.25,0l-16,16c-1.261,1.261-2.304,2.648-3.31,4.051-21.739-8.561-45.324-13.426-70.065-13.426-105.867,0-192,86.133-192,192s86.133,192 192,192 192-86.133 192-192c0-24.741-4.864-48.327-13.426-70.065 1.402-1.007 2.79-2.049 4.051-3.31l16-16c12.5-12.492 12.5-32.758 0-45.25l-20.688-20.688 9.375-9.375 32.001-31.999zm-219.313,100.687c-52.938,0-96,43.063-96,96 0,8.836-7.164,16-16,16s-16-7.164-16-16c0-70.578 57.422-128 128-128 8.836,0 16,7.164 16,16s-7.164,16-16,16z"),r.append("path").attr("class","error-icon").attr("d","m459.02,148.98c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l16,16c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16.001-16z"),r.append("path").attr("class","error-icon").attr("d","m340.395,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16-16c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l15.999,16z"),r.append("path").attr("class","error-icon").attr("d","m400,64c8.844,0 16-7.164 16-16v-32c0-8.836-7.156-16-16-16-8.844,0-16,7.164-16,16v32c0,8.836 7.156,16 16,16z"),r.append("path").attr("class","error-icon").attr("d","m496,96.586h-32c-8.844,0-16,7.164-16,16 0,8.836 7.156,16 16,16h32c8.844,0 16-7.164 16-16 0-8.836-7.156-16-16-16z"),r.append("path").attr("class","error-icon").attr("d","m436.98,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688l32-32c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32c-6.251,6.25-6.251,16.375-0.001,22.625z"),r.append("text").attr("class","error-text").attr("x",1240).attr("y",250).attr("font-size","150px").style("text-anchor","middle").text("Syntax error in graph"),r.append("text").attr("class","error-text").attr("x",1050).attr("y",400).attr("font-size","100px").style("text-anchor","middle").text("mermaid version "+e),n.attr("height",100),n.attr("width",400),n.attr("viewBox","768 0 512 512")}catch(t){c.error("Error while rendering info diagram"),c.error(t.message)}},Fa={},Pa="",Ia={parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},getConfig:function(){return _t().pie},addSection:function(t,e){void 0===Fa[t]&&(Fa[t]=e,c.debug("Added new section :",t))},getSections:function(){return Fa},cleanupValue:function(t){return":"===t.substring(0,1)?(t=t.substring(1).trim(),Number(t.trim())):Number(t.trim())},clear:function(){Fa={},Pa=""},setTitle:function(t){Pa=t},getTitle:function(){return Pa}},ja=n(73),Ra=n.n(ja),Ya={},za=function(t){Object.keys(t).forEach((function(e){Ya[e]=t[e]}))},Ua=function(t,e){try{var n=Ra.a.parser;n.yy=Ia,c.debug("Rendering info diagram\n"+t),n.yy.clear(),n.parse(t),c.debug("Parsed info diagram");var r=document.getElementById(e);void 0===(_a=r.parentElement.offsetWidth)&&(_a=1200),void 0!==Ya.useWidth&&(_a=Ya.useWidth);var i=Object(d.select)("#"+e);W(i,450,_a,Ya.useMaxWidth),r.setAttribute("viewBox","0 0 "+_a+" 450");var a=Math.min(_a,450)/2-40,o=i.append("g").attr("transform","translate("+_a/2+",225)"),s=Ia.getSections(),u=0;Object.keys(s).forEach((function(t){u+=s[t]}));var l=Object(d.scaleOrdinal)().domain(s).range(d.schemeSet2),h=Object(d.pie)().value((function(t){return t.value}))(Object(d.entries)(s)),f=Object(d.arc)().innerRadius(0).outerRadius(a);o.selectAll("mySlices").data(h).enter().append("path").attr("d",f).attr("fill",(function(t){return l(t.data.key)})).attr("stroke","black").style("stroke-width","2px").style("opacity",.7),o.selectAll("mySlices").data(h).enter().append("text").text((function(t){return(t.data.value/u*100).toFixed(0)+"%"})).attr("transform",(function(t){return"translate("+f.centroid(t)+")"})).style("text-anchor","middle").attr("class","slice").style("font-size",17),o.append("text").text(n.yy.getTitle()).attr("x",0).attr("y",-200).attr("class","pieTitleText");var p=o.selectAll(".legend").data(l.domain()).enter().append("g").attr("class","legend").attr("transform",(function(t,e){return"translate(216,"+(22*e-22*l.domain().length/2)+")"}));p.append("rect").attr("width",18).attr("height",18).style("fill",l).style("stroke",l),p.append("text").attr("x",22).attr("y",14).text((function(t){return t}))}catch(t){c.error("Error while rendering info diagram"),c.error(t)}},$a={},Wa=[],Ha="",Va=function(t){return void 0===$a[t]&&($a[t]={attributes:[]},c.info("Added new entity :",t)),$a[t]},Ga={Cardinality:{ZERO_OR_ONE:"ZERO_OR_ONE",ZERO_OR_MORE:"ZERO_OR_MORE",ONE_OR_MORE:"ONE_OR_MORE",ONLY_ONE:"ONLY_ONE"},Identification:{NON_IDENTIFYING:"NON_IDENTIFYING",IDENTIFYING:"IDENTIFYING"},parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},getConfig:function(){return _t().er},addEntity:Va,addAttributes:function(t,e){var n,r=Va(t);for(n=e.length-1;n>=0;n--)r.attributes.push(e[n]),c.debug("Added attribute ",e[n].attributeName)},getEntities:function(){return $a},addRelationship:function(t,e,n,r){var i={entityA:t,roleA:e,entityB:n,relSpec:r};Wa.push(i),c.debug("Added new relationship :",i)},getRelationships:function(){return Wa},clear:function(){$a={},Wa=[],Ha=""},setTitle:function(t){Ha=t},getTitle:function(){return Ha}},qa=n(74),Xa=n.n(qa),Za={ONLY_ONE_START:"ONLY_ONE_START",ONLY_ONE_END:"ONLY_ONE_END",ZERO_OR_ONE_START:"ZERO_OR_ONE_START",ZERO_OR_ONE_END:"ZERO_OR_ONE_END",ONE_OR_MORE_START:"ONE_OR_MORE_START",ONE_OR_MORE_END:"ONE_OR_MORE_END",ZERO_OR_MORE_START:"ZERO_OR_MORE_START",ZERO_OR_MORE_END:"ZERO_OR_MORE_END"},Ja=Za,Ka=function(t,e){var n;t.append("defs").append("marker").attr("id",Za.ONLY_ONE_START).attr("refX",0).attr("refY",9).attr("markerWidth",18).attr("markerHeight",18).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M9,0 L9,18 M15,0 L15,18"),t.append("defs").append("marker").attr("id",Za.ONLY_ONE_END).attr("refX",18).attr("refY",9).attr("markerWidth",18).attr("markerHeight",18).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M3,0 L3,18 M9,0 L9,18"),(n=t.append("defs").append("marker").attr("id",Za.ZERO_OR_ONE_START).attr("refX",0).attr("refY",9).attr("markerWidth",30).attr("markerHeight",18).attr("orient","auto")).append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",21).attr("cy",9).attr("r",6),n.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M9,0 L9,18"),(n=t.append("defs").append("marker").attr("id",Za.ZERO_OR_ONE_END).attr("refX",30).attr("refY",9).attr("markerWidth",30).attr("markerHeight",18).attr("orient","auto")).append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",9).attr("cy",9).attr("r",6),n.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M21,0 L21,18"),t.append("defs").append("marker").attr("id",Za.ONE_OR_MORE_START).attr("refX",18).attr("refY",18).attr("markerWidth",45).attr("markerHeight",36).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M0,18 Q 18,0 36,18 Q 18,36 0,18 M42,9 L42,27"),t.append("defs").append("marker").attr("id",Za.ONE_OR_MORE_END).attr("refX",27).attr("refY",18).attr("markerWidth",45).attr("markerHeight",36).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M3,9 L3,27 M9,18 Q27,0 45,18 Q27,36 9,18"),(n=t.append("defs").append("marker").attr("id",Za.ZERO_OR_MORE_START).attr("refX",18).attr("refY",18).attr("markerWidth",57).attr("markerHeight",36).attr("orient","auto")).append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",48).attr("cy",18).attr("r",6),n.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M0,18 Q18,0 36,18 Q18,36 0,18"),(n=t.append("defs").append("marker").attr("id",Za.ZERO_OR_MORE_END).attr("refX",39).attr("refY",18).attr("markerWidth",57).attr("markerHeight",36).attr("orient","auto")).append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",9).attr("cy",18).attr("r",6),n.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M21,18 Q39,0 57,18 Q39,36 21,18")},Qa={},to=function(t,e,n){var r;return Object.keys(e).forEach((function(i){var a=t.append("g").attr("id",i);r=void 0===r?i:r;var o="entity-"+i,s=a.append("text").attr("class","er entityLabel").attr("id",o).attr("x",0).attr("y",0).attr("dominant-baseline","middle").attr("text-anchor","middle").attr("style","font-family: "+_t().fontFamily+"; font-size: "+Qa.fontSize+"px").text(i),c=function(t,e,n){var r=Qa.entityPadding/3,i=Qa.entityPadding/3,a=.85*Qa.fontSize,o=e.node().getBBox(),s=[],c=0,u=0,l=o.height+2*r,h=1;n.forEach((function(n){var i="".concat(e.node().id,"-attr-").concat(h),o=t.append("text").attr("class","er entityLabel").attr("id","".concat(i,"-type")).attr("x",0).attr("y",0).attr("dominant-baseline","middle").attr("text-anchor","left").attr("style","font-family: "+_t().fontFamily+"; font-size: "+a+"px").text(n.attributeType),f=t.append("text").attr("class","er entityLabel").attr("id","".concat(i,"-name")).attr("x",0).attr("y",0).attr("dominant-baseline","middle").attr("text-anchor","left").attr("style","font-family: "+_t().fontFamily+"; font-size: "+a+"px").text(n.attributeName);s.push({tn:o,nn:f});var d=o.node().getBBox(),p=f.node().getBBox();c=Math.max(c,d.width),u=Math.max(u,p.width),l+=Math.max(d.height,p.height)+2*r,h+=1}));var f={width:Math.max(Qa.minEntityWidth,Math.max(o.width+2*Qa.entityPadding,c+u+4*i)),height:n.length>0?l:Math.max(Qa.minEntityHeight,o.height+2*Qa.entityPadding)},d=Math.max(0,f.width-(c+u)-4*i);if(n.length>0){e.attr("transform","translate("+f.width/2+","+(r+o.height/2)+")");var p=o.height+2*r,g="attributeBoxOdd";s.forEach((function(e){var n=p+r+Math.max(e.tn.node().getBBox().height,e.nn.node().getBBox().height)/2;e.tn.attr("transform","translate("+i+","+n+")");var a=t.insert("rect","#"+e.tn.node().id).attr("class","er ".concat(g)).attr("fill",Qa.fill).attr("fill-opacity","100%").attr("stroke",Qa.stroke).attr("x",0).attr("y",p).attr("width",c+2*i+d/2).attr("height",e.tn.node().getBBox().height+2*r);e.nn.attr("transform","translate("+(parseFloat(a.attr("width"))+i)+","+n+")"),t.insert("rect","#"+e.nn.node().id).attr("class","er ".concat(g)).attr("fill",Qa.fill).attr("fill-opacity","100%").attr("stroke",Qa.stroke).attr("x","".concat(a.attr("x")+a.attr("width"))).attr("y",p).attr("width",u+2*i+d/2).attr("height",e.nn.node().getBBox().height+2*r),p+=Math.max(e.tn.node().getBBox().height,e.nn.node().getBBox().height)+2*r,g="attributeBoxOdd"==g?"attributeBoxEven":"attributeBoxOdd"}))}else f.height=Math.max(Qa.minEntityHeight,l),e.attr("transform","translate("+f.width/2+","+f.height/2+")");return f}(a,s,e[i].attributes),u=c.width,l=c.height,h=a.insert("rect","#"+o).attr("class","er entityBox").attr("fill",Qa.fill).attr("fill-opacity","100%").attr("stroke",Qa.stroke).attr("x",0).attr("y",0).attr("width",u).attr("height",l).node().getBBox();n.setNode(i,{width:h.width,height:h.height,shape:"rect",id:i})})),r},eo=function(t){return(t.entityA+t.roleA+t.entityB).replace(/\s/g,"")},no=0,ro=function(t){for(var e=Object.keys(t),n=0;n<e.length;n++)Qa[e[n]]=t[e[n]]},io=function(t,e){c.info("Drawing ER diagram"),Ga.clear();var n=Xa.a.parser;n.yy=Ga;try{n.parse(t)}catch(t){c.debug("Parsing failed")}var r,i=Object(d.select)("[id='".concat(e,"']"));Ka(i,Qa),r=new G.a.Graph({multigraph:!0,directed:!0,compound:!1}).setGraph({rankdir:Qa.layoutDirection,marginx:20,marginy:20,nodesep:100,edgesep:100,ranksep:100}).setDefaultEdgeLabel((function(){return{}}));var a,o,s=to(i,Ga.getEntities(),r),u=function(t,e){return t.forEach((function(t){e.setEdge(t.entityA,t.entityB,{relationship:t},eo(t))})),t}(Ga.getRelationships(),r);ke.a.layout(r),a=i,(o=r).nodes().forEach((function(t){void 0!==t&&void 0!==o.node(t)&&a.select("#"+t).attr("transform","translate("+(o.node(t).x-o.node(t).width/2)+","+(o.node(t).y-o.node(t).height/2)+" )")})),u.forEach((function(t){!function(t,e,n,r){no++;var i=n.edge(e.entityA,e.entityB,eo(e)),a=Object(d.line)().x((function(t){return t.x})).y((function(t){return t.y})).curve(d.curveBasis),o=t.insert("path","#"+r).attr("class","er relationshipLine").attr("d",a(i.points)).attr("stroke",Qa.stroke).attr("fill","none");e.relSpec.relType===Ga.Identification.NON_IDENTIFYING&&o.attr("stroke-dasharray","8,8");var s="";switch(Qa.arrowMarkerAbsolute&&(s=(s=(s=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),e.relSpec.cardA){case Ga.Cardinality.ZERO_OR_ONE:o.attr("marker-end","url("+s+"#"+Ja.ZERO_OR_ONE_END+")");break;case Ga.Cardinality.ZERO_OR_MORE:o.attr("marker-end","url("+s+"#"+Ja.ZERO_OR_MORE_END+")");break;case Ga.Cardinality.ONE_OR_MORE:o.attr("marker-end","url("+s+"#"+Ja.ONE_OR_MORE_END+")");break;case Ga.Cardinality.ONLY_ONE:o.attr("marker-end","url("+s+"#"+Ja.ONLY_ONE_END+")")}switch(e.relSpec.cardB){case Ga.Cardinality.ZERO_OR_ONE:o.attr("marker-start","url("+s+"#"+Ja.ZERO_OR_ONE_START+")");break;case Ga.Cardinality.ZERO_OR_MORE:o.attr("marker-start","url("+s+"#"+Ja.ZERO_OR_MORE_START+")");break;case Ga.Cardinality.ONE_OR_MORE:o.attr("marker-start","url("+s+"#"+Ja.ONE_OR_MORE_START+")");break;case Ga.Cardinality.ONLY_ONE:o.attr("marker-start","url("+s+"#"+Ja.ONLY_ONE_START+")")}var c=o.node().getTotalLength(),u=o.node().getPointAtLength(.5*c),l="rel"+no,h=t.append("text").attr("class","er relationshipLabel").attr("id",l).attr("x",u.x).attr("y",u.y).attr("text-anchor","middle").attr("dominant-baseline","middle").attr("style","font-family: "+_t().fontFamily+"; font-size: "+Qa.fontSize+"px").text(e.roleA).node().getBBox();t.insert("rect","#"+l).attr("class","er relationshipLabelBox").attr("x",u.x-h.width/2).attr("y",u.y-h.height/2).attr("width",h.width).attr("height",h.height).attr("fill","white").attr("fill-opacity","85%")}(i,t,r,s)}));var l=Qa.diagramPadding,h=i.node().getBBox(),f=h.width+2*l,p=h.height+2*l;W(i,p,f,Qa.useMaxWidth),i.attr("viewBox","".concat(h.x-l," ").concat(h.y-l," ").concat(f," ").concat(p))},ao=n(28),oo=n.n(ao);function so(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e<t.length;e++)n[e]=t[e];return n}}(t)||function(t){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t))return Array.from(t)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance")}()}var co="",uo="",lo=[],ho=[],fo=[],po=function(){for(var t=!0,e=0;e<fo.length;e++)fo[e].processed,t=t&&fo[e].processed;return t},go={parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},getConfig:function(){return _t().journey},clear:function(){lo.length=0,ho.length=0,uo="",co="",fo.length=0},setTitle:function(t){co=t},getTitle:function(){return co},addSection:function(t){uo=t,lo.push(t)},getSections:function(){return lo},getTasks:function(){for(var t=po(),e=0;!t&&e<100;)t=po(),e++;return ho.push.apply(ho,fo),ho},addTask:function(t,e){var n=e.substr(1).split(":"),r=0,i=[];1===n.length?(r=Number(n[0]),i=[]):(r=Number(n[0]),i=n[1].split(","));var a=i.map((function(t){return t.trim()})),o={section:uo,type:uo,people:a,task:t,score:r};fo.push(o)},addTaskOrg:function(t){var e={section:uo,type:uo,description:t,task:t,classes:[]};ho.push(e)},getActors:function(){return t=[],ho.forEach((function(e){e.people&&t.push.apply(t,so(e.people))})),so(new Set(t)).sort();var t}},yo=function(t,e){var n=t.append("rect");return n.attr("x",e.x),n.attr("y",e.y),n.attr("fill",e.fill),n.attr("stroke",e.stroke),n.attr("width",e.width),n.attr("height",e.height),n.attr("rx",e.rx),n.attr("ry",e.ry),void 0!==e.class&&n.attr("class",e.class),n},vo=function(t,e){var n=t.append("circle");return n.attr("cx",e.cx),n.attr("cy",e.cy),n.attr("fill",e.fill),n.attr("stroke",e.stroke),n.attr("r",e.r),void 0!==n.class&&n.attr("class",n.class),void 0!==e.title&&n.append("title").text(e.title),n},mo=function(t,e){var n=e.text.replace(/<br\s*\/?>/gi," "),r=t.append("text");r.attr("x",e.x),r.attr("y",e.y),r.attr("class","legend"),r.style("text-anchor",e.anchor),void 0!==e.class&&r.attr("class",e.class);var i=r.append("tspan");return i.attr("x",e.x+2*e.textMargin),i.text(n),r},bo=-1,xo=function(){return{x:0,y:0,width:100,anchor:"start",height:100,rx:0,ry:0}},_o=function(){function t(t,e,n,i,a,o,s,c){r(e.append("text").attr("x",n+a/2).attr("y",i+o/2+5).style("font-color",c).style("text-anchor","middle").text(t),s)}function e(t,e,n,i,a,o,s,c,u){for(var l=c.taskFontSize,h=c.taskFontFamily,f=t.split(/<br\s*\/?>/gi),d=0;d<f.length;d++){var p=d*l-l*(f.length-1)/2,g=e.append("text").attr("x",n+a/2).attr("y",i).attr("fill",u).style("text-anchor","middle").style("font-size",l).style("font-family",h);g.append("tspan").attr("x",n+a/2).attr("dy",p).text(f[d]),g.attr("y",i+o/2).attr("dominant-baseline","central").attr("alignment-baseline","central"),r(g,s)}}function n(t,n,i,a,o,s,c,u){var l=n.append("switch"),h=l.append("foreignObject").attr("x",i).attr("y",a).attr("width",o).attr("height",s).attr("position","fixed").append("div").style("display","table").style("height","100%").style("width","100%");h.append("div").attr("class","label").style("display","table-cell").style("text-align","center").style("vertical-align","middle").text(t),e(t,l,i,a,o,s,c,u),r(h,c)}function r(t,e){for(var n in e)n in e&&t.attr(n,e[n])}return function(r){return"fo"===r.textPlacement?n:"old"===r.textPlacement?t:e}}(),ko=vo,wo=function(t,e,n){var r=t.append("g"),i=xo();i.x=e.x,i.y=e.y,i.fill=e.fill,i.width=n.width,i.height=n.height,i.class="journey-section section-type-"+e.num,i.rx=3,i.ry=3,yo(r,i),_o(n)(e.text,r,i.x,i.y,i.width,i.height,{class:"journey-section section-type-"+e.num},n,e.colour)},Eo=mo,To=function(t,e,n){var r=e.x+n.width/2,i=t.append("g");bo++;var a,o,s;i.append("line").attr("id","task"+bo).attr("x1",r).attr("y1",e.y).attr("x2",r).attr("y2",450).attr("class","task-line").attr("stroke-width","1px").attr("stroke-dasharray","4 2").attr("stroke","#666"),a=i,o={cx:r,cy:300+30*(5-e.score),score:e.score},a.append("circle").attr("cx",o.cx).attr("cy",o.cy).attr("class","face").attr("r",15).attr("stroke-width",2).attr("overflow","visible"),(s=a.append("g")).append("circle").attr("cx",o.cx-5).attr("cy",o.cy-5).attr("r",1.5).attr("stroke-width",2).attr("fill","#666").attr("stroke","#666"),s.append("circle").attr("cx",o.cx+5).attr("cy",o.cy-5).attr("r",1.5).attr("stroke-width",2).attr("fill","#666").attr("stroke","#666"),o.score>3?function(t){var e=Object(d.arc)().startAngle(Math.PI/2).endAngle(Math.PI/2*3).innerRadius(7.5).outerRadius(15/2.2);t.append("path").attr("class","mouth").attr("d",e).attr("transform","translate("+o.cx+","+(o.cy+2)+")")}(s):o.score<3?function(t){var e=Object(d.arc)().startAngle(3*Math.PI/2).endAngle(Math.PI/2*5).innerRadius(7.5).outerRadius(15/2.2);t.append("path").attr("class","mouth").attr("d",e).attr("transform","translate("+o.cx+","+(o.cy+7)+")")}(s):function(t){t.append("line").attr("class","mouth").attr("stroke",2).attr("x1",o.cx-5).attr("y1",o.cy+7).attr("x2",o.cx+5).attr("y2",o.cy+7).attr("class","mouth").attr("stroke-width","1px").attr("stroke","#666")}(s);var c=xo();c.x=e.x,c.y=e.y,c.fill=e.fill,c.width=n.width,c.height=n.height,c.class="task task-type-"+e.num,c.rx=3,c.ry=3,yo(i,c);var u=e.x+14;e.people.forEach((function(t){var n=e.actors[t],r={cx:u,cy:e.y,r:7,fill:n,stroke:"#000",title:t};vo(i,r),u+=10})),_o(n)(e.task,i,c.x,c.y,c.width,c.height,{class:"task"},n,e.colour)},Co=function(t){t.append("defs").append("marker").attr("id","arrowhead").attr("refX",5).attr("refY",2).attr("markerWidth",6).attr("markerHeight",4).attr("orient","auto").append("path").attr("d","M 0,0 V 4 L6,2 Z")};ao.parser.yy=go;var Ao={leftMargin:150,diagramMarginX:50,diagramMarginY:20,taskMargin:50,width:150,height:50,taskFontSize:14,taskFontFamily:'"Open-Sans", "sans-serif"',boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,messageAlign:"center",bottomMarginAdj:1,activationWidth:10,textPlacement:"fo",actorColours:["#8FBC8F","#7CFC00","#00FFFF","#20B2AA","#B0E0E6","#FFFFE0"],sectionFills:["#191970","#8B008B","#4B0082","#2F4F4F","#800000","#8B4513","#00008B"],sectionColours:["#fff"]},So={};var Mo=Ao.leftMargin,Oo={data:{startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},verticalPos:0,sequenceItems:[],init:function(){this.sequenceItems=[],this.data={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},this.verticalPos=0},updateVal:function(t,e,n,r){void 0===t[e]?t[e]=n:t[e]=r(n,t[e])},updateBounds:function(t,e,n,r){var i,a=this,o=0;this.sequenceItems.forEach((function(s){o++;var c=a.sequenceItems.length-o+1;a.updateVal(s,"starty",e-c*Ao.boxMargin,Math.min),a.updateVal(s,"stopy",r+c*Ao.boxMargin,Math.max),a.updateVal(Oo.data,"startx",t-c*Ao.boxMargin,Math.min),a.updateVal(Oo.data,"stopx",n+c*Ao.boxMargin,Math.max),"activation"!==i&&(a.updateVal(s,"startx",t-c*Ao.boxMargin,Math.min),a.updateVal(s,"stopx",n+c*Ao.boxMargin,Math.max),a.updateVal(Oo.data,"starty",e-c*Ao.boxMargin,Math.min),a.updateVal(Oo.data,"stopy",r+c*Ao.boxMargin,Math.max))}))},insert:function(t,e,n,r){var i=Math.min(t,n),a=Math.max(t,n),o=Math.min(e,r),s=Math.max(e,r);this.updateVal(Oo.data,"startx",i,Math.min),this.updateVal(Oo.data,"starty",o,Math.min),this.updateVal(Oo.data,"stopx",a,Math.max),this.updateVal(Oo.data,"stopy",s,Math.max),this.updateBounds(i,o,a,s)},bumpVerticalPos:function(t){this.verticalPos=this.verticalPos+t,this.data.stopy=this.verticalPos},getVerticalPos:function(){return this.verticalPos},getBounds:function(){return this.data}},Do=Ao.sectionFills,No=Ao.sectionColours,Bo=function(t,e,n){for(var r="",i=n+(2*Ao.height+Ao.diagramMarginY),a=0,o="#CCC",s="black",c=0,u=0;u<e.length;u++){var l=e[u];if(r!==l.section){o=Do[a%Do.length],c=a%Do.length,s=No[a%No.length];var h={x:u*Ao.taskMargin+u*Ao.width+Mo,y:50,text:l.section,fill:o,num:c,colour:s};wo(t,h,Ao),r=l.section,a++}var f=l.people.reduce((function(t,e){return So[e]&&(t[e]=So[e]),t}),{});l.x=u*Ao.taskMargin+u*Ao.width+Mo,l.y=i,l.width=Ao.diagramMarginX,l.height=Ao.diagramMarginY,l.colour=s,l.fill=o,l.num=c,l.actors=f,To(t,l,Ao),Oo.insert(l.x,l.y,l.x+l.width+Ao.taskMargin,450)}},Lo=function(t){Object.keys(t).forEach((function(e){Ao[e]=t[e]}))},Fo=function(t,e){ao.parser.yy.clear(),ao.parser.parse(t+"\n"),Oo.init();var n=Object(d.select)("#"+e);n.attr("xmlns:xlink","http://www.w3.org/1999/xlink"),Co(n);var r=ao.parser.yy.getTasks(),i=ao.parser.yy.getTitle(),a=ao.parser.yy.getActors();for(var o in So)delete So[o];var s=0;a.forEach((function(t){So[t]=Ao.actorColours[s%Ao.actorColours.length],s++})),function(t){var e=60;Object.keys(So).forEach((function(n){var r=So[n];ko(t,{cx:20,cy:e,r:7,fill:r,stroke:"#000"});var i={x:40,y:e+7,fill:"#666",text:n,textMargin:5|Ao.boxTextMargin};Eo(t,i),e+=20}))}(n),Oo.insert(0,0,Mo,50*Object.keys(So).length),Bo(n,r,0);var c=Oo.getBounds();i&&n.append("text").text(i).attr("x",Mo).attr("font-size","4ex").attr("font-weight","bold").attr("y",25);var u=c.stopy-c.starty+2*Ao.diagramMarginY,l=Mo+c.stopx+2*Ao.diagramMarginX;W(n,u,l,Ao.useMaxWidth),n.append("line").attr("x1",Mo).attr("y1",4*Ao.height).attr("x2",l-Mo-4).attr("y2",4*Ao.height).attr("stroke-width",4).attr("stroke","black").attr("marker-end","url(#arrowhead)");var h=i?70:0;n.attr("viewBox","".concat(c.startx," -25 ").concat(l," ").concat(u+h)),n.attr("preserveAspectRatio","xMinYMin meet")},Po=function(t){return"g.classGroup text {\n fill: ".concat(t.nodeBorder,";\n fill: ").concat(t.classText,";\n stroke: none;\n font-family: ").concat(t.fontFamily,";\n font-size: 10px;\n\n .title {\n font-weight: bolder;\n }\n\n}\n\n.classTitle {\n font-weight: bolder;\n}\n.node rect,\n .node circle,\n .node ellipse,\n .node polygon,\n .node path {\n fill: ").concat(t.mainBkg,";\n stroke: ").concat(t.nodeBorder,";\n stroke-width: 1px;\n }\n\n\n.divider {\n stroke: ").concat(t.nodeBorder,";\n stroke: 1;\n}\n\ng.clickable {\n cursor: pointer;\n}\n\ng.classGroup rect {\n fill: ").concat(t.mainBkg,";\n stroke: ").concat(t.nodeBorder,";\n}\n\ng.classGroup line {\n stroke: ").concat(t.nodeBorder,";\n stroke-width: 1;\n}\n\n.classLabel .box {\n stroke: none;\n stroke-width: 0;\n fill: ").concat(t.mainBkg,";\n opacity: 0.5;\n}\n\n.classLabel .label {\n fill: ").concat(t.nodeBorder,";\n font-size: 10px;\n}\n\n.relation {\n stroke: ").concat(t.lineColor,";\n stroke-width: 1;\n fill: none;\n}\n\n.dashed-line{\n stroke-dasharray: 3;\n}\n\n#compositionStart, .composition {\n fill: ").concat(t.lineColor," !important;\n stroke: ").concat(t.lineColor," !important;\n stroke-width: 1;\n}\n\n#compositionEnd, .composition {\n fill: ").concat(t.lineColor," !important;\n stroke: ").concat(t.lineColor," !important;\n stroke-width: 1;\n}\n\n#dependencyStart, .dependency {\n fill: ").concat(t.lineColor," !important;\n stroke: ").concat(t.lineColor," !important;\n stroke-width: 1;\n}\n\n#dependencyStart, .dependency {\n fill: ").concat(t.lineColor," !important;\n stroke: ").concat(t.lineColor," !important;\n stroke-width: 1;\n}\n\n#extensionStart, .extension {\n fill: ").concat(t.lineColor," !important;\n stroke: ").concat(t.lineColor," !important;\n stroke-width: 1;\n}\n\n#extensionEnd, .extension {\n fill: ").concat(t.lineColor," !important;\n stroke: ").concat(t.lineColor," !important;\n stroke-width: 1;\n}\n\n#aggregationStart, .aggregation {\n fill: ").concat(t.mainBkg," !important;\n stroke: ").concat(t.lineColor," !important;\n stroke-width: 1;\n}\n\n#aggregationEnd, .aggregation {\n fill: ").concat(t.mainBkg," !important;\n stroke: ").concat(t.lineColor," !important;\n stroke-width: 1;\n}\n\n.edgeTerminals {\n font-size: 11px;\n}\n\n")},Io=function(t){return".label {\n font-family: ".concat(t.fontFamily,";\n color: ").concat(t.nodeTextColor||t.textColor,";\n }\n\n .label text {\n fill: ").concat(t.nodeTextColor||t.textColor,";\n }\n\n .node rect,\n .node circle,\n .node ellipse,\n .node polygon,\n .node path {\n fill: ").concat(t.mainBkg,";\n stroke: ").concat(t.nodeBorder,";\n stroke-width: 1px;\n }\n\n .node .label {\n text-align: center;\n }\n .node.clickable {\n cursor: pointer;\n }\n\n .arrowheadPath {\n fill: ").concat(t.arrowheadColor,";\n }\n\n .edgePath .path {\n stroke: ").concat(t.lineColor,";\n stroke-width: 1.5px;\n }\n\n .flowchart-link {\n stroke: ").concat(t.lineColor,";\n fill: none;\n }\n\n .edgeLabel {\n background-color: ").concat(t.edgeLabelBackground,";\n rect {\n opacity: 0.5;\n background-color: ").concat(t.edgeLabelBackground,";\n fill: ").concat(t.edgeLabelBackground,";\n }\n text-align: center;\n }\n\n .cluster rect {\n fill: ").concat(t.clusterBkg,";\n stroke: ").concat(t.clusterBorder,";\n stroke-width: 1px;\n }\n\n .cluster text {\n fill: ").concat(t.titleColor,";\n }\n\n div.mermaidTooltip {\n position: absolute;\n text-align: center;\n max-width: 200px;\n padding: 2px;\n font-family: ").concat(t.fontFamily,";\n font-size: 12px;\n background: ").concat(t.tertiaryColor,";\n border: 1px solid ").concat(t.border2,";\n border-radius: 2px;\n pointer-events: none;\n z-index: 100;\n }\n")},jo=function(t){return"g.stateGroup text {\n fill: ".concat(t.nodeBorder,";\n stroke: none;\n font-size: 10px;\n}\ng.stateGroup text {\n fill: ").concat(t.textColor,";\n stroke: none;\n font-size: 10px;\n\n}\ng.stateGroup .state-title {\n font-weight: bolder;\n fill: ").concat(t.labelColor,";\n}\n\ng.stateGroup rect {\n fill: ").concat(t.mainBkg,";\n stroke: ").concat(t.nodeBorder,";\n}\n\ng.stateGroup line {\n stroke: ").concat(t.lineColor,";\n stroke-width: 1;\n}\n\n.transition {\n stroke: ").concat(t.lineColor,";\n stroke-width: 1;\n fill: none;\n}\n\n.stateGroup .composit {\n fill: ").concat(t.background,";\n border-bottom: 1px\n}\n\n.stateGroup .alt-composit {\n fill: #e0e0e0;\n border-bottom: 1px\n}\n\n.state-note {\n stroke: ").concat(t.noteBorderColor,";\n fill: ").concat(t.noteBkgColor,";\n\n text {\n fill: black;\n stroke: none;\n font-size: 10px;\n }\n}\n\n.stateLabel .box {\n stroke: none;\n stroke-width: 0;\n fill: ").concat(t.mainBkg,";\n opacity: 0.5;\n}\n\n.edgeLabel .label rect {\n fill: ").concat(t.tertiaryColor,";\n opacity: 0.5;\n}\n.edgeLabel .label text {\n fill: ").concat(t.tertiaryTextColor,";\n}\n.label div .edgeLabel {\n color: ").concat(t.tertiaryTextColor,";\n}\n\n.stateLabel text {\n fill: ").concat(t.labelColor,";\n font-size: 10px;\n font-weight: bold;\n}\n\n.node circle.state-start {\n fill: ").concat(t.lineColor,";\n stroke: black;\n}\n.node circle.state-end {\n fill: ").concat(t.primaryBorderColor,";\n stroke: ").concat(t.background,";\n stroke-width: 1.5\n}\n.end-state-inner {\n fill: ").concat(t.background,";\n // stroke: ").concat(t.background,";\n stroke-width: 1.5\n}\n\n.node rect {\n fill: ").concat(t.mainBkg,";\n stroke: ").concat(t.nodeBorder,";\n stroke-width: 1px;\n}\n#statediagram-barbEnd {\n fill: ").concat(t.lineColor,";\n}\n\n.statediagram-cluster rect {\n fill: ").concat(t.mainBkg,";\n stroke: ").concat(t.nodeBorder,";\n stroke-width: 1px;\n}\n\n.cluster-label, .nodeLabel {\n color: ").concat(t.textColor,";\n}\n\n.statediagram-cluster rect.outer {\n rx: 5px;\n ry: 5px;\n}\n.statediagram-state .divider {\n stroke: ").concat(t.nodeBorder,";\n}\n\n.statediagram-state .title-state {\n rx: 5px;\n ry: 5px;\n}\n.statediagram-cluster.statediagram-cluster .inner {\n fill: ").concat(t.background,";\n}\n.statediagram-cluster.statediagram-cluster-alt .inner {\n fill: #e0e0e0;\n}\n\n.statediagram-cluster .inner {\n rx:0;\n ry:0;\n}\n\n.statediagram-state rect.basic {\n rx: 5px;\n ry: 5px;\n}\n.statediagram-state rect.divider {\n stroke-dasharray: 10,10;\n fill: ").concat(t.altBackground?t.altBackground:"#efefef",";\n}\n\n.note-edge {\n stroke-dasharray: 5;\n}\n\n.statediagram-note rect {\n fill: ").concat(t.noteBkgColor,";\n stroke: ").concat(t.noteBorderColor,";\n stroke-width: 1px;\n rx: 0;\n ry: 0;\n}\n.statediagram-note rect {\n fill: ").concat(t.noteBkgColor,";\n stroke: ").concat(t.noteBorderColor,";\n stroke-width: 1px;\n rx: 0;\n ry: 0;\n}\n\n.statediagram-note text {\n fill: ").concat(t.noteTextColor,";\n}\n\n.statediagram-note .nodeLabel {\n color: ").concat(t.noteTextColor,";\n}\n\n#dependencyStart, #dependencyEnd {\n fill: ").concat(t.lineColor,";\n stroke: ").concat(t.lineColor,";\n stroke-width: 1;\n}\n")},Ro={flowchart:Io,"flowchart-v2":Io,sequence:function(t){return".actor {\n stroke: ".concat(t.actorBorder,";\n fill: ").concat(t.actorBkg,";\n }\n\n text.actor > tspan {\n fill: ").concat(t.actorTextColor,";\n stroke: none;\n }\n\n .actor-line {\n stroke: ").concat(t.actorLineColor,";\n }\n\n .messageLine0 {\n stroke-width: 1.5;\n stroke-dasharray: none;\n stroke: ").concat(t.signalColor,";\n }\n\n .messageLine1 {\n stroke-width: 1.5;\n stroke-dasharray: 2, 2;\n stroke: ").concat(t.signalColor,";\n }\n\n #arrowhead path {\n fill: ").concat(t.signalColor,";\n stroke: ").concat(t.signalColor,";\n }\n\n .sequenceNumber {\n fill: ").concat(t.sequenceNumberColor,";\n }\n\n #sequencenumber {\n fill: ").concat(t.signalColor,";\n }\n\n #crosshead path {\n fill: ").concat(t.signalColor,";\n stroke: ").concat(t.signalColor,";\n }\n\n .messageText {\n fill: ").concat(t.signalTextColor,";\n stroke: ").concat(t.signalTextColor,";\n }\n\n .labelBox {\n stroke: ").concat(t.labelBoxBorderColor,";\n fill: ").concat(t.labelBoxBkgColor,";\n }\n\n .labelText, .labelText > tspan {\n fill: ").concat(t.labelTextColor,";\n stroke: none;\n }\n\n .loopText, .loopText > tspan {\n fill: ").concat(t.loopTextColor,";\n stroke: none;\n }\n\n .loopLine {\n stroke-width: 2px;\n stroke-dasharray: 2, 2;\n stroke: ").concat(t.labelBoxBorderColor,";\n fill: ").concat(t.labelBoxBorderColor,";\n }\n\n .note {\n //stroke: #decc93;\n stroke: ").concat(t.noteBorderColor,";\n fill: ").concat(t.noteBkgColor,";\n }\n\n .noteText, .noteText > tspan {\n fill: ").concat(t.noteTextColor,";\n stroke: none;\n }\n\n .activation0 {\n fill: ").concat(t.activationBkgColor,";\n stroke: ").concat(t.activationBorderColor,";\n }\n\n .activation1 {\n fill: ").concat(t.activationBkgColor,";\n stroke: ").concat(t.activationBorderColor,";\n }\n\n .activation2 {\n fill: ").concat(t.activationBkgColor,";\n stroke: ").concat(t.activationBorderColor,";\n }\n")},gantt:function(t){return'\n .mermaid-main-font {\n font-family: "trebuchet ms", verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n }\n\n .section {\n stroke: none;\n opacity: 0.2;\n }\n\n .section0 {\n fill: '.concat(t.sectionBkgColor,";\n }\n\n .section2 {\n fill: ").concat(t.sectionBkgColor2,";\n }\n\n .section1,\n .section3 {\n fill: ").concat(t.altSectionBkgColor,";\n opacity: 0.2;\n }\n\n .sectionTitle0 {\n fill: ").concat(t.titleColor,";\n }\n\n .sectionTitle1 {\n fill: ").concat(t.titleColor,";\n }\n\n .sectionTitle2 {\n fill: ").concat(t.titleColor,";\n }\n\n .sectionTitle3 {\n fill: ").concat(t.titleColor,";\n }\n\n .sectionTitle {\n text-anchor: start;\n font-size: 11px;\n text-height: 14px;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n\n }\n\n\n /* Grid and axis */\n\n .grid .tick {\n stroke: ").concat(t.gridColor,";\n opacity: 0.8;\n shape-rendering: crispEdges;\n text {\n font-family: ").concat(t.fontFamily,";\n fill: ").concat(t.textColor,";\n }\n }\n\n .grid path {\n stroke-width: 0;\n }\n\n\n /* Today line */\n\n .today {\n fill: none;\n stroke: ").concat(t.todayLineColor,";\n stroke-width: 2px;\n }\n\n\n /* Task styling */\n\n /* Default task */\n\n .task {\n stroke-width: 2;\n }\n\n .taskText {\n text-anchor: middle;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n }\n\n .taskText:not([font-size]) {\n font-size: 11px;\n }\n\n .taskTextOutsideRight {\n fill: ").concat(t.taskTextDarkColor,";\n text-anchor: start;\n font-size: 11px;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n\n }\n\n .taskTextOutsideLeft {\n fill: ").concat(t.taskTextDarkColor,";\n text-anchor: end;\n font-size: 11px;\n }\n\n /* Special case clickable */\n .task.clickable {\n cursor: pointer;\n }\n .taskText.clickable {\n cursor: pointer;\n fill: ").concat(t.taskTextClickableColor," !important;\n font-weight: bold;\n }\n\n .taskTextOutsideLeft.clickable {\n cursor: pointer;\n fill: ").concat(t.taskTextClickableColor," !important;\n font-weight: bold;\n }\n\n .taskTextOutsideRight.clickable {\n cursor: pointer;\n fill: ").concat(t.taskTextClickableColor," !important;\n font-weight: bold;\n }\n\n /* Specific task settings for the sections*/\n\n .taskText0,\n .taskText1,\n .taskText2,\n .taskText3 {\n fill: ").concat(t.taskTextColor,";\n }\n\n .task0,\n .task1,\n .task2,\n .task3 {\n fill: ").concat(t.taskBkgColor,";\n stroke: ").concat(t.taskBorderColor,";\n }\n\n .taskTextOutside0,\n .taskTextOutside2\n {\n fill: ").concat(t.taskTextOutsideColor,";\n }\n\n .taskTextOutside1,\n .taskTextOutside3 {\n fill: ").concat(t.taskTextOutsideColor,";\n }\n\n\n /* Active task */\n\n .active0,\n .active1,\n .active2,\n .active3 {\n fill: ").concat(t.activeTaskBkgColor,";\n stroke: ").concat(t.activeTaskBorderColor,";\n }\n\n .activeText0,\n .activeText1,\n .activeText2,\n .activeText3 {\n fill: ").concat(t.taskTextDarkColor," !important;\n }\n\n\n /* Completed task */\n\n .done0,\n .done1,\n .done2,\n .done3 {\n stroke: ").concat(t.doneTaskBorderColor,";\n fill: ").concat(t.doneTaskBkgColor,";\n stroke-width: 2;\n }\n\n .doneText0,\n .doneText1,\n .doneText2,\n .doneText3 {\n fill: ").concat(t.taskTextDarkColor," !important;\n }\n\n\n /* Tasks on the critical line */\n\n .crit0,\n .crit1,\n .crit2,\n .crit3 {\n stroke: ").concat(t.critBorderColor,";\n fill: ").concat(t.critBkgColor,";\n stroke-width: 2;\n }\n\n .activeCrit0,\n .activeCrit1,\n .activeCrit2,\n .activeCrit3 {\n stroke: ").concat(t.critBorderColor,";\n fill: ").concat(t.activeTaskBkgColor,";\n stroke-width: 2;\n }\n\n .doneCrit0,\n .doneCrit1,\n .doneCrit2,\n .doneCrit3 {\n stroke: ").concat(t.critBorderColor,";\n fill: ").concat(t.doneTaskBkgColor,";\n stroke-width: 2;\n cursor: pointer;\n shape-rendering: crispEdges;\n }\n\n .milestone {\n transform: rotate(45deg) scale(0.8,0.8);\n }\n\n .milestoneText {\n font-style: italic;\n }\n .doneCritText0,\n .doneCritText1,\n .doneCritText2,\n .doneCritText3 {\n fill: ").concat(t.taskTextDarkColor," !important;\n }\n\n .activeCritText0,\n .activeCritText1,\n .activeCritText2,\n .activeCritText3 {\n fill: ").concat(t.taskTextDarkColor," !important;\n }\n\n .titleText {\n text-anchor: middle;\n font-size: 18px;\n fill: ").concat(t.textColor," ;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n }\n")},classDiagram:Po,"classDiagram-v2":Po,class:Po,stateDiagram:jo,state:jo,git:function(){return"\n .commit-id,\n .commit-msg,\n .branch-label {\n fill: lightgrey;\n color: lightgrey;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n }\n"},info:function(){return""},pie:function(t){return".pieTitleText {\n text-anchor: middle;\n font-size: 25px;\n fill: ".concat(t.taskTextDarkColor,";\n font-family: ").concat(t.fontFamily,";\n }\n .slice {\n font-family: ").concat(t.fontFamily,";\n fill: ").concat(t.textColor,";\n // fill: white;\n }\n .legend text {\n fill: ").concat(t.taskTextDarkColor,";\n font-family: ").concat(t.fontFamily,";\n font-size: 17px;\n }\n")},er:function(t){return"\n .entityBox {\n fill: ".concat(t.mainBkg,";\n stroke: ").concat(t.nodeBorder,";\n }\n\n .attributeBoxOdd {\n fill: #ffffff;\n stroke: ").concat(t.nodeBorder,";\n }\n\n .attributeBoxEven {\n fill: #f2f2f2;\n stroke: ").concat(t.nodeBorder,";\n }\n\n .relationshipLabelBox {\n fill: ").concat(t.tertiaryColor,";\n opacity: 0.7;\n background-color: ").concat(t.tertiaryColor,";\n rect {\n opacity: 0.5;\n }\n }\n\n .relationshipLine {\n stroke: ").concat(t.lineColor,";\n }\n")},journey:function(t){return".label {\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n color: ".concat(t.textColor,";\n }\n .mouth {\n stroke: #666;\n }\n\n line {\n stroke: ").concat(t.textColor,"\n }\n\n .legend {\n fill: ").concat(t.textColor,";\n }\n\n .label text {\n fill: #333;\n }\n .label {\n color: ").concat(t.textColor,"\n }\n\n .face {\n fill: #FFF8DC;\n stroke: #999;\n }\n\n .node rect,\n .node circle,\n .node ellipse,\n .node polygon,\n .node path {\n fill: ").concat(t.mainBkg,";\n stroke: ").concat(t.nodeBorder,";\n stroke-width: 1px;\n }\n\n .node .label {\n text-align: center;\n }\n .node.clickable {\n cursor: pointer;\n }\n\n .arrowheadPath {\n fill: ").concat(t.arrowheadColor,";\n }\n\n .edgePath .path {\n stroke: ").concat(t.lineColor,";\n stroke-width: 1.5px;\n }\n\n .flowchart-link {\n stroke: ").concat(t.lineColor,";\n fill: none;\n }\n\n .edgeLabel {\n background-color: ").concat(t.edgeLabelBackground,";\n rect {\n opacity: 0.5;\n }\n text-align: center;\n }\n\n .cluster rect {\n }\n\n .cluster text {\n fill: ").concat(t.titleColor,";\n }\n\n div.mermaidTooltip {\n position: absolute;\n text-align: center;\n max-width: 200px;\n padding: 2px;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n font-size: 12px;\n background: ").concat(t.tertiaryColor,";\n border: 1px solid ").concat(t.border2,";\n border-radius: 2px;\n pointer-events: none;\n z-index: 100;\n }\n\n .task-type-0, .section-type-0 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType0):"",";\n }\n .task-type-1, .section-type-1 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType1):"",";\n }\n .task-type-2, .section-type-2 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType2):"",";\n }\n .task-type-3, .section-type-3 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType3):"",";\n }\n .task-type-4, .section-type-4 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType4):"",";\n }\n .task-type-5, .section-type-5 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType5):"",";\n }\n .task-type-6, .section-type-6 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType6):"",";\n }\n .task-type-7, .section-type-7 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType7):"",";\n }\n")}},Yo=function(t,e,n){return" {\n font-family: ".concat(n.fontFamily,";\n font-size: ").concat(n.fontSize,";\n fill: ").concat(n.textColor,"\n }\n\n /* Classes common for multiple diagrams */\n\n .error-icon {\n fill: ").concat(n.errorBkgColor,";\n }\n .error-text {\n fill: ").concat(n.errorTextColor,";\n stroke: ").concat(n.errorTextColor,";\n }\n\n .edge-thickness-normal {\n stroke-width: 2px;\n }\n .edge-thickness-thick {\n stroke-width: 3.5px\n }\n .edge-pattern-solid {\n stroke-dasharray: 0;\n }\n\n .edge-pattern-dashed{\n stroke-dasharray: 3;\n }\n .edge-pattern-dotted {\n stroke-dasharray: 2;\n }\n\n .marker {\n fill: ").concat(n.lineColor,";\n }\n .marker.cross {\n stroke: ").concat(n.lineColor,";\n }\n\n svg {\n font-family: ").concat(n.fontFamily,";\n font-size: ").concat(n.fontSize,";\n }\n\n ").concat(Ro[t](n),"\n\n ").concat(e,"\n\n ").concat(t," { fill: apa;}\n")};function zo(t){return(zo="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var Uo={},$o=function(t,e,n){switch(c.debug("Directive type=".concat(e.type," with args:"),e.args),e.type){case"init":case"initialize":["config"].forEach((function(t){void 0!==e.args[t]&&("flowchart-v2"===n&&(n="flowchart"),e.args[n]=e.args[t],delete e.args[t])})),e.args,wt(e.args);break;case"wrap":case"nowrap":t&&t.setWrap&&t.setWrap("wrap"===e.type);break;default:c.warn("Unhandled directive: source: '%%{".concat(e.type,": ").concat(JSON.stringify(e.args?e.args:{}),"}%%"),e)}};function Wo(t){ka(t.git),me(t.flowchart),Ln(t.flowchart),void 0!==t.sequenceDiagram&&_r.setConf(I(t.sequence,t.sequenceDiagram)),_r.setConf(t.sequence),ri(t.gantt),li(t.class),zi(t.state),qi(t.state),Oa(t.class),za(t.class),ro(t.er),Lo(t.journey),Ba(t.class)}function Ho(){}var Vo=Object.freeze({render:function(t,e,n,r){Et();var i=e,a=H.detectInit(i);a&&wt(a);var o=_t();if(e.length>o.maxTextSize&&(i="graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa"),void 0!==r)r.innerHTML="",Object(d.select)(r).append("div").attr("id","d"+t).attr("style","font-family: "+o.fontFamily).append("svg").attr("id",t).attr("width","100%").attr("xmlns","http://www.w3.org/2000/svg").append("g");else{var s=document.getElementById(t);s&&s.remove();var u=document.querySelector("#d"+t);u&&u.remove(),Object(d.select)("body").append("div").attr("id","d"+t).append("svg").attr("id",t).attr("width","100%").attr("xmlns","http://www.w3.org/2000/svg").append("g")}window.txt=i,i=function(t){var e=t;return e=(e=(e=e.replace(/style.*:\S*#.*;/g,(function(t){return t.substring(0,t.length-1)}))).replace(/classDef.*:\S*#.*;/g,(function(t){return t.substring(0,t.length-1)}))).replace(/#\w+;/g,(function(t){var e=t.substring(1,t.length-1);return/^\+?\d+$/.test(e)?"fl°°"+e+"¶ß":"fl°"+e+"¶ß"}))}(i);var l=Object(d.select)("#d"+t).node(),h=H.detectType(i),g=l.firstChild,y=g.firstChild,v="";if(void 0!==o.themeCSS&&(v+="\n".concat(o.themeCSS)),void 0!==o.fontFamily&&(v+="\n:root { --mermaid-font-family: ".concat(o.fontFamily,"}")),void 0!==o.altFontFamily&&(v+="\n:root { --mermaid-alt-font-family: ".concat(o.altFontFamily,"}")),"flowchart"===h||"flowchart-v2"===h||"graph"===h){var m=be(i);for(var b in m)v+="\n.".concat(b," > * { ").concat(m[b].styles.join(" !important; ")," !important; }"),m[b].textStyles&&(v+="\n.".concat(b," tspan { ").concat(m[b].textStyles.join(" !important; ")," !important; }"))}var x=(new f.a)("#".concat(t),Yo(h,v,o.themeVariables)),_=document.createElement("style");_.innerHTML=x,g.insertBefore(_,y);try{switch(h){case"git":o.flowchart.arrowMarkerAbsolute=o.arrowMarkerAbsolute,ka(o.git),wa(i,t,!1);break;case"flowchart":o.flowchart.arrowMarkerAbsolute=o.arrowMarkerAbsolute,me(o.flowchart),xe(i,t,!1);break;case"flowchart-v2":o.flowchart.arrowMarkerAbsolute=o.arrowMarkerAbsolute,Ln(o.flowchart),Fn(i,t,!1);break;case"sequence":o.sequence.arrowMarkerAbsolute=o.arrowMarkerAbsolute,o.sequenceDiagram?(_r.setConf(Object.assign(o.sequence,o.sequenceDiagram)),console.error("`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.")):_r.setConf(o.sequence),_r.draw(i,t);break;case"gantt":o.gantt.arrowMarkerAbsolute=o.arrowMarkerAbsolute,ri(o.gantt),ii(i,t);break;case"class":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,li(o.class),hi(i,t);break;case"classDiagram":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,di(o.class),pi(i,t);break;case"state":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,zi(o.state),Ui(i,t);break;case"stateDiagram":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,qi(o.state),Xi(i,t);break;case"info":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,Oa(o.class),Da(i,t,p.version);break;case"pie":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,za(o.pie),Ua(i,t,p.version);break;case"er":ro(o.er),io(i,t,p.version);break;case"journey":Lo(o.journey),Fo(i,t,p.version)}}catch(e){throw La(t,p.version),e}Object(d.select)('[id="'.concat(t,'"]')).selectAll("foreignobject > *").attr("xmlns","http://www.w3.org/1999/xhtml");var k=Object(d.select)("#d"+t).node().innerHTML;if(c.debug("cnf.arrowMarkerAbsolute",o.arrowMarkerAbsolute),o.arrowMarkerAbsolute&&"false"!==o.arrowMarkerAbsolute||(k=k.replace(/marker-end="url\(.*?#/g,'marker-end="url(#',"g")),k=function(t){var e=t;return e=(e=(e=e.replace(/fl°°/g,(function(){return"&#"}))).replace(/fl°/g,(function(){return"&"}))).replace(/¶ß/g,(function(){return";"}))}(k),void 0!==n)switch(h){case"flowchart":case"flowchart-v2":n(k,Xt.bindFunctions);break;case"gantt":n(k,Qr.bindFunctions);break;case"class":case"classDiagram":n(k,cn.bindFunctions);break;default:n(k)}else c.debug("CB = undefined!");var w=Object(d.select)("#d"+t).node();return null!==w&&"function"==typeof w.remove&&Object(d.select)("#d"+t).node().remove(),k},parse:function(t){var e=H.detectInit(t);e&&c.debug("reinit ",e);var n,r=H.detectType(t);switch(c.debug("Type "+r),r){case"git":(n=ha.a).parser.yy=ua;break;case"flowchart":case"flowchart-v2":Xt.clear(),(n=Jt.a).parser.yy=Xt;break;case"sequence":(n=Hn.a).parser.yy=sr;break;case"gantt":(n=wr.a).parser.yy=Qr;break;case"class":case"classDiagram":(n=oi.a).parser.yy=cn;break;case"state":case"stateDiagram":(n=Di.a).parser.yy=Mi;break;case"info":c.debug("info info info"),(n=Sa.a).parser.yy=Ca;break;case"pie":c.debug("pie"),(n=Ra.a).parser.yy=Ia;break;case"er":c.debug("er"),(n=Xa.a).parser.yy=Ga;break;case"journey":c.debug("Journey"),(n=oo.a).parser.yy=go}return n.parser.yy.graphType=r,n.parser.yy.parseError=function(t,e){throw{str:t,hash:e}},n.parse(t),n},parseDirective:function(t,e,n,r){try{if(void 0!==e)switch(e=e.trim(),n){case"open_directive":Uo={};break;case"type_directive":Uo.type=e.toLowerCase();break;case"arg_directive":Uo.args=JSON.parse(e);break;case"close_directive":$o(t,Uo,r),Uo=null}}catch(t){c.error("Error while rendering sequenceDiagram directive: ".concat(e," jison context: ").concat(n)),c.error(t.message)}},initialize:function(t){t&&t.fontFamily&&(t.themeVariables&&t.themeVariables.fontFamily||(t.themeVariables={fontFamily:t.fontFamily})),dt=I({},t),t&&t.theme&&ht[t.theme]?t.themeVariables=ht[t.theme].getThemeVariables(t.themeVariables):t&&(t.themeVariables=ht.default.getThemeVariables(t.themeVariables));var e="object"===zo(t)?function(t){return yt=I({},gt),yt=I(yt,t),t.theme&&(yt.themeVariables=ht[t.theme].getThemeVariables(t.themeVariables)),mt=bt(yt,vt),yt}(t):xt();Wo(e),u(e.logLevel)},reinitialize:Ho,getConfig:_t,setConfig:function(t){return I(mt,t),_t()},getSiteConfig:xt,updateSiteConfig:function(t){return yt=I(yt,t),bt(yt,vt),yt},reset:function(){Et()},globalReset:function(){Et(),Wo(_t())},defaultConfig:gt});u(_t().logLevel),Et(_t());var Go=Vo,qo=function(){Xo.startOnLoad?Go.getConfig().startOnLoad&&Xo.init():void 0===Xo.startOnLoad&&(c.debug("In start, no config"),Go.getConfig().startOnLoad&&Xo.init())};"undefined"!=typeof document&&
+/*!
+ * Wait for document loaded before starting the execution
+ */
+window.addEventListener("load",(function(){qo()}),!1);var Xo={startOnLoad:!0,htmlLabels:!0,mermaidAPI:Go,parse:Go.parse,render:Go.render,init:function(){var t,e,n=this,r=Go.getConfig();arguments.length>=2?(
+/*! sequence config was passed as #1 */
+void 0!==arguments[0]&&(Xo.sequenceConfig=arguments[0]),t=arguments[1]):t=arguments[0],"function"==typeof arguments[arguments.length-1]?(e=arguments[arguments.length-1],c.debug("Callback function found")):void 0!==r.mermaid&&("function"==typeof r.mermaid.callback?(e=r.mermaid.callback,c.debug("Callback function found")):c.debug("No Callback function found")),t=void 0===t?document.querySelectorAll(".mermaid"):"string"==typeof t?document.querySelectorAll(t):t instanceof window.Node?[t]:t,c.debug("Start On Load before: "+Xo.startOnLoad),void 0!==Xo.startOnLoad&&(c.debug("Start On Load inner: "+Xo.startOnLoad),Go.updateSiteConfig({startOnLoad:Xo.startOnLoad})),void 0!==Xo.ganttConfig&&Go.updateSiteConfig({gantt:Xo.ganttConfig});for(var a,o=H.initIdGeneratior(r.deterministicIds,r.deterministicIDSeed).next,s=function(r){var s=t[r];
+/*! Check if previously processed */if(s.getAttribute("data-processed"))return"continue";s.setAttribute("data-processed",!0);var u="mermaid-".concat(o());a=i(a=s.innerHTML).trim().replace(/<br\s*\/?>/gi,"<br/>");var l=H.detectInit(a);l&&c.debug("Detected early reinit: ",l);try{Go.render(u,a,(function(t,n){s.innerHTML=t,void 0!==e&&e(u),n&&n(s)}),s)}catch(t){c.warn("Syntax Error rendering"),c.warn(t),n.parseError&&n.parseError(t)}},u=0;u<t.length;u++)s(u)},initialize:function(t){void 0!==t.mermaid&&(void 0!==t.mermaid.startOnLoad&&(Xo.startOnLoad=t.mermaid.startOnLoad),void 0!==t.mermaid.htmlLabels&&(Xo.htmlLabels=t.mermaid.htmlLabels)),Go.initialize(t)},contentLoaded:qo};e.default=Xo}]).default}));
+//# sourceMappingURL=mermaid.min.js.map \ No newline at end of file
diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md
new file mode 100644
index 000000000..46be9180f
--- /dev/null
+++ b/docs/book/src/SUMMARY.md
@@ -0,0 +1,27 @@
+# Summary
+
+- [Introduction](./introduction.md)
+- [Building Crosvm](./building_crosvm/index.md)
+ - [For Linux](./building_crosvm/linux.md)
+ - [For Chromium OS](./building_crosvm/chromium_os.md)
+- [Running Crosvm](./running_crosvm/index.md)
+ - [Example Usage](./running_crosvm/example_usage.md)
+ - [Advanced Usage](./running_crosvm/advanced_usage.md)
+ - [Custom Kernel / Rootfs](./running_crosvm/custom_kernel_rootfs.md)
+ - [System Requirements](./running_crosvm/requirements.md)
+ - [Features](./running_crosvm/features.md)
+ - [Devices](./running_crosvm/devices.md)
+- [Architecture](./architecture.md)
+- [Contributing](./contributing.md)
+
+______________________________________________________________________
+
+- [Onboarding Resources](./onboarding.md)
+- [Appendix](./appendix/index.md)
+ - [Sandboxing](./appendix/sandboxing.md)
+ - [Seccomp](./appendix/seccomp.md)
+ - [Minijail](./appendix/minijail.md)
+
+______________________________________________________________________
+
+[API Documentation](./api.md)
diff --git a/docs/book/src/api.md b/docs/book/src/api.md
new file mode 100644
index 000000000..0969c8fc1
--- /dev/null
+++ b/docs/book/src/api.md
@@ -0,0 +1,4 @@
+# API Document
+
+The API documentation generated by `cargo doc` is available
+[here](https://google.github.io/crosvm/doc/crosvm/).
diff --git a/docs/book/src/appendix/index.md b/docs/book/src/appendix/index.md
new file mode 100644
index 000000000..e3d60ec3f
--- /dev/null
+++ b/docs/book/src/appendix/index.md
@@ -0,0 +1,4 @@
+# Appendix
+
+The following sections contain reference material you may find useful when working on crosvm. Note
+that some of contents might be outdated.
diff --git a/docs/book/src/appendix/minijail.md b/docs/book/src/appendix/minijail.md
new file mode 100644
index 000000000..9a4890655
--- /dev/null
+++ b/docs/book/src/appendix/minijail.md
@@ -0,0 +1,25 @@
+# Minijail
+
+On Linux hosts, crosvm uses [minijail](https://google.github.io/minijail/) to sandbox the child
+devices. The minijail C library is utilized via a
+[Rust wrapper](https://android.googlesource.com/platform/external/minijail/+/refs/heads/master/rust/minijail/src/lib.rs)
+so as not to repeat the intricate sequence of syscalls used to make a secure isolated child process.
+The fact that minijail was written, maintained, and continuously tested by a professional security
+team more than makes up for its being written in an memory unsafe language.
+
+The exact configuration of the sandbox varies by device, but they are mostly alike. See
+`create_base_minijail` from `linux/jail_helpers.rs`. The set of security constraints explicitly used
+in crosvm are:
+
+- PID Namespace
+ - Runs as init
+- [Deny setgroups](https://lwn.net/Articles/626665/)
+- Optional limit the capabilities mask to `0`
+- User namespace
+ - Optional uid/gid mapping
+- Mount namespace
+ - Optional pivot into a new root
+- Network namespace
+- [PR_SET_NO_NEW_PRIVS](https://www.kernel.org/doc/Documentation/prctl/no_new_privs.txt)
+- [seccomp](seccomp.html) with optional log failure mode
+- Limit to number of file descriptors
diff --git a/docs/rust-vmm.md b/docs/book/src/appendix/rust-vmm.md
index c593ed5b0..d26ef2046 100644
--- a/docs/rust-vmm.md
+++ b/docs/book/src/appendix/rust-vmm.md
@@ -1,4 +1,5 @@
# Summary
+
crosvm is open to using rust-vmm modules. However, as of Fall 2020 there has been no progress toward
that goal. Among other areas, differences in host operating system support methods in `sys-util`
make integration challenging . It is possible to overcome this and enable crosvm to use common
@@ -9,9 +10,9 @@ modules, but that work is not yet started.
## VMMs
Soon after crosvm's code was public, Amazon used it as the basis for their own VMM named
-Firecracker. After Firecracker came other rust-based VMM implementations, all using parts of
-crosvm. In order to drive collaboration and code sharing, an independent organization was created,
-named [rust-vmm](https://github.com/rust-vmm).
+Firecracker. After Firecracker came other rust-based VMM implementations, all using parts of crosvm.
+In order to drive collaboration and code sharing, an independent organization was created, named
+[rust-vmm](https://github.com/rust-vmm).
## Sharing Model
@@ -21,6 +22,7 @@ individual VMM implementation. The goal is for several VMM projects, Firecracker
and crosvm to use the shared components.
## Future
+
crosvm and rust-vmm are most alike in the
[kvm-bindings](https://github.com/rust-vmm/kvm-bindings)(limited by crosvm's use of aarch64 bindings
on arm32 hosts), and [vmm-sys-util](https://github.com/rust-vmm/vmm-sys-util)(currently limited by
diff --git a/docs/book/src/appendix/sandboxing.md b/docs/book/src/appendix/sandboxing.md
new file mode 100644
index 000000000..ffda4ed67
--- /dev/null
+++ b/docs/book/src/appendix/sandboxing.md
@@ -0,0 +1,41 @@
+# Sandboxing
+
+```mermaid
+%%{init: {'theme':'base'}}%%
+graph BT
+ subgraph guest
+ subgraph guest_kernel
+ virtio_blk_driver
+ virtio_net_driver
+ end
+ end
+ subgraph crosvm Process
+ vcpu0:::vcpu
+ vcpu1:::vcpu
+ subgraph device_proc0[Device Process]
+ virtio_blk --- virtio_blk_driver
+ disk_fd[(Disk FD)]
+ end
+ subgraph device_proc1[Device Process]
+ virtio_net --- virtio_net_driver
+ tapfd{{TAP FD}}
+ end
+ end
+ subgraph kernel[Host Kernel]
+ KVM --- vcpu1 & vcpu0
+ end
+ style KVM fill:#4285f4
+ classDef vcpu fill:#7890cd
+ classDef system fill:#fff,stroke:#777;
+ class crosvm,guest,kernel system;
+ style guest_kernel fill:#d23369,stroke:#777
+```
+
+Generally speaking, sandboxing is achieved in crosvm by isolating each virtualized devices into its
+own process. A process is always somewhat isolated from another by virtue of being in a different
+address space. Depending on the operating system, crosvm will use additional measures to sandbox the
+child processes of crosvm by limiting each process to just what it needs to function.
+
+In the example diagram above, the virtio block device exists as a child process of crosvm. It has
+been limited to having just the FD needed to access the backing file on the host and has no ability
+to open new files. A similar setup exists for other devices like virtio net.
diff --git a/docs/book/src/appendix/seccomp.md b/docs/book/src/appendix/seccomp.md
new file mode 100644
index 000000000..bb24ea244
--- /dev/null
+++ b/docs/book/src/appendix/seccomp.md
@@ -0,0 +1,30 @@
+# Seccomp
+
+The seccomp system is used to filter the syscalls that sandboxed processes can use. The form of
+seccomp used by crosvm (`SECCOMP_SET_MODE_FILTER`) allows for a BPF program to be used. To generate
+the BPF programs, crosvm uses minijail's policy file format. A policy file is written for each
+device per architecture. Each device requires a unique set of syscalls to accomplish their function
+and each architecture has slightly different naming for similar syscalls. The Chrome OS docs have a
+useful
+[listing of syscalls](https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md).
+
+## Writing a Policy for crosvm
+
+Most policy files will include the `common_device.policy` from a given architecture using this
+directive near the top:
+
+```
+@include /usr/share/policy/crosvm/common_device.policy
+```
+
+The common device policy for `x86_64` is:
+
+```
+{{#include ../../../../seccomp/x86_64/common_device.policy:5:}}
+```
+
+The syntax is simple: one syscall per line, followed by a colon `:`, followed by a boolean
+expression used to constrain the arguments of the syscall. The simplest expression is `1` which
+unconditionally allows the syscall. Only simple expressions work, often to allow or deny specific
+flags. A major limitation is that checking the contents of pointers isn't possible using minijail's
+policy format. If a syscall is not listed in a policy file, it is not allowed.
diff --git a/docs/book/src/architecture.md b/docs/book/src/architecture.md
new file mode 100644
index 000000000..4708086a4
--- /dev/null
+++ b/docs/book/src/architecture.md
@@ -0,0 +1 @@
+{{#include ../../../ARCHITECTURE.md}}
diff --git a/docs/book/src/building_crosvm/chromium_os.md b/docs/book/src/building_crosvm/chromium_os.md
new file mode 100644
index 000000000..a444f3f3d
--- /dev/null
+++ b/docs/book/src/building_crosvm/chromium_os.md
@@ -0,0 +1,27 @@
+# Building for ChromeOS
+
+crosvm is included in the ChromeOS source tree at `src/platform/crosvm`. Crosvm can be built with
+ChromeOS features using Portage or cargo.
+
+If ChromeOS-specific features are not needed, or you want to run the full test suite of crosvm, the
+[Building for Linux](#building-for-linux) workflows can be used from the crosvm repository of
+ChromeOS as well.
+
+## Using Portage
+
+crosvm on ChromeOS is usually built with Portage, so it follows the same general workflow as any
+`cros_workon` package. The full package name is `chromeos-base/crosvm`.
+
+See the [Chromium OS developer guide] for more on how to build and deploy with Portage.
+
+> NOTE: `cros_workon_make` modifies crosvm's Cargo.toml and Cargo.lock. Please be careful not to
+> commit the changes. Moreover, with the changes cargo will fail to build and clippy preupload check
+> will fail.
+
+## Using Cargo
+
+Since development using portage can be slow, it's possible to build crosvm for ChromeOS using cargo
+for faster iteration times. To do so, the `Cargo.toml` file needs to be updated to point to
+dependencies provided by ChromeOS using `./tools/chromeos/setup_cargo`.
+
+[chromium os developer guide]: https://chromium.googlesource.com/chromiumos/docs/+/HEAD/developer_guide.md
diff --git a/docs/book/src/building_crosvm/index.md b/docs/book/src/building_crosvm/index.md
new file mode 100644
index 000000000..6eddc7896
--- /dev/null
+++ b/docs/book/src/building_crosvm/index.md
@@ -0,0 +1,3 @@
+# Building Crosvm
+
+This chapter includes how to set up crosvm on each platform.
diff --git a/docs/book/src/building_crosvm/linux.md b/docs/book/src/building_crosvm/linux.md
new file mode 100644
index 000000000..47070a645
--- /dev/null
+++ b/docs/book/src/building_crosvm/linux.md
@@ -0,0 +1,190 @@
+# Building for Linux
+
+## Checking out
+
+Obtain the source code via git clone.
+
+```sh
+git clone https://chromium.googlesource.com/chromiumos/platform/crosvm
+```
+
+## Setting up the development environment
+
+Crosvm uses submodules to manage external dependencies. Initialize them via:
+
+```sh
+git submodule update --init
+```
+
+It is recommended to enable automatic recursive operations to keep the submodules in sync with the
+main repository (But do not push them, as that can conflict with `repo`):
+
+```sh
+git config submodule.recurse true
+git config push.recurseSubmodules no
+```
+
+Crosvm development best works on Debian derivatives. First install rust via https://rustup.rs/. Then
+for the rest, we provide a script to install the necessary packages on Debian:
+
+```sh
+./tools/install-deps
+```
+
+For other systems, please see below for instructions on
+[Using the development container](#using-the-development-container).
+
+### Setting up for cross-compilation
+
+Crosvm is built and tested on x86, aarch64 and armhf. Your host needs to be set up to allow
+installation of foreign architecture packages.
+
+On Debian this is as easy as:
+
+```sh
+sudo dpkg --add-architecture arm64
+sudo dpkg --add-architecture armhf
+sudo apt update
+```
+
+On ubuntu this is a little harder and needs some
+[manual modifications](https://askubuntu.com/questions/430705/how-to-use-apt-get-to-download-multi-arch-library)
+of APT sources.
+
+For other systems (**including gLinux**), please see below for instructions on
+[Using the development container](#using-the-development-container).
+
+With that enabled, the following scripts will install the needed packages:
+
+```sh
+./tools/install-aarch64-deps
+./tools/install-armhf-deps
+```
+
+### Using the development container
+
+We provide a Debian container with the required packages installed. With
+[Docker installed](https://docs.docker.com/get-docker/), it can be started with:
+
+```sh
+./tools/dev_container
+```
+
+The container image is big and may take a while to download when first used. Once started, you can
+follow all instructions in this document within the container shell.
+
+Instead of using the interactive shell, commands to execute can be provided directly:
+
+```sh
+./tools/dev_container cargo build
+```
+
+Note: The container and build artifacts are preserved between calls to `./tools/dev_container`. If
+you wish to start fresh, use the `--reset` flag.
+
+## Building a binary
+
+If you simply want to try crosvm, run `cargo build`. Then the binary is generated at
+`./target/debug/crosvm`. Now you can move to [Example Usage](../running_crosvm/example_usage.md).
+
+If you want to enable [additional features](../running_crosvm/features.md), use the `--features`
+flag. (e.g. `cargo build --features=gdb`)
+
+## Development
+
+### Iterative development
+
+You can use cargo as usual for crosvm development to `cargo build` and `cargo test` single crates
+that you are working on.
+
+If you are working on aarch64 specific code, you can use the `set_test_target` tool to instruct
+cargo to build for aarch64 and run tests on a VM:
+
+```sh
+./tools/set_test_target vm:aarch64 && source .envrc
+cd mycrate && cargo test
+```
+
+The script will start a VM for testing and write environment variables for cargo to `.envrc`. With
+those `cargo build` will build for aarch64 and `cargo test` will run tests inside the VM.
+
+The aarch64 VM can be managed with the `./tools/aarch64vm` script.
+
+### Running all tests
+
+Crosvm cannot use `cargo test --workspace` because of various restrictions of cargo. So we have our
+own test runner:
+
+```sh
+./tools/run_tests
+```
+
+Which will run all tests locally. Since we have some architecture-dependent code, we also have the
+option of running tests within an aarch64 VM:
+
+```sh
+./tools/run_tests --target=vm:aarch64
+```
+
+When working on a machine that does not support cross-compilation (e.g. gLinux), you can use the dev
+container to build and run the tests.
+
+```sh
+./tools/dev_container ./tools/run_tests --target=vm:aarch64
+```
+
+It is also possible to run tests on a remote machine via ssh. The target architecture is
+automatically detected:
+
+```sh
+./tools/run_tests --target=ssh:hostname
+```
+
+However, it is your responsibility to make sure the required libraries for crosvm are installed and
+password-less authentication is set up. See `./tools/impl/testvm/cloud_init.yaml` for hints on what
+the VM has installed.
+
+### Presubmit checks
+
+To verify changes before submitting, use the `presubmit` script:
+
+```sh
+./tools/presubmit
+```
+
+This will run clippy, formatters and runs all tests. The presubmits will use the dev container to
+build for other platforms if your host is not set up to do so.
+
+To run checks faster, they can be run in parallel in multiple tmux panes:
+
+```sh
+./tools/presubmit --tmux
+```
+
+The `--quick` variant will skip some slower checks, like building for other platforms altogether:
+
+```sh
+./tools/presubmit --quick
+```
+
+## Known issues
+
+- By default, crosvm is running devices in sandboxed mode, which requires seccomp policy files to be
+ set up. For local testing it is often easier to `--disable-sandbox` to run everything in a single
+ process.
+- If your Linux header files are too old, you may find minijail rejecting seccomp filters for
+ containing unknown syscalls. You can try removing the offending lines from the filter file, or add
+ `--seccomp-log-failures` to the crosvm command line to turn these into warnings. Note that this
+ option will also stop minijail from killing processes that violate the seccomp rule, making the
+ sandboxing much less aggressive.
+- Seccomp policy files have hardcoded absolute paths. You can either fix up the paths locally, or
+ set up an awesome hacky symlink:
+ `sudo mkdir /usr/share/policy && sudo ln -s /path/to/crosvm/seccomp/x86_64 /usr/share/policy/crosvm`.
+ We'll eventually build the precompiled policies
+ [into the crosvm binary](http://crbug.com/1052126).
+- Devices can't be jailed if `/var/empty` doesn't exist. `sudo mkdir -p /var/empty` to work around
+ this for now.
+- You need read/write permissions for `/dev/kvm` to run tests or other crosvm instances. Usually
+ it's owned by the `kvm` group, so `sudo usermod -a -G kvm $USER` and then log out and back in
+ again to fix this.
+- Some other features (networking) require `CAP_NET_ADMIN` so those usually need to be run as root.
diff --git a/docs/book/src/contributing.md b/docs/book/src/contributing.md
new file mode 100644
index 000000000..9cdba4c60
--- /dev/null
+++ b/docs/book/src/contributing.md
@@ -0,0 +1 @@
+{{#include ../../../CONTRIBUTING.md}}
diff --git a/docs/book/src/introduction.md b/docs/book/src/introduction.md
new file mode 100644
index 000000000..9bc8369d9
--- /dev/null
+++ b/docs/book/src/introduction.md
@@ -0,0 +1,22 @@
+# Introduction
+
+The crosvm project is a hosted (a.k.a.
+[type-2](https://en.wikipedia.org/wiki/Hypervisor#Classification)) virtual machine monitor.
+
+crosvm runs untrusted operating systems along with virtualized devices. Initially intended to be
+used with KVM and Linux, crosvm supports multiple kinds of hypervisors. crosvm is focussed on safety
+within the programming language and a sandbox around the virtual devices to protect the host from
+attack in case of exploits in crosvm itself.
+
+Other programs similar to crosvm are QEMU and VirtualBox. An operating system, made of a root file
+system image and a kernel binary, are given as input to crosvm and then crosvm will run the
+operating system using the platform's hypervisor.
+
+- [Source code](https://chromium.googlesource.com/chromiumos/platform/crosvm/)
+ - [GitHub mirror](https://github.com/google/crosvm)
+ - [API documentation](https://google.github.io/crosvm/doc/crosvm/), useful for searching API.
+ - Files for this book are under
+ [/docs/](https://chromium.googlesource.com/chromiumos/platform/crosvm/+/HEAD/docs/).
+- [Issue tracker](https://bugs.chromium.org/p/chromium/issues/list?q=component:OS%3ESystems%3EContainers)
+
+![logo](./logo.svg)
diff --git a/docs/book/src/logo.svg b/docs/book/src/logo.svg
new file mode 100644
index 000000000..25140a9b0
--- /dev/null
+++ b/docs/book/src/logo.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 135.467 135.467" height="512" width="512">
+ <path d="M117.55 62.332l3.259-.35 3.512.478 6.928 3.007V70l-6.928 3.008-3.512.478-3.258-.35-1.435 7.675 3.165.85 3.102 1.715 5.373 5.307-1.637 4.226-7.546.302-3.448-.823-2.911-1.504-4.111 6.639 2.644 1.936 2.273 2.72 3.094 6.89-3.053 3.349-7.146-2.445-2.918-2.012-2.172-2.455-6.231 4.706 1.767 2.76 1.137 3.357.395 7.543-4.056 2.02-5.78-4.861-1.995-2.93-1.138-3.074-7.51 2.137.65 3.212-.153 3.541-2.355 7.176-4.513.418-3.634-6.62-.8-3.454.048-3.277-7.775-.72-.554 3.23-1.421 3.247-4.79 5.84-4.358-1.24-.997-7.486.5-3.51 1.23-3.037-6.99-3.48-1.683 2.811-2.499 2.514-6.575 3.716-3.616-2.73 1.774-7.342 1.735-3.09 2.244-2.39-5.261-5.77-2.586 2.013-3.237 1.442-7.474 1.09-2.386-3.853 4.307-6.204 2.734-2.256 2.955-1.417-2.82-7.281-3.139.944-3.54.175-7.363-1.684-.832-4.454 6.257-4.23 3.364-1.116 3.268-.253v-7.809l-3.268-.254-3.364-1.115-6.257-4.23.832-4.454 7.363-1.684 3.54.175 3.139.944 2.82-7.282-2.955-1.417-2.734-2.255-4.307-6.205 2.386-3.853 7.474 1.09 3.237 1.442 2.586 2.014 5.26-5.77-2.243-2.39-1.735-3.09-1.774-7.342 3.616-2.73 6.575 3.715 2.499 2.515 1.683 2.812 6.99-3.481-1.23-3.038-.5-3.509.997-7.486 4.359-1.24 4.789 5.84 1.42 3.247.555 3.23 7.775-.72-.049-3.278.8-3.453 3.635-6.62 4.513.418 2.355 7.176.153 3.54-.65 3.213 7.51 2.137 1.138-3.073 1.994-2.931 5.78-4.86 4.057 2.02-.395 7.541-1.137 3.357-1.767 2.76 6.231 4.707 2.172-2.455 2.918-2.012 7.146-2.445 3.053 3.35-3.094 6.89-2.273 2.719-2.644 1.936 4.11 6.639 2.912-1.505 3.448-.822 7.546.302 1.637 4.226-5.373 5.307-3.102 1.715-3.165.85z" fill="none" stroke-width="4.000059149999999" stroke="#045" stroke-opacity="1" />
+ <g stroke-width=".226">
+ <path d="M55.046 63.741q.75-.917 1.656-1.281.906-.375 2.23-.375 1.315 0 1.823.828.52.817.52 2.44v.067H59.55q0-.11-.01-.354-.012-.254-.023-.353 0-.1-.033-.276-.022-.188-.088-.254-.056-.077-.155-.166-.1-.1-.254-.132-.155-.033-.364-.033-.475 0-.895.11-.42.11-.916.398-.486.276-.95.872-.464.596-.862 1.48v6.692l-1.722-.01v-8.427q0-.1-.011-.188-.012-.1-.056-.188-.044-.099-.077-.165-.022-.078-.11-.166l-.122-.154q-.033-.056-.155-.166-.121-.11-.176-.155-.055-.055-.21-.187-.144-.133-.21-.188l1.149-1.557q.077.066.32.265.254.187.353.265.1.077.287.265.2.176.31.32.11.133.243.342.132.21.232.431zM69.888 71.03q.983-.928.983-3.479v-.199q-.022-1.656-.663-2.65-.64-.994-2.02-.994-.84 0-1.436.32-.586.32-.917.939-.331.618-.486 1.413-.143.785-.143 1.845 0 1.634.717 2.55.718.906 2.088.906.63-.01 1.038-.154.42-.144.84-.497zm-1.9 2.363q-2.208-.01-3.368-1.37-1.16-1.369-1.16-3.743 0-1.005.133-1.888.144-.895.464-1.701.331-.817.817-1.403.497-.585 1.248-.927.751-.354 1.69-.354 1.59 0 2.573.552.994.542 1.59 1.668.652 1.259.652 3.18 0 .994-.144 1.845-.132.84-.464 1.623-.33.773-.85 1.325-.508.542-1.314.873-.806.32-1.844.32zM77.331 67.12q-1.28-.684-1.28-2.363.01-1.347 1.082-2.032 1.07-.685 2.65-.685 1.16 0 2.363.398 1.204.386 1.834 1.16l-1.248 1.225q-.431-.497-1.248-.817-.806-.331-1.546-.331-.939 0-1.546.265-.608.265-.608.773 0 .386.155.607.165.221.508.376.475.199 1.104.43l1.071.376q.453.155.972.375.53.21.884.42.364.199.729.497.364.298.574.64.22.343.353.818.133.475.133 1.049 0 1.248-.718 2.043-.718.795-1.723 1.06-.95.221-1.91.221-2.077 0-3.159-1.016-1.082-1.027-1.082-2.85l1.734.012q0 2.275 2.474 2.275 1.104 0 1.888-.464.784-.475.784-1.336 0-.486-.22-.784-.222-.31-.73-.696-.452-.331-2.109-.85-1.646-.52-2.165-.796z" fill="#37abc8" fill-opacity="1" stroke-opacity="0" />
+ <path d="M87.768 61.83q.54.575.795 1.039.265.463.508 1.16l.784 2.086q.75 2 1.226 3.413.475 1.413.53 1.921 1.06-1.225 1.91-3.313.85-2.087.895-3.754.01-.31.01-1.072 0-.872-.021-1.07l1.678-.045q.056.298.056 2.142-.011 1.933-1.005 4.307-.994 2.375-2.275 3.976-.431.53-1.05.674-.607.143-1.678.143 0-1.005-1.05-4.152-1.048-3.147-1.855-5.146-.066-.177-.375-.608-.298-.442-.508-.64zM106.232 62.073q.564 0 .961.343.398.331.586.872.187.53.254 1.005.077.475.077.95v5.776q0 .386.31.696.308.298.706.452l-.674 1.47q-.32-.067-.63-.222-.309-.154-.607-.42-.287-.264-.475-.706-.176-.442-.176-.994v-6.427q0-1.105-.707-1.105-.872 0-1.79 1.999v7.642h-1.556v-8.9q0-.21-.022-.343-.022-.133-.155-.265-.121-.133-.353-.133-.453 0-.983.486-.52.486-.994 1.524v7.631h-1.557l-.033-9q0-1.17-.332-2.12l1.657-.442q.121.243.22.85.1.607.122 1.027.398-.685 1.038-1.16.652-.486 1.237-.486.762 0 1.226.508.475.508.475 1.138.894-1.646 2.175-1.646z" fill="#164450" fill-opacity="1" stroke-opacity="0" />
+ </g>
+ <g>
+ <path d="M76.46 80.897h9.666q-1.107 10.422-7.853 17.37-6.747 6.947-19.937 6.947-12.788 0-20.641-9.113-7.804-9.162-7.955-24.316V63.93q0-15.406 7.904-24.72 7.955-9.313 21.548-9.313 12.435 0 19.13 6.847 6.697 6.847 7.804 17.67H76.46q-1.107-7.652-4.934-12.082-3.826-4.48-12.334-4.48-9.717 0-14.751 7.148-5.035 7.15-5.035 18.83v7.4q0 10.824 4.582 18.477 4.581 7.602 14.348 7.602 9.263 0 13.04-4.33 3.826-4.33 5.084-12.083z" fill="#37abc8" fill-opacity="1" stroke-width="2.578" stroke-opacity="0" />
+ </g>
+</svg>
diff --git a/docs/book/src/onboarding.md b/docs/book/src/onboarding.md
new file mode 100644
index 000000000..c432a38aa
--- /dev/null
+++ b/docs/book/src/onboarding.md
@@ -0,0 +1,85 @@
+# Onboarding Resources
+
+Various links to useful resources for learning about virtual machines and the technology behind
+crosvm.
+
+## Talks
+
+### [Chrome University](https://www.youtube.com/watch?v=2Pc71zYWFDM) by zachr (2018, 30m)
+
+- Life of a Crostini VM (user click -> terminal opens)
+- All those French daemons (Concierge, Maitred, Garcon, Sommelier)
+
+### [NYULG: Crostini](https://www.youtube.com/watch?v=WwrXqDERFm8) by zachr / reveman (2018, 50m)
+
+- Overlaps Chrome University talk
+- More details on wayland / sommelier from reveman
+- More details on crostini integration of app icons, files, clipboard
+- Lots of demos
+
+## Introductory Resources
+
+### OS Basics
+
+- [OSDev Wiki](https://wiki.osdev.org/Main_Page) (A lot of articles on OS development)
+- [PCI Enumeration](https://www.khoury.northeastern.edu/~pjd/cs7680/homework/pci-enumeration.html)
+ (Most of our devices are on PCI, this is how they are found)
+- [ACPI Source Language Tutorial](https://acpica.org/sites/acpica/files/asl_tutorial_v20190625.pdf)
+
+### Rust
+
+- [Rust Cheat Sheet](https://cheats.rs/) Beautiful website with idiomatic rust examples, overview of
+ pointer- and container types
+- [Rust Programming Tipz](https://github.com/ferrous-systems/elements-of-rust) (with a z, that’s how
+ you know it’s cool!)
+- Rust [design patterns](https://github.com/rust-unofficial/patterns) repo
+- Organized [collection](https://github.com/brson/rust-anthology/blob/master/master-list.md) of blog
+ posts on various Rust topics
+
+### KVM Virtualization
+
+- [Low-level tutorial](https://lwn.net/Articles/658511/) on how to run code via KVM
+- [KVM Hello World](https://github.com/dpw/kvm-hello-world) sample program (host + guest)
+- [KVM API docs](https://www.kernel.org/doc/html/latest/virt/kvm/api.html)
+- [Awesome Virtualization](https://github.com/Wenzel/awesome-virtualization) (Definitely check out
+ the Hypervisor Development section)
+
+### Virtio (device emulation)
+
+- [Good overview](https://developer.ibm.com/technologies/linux/articles/l-virtio/) of virtio
+ architecture from IBM
+- [Virtio drivers](https://www.redhat.com/en/blog/virtio-devices-and-drivers-overview-headjack-and-phone)
+ overview by RedHat
+- [Virtio specs](https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html)
+ (so exciting, I can’t stop reading)
+- [Basics of devices in QEMU ](https://www.qemu.org/2018/02/09/understanding-qemu-devices/)
+
+### VFIO (Device passthrough)
+
+- [Introduction to PCI Device Assignment with VFIO](https://www.youtube.com/watch?v=WFkdTFTOTpA)
+
+### Virtualization History and Basics
+
+- By the end of this section you should be able to answer the following questions
+ - What problems do VMs solve?
+ - What is trap-and-emulate?
+ - Why was the x86 instruction set not “virtualizable” with just trap-and-emulate?
+ - What is binary translation? Why is it required?
+ - What is a hypervisor? What is a VMM? What is the difference? (If any)
+ - What problem does paravirtualization solve?
+ - What is the virtualization model we use with Crostini?
+ - What is our hypervisor?
+ - What is our VMM?
+- [CMU slides](http://www.cs.cmu.edu/~410-f06/lectures/L31_Virtualization.pdf) go over motivation,
+ why x86 instruction set wasn’t “virtualizable” and the good old trap-and-emulate
+- Why Intel VMX was needed; what does it do
+ ([Link](https://lettieri.iet.unipi.it/virtualization/2018/hardware-assisted-intel-vmx.pdf))
+- What is a VMM and what does it do ([Link](http://pages.cs.wisc.edu/~remzi/OSTEP/vmm-intro.pdf))
+- Building a super simple VMM blog article
+ ([Link](https://unixism.net/2019/10/sparkler-kvm-based-virtual-machine-manager/))
+
+## Relevant Specs
+
+- [ACPI Specs](https://uefi.org/acpi/specs)
+- [DeviceTree Specs](https://www.devicetree.org/specifications/)
+- [Vhost-user protocol](https://qemu-project.gitlab.io/qemu/interop/vhost-user.html)
diff --git a/docs/book/src/running_crosvm/advanced_usage.md b/docs/book/src/running_crosvm/advanced_usage.md
new file mode 100644
index 000000000..8c069cab3
--- /dev/null
+++ b/docs/book/src/running_crosvm/advanced_usage.md
@@ -0,0 +1,261 @@
+# Advanced Usage
+
+To see the usage information for your version of crosvm, run `crosvm` or `crosvm run --help`.
+
+## Boot a Kernel
+
+To run a very basic VM with just a kernel and default devices:
+
+```sh
+crosvm run "${KERNEL_PATH}"
+```
+
+The uncompressed kernel image, also known as vmlinux, can be found in your kernel build directory in
+the case of x86 at `arch/x86/boot/compressed/vmlinux`.
+
+## Rootfs
+
+### With a disk image
+
+In most cases, you will want to give the VM a virtual block device to use as a root file system:
+
+```sh
+crosvm run -r "${ROOT_IMAGE}" "${KERNEL_PATH}"
+```
+
+The root image must be a path to a disk image formatted in a way that the kernel can read. Typically
+this is a squashfs image made with `mksquashfs` or an ext4 image made with `mkfs.ext4`. By using the
+`-r` argument, the kernel is automatically told to use that image as the root, and therefore can
+only be given once. More disks can be given with `-d` or `--rwdisk` if a writable disk is desired.
+
+To run crosvm with a writable rootfs:
+
+> **WARNING:** Writable disks are at risk of corruption by a malicious or malfunctioning guest OS.
+
+```sh
+crosvm run --rwdisk "${ROOT_IMAGE}" -p "root=/dev/vda" vmlinux
+```
+
+> **NOTE:** If more disks arguments are added prior to the desired rootfs image, the `root=/dev/vda`
+> must be adjusted to the appropriate letter.
+
+### With virtiofs
+
+Linux kernel 5.4+ is required for using virtiofs. This is convenient for testing. The file system
+must be named "mtd\*" or "ubi\*".
+
+```sh
+crosvm run --shared-dir "/:mtdfake:type=fs:cache=always" \
+ -p "rootfstype=virtiofs root=mtdfake" vmlinux
+```
+
+## Network device
+
+The most convenient way to provide a network device to a guest is to setup a persistent TAP
+interface on the host. This section will explain how to do this for basic IPv4 connectivity.
+
+```sh
+sudo ip tuntap add mode tap user $USER vnet_hdr crosvm_tap
+sudo ip addr add 192.168.10.1/24 dev crosvm_tap
+sudo ip link set crosvm_tap up
+```
+
+These commands create a TAP interface named `crosvm_tap` that is accessible to the current user,
+configure the host to use the IP address `192.168.10.1`, and bring the interface up.
+
+The next step is to make sure that traffic from/to this interface is properly routed:
+
+```sh
+sudo sysctl net.ipv4.ip_forward=1
+# Network interface used to connect to the internet.
+HOST_DEV=$(ip route get 8.8.8.8 | awk -- '{printf $5}')
+sudo iptables -t nat -A POSTROUTING -o "${HOST_DEV}" -j MASQUERADE
+sudo iptables -A FORWARD -i "${HOST_DEV}" -o crosvm_tap -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i crosvm_tap -o "${HOST_DEV}" -j ACCEPT
+```
+
+The interface is now configured and can be used by crosvm:
+
+```sh
+crosvm run \
+ ...
+ --tap-name crosvm_tap \
+ ...
+```
+
+Provided the guest kernel had support for `VIRTIO_NET`, the network device should be visible and
+configurable from the guest:
+
+```sh
+# Replace with the actual network interface name of the guest
+# (use "ip addr" to list the interfaces)
+GUEST_DEV=enp0s5
+sudo ip addr add 192.168.10.2/24 dev "${GUEST_DEV}"
+sudo ip link set "${GUEST_DEV}" up
+sudo ip route add default via 192.168.10.1
+# "8.8.8.8" is chosen arbitrarily as a default, please replace with your local (or preferred global)
+# DNS provider, which should be visible in `/etc/resolv.conf` on the host.
+echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf
+```
+
+These commands assign IP address `192.168.10.2` to the guest, activate the interface, and route all
+network traffic to the host. The last line also ensures DNS will work.
+
+Please refer to your distribution's documentation for instructions on how to make these settings
+persistent for the host and guest if desired.
+
+## Control Socket
+
+If the control socket was enabled with `-s`, the main process can be controlled while crosvm is
+running. To tell crosvm to stop and exit, for example:
+
+> **NOTE:** If the socket path given is for a directory, a socket name underneath that path will be
+> generated based on crosvm's PID.
+
+```sh
+crosvm run -s /run/crosvm.sock ${USUAL_CROSVM_ARGS}
+ <in another shell>
+crosvm stop /run/crosvm.sock
+```
+
+> **WARNING:** The guest OS will not be notified or gracefully shutdown.
+
+This will cause the original crosvm process to exit in an orderly fashion, allowing it to clean up
+any OS resources that might have stuck around if crosvm were terminated early.
+
+## Multiprocess Mode
+
+By default crosvm runs in multiprocess mode. Each device that supports running inside of a sandbox
+will run in a jailed child process of crosvm. The appropriate minijail seccomp policy files must be
+present either in `/usr/share/policy/crosvm` or in the path specified by the `--seccomp-policy-dir`
+argument. The sandbox can be disabled for testing with the `--disable-sandbox` option.
+
+## Wayland forwarding to host
+
+If you have a Wayland compositor running on your host, it is possible to display and control guest
+applications from it. This requires:
+
+- A guest kernel version 5.16 or above with `CONFIG_DRM_VIRTIO_GPU` enabled,
+- The `sommelier` Wayland proxy in your guest image.
+
+This section will walk you through the steps needed to get this to work.
+
+### Guest kernel requirements
+
+Wayland support on crosvm relies on virtio-gpu contexts, which have been introduced in Linux 5.16.
+Make sure your guest kernel is either this version or a more recent one, and that
+`CONFIG_DRM_VIRTIO_GPU` is enabled in your kernel configuration.
+
+### Crosvm requirements
+
+Wayland forwarding requires the GPU feature and any non-2d virtio-gpu mode to be enabled, so pass
+them to your `cargo build` or `cargo run` command, e.g:
+
+```sh
+cargo build --features "gpu,virgl_renderer,virgl_renderer_next"
+```
+
+### Building sommelier
+
+[Sommelier] is a proxy Wayland compositor that forwards the Wayland protocol from a guest to a
+compositor running on the host through the guest GPU device. As it is not a standard tool, we will
+have to build it by ourselves. It is recommended to do this from the guest
+[with networking enabled](./example_usage.md#add-networking-support).
+
+Clone Chrome OS' `platform2` repository, which contains the source for sommelier:
+
+```sh
+git clone https://chromium.googlesource.com/chromiumos/platform2
+```
+
+Go into the sommelier directory and prepare for building:
+
+```sh
+cd platform2/vm_tools/sommelier/
+meson setup build -Dwith_tests=false
+```
+
+This setup step will check for all libraries required to build sommelier. If some are missing,
+install them using your guest's distro package manager and re-run `meson setup` until it passes.
+
+Finally, build sommelier and install it:
+
+```sh
+meson compile -C build
+sudo meson install -C build
+```
+
+This last step will put the `sommelier` binary into `/usr/local/bin`.
+
+### Running guest Wayland apps
+
+Crosvm can connect to a running Wayland server (e.g. [weston]) on the host and forward the protocol
+from all Wayland guest applications to it. To enable this you need to know the socket of the Wayland
+server running on your host - typically it would be `$XDG_RUNTIME_DIR/wayland-0`.
+
+Once you have confirmed the socket, create a GPU device and enable forwarding by adding the
+`--gpu --wayland-sock $XDG_RUNTIME_DIR/wayland-0` arguments to your crosvm command-line.
+
+You can now run Wayland clients through sommelier, e.g:
+
+```sh
+sommelier --virtgpu-channel weston-terminal
+```
+
+Or
+
+```sh
+sommelier --virtgpu-channel gedit
+```
+
+Applications started that way should appear on and be controllable from the Wayland server running
+on your host.
+
+The `--virtgpu-channel` option is currently necessary for sommelier to work with the setup of this
+document, but will likely not be required in the future.
+
+If you have `Xwayland` installed in the guest you can also run X applications:
+
+```sh
+sommelier -X --xwayland-path=/usr/bin/Xwayland xeyes
+```
+
+## GDB Support
+
+crosvm supports [GDB Remote Serial Protocol] to allow developers to debug guest kernel via GDB.
+
+You can enable the feature by `--gdb` flag:
+
+```sh
+# Use uncompressed vmlinux
+crosvm run --gdb <port> ${USUAL_CROSVM_ARGS} vmlinux
+```
+
+Then, you can start GDB in another shell.
+
+```sh
+gdb vmlinux
+(gdb) target remote :<port>
+(gdb) hbreak start_kernel
+(gdb) c
+<start booting in the other shell>
+```
+
+For general techniques for debugging the Linux kernel via GDB, see this [kernel documentation].
+
+## Defaults
+
+The following are crosvm's default arguments and how to override them.
+
+- 256MB of memory (set with `-m`)
+- 1 virtual CPU (set with `-c`)
+- no block devices (set with `-r`, `-d`, or `--rwdisk`)
+- no network (set with `--host_ip`, `--netmask`, and `--mac`)
+- only the kernel arguments necessary to run with the supported devices (add more with `-p`)
+- run in multiprocess mode (run in single process mode with `--disable-sandbox`)
+- no control socket (set with `-s`)
+
+[gdb remote serial protocol]: https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html
+[kernel documentation]: https://www.kernel.org/doc/html/latest/dev-tools/gdb-kernel-debugging.html
+[sommelier]: https://chromium.googlesource.com/chromiumos/platform2/+/master/vm_tools/sommelier
+[weston]: https://github.com/wayland-project/weston
diff --git a/docs/book/src/running_crosvm/custom_kernel_rootfs.md b/docs/book/src/running_crosvm/custom_kernel_rootfs.md
new file mode 100644
index 000000000..7e4800046
--- /dev/null
+++ b/docs/book/src/running_crosvm/custom_kernel_rootfs.md
@@ -0,0 +1,67 @@
+# Custom Kernel / Rootfs
+
+This document explains how to build a custom kernel and use debootstrab to build a rootfs for
+running crosvm.
+
+For an easier way to get started with prebuilt images, see [Example Usage](./example_usage.md)
+
+### Build a kernel
+
+The linux kernel in chromiumos comes preconfigured for running in a crosvm guest and is the easiest
+to build. You can use any mainline kernel though as long as it's configured for para-virtualized
+(virtio) devices
+
+If you are using the chroot for Chromium OS development, you already have the kernel source.
+Otherwise, you can clone it:
+
+```bash
+git clone --depth 1 -b chromeos-5.10 https://chromium.googlesource.com/chromiumos/third_party/kernel
+```
+
+Either way that you get the kernel, the next steps are to configure and build the bzImage:
+
+```bash
+CHROMEOS_KERNEL_FAMILY=termina ./chromeos/scripts/prepareconfig container-vm-x86_64
+make olddefconfig
+make -j$(nproc) bzImage
+```
+
+This kernel does not build any modules, nor does it support loading them, so there is no need to
+worry about an initramfs, although they are supported in crosvm.
+
+### Build a rootfs disk
+
+This stage enjoys the most flexibility. There aren't any special requirements for a rootfs in
+crosvm, but you will at a minimum need an init binary. This could even be `/bin/bash` if that is
+enough for your purposes. To get you started, a Debian rootfs can be created with [debootstrap].
+Make sure to define `$CHROOT_PATH`.
+
+```bash
+truncate -s 20G debian.ext4
+mkfs.ext4 debian.ext4
+mkdir -p "${CHROOT_PATH}"
+sudo mount debian.ext4 "${CHROOT_PATH}"
+sudo debootstrap stable "${CHROOT_PATH}" http://deb.debian.org/debian/
+sudo chroot "${CHROOT_PATH}"
+passwd
+echo "tmpfs /tmp tmpfs defaults 0 0" >> /etc/fstab
+echo "tmpfs /var/log tmpfs defaults 0 0" >> /etc/fstab
+echo "tmpfs /root tmpfs defaults 0 0" >> /etc/fstab
+echo "sysfs /sys sysfs defaults 0 0" >> /etc/fstab
+echo "proc /proc proc defaults 0 0" >> /etc/fstab
+exit
+sudo umount "${CHROOT_PATH}"
+```
+
+> Note: If you run crosvm on a testing device (e.g. Chromebook in Developer mode), another option is
+> to share the host's rootfs with the guest via virtiofs. See the
+> [virtiofs usage](./advanced_usage.md#virtiofs-as-rootfs).
+
+You can simply create a disk image as follows:
+
+```bash
+fallocate --length 4G disk.img
+mkfs.ext4 ./disk.img
+```
+
+[debootstrap]: https://wiki.debian.org/Debootstrap
diff --git a/docs/book/src/running_crosvm/devices.md b/docs/book/src/running_crosvm/devices.md
new file mode 100644
index 000000000..027ab79e4
--- /dev/null
+++ b/docs/book/src/running_crosvm/devices.md
@@ -0,0 +1,55 @@
+# Devices
+
+This document lists emulated devices in crosvm.
+
+## Emulated Devices
+
+- [`CMOS/RTC`] - Used to get the current calendar time.
+- [`i8042`] - Used by the guest kernel to exit crosvm.
+- [`serial`] - x86 I/O port driven serial devices that print to stdout and take input from stdin.
+
+## VirtIO Devices
+
+- [`balloon`] - Allows the host to reclaim the guest's memories.
+- [`block`] - Basic read/write block device.
+- [`console`] - Input and outputs on console.
+- [`fs`] - Shares file systems over the FUSE protocol.
+- [`gpu`] - Graphics adapter.
+- [`input`] - Creates virtual human interface devices such as keyboards.
+- [`iommu`] - Emulates an IOMMU device to manage DMA from endpoints in the guest.
+- [`net`] - Device to interface the host and guest networks.
+- [`p9`] - Shares file systems over the 9P protocol.
+- [`pmem`] - Persistent memory.
+- [`rng`] - Entropy source used to seed guest OS's entropy pool.
+- [`snd`] - Encodes and decodes audio streams.
+- [`tpm`] - Creates a TPM (Trusted Platform Module) device backed by libtpm2 simulator.
+- [`video`] - Encodes and decodes video streams.
+- [`wayland`] - Allows the guest to use the host's Wayland socket.
+- [`vsock`] - Enables use of virtual sockets for the guest.
+- `vhost-user` - VirtIO devices which offloads the device implementation to another process through
+ the [vhost-user protocol].
+ - [vmm side]: Shares its virtqueues.
+ - [device side]: Consumes virtqueues.
+
+[device side]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/vhost/user/device/
+[vhost-user protocol]: https://qemu.readthedocs.io/en/latest/interop/vhost-user.html
+[vmm side]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/vhost/user/vmm/
+[`balloon`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/balloon.rs
+[`block`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/block/
+[`cmos/rtc`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/cmos.rs
+[`console`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/console.rs
+[`fs`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/fs/
+[`gpu`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/gpu/
+[`i8042`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/i8042.rs
+[`input`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/input/
+[`iommu`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/iommu.rs
+[`net`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/net.rs
+[`p9`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/p9.rs
+[`pmem`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/pmem.rs
+[`rng`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/rng.rs
+[`serial`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/serial.rs
+[`snd`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/snd/
+[`tpm`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/tpm.rs
+[`video`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/video/
+[`vsock`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/vhost/vsock.rs
+[`wayland`]: https://chromium.googlesource.com/chromiumos/platform/crosvm/+/refs/heads/main/devices/src/virtio/wl.rs
diff --git a/docs/book/src/running_crosvm/example_desktop.png b/docs/book/src/running_crosvm/example_desktop.png
new file mode 100644
index 000000000..70656f716
--- /dev/null
+++ b/docs/book/src/running_crosvm/example_desktop.png
Binary files differ
diff --git a/docs/book/src/running_crosvm/example_usage.md b/docs/book/src/running_crosvm/example_usage.md
new file mode 100644
index 000000000..4f7d5b3c4
--- /dev/null
+++ b/docs/book/src/running_crosvm/example_usage.md
@@ -0,0 +1,121 @@
+# Example Usage
+
+This section will explain how to use a prebuilt Ubuntu image as the guest OS. If you want to prepare
+a kernel and rootfs by yourself, please see [Building crosvm].
+
+The example code for this guide is available in [tools/examples]
+
+## Run a simple Guest OS (usig virt-builder)
+
+To run a VM with crosvm, we need two things: A kernel binary and a rootfs. You can
+[build those yourself](./custom_kernel_rootfs.md) or use prebuilt cloud/vm images that some linux
+distributions provide.
+
+### Preparing the guest OS image
+
+One of the more convenient ways to customize these VM images is to use [virt-builder] from the
+`libguestfs-tools` package.
+
+```bash
+{{#include ../../../../tools/examples/example_simple:build}}
+```
+
+### Extract the Kernel (And initrd)
+
+Crosvm directly runs the kernel instead of using the bootloader. So we need to extract the kernel
+binary from the image. [virt-builder] has a tool for that:
+
+```bash
+{{#include ../../../../tools/examples/example_simple:kernel}}
+```
+
+The kernel binary is going to be saved in the same directory.
+
+Note: Most distributions use an init ramdisk, which is extracted at the same time and needs to be
+passed to crosvm as well.
+
+### Launch the VM
+
+With all the files in place, crosvm can be run:
+
+```bash
+{{#include ../../../../tools/examples/example_simple:run}}
+```
+
+The full source for this example can be executed directly:
+
+```bash
+./tools/examples/example_simple
+```
+
+## Add Networking Support
+
+Networking support is easiest set up with a TAP device on the host, which can be done with:
+
+```bash
+./tools/examples/setup_network
+```
+
+The script will create a TAP device called `crosvm_tap` and sets up routing. For details, see the
+instructions for [network devices](./advanced_usage.md#network-device).
+
+With the `crosvm_tap` in place we can use it when running crosvm:
+
+```bash
+{{#include ../../../../tools/examples/example_network:run}}
+```
+
+To use the network device in the guest, we need to assign it a static IP address. In our example
+guest this can be done via a netplan config:
+
+```yaml
+{{#include ../../../../tools/examples/guest/01-netcfg.yaml:5:}}
+```
+
+Which can be installed when building the VM image:
+
+```bash
+{{#include ../../../../tools/examples/example_network:build}}
+```
+
+This also allows us to use SSH to access the VM. The script above will install your
+`~/.ssh/id_rsa.pub` into the VM, so you'll be able to SSH from the host to the guest with no
+password:
+
+```bash
+ssh 192.168.10.2
+```
+
+The full source for this example can be executed directly:
+
+```bash
+./tools/examples/example_network
+```
+
+## Add GUI support
+
+First you'll want to add some desktop environment to the VM image:
+
+```bash
+{{#include ../../../../tools/examples/example_desktop:build}}
+```
+
+Then you can use the `--gpu` argument to specify how gpu output of the VM should be handled. In this
+example we are using the virglrenderer backend and output into an X11 window on the host.
+
+```bash
+{{#include ../../../../tools/examples/example_desktop:run}}
+```
+
+![Desktop Example](./example_desktop.png)
+
+The full source for this example can be executed directly (Note, you may want to run
+[setup_networking](#add-networking-support) first):
+
+```bash
+./tools/examples/example_desktop
+```
+
+[building crosvm]: ../building_crosvm/index.md
+[tools/examples]: https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform/crosvm/tools/examples
+[virt-builder]: https://libguestfs.org/virt-builder.1.html
diff --git a/docs/book/src/running_crosvm/features.md b/docs/book/src/running_crosvm/features.md
new file mode 100644
index 000000000..70e81d6c3
--- /dev/null
+++ b/docs/book/src/running_crosvm/features.md
@@ -0,0 +1,90 @@
+# Features
+
+These features can be enabled using cargo's `--features` flag. Refer to the top-level `Cargo.toml`
+file to see which features are enabled by default.
+
+## `audio`
+
+Enables experimental audio input/ouput to the host. Requires some Chrome OS specific dependencies
+and daemons currently.
+
+## `chromeos`
+
+This option enables features specific to a Chrome OS environment. Examples of that are usage of
+non-upstream kernel security features in the Chrome OS kernel, which should be temporary until
+upstream catches up. Another example would be code to use Chrome OS system daemons like the low
+memory notifier.
+
+These features exist because crosvm was historically a Chrome OS only project, but crosvm is
+intended to be OS agnostic now. If Chrome OS specific code is identified, it should be conditionally
+compiled in using this feature.
+
+## `composite-disk`
+
+Enables the composite-disk format, which adds protobufs as a dependency of the build. This format is
+intended to speed up crosvm's usage in CI environments that might otherwise have to concatenate
+large file system images into a single disk image.
+
+## `default-no-sandbox`
+
+This feature is useful only in testing so that the `--disable-sandbox` flag doesn't need to be
+passed to crosvm every invocation. It is not secure to deploy crosvm with this flag.
+
+## `direct`
+
+Enables a set of features to passthrough devices to the guest via VFIO.
+
+## `gdb`
+
+Enables using gdb to debug the guest kernel.
+
+## `gfxstream`
+
+Enables 3D acceleration for guest via the `gfxstream` protocol over virtio-gpu. This is used for
+compatibility with the Android Emulator. The protocol provides the best speed and compatibility with
+GL/vulkan versions by forwarding the guest's calls to the host's graphics libraries and GPU.
+However, this means the sandbox is not enabled for the virtio-gpu device.
+
+## `gpu`
+
+Enables basic virtio-gpu support. This includes basic display and input features, but lacks 3D
+acceleration in the absence of other crosvm features.
+
+## `plugin`
+
+Enables the plugin mode of crosvm. The plugin mode delegates almost all device emulation to a
+sandboxed child process. Unless you know what you're doing, you almost certainly don't need this
+feature.
+
+## `power-monitor-powerd`
+
+Enables emulation of a battery using the host's power information provided by
+[powerd](https://chromium.googlesource.com/chromiumos/platform2/+/HEAD/power_manager/README.md).
+
+## `tpm`
+
+Enables trusted platform module emulation for the guest. This relies on the software emulated vTPM
+implementation from `libtpm2` which is suited only for testing purposes.
+
+## `usb`
+
+Enables USB host device passthrough via an emulated XHCI controller.
+
+## `video-decoder`/`video-encoder`
+
+Enables the unstable virtio video encoder or decoder devices.
+
+## `virgl_renderer`/`virgl_renderer_next`
+
+Enables 3D acceleration for the guest via the `virglrenderer` library over virtio-gpu. The
+`virgl_renderer_next` variant is used to enable in development features of `virglrenderer` to
+support newer OpenGL versions.
+
+## `wl`
+
+Enables the non-upstream virtio wayland protocol. This can be used in conjuction with the `gpu`
+feature to enable a zero-copy display pipeline.
+
+## `x`
+
+Enables the usage of the X11 protocol for display on the host.
diff --git a/docs/book/src/running_crosvm/index.md b/docs/book/src/running_crosvm/index.md
new file mode 100644
index 000000000..c8b516033
--- /dev/null
+++ b/docs/book/src/running_crosvm/index.md
@@ -0,0 +1,12 @@
+# Running Crosvm
+
+This chapter includes instructions on how to run crosvm.
+
+- [Example Usage](./example_usage.md): Functioning examples to get started.
+- [Advanced Usage](./advanced_usage.md): Details on how to enable and configure features and devices
+ of crosvm.
+- [Custom Kernel / Rootfs](./custom_kernel_rootfs.md): Instructions on how to build a kernel and
+ rootfs for crosvm.
+- [System Requirements](./requirements.md): Host and guest requirements for running crosvm
+- [Features](./features.md): Feature flags available when building crosvm
+- [Devices](./devices.md): Devices provided by crosvm
diff --git a/docs/book/src/running_crosvm/requirements.md b/docs/book/src/running_crosvm/requirements.md
new file mode 100644
index 000000000..296d8a63c
--- /dev/null
+++ b/docs/book/src/running_crosvm/requirements.md
@@ -0,0 +1,10 @@
+# System Requirements
+
+A Linux kernel with KVM support (check for `/dev/kvm`) is required to run crosvm. In order to run
+certain devices, there are additional system requirements:
+
+- `virtio-wayland` - The `memfd_create` syscall, introduced in Linux 3.17, and a Wayland compositor.
+- `vsock` - Host Linux kernel with vhost-vsock support, introduced in Linux 4.8.
+- `multiprocess` - Host Linux kernel with seccomp-bpf and Linux namespacing support.
+- `virtio-net` - Host Linux kernel with TUN/TAP support (check for `/dev/net/tun`) and running with
+ `CAP_NET_ADMIN` privileges.
diff --git a/enumn/Android.bp b/enumn/Android.bp
deleted file mode 100644
index 501a4f988..000000000
--- a/enumn/Android.bp
+++ /dev/null
@@ -1,48 +0,0 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Do not modify this file as changes will be overridden on upgrade.
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "external_crosvm_license"
- // to get the below license kinds:
- // SPDX-license-identifier-BSD
- default_applicable_licenses: ["external_crosvm_license"],
-}
-
-rust_test_host {
- name: "enumn_host_test_src_lib",
- defaults: ["crosvm_defaults"],
- crate_name: "enumn",
- srcs: ["src/lib.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libproc_macro2",
- "libquote",
- "libsyn",
- ],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_proc_macro {
- name: "libenumn",
- defaults: ["crosvm_proc_macro_defaults"],
- crate_name: "enumn",
- srcs: ["src/lib.rs"],
- edition: "2018",
- rustlibs: [
- "libproc_macro2",
- "libquote",
- "libsyn",
- ],
-}
-
-// dependent_library ["feature_list"]
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// syn-1.0.70 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// unicode-xid-0.2.1 "default"
diff --git a/enumn/src/lib.rs b/enumn/src/lib.rs
deleted file mode 100644
index faf9b475d..000000000
--- a/enumn/src/lib.rs
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright 2018 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-//! Convert number to enum.
-//!
-//! This crate provides a derive macro to generate a function for converting a
-//! primitive integer into the corresponding variant of an enum.
-//!
-//! The generated function is named `n` and has the following signature:
-//!
-//! ```rust
-//! # const IGNORE: &str = stringify! {
-//! impl YourEnum {
-//! pub fn n(value: Repr) -> Option<Self>;
-//! }
-//! # };
-//! ```
-//!
-//! where `Repr` is an integer type of the right size as described in more
-//! detail below.
-//!
-//! # Example
-//!
-//! ```rust
-//! use enumn::N;
-//!
-//! #[derive(PartialEq, Debug, N)]
-//! enum Status {
-//! LegendaryTriumph,
-//! QualifiedSuccess,
-//! FortuitousRevival,
-//! IndeterminateStalemate,
-//! RecoverableSetback,
-//! DireMisadventure,
-//! AbjectFailure,
-//! }
-//!
-//! let s = Status::n(1);
-//! assert_eq!(s, Some(Status::QualifiedSuccess));
-//!
-//! let s = Status::n(9);
-//! assert_eq!(s, None);
-//! ```
-//!
-//! # Signature
-//!
-//! The generated signature depends on whether the enum has a `#[repr(..)]`
-//! attribute. If a `repr` is specified, the input to `n` will be required to be
-//! of that type.
-//!
-//! ```ignore
-//! use enumn::N;
-//!
-//! #[derive(N)]
-//! #[repr(u8)]
-//! enum E {
-//! /* ... */
-//! # IGNORE
-//! }
-//!
-//! // expands to:
-//! impl E {
-//! pub fn n(value: u8) -> Option<Self> {
-//! /* ... */
-//! # unimplemented!()
-//! }
-//! }
-//! ```
-//!
-//! On the other hand if no `repr` is specified then we get a signature that is
-//! generic over a variety of possible types.
-//!
-//! ```ignore
-//! # enum E {}
-//! #
-//! impl E {
-//! pub fn n<REPR: Into<i64>>(value: REPR) -> Option<Self> {
-//! /* ... */
-//! # unimplemented!()
-//! }
-//! }
-//! ```
-//!
-//! # Discriminants
-//!
-//! The conversion respects explictly specified enum discriminants. Consider
-//! this enum:
-//!
-//! ```rust
-//! use enumn::N;
-//!
-//! #[derive(N)]
-//! enum Letter {
-//! A = 65,
-//! B = 66,
-//! }
-//! ```
-//!
-//! Here `Letter::n(65)` would return `Some(Letter::A)`.
-
-#![recursion_limit = "128"]
-
-extern crate proc_macro;
-
-#[cfg(test)]
-mod tests;
-
-use proc_macro::TokenStream;
-use quote::quote;
-use syn::parse::Error;
-use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, Meta, NestedMeta};
-
-fn testable_derive(input: DeriveInput) -> proc_macro2::TokenStream {
- let variants = match input.data {
- Data::Enum(data) => data.variants,
- Data::Struct(_) | Data::Union(_) => panic!("input must be an enum"),
- };
-
- for variant in &variants {
- match variant.fields {
- Fields::Unit => {}
- Fields::Named(_) | Fields::Unnamed(_) => {
- let span = variant.ident.span();
- let err = Error::new(span, "enumn: variant with data is not supported");
- return err.to_compile_error();
- }
- }
- }
-
- // Parse repr attribute like #[repr(u16)].
- let mut repr = None;
- for attr in input.attrs {
- if let Ok(Meta::List(list)) = attr.parse_meta() {
- if list.path.is_ident("repr") {
- if let Some(NestedMeta::Meta(Meta::Path(word))) = list.nested.into_iter().next() {
- if let Some(s) = word.get_ident() {
- match s.to_string().as_str() {
- "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16"
- | "i32" | "i64" | "i128" | "isize" => {
- repr = Some(word);
- }
- _ => {}
- }
- }
- }
- }
- }
- }
-
- let signature;
- let value;
- match &repr {
- Some(repr) => {
- signature = quote! {
- fn n(value: #repr)
- };
- value = quote!(value);
- }
- None => {
- repr = Some(parse_quote!(i64));
- signature = quote! {
- fn n<REPR: Into<i64>>(value: REPR)
- };
- value = quote! {
- <REPR as Into<i64>>::into(value)
- };
- }
- }
-
- let ident = input.ident;
- let declare_discriminants = variants.iter().map(|variant| {
- let variant = &variant.ident;
- quote! {
- const #variant: #repr = #ident::#variant as #repr;
- }
- });
- let match_discriminants = variants.iter().map(|variant| {
- let variant = &variant.ident;
- quote! {
- discriminant::#variant => Some(#ident::#variant),
- }
- });
-
- quote! {
- #[allow(non_upper_case_globals)]
- impl #ident {
- pub #signature -> Option<Self> {
- struct discriminant;
- impl discriminant {
- #(#declare_discriminants)*
- }
- match #value {
- #(#match_discriminants)*
- _ => None,
- }
- }
- }
- }
-}
-
-#[proc_macro_derive(N)]
-pub fn derive(input: TokenStream) -> TokenStream {
- let input = parse_macro_input!(input as DeriveInput);
- let expanded = testable_derive(input);
- TokenStream::from(expanded)
-}
diff --git a/enumn/src/tests.rs b/enumn/src/tests.rs
deleted file mode 100644
index cf5dd4207..000000000
--- a/enumn/src/tests.rs
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2018 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use quote::quote;
-use syn::{parse_quote, DeriveInput};
-
-#[test]
-fn test_repr() {
- let input: DeriveInput = parse_quote! {
- #[repr(u8)]
- enum E {
- A,
- B,
- C,
- }
- };
- let actual = crate::testable_derive(input);
- let expected = quote! {
- #[allow(non_upper_case_globals)]
- impl E {
- pub fn n(value: u8) -> Option<Self> {
- struct discriminant;
- impl discriminant {
- const A: u8 = E::A as u8;
- const B: u8 = E::B as u8;
- const C: u8 = E::C as u8;
- }
- match value {
- discriminant::A => Some(E::A),
- discriminant::B => Some(E::B),
- discriminant::C => Some(E::C),
- _ => None,
- }
- }
- }
- };
- assert_eq!(actual.to_string(), expected.to_string());
-}
-
-#[test]
-fn test_no_repr() {
- let input: DeriveInput = parse_quote! {
- enum E {
- A,
- B,
- C,
- }
- };
- let actual = crate::testable_derive(input);
- let expected = quote! {
- #[allow(non_upper_case_globals)]
- impl E {
- pub fn n<REPR: Into<i64>>(value: REPR) -> Option<Self> {
- struct discriminant;
- impl discriminant {
- const A: i64 = E::A as i64;
- const B: i64 = E::B as i64;
- const C: i64 = E::C as i64;
- }
- match <REPR as Into<i64>>::into(value) {
- discriminant::A => Some(E::A),
- discriminant::B => Some(E::B),
- discriminant::C => Some(E::C),
- _ => None,
- }
- }
- }
- };
- assert_eq!(actual.to_string(), expected.to_string());
-}
diff --git a/fuse/Android.bp b/fuse/Android.bp
index e9ed86b2f..95590f470 100644
--- a/fuse/Android.bp
+++ b/fuse/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -10,35 +10,32 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "fuse_defaults",
+rust_test {
+ name: "fuse_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "fuse",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2021",
rustlibs: [
"libbase_rust",
"libbitflags",
+ "libcrossbeam_utils",
"libdata_model",
"liblibc",
"libthiserror",
],
- proc_macros: ["libenumn"],
-}
-
-rust_test_host {
- name: "fuse_host_test_src_lib",
- defaults: ["fuse_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "fuse_device_test_src_lib",
- defaults: ["fuse_defaults"],
+ proc_macros: [
+ "libenumn",
+ "libremain",
+ ],
}
rust_library {
@@ -47,56 +44,20 @@ rust_library {
stem: "libfuse",
host_supported: true,
crate_name: "fuse",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
"libbitflags",
+ "libcrossbeam_utils",
"libdata_model",
"liblibc",
"libthiserror",
],
- proc_macros: ["libenumn"],
+ proc_macros: [
+ "libenumn",
+ "libremain",
+ ],
}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../enumn/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/fuse/Cargo.toml b/fuse/Cargo.toml
index 94226b55a..b47e7f9ea 100644
--- a/fuse/Cargo.toml
+++ b/fuse/Cargo.toml
@@ -2,7 +2,7 @@
name = "fuse"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[lib]
path = "src/lib.rs"
@@ -10,7 +10,9 @@ path = "src/lib.rs"
[dependencies]
base = { path = "../base" }
bitflags = "1"
-data_model = { path = "../data_model" }
-enumn = { path = "../enumn" }
+crossbeam-utils = "0.8"
+data_model = { path = "../common/data_model" }
+enumn = "0.1.0"
libc = "*"
+remain = "0.2"
thiserror = "1.0.20"
diff --git a/fuse/src/lib.rs b/fuse/src/lib.rs
index f7ac2e74a..74152d765 100644
--- a/fuse/src/lib.rs
+++ b/fuse/src/lib.rs
@@ -3,8 +3,10 @@
// found in the LICENSE file.
use std::ffi::FromBytesWithNulError;
+use std::fs::File;
use std::io;
+use remain::sorted;
use thiserror::Error as ThisError;
pub mod filesystem;
@@ -19,7 +21,10 @@ pub mod worker;
pub use mount::mount;
pub use server::{Mapper, Reader, Server, Writer};
+use filesystem::FileSystem;
+
/// Errors that may occur during the creation or operation of an Fs device.
+#[sorted]
#[derive(ThisError, Debug)]
pub enum Error {
/// A request is missing readable descriptors.
@@ -29,15 +34,12 @@ pub enum Error {
/// Failed to encode protocol messages.
#[error("failed to encode fuse message: {0}")]
EncodeMessage(io::Error),
- /// Failed to flush protocol messages.
- #[error("failed to flush fuse message: {0}")]
- FlushMessage(io::Error),
/// Failed to set up FUSE endpoint to talk with.
#[error("failed to set up FUSE endpoint to talk with: {0}")]
EndpointSetup(io::Error),
- /// One or more parameters are missing.
- #[error("one or more parameters are missing")]
- MissingParameter,
+ /// Failed to flush protocol messages.
+ #[error("failed to flush fuse message: {0}")]
+ FlushMessage(io::Error),
/// A C string parameter is invalid.
#[error("a c string parameter is invalid: {0}")]
InvalidCString(FromBytesWithNulError),
@@ -51,6 +53,12 @@ pub enum Error {
length of the decoded value: size = {0}, value.len() = {1}"
)]
InvalidXattrSize(u32, usize),
+ /// One or more parameters are missing.
+ #[error("one or more parameters are missing")]
+ MissingParameter,
+ /// Thread exited
+ #[error("Thread exited")]
+ ThreadExited,
/// Requested too many `iovec`s for an `ioctl` retry.
#[error(
"requested too many `iovec`s for an `ioctl` retry reply: requested\
@@ -60,3 +68,70 @@ pub enum Error {
}
pub type Result<T> = ::std::result::Result<T, Error>;
+
+#[derive(Default)]
+pub struct FuseConfig {
+ dev_fuse_file: Option<File>,
+ max_write_bytes: Option<u32>,
+ max_read_bytes: Option<u32>,
+ num_of_threads: Option<usize>,
+}
+
+impl FuseConfig {
+ pub fn new() -> Self {
+ FuseConfig {
+ ..Default::default()
+ }
+ }
+
+ /// Set the FUSE device.
+ pub fn dev_fuse(&mut self, file: File) -> &mut Self {
+ self.dev_fuse_file = Some(file);
+ self
+ }
+
+ /// Set the maximum data in a read request. Must be large enough (usually equal) to `n` in
+ /// `MountOption::MaxRead(n)`.
+ pub fn max_read(&mut self, bytes: u32) -> &mut Self {
+ self.max_read_bytes = Some(bytes);
+ self
+ }
+
+ /// Set the maximum data in a write request.
+ pub fn max_write(&mut self, bytes: u32) -> &mut Self {
+ self.max_write_bytes = Some(bytes);
+ self
+ }
+
+ /// Set the number of threads to run the `FileSystem`.
+ pub fn num_threads(&mut self, num: usize) -> &mut Self {
+ self.num_of_threads = Some(num);
+ self
+ }
+
+ pub fn enter_message_loop<F: FileSystem + Sync + Send>(self, fs: F) -> Result<()> {
+ let FuseConfig {
+ dev_fuse_file,
+ max_write_bytes,
+ max_read_bytes,
+ num_of_threads,
+ } = self;
+ let num = num_of_threads.unwrap_or(1);
+ if num == 1 {
+ worker::start_message_loop(
+ dev_fuse_file.ok_or(Error::MissingParameter)?,
+ max_read_bytes.ok_or(Error::MissingParameter)?,
+ max_write_bytes.ok_or(Error::MissingParameter)?,
+ fs,
+ )
+ } else {
+ worker::internal::start_message_loop_mt(
+ dev_fuse_file.ok_or(Error::MissingParameter)?,
+ max_read_bytes.ok_or(Error::MissingParameter)?,
+ max_write_bytes.ok_or(Error::MissingParameter)?,
+ num,
+ fs,
+ )
+ }
+ }
+}
diff --git a/fuse/src/mount.rs b/fuse/src/mount.rs
index 66276d4d4..427f69670 100644
--- a/fuse/src/mount.rs
+++ b/fuse/src/mount.rs
@@ -11,7 +11,7 @@ use std::os::unix::io::RawFd;
/// Mount options to pass to mount(2) for a FUSE filesystem. See the [official document](
/// https://www.kernel.org/doc/html/latest/filesystems/fuse.html#mount-options) for the
/// descriptions.
-pub enum MountOption {
+pub enum MountOption<'a> {
FD(RawFd),
RootMode(u32),
UserId(libc::uid_t),
@@ -20,10 +20,13 @@ pub enum MountOption {
AllowOther,
MaxRead(u32),
BlockSize(u32),
+ // General mount options that are not specific to FUSE. Note that the value is not checked
+ // or interpreted by this library, but by kernel.
+ Extra(&'a str),
}
// Implement Display for ToString to convert to actual mount options.
-impl fmt::Display for MountOption {
+impl<'a> fmt::Display for MountOption<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
MountOption::FD(fd) => write!(f, "fd={}", fd),
@@ -34,6 +37,7 @@ impl fmt::Display for MountOption {
MountOption::AllowOther => write!(f, "allow_other"),
MountOption::MaxRead(size) => write!(f, "max_read={}", size),
MountOption::BlockSize(size) => write!(f, "blksize={}", size),
+ MountOption::Extra(text) => write!(f, "{}", text),
}
}
}
@@ -122,5 +126,10 @@ mod tests {
MountOption::MaxRead(4096),
])
);
+
+ assert_eq!(
+ "option1=a,option2=b".to_string(),
+ join_mount_options(&[MountOption::Extra("option1=a,option2=b"),])
+ );
}
}
diff --git a/fuse/src/server.rs b/fuse/src/server.rs
index f28e9488a..4a6362e66 100644
--- a/fuse/src/server.rs
+++ b/fuse/src/server.rs
@@ -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::cmp::{max, min};
use std::convert::TryInto;
use std::ffi::CStr;
use std::io;
@@ -24,11 +25,17 @@ const DIRENT_PADDING: [u8; 8] = [0; 8];
/// A trait for reading from the underlying FUSE endpoint.
pub trait Reader: io::Read {}
+impl<R: Reader> Reader for &'_ mut R {}
+
/// A trait for writing to the underlying FUSE endpoint. The FUSE device expects the write
/// operation to happen in one write transaction. Since there are cases when data needs to be
/// generated earlier than the header, it implies the writer implementation to keep an internal
/// buffer. The buffer then can be flushed once header and data are both prepared.
pub trait Writer: io::Write {
+ /// The type passed in to the closure in `write_at`. For most implementations, this should be
+ /// `Self`.
+ type ClosureWriter: Writer + ZeroCopyWriter;
+
/// Allows a closure to generate and write data at the current writer's offset. The current
/// writer is passed as a mutable reference to the closure. As an example, this provides an
/// adapter for the read implementation of a filesystem to write directly to the final buffer
@@ -41,12 +48,27 @@ pub trait Writer: io::Write {
/// complexity.
fn write_at<F>(&mut self, offset: usize, f: F) -> io::Result<usize>
where
- F: Fn(&mut Self) -> io::Result<usize>;
+ F: Fn(&mut Self::ClosureWriter) -> io::Result<usize>;
/// Checks if the writer can still accept certain amount of data.
fn has_sufficient_buffer(&self, size: u32) -> bool;
}
+impl<W: Writer> Writer for &'_ mut W {
+ type ClosureWriter = W::ClosureWriter;
+
+ fn write_at<F>(&mut self, offset: usize, f: F) -> io::Result<usize>
+ where
+ F: Fn(&mut Self::ClosureWriter) -> io::Result<usize>,
+ {
+ (**self).write_at(offset, f)
+ }
+
+ fn has_sufficient_buffer(&self, size: u32) -> bool {
+ (**self).has_sufficient_buffer(size)
+ }
+}
+
/// A trait for memory mapping for DAX.
///
/// For some transports (like virtio) it may be possible to share a region of memory with the
@@ -194,7 +216,7 @@ impl<F: FileSystem + Sync> Server<F> {
match self
.fs
- .lookup(Context::from(in_header), in_header.nodeid.into(), &name)
+ .lookup(Context::from(in_header), in_header.nodeid.into(), name)
{
Ok(entry) => {
let out = EntryOut::from(entry);
@@ -933,6 +955,7 @@ impl<F: FileSystem + Sync> Server<F> {
| FsOptions::DO_READDIRPLUS
| FsOptions::READDIRPLUS_AUTO
| FsOptions::ATOMIC_O_TRUNC
+ | FsOptions::MAX_PAGES
| FsOptions::MAP_ALIGNMENT;
let capable = FsOptions::from_bits_truncate(flags);
@@ -952,6 +975,11 @@ impl<F: FileSystem + Sync> Server<F> {
enabled.remove(FsOptions::ATOMIC_O_TRUNC);
}
+ let max_write = self.fs.max_buffer_size();
+ let max_pages = min(
+ max(max_readahead, max_write) / pagesize() as u32,
+ u16::MAX as u32,
+ ) as u16;
let out = InitOut {
major: KERNEL_VERSION,
minor: KERNEL_MINOR_VERSION,
@@ -959,8 +987,9 @@ impl<F: FileSystem + Sync> Server<F> {
flags: enabled.bits(),
max_background: ::std::u16::MAX,
congestion_threshold: (::std::u16::MAX / 4) * 3,
- max_write: self.fs.max_buffer_size(),
+ max_write,
time_gran: 1, // nanoseconds
+ max_pages,
map_alignment: pagesize().trailing_zeros() as u16,
..Default::default()
};
@@ -1672,7 +1701,7 @@ fn reply_ok<T: DataInit, W: Writer>(
len += size_of::<T>();
}
- if let Some(ref data) = data {
+ if let Some(data) = data {
len += data.len();
}
diff --git a/fuse/src/sys.rs b/fuse/src/sys.rs
index 5ae9a1283..07e1b6a5e 100644
--- a/fuse/src/sys.rs
+++ b/fuse/src/sys.rs
@@ -363,6 +363,13 @@ bitflags! {
/// mapping requests are pagesize-aligned. This field automatically set by the server and
/// this feature is enabled by default.
const MAP_ALIGNMENT = MAP_ALIGNMENT;
+
+
+ /// Indicates that the `max_pages` field of the `InitOut` struct is valid.
+ ///
+ /// This field is used by the kernel driver to determine the maximum number of pages that
+ /// may be used for any read or write requests.
+ const MAX_PAGES = MAX_PAGES;
}
}
@@ -743,20 +750,20 @@ pub struct SetattrIn {
}
unsafe impl DataInit for SetattrIn {}
-impl Into<libc::stat64> for SetattrIn {
- fn into(self) -> libc::stat64 {
+impl From<SetattrIn> for libc::stat64 {
+ fn from(s: SetattrIn) -> libc::stat64 {
// Safe because we are zero-initializing a struct with only POD fields.
let mut out: libc::stat64 = unsafe { mem::zeroed() };
- out.st_mode = self.mode;
- out.st_uid = self.uid;
- out.st_gid = self.gid;
- out.st_size = self.size as i64;
- out.st_atime = self.atime as libc::time_t;
- out.st_mtime = self.mtime as libc::time_t;
- out.st_ctime = self.ctime as libc::time_t;
- out.st_atime_nsec = self.atimensec as libc::c_long;
- out.st_mtime_nsec = self.mtimensec as libc::c_long;
- out.st_ctime_nsec = self.ctimensec as libc::c_long;
+ out.st_mode = s.mode;
+ out.st_uid = s.uid;
+ out.st_gid = s.gid;
+ out.st_size = s.size as i64;
+ out.st_atime = s.atime as libc::time_t;
+ out.st_mtime = s.mtime as libc::time_t;
+ out.st_ctime = s.ctime as libc::time_t;
+ out.st_atime_nsec = s.atimensec as libc::c_long;
+ out.st_mtime_nsec = s.mtimensec as libc::c_long;
+ out.st_ctime_nsec = s.ctimensec as libc::c_long;
out
}
diff --git a/fuse/src/worker.rs b/fuse/src/worker.rs
index 378e5eee3..6574d20de 100644
--- a/fuse/src/worker.rs
+++ b/fuse/src/worker.rs
@@ -7,33 +7,38 @@ use std::io::{self, BufRead, BufReader, Cursor, Read, Write};
use std::mem::size_of;
use std::os::unix::fs::FileExt;
use std::os::unix::io::AsRawFd;
+use std::sync::Arc;
use crate::filesystem::{FileSystem, ZeroCopyReader, ZeroCopyWriter};
use crate::server::{Mapper, Reader, Server, Writer};
use crate::sys;
use crate::{Error, Result};
-struct DevFuseReader<'a> {
+struct DevFuseReader {
// File representing /dev/fuse for reading, with sufficient buffer to accommodate a FUSE read
// transaction.
- reader: &'a mut BufReader<File>,
+ reader: BufReader<File>,
}
-impl<'a> DevFuseReader<'a> {
- pub fn new(reader: &'a mut BufReader<File>) -> Self {
+impl DevFuseReader {
+ pub fn new(reader: BufReader<File>) -> Self {
DevFuseReader { reader }
}
+
+ fn drain(&mut self) {
+ self.reader.consume(self.reader.buffer().len());
+ }
}
-impl Read for DevFuseReader<'_> {
+impl Read for DevFuseReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.reader.read(buf)
}
}
-impl Reader for DevFuseReader<'_> {}
+impl Reader for DevFuseReader {}
-impl ZeroCopyReader for DevFuseReader<'_> {
+impl ZeroCopyReader for DevFuseReader {
fn read_to(&mut self, f: &mut File, count: usize, off: u64) -> io::Result<usize> {
let buf = self.reader.fill_buf()?;
let end = std::cmp::min(count, buf.len());
@@ -43,17 +48,17 @@ impl ZeroCopyReader for DevFuseReader<'_> {
}
}
-struct DevFuseWriter<'a> {
+struct DevFuseWriter {
// File representing /dev/fuse for writing.
- dev_fuse: &'a mut File,
+ dev_fuse: File,
// An internal buffer to allow generating data and header out of order, such that they can be
// flushed at once. This is wrapped by a cursor for tracking the current written position.
- write_buf: &'a mut Cursor<Vec<u8>>,
+ write_buf: Cursor<Vec<u8>>,
}
-impl<'a> DevFuseWriter<'a> {
- pub fn new(dev_fuse: &'a mut File, write_buf: &'a mut Cursor<Vec<u8>>) -> Self {
+impl DevFuseWriter {
+ pub fn new(dev_fuse: File, write_buf: Cursor<Vec<u8>>) -> Self {
debug_assert_eq!(write_buf.position(), 0);
DevFuseWriter {
@@ -63,7 +68,7 @@ impl<'a> DevFuseWriter<'a> {
}
}
-impl Write for DevFuseWriter<'_> {
+impl Write for DevFuseWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.write_buf.write(buf)
}
@@ -76,7 +81,9 @@ impl Write for DevFuseWriter<'_> {
}
}
-impl Writer for DevFuseWriter<'_> {
+impl Writer for DevFuseWriter {
+ type ClosureWriter = Self;
+
fn write_at<F>(&mut self, offset: usize, f: F) -> io::Result<usize>
where
F: Fn(&mut Self) -> io::Result<usize>,
@@ -94,7 +101,7 @@ impl Writer for DevFuseWriter<'_> {
}
}
-impl ZeroCopyWriter for DevFuseWriter<'_> {
+impl ZeroCopyWriter for DevFuseWriter {
fn write_from(&mut self, f: &mut File, count: usize, off: u64) -> io::Result<usize> {
let pos = self.write_buf.position() as usize;
let end = pos + count;
@@ -138,27 +145,89 @@ impl Mapper for DevFuseMapper {
}
/// Start the FUSE message handling loop. Returns when an error happens.
+///
+/// # Arguments
+///
+/// * `dev_fuse` - A `File` object of /dev/fuse
+/// * `input_buffer_size` - Maximum bytes of the buffer when reads from /dev/fuse.
+/// * `output_buffer_size` - Maximum bytes of the buffer when writes to /dev/fuse. Must be large
+/// enough (usually equal) to `n` in `MountOption::MaxRead(n)`.
+///
+/// [deprecated(note="Please migrate to the `FuseConfig` builder API"]
pub fn start_message_loop<F: FileSystem + Sync>(
dev_fuse: File,
- max_write: u32,
- max_read: u32,
+ input_buffer_size: u32,
+ output_buffer_size: u32,
fs: F,
) -> Result<()> {
let server = Server::new(fs);
- let mut buf_reader = BufReader::with_capacity(
- max_write as usize + size_of::<sys::InHeader>() + size_of::<sys::WriteIn>(),
- dev_fuse.try_clone().map_err(Error::EndpointSetup)?,
- );
+ do_start_message_loop(dev_fuse, input_buffer_size, output_buffer_size, &server)
+}
- let mut write_buf = Cursor::new(Vec::with_capacity(max_read as usize));
- let mut wfile = dev_fuse.try_clone().map_err(Error::EndpointSetup)?;
+fn do_start_message_loop<F: FileSystem + Sync>(
+ dev_fuse: File,
+ input_buffer_size: u32,
+ output_buffer_size: u32,
+ server: &Server<F>,
+) -> Result<()> {
+ let mut dev_fuse_reader = {
+ let rfile = dev_fuse.try_clone().map_err(Error::EndpointSetup)?;
+ let buf_reader = BufReader::with_capacity(
+ input_buffer_size as usize + size_of::<sys::InHeader>() + size_of::<sys::WriteIn>(),
+ rfile,
+ );
+ DevFuseReader::new(buf_reader)
+ };
+ let mut dev_fuse_writer = {
+ let wfile = dev_fuse;
+ let write_buf = Cursor::new(Vec::with_capacity(output_buffer_size as usize));
+ DevFuseWriter::new(wfile, write_buf)
+ };
+ let dev_fuse_mapper = DevFuseMapper::new();
loop {
- let dev_fuse_reader = DevFuseReader::new(&mut buf_reader);
- let dev_fuse_writer = DevFuseWriter::new(&mut wfile, &mut write_buf);
- let dev_fuse_mapper = DevFuseMapper::new();
+ server.handle_message(&mut dev_fuse_reader, &mut dev_fuse_writer, &dev_fuse_mapper)?;
- if let Err(e) = server.handle_message(dev_fuse_reader, dev_fuse_writer, &dev_fuse_mapper) {
- return Err(e);
- }
+ // Since we're reusing the buffer to avoid repeated allocation, drain the possible
+ // residual from the buffer.
+ dev_fuse_reader.drain();
+ }
+}
+
+// TODO: Remove worker and this namespace from public
+pub mod internal {
+ use super::*;
+ use crossbeam_utils::thread;
+
+ /// Start the FUSE message handling loops in multiple threads. Returns when an error happens.
+ ///
+ /// # Arguments
+ ///
+ /// * `dev_fuse` - A `File` object of /dev/fuse
+ /// * `input_buffer_size` - Maximum bytes of the buffer when reads from /dev/fuse.
+ /// * `output_buffer_size` - Maximum bytes of the buffer when writes to /dev/fuse.
+ ///
+ /// [deprecated(note="Please migrate to the `FuseConfig` builder API"]
+ pub fn start_message_loop_mt<F: FileSystem + Sync + Send>(
+ dev_fuse: File,
+ input_buffer_size: u32,
+ output_buffer_size: u32,
+ thread_numbers: usize,
+ fs: F,
+ ) -> Result<()> {
+ let result = thread::scope(|s| {
+ let server = Arc::new(Server::new(fs));
+ for _ in 0..thread_numbers {
+ let dev_fuse = dev_fuse
+ .try_clone()
+ .map_err(Error::EndpointSetup)
+ .expect("Failed to clone /dev/fuse FD");
+ let server = server.clone();
+ s.spawn(move |_| {
+ do_start_message_loop(dev_fuse, input_buffer_size, output_buffer_size, &server)
+ });
+ }
+ });
+
+ unreachable!("Threads exited or crashed unexpectedly: {:?}", result);
}
}
diff --git a/fuzz/OWNERS b/fuzz/OWNERS
deleted file mode 100644
index 8c53fc578..000000000
--- a/fuzz/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-dgreid@chromium.org
diff --git a/fuzz/fs_server_fuzzer.rs b/fuzz/fs_server_fuzzer.rs
deleted file mode 100644
index f1154c5d8..000000000
--- a/fuzz/fs_server_fuzzer.rs
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#![no_main]
-
-use std::convert::TryInto;
-
-use cros_fuzz::fuzz_target;
-use devices::virtio::{create_descriptor_chain, DescriptorType, Reader, Writer};
-use fuse::fuzzing::fuzz_server;
-use vm_memory::{GuestAddress, GuestMemory};
-
-const MEM_SIZE: u64 = 256 * 1024 * 1024;
-const BUFFER_ADDR: GuestAddress = GuestAddress(0x100);
-
-thread_local! {
- static GUEST_MEM: GuestMemory = GuestMemory::new(&[(GuestAddress(0), MEM_SIZE)]).unwrap();
-}
-
-fuzz_target!(|data| {
- use DescriptorType::*;
-
- GUEST_MEM.with(|mem| {
- mem.write_all_at_addr(data, BUFFER_ADDR).unwrap();
-
- let chain = create_descriptor_chain(
- mem,
- GuestAddress(0),
- BUFFER_ADDR,
- vec![
- (Readable, data.len().try_into().unwrap()),
- (
- Writable,
- (MEM_SIZE as u32)
- .saturating_sub(data.len().try_into().unwrap())
- .saturating_sub(0x100),
- ),
- ],
- 0,
- )
- .unwrap();
-
- let r = Reader::new(mem.clone(), chain.clone()).unwrap();
- let w = Writer::new(mem.clone(), chain).unwrap();
- fuzz_server(r, w);
- });
-});
diff --git a/gpu_display/Android.bp b/gpu_display/Android.bp
index 440311b5e..825193365 100644
--- a/gpu_display/Android.bp
+++ b/gpu_display/Android.bp
@@ -1,4 +1,5 @@
-// This file is manually copied from old Android.bp
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
// cargo2android.py limitations:
// does not handle "-l dylib=wayland-client" yet
@@ -20,17 +21,19 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "gpu_display",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
"libdata_model",
"liblibc",
"liblinux_input_sys",
+ "libthiserror",
],
- static_libs: [
- "libdisplay_wl",
- ],
+ proc_macros: ["libremain"],
+ static_libs: ["libdisplay_wl"],
// added manually
target: {
@@ -40,7 +43,7 @@ rust_library {
android: {
static_libs: [
"libwayland_client_static",
- "libffi"
+ "libffi",
],
},
},
@@ -52,9 +55,18 @@ cc_library_static {
c_std: "c11",
srcs: ["src/display_wl.c"],
- generated_sources: ["gpu_display_protocol_sources"],
- generated_headers: ["gpu_display_client_protocol_headers"],
- export_generated_headers: ["gpu_display_client_protocol_headers"],
+ generated_sources: [
+ "gpu_display_protocol_sources",
+ "wayland_extension_protocol_sources",
+ ],
+ generated_headers: [
+ "gpu_display_client_protocol_headers",
+ "wayland_extension_client_protocol_headers",
+ ],
+ export_generated_headers: [
+ "gpu_display_client_protocol_headers",
+ "wayland_extension_client_protocol_headers",
+ ],
// added manually
target: {
@@ -64,7 +76,7 @@ cc_library_static {
android: {
static_libs: [
"libwayland_client_static",
- "libffi"
+ "libffi",
],
},
linux_glibc_x86: {
@@ -86,7 +98,7 @@ wayland_protocol_codegen {
"protocol/aura-shell.xml",
"protocol/linux-dmabuf-unstable-v1.xml",
"protocol/viewporter.xml",
- "protocol/xdg-shell-unstable-v6.xml",
+ "protocol/virtio-gpu-metadata-v1.xml",
],
tools: ["wayland_scanner"],
}
@@ -99,49 +111,37 @@ wayland_protocol_codegen {
"protocol/aura-shell.xml",
"protocol/linux-dmabuf-unstable-v1.xml",
"protocol/viewporter.xml",
- "protocol/xdg-shell-unstable-v6.xml",
+ "protocol/virtio-gpu-metadata-v1.xml",
+ ],
+ tools: ["wayland_scanner"],
+}
+
+wayland_protocol_codegen {
+ name: "gpu_display_server_protocol_headers",
+ cmd: "$(location wayland_scanner) server-header < $(in) > $(out)",
+ suffix: ".h",
+ srcs: [
+ "protocol/aura-shell.xml",
+ "protocol/linux-dmabuf-unstable-v1.xml",
+ "protocol/viewporter.xml",
+ "protocol/virtio-gpu-metadata-v1.xml",
],
tools: ["wayland_scanner"],
}
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../linux_input_sys/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// cc-1.0.25
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
+cc_library_static {
+ name: "libwayland_crosvm_gpu_display_extension_server_protocols",
+ vendor_available: true,
+ host_supported: true,
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ "-g",
+ "-fvisibility=hidden",
+ ],
+ static_libs: ["libwayland_server"],
+ generated_sources: ["gpu_display_protocol_sources"],
+ generated_headers: ["gpu_display_server_protocol_headers"],
+ export_generated_headers: ["gpu_display_server_protocol_headers"],
+}
diff --git a/gpu_display/Cargo.toml b/gpu_display/Cargo.toml
index e5cfe7205..7428d7fc4 100644
--- a/gpu_display/Cargo.toml
+++ b/gpu_display/Cargo.toml
@@ -2,16 +2,19 @@
name = "gpu_display"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[features]
x = []
[dependencies]
-data_model = { path = "../data_model" }
+data_model = { path = "../common/data_model" }
libc = "*"
base = { path = "../base" }
linux_input_sys = { path = "../linux_input_sys" }
+remain = "*"
+thiserror = "*"
[build-dependencies]
-cc = "=1.0.25"
+cc = "1.0.25"
+pkg-config = "0.3.11"
diff --git a/gpu_display/build.rs b/gpu_display/build.rs
index 5cdba2f7e..717c87ad3 100644
--- a/gpu_display/build.rs
+++ b/gpu_display/build.rs
@@ -11,19 +11,17 @@ use std::process::Command;
// Performs a recursive search for a file with name under path and returns the full path if such a
// file is found.
fn scan_path<P: AsRef<Path>, O: AsRef<OsStr>>(path: P, name: O) -> Option<PathBuf> {
- for entry in fs::read_dir(path).ok()? {
- if let Ok(entry) = entry {
- let file_type = match entry.file_type() {
- Ok(t) => t,
- Err(_) => continue,
- };
+ for entry in (fs::read_dir(path).ok()?).flatten() {
+ let file_type = match entry.file_type() {
+ Ok(t) => t,
+ Err(_) => continue,
+ };
- if file_type.is_file() && entry.file_name() == name.as_ref() {
- return Some(entry.path());
- } else if file_type.is_dir() {
- if let Some(found) = scan_path(entry.path(), name.as_ref()) {
- return Some(found);
- }
+ if file_type.is_file() && entry.file_name() == name.as_ref() {
+ return Some(entry.path());
+ } else if file_type.is_dir() {
+ if let Some(found) = scan_path(entry.path(), name.as_ref()) {
+ return Some(found);
}
}
}
@@ -32,7 +30,7 @@ fn scan_path<P: AsRef<Path>, O: AsRef<OsStr>>(path: P, name: O) -> Option<PathBu
// Searches for the given protocol in both the system wide and bundles protocols path.
fn find_protocol(name: &str) -> PathBuf {
- let protocols_path = env::var("WAYLAND_PROTOCOLS_PATH")
+ let protocols_path = pkg_config::get_variable("wayland-protocols", "pkgdatadir")
.unwrap_or_else(|_| "/usr/share/wayland-protocols".to_owned());
let protocol_file_name = PathBuf::from(format!("{}.xml", name));
@@ -87,8 +85,9 @@ fn main() {
for protocol in &[
"aura-shell",
"linux-dmabuf-unstable-v1",
- "xdg-shell-unstable-v6",
+ "xdg-shell",
"viewporter",
+ "virtio-gpu-metadata-v1",
] {
build.file(compile_protocol(protocol, &out_dir));
}
diff --git a/gpu_display/cargo2android.json b/gpu_display/cargo2android.json
new file mode 100644
index 000000000..26b463aba
--- /dev/null
+++ b/gpu_display/cargo2android.json
@@ -0,0 +1,8 @@
+{
+ "run": true,
+ "device": true,
+ "tests": true,
+ "global_defaults": "crosvm_defaults",
+ "add_workspace": true,
+ "patch": "patches/Android.bp.patch"
+}
diff --git a/gpu_display/examples/simple.rs b/gpu_display/examples/simple.rs
index ace982836..83eb0a2cd 100644
--- a/gpu_display/examples/simple.rs
+++ b/gpu_display/examples/simple.rs
@@ -6,10 +6,12 @@ use gpu_display::*;
fn main() {
let mut disp = GpuDisplay::open_wayland(None::<&str>).unwrap();
- let surface_id = disp.create_surface(None, 1280, 1024).unwrap();
+ let surface_id = disp
+ .create_surface(None, 1280, 1024, SurfaceType::Scanout)
+ .unwrap();
disp.flip(surface_id);
- disp.commit(surface_id);
+ disp.commit(surface_id).unwrap();
while !disp.close_requested(surface_id) {
- disp.dispatch_events();
+ disp.dispatch_events().unwrap();
}
}
diff --git a/gpu_display/examples/simple_open.rs b/gpu_display/examples/simple_open.rs
index ab375bff3..cc38b8bf5 100644
--- a/gpu_display/examples/simple_open.rs
+++ b/gpu_display/examples/simple_open.rs
@@ -1,8 +1,10 @@
-use gpu_display::GpuDisplay;
+use gpu_display::{GpuDisplay, SurfaceType};
fn main() {
let mut disp = GpuDisplay::open_x(None::<&str>).unwrap();
- let surface_id = disp.create_surface(None, 1280, 1024).unwrap();
+ let surface_id = disp
+ .create_surface(None, 1280, 1024, SurfaceType::Scanout)
+ .unwrap();
let mem = disp.framebuffer(surface_id).unwrap();
for y in 0..1024 {
@@ -20,6 +22,6 @@ fn main() {
disp.flip(surface_id);
while !disp.close_requested(surface_id) {
- disp.dispatch_events();
+ disp.dispatch_events().unwrap();
}
}
diff --git a/gpu_display/patches/Android.bp.patch b/gpu_display/patches/Android.bp.patch
new file mode 100644
index 000000000..f03d8de51
--- /dev/null
+++ b/gpu_display/patches/Android.bp.patch
@@ -0,0 +1,121 @@
+diff --git b/gpu_display/Android.bp a/gpu_display/Android.bp
+--- b/gpu_display/Android.bp
++++ a/gpu_display/Android.bp
+@@ -32,7 +32,116 @@ rust_library {
+ "liblinux_input_sys",
+ "libthiserror",
+ ],
+ proc_macros: ["libremain"],
+ static_libs: ["libdisplay_wl"],
+- shared_libs: ["libwayland-client"],
++
++ // added manually
++ target: {
++ host: {
++ shared_libs: ["libwayland_client"],
++ },
++ android: {
++ static_libs: [
++ "libwayland_client_static",
++ "libffi",
++ ],
++ },
++ },
++}
++
++cc_library_static {
++ name: "libdisplay_wl",
++ host_supported: true,
++ c_std: "c11",
++ srcs: ["src/display_wl.c"],
++
++ generated_sources: [
++ "gpu_display_protocol_sources",
++ "wayland_extension_protocol_sources",
++ ],
++ generated_headers: [
++ "gpu_display_client_protocol_headers",
++ "wayland_extension_client_protocol_headers",
++ ],
++ export_generated_headers: [
++ "gpu_display_client_protocol_headers",
++ "wayland_extension_client_protocol_headers",
++ ],
++
++ // added manually
++ target: {
++ host: {
++ shared_libs: ["libwayland_client"],
++ },
++ android: {
++ static_libs: [
++ "libwayland_client_static",
++ "libffi",
++ ],
++ },
++ linux_glibc_x86: {
++ // libffi broken on x86, see b/162610242
++ enabled: false,
++ },
++ },
++ apex_available: [
++ "//apex_available:platform",
++ "com.android.virt",
++ ],
++}
++
++wayland_protocol_codegen {
++ name: "gpu_display_protocol_sources",
++ cmd: "$(location wayland_scanner) private-code < $(in) > $(out)",
++ suffix: ".c",
++ srcs: [
++ "protocol/aura-shell.xml",
++ "protocol/linux-dmabuf-unstable-v1.xml",
++ "protocol/viewporter.xml",
++ "protocol/virtio-gpu-metadata-v1.xml",
++ ],
++ tools: ["wayland_scanner"],
++}
++
++wayland_protocol_codegen {
++ name: "gpu_display_client_protocol_headers",
++ cmd: "$(location wayland_scanner) client-header < $(in) > $(out)",
++ suffix: ".h",
++ srcs: [
++ "protocol/aura-shell.xml",
++ "protocol/linux-dmabuf-unstable-v1.xml",
++ "protocol/viewporter.xml",
++ "protocol/virtio-gpu-metadata-v1.xml",
++ ],
++ tools: ["wayland_scanner"],
++}
++
++wayland_protocol_codegen {
++ name: "gpu_display_server_protocol_headers",
++ cmd: "$(location wayland_scanner) server-header < $(in) > $(out)",
++ suffix: ".h",
++ srcs: [
++ "protocol/aura-shell.xml",
++ "protocol/linux-dmabuf-unstable-v1.xml",
++ "protocol/viewporter.xml",
++ "protocol/virtio-gpu-metadata-v1.xml",
++ ],
++ tools: ["wayland_scanner"],
++}
++
++cc_library_static {
++ name: "libwayland_crosvm_gpu_display_extension_server_protocols",
++ vendor_available: true,
++ host_supported: true,
++ cflags: [
++ "-Wall",
++ "-Wextra",
++ "-Werror",
++ "-g",
++ "-fvisibility=hidden",
++ ],
++ static_libs: ["libwayland_server"],
++ generated_sources: ["gpu_display_protocol_sources"],
++ generated_headers: ["gpu_display_server_protocol_headers"],
++ export_generated_headers: ["gpu_display_server_protocol_headers"],
+ }
diff --git a/gpu_display/protocol/virtio-gpu-metadata-v1.xml b/gpu_display/protocol/virtio-gpu-metadata-v1.xml
new file mode 100644
index 000000000..2aca85b51
--- /dev/null
+++ b/gpu_display/protocol/virtio-gpu-metadata-v1.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="wp_virtio_gpu_metadata_v1">
+ <copyright>
+ Copyright 2021 The Chromium Authors.
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+ The above copyright notice and this permission notice (including the next
+ paragraph) shall be included in all copies or substantial portions of the
+ Software.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+ </copyright>
+
+ <interface name="wp_virtio_gpu_metadata_v1" version="1">
+ <description summary="attach virtio gpu metadata">
+ The global interface which allows attaching virtio-gpu metadata
+ to wl_surface objects.
+ </description>
+
+ <enum name="error">
+ <entry name="surface_metadata_exists" value="0"
+ summary="the surface already has a metadata object associated"/>
+ </enum>
+
+ <request name="get_surface_metadata">
+ <description summary="extend surface interface for attaching metadata">
+ Instantiate an virtio_gpu_surface_metadata_v1 extension for the given
+ wl_surface to attach virtio gpu metadata. If the given wl_surface
+ already has a surface metadata object associated, the
+ surface_metadata_exists protocol error is raised.
+ </description>
+ <arg name="id" type="new_id" interface="wp_virtio_gpu_surface_metadata_v1"
+ summary="the new metadata interface id"/>
+ <arg name="surface" type="object" interface="wl_surface"
+ summary="the surface"/>
+ </request>
+ </interface>
+
+ <interface name="wp_virtio_gpu_surface_metadata_v1" version="1">
+ <description summary="interface to attach virtio gpu metadata to a wl_surface">
+ An additional interface to a wl_surface object, which allows the
+ client to attach additional metadata to the surface.
+
+ If the wl_surface associated with the virtio_gpu_surface_metadata_v1 is
+ destroyed, all virtio_gpu_surface_metadata_v1 requests except 'destroy'
+ raise the protocol error no_surface.
+
+ If the virtio_gpu_surface_metadata_v1 object is destroyed, the metadata
+ state is removed from the wl_surface. The change will be applied
+ on the next wl_surface.commit.
+ </description>
+
+ <enum name="error">
+ <entry name="no_surface" value="0"
+ summary="the wl_surface was destroyed"/>
+ </enum>
+
+ <request name="set_scanout_id">
+ <description summary="set the virtio gpu scanout id of the surface">
+ Set the virtio gpu scanout id of the associated wl_surface.
+ </description>
+ <arg name="scanout_id" type="uint" summary="virtio gpu scanout id"/>
+ </request>
+ </interface>
+
+</protocol> \ No newline at end of file
diff --git a/gpu_display/protocol/xdg-shell-unstable-v6.xml b/gpu_display/protocol/xdg-shell-unstable-v6.xml
deleted file mode 100644
index 1c0f92452..000000000
--- a/gpu_display/protocol/xdg-shell-unstable-v6.xml
+++ /dev/null
@@ -1,1044 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<protocol name="xdg_shell_unstable_v6">
-
- <copyright>
- Copyright © 2008-2013 Kristian Høgsberg
- Copyright © 2013 Rafael Antognolli
- Copyright © 2013 Jasper St. Pierre
- Copyright © 2010-2013 Intel Corporation
-
- Permission is hereby granted, free of charge, to any person obtaining a
- copy of this software and associated documentation files (the "Software"),
- to deal in the Software without restriction, including without limitation
- the rights to use, copy, modify, merge, publish, distribute, sublicense,
- and/or sell copies of the Software, and to permit persons to whom the
- Software is furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice (including the next
- paragraph) shall be included in all copies or substantial portions of the
- Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- DEALINGS IN THE SOFTWARE.
- </copyright>
-
- <interface name="zxdg_shell_v6" version="1">
- <description summary="create desktop-style surfaces">
- xdg_shell allows clients to turn a wl_surface into a "real window"
- which can be dragged, resized, stacked, and moved around by the
- user. Everything about this interface is suited towards traditional
- desktop environments.
- </description>
-
- <enum name="error">
- <entry name="role" value="0" summary="given wl_surface has another role"/>
- <entry name="defunct_surfaces" value="1"
- summary="xdg_shell was destroyed before children"/>
- <entry name="not_the_topmost_popup" value="2"
- summary="the client tried to map or destroy a non-topmost popup"/>
- <entry name="invalid_popup_parent" value="3"
- summary="the client specified an invalid popup parent surface"/>
- <entry name="invalid_surface_state" value="4"
- summary="the client provided an invalid surface state"/>
- <entry name="invalid_positioner" value="5"
- summary="the client provided an invalid positioner"/>
- </enum>
-
- <request name="destroy" type="destructor">
- <description summary="destroy xdg_shell">
- Destroy this xdg_shell object.
-
- Destroying a bound xdg_shell object while there are surfaces
- still alive created by this xdg_shell object instance is illegal
- and will result in a protocol error.
- </description>
- </request>
-
- <request name="create_positioner">
- <description summary="create a positioner object">
- Create a positioner object. A positioner object is used to position
- surfaces relative to some parent surface. See the interface description
- and xdg_surface.get_popup for details.
- </description>
- <arg name="id" type="new_id" interface="zxdg_positioner_v6"/>
- </request>
-
- <request name="get_xdg_surface">
- <description summary="create a shell surface from a surface">
- This creates an xdg_surface for the given surface. While xdg_surface
- itself is not a role, the corresponding surface may only be assigned
- a role extending xdg_surface, such as xdg_toplevel or xdg_popup.
-
- This creates an xdg_surface for the given surface. An xdg_surface is
- used as basis to define a role to a given surface, such as xdg_toplevel
- or xdg_popup. It also manages functionality shared between xdg_surface
- based surface roles.
-
- See the documentation of xdg_surface for more details about what an
- xdg_surface is and how it is used.
- </description>
- <arg name="id" type="new_id" interface="zxdg_surface_v6"/>
- <arg name="surface" type="object" interface="wl_surface"/>
- </request>
-
- <request name="pong">
- <description summary="respond to a ping event">
- A client must respond to a ping event with a pong request or
- the client may be deemed unresponsive. See xdg_shell.ping.
- </description>
- <arg name="serial" type="uint" summary="serial of the ping event"/>
- </request>
-
- <event name="ping">
- <description summary="check if the client is alive">
- The ping event asks the client if it's still alive. Pass the
- serial specified in the event back to the compositor by sending
- a "pong" request back with the specified serial. See xdg_shell.ping.
-
- Compositors can use this to determine if the client is still
- alive. It's unspecified what will happen if the client doesn't
- respond to the ping request, or in what timeframe. Clients should
- try to respond in a reasonable amount of time.
-
- A compositor is free to ping in any way it wants, but a client must
- always respond to any xdg_shell object it created.
- </description>
- <arg name="serial" type="uint" summary="pass this to the pong request"/>
- </event>
- </interface>
-
- <interface name="zxdg_positioner_v6" version="1">
- <description summary="child surface positioner">
- The xdg_positioner provides a collection of rules for the placement of a
- child surface relative to a parent surface. Rules can be defined to ensure
- the child surface remains within the visible area's borders, and to
- specify how the child surface changes its position, such as sliding along
- an axis, or flipping around a rectangle. These positioner-created rules are
- constrained by the requirement that a child surface must intersect with or
- be at least partially adjacent to its parent surface.
-
- See the various requests for details about possible rules.
-
- At the time of the request, the compositor makes a copy of the rules
- specified by the xdg_positioner. Thus, after the request is complete the
- xdg_positioner object can be destroyed or reused; further changes to the
- object will have no effect on previous usages.
-
- For an xdg_positioner object to be considered complete, it must have a
- non-zero size set by set_size, and a non-zero anchor rectangle set by
- set_anchor_rect. Passing an incomplete xdg_positioner object when
- positioning a surface raises an error.
- </description>
-
- <enum name="error">
- <entry name="invalid_input" value="0" summary="invalid input provided"/>
- </enum>
-
- <request name="destroy" type="destructor">
- <description summary="destroy the xdg_positioner object">
- Notify the compositor that the xdg_positioner will no longer be used.
- </description>
- </request>
-
- <request name="set_size">
- <description summary="set the size of the to-be positioned rectangle">
- Set the size of the surface that is to be positioned with the positioner
- object. The size is in surface-local coordinates and corresponds to the
- window geometry. See xdg_surface.set_window_geometry.
-
- If a zero or negative size is set the invalid_input error is raised.
- </description>
- <arg name="width" type="int" summary="width of positioned rectangle"/>
- <arg name="height" type="int" summary="height of positioned rectangle"/>
- </request>
-
- <request name="set_anchor_rect">
- <description summary="set the anchor rectangle within the parent surface">
- Specify the anchor rectangle within the parent surface that the child
- surface will be placed relative to. The rectangle is relative to the
- window geometry as defined by xdg_surface.set_window_geometry of the
- parent surface. The rectangle must be at least 1x1 large.
-
- When the xdg_positioner object is used to position a child surface, the
- anchor rectangle may not extend outside the window geometry of the
- positioned child's parent surface.
-
- If a zero or negative size is set the invalid_input error is raised.
- </description>
- <arg name="x" type="int" summary="x position of anchor rectangle"/>
- <arg name="y" type="int" summary="y position of anchor rectangle"/>
- <arg name="width" type="int" summary="width of anchor rectangle"/>
- <arg name="height" type="int" summary="height of anchor rectangle"/>
- </request>
-
- <enum name="anchor" bitfield="true">
- <entry name="none" value="0"
- summary="the center of the anchor rectangle"/>
- <entry name="top" value="1"
- summary="the top edge of the anchor rectangle"/>
- <entry name="bottom" value="2"
- summary="the bottom edge of the anchor rectangle"/>
- <entry name="left" value="4"
- summary="the left edge of the anchor rectangle"/>
- <entry name="right" value="8"
- summary="the right edge of the anchor rectangle"/>
- </enum>
-
- <request name="set_anchor">
- <description summary="set anchor rectangle anchor edges">
- Defines a set of edges for the anchor rectangle. These are used to
- derive an anchor point that the child surface will be positioned
- relative to. If two orthogonal edges are specified (e.g. 'top' and
- 'left'), then the anchor point will be the intersection of the edges
- (e.g. the top left position of the rectangle); otherwise, the derived
- anchor point will be centered on the specified edge, or in the center of
- the anchor rectangle if no edge is specified.
-
- If two parallel anchor edges are specified (e.g. 'left' and 'right'),
- the invalid_input error is raised.
- </description>
- <arg name="anchor" type="uint" enum="anchor"
- summary="bit mask of anchor edges"/>
- </request>
-
- <enum name="gravity" bitfield="true">
- <entry name="none" value="0"
- summary="center over the anchor edge"/>
- <entry name="top" value="1"
- summary="position above the anchor edge"/>
- <entry name="bottom" value="2"
- summary="position below the anchor edge"/>
- <entry name="left" value="4"
- summary="position to the left of the anchor edge"/>
- <entry name="right" value="8"
- summary="position to the right of the anchor edge"/>
- </enum>
-
- <request name="set_gravity">
- <description summary="set child surface gravity">
- Defines in what direction a surface should be positioned, relative to
- the anchor point of the parent surface. If two orthogonal gravities are
- specified (e.g. 'bottom' and 'right'), then the child surface will be
- placed in the specified direction; otherwise, the child surface will be
- centered over the anchor point on any axis that had no gravity
- specified.
-
- If two parallel gravities are specified (e.g. 'left' and 'right'), the
- invalid_input error is raised.
- </description>
- <arg name="gravity" type="uint" enum="gravity"
- summary="bit mask of gravity directions"/>
- </request>
-
- <enum name="constraint_adjustment" bitfield="true">
- <description summary="constraint adjustments">
- The constraint adjustment value define ways the compositor will adjust
- the position of the surface, if the unadjusted position would result
- in the surface being partly constrained.
-
- Whether a surface is considered 'constrained' is left to the compositor
- to determine. For example, the surface may be partly outside the
- compositor's defined 'work area', thus necessitating the child surface's
- position be adjusted until it is entirely inside the work area.
-
- The adjustments can be combined, according to a defined precedence: 1)
- Flip, 2) Slide, 3) Resize.
- </description>
- <entry name="none" value="0">
- <description summary="don't move the child surface when constrained">
- Don't alter the surface position even if it is constrained on some
- axis, for example partially outside the edge of a monitor.
- </description>
- </entry>
- <entry name="slide_x" value="1">
- <description summary="move along the x axis until unconstrained">
- Slide the surface along the x axis until it is no longer constrained.
-
- First try to slide towards the direction of the gravity on the x axis
- until either the edge in the opposite direction of the gravity is
- unconstrained or the edge in the direction of the gravity is
- constrained.
-
- Then try to slide towards the opposite direction of the gravity on the
- x axis until either the edge in the direction of the gravity is
- unconstrained or the edge in the opposite direction of the gravity is
- constrained.
- </description>
- </entry>
- <entry name="slide_y" value="2">
- <description summary="move along the y axis until unconstrained">
- Slide the surface along the y axis until it is no longer constrained.
-
- First try to slide towards the direction of the gravity on the y axis
- until either the edge in the opposite direction of the gravity is
- unconstrained or the edge in the direction of the gravity is
- constrained.
-
- Then try to slide towards the opposite direction of the gravity on the
- y axis until either the edge in the direction of the gravity is
- unconstrained or the edge in the opposite direction of the gravity is
- constrained.
- </description>
- </entry>
- <entry name="flip_x" value="4">
- <description summary="invert the anchor and gravity on the x axis">
- Invert the anchor and gravity on the x axis if the surface is
- constrained on the x axis. For example, if the left edge of the
- surface is constrained, the gravity is 'left' and the anchor is
- 'left', change the gravity to 'right' and the anchor to 'right'.
-
- If the adjusted position also ends up being constrained, the resulting
- position of the flip_x adjustment will be the one before the
- adjustment.
- </description>
- </entry>
- <entry name="flip_y" value="8">
- <description summary="invert the anchor and gravity on the y axis">
- Invert the anchor and gravity on the y axis if the surface is
- constrained on the y axis. For example, if the bottom edge of the
- surface is constrained, the gravity is 'bottom' and the anchor is
- 'bottom', change the gravity to 'top' and the anchor to 'top'.
-
- If the adjusted position also ends up being constrained, the resulting
- position of the flip_y adjustment will be the one before the
- adjustment.
- </description>
- </entry>
- <entry name="resize_x" value="16">
- <description summary="horizontally resize the surface">
- Resize the surface horizontally so that it is completely
- unconstrained.
- </description>
- </entry>
- <entry name="resize_y" value="32">
- <description summary="vertically resize the surface">
- Resize the surface vertically so that it is completely unconstrained.
- </description>
- </entry>
- </enum>
-
- <request name="set_constraint_adjustment">
- <description summary="set the adjustment to be done when constrained">
- Specify how the window should be positioned if the originally intended
- position caused the surface to be constrained, meaning at least
- partially outside positioning boundaries set by the compositor. The
- adjustment is set by constructing a bitmask describing the adjustment to
- be made when the surface is constrained on that axis.
-
- If no bit for one axis is set, the compositor will assume that the child
- surface should not change its position on that axis when constrained.
-
- If more than one bit for one axis is set, the order of how adjustments
- are applied is specified in the corresponding adjustment descriptions.
-
- The default adjustment is none.
- </description>
- <arg name="constraint_adjustment" type="uint"
- summary="bit mask of constraint adjustments"/>
- </request>
-
- <request name="set_offset">
- <description summary="set surface position offset">
- Specify the surface position offset relative to the position of the
- anchor on the anchor rectangle and the anchor on the surface. For
- example if the anchor of the anchor rectangle is at (x, y), the surface
- has the gravity bottom|right, and the offset is (ox, oy), the calculated
- surface position will be (x + ox, y + oy). The offset position of the
- surface is the one used for constraint testing. See
- set_constraint_adjustment.
-
- An example use case is placing a popup menu on top of a user interface
- element, while aligning the user interface element of the parent surface
- with some user interface element placed somewhere in the popup surface.
- </description>
- <arg name="x" type="int" summary="surface position x offset"/>
- <arg name="y" type="int" summary="surface position y offset"/>
- </request>
- </interface>
-
- <interface name="zxdg_surface_v6" version="1">
- <description summary="desktop user interface surface base interface">
- An interface that may be implemented by a wl_surface, for
- implementations that provide a desktop-style user interface.
-
- It provides a base set of functionality required to construct user
- interface elements requiring management by the compositor, such as
- toplevel windows, menus, etc. The types of functionality are split into
- xdg_surface roles.
-
- Creating an xdg_surface does not set the role for a wl_surface. In order
- to map an xdg_surface, the client must create a role-specific object
- using, e.g., get_toplevel, get_popup. The wl_surface for any given
- xdg_surface can have at most one role, and may not be assigned any role
- not based on xdg_surface.
-
- A role must be assigned before any other requests are made to the
- xdg_surface object.
-
- The client must call wl_surface.commit on the corresponding wl_surface
- for the xdg_surface state to take effect.
-
- Creating an xdg_surface from a wl_surface which has a buffer attached or
- committed is a client error, and any attempts by a client to attach or
- manipulate a buffer prior to the first xdg_surface.configure call must
- also be treated as errors.
-
- For a surface to be mapped by the compositor, the following conditions
- must be met: (1) the client has assigned a xdg_surface based role to the
- surface, (2) the client has set and committed the xdg_surface state and
- the role dependent state to the surface and (3) the client has committed a
- buffer to the surface.
- </description>
-
- <enum name="error">
- <entry name="not_constructed" value="1"/>
- <entry name="already_constructed" value="2"/>
- <entry name="unconfigured_buffer" value="3"/>
- </enum>
-
- <request name="destroy" type="destructor">
- <description summary="destroy the xdg_surface">
- Destroy the xdg_surface object. An xdg_surface must only be destroyed
- after its role object has been destroyed.
- </description>
- </request>
-
- <request name="get_toplevel">
- <description summary="assign the xdg_toplevel surface role">
- This creates an xdg_toplevel object for the given xdg_surface and gives
- the associated wl_surface the xdg_toplevel role.
-
- See the documentation of xdg_toplevel for more details about what an
- xdg_toplevel is and how it is used.
- </description>
- <arg name="id" type="new_id" interface="zxdg_toplevel_v6"/>
- </request>
-
- <request name="get_popup">
- <description summary="assign the xdg_popup surface role">
- This creates an xdg_popup object for the given xdg_surface and gives the
- associated wl_surface the xdg_popup role.
-
- See the documentation of xdg_popup for more details about what an
- xdg_popup is and how it is used.
- </description>
- <arg name="id" type="new_id" interface="zxdg_popup_v6"/>
- <arg name="parent" type="object" interface="zxdg_surface_v6"/>
- <arg name="positioner" type="object" interface="zxdg_positioner_v6"/>
- </request>
-
- <request name="set_window_geometry">
- <description summary="set the new window geometry">
- The window geometry of a surface is its "visible bounds" from the
- user's perspective. Client-side decorations often have invisible
- portions like drop-shadows which should be ignored for the
- purposes of aligning, placing and constraining windows.
-
- The window geometry is double buffered, and will be applied at the
- time wl_surface.commit of the corresponding wl_surface is called.
-
- Once the window geometry of the surface is set, it is not possible to
- unset it, and it will remain the same until set_window_geometry is
- called again, even if a new subsurface or buffer is attached.
-
- If never set, the value is the full bounds of the surface,
- including any subsurfaces. This updates dynamically on every
- commit. This unset is meant for extremely simple clients.
-
- The arguments are given in the surface-local coordinate space of
- the wl_surface associated with this xdg_surface.
-
- The width and height must be greater than zero. Setting an invalid size
- will raise an error. When applied, the effective window geometry will be
- the set window geometry clamped to the bounding rectangle of the
- combined geometry of the surface of the xdg_surface and the associated
- subsurfaces.
- </description>
- <arg name="x" type="int"/>
- <arg name="y" type="int"/>
- <arg name="width" type="int"/>
- <arg name="height" type="int"/>
- </request>
-
- <request name="ack_configure">
- <description summary="ack a configure event">
- When a configure event is received, if a client commits the
- surface in response to the configure event, then the client
- must make an ack_configure request sometime before the commit
- request, passing along the serial of the configure event.
-
- For instance, for toplevel surfaces the compositor might use this
- information to move a surface to the top left only when the client has
- drawn itself for the maximized or fullscreen state.
-
- If the client receives multiple configure events before it
- can respond to one, it only has to ack the last configure event.
-
- A client is not required to commit immediately after sending
- an ack_configure request - it may even ack_configure several times
- before its next surface commit.
-
- A client may send multiple ack_configure requests before committing, but
- only the last request sent before a commit indicates which configure
- event the client really is responding to.
- </description>
- <arg name="serial" type="uint" summary="the serial from the configure event"/>
- </request>
-
- <event name="configure">
- <description summary="suggest a surface change">
- The configure event marks the end of a configure sequence. A configure
- sequence is a set of one or more events configuring the state of the
- xdg_surface, including the final xdg_surface.configure event.
-
- Where applicable, xdg_surface surface roles will during a configure
- sequence extend this event as a latched state sent as events before the
- xdg_surface.configure event. Such events should be considered to make up
- a set of atomically applied configuration states, where the
- xdg_surface.configure commits the accumulated state.
-
- Clients should arrange their surface for the new states, and then send
- an ack_configure request with the serial sent in this configure event at
- some point before committing the new surface.
-
- If the client receives multiple configure events before it can respond
- to one, it is free to discard all but the last event it received.
- </description>
- <arg name="serial" type="uint" summary="serial of the configure event"/>
- </event>
- </interface>
-
- <interface name="zxdg_toplevel_v6" version="1">
- <description summary="toplevel surface">
- This interface defines an xdg_surface role which allows a surface to,
- among other things, set window-like properties such as maximize,
- fullscreen, and minimize, set application-specific metadata like title and
- id, and well as trigger user interactive operations such as interactive
- resize and move.
- </description>
-
- <request name="destroy" type="destructor">
- <description summary="destroy the xdg_toplevel">
- Unmap and destroy the window. The window will be effectively
- hidden from the user's point of view, and all state like
- maximization, fullscreen, and so on, will be lost.
- </description>
- </request>
-
- <request name="set_parent">
- <description summary="set the parent of this surface">
- Set the "parent" of this surface. This window should be stacked
- above a parent. The parent surface must be mapped as long as this
- surface is mapped.
-
- Parent windows should be set on dialogs, toolboxes, or other
- "auxiliary" surfaces, so that the parent is raised when the dialog
- is raised.
- </description>
- <arg name="parent" type="object" interface="zxdg_toplevel_v6" allow-null="true"/>
- </request>
-
- <request name="set_title">
- <description summary="set surface title">
- Set a short title for the surface.
-
- This string may be used to identify the surface in a task bar,
- window list, or other user interface elements provided by the
- compositor.
-
- The string must be encoded in UTF-8.
- </description>
- <arg name="title" type="string"/>
- </request>
-
- <request name="set_app_id">
- <description summary="set application ID">
- Set an application identifier for the surface.
-
- The app ID identifies the general class of applications to which
- the surface belongs. The compositor can use this to group multiple
- surfaces together, or to determine how to launch a new application.
-
- For D-Bus activatable applications, the app ID is used as the D-Bus
- service name.
-
- The compositor shell will try to group application surfaces together
- by their app ID. As a best practice, it is suggested to select app
- ID's that match the basename of the application's .desktop file.
- For example, "org.freedesktop.FooViewer" where the .desktop file is
- "org.freedesktop.FooViewer.desktop".
-
- See the desktop-entry specification [0] for more details on
- application identifiers and how they relate to well-known D-Bus
- names and .desktop files.
-
- [0] http://standards.freedesktop.org/desktop-entry-spec/
- </description>
- <arg name="app_id" type="string"/>
- </request>
-
- <request name="show_window_menu">
- <description summary="show the window menu">
- Clients implementing client-side decorations might want to show
- a context menu when right-clicking on the decorations, giving the
- user a menu that they can use to maximize or minimize the window.
-
- This request asks the compositor to pop up such a window menu at
- the given position, relative to the local surface coordinates of
- the parent surface. There are no guarantees as to what menu items
- the window menu contains.
-
- This request must be used in response to some sort of user action
- like a button press, key press, or touch down event.
- </description>
- <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
- <arg name="serial" type="uint" summary="the serial of the user event"/>
- <arg name="x" type="int" summary="the x position to pop up the window menu at"/>
- <arg name="y" type="int" summary="the y position to pop up the window menu at"/>
- </request>
-
- <request name="move">
- <description summary="start an interactive move">
- Start an interactive, user-driven move of the surface.
-
- This request must be used in response to some sort of user action
- like a button press, key press, or touch down event. The passed
- serial is used to determine the type of interactive move (touch,
- pointer, etc).
-
- The server may ignore move requests depending on the state of
- the surface (e.g. fullscreen or maximized), or if the passed serial
- is no longer valid.
-
- If triggered, the surface will lose the focus of the device
- (wl_pointer, wl_touch, etc) used for the move. It is up to the
- compositor to visually indicate that the move is taking place, such as
- updating a pointer cursor, during the move. There is no guarantee
- that the device focus will return when the move is completed.
- </description>
- <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
- <arg name="serial" type="uint" summary="the serial of the user event"/>
- </request>
-
- <enum name="resize_edge">
- <description summary="edge values for resizing">
- These values are used to indicate which edge of a surface
- is being dragged in a resize operation.
- </description>
- <entry name="none" value="0"/>
- <entry name="top" value="1"/>
- <entry name="bottom" value="2"/>
- <entry name="left" value="4"/>
- <entry name="top_left" value="5"/>
- <entry name="bottom_left" value="6"/>
- <entry name="right" value="8"/>
- <entry name="top_right" value="9"/>
- <entry name="bottom_right" value="10"/>
- </enum>
-
- <request name="resize">
- <description summary="start an interactive resize">
- Start a user-driven, interactive resize of the surface.
-
- This request must be used in response to some sort of user action
- like a button press, key press, or touch down event. The passed
- serial is used to determine the type of interactive resize (touch,
- pointer, etc).
-
- The server may ignore resize requests depending on the state of
- the surface (e.g. fullscreen or maximized).
-
- If triggered, the client will receive configure events with the
- "resize" state enum value and the expected sizes. See the "resize"
- enum value for more details about what is required. The client
- must also acknowledge configure events using "ack_configure". After
- the resize is completed, the client will receive another "configure"
- event without the resize state.
-
- If triggered, the surface also will lose the focus of the device
- (wl_pointer, wl_touch, etc) used for the resize. It is up to the
- compositor to visually indicate that the resize is taking place,
- such as updating a pointer cursor, during the resize. There is no
- guarantee that the device focus will return when the resize is
- completed.
-
- The edges parameter specifies how the surface should be resized,
- and is one of the values of the resize_edge enum. The compositor
- may use this information to update the surface position for
- example when dragging the top left corner. The compositor may also
- use this information to adapt its behavior, e.g. choose an
- appropriate cursor image.
- </description>
- <arg name="seat" type="object" interface="wl_seat" summary="the wl_seat of the user event"/>
- <arg name="serial" type="uint" summary="the serial of the user event"/>
- <arg name="edges" type="uint" summary="which edge or corner is being dragged"/>
- </request>
-
- <enum name="state">
- <description summary="types of state on the surface">
- The different state values used on the surface. This is designed for
- state values like maximized, fullscreen. It is paired with the
- configure event to ensure that both the client and the compositor
- setting the state can be synchronized.
-
- States set in this way are double-buffered. They will get applied on
- the next commit.
- </description>
- <entry name="maximized" value="1" summary="the surface is maximized">
- <description summary="the surface is maximized">
- The surface is maximized. The window geometry specified in the configure
- event must be obeyed by the client.
- </description>
- </entry>
- <entry name="fullscreen" value="2" summary="the surface is fullscreen">
- <description summary="the surface is fullscreen">
- The surface is fullscreen. The window geometry specified in the configure
- event must be obeyed by the client.
- </description>
- </entry>
- <entry name="resizing" value="3" summary="the surface is being resized">
- <description summary="the surface is being resized">
- The surface is being resized. The window geometry specified in the
- configure event is a maximum; the client cannot resize beyond it.
- Clients that have aspect ratio or cell sizing configuration can use
- a smaller size, however.
- </description>
- </entry>
- <entry name="activated" value="4" summary="the surface is now activated">
- <description summary="the surface is now activated">
- Client window decorations should be painted as if the window is
- active. Do not assume this means that the window actually has
- keyboard or pointer focus.
- </description>
- </entry>
- </enum>
-
- <request name="set_max_size">
- <description summary="set the maximum size">
- Set a maximum size for the window.
-
- The client can specify a maximum size so that the compositor does
- not try to configure the window beyond this size.
-
- The width and height arguments are in window geometry coordinates.
- See xdg_surface.set_window_geometry.
-
- Values set in this way are double-buffered. They will get applied
- on the next commit.
-
- The compositor can use this information to allow or disallow
- different states like maximize or fullscreen and draw accurate
- animations.
-
- Similarly, a tiling window manager may use this information to
- place and resize client windows in a more effective way.
-
- The client should not rely on the compositor to obey the maximum
- size. The compositor may decide to ignore the values set by the
- client and request a larger size.
-
- If never set, or a value of zero in the request, means that the
- client has no expected maximum size in the given dimension.
- As a result, a client wishing to reset the maximum size
- to an unspecified state can use zero for width and height in the
- request.
-
- Requesting a maximum size to be smaller than the minimum size of
- a surface is illegal and will result in a protocol error.
-
- The width and height must be greater than or equal to zero. Using
- strictly negative values for width and height will result in a
- protocol error.
- </description>
- <arg name="width" type="int"/>
- <arg name="height" type="int"/>
- </request>
-
- <request name="set_min_size">
- <description summary="set the minimum size">
- Set a minimum size for the window.
-
- The client can specify a minimum size so that the compositor does
- not try to configure the window below this size.
-
- The width and height arguments are in window geometry coordinates.
- See xdg_surface.set_window_geometry.
-
- Values set in this way are double-buffered. They will get applied
- on the next commit.
-
- The compositor can use this information to allow or disallow
- different states like maximize or fullscreen and draw accurate
- animations.
-
- Similarly, a tiling window manager may use this information to
- place and resize client windows in a more effective way.
-
- The client should not rely on the compositor to obey the minimum
- size. The compositor may decide to ignore the values set by the
- client and request a smaller size.
-
- If never set, or a value of zero in the request, means that the
- client has no expected minimum size in the given dimension.
- As a result, a client wishing to reset the minimum size
- to an unspecified state can use zero for width and height in the
- request.
-
- Requesting a minimum size to be larger than the maximum size of
- a surface is illegal and will result in a protocol error.
-
- The width and height must be greater than or equal to zero. Using
- strictly negative values for width and height will result in a
- protocol error.
- </description>
- <arg name="width" type="int"/>
- <arg name="height" type="int"/>
- </request>
-
- <request name="set_maximized">
- <description summary="maximize the window">
- Maximize the surface.
-
- After requesting that the surface should be maximized, the compositor
- will respond by emitting a configure event with the "maximized" state
- and the required window geometry. The client should then update its
- content, drawing it in a maximized state, i.e. without shadow or other
- decoration outside of the window geometry. The client must also
- acknowledge the configure when committing the new content (see
- ack_configure).
-
- It is up to the compositor to decide how and where to maximize the
- surface, for example which output and what region of the screen should
- be used.
-
- If the surface was already maximized, the compositor will still emit
- a configure event with the "maximized" state.
- </description>
- </request>
-
- <request name="unset_maximized">
- <description summary="unmaximize the window">
- Unmaximize the surface.
-
- After requesting that the surface should be unmaximized, the compositor
- will respond by emitting a configure event without the "maximized"
- state. If available, the compositor will include the window geometry
- dimensions the window had prior to being maximized in the configure
- request. The client must then update its content, drawing it in a
- regular state, i.e. potentially with shadow, etc. The client must also
- acknowledge the configure when committing the new content (see
- ack_configure).
-
- It is up to the compositor to position the surface after it was
- unmaximized; usually the position the surface had before maximizing, if
- applicable.
-
- If the surface was already not maximized, the compositor will still
- emit a configure event without the "maximized" state.
- </description>
- </request>
-
- <request name="set_fullscreen">
- <description summary="set the window as fullscreen on a monitor">
- Make the surface fullscreen.
-
- You can specify an output that you would prefer to be fullscreen.
- If this value is NULL, it's up to the compositor to choose which
- display will be used to map this surface.
-
- If the surface doesn't cover the whole output, the compositor will
- position the surface in the center of the output and compensate with
- black borders filling the rest of the output.
- </description>
- <arg name="output" type="object" interface="wl_output" allow-null="true"/>
- </request>
- <request name="unset_fullscreen" />
-
- <request name="set_minimized">
- <description summary="set the window as minimized">
- Request that the compositor minimize your surface. There is no
- way to know if the surface is currently minimized, nor is there
- any way to unset minimization on this surface.
-
- If you are looking to throttle redrawing when minimized, please
- instead use the wl_surface.frame event for this, as this will
- also work with live previews on windows in Alt-Tab, Expose or
- similar compositor features.
- </description>
- </request>
-
- <event name="configure">
- <description summary="suggest a surface change">
- This configure event asks the client to resize its toplevel surface or
- to change its state. The configured state should not be applied
- immediately. See xdg_surface.configure for details.
-
- The width and height arguments specify a hint to the window
- about how its surface should be resized in window geometry
- coordinates. See set_window_geometry.
-
- If the width or height arguments are zero, it means the client
- should decide its own window dimension. This may happen when the
- compositor needs to configure the state of the surface but doesn't
- have any information about any previous or expected dimension.
-
- The states listed in the event specify how the width/height
- arguments should be interpreted, and possibly how it should be
- drawn.
-
- Clients must send an ack_configure in response to this event. See
- xdg_surface.configure and xdg_surface.ack_configure for details.
- </description>
- <arg name="width" type="int"/>
- <arg name="height" type="int"/>
- <arg name="states" type="array"/>
- </event>
-
- <event name="close">
- <description summary="surface wants to be closed">
- The close event is sent by the compositor when the user
- wants the surface to be closed. This should be equivalent to
- the user clicking the close button in client-side decorations,
- if your application has any.
-
- This is only a request that the user intends to close the
- window. The client may choose to ignore this request, or show
- a dialog to ask the user to save their data, etc.
- </description>
- </event>
- </interface>
-
- <interface name="zxdg_popup_v6" version="1">
- <description summary="short-lived, popup surfaces for menus">
- A popup surface is a short-lived, temporary surface. It can be used to
- implement for example menus, popovers, tooltips and other similar user
- interface concepts.
-
- A popup can be made to take an explicit grab. See xdg_popup.grab for
- details.
-
- When the popup is dismissed, a popup_done event will be sent out, and at
- the same time the surface will be unmapped. See the xdg_popup.popup_done
- event for details.
-
- Explicitly destroying the xdg_popup object will also dismiss the popup and
- unmap the surface. Clients that want to dismiss the popup when another
- surface of their own is clicked should dismiss the popup using the destroy
- request.
-
- The parent surface must have either the xdg_toplevel or xdg_popup surface
- role.
-
- A newly created xdg_popup will be stacked on top of all previously created
- xdg_popup surfaces associated with the same xdg_toplevel.
-
- The parent of an xdg_popup must be mapped (see the xdg_surface
- description) before the xdg_popup itself.
-
- The x and y arguments passed when creating the popup object specify
- where the top left of the popup should be placed, relative to the
- local surface coordinates of the parent surface. See
- xdg_surface.get_popup. An xdg_popup must intersect with or be at least
- partially adjacent to its parent surface.
-
- The client must call wl_surface.commit on the corresponding wl_surface
- for the xdg_popup state to take effect.
- </description>
-
- <enum name="error">
- <entry name="invalid_grab" value="0"
- summary="tried to grab after being mapped"/>
- </enum>
-
- <request name="destroy" type="destructor">
- <description summary="remove xdg_popup interface">
- This destroys the popup. Explicitly destroying the xdg_popup
- object will also dismiss the popup, and unmap the surface.
-
- If this xdg_popup is not the "topmost" popup, a protocol error
- will be sent.
- </description>
- </request>
-
- <request name="grab">
- <description summary="make the popup take an explicit grab">
- This request makes the created popup take an explicit grab. An explicit
- grab will be dismissed when the user dismisses the popup, or when the
- client destroys the xdg_popup. This can be done by the user clicking
- outside the surface, using the keyboard, or even locking the screen
- through closing the lid or a timeout.
-
- If the compositor denies the grab, the popup will be immediately
- dismissed.
-
- This request must be used in response to some sort of user action like a
- button press, key press, or touch down event. The serial number of the
- event should be passed as 'serial'.
-
- The parent of a grabbing popup must either be an xdg_toplevel surface or
- another xdg_popup with an explicit grab. If the parent is another
- xdg_popup it means that the popups are nested, with this popup now being
- the topmost popup.
-
- Nested popups must be destroyed in the reverse order they were created
- in, e.g. the only popup you are allowed to destroy at all times is the
- topmost one.
-
- When compositors choose to dismiss a popup, they may dismiss every
- nested grabbing popup as well. When a compositor dismisses popups, it
- will follow the same dismissing order as required from the client.
-
- The parent of a grabbing popup must either be another xdg_popup with an
- active explicit grab, or an xdg_popup or xdg_toplevel, if there are no
- explicit grabs already taken.
-
- If the topmost grabbing popup is destroyed, the grab will be returned to
- the parent of the popup, if that parent previously had an explicit grab.
-
- If the parent is a grabbing popup which has already been dismissed, this
- popup will be immediately dismissed. If the parent is a popup that did
- not take an explicit grab, an error will be raised.
-
- During a popup grab, the client owning the grab will receive pointer
- and touch events for all their surfaces as normal (similar to an
- "owner-events" grab in X11 parlance), while the top most grabbing popup
- will always have keyboard focus.
- </description>
- <arg name="seat" type="object" interface="wl_seat"
- summary="the wl_seat of the user event"/>
- <arg name="serial" type="uint" summary="the serial of the user event"/>
- </request>
-
- <event name="configure">
- <description summary="configure the popup surface">
- This event asks the popup surface to configure itself given the
- configuration. The configured state should not be applied immediately.
- See xdg_surface.configure for details.
-
- The x and y arguments represent the position the popup was placed at
- given the xdg_positioner rule, relative to the upper left corner of the
- window geometry of the parent surface.
- </description>
- <arg name="x" type="int"
- summary="x position relative to parent surface window geometry"/>
- <arg name="y" type="int"
- summary="y position relative to parent surface window geometry"/>
- <arg name="width" type="int" summary="window geometry width"/>
- <arg name="height" type="int" summary="window geometry height"/>
- </event>
-
- <event name="popup_done">
- <description summary="popup interaction is done">
- The popup_done event is sent out when a popup is dismissed by the
- compositor. The client should destroy the xdg_popup object at this
- point.
- </description>
- </event>
-
- </interface>
-</protocol>
diff --git a/gpu_display/src/display_wl.c b/gpu_display/src/display_wl.c
index b3eca679e..b243120be 100644
--- a/gpu_display/src/display_wl.c
+++ b/gpu_display/src/display_wl.c
@@ -11,6 +11,11 @@
#include <errno.h>
#include <fcntl.h>
+// Android edit:
+// #include <linux/input-event-codes.h>
+#ifndef BTN_LEFT
+#define BTN_LEFT 0x110
+#endif
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
@@ -23,13 +28,42 @@
#include "aura-shell.h"
#include "linux-dmabuf-unstable-v1.h"
#include "viewporter.h"
-#include "xdg-shell-unstable-v6.h"
+#ifdef ANDROID
+#include "xdg-shell-client-protocol.h"
+#else
+#include "xdg-shell.h"
+#endif
+#include "virtio-gpu-metadata-v1.h"
#include <wayland-client-core.h>
#include <wayland-client-protocol.h>
#include <wayland-client.h>
#define DEFAULT_SCALE 2
#define MAX_BUFFER_COUNT 64
+#define EVENT_BUF_SIZE 256
+
+const int32_t DWL_KEYBOARD_KEY_STATE_RELEASED = WL_KEYBOARD_KEY_STATE_RELEASED;
+const int32_t DWL_KEYBOARD_KEY_STATE_PRESSED = WL_KEYBOARD_KEY_STATE_PRESSED;
+
+const uint32_t DWL_EVENT_TYPE_KEYBOARD_ENTER = 0x00;
+const uint32_t DWL_EVENT_TYPE_KEYBOARD_LEAVE = 0x01;
+const uint32_t DWL_EVENT_TYPE_KEYBOARD_KEY = 0x02;
+const uint32_t DWL_EVENT_TYPE_POINTER_ENTER = 0x10;
+const uint32_t DWL_EVENT_TYPE_POINTER_LEAVE = 0x11;
+const uint32_t DWL_EVENT_TYPE_POINTER_MOVE = 0x12;
+const uint32_t DWL_EVENT_TYPE_POINTER_BUTTON = 0x13;
+const uint32_t DWL_EVENT_TYPE_TOUCH_DOWN = 0x20;
+const uint32_t DWL_EVENT_TYPE_TOUCH_UP = 0x21;
+const uint32_t DWL_EVENT_TYPE_TOUCH_MOTION = 0x22;
+
+const uint32_t DWL_SURFACE_FLAG_RECEIVE_INPUT = 1 << 0;
+const uint32_t DWL_SURFACE_FLAG_HAS_ALPHA = 1 << 1;
+
+struct dwl_event {
+ const void *surface_descriptor;
+ uint32_t event_type;
+ int32_t params[3];
+};
struct dwl_context;
@@ -38,12 +72,12 @@ struct interfaces {
struct wl_compositor *compositor;
struct wl_subcompositor *subcompositor;
struct wl_shm *shm;
- struct wl_shell *shell;
struct wl_seat *seat;
struct zaura_shell *aura; // optional
struct zwp_linux_dmabuf_v1 *linux_dmabuf;
- struct zxdg_shell_v6 *xdg_shell;
+ struct xdg_wm_base *xdg_wm_base;
struct wp_viewporter *viewporter; // optional
+ struct wp_virtio_gpu_metadata_v1 *virtio_gpu_metadata; // optional
};
struct output {
@@ -56,11 +90,28 @@ struct output {
bool internal;
};
+struct input {
+ struct wl_keyboard *wl_keyboard;
+ struct wl_pointer *wl_pointer;
+ struct wl_surface *keyboard_input_surface;
+ struct wl_surface *pointer_input_surface;
+ int32_t pointer_x;
+ int32_t pointer_y;
+ bool pointer_lbutton_state;
+};
+
struct dwl_context {
struct wl_display *display;
+ struct dwl_surface *surfaces[MAX_BUFFER_COUNT];
+ struct dwl_dmabuf *dmabufs[MAX_BUFFER_COUNT];
struct interfaces ifaces;
+ struct input input;
bool output_added;
struct output outputs[8];
+
+ struct dwl_event event_cbuf[EVENT_BUF_SIZE];
+ size_t event_read_pos;
+ size_t event_write_pos;
};
#define outputs_for_each(context, pos, output) \
@@ -71,20 +122,24 @@ struct dwl_context {
struct dwl_dmabuf {
uint32_t width;
uint32_t height;
+ uint32_t import_id;
bool in_use;
struct wl_buffer *buffer;
+ struct dwl_context *context;
};
struct dwl_surface {
struct dwl_context *context;
- struct wl_surface *surface;
+ struct wl_surface *wl_surface;
struct zaura_surface *aura;
- struct zxdg_surface_v6 *xdg;
- struct zxdg_toplevel_v6 *toplevel;
+ struct xdg_surface *xdg_surface;
+ struct xdg_toplevel *xdg_toplevel;
struct wp_viewport *viewport;
+ struct wp_virtio_gpu_surface_metadata_v1 *virtio_gpu_surface_metadata;
struct wl_subsurface *subsurface;
uint32_t width;
uint32_t height;
+ uint32_t surface_id;
double scale;
bool close_requested;
size_t buffer_count;
@@ -183,6 +238,290 @@ static const struct zaura_output_listener aura_output_listener = {
.connection = aura_output_connection,
.device_scale_factor = aura_output_device_scale_factor};
+static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base,
+ uint32_t serial)
+{
+ (void)data;
+ xdg_wm_base_pong(xdg_wm_base, serial);
+}
+
+static const struct xdg_wm_base_listener xdg_wm_base_listener = {
+ .ping = xdg_wm_base_ping,
+};
+
+
+static void wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t format, int32_t fd, uint32_t size)
+{
+ (void)data;
+ (void)wl_keyboard;
+ (void)fd;
+ (void)size;
+ if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
+ syslog(LOG_ERR, "wl_keyboard: invalid keymap format");
+ }
+}
+
+static void dwl_context_push_event(struct dwl_context *self,
+ struct dwl_event *event)
+{
+ if (!self)
+ return;
+
+ memcpy(self->event_cbuf + self->event_write_pos, event,
+ sizeof(struct dwl_event));
+
+ if (++self->event_write_pos == EVENT_BUF_SIZE)
+ self->event_write_pos = 0;
+}
+
+static void wl_keyboard_enter(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, struct wl_surface *surface,
+ struct wl_array *keys)
+{
+ (void)wl_keyboard;
+ (void)serial;
+ (void)surface;
+ struct dwl_context *context = (struct dwl_context*)data;
+ struct input *input = &context->input;
+ uint32_t *key;
+ struct dwl_event event = {0};
+ input->keyboard_input_surface = surface;
+ wl_array_for_each(key, keys) {
+ event.surface_descriptor = input->keyboard_input_surface;
+ event.event_type = DWL_EVENT_TYPE_KEYBOARD_KEY;
+ event.params[0] = (int32_t)*key;
+ event.params[1] = DWL_KEYBOARD_KEY_STATE_PRESSED;
+ dwl_context_push_event(context, &event);
+ }
+}
+
+static void wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, uint32_t time, uint32_t key,
+ uint32_t state)
+{
+ struct dwl_context *context = (struct dwl_context*)data;
+ struct input *input = &context->input;
+ (void)wl_keyboard;
+ (void)serial;
+ (void)time;
+ struct dwl_event event = {0};
+ event.surface_descriptor = input->keyboard_input_surface;
+ event.event_type = DWL_EVENT_TYPE_KEYBOARD_KEY;
+ event.params[0] = (int32_t)key;
+ event.params[1] = state;
+ dwl_context_push_event(context, &event);
+}
+
+static void wl_keyboard_leave(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, struct wl_surface *surface)
+{
+ struct dwl_context *context = (struct dwl_context*)data;
+ struct input *input = &context->input;
+ struct dwl_event event = {0};
+ (void)wl_keyboard;
+ (void)serial;
+ (void)surface;
+
+ event.surface_descriptor = input->keyboard_input_surface;
+ event.event_type = DWL_EVENT_TYPE_KEYBOARD_LEAVE;
+ dwl_context_push_event(context, &event);
+
+ input->keyboard_input_surface = NULL;
+}
+
+static void wl_keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard,
+ uint32_t serial, uint32_t mods_depressed,
+ uint32_t mods_latched, uint32_t mods_locked,
+ uint32_t group)
+{
+ (void)data;
+ (void)wl_keyboard;
+ (void)serial;
+ (void)mods_depressed;
+ (void)mods_latched;
+ (void)mods_locked;
+ (void)group;
+}
+
+static void wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
+ int32_t rate, int32_t delay)
+{
+ (void)data;
+ (void)wl_keyboard;
+ (void)rate;
+ (void)delay;
+}
+
+static const struct wl_keyboard_listener wl_keyboard_listener = {
+ .keymap = wl_keyboard_keymap,
+ .enter = wl_keyboard_enter,
+ .leave = wl_keyboard_leave,
+ .key = wl_keyboard_key,
+ .modifiers = wl_keyboard_modifiers,
+ .repeat_info = wl_keyboard_repeat_info,
+};
+
+static void pointer_enter_handler(void *data, struct wl_pointer *wl_pointer,
+ uint32_t serial, struct wl_surface *surface,
+ wl_fixed_t x, wl_fixed_t y)
+{
+ struct dwl_context *context = (struct dwl_context*)data;
+ struct input *input = &context->input;
+ (void)wl_pointer;
+ (void)serial;
+
+ input->pointer_input_surface = surface;
+ input->pointer_x = wl_fixed_to_int(x);
+ input->pointer_y = wl_fixed_to_int(y);
+}
+
+static void pointer_leave_handler(void *data, struct wl_pointer *wl_pointer,
+ uint32_t serial, struct wl_surface *surface)
+{
+ struct dwl_context *context = (struct dwl_context*)data;
+ struct input *input = &context->input;
+ (void)wl_pointer;
+ (void)serial;
+ (void)surface;
+
+ input->pointer_input_surface = NULL;
+}
+
+static void pointer_motion_handler(void *data, struct wl_pointer *wl_pointer,
+ uint32_t time, wl_fixed_t x, wl_fixed_t y)
+{
+ struct dwl_context *context = (struct dwl_context*)data;
+ struct input *input = &context->input;
+ struct dwl_event event = {0};
+ (void)wl_pointer;
+ (void)time;
+
+ input->pointer_x = wl_fixed_to_int(x);
+ input->pointer_y = wl_fixed_to_int(y);
+ if (input->pointer_lbutton_state) {
+ event.surface_descriptor = input->pointer_input_surface;
+ event.event_type = DWL_EVENT_TYPE_TOUCH_MOTION;
+ event.params[0] = input->pointer_x;
+ event.params[1] = input->pointer_y;
+ dwl_context_push_event(context, &event);
+ }
+}
+
+static void pointer_button_handler(void *data, struct wl_pointer *wl_pointer,
+ uint32_t serial, uint32_t time, uint32_t button,
+ uint32_t state)
+{
+ struct dwl_context *context = (struct dwl_context*)data;
+ struct input *input = &context->input;
+ (void)wl_pointer;
+ (void)time;
+ (void)serial;
+
+ // we track only the left mouse button since we emulate a single touch device
+ if (button == BTN_LEFT) {
+ input->pointer_lbutton_state = state != 0;
+ struct dwl_event event = {0};
+ event.surface_descriptor = input->pointer_input_surface;
+ event.event_type = (state != 0)?
+ DWL_EVENT_TYPE_TOUCH_DOWN:DWL_EVENT_TYPE_TOUCH_UP;
+ event.params[0] = input->pointer_x;
+ event.params[1] = input->pointer_y;
+ dwl_context_push_event(context, &event);
+ }
+}
+
+static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer)
+{
+ (void)data;
+ (void)wl_pointer;
+}
+
+static void pointer_axis_handler(void *data, struct wl_pointer *wl_pointer,
+ uint32_t time, uint32_t axis, wl_fixed_t value)
+{
+ (void)data;
+ (void)wl_pointer;
+ (void)time;
+ (void)axis;
+ (void)value;
+}
+
+static void wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer,
+ uint32_t axis_source)
+{
+ (void)data;
+ (void)wl_pointer;
+ (void)axis_source;
+}
+
+static void wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer,
+ uint32_t time, uint32_t axis)
+{
+ (void)data;
+ (void)wl_pointer;
+ (void)time;
+ (void)axis;
+}
+
+static void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer,
+ uint32_t axis, int32_t discrete)
+{
+ (void)data;
+ (void)wl_pointer;
+ (void)axis;
+ (void)discrete;
+}
+
+const struct wl_pointer_listener wl_pointer_listener = {
+ .enter = pointer_enter_handler,
+ .leave = pointer_leave_handler,
+ .motion = pointer_motion_handler,
+ .button = pointer_button_handler,
+ .axis = pointer_axis_handler,
+ .frame = wl_pointer_frame,
+ .axis_source = wl_pointer_axis_source,
+ .axis_stop = wl_pointer_axis_stop,
+ .axis_discrete = wl_pointer_axis_discrete,
+};
+
+static void wl_seat_capabilities(void *data, struct wl_seat *wl_seat,
+ uint32_t capabilities)
+{
+ struct dwl_context *context = (struct dwl_context*)data;
+ struct input *input = &context->input;
+ bool have_keyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD;
+ bool have_pointer = capabilities & WL_SEAT_CAPABILITY_POINTER;
+
+ if (have_keyboard && input->wl_keyboard == NULL) {
+ input->wl_keyboard = wl_seat_get_keyboard(wl_seat);
+ wl_keyboard_add_listener(input->wl_keyboard, &wl_keyboard_listener, context);
+ } else if (!have_keyboard && input->wl_keyboard != NULL) {
+ wl_keyboard_release(input->wl_keyboard);
+ input->wl_keyboard = NULL;
+ }
+
+ if (have_pointer && input->wl_pointer == NULL) {
+ input->wl_pointer = wl_seat_get_pointer(wl_seat);
+ wl_pointer_add_listener(input->wl_pointer, &wl_pointer_listener, context);
+ } else if (!have_pointer && input->wl_pointer != NULL) {
+ wl_pointer_release(input->wl_pointer);
+ input->wl_pointer = NULL;
+ }
+}
+
+static void wl_seat_name(void *data, struct wl_seat *wl_seat, const char *name)
+{
+ (void)data;
+ (void)wl_seat;
+ (void)name;
+}
+
+static const struct wl_seat_listener wl_seat_listener = {
+ .capabilities = wl_seat_capabilities,
+ .name = wl_seat_name,
+};
+
static void dwl_context_output_add(struct dwl_context *context,
struct wl_output *wl_output, uint32_t id)
{
@@ -249,20 +588,21 @@ static void registry_global(void *data, struct wl_registry *registry,
{
(void)version;
struct interfaces *ifaces = (struct interfaces *)data;
- if (strcmp(interface, "wl_compositor") == 0) {
+ if (strcmp(interface, wl_compositor_interface.name) == 0) {
ifaces->compositor = (struct wl_compositor *)wl_registry_bind(
registry, id, &wl_compositor_interface, 3);
- } else if (strcmp(interface, "wl_subcompositor") == 0) {
+ } else if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
ifaces->subcompositor =
(struct wl_subcompositor *)wl_registry_bind(
registry, id, &wl_subcompositor_interface, 1);
- } else if (strcmp(interface, "wl_shm") == 0) {
+ } else if (strcmp(interface, wl_shm_interface.name) == 0) {
ifaces->shm = (struct wl_shm *)wl_registry_bind(
registry, id, &wl_shm_interface, 1);
- } else if (strcmp(interface, "wl_seat") == 0) {
+ } else if (strcmp(interface, wl_seat_interface.name) == 0) {
ifaces->seat = (struct wl_seat *)wl_registry_bind(
registry, id, &wl_seat_interface, 5);
- } else if (strcmp(interface, "wl_output") == 0) {
+ wl_seat_add_listener(ifaces->seat, &wl_seat_listener, ifaces->context);
+ } else if (strcmp(interface, wl_output_interface.name) == 0) {
struct wl_output *output = (struct wl_output *)wl_registry_bind(
registry, id, &wl_output_interface, 2);
dwl_context_output_add(ifaces->context, output, id);
@@ -273,12 +613,18 @@ static void registry_global(void *data, struct wl_registry *registry,
ifaces->linux_dmabuf =
(struct zwp_linux_dmabuf_v1 *)wl_registry_bind(
registry, id, &zwp_linux_dmabuf_v1_interface, 1);
- } else if (strcmp(interface, "zxdg_shell_v6") == 0) {
- ifaces->xdg_shell = (struct zxdg_shell_v6 *)wl_registry_bind(
- registry, id, &zxdg_shell_v6_interface, 1);
+ } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
+ ifaces->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(
+ registry, id, &xdg_wm_base_interface, 1);
+ xdg_wm_base_add_listener(ifaces->xdg_wm_base, &xdg_wm_base_listener,
+ NULL);
} else if (strcmp(interface, "wp_viewporter") == 0) {
ifaces->viewporter = (struct wp_viewporter *)wl_registry_bind(
registry, id, &wp_viewporter_interface, 1);
+ } else if (strcmp(interface, "wp_virtio_gpu_metadata_v1") == 0) {
+ ifaces->virtio_gpu_metadata =
+ (struct wp_virtio_gpu_metadata_v1 *)wl_registry_bind(
+ registry, id, &wp_virtio_gpu_metadata_v1_interface, 1);
}
}
@@ -305,37 +651,37 @@ static const struct wl_registry_listener registry_listener = {
.global = registry_global, .global_remove = global_remove};
static void toplevel_configure(void *data,
- struct zxdg_toplevel_v6 *zxdg_toplevel_v6,
+ struct xdg_toplevel *xdg_toplevel,
int32_t width, int32_t height,
struct wl_array *states)
{
(void)data;
- (void)zxdg_toplevel_v6;
+ (void)xdg_toplevel;
(void)width;
(void)height;
(void)states;
}
static void toplevel_close(void *data,
- struct zxdg_toplevel_v6 *zxdg_toplevel_v6)
+ struct xdg_toplevel *xdg_toplevel)
{
- (void)zxdg_toplevel_v6;
+ (void)xdg_toplevel;
struct dwl_surface *surface = (struct dwl_surface *)data;
surface->close_requested = true;
}
-static const struct zxdg_toplevel_v6_listener toplevel_listener = {
+static const struct xdg_toplevel_listener toplevel_listener = {
.configure = toplevel_configure, .close = toplevel_close};
static void xdg_surface_configure_handler(void *data,
- struct zxdg_surface_v6 *xdg_surface,
+ struct xdg_surface *xdg_surface,
uint32_t serial)
{
(void)data;
- zxdg_surface_v6_ack_configure(xdg_surface, serial);
+ xdg_surface_ack_configure(xdg_surface, serial);
}
-static const struct zxdg_surface_v6_listener xdg_surface_listener = {
+static const struct xdg_surface_listener xdg_surface_listener = {
.configure = xdg_surface_configure_handler
};
@@ -428,8 +774,8 @@ bool dwl_context_setup(struct dwl_context *self, const char *socket_path)
syslog(LOG_ERR, "missing interface linux_dmabuf");
goto fail;
}
- if (!ifaces->xdg_shell) {
- syslog(LOG_ERR, "missing interface xdg_shell");
+ if (!ifaces->xdg_wm_base) {
+ syslog(LOG_ERR, "missing interface xdg_wm_base");
goto fail;
}
@@ -486,10 +832,52 @@ static void dmabuf_buffer_release(void *data, struct wl_buffer *buffer)
static const struct wl_buffer_listener dmabuf_buffer_listener = {
.release = dmabuf_buffer_release};
-struct dwl_dmabuf *dwl_context_dmabuf_new(struct dwl_context *self, int fd,
- uint32_t offset, uint32_t stride,
- uint64_t modifiers, uint32_t width,
- uint32_t height, uint32_t fourcc)
+static bool dwl_context_add_dmabuf(struct dwl_context *self,
+ struct dwl_dmabuf *dmabuf)
+{
+ size_t i;
+ for (i = 0; i < MAX_BUFFER_COUNT; i++) {
+ if (!self->dmabufs[i]) {
+ self->dmabufs[i] = dmabuf;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void dwl_context_remove_dmabuf(struct dwl_context *self,
+ uint32_t import_id)
+{
+ size_t i;
+ for (i = 0; i < MAX_BUFFER_COUNT; i++) {
+ if (self->dmabufs[i] &&
+ self->dmabufs[i]->import_id == import_id) {
+ self->dmabufs[i] = NULL;
+ }
+ }
+}
+
+static struct dwl_dmabuf *dwl_context_get_dmabuf(struct dwl_context *self,
+ uint32_t import_id)
+{
+ size_t i;
+ for (i = 0; i < MAX_BUFFER_COUNT; i++) {
+ if (self->dmabufs[i] &&
+ self->dmabufs[i]->import_id == import_id) {
+ return self->dmabufs[i];
+ }
+ }
+
+ return NULL;
+}
+
+struct dwl_dmabuf *dwl_context_dmabuf_new(struct dwl_context *self,
+ uint32_t import_id,
+ int fd, uint32_t offset,
+ uint32_t stride, uint64_t modifier,
+ uint32_t width, uint32_t height,
+ uint32_t fourcc)
{
struct dwl_dmabuf *dmabuf = calloc(1, sizeof(struct dwl_dmabuf));
if (!dmabuf) {
@@ -511,8 +899,8 @@ struct dwl_dmabuf *dwl_context_dmabuf_new(struct dwl_context *self, int fd,
zwp_linux_buffer_params_v1_add_listener(params, &linux_buffer_listener,
dmabuf);
zwp_linux_buffer_params_v1_add(params, fd, 0 /* plane_idx */, offset,
- stride, modifiers >> 32,
- (uint32_t)modifiers);
+ stride, modifier >> 32,
+ (uint32_t)modifier);
zwp_linux_buffer_params_v1_create(params, width, height, fourcc, 0);
wl_display_roundtrip(self->display);
zwp_linux_buffer_params_v1_destroy(params);
@@ -525,11 +913,20 @@ struct dwl_dmabuf *dwl_context_dmabuf_new(struct dwl_context *self, int fd,
wl_buffer_add_listener(dmabuf->buffer, &dmabuf_buffer_listener, dmabuf);
+ dmabuf->import_id = import_id;
+ dmabuf->context = self;
+ if (!dwl_context_add_dmabuf(self, dmabuf)) {
+ syslog(LOG_ERR, "failed to add dmabuf to context");
+ free(dmabuf);
+ return NULL;
+ }
+
return dmabuf;
}
void dwl_dmabuf_destroy(struct dwl_dmabuf **self)
{
+ dwl_context_remove_dmabuf((*self)->context, (*self)->import_id);
wl_buffer_destroy((*self)->buffer);
free(*self);
*self = NULL;
@@ -552,14 +949,57 @@ static void surface_buffer_release(void *data, struct wl_buffer *buffer)
static const struct wl_buffer_listener surface_buffer_listener = {
.release = surface_buffer_release};
+static struct dwl_surface *dwl_context_get_surface(struct dwl_context *self,
+ uint32_t surface_id)
+{
+ size_t i;
+ for (i = 0; i < MAX_BUFFER_COUNT; i++) {
+ if (self->surfaces[i] &&
+ self->surfaces[i]->surface_id == surface_id) {
+ return self->surfaces[i];
+ }
+ }
+
+ return NULL;
+}
+
+static bool dwl_context_add_surface(struct dwl_context *self,
+ struct dwl_surface *surface)
+{
+ size_t i;
+ for (i = 0; i < MAX_BUFFER_COUNT; i++) {
+ if (!self->surfaces[i]) {
+ self->surfaces[i] = surface;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void dwl_context_remove_surface(struct dwl_context *self,
+ uint32_t surface_id)
+{
+ size_t i;
+ for (i = 0; i < MAX_BUFFER_COUNT; i++) {
+ if (self->surfaces[i] &&
+ self->surfaces[i]->surface_id == surface_id) {
+ self->surfaces[i] = NULL;
+ }
+ }
+}
+
struct dwl_surface *dwl_context_surface_new(struct dwl_context *self,
- struct dwl_surface *parent,
+ uint32_t parent_id,
+ uint32_t surface_id,
int shm_fd, size_t shm_size,
size_t buffer_size, uint32_t width,
- uint32_t height, uint32_t stride)
+ uint32_t height, uint32_t stride,
+ uint32_t flags)
{
if (buffer_size == 0)
return NULL;
+
size_t buffer_count = shm_size / buffer_size;
if (buffer_count == 0)
return NULL;
@@ -571,13 +1011,13 @@ struct dwl_surface *dwl_context_surface_new(struct dwl_context *self,
sizeof(struct wl_buffer *) * buffer_count);
if (!disp_surface)
return NULL;
+
disp_surface->context = self;
disp_surface->width = width;
disp_surface->height = height;
disp_surface->scale = DEFAULT_SCALE;
disp_surface->buffer_count = buffer_count;
- struct wl_region *region = NULL;
struct wl_shm_pool *shm_pool =
wl_shm_create_pool(self->ifaces.shm, shm_fd, shm_size);
if (!shm_pool) {
@@ -586,10 +1026,12 @@ struct dwl_surface *dwl_context_surface_new(struct dwl_context *self,
}
size_t i;
+ uint32_t format = (flags & DWL_SURFACE_FLAG_HAS_ALPHA)?
+ WL_SHM_FORMAT_ARGB8888:WL_SHM_FORMAT_XRGB8888;
+
for (i = 0; i < buffer_count; i++) {
struct wl_buffer *buffer = wl_shm_pool_create_buffer(
- shm_pool, buffer_size * i, width, height, stride,
- WL_SHM_FORMAT_ARGB8888);
+ shm_pool, buffer_size * i, width, height, stride, format);
if (!buffer) {
syslog(LOG_ERR, "failed to create buffer");
goto fail;
@@ -601,49 +1043,58 @@ struct dwl_surface *dwl_context_surface_new(struct dwl_context *self,
wl_buffer_add_listener(disp_surface->buffers[i],
&surface_buffer_listener, disp_surface);
- disp_surface->surface =
+ disp_surface->wl_surface =
wl_compositor_create_surface(self->ifaces.compositor);
- if (!disp_surface->surface) {
+ if (!disp_surface->wl_surface) {
syslog(LOG_ERR, "failed to make surface");
goto fail;
}
- wl_surface_add_listener(disp_surface->surface, &surface_listener,
+ wl_surface_add_listener(disp_surface->wl_surface, &surface_listener,
disp_surface);
- region = wl_compositor_create_region(self->ifaces.compositor);
+ struct wl_region *region = wl_compositor_create_region(self->ifaces.compositor);
if (!region) {
syslog(LOG_ERR, "failed to make region");
goto fail;
}
- wl_region_add(region, 0, 0, width, height);
- wl_surface_set_opaque_region(disp_surface->surface, region);
- if (!parent) {
- disp_surface->xdg = zxdg_shell_v6_get_xdg_surface(
- self->ifaces.xdg_shell, disp_surface->surface);
- if (!disp_surface->xdg) {
+ bool receive_input = (flags & DWL_SURFACE_FLAG_RECEIVE_INPUT);
+ if (receive_input) {
+ wl_region_add(region, 0, 0, width, height);
+ } else {
+ // We have to add an empty region because NULL doesn't work
+ wl_region_add(region, 0, 0, 0, 0);
+ }
+ wl_surface_set_input_region(disp_surface->wl_surface, region);
+ wl_surface_set_opaque_region(disp_surface->wl_surface, region);
+ wl_region_destroy(region);
+
+ if (!parent_id) {
+ disp_surface->xdg_surface = xdg_wm_base_get_xdg_surface(
+ self->ifaces.xdg_wm_base, disp_surface->wl_surface);
+ if (!disp_surface->xdg_surface) {
syslog(LOG_ERR, "failed to make xdg shell surface");
goto fail;
}
- disp_surface->toplevel =
- zxdg_surface_v6_get_toplevel(disp_surface->xdg);
- if (!disp_surface->toplevel) {
+ disp_surface->xdg_toplevel =
+ xdg_surface_get_toplevel(disp_surface->xdg_surface);
+ if (!disp_surface->xdg_toplevel) {
syslog(LOG_ERR,
"failed to make toplevel xdg shell surface");
goto fail;
}
- zxdg_toplevel_v6_set_title(disp_surface->toplevel, "crosvm");
- zxdg_toplevel_v6_add_listener(disp_surface->toplevel,
+ xdg_toplevel_set_title(disp_surface->xdg_toplevel, "crosvm");
+ xdg_toplevel_add_listener(disp_surface->xdg_toplevel,
&toplevel_listener, disp_surface);
- zxdg_surface_v6_add_listener(disp_surface->xdg,
+ xdg_surface_add_listener(disp_surface->xdg_surface,
&xdg_surface_listener,
NULL);
if (self->ifaces.aura) {
disp_surface->aura = zaura_shell_get_aura_surface(
- self->ifaces.aura, disp_surface->surface);
+ self->ifaces.aura, disp_surface->wl_surface);
if (!disp_surface->aura) {
syslog(LOG_ERR, "failed to make aura surface");
goto fail;
@@ -654,14 +1105,22 @@ struct dwl_surface *dwl_context_surface_new(struct dwl_context *self,
}
// signal that the surface is ready to be configured
- wl_surface_commit(disp_surface->surface);
+ wl_surface_commit(disp_surface->wl_surface);
// wait for the surface to be configured
wl_display_roundtrip(self->display);
} else {
+ struct dwl_surface *parent_surface =
+ dwl_context_get_surface(self, parent_id);
+
+ if (!parent_surface) {
+ syslog(LOG_ERR, "failed to find parent_surface");
+ goto fail;
+ }
+
disp_surface->subsurface = wl_subcompositor_get_subsurface(
- self->ifaces.subcompositor, disp_surface->surface,
- parent->surface);
+ self->ifaces.subcompositor, disp_surface->wl_surface,
+ parent_surface->wl_surface);
if (!disp_surface->subsurface) {
syslog(LOG_ERR, "failed to make subsurface");
goto fail;
@@ -671,17 +1130,26 @@ struct dwl_surface *dwl_context_surface_new(struct dwl_context *self,
if (self->ifaces.viewporter) {
disp_surface->viewport = wp_viewporter_get_viewport(
- self->ifaces.viewporter, disp_surface->surface);
+ self->ifaces.viewporter, disp_surface->wl_surface);
if (!disp_surface->viewport) {
syslog(LOG_ERR, "failed to make surface viewport");
goto fail;
}
}
- wl_surface_attach(disp_surface->surface, disp_surface->buffers[0], 0,
+ if (self->ifaces.virtio_gpu_metadata) {
+ disp_surface->virtio_gpu_surface_metadata =
+ wp_virtio_gpu_metadata_v1_get_surface_metadata(
+ self->ifaces.virtio_gpu_metadata, disp_surface->wl_surface);
+ if (!disp_surface->virtio_gpu_surface_metadata) {
+ syslog(LOG_ERR, "failed to make surface virtio surface metadata");
+ goto fail;
+ }
+ }
+
+ wl_surface_attach(disp_surface->wl_surface, disp_surface->buffers[0], 0,
0);
- wl_surface_damage(disp_surface->surface, 0, 0, width, height);
- wl_region_destroy(region);
+ wl_surface_damage(disp_surface->wl_surface, 0, 0, width, height);
wl_shm_pool_destroy(shm_pool);
// Needed to get outputs before iterating them.
@@ -695,30 +1163,37 @@ struct dwl_surface *dwl_context_surface_new(struct dwl_context *self,
outputs_for_each(self, i, output)
{
if (output->internal) {
- surface_enter(disp_surface, disp_surface->surface,
+ surface_enter(disp_surface, disp_surface->wl_surface,
output->output);
}
}
- wl_surface_commit(disp_surface->surface);
+ wl_surface_commit(disp_surface->wl_surface);
wl_display_flush(self->display);
+ disp_surface->surface_id = surface_id;
+ if (!dwl_context_add_surface(self, disp_surface)) {
+ syslog(LOG_ERR, "failed to add surface to context");
+ goto fail;
+ }
+
return disp_surface;
fail:
+ if (disp_surface->virtio_gpu_surface_metadata)
+ wp_virtio_gpu_surface_metadata_v1_destroy(
+ disp_surface->virtio_gpu_surface_metadata);
if (disp_surface->viewport)
wp_viewport_destroy(disp_surface->viewport);
if (disp_surface->subsurface)
wl_subsurface_destroy(disp_surface->subsurface);
- if (disp_surface->toplevel)
- zxdg_toplevel_v6_destroy(disp_surface->toplevel);
- if (disp_surface->xdg)
- zxdg_surface_v6_destroy(disp_surface->xdg);
+ if (disp_surface->xdg_toplevel)
+ xdg_toplevel_destroy(disp_surface->xdg_toplevel);
+ if (disp_surface->xdg_surface)
+ xdg_surface_destroy(disp_surface->xdg_surface);
if (disp_surface->aura)
zaura_surface_destroy(disp_surface->aura);
- if (region)
- wl_region_destroy(region);
- if (disp_surface->surface)
- wl_surface_destroy(disp_surface->surface);
+ if (disp_surface->wl_surface)
+ wl_surface_destroy(disp_surface->wl_surface);
for (i = 0; i < buffer_count; i++)
if (disp_surface->buffers[i])
wl_buffer_destroy(disp_surface->buffers[i]);
@@ -731,18 +1206,23 @@ fail:
void dwl_surface_destroy(struct dwl_surface **self)
{
size_t i;
+
+ dwl_context_remove_surface((*self)->context, (*self)->surface_id);
+ if ((*self)->virtio_gpu_surface_metadata)
+ wp_virtio_gpu_surface_metadata_v1_destroy(
+ (*self)->virtio_gpu_surface_metadata);
if ((*self)->viewport)
wp_viewport_destroy((*self)->viewport);
if ((*self)->subsurface)
wl_subsurface_destroy((*self)->subsurface);
- if ((*self)->toplevel)
- zxdg_toplevel_v6_destroy((*self)->toplevel);
- if ((*self)->xdg)
- zxdg_surface_v6_destroy((*self)->xdg);
+ if ((*self)->xdg_toplevel)
+ xdg_toplevel_destroy((*self)->xdg_toplevel);
+ if ((*self)->xdg_surface)
+ xdg_surface_destroy((*self)->xdg_surface);
if ((*self)->aura)
zaura_surface_destroy((*self)->aura);
- if ((*self)->surface)
- wl_surface_destroy((*self)->surface);
+ if ((*self)->wl_surface)
+ wl_surface_destroy((*self)->wl_surface);
for (i = 0; i < (*self)->buffer_count; i++)
wl_buffer_destroy((*self)->buffers[i]);
wl_display_flush((*self)->context->display);
@@ -758,7 +1238,7 @@ void dwl_surface_commit(struct dwl_surface *self)
// apply back pressure to the guest gpu driver right now. The intention
// of this module is to help bootstrap gpu support, so it does not have
// to have artifact free rendering.
- wl_surface_commit(self->surface);
+ wl_surface_commit(self->wl_surface);
wl_display_flush(self->context->display);
}
@@ -771,18 +1251,24 @@ void dwl_surface_flip(struct dwl_surface *self, size_t buffer_index)
{
if (buffer_index >= self->buffer_count)
return;
- wl_surface_attach(self->surface, self->buffers[buffer_index], 0, 0);
- wl_surface_damage(self->surface, 0, 0, self->width, self->height);
+ wl_surface_attach(self->wl_surface, self->buffers[buffer_index], 0, 0);
+ wl_surface_damage(self->wl_surface, 0, 0, self->width, self->height);
dwl_surface_commit(self);
self->buffer_use_bit_mask |= 1 << buffer_index;
}
-void dwl_surface_flip_to(struct dwl_surface *self, struct dwl_dmabuf *dmabuf)
+void dwl_surface_flip_to(struct dwl_surface *self, uint32_t import_id)
{
+ // Surface and dmabuf have to exist in same context.
+ struct dwl_dmabuf *dmabuf = dwl_context_get_dmabuf(self->context,
+ import_id);
+ if (!dmabuf)
+ return;
+
if (self->width != dmabuf->width || self->height != dmabuf->height)
return;
- wl_surface_attach(self->surface, dmabuf->buffer, 0, 0);
- wl_surface_damage(self->surface, 0, 0, self->width, self->height);
+ wl_surface_attach(self->wl_surface, dmabuf->buffer, 0, 0);
+ wl_surface_damage(self->wl_surface, 0, 0, self->width, self->height);
dwl_surface_commit(self);
dmabuf->in_use = true;
}
@@ -797,7 +1283,37 @@ void dwl_surface_set_position(struct dwl_surface *self, uint32_t x, uint32_t y)
if (self->subsurface) {
wl_subsurface_set_position(self->subsurface, x / self->scale,
y / self->scale);
- wl_surface_commit(self->surface);
+ wl_surface_commit(self->wl_surface);
wl_display_flush(self->context->display);
}
}
+
+const void* dwl_surface_descriptor(const struct dwl_surface *self)
+{
+ return self->wl_surface;
+}
+
+bool dwl_context_pending_events(const struct dwl_context *self)
+{
+ if (self->event_write_pos == self->event_read_pos)
+ return false;
+
+ return true;
+}
+
+void dwl_context_next_event(struct dwl_context *self, struct dwl_event *event)
+{
+ memcpy(event, self->event_cbuf + self->event_read_pos,
+ sizeof(struct dwl_event));
+
+ if (++self->event_read_pos == EVENT_BUF_SIZE)
+ self->event_read_pos = 0;
+}
+
+void dwl_surface_set_scanout_id(struct dwl_surface *self, uint32_t scanout_id)
+{
+ if (self->virtio_gpu_surface_metadata) {
+ wp_virtio_gpu_surface_metadata_v1_set_scanout_id(
+ self->virtio_gpu_surface_metadata, scanout_id);
+ }
+}
diff --git a/gpu_display/src/dwl.rs b/gpu_display/src/dwl.rs
index 3f2d67ddd..c51dea16e 100644
--- a/gpu_display/src/dwl.rs
+++ b/gpu_display/src/dwl.rs
@@ -57,6 +57,39 @@ pub struct dwl_dmabuf {
pub struct dwl_surface {
pub _bindgen_opaque_blob: [u64; 12usize],
}
+
+#[allow(dead_code)]
+pub const DWL_KEYBOARD_KEY_STATE_RELEASED: i32 = 0;
+pub const DWL_KEYBOARD_KEY_STATE_PRESSED: i32 = 1;
+
+#[allow(dead_code)]
+pub const DWL_EVENT_TYPE_KEYBOARD_ENTER: u32 = 0x00;
+#[allow(dead_code)]
+pub const DWL_EVENT_TYPE_KEYBOARD_LEAVE: u32 = 0x01;
+pub const DWL_EVENT_TYPE_KEYBOARD_KEY: u32 = 0x02;
+#[allow(dead_code)]
+pub const DWL_EVENT_TYPE_POINTER_ENTER: u32 = 0x10;
+#[allow(dead_code)]
+pub const DWL_EVENT_TYPE_POINTER_LEAVE: u32 = 0x11;
+#[allow(dead_code)]
+pub const DWL_EVENT_TYPE_POINTER_MOVE: u32 = 0x12;
+#[allow(dead_code)]
+pub const DWL_EVENT_TYPE_POINTER_BUTTON: u32 = 0x13;
+pub const DWL_EVENT_TYPE_TOUCH_DOWN: u32 = 0x20;
+pub const DWL_EVENT_TYPE_TOUCH_UP: u32 = 0x21;
+pub const DWL_EVENT_TYPE_TOUCH_MOTION: u32 = 0x22;
+
+pub const DWL_SURFACE_FLAG_RECEIVE_INPUT: u32 = 1 << 0;
+pub const DWL_SURFACE_FLAG_HAS_ALPHA: u32 = 1 << 1;
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct dwl_event {
+ pub surface_descriptor: *const ::std::ffi::c_void,
+ pub event_type: u32,
+ pub params: [i32; 3usize],
+}
+
extern "C" {
pub fn dwl_context_new() -> *mut dwl_context;
}
@@ -78,6 +111,7 @@ extern "C" {
extern "C" {
pub fn dwl_context_dmabuf_new(
self_: *mut dwl_context,
+ import_id: u32,
fd: ::std::os::raw::c_int,
offset: u32,
stride: u32,
@@ -93,15 +127,18 @@ extern "C" {
extern "C" {
pub fn dwl_context_surface_new(
self_: *mut dwl_context,
- parent: *mut dwl_surface,
+ parent_id: u32,
+ surface_id: u32,
shm_fd: ::std::os::raw::c_int,
shm_size: usize,
buffer_size: usize,
width: u32,
height: u32,
stride: u32,
+ flags: u32,
) -> *mut dwl_surface;
}
+
extern "C" {
pub fn dwl_surface_destroy(self_: *mut *mut dwl_surface);
}
@@ -115,7 +152,7 @@ extern "C" {
pub fn dwl_surface_flip(self_: *mut dwl_surface, buffer_index: usize);
}
extern "C" {
- pub fn dwl_surface_flip_to(self_: *mut dwl_surface, dmabuf: *mut dwl_dmabuf);
+ pub fn dwl_surface_flip_to(self_: *mut dwl_surface, import_id: u32);
}
extern "C" {
pub fn dwl_surface_close_requested(self_: *const dwl_surface) -> bool;
@@ -123,3 +160,15 @@ extern "C" {
extern "C" {
pub fn dwl_surface_set_position(self_: *mut dwl_surface, x: u32, y: u32);
}
+extern "C" {
+ pub fn dwl_surface_descriptor(self_: *const dwl_surface) -> *const ::std::ffi::c_void;
+}
+extern "C" {
+ pub fn dwl_context_pending_events(self_: *const dwl_context) -> bool;
+}
+extern "C" {
+ pub fn dwl_context_next_event(self_: *mut dwl_context, event: *mut dwl_event);
+}
+extern "C" {
+ pub fn dwl_surface_set_scanout_id(self_: *mut dwl_surface, scanout_id: u32);
+}
diff --git a/gpu_display/src/event_device.rs b/gpu_display/src/event_device.rs
index 4453c8453..06271419a 100644
--- a/gpu_display/src/event_device.rs
+++ b/gpu_display/src/event_device.rs
@@ -4,7 +4,11 @@
use base::{AsRawDescriptor, RawDescriptor};
use data_model::DataInit;
-use linux_input_sys::{virtio_input_event, InputEventDecoder};
+use linux_input_sys::{
+ virtio_input_event, InputEventDecoder, ABS_MT_POSITION_X, ABS_MT_POSITION_Y, ABS_MT_SLOT,
+ ABS_MT_TRACKING_ID,
+};
+use std::cmp::max;
use std::collections::VecDeque;
use std::io::{self, Error, ErrorKind, Read, Write};
use std::iter::ExactSizeIterator;
@@ -27,7 +31,7 @@ const EVENT_BUFFER_LEN_MAX: usize = 16 * EVENT_SIZE;
// }
// }
-#[derive(Copy, Clone, PartialEq, Eq)]
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum EventDeviceKind {
/// Produces relative mouse motions, wheel, and button clicks while the real mouse is captured.
Mouse,
@@ -44,6 +48,15 @@ pub struct EventDevice {
event_socket: UnixStream,
}
+// Captures information for emulating MT events. Once more than slot 0 is supported will need to
+// store slot information as well.
+#[derive(Default, Copy, Clone)]
+struct MTEvent {
+ id: i32,
+ x: i32,
+ y: i32,
+}
+
impl EventDevice {
pub fn new(kind: EventDeviceKind, event_socket: UnixStream) -> EventDevice {
let _ = event_socket.set_nonblocking(true);
@@ -80,7 +93,7 @@ impl EventDevice {
/// empty.
pub fn flush_buffered_events(&mut self) -> io::Result<bool> {
while !self.event_buffer.is_empty() {
- let written = self.event_socket.write(&self.event_buffer.as_slices().0)?;
+ let written = self.event_socket.write(self.event_buffer.as_slices().0)?;
if written == 0 {
return Ok(false);
}
@@ -93,6 +106,24 @@ impl EventDevice {
self.event_buffer.is_empty()
}
+ /// Compares (potential) events and calculates the oldest one
+ fn capture_oldest_event(
+ current_mt_event: &Option<MTEvent>,
+ oldest_mt_event: &mut Option<MTEvent>,
+ ) {
+ if let Some(current) = current_mt_event {
+ if current.id != -1 {
+ if let Some(oldest) = oldest_mt_event {
+ if current.id < oldest.id {
+ *oldest_mt_event = Some(*current);
+ }
+ } else {
+ *oldest_mt_event = Some(*current);
+ }
+ }
+ }
+ }
+
pub fn send_report<E: IntoIterator<Item = virtio_input_event>>(
&mut self,
events: E,
@@ -101,13 +132,82 @@ impl EventDevice {
E::IntoIter: ExactSizeIterator,
{
let it = events.into_iter();
+ let mut emulated_events: Vec<virtio_input_event> = vec![];
+ let mut evdev_st_events_present = false;
+ let mut oldest_mt_event: Option<MTEvent> = None;
+ let mut current_mt_event: Option<MTEvent> = None;
+
if self.event_buffer.len() > (EVENT_BUFFER_LEN_MAX - EVENT_SIZE * (it.len() + 1)) {
return Ok(false);
}
+ // Assumptions made for MT emulation:
+ // * There may exist real ABS_* events coming from e.g. evdev. cancel emulation in this case
+ // * For current gpu_display_{x, wl} implementations, there is only one slot and all
+ // available tracking id information is sent. If this assumption is broken in the future,
+ // `EventDevice` will need to maintain state of the oldest contact for each slot, so that
+ // this can be compared to any incoming 'sparse' event set.
for event in it {
let bytes = event.as_slice();
self.event_buffer.extend(bytes.iter());
+
+ if event.is_valid_st_event() {
+ // Real evdev event, we shouldnt emulate anything
+ evdev_st_events_present = true;
+ }
+
+ // For MT events, we want to also emulate their corresponding ST events
+ if !evdev_st_events_present {
+ match event.code.to_native() {
+ ABS_MT_SLOT => {
+ // MT packets begin with a SLOT event, begin a new current event and potentially
+ // store the current as oldest.
+ EventDevice::capture_oldest_event(&current_mt_event, &mut oldest_mt_event);
+ // Only care about slot 0 in current implementation, if more slots are added
+ // we will need to keep track of events / ids per slot, as well as maintain
+ // which slot is currently active.
+ current_mt_event = Some(MTEvent {
+ ..Default::default()
+ })
+ }
+ ABS_MT_TRACKING_ID => {
+ if let Some(mut current) = current_mt_event {
+ current.id = event.value.to_native();
+ }
+ }
+ ABS_MT_POSITION_X => {
+ if let Some(mut current) = current_mt_event {
+ current.x = event.value.to_native();
+ }
+ }
+ ABS_MT_POSITION_Y => {
+ if let Some(mut current) = current_mt_event {
+ current.y = event.value.to_native();
+ }
+ }
+ _ => {}
+ }
+ }
+ }
+
+ // Finalize the current event - MT packets have a 'begin' signal but not an 'end' signal, so the
+ // last collected 'current' event has no chance to be compared in the event loop.
+ EventDevice::capture_oldest_event(&current_mt_event, &mut oldest_mt_event);
+
+ if !evdev_st_events_present {
+ if let Some(oldest_event) = oldest_mt_event {
+ emulated_events.push(virtio_input_event::touch(true));
+ emulated_events.push(virtio_input_event::absolute_x(max(0, oldest_event.x)));
+ emulated_events.push(virtio_input_event::absolute_y(max(0, oldest_event.y)));
+ } else {
+ // No contacts remain, emit lift
+ emulated_events.push(virtio_input_event::touch(false));
+ }
+ }
+
+ for event in emulated_events.into_iter() {
+ let bytes = event.as_slice();
+ self.event_buffer.extend(bytes.iter());
}
self.event_buffer
@@ -124,7 +224,7 @@ impl EventDevice {
}
let bytes = event.as_slice();
- let written = self.event_socket.write(&bytes)?;
+ let written = self.event_socket.write(bytes)?;
if written == bytes.len() {
return Ok(true);
diff --git a/gpu_display/src/generated/xlib.rs b/gpu_display/src/generated/xlib.rs
index f6ff38240..4e773a8e7 100644
--- a/gpu_display/src/generated/xlib.rs
+++ b/gpu_display/src/generated/xlib.rs
@@ -4,6 +4,8 @@
//! Generated using ./xlib_generator.sh
+#![allow(clippy::upper_case_acronyms)]
+
#[link(name = "X11")]
extern "C" {}
diff --git a/gpu_display/src/generated/xlib_generator.sh b/gpu_display/src/generated/xlib_generator.sh
index 304904a7a..edfea83cf 100755
--- a/gpu_display/src/generated/xlib_generator.sh
+++ b/gpu_display/src/generated/xlib_generator.sh
@@ -12,6 +12,8 @@ cat >xlib.rs <<EOF
//! Generated using ./xlib_generator.sh
+#![allow(clippy::upper_case_acronyms)]
+
#[link(name = "X11")]
extern "C" {}
@@ -21,70 +23,70 @@ extern "C" {}
EOF
bindgen --no-layout-tests --no-derive-debug \
- --whitelist-function XAllocSizeHints \
- --whitelist-function XBlackPixelOfScreen \
- --whitelist-function XClearWindow \
- --whitelist-function XCloseDisplay \
- --whitelist-function XConnectionNumber \
- --whitelist-function XCreateGC \
- --whitelist-function XCreateSimpleWindow \
- --whitelist-function XDefaultDepthOfScreen \
- --whitelist-function XDefaultScreenOfDisplay \
- --whitelist-function XDefaultVisualOfScreen \
- --whitelist-function XDestroyImage \
- --whitelist-function XDestroyWindow \
- --whitelist-function XFlush \
- --whitelist-function XFree \
- --whitelist-function XFreeGC \
- --whitelist-function XGetVisualInfo \
- --whitelist-function XInternAtom \
- --whitelist-function XKeycodeToKeysym \
- --whitelist-function XMapRaised \
- --whitelist-function XNextEvent \
- --whitelist-function XOpenDisplay \
- --whitelist-function XPending \
- --whitelist-function XRootWindowOfScreen \
- --whitelist-function XScreenNumberOfScreen \
- --whitelist-function XSelectInput \
- --whitelist-function XSetWMNormalHints \
- --whitelist-function XSetWMProtocols \
- --whitelist-function XShmAttach \
- --whitelist-function XShmCreateImage \
- --whitelist-function XShmDetach \
- --whitelist-function XShmGetEventBase \
- --whitelist-function XShmPutImage \
- --whitelist-function XShmQueryExtension \
- --whitelist-var 'XK_.*' \
- --whitelist-var ButtonPress \
- --whitelist-var ButtonPressMask \
- --whitelist-var Button1 \
- --whitelist-var Button1Mask \
- --whitelist-var ButtonRelease \
- --whitelist-var ButtonReleaseMask \
- --whitelist-var ClientMessage \
- --whitelist-var Expose \
- --whitelist-var ExposureMask \
- --whitelist-var KeyPress \
- --whitelist-var KeyPressMask \
- --whitelist-var KeyRelease \
- --whitelist-var KeyReleaseMask \
- --whitelist-var MotionNotify \
- --whitelist-var PMaxSize \
- --whitelist-var PMinSize \
- --whitelist-var PointerMotionMask \
- --whitelist-var ShmCompletion \
- --whitelist-var VisualBlueMaskMask \
- --whitelist-var VisualDepthMask \
- --whitelist-var VisualGreenMaskMask \
- --whitelist-var VisualRedMaskMask \
- --whitelist-var VisualScreenMask \
- --whitelist-var ZPixmap \
- --whitelist-type Display \
- --whitelist-type GC \
- --whitelist-type Screen \
- --whitelist-type XShmCompletionEvent \
- --whitelist-type ShmSeg \
- --whitelist-type Visual \
- --whitelist-type Window \
- --whitelist-type XVisualInfo \
+ --allowlist-function XAllocSizeHints \
+ --allowlist-function XBlackPixelOfScreen \
+ --allowlist-function XClearWindow \
+ --allowlist-function XCloseDisplay \
+ --allowlist-function XConnectionNumber \
+ --allowlist-function XCreateGC \
+ --allowlist-function XCreateSimpleWindow \
+ --allowlist-function XDefaultDepthOfScreen \
+ --allowlist-function XDefaultScreenOfDisplay \
+ --allowlist-function XDefaultVisualOfScreen \
+ --allowlist-function XDestroyImage \
+ --allowlist-function XDestroyWindow \
+ --allowlist-function XFlush \
+ --allowlist-function XFree \
+ --allowlist-function XFreeGC \
+ --allowlist-function XGetVisualInfo \
+ --allowlist-function XInternAtom \
+ --allowlist-function XKeycodeToKeysym \
+ --allowlist-function XMapRaised \
+ --allowlist-function XNextEvent \
+ --allowlist-function XOpenDisplay \
+ --allowlist-function XPending \
+ --allowlist-function XRootWindowOfScreen \
+ --allowlist-function XScreenNumberOfScreen \
+ --allowlist-function XSelectInput \
+ --allowlist-function XSetWMNormalHints \
+ --allowlist-function XSetWMProtocols \
+ --allowlist-function XShmAttach \
+ --allowlist-function XShmCreateImage \
+ --allowlist-function XShmDetach \
+ --allowlist-function XShmGetEventBase \
+ --allowlist-function XShmPutImage \
+ --allowlist-function XShmQueryExtension \
+ --allowlist-var 'XK_.*' \
+ --allowlist-var ButtonPress \
+ --allowlist-var ButtonPressMask \
+ --allowlist-var Button1 \
+ --allowlist-var Button1Mask \
+ --allowlist-var ButtonRelease \
+ --allowlist-var ButtonReleaseMask \
+ --allowlist-var ClientMessage \
+ --allowlist-var Expose \
+ --allowlist-var ExposureMask \
+ --allowlist-var KeyPress \
+ --allowlist-var KeyPressMask \
+ --allowlist-var KeyRelease \
+ --allowlist-var KeyReleaseMask \
+ --allowlist-var MotionNotify \
+ --allowlist-var PMaxSize \
+ --allowlist-var PMinSize \
+ --allowlist-var PointerMotionMask \
+ --allowlist-var ShmCompletion \
+ --allowlist-var VisualBlueMaskMask \
+ --allowlist-var VisualDepthMask \
+ --allowlist-var VisualGreenMaskMask \
+ --allowlist-var VisualRedMaskMask \
+ --allowlist-var VisualScreenMask \
+ --allowlist-var ZPixmap \
+ --allowlist-type Display \
+ --allowlist-type GC \
+ --allowlist-type Screen \
+ --allowlist-type XShmCompletionEvent \
+ --allowlist-type ShmSeg \
+ --allowlist-type Visual \
+ --allowlist-type Window \
+ --allowlist-type XVisualInfo \
xlib_wrapper.h >>xlib.rs
diff --git a/gpu_display/src/gpu_display_stub.rs b/gpu_display/src/gpu_display_stub.rs
index 11b96c59c..32106eb9e 100644
--- a/gpu_display/src/gpu_display_stub.rs
+++ b/gpu_display/src/gpu_display_stub.rs
@@ -2,17 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::collections::BTreeMap;
-use std::num::NonZeroU32;
-
-use crate::{DisplayT, EventDevice, GpuDisplayError, GpuDisplayFramebuffer};
+use crate::{
+ DisplayT, GpuDisplayError, GpuDisplayFramebuffer, GpuDisplayResult, GpuDisplaySurface,
+ SurfaceType,
+};
use base::{AsRawDescriptor, Event, RawDescriptor};
use data_model::VolatileSlice;
-type SurfaceId = NonZeroU32;
-
-#[allow(dead_code, unused_variables)]
+#[allow(dead_code)]
struct Buffer {
width: u32,
height: u32,
@@ -38,21 +36,13 @@ impl Buffer {
}
}
-struct Surface {
+struct StubSurface {
width: u32,
height: u32,
buffer: Option<Buffer>,
}
-impl Surface {
- fn create(width: u32, height: u32) -> Surface {
- Surface {
- width,
- height,
- buffer: None,
- }
- }
-
+impl StubSurface {
/// Gets the buffer at buffer_index, allocating it if necessary.
fn lazily_allocate_buffer(&mut self) -> Option<&mut Buffer> {
if self.buffer.is_none() {
@@ -70,8 +60,9 @@ impl Surface {
self.buffer.as_mut()
}
+}
- /// Gets the next framebuffer, allocating if necessary.
+impl GpuDisplaySurface for StubSurface {
fn framebuffer(&mut self) -> Option<GpuDisplayFramebuffer> {
let framebuffer = self.lazily_allocate_buffer()?;
let framebuffer_stride = framebuffer.stride() as u32;
@@ -82,145 +73,43 @@ impl Surface {
framebuffer_bytes_per_pixel,
))
}
-
- fn flip(&mut self) {}
}
-impl Drop for Surface {
+impl Drop for StubSurface {
fn drop(&mut self) {}
}
-struct SurfacesHelper {
- next_surface_id: SurfaceId,
- surfaces: BTreeMap<SurfaceId, Surface>,
-}
-
-impl SurfacesHelper {
- fn new() -> SurfacesHelper {
- SurfacesHelper {
- next_surface_id: SurfaceId::new(1).unwrap(),
- surfaces: Default::default(),
- }
- }
-
- fn create_surface(&mut self, width: u32, height: u32) -> u32 {
- let new_surface = Surface::create(width, height);
- let new_surface_id = self.next_surface_id;
-
- self.surfaces.insert(new_surface_id, new_surface);
- self.next_surface_id = SurfaceId::new(self.next_surface_id.get() + 1).unwrap();
-
- new_surface_id.get()
- }
-
- fn get_surface(&mut self, surface_id: u32) -> Option<&mut Surface> {
- SurfaceId::new(surface_id).and_then(move |id| self.surfaces.get_mut(&id))
- }
-
- fn destroy_surface(&mut self, surface_id: u32) {
- SurfaceId::new(surface_id).and_then(|id| self.surfaces.remove(&id));
- }
-
- fn flip_surface(&mut self, surface_id: u32) {
- if let Some(surface) = self.get_surface(surface_id) {
- surface.flip();
- }
- }
-}
-
pub struct DisplayStub {
/// This event is never triggered and is used solely to fulfill AsRawDescriptor.
event: Event,
- surfaces: SurfacesHelper,
}
impl DisplayStub {
- pub fn new() -> Result<DisplayStub, GpuDisplayError> {
+ pub fn new() -> GpuDisplayResult<DisplayStub> {
let event = Event::new().map_err(|_| GpuDisplayError::CreateEvent)?;
- Ok(DisplayStub {
- event,
- surfaces: SurfacesHelper::new(),
- })
+ Ok(DisplayStub { event })
}
}
impl DisplayT for DisplayStub {
- fn dispatch_events(&mut self) {}
-
fn create_surface(
&mut self,
parent_surface_id: Option<u32>,
+ _surface_id: u32,
width: u32,
height: u32,
- ) -> Result<u32, GpuDisplayError> {
+ _surf_type: SurfaceType,
+ ) -> GpuDisplayResult<Box<dyn GpuDisplaySurface>> {
if parent_surface_id.is_some() {
return Err(GpuDisplayError::Unsupported);
}
- Ok(self.surfaces.create_surface(width, height))
- }
- fn release_surface(&mut self, surface_id: u32) {
- self.surfaces.destroy_surface(surface_id);
- }
-
- fn framebuffer(&mut self, surface_id: u32) -> Option<GpuDisplayFramebuffer> {
- self.surfaces
- .get_surface(surface_id)
- .and_then(|s| s.framebuffer())
- }
-
- fn next_buffer_in_use(&self, _surface_id: u32) -> bool {
- false
- }
-
- fn flip(&mut self, surface_id: u32) {
- self.surfaces.flip_surface(surface_id);
- }
-
- fn close_requested(&self, _surface_id: u32) -> bool {
- false
- }
-
- fn import_dmabuf(
- &mut self,
- _fd: RawDescriptor,
- _offset: u32,
- _stride: u32,
- _modifiers: u64,
- _width: u32,
- _height: u32,
- _fourcc: u32,
- ) -> Result<u32, GpuDisplayError> {
- Err(GpuDisplayError::Unsupported)
- }
-
- fn release_import(&mut self, _import_id: u32) {
- // unsupported
- }
-
- fn commit(&mut self, _surface_id: u32) {
- // unsupported
- }
-
- fn flip_to(&mut self, _surface_id: u32, _import_id: u32) {
- // unsupported
- }
-
- fn set_position(&mut self, _surface_id: u32, _x: u32, _y: u32) {
- // unsupported
- }
-
- fn import_event_device(&mut self, _event_device: EventDevice) -> Result<u32, GpuDisplayError> {
- Err(GpuDisplayError::Unsupported)
- }
-
- fn release_event_device(&mut self, _event_device_id: u32) {
- // unsupported
- }
-
- fn attach_event_device(&mut self, _surface_id: u32, _event_device_id: u32) {
- // unsupported
+ Ok(Box::new(StubSurface {
+ width,
+ height,
+ buffer: None,
+ }))
}
}
diff --git a/gpu_display/src/gpu_display_wl.rs b/gpu_display/src/gpu_display_wl.rs
index d242bbbd1..acfebc448 100644
--- a/gpu_display/src/gpu_display_wl.rs
+++ b/gpu_display/src/gpu_display_wl.rs
@@ -12,17 +12,22 @@ mod dwl;
use dwl::*;
-use crate::{DisplayT, EventDevice, GpuDisplayError, GpuDisplayFramebuffer};
+use crate::{
+ DisplayT, EventDeviceKind, GpuDisplayError, GpuDisplayEvents, GpuDisplayFramebuffer,
+ GpuDisplayImport, GpuDisplayResult, GpuDisplaySurface, SurfaceType,
+};
+use linux_input_sys::virtio_input_event;
use std::cell::Cell;
-use std::collections::HashMap;
+use std::cmp::max;
use std::ffi::{CStr, CString};
+use std::mem::zeroed;
use std::path::Path;
-use std::ptr::{null, null_mut};
+use std::ptr::null;
use base::{
- round_up_to_page_size, AsRawDescriptor, MemoryMapping, MemoryMappingBuilder, RawDescriptor,
- SharedMemory,
+ error, round_up_to_page_size, AsRawDescriptor, MemoryMapping, MemoryMappingBuilder,
+ RawDescriptor, SharedMemory,
};
use data_model::VolatileMemory;
@@ -42,7 +47,17 @@ impl Drop for DwlContext {
}
}
+impl AsRawDescriptor for DwlContext {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ // Safe given that the context pointer is valid.
+ unsafe { dwl_context_fd(self.0) }
+ }
+}
+
struct DwlDmabuf(*mut dwl_dmabuf);
+
+impl GpuDisplayImport for DwlDmabuf {}
+
impl Drop for DwlDmabuf {
fn drop(&mut self) {
if !self.0.is_null() {
@@ -68,7 +83,7 @@ impl Drop for DwlSurface {
}
}
-struct Surface {
+struct WaylandSurface {
surface: DwlSurface,
row_size: u32,
buffer_size: usize,
@@ -76,27 +91,97 @@ struct Surface {
buffer_mem: MemoryMapping,
}
-impl Surface {
+impl WaylandSurface {
fn surface(&self) -> *mut dwl_surface {
self.surface.0
}
}
+impl GpuDisplaySurface for WaylandSurface {
+ fn surface_descriptor(&self) -> u64 {
+ // Safe if the surface is valid.
+ let pointer = unsafe { dwl_surface_descriptor(self.surface.0) };
+ pointer as u64
+ }
+
+ fn framebuffer(&mut self) -> Option<GpuDisplayFramebuffer> {
+ let buffer_index = (self.buffer_index.get() + 1) % BUFFER_COUNT;
+ let framebuffer = self
+ .buffer_mem
+ .get_slice(buffer_index * self.buffer_size, self.buffer_size)
+ .ok()?;
+
+ Some(GpuDisplayFramebuffer::new(
+ framebuffer,
+ self.row_size,
+ BYTES_PER_PIXEL,
+ ))
+ }
+
+ fn next_buffer_in_use(&self) -> bool {
+ let next_buffer_index = (self.buffer_index.get() + 1) % BUFFER_COUNT;
+ // Safe because only a valid surface and buffer index is used.
+ unsafe { dwl_surface_buffer_in_use(self.surface(), next_buffer_index) }
+ }
+
+ fn close_requested(&self) -> bool {
+ // Safe because only a valid surface is used.
+ unsafe { dwl_surface_close_requested(self.surface()) }
+ }
+
+ fn flip(&mut self) {
+ self.buffer_index
+ .set((self.buffer_index.get() + 1) % BUFFER_COUNT);
+
+ // Safe because only a valid surface and buffer index is used.
+ unsafe {
+ dwl_surface_flip(self.surface(), self.buffer_index.get());
+ }
+ }
+
+ fn flip_to(&mut self, import_id: u32) {
+ // Safe because only a valid surface and import_id is used.
+ unsafe { dwl_surface_flip_to(self.surface(), import_id) }
+ }
+
+ fn commit(&mut self) -> GpuDisplayResult<()> {
+ // Safe because only a valid surface is used.
+ unsafe {
+ dwl_surface_commit(self.surface());
+ }
+
+ Ok(())
+ }
+
+ fn set_position(&mut self, x: u32, y: u32) {
+ // Safe because only a valid surface is used.
+ unsafe {
+ dwl_surface_set_position(self.surface(), x, y);
+ }
+ }
+
+ fn set_scanout_id(&mut self, scanout_id: u32) {
+ // Safe because only a valid surface is used.
+ unsafe {
+ dwl_surface_set_scanout_id(self.surface(), scanout_id);
+ }
+ }
+}
+
/// A connection to the compositor and associated collection of state.
///
/// The user of `GpuDisplay` can use `AsRawDescriptor` to poll on the compositor connection's file
/// descriptor. When the connection is readable, `dispatch_events` can be called to process it.
+
pub struct DisplayWl {
- dmabufs: HashMap<u32, DwlDmabuf>,
- dmabuf_next_id: u32,
- surfaces: HashMap<u32, Surface>,
- surface_next_id: u32,
ctx: DwlContext,
+ current_event: Option<dwl_event>,
+ mt_tracking_id: u16,
}
impl DisplayWl {
/// Opens a fresh connection to the compositor.
- pub fn new(wayland_path: Option<&Path>) -> Result<DisplayWl, GpuDisplayError> {
+ pub fn new(wayland_path: Option<&Path>) -> GpuDisplayResult<DisplayWl> {
// The dwl_context_new call should always be safe to call, and we check its result.
let ctx = DwlContext(unsafe { dwl_context_new() });
if ctx.0.is_null() {
@@ -126,11 +211,9 @@ impl DisplayWl {
}
Ok(DisplayWl {
- dmabufs: Default::default(),
- dmabuf_next_id: 0,
- surfaces: Default::default(),
- surface_next_id: 0,
ctx,
+ current_event: None,
+ mt_tracking_id: 0u16,
})
}
@@ -138,52 +221,97 @@ impl DisplayWl {
self.ctx.0
}
- fn get_surface(&self, surface_id: u32) -> Option<&Surface> {
- self.surfaces.get(&surface_id)
+ fn pop_event(&self) -> dwl_event {
+ // Safe because dwl_next_events from a context's circular buffer.
+ unsafe {
+ let mut ev = zeroed();
+ dwl_context_next_event(self.ctx(), &mut ev);
+ ev
+ }
+ }
+
+ fn next_tracking_id(&mut self) -> i32 {
+ let cur_id: i32 = self.mt_tracking_id as i32;
+ self.mt_tracking_id = self.mt_tracking_id.wrapping_add(1);
+ cur_id
+ }
+
+ fn current_tracking_id(&self) -> i32 {
+ self.mt_tracking_id as i32
}
}
impl DisplayT for DisplayWl {
- fn import_dmabuf(
- &mut self,
- fd: RawDescriptor,
- offset: u32,
- stride: u32,
- modifiers: u64,
- width: u32,
- height: u32,
- fourcc: u32,
- ) -> Result<u32, GpuDisplayError> {
- // Safe given that the context pointer is valid. Any other invalid parameters would be
- // rejected by dwl_context_dmabuf_new safely. We check that the resulting dmabuf is valid
- // before filing it away.
- let dmabuf = DwlDmabuf(unsafe {
- dwl_context_dmabuf_new(
- self.ctx(),
- fd,
- offset,
- stride,
- modifiers,
- width,
- height,
- fourcc,
- )
- });
- if dmabuf.0.is_null() {
- return Err(GpuDisplayError::FailedImport);
- }
+ fn pending_events(&self) -> bool {
+ // Safe because the function just queries the values of two variables in a context.
+ unsafe { dwl_context_pending_events(self.ctx()) }
+ }
- let next_id = self.dmabuf_next_id;
- self.dmabufs.insert(next_id, dmabuf);
- self.dmabuf_next_id += 1;
- Ok(next_id)
+ fn next_event(&mut self) -> GpuDisplayResult<u64> {
+ let ev = self.pop_event();
+ let descriptor = ev.surface_descriptor as u64;
+ self.current_event = Some(ev);
+ Ok(descriptor)
}
- fn release_import(&mut self, import_id: u32) {
- self.dmabufs.remove(&import_id);
+ fn handle_next_event(
+ &mut self,
+ _surface: &mut Box<dyn GpuDisplaySurface>,
+ ) -> Option<GpuDisplayEvents> {
+ // Should not panic since the common layer only calls this when an event occurs.
+ let event = self.current_event.take().unwrap();
+
+ match event.event_type {
+ DWL_EVENT_TYPE_KEYBOARD_ENTER => None,
+ DWL_EVENT_TYPE_KEYBOARD_LEAVE => None,
+ DWL_EVENT_TYPE_KEYBOARD_KEY => {
+ let linux_keycode = event.params[0] as u16;
+ let pressed = event.params[1] == DWL_KEYBOARD_KEY_STATE_PRESSED;
+ let events = vec![virtio_input_event::key(linux_keycode, pressed)];
+ Some(GpuDisplayEvents {
+ events,
+ device_type: EventDeviceKind::Keyboard,
+ })
+ }
+ // TODO(tutankhamen): slot is always 0, because all the input
+ // events come from mouse device, i.e. only one touch is possible at a time.
+ // Full MT protocol has to be implemented and properly wired later.
+ DWL_EVENT_TYPE_TOUCH_DOWN | DWL_EVENT_TYPE_TOUCH_MOTION => {
+ let tracking_id = if event.event_type == DWL_EVENT_TYPE_TOUCH_DOWN {
+ self.next_tracking_id()
+ } else {
+ self.current_tracking_id()
+ };
+
+ let events = vec![
+ virtio_input_event::multitouch_slot(0),
+ virtio_input_event::multitouch_tracking_id(tracking_id),
+ virtio_input_event::multitouch_absolute_x(max(0, event.params[0])),
+ virtio_input_event::multitouch_absolute_y(max(0, event.params[1])),
+ ];
+ Some(GpuDisplayEvents {
+ events,
+ device_type: EventDeviceKind::Touchscreen,
+ })
+ }
+ DWL_EVENT_TYPE_TOUCH_UP => {
+ let events = vec![
+ virtio_input_event::multitouch_slot(0),
+ virtio_input_event::multitouch_tracking_id(-1),
+ ];
+ Some(GpuDisplayEvents {
+ events,
+ device_type: EventDeviceKind::Touchscreen,
+ })
+ }
+ _ => {
+ error!("unknown event type {}", event.event_type);
+ None
+ }
+ }
}
- fn dispatch_events(&mut self) {
+ fn flush(&self) {
// Safe given that the context pointer is valid.
unsafe {
dwl_context_dispatch(self.ctx());
@@ -193,38 +321,40 @@ impl DisplayT for DisplayWl {
fn create_surface(
&mut self,
parent_surface_id: Option<u32>,
+ surface_id: u32,
width: u32,
height: u32,
- ) -> Result<u32, GpuDisplayError> {
- let parent_ptr = match parent_surface_id {
- Some(id) => match self.get_surface(id).map(|p| p.surface()) {
- Some(ptr) => ptr,
- None => return Err(GpuDisplayError::InvalidSurfaceId),
- },
- None => null_mut(),
- };
+ surf_type: SurfaceType,
+ ) -> GpuDisplayResult<Box<dyn GpuDisplaySurface>> {
+ let parent_id = parent_surface_id.unwrap_or(0);
+
let row_size = width * BYTES_PER_PIXEL;
let fb_size = row_size * height;
let buffer_size = round_up_to_page_size(fb_size as usize * BUFFER_COUNT);
- let buffer_shm = SharedMemory::named("GpuDisplaySurface", buffer_size as u64)
- .map_err(GpuDisplayError::CreateShm)?;
+ let buffer_shm = SharedMemory::named("GpuDisplaySurface", buffer_size as u64)?;
let buffer_mem = MemoryMappingBuilder::new(buffer_size)
.from_shared_memory(&buffer_shm)
.build()
.unwrap();
- // Safe because only a valid context, parent pointer (if not None), and buffer FD are used.
+ let dwl_surf_flags = match surf_type {
+ SurfaceType::Cursor => DWL_SURFACE_FLAG_HAS_ALPHA,
+ SurfaceType::Scanout => DWL_SURFACE_FLAG_RECEIVE_INPUT,
+ };
+ // Safe because only a valid context, parent ID (if not non-zero), and buffer FD are used.
// The returned surface is checked for validity before being filed away.
let surface = DwlSurface(unsafe {
dwl_context_surface_new(
self.ctx(),
- parent_ptr,
+ parent_id,
+ surface_id,
buffer_shm.as_raw_descriptor(),
buffer_size,
fb_size as usize,
width,
height,
row_size,
+ dwl_surf_flags,
)
});
@@ -232,123 +362,48 @@ impl DisplayT for DisplayWl {
return Err(GpuDisplayError::CreateSurface);
}
- let next_id = self.surface_next_id;
- self.surfaces.insert(
- next_id,
- Surface {
- surface,
- row_size,
- buffer_size: fb_size as usize,
- buffer_index: Cell::new(0),
- buffer_mem,
- },
- );
-
- self.surface_next_id += 1;
- Ok(next_id)
- }
-
- fn release_surface(&mut self, surface_id: u32) {
- self.surfaces.remove(&surface_id);
- }
-
- fn framebuffer(&mut self, surface_id: u32) -> Option<GpuDisplayFramebuffer> {
- let surface = self.get_surface(surface_id)?;
- let buffer_index = (surface.buffer_index.get() + 1) % BUFFER_COUNT;
- let framebuffer = surface
- .buffer_mem
- .get_slice(buffer_index * surface.buffer_size, surface.buffer_size)
- .ok()?;
- Some(GpuDisplayFramebuffer::new(
- framebuffer,
- surface.row_size,
- BYTES_PER_PIXEL,
- ))
- }
-
- fn commit(&mut self, surface_id: u32) {
- match self.get_surface(surface_id) {
- Some(surface) => {
- // Safe because only a valid surface is used.
- unsafe {
- dwl_surface_commit(surface.surface());
- }
- }
- None => debug_assert!(false, "invalid surface_id {}", surface_id),
- }
- }
-
- fn next_buffer_in_use(&self, surface_id: u32) -> bool {
- match self.get_surface(surface_id) {
- Some(surface) => {
- let next_buffer_index = (surface.buffer_index.get() + 1) % BUFFER_COUNT;
- // Safe because only a valid surface and buffer index is used.
- unsafe { dwl_surface_buffer_in_use(surface.surface(), next_buffer_index) }
- }
- None => {
- debug_assert!(false, "invalid surface_id {}", surface_id);
- false
- }
- }
- }
-
- fn flip(&mut self, surface_id: u32) {
- match self.get_surface(surface_id) {
- Some(surface) => {
- surface
- .buffer_index
- .set((surface.buffer_index.get() + 1) % BUFFER_COUNT);
- // Safe because only a valid surface and buffer index is used.
- unsafe {
- dwl_surface_flip(surface.surface(), surface.buffer_index.get());
- }
- }
- None => debug_assert!(false, "invalid surface_id {}", surface_id),
- }
+ Ok(Box::new(WaylandSurface {
+ surface,
+ row_size,
+ buffer_size: fb_size as usize,
+ buffer_index: Cell::new(0),
+ buffer_mem,
+ }))
}
- fn flip_to(&mut self, surface_id: u32, import_id: u32) {
- match self.get_surface(surface_id) {
- Some(surface) => {
- match self.dmabufs.get(&import_id) {
- // Safe because only a valid surface and dmabuf is used.
- Some(dmabuf) => unsafe { dwl_surface_flip_to(surface.surface(), dmabuf.0) },
- None => debug_assert!(false, "invalid import_id {}", import_id),
- }
- }
- None => debug_assert!(false, "invalid surface_id {}", surface_id),
- }
- }
-
- fn close_requested(&self, surface_id: u32) -> bool {
- match self.get_surface(surface_id) {
- Some(surface) =>
- // Safe because only a valid surface is used.
- unsafe { dwl_surface_close_requested(surface.surface()) }
- None => false,
- }
- }
+ fn import_memory(
+ &mut self,
+ import_id: u32,
+ descriptor: &dyn AsRawDescriptor,
+ offset: u32,
+ stride: u32,
+ modifiers: u64,
+ width: u32,
+ height: u32,
+ fourcc: u32,
+ ) -> GpuDisplayResult<Box<dyn GpuDisplayImport>> {
+ // Safe given that the context pointer is valid. Any other invalid parameters would be
+ // rejected by dwl_context_dmabuf_new safely. We check that the resulting dmabuf is valid
+ // before filing it away.
+ let dmabuf = DwlDmabuf(unsafe {
+ dwl_context_dmabuf_new(
+ self.ctx(),
+ import_id,
+ descriptor.as_raw_descriptor(),
+ offset,
+ stride,
+ modifiers,
+ width,
+ height,
+ fourcc,
+ )
+ });
- fn set_position(&mut self, surface_id: u32, x: u32, y: u32) {
- match self.get_surface(surface_id) {
- Some(surface) => {
- // Safe because only a valid surface is used.
- unsafe {
- dwl_surface_set_position(surface.surface(), x, y);
- }
- }
- None => debug_assert!(false, "invalid surface_id {}", surface_id),
+ if dmabuf.0.is_null() {
+ return Err(GpuDisplayError::FailedImport);
}
- }
- fn import_event_device(&mut self, _event_device: EventDevice) -> Result<u32, GpuDisplayError> {
- Err(GpuDisplayError::Unsupported)
- }
- fn release_event_device(&mut self, _event_device_id: u32) {
- // unsupported
- }
- fn attach_event_device(&mut self, _surface_id: u32, _event_device_id: u32) {
- // unsupported
+ Ok(Box::new(dmabuf))
}
}
diff --git a/gpu_display/src/gpu_display_x.rs b/gpu_display/src/gpu_display_x.rs
index 16bdd1096..82a654e3e 100644
--- a/gpu_display/src/gpu_display_x.rs
+++ b/gpu_display/src/gpu_display_x.rs
@@ -13,29 +13,25 @@ mod xlib;
use linux_input_sys::virtio_input_event;
use std::cmp::max;
-use std::collections::BTreeMap;
use std::ffi::{c_void, CStr, CString};
use std::mem::{transmute_copy, zeroed};
-use std::num::NonZeroU32;
use std::os::raw::c_ulong;
use std::ptr::{null, null_mut, NonNull};
use std::rc::Rc;
-use std::time::Duration;
use libc::{shmat, shmctl, shmdt, shmget, IPC_CREAT, IPC_PRIVATE, IPC_RMID};
use crate::{
- keycode_converter::KeycodeTranslator, keycode_converter::KeycodeTypes, DisplayT, EventDevice,
- EventDeviceKind, GpuDisplayError, GpuDisplayFramebuffer,
+ keycode_converter::KeycodeTranslator, keycode_converter::KeycodeTypes, DisplayT,
+ EventDeviceKind, GpuDisplayError, GpuDisplayEvents, GpuDisplayFramebuffer, GpuDisplayResult,
+ GpuDisplaySurface, SurfaceType,
};
-use base::{error, AsRawDescriptor, EventType, PollToken, RawDescriptor, WaitContext};
+use base::{AsRawDescriptor, RawDescriptor};
use data_model::VolatileSlice;
const BUFFER_COUNT: usize = 2;
-type ObjectId = NonZeroU32;
-
/// A wrapper for XFree that takes any type.
unsafe fn x_free<T>(t: *mut T) {
xlib::XFree(t as *mut c_void);
@@ -54,10 +50,18 @@ impl Drop for XDisplay {
}
impl XDisplay {
+ /// Returns a pointer to the X display object.
fn as_ptr(&self) -> *mut xlib::Display {
self.0.as_ptr()
}
+ /// Sends any pending commands to the X server.
+ fn flush(&self) {
+ unsafe {
+ xlib::XFlush(self.as_ptr());
+ }
+ }
+
/// Returns true of the XShm extension is supported on this display.
fn supports_shm(&self) -> bool {
unsafe { xlib::XShmQueryExtension(self.as_ptr()) != 0 }
@@ -70,18 +74,6 @@ impl XDisplay {
})?))
}
- /// Returns true if there are events that are on the queue.
- fn pending_events(&self) -> bool {
- unsafe { xlib::XPending(self.as_ptr()) != 0 }
- }
-
- /// Sends any pending commands to the X server.
- fn flush(&self) {
- unsafe {
- xlib::XFlush(self.as_ptr());
- }
- }
-
/// Blocks until the next event from the display is received and returns that event.
///
/// Always flush before using this if any X commands where issued.
@@ -215,7 +207,7 @@ impl Buffer {
}
// Surfaces here are equivalent to XWindows.
-struct Surface {
+struct XSurface {
display: XDisplay,
visual: *mut xlib::Visual,
depth: u32,
@@ -223,8 +215,6 @@ struct Surface {
gc: xlib::GC,
width: u32,
height: u32,
- event_devices: BTreeMap<ObjectId, EventDevice>,
- keycode_translator: KeycodeTranslator,
// Fields for handling the buffer swap chain.
buffers: [Option<Buffer>; BUFFER_COUNT],
@@ -236,94 +226,7 @@ struct Surface {
close_requested: bool,
}
-impl Surface {
- fn create(
- display: XDisplay,
- screen: &XScreen,
- visual: *mut xlib::Visual,
- width: u32,
- height: u32,
- ) -> Surface {
- let keycode_translator = KeycodeTranslator::new(KeycodeTypes::XkbScancode);
- unsafe {
- let depth = xlib::XDefaultDepthOfScreen(screen.as_ptr()) as u32;
-
- let black_pixel = xlib::XBlackPixelOfScreen(screen.as_ptr());
-
- let window = xlib::XCreateSimpleWindow(
- display.as_ptr(),
- xlib::XRootWindowOfScreen(screen.as_ptr()),
- 0,
- 0,
- width,
- height,
- 1,
- black_pixel,
- black_pixel,
- );
-
- let gc = xlib::XCreateGC(display.as_ptr(), window, 0, null_mut());
-
- // Because the event is from an extension, its type must be calculated dynamically.
- let buffer_completion_type =
- xlib::XShmGetEventBase(display.as_ptr()) as u32 + xlib::ShmCompletion;
-
- // Mark this window as responding to close requests.
- let mut delete_window_atom = xlib::XInternAtom(
- display.as_ptr(),
- CStr::from_bytes_with_nul(b"WM_DELETE_WINDOW\0")
- .unwrap()
- .as_ptr(),
- 0,
- );
- xlib::XSetWMProtocols(display.as_ptr(), window, &mut delete_window_atom, 1);
-
- let size_hints = xlib::XAllocSizeHints();
- (*size_hints).flags = (xlib::PMinSize | xlib::PMaxSize) as i64;
- (*size_hints).max_width = width as i32;
- (*size_hints).min_width = width as i32;
- (*size_hints).max_height = height as i32;
- (*size_hints).min_height = height as i32;
- xlib::XSetWMNormalHints(display.as_ptr(), window, size_hints);
- x_free(size_hints);
-
- // We will use redraw the buffer when we are exposed.
- xlib::XSelectInput(
- display.as_ptr(),
- window,
- (xlib::ExposureMask
- | xlib::KeyPressMask
- | xlib::KeyReleaseMask
- | xlib::ButtonPressMask
- | xlib::ButtonReleaseMask
- | xlib::PointerMotionMask) as i64,
- );
-
- xlib::XClearWindow(display.as_ptr(), window);
- xlib::XMapRaised(display.as_ptr(), window);
-
- // Flush everything so that the window is visible immediately.
- display.flush();
-
- Surface {
- display,
- visual,
- depth,
- window,
- gc,
- width,
- height,
- event_devices: Default::default(),
- keycode_translator,
- buffers: Default::default(),
- buffer_next: 0,
- buffer_completion_type,
- delete_window_atom,
- close_requested: false,
- }
- }
- }
-
+impl XSurface {
/// Returns index of the current (on-screen) buffer, or 0 if there are no buffers.
fn current_buffer(&self) -> usize {
match self.buffer_next.checked_sub(1) {
@@ -332,77 +235,6 @@ impl Surface {
}
}
- fn dispatch_to_event_devices(
- &mut self,
- events: &[virtio_input_event],
- device_type: EventDeviceKind,
- ) {
- for event_device in self.event_devices.values_mut() {
- if event_device.kind() != device_type {
- continue;
- }
- if let Err(e) = event_device.send_report(events.iter().cloned()) {
- error!("error sending events to event device: {}", e);
- }
- }
- }
-
- fn handle_event(&mut self, ev: XEvent) {
- match ev.as_enum(self.buffer_completion_type) {
- XEventEnum::KeyEvent(key) => {
- if let Some(linux_keycode) = self.keycode_translator.translate(key.keycode) {
- let events = &[virtio_input_event::key(
- linux_keycode,
- key.type_ == xlib::KeyPress as i32,
- )];
- self.dispatch_to_event_devices(events, EventDeviceKind::Keyboard);
- }
- }
- XEventEnum::ButtonEvent {
- event: button_event,
- pressed,
- } => {
- // We only support a single touch from button 1 (left mouse button).
- if button_event.button & xlib::Button1 != 0 {
- // The touch event *must* be first per the Linux input subsystem's guidance.
- let events = &[
- virtio_input_event::touch(pressed),
- virtio_input_event::absolute_x(max(0, button_event.x)),
- virtio_input_event::absolute_y(max(0, button_event.y)),
- ];
- self.dispatch_to_event_devices(events, EventDeviceKind::Touchscreen);
- }
- }
- XEventEnum::Motion(motion) => {
- if motion.state & xlib::Button1Mask != 0 {
- let events = &[
- virtio_input_event::touch(true),
- virtio_input_event::absolute_x(max(0, motion.x)),
- virtio_input_event::absolute_y(max(0, motion.y)),
- ];
- self.dispatch_to_event_devices(events, EventDeviceKind::Touchscreen);
- }
- }
- XEventEnum::Expose => self.draw_buffer(self.current_buffer()),
- XEventEnum::ClientMessage(xclient_data) => {
- if xclient_data == self.delete_window_atom {
- self.close_requested = true;
- }
- }
- XEventEnum::ShmCompletionEvent(shmseg) => {
- // Find the buffer associated with this event and mark it as not in use.
- for buffer_opt in self.buffers.iter_mut() {
- if let Some(buffer) = buffer_opt {
- if buffer.segment_info.shmseg == shmseg {
- buffer.in_use = false;
- }
- }
- }
- }
- XEventEnum::Unhandled => {}
- }
- }
-
/// Draws the indicated buffer onto the screen.
fn draw_buffer(&mut self, buffer_index: usize) {
let buffer = match self.buffers.get_mut(buffer_index) {
@@ -493,8 +325,13 @@ impl Surface {
self.buffers[buffer_index].as_ref()
}
}
+}
+
+impl GpuDisplaySurface for XSurface {
+ fn surface_descriptor(&self) -> u64 {
+ self.window as u64
+ }
- /// Gets the next framebuffer, allocating if necessary.
fn framebuffer(&mut self) -> Option<GpuDisplayFramebuffer> {
// Framebuffers are lazily allocated. If the next buffer is not in self.buffers, add it
// using push_new_buffer and then get its memory.
@@ -507,7 +344,6 @@ impl Surface {
))
}
- /// True if the next buffer is in use because of an XShmPutImage call.
fn next_buffer_in_use(&self) -> bool {
// Buffers that have not yet been made are not in use, hence unwrap_or(false).
self.buffers
@@ -516,15 +352,40 @@ impl Surface {
.unwrap_or(false)
}
- /// Puts the next buffer onto the screen and sets the next buffer in the swap chain.
+ fn close_requested(&self) -> bool {
+ self.close_requested
+ }
+
fn flip(&mut self) {
let current_buffer_index = self.buffer_next;
self.buffer_next = (self.buffer_next + 1) % self.buffers.len();
self.draw_buffer(current_buffer_index);
}
+
+ fn buffer_completion_type(&self) -> u32 {
+ self.buffer_completion_type
+ }
+
+ fn draw_current_buffer(&mut self) {
+ self.draw_buffer(self.current_buffer())
+ }
+
+ fn on_client_message(&mut self, client_data: u64) {
+ if client_data == self.delete_window_atom {
+ self.close_requested = true;
+ }
+ }
+
+ fn on_shm_completion(&mut self, shm_complete: u64) {
+ for buffer in self.buffers.iter_mut().flatten() {
+ if buffer.segment_info.shmseg == shm_complete {
+ buffer.in_use = false;
+ }
+ }
+ }
}
-impl Drop for Surface {
+impl Drop for XSurface {
fn drop(&mut self) {
// Safe given it should always be of the correct type.
unsafe {
@@ -534,32 +395,25 @@ impl Drop for Surface {
}
}
-#[derive(PollToken)]
-enum DisplayXPollToken {
- Display,
- EventDevice { event_device_id: u32 },
-}
-
pub struct DisplayX {
- wait_ctx: WaitContext<DisplayXPollToken>,
display: XDisplay,
screen: XScreen,
visual: *mut xlib::Visual,
- next_id: ObjectId,
- surfaces: BTreeMap<ObjectId, Surface>,
- event_devices: BTreeMap<ObjectId, EventDevice>,
+ keycode_translator: KeycodeTranslator,
+ current_event: Option<XEvent>,
+ mt_tracking_id: u16,
}
impl DisplayX {
- pub fn open_display(display: Option<&str>) -> Result<DisplayX, GpuDisplayError> {
- let wait_ctx = WaitContext::new().map_err(|_| GpuDisplayError::Allocate)?;
-
+ pub fn open_display(display: Option<&str>) -> GpuDisplayResult<DisplayX> {
let display_cstr = match display.map(CString::new) {
Some(Ok(s)) => Some(s),
Some(Err(_)) => return Err(GpuDisplayError::InvalidPath),
None => None,
};
+ let keycode_translator = KeycodeTranslator::new(KeycodeTypes::XkbScancode);
+
unsafe {
// Open the display
let display = match NonNull::new(xlib::XOpenDisplay(
@@ -572,10 +426,6 @@ impl DisplayX {
None => return Err(GpuDisplayError::Connect),
};
- wait_ctx
- .add(&display, DisplayXPollToken::Display)
- .map_err(|_| GpuDisplayError::Allocate)?;
-
// Check for required extension.
if !display.supports_shm() {
return Err(GpuDisplayError::RequiredFeature("xshm extension"));
@@ -617,226 +467,220 @@ impl DisplayX {
x_free(visual_info);
Ok(DisplayX {
- wait_ctx,
display,
screen,
visual,
- next_id: ObjectId::new(1).unwrap(),
- surfaces: Default::default(),
- event_devices: Default::default(),
+ keycode_translator,
+ current_event: None,
+ mt_tracking_id: 0,
})
}
}
- fn surface_ref(&self, surface_id: u32) -> Option<&Surface> {
- ObjectId::new(surface_id).and_then(move |id| self.surfaces.get(&id))
+ pub fn next_tracking_id(&mut self) -> i32 {
+ let cur_id: i32 = self.mt_tracking_id as i32;
+ self.mt_tracking_id = self.mt_tracking_id.wrapping_add(1);
+ cur_id
}
- fn surface_mut(&mut self, surface_id: u32) -> Option<&mut Surface> {
- ObjectId::new(surface_id).and_then(move |id| self.surfaces.get_mut(&id))
+ pub fn current_tracking_id(&self) -> i32 {
+ self.mt_tracking_id as i32
}
+}
- fn event_device(&self, event_device_id: u32) -> Option<&EventDevice> {
- ObjectId::new(event_device_id).and_then(move |id| self.event_devices.get(&id))
+impl DisplayT for DisplayX {
+ fn pending_events(&self) -> bool {
+ unsafe { xlib::XPending(self.display.as_ptr()) != 0 }
}
- fn event_device_mut(&mut self, event_device_id: u32) -> Option<&mut EventDevice> {
- ObjectId::new(event_device_id).and_then(move |id| self.event_devices.get_mut(&id))
+ fn flush(&self) {
+ self.display.flush();
}
- fn handle_event(&mut self, ev: XEvent) {
- let window = ev.window();
- for surface in self.surfaces.values_mut() {
- if surface.window != window {
- continue;
- }
- surface.handle_event(ev);
- return;
- }
+ fn next_event(&mut self) -> GpuDisplayResult<u64> {
+ let ev = self.display.next_event();
+ let descriptor = ev.window() as u64;
+ self.current_event = Some(ev);
+ Ok(descriptor)
}
- fn dispatch_display_events(&mut self) {
- loop {
- self.display.flush();
- if !self.display.pending_events() {
- break;
- }
- let ev = self.display.next_event();
- self.handle_event(ev);
- }
- }
+ fn handle_next_event(
+ &mut self,
+ surface: &mut Box<dyn GpuDisplaySurface>,
+ ) -> Option<GpuDisplayEvents> {
+ // Should not panic since the common layer only calls this when an event exists.
+ let ev = self.current_event.take().unwrap();
- fn handle_event_device(&mut self, event_device_id: u32) {
- if let Some(event_device) = self.event_device(event_device_id) {
- // TODO(zachr): decode the event and forward to the device.
- let _ = event_device.recv_event_encoded();
- }
- }
+ match ev.as_enum(surface.buffer_completion_type()) {
+ XEventEnum::KeyEvent(key) => {
+ if let Some(linux_keycode) = self.keycode_translator.translate(key.keycode) {
+ let events = vec![virtio_input_event::key(
+ linux_keycode,
+ key.type_ == xlib::KeyPress as i32,
+ )];
- fn handle_poll_ctx(&mut self) -> base::Result<()> {
- let wait_events = self.wait_ctx.wait_timeout(Duration::default())?;
- for wait_event in wait_events.iter().filter(|e| e.is_writable) {
- if let DisplayXPollToken::EventDevice { event_device_id } = wait_event.token {
- if let Some(event_device) = self.event_device_mut(event_device_id) {
- if !event_device.flush_buffered_events()? {
- continue;
- }
+ return Some(GpuDisplayEvents {
+ events,
+ device_type: EventDeviceKind::Keyboard,
+ });
}
- // Although this looks exactly like the previous if-block, we need to reborrow self
- // as immutable in order to make use of self.wait_ctx.
- if let Some(event_device) = self.event_device(event_device_id) {
- self.wait_ctx.modify(
- event_device,
- EventType::Read,
- DisplayXPollToken::EventDevice { event_device_id },
- )?;
+ }
+ XEventEnum::ButtonEvent {
+ event: button_event,
+ pressed,
+ } => {
+ // We only support a single touch from button 1 (left mouse button).
+ // TODO(tutankhamen): slot is always 0, because all the input
+ // events come from mouse device, i.e. only one touch is possible at a time.
+ // Full MT protocol has to be implemented and properly wired later.
+ if button_event.button & xlib::Button1 != 0 {
+ // The touch event *must* be first per the Linux input subsystem's guidance.
+ let mut events = vec![virtio_input_event::multitouch_slot(0)];
+
+ if pressed {
+ events.push(virtio_input_event::multitouch_tracking_id(
+ self.next_tracking_id(),
+ ));
+ events.push(virtio_input_event::multitouch_absolute_x(max(
+ 0,
+ button_event.x,
+ )));
+ events.push(virtio_input_event::multitouch_absolute_y(max(
+ 0,
+ button_event.y,
+ )));
+ } else {
+ events.push(virtio_input_event::multitouch_tracking_id(-1));
+ }
+
+ return Some(GpuDisplayEvents {
+ events,
+ device_type: EventDeviceKind::Touchscreen,
+ });
}
}
- }
+ XEventEnum::Motion(motion) => {
+ if motion.state & xlib::Button1Mask != 0 {
+ let events = vec![
+ virtio_input_event::multitouch_slot(0),
+ virtio_input_event::multitouch_tracking_id(self.current_tracking_id()),
+ virtio_input_event::multitouch_absolute_x(max(0, motion.x)),
+ virtio_input_event::multitouch_absolute_y(max(0, motion.y)),
+ ];
- for wait_event in wait_events.iter().filter(|e| e.is_readable) {
- match wait_event.token {
- DisplayXPollToken::Display => self.dispatch_display_events(),
- DisplayXPollToken::EventDevice { event_device_id } => {
- self.handle_event_device(event_device_id)
+ return Some(GpuDisplayEvents {
+ events,
+ device_type: EventDeviceKind::Touchscreen,
+ });
}
}
+ XEventEnum::Expose => surface.draw_current_buffer(),
+ XEventEnum::ClientMessage(xclient_data) => {
+ surface.on_client_message(xclient_data);
+ return None;
+ }
+ XEventEnum::ShmCompletionEvent(shmseg) => {
+ surface.on_shm_completion(shmseg);
+ return None;
+ }
+ XEventEnum::Unhandled => return None,
}
- Ok(())
- }
-}
-
-impl DisplayT for DisplayX {
- fn dispatch_events(&mut self) {
- if let Err(e) = self.handle_poll_ctx() {
- error!("failed to dispatch events: {}", e);
- }
+ None
}
fn create_surface(
&mut self,
parent_surface_id: Option<u32>,
+ _surface_id: u32,
width: u32,
height: u32,
- ) -> Result<u32, GpuDisplayError> {
+ _surf_type: SurfaceType,
+ ) -> GpuDisplayResult<Box<dyn GpuDisplaySurface>> {
if parent_surface_id.is_some() {
return Err(GpuDisplayError::Unsupported);
}
- let new_surface = Surface::create(
- self.display.clone(),
- &self.screen,
- self.visual,
- width,
- height,
- );
- let new_surface_id = self.next_id;
- self.surfaces.insert(new_surface_id, new_surface);
- self.next_id = ObjectId::new(self.next_id.get() + 1).unwrap();
-
- Ok(new_surface_id.get())
- }
-
- fn release_surface(&mut self, surface_id: u32) {
- if let Some(mut surface) =
- ObjectId::new(surface_id).and_then(|id| self.surfaces.remove(&id))
- {
- self.event_devices.append(&mut surface.event_devices);
- }
- }
-
- fn framebuffer(&mut self, surface_id: u32) -> Option<GpuDisplayFramebuffer> {
- self.surface_mut(surface_id).and_then(|s| s.framebuffer())
- }
+ unsafe {
+ let depth = xlib::XDefaultDepthOfScreen(self.screen.as_ptr()) as u32;
- fn next_buffer_in_use(&self, surface_id: u32) -> bool {
- self.surface_ref(surface_id)
- .map(|s| s.next_buffer_in_use())
- .unwrap_or(false)
- }
+ let black_pixel = xlib::XBlackPixelOfScreen(self.screen.as_ptr());
- fn flip(&mut self, surface_id: u32) {
- if let Some(surface) = self.surface_mut(surface_id) {
- surface.flip()
- }
- }
+ let window = xlib::XCreateSimpleWindow(
+ self.display.as_ptr(),
+ xlib::XRootWindowOfScreen(self.screen.as_ptr()),
+ 0,
+ 0,
+ width,
+ height,
+ 1,
+ black_pixel,
+ black_pixel,
+ );
- fn close_requested(&self, surface_id: u32) -> bool {
- self.surface_ref(surface_id)
- .map(|s| s.close_requested)
- .unwrap_or(true)
- }
+ let gc = xlib::XCreateGC(self.display.as_ptr(), window, 0, null_mut());
- #[allow(unused_variables)]
- fn import_dmabuf(
- &mut self,
- fd: RawDescriptor,
- offset: u32,
- stride: u32,
- modifiers: u64,
- width: u32,
- height: u32,
- fourcc: u32,
- ) -> Result<u32, GpuDisplayError> {
- Err(GpuDisplayError::Unsupported)
- }
- #[allow(unused_variables)]
- fn release_import(&mut self, import_id: u32) {
- // unsupported
- }
- #[allow(unused_variables)]
- fn commit(&mut self, surface_id: u32) {
- // unsupported
- }
- #[allow(unused_variables)]
- fn flip_to(&mut self, surface_id: u32, import_id: u32) {
- // unsupported
- }
- #[allow(unused_variables)]
- fn set_position(&mut self, surface_id: u32, x: u32, y: u32) {
- // unsupported
- }
+ // Because the event is from an extension, its type must be calculated dynamically.
+ let buffer_completion_type =
+ xlib::XShmGetEventBase(self.display.as_ptr()) as u32 + xlib::ShmCompletion;
- fn import_event_device(&mut self, event_device: EventDevice) -> Result<u32, GpuDisplayError> {
- let new_event_device_id = self.next_id;
+ // Mark this window as responding to close requests.
+ let mut delete_window_atom = xlib::XInternAtom(
+ self.display.as_ptr(),
+ CStr::from_bytes_with_nul(b"WM_DELETE_WINDOW\0")
+ .unwrap()
+ .as_ptr(),
+ 0,
+ );
+ xlib::XSetWMProtocols(self.display.as_ptr(), window, &mut delete_window_atom, 1);
- self.wait_ctx
- .add(
- &event_device,
- DisplayXPollToken::EventDevice {
- event_device_id: new_event_device_id.get(),
- },
- )
- .map_err(|_| GpuDisplayError::Allocate)?;
+ let size_hints = xlib::XAllocSizeHints();
+ (*size_hints).flags = (xlib::PMinSize | xlib::PMaxSize) as i64;
+ (*size_hints).max_width = width as i32;
+ (*size_hints).min_width = width as i32;
+ (*size_hints).max_height = height as i32;
+ (*size_hints).min_height = height as i32;
+ xlib::XSetWMNormalHints(self.display.as_ptr(), window, size_hints);
+ x_free(size_hints);
- self.event_devices.insert(new_event_device_id, event_device);
- self.next_id = ObjectId::new(self.next_id.get() + 1).unwrap();
+ // We will use redraw the buffer when we are exposed.
+ xlib::XSelectInput(
+ self.display.as_ptr(),
+ window,
+ (xlib::ExposureMask
+ | xlib::KeyPressMask
+ | xlib::KeyReleaseMask
+ | xlib::ButtonPressMask
+ | xlib::ButtonReleaseMask
+ | xlib::PointerMotionMask) as i64,
+ );
- Ok(new_event_device_id.get())
- }
+ xlib::XClearWindow(self.display.as_ptr(), window);
+ xlib::XMapRaised(self.display.as_ptr(), window);
- fn release_event_device(&mut self, event_device_id: u32) {
- ObjectId::new(event_device_id).and_then(|id| self.event_devices.remove(&id));
- }
+ // Flush everything so that the window is visible immediately.
+ self.display.flush();
- fn attach_event_device(&mut self, surface_id: u32, event_device_id: u32) {
- let event_device_id = match ObjectId::new(event_device_id) {
- Some(id) => id,
- None => return,
- };
- let surface_id = match ObjectId::new(surface_id) {
- Some(id) => id,
- None => return,
- };
- let surface = self.surfaces.get_mut(&surface_id).unwrap();
- let event_device = self.event_devices.remove(&event_device_id).unwrap();
- surface.event_devices.insert(event_device_id, event_device);
+ Ok(Box::new(XSurface {
+ display: self.display.clone(),
+ visual: self.visual,
+ depth,
+ window,
+ gc,
+ width,
+ height,
+ buffers: Default::default(),
+ buffer_next: 0,
+ buffer_completion_type,
+ delete_window_atom,
+ close_requested: false,
+ }))
+ }
}
}
impl AsRawDescriptor for DisplayX {
fn as_raw_descriptor(&self) -> RawDescriptor {
- self.wait_ctx.as_raw_descriptor()
+ self.display.as_raw_descriptor()
}
}
diff --git a/gpu_display/src/keycode_converter/mod.rs b/gpu_display/src/keycode_converter/mod.rs
index 884709585..00549ed04 100644
--- a/gpu_display/src/keycode_converter/mod.rs
+++ b/gpu_display/src/keycode_converter/mod.rs
@@ -16,14 +16,12 @@ pub enum KeycodeTypes {
}
/// Translates scancodes of a particular type into Linux keycodes.
-#[allow(dead_code)]
pub struct KeycodeTranslator {
keycode_map: HashMap<u32, MapEntry>,
}
impl KeycodeTranslator {
/// Create a new KeycodeTranslator that translates from the `from_type` type to Linux keycodes.
- #[allow(dead_code)]
pub fn new(from_type: KeycodeTypes) -> KeycodeTranslator {
let mut kcm: HashMap<u32, MapEntry> = HashMap::new();
for entry in KEYCODE_MAP.iter() {
@@ -40,7 +38,6 @@ impl KeycodeTranslator {
}
/// Translates the scancode in `from_code` into a Linux keycode.
- #[allow(dead_code)]
pub fn translate(&self, from_code: u32) -> Option<u16> {
Some(self.keycode_map.get(&from_code)?.linux_keycode)
}
diff --git a/gpu_display/src/lib.rs b/gpu_display/src/lib.rs
index 1f8ea0f13..e6522f767 100644
--- a/gpu_display/src/lib.rs
+++ b/gpu_display/src/lib.rs
@@ -2,13 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-//! Crate for displaying simple surfaces and GPU buffers over wayland.
+//! Crate for displaying simple surfaces and GPU buffers over a low-level display backend such as
+//! Wayland or X.
-use std::fmt::{self, Display};
+use std::collections::BTreeMap;
+use std::io::Error as IoError;
use std::path::Path;
+use std::time::Duration;
-use base::{AsRawDescriptor, Error as SysError, RawDescriptor};
+use base::{AsRawDescriptor, Error as BaseError, EventType, PollToken, RawDescriptor, WaitContext};
use data_model::VolatileSlice;
+use remain::sorted;
+use thiserror::Error;
mod event_device;
mod gpu_display_stub;
@@ -19,51 +24,80 @@ mod gpu_display_x;
mod keycode_converter;
pub use event_device::{EventDevice, EventDeviceKind};
+use linux_input_sys::virtio_input_event;
/// An error generated by `GpuDisplay`.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum GpuDisplayError {
/// An internal allocation failed.
+ #[error("internal allocation failed")]
Allocate,
+ /// A base error occurred.
+ #[error("received a base error: {0}")]
+ BaseError(BaseError),
/// Connecting to the compositor failed.
+ #[error("failed to connect to compositor")]
Connect,
/// Creating event file descriptor failed.
+ #[error("failed to create event file descriptor")]
CreateEvent,
- /// Creating shared memory failed.
- CreateShm(SysError),
/// Failed to create a surface on the compositor.
+ #[error("failed to crate surface on the compositor")]
CreateSurface,
/// Failed to import a buffer to the compositor.
+ #[error("failed to import a buffer to the compositor")]
FailedImport,
+ /// The import ID is invalid.
+ #[error("invalid import ID")]
+ InvalidImportId,
+ /// The path is invalid.
+ #[error("invalid path")]
+ InvalidPath,
/// The surface ID is invalid.
+ #[error("invalid surface ID")]
InvalidSurfaceId,
+ /// An input/output error occured.
+ #[error("an input/output error occur: {0}")]
+ IoError(IoError),
/// A required feature was missing.
+ #[error("required feature was missing: {0}")]
RequiredFeature(&'static str),
- /// The path is invalid.
- InvalidPath,
/// The method is unsupported by the implementation.
+ #[error("unsupported by the implementation")]
Unsupported,
}
-impl Display for GpuDisplayError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::GpuDisplayError::*;
-
- match self {
- Allocate => write!(f, "internal allocation failed"),
- Connect => write!(f, "failed to connect to compositor"),
- CreateEvent => write!(f, "failed to create event file descriptor"),
- CreateShm(e) => write!(f, "failed to create shared memory: {}", e),
- CreateSurface => write!(f, "failed to crate surface on the compositor"),
- FailedImport => write!(f, "failed to import a buffer to the compositor"),
- InvalidPath => write!(f, "invalid path"),
- InvalidSurfaceId => write!(f, "invalid surface ID"),
- RequiredFeature(feature) => write!(f, "required feature was missing: {}", feature),
- Unsupported => write!(f, "unsupported by the implementation"),
- }
+pub type GpuDisplayResult<T> = std::result::Result<T, GpuDisplayError>;
+
+impl From<BaseError> for GpuDisplayError {
+ fn from(e: BaseError) -> GpuDisplayError {
+ GpuDisplayError::BaseError(e)
+ }
+}
+
+impl From<IoError> for GpuDisplayError {
+ fn from(e: IoError) -> GpuDisplayError {
+ GpuDisplayError::IoError(e)
}
}
+/// A surface type
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum SurfaceType {
+ /// Scanout surface
+ Scanout,
+ /// Mouse cursor surface
+ Cursor,
+}
+
+/// Poll token for display instances
+#[derive(PollToken)]
+pub enum DisplayPollToken {
+ Display,
+ EventDevice { event_device_id: u32 },
+}
+
#[derive(Clone)]
pub struct GpuDisplayFramebuffer<'a> {
framebuffer: VolatileSlice<'a>,
@@ -119,47 +153,130 @@ impl<'a> GpuDisplayFramebuffer<'a> {
}
}
+/// Empty trait, just used as a bounds for now
+trait GpuDisplayImport {}
+
+trait GpuDisplaySurface {
+ /// Returns an unique ID associated with the surface. This is typically generated by the
+ /// compositor or cast of a raw pointer.
+ fn surface_descriptor(&self) -> u64 {
+ 0
+ }
+
+ /// Returns the next framebuffer, allocating if necessary.
+ fn framebuffer(&mut self) -> Option<GpuDisplayFramebuffer>;
+
+ /// Returns true if the next buffer in the swapchain is already in use.
+ fn next_buffer_in_use(&self) -> bool {
+ false
+ }
+
+ /// Returns true if the surface should be closed.
+ fn close_requested(&self) -> bool {
+ false
+ }
+
+ /// Puts the next buffer on the screen, making it the current buffer.
+ fn flip(&mut self) {
+ // no-op
+ }
+
+ /// Puts the specified import_id on the screen.
+ fn flip_to(&mut self, _import_id: u32) {
+ // no-op
+ }
+
+ /// Commits the surface to the compositor.
+ fn commit(&mut self) -> GpuDisplayResult<()> {
+ Ok(())
+ }
+
+ /// Sets the position of the identified subsurface relative to its parent.
+ fn set_position(&mut self, _x: u32, _y: u32) {
+ // no-op
+ }
+
+ /// Returns the type of the completed buffer.
+ fn buffer_completion_type(&self) -> u32 {
+ 0
+ }
+
+ /// Draws the current buffer on the screen.
+ fn draw_current_buffer(&mut self) {
+ // no-op
+ }
+
+ /// Handles a compositor-specific client event.
+ fn on_client_message(&mut self, _client_data: u64) {
+ // no-op
+ }
+
+ /// Handles a compositor-specific shared memory completion event.
+ fn on_shm_completion(&mut self, _shm_complete: u64) {
+ // no-op
+ }
+
+ /// Sets the scanout ID for the surface.
+ fn set_scanout_id(&mut self, _scanout_id: u32) {
+ // no-op
+ }
+}
+
+struct GpuDisplayEvents {
+ events: Vec<virtio_input_event>,
+ device_type: EventDeviceKind,
+}
+
trait DisplayT: AsRawDescriptor {
- fn import_dmabuf(
+ /// Returns true if there are events that are on the queue.
+ fn pending_events(&self) -> bool {
+ false
+ }
+
+ /// Sends any pending commands to the compositor.
+ fn flush(&self) {
+ // no-op
+ }
+
+ /// Returns the surface descirptor associated with the current event
+ fn next_event(&mut self) -> GpuDisplayResult<u64> {
+ Ok(0)
+ }
+
+ /// Handles the event from the compositor, and returns an list of events
+ fn handle_next_event(
&mut self,
- fd: RawDescriptor,
- offset: u32,
- stride: u32,
- modifiers: u64,
- width: u32,
- height: u32,
- fourcc: u32,
- ) -> Result<u32, GpuDisplayError>;
- fn release_import(&mut self, import_id: u32);
- fn dispatch_events(&mut self);
+ _surface: &mut Box<dyn GpuDisplaySurface>,
+ ) -> Option<GpuDisplayEvents> {
+ None
+ }
+
+ /// Creates a surface with the given parameters. The display backend is given a non-zero
+ /// `surface_id` as a handle for subsequent operations.
fn create_surface(
&mut self,
parent_surface_id: Option<u32>,
- width: u32,
- height: u32,
- ) -> Result<u32, GpuDisplayError>;
- fn release_surface(&mut self, surface_id: u32);
- fn framebuffer(&mut self, surface_id: u32) -> Option<GpuDisplayFramebuffer>;
- fn framebuffer_region(
- &mut self,
surface_id: u32,
- x: u32,
- y: u32,
width: u32,
height: u32,
- ) -> Option<GpuDisplayFramebuffer> {
- let framebuffer = self.framebuffer(surface_id)?;
- framebuffer.sub_region(x, y, width, height)
+ surf_type: SurfaceType,
+ ) -> GpuDisplayResult<Box<dyn GpuDisplaySurface>>;
+
+ /// Imports memory into the display backend. The display backend is given a non-zero
+ /// `import_id` as a handle for subsequent operations.
+ fn import_memory(
+ &mut self,
+ _import_id: u32,
+ _descriptor: &dyn AsRawDescriptor,
+ _offset: u32,
+ _stride: u32,
+ _modifiers: u64,
+ _width: u32,
+ _height: u32,
+ _fourcc: u32,
+ ) -> GpuDisplayResult<Box<dyn GpuDisplayImport>> {
+ Err(GpuDisplayError::Unsupported)
}
- fn commit(&mut self, surface_id: u32);
- fn next_buffer_in_use(&self, surface_id: u32) -> bool;
- fn flip(&mut self, surface_id: u32);
- fn flip_to(&mut self, surface_id: u32, import_id: u32);
- fn close_requested(&self, surface_id: u32) -> bool;
- fn set_position(&mut self, surface_id: u32, x: u32, y: u32);
- fn import_event_device(&mut self, event_device: EventDevice) -> Result<u32, GpuDisplayError>;
- fn release_event_device(&mut self, event_device_id: u32);
- fn attach_event_device(&mut self, surface_id: u32, event_device_id: u32);
}
/// A connection to the compositor and associated collection of state.
@@ -167,12 +284,21 @@ trait DisplayT: AsRawDescriptor {
/// The user of `GpuDisplay` can use `AsRawDescriptor` to poll on the compositor connection's file
/// descriptor. When the connection is readable, `dispatch_events` can be called to process it.
pub struct GpuDisplay {
+ next_id: u32,
+ event_devices: BTreeMap<u32, EventDevice>,
+ surfaces: BTreeMap<u32, Box<dyn GpuDisplaySurface>>,
+ imports: BTreeMap<u32, Box<dyn GpuDisplayImport>>,
+ // `inner` must be after `imports` and `surfaces` to ensure those objects are dropped before
+ // the display context. The drop order for fields inside a struct is the order in which they
+ // are declared [Rust RFC 1857].
inner: Box<dyn DisplayT>,
+ wait_ctx: WaitContext<DisplayPollToken>,
is_x: bool,
}
impl GpuDisplay {
- pub fn open_x<S: AsRef<str>>(display_name: Option<S>) -> Result<GpuDisplay, GpuDisplayError> {
+ /// Opens a connection to X server
+ pub fn open_x<S: AsRef<str>>(display_name: Option<S>) -> GpuDisplayResult<GpuDisplay> {
let _ = display_name;
#[cfg(feature = "x")]
{
@@ -180,29 +306,59 @@ impl GpuDisplay {
Some(s) => gpu_display_x::DisplayX::open_display(Some(s.as_ref()))?,
None => gpu_display_x::DisplayX::open_display(None)?,
};
- let inner = Box::new(display);
- Ok(GpuDisplay { inner, is_x: true })
+
+ let wait_ctx = WaitContext::new()?;
+ wait_ctx.add(&display, DisplayPollToken::Display)?;
+
+ Ok(GpuDisplay {
+ inner: Box::new(display),
+ next_id: 1,
+ event_devices: Default::default(),
+ surfaces: Default::default(),
+ imports: Default::default(),
+ wait_ctx,
+ is_x: true,
+ })
}
#[cfg(not(feature = "x"))]
Err(GpuDisplayError::Unsupported)
}
/// Opens a fresh connection to the compositor.
- pub fn open_wayland<P: AsRef<Path>>(
- wayland_path: Option<P>,
- ) -> Result<GpuDisplay, GpuDisplayError> {
+ pub fn open_wayland<P: AsRef<Path>>(wayland_path: Option<P>) -> GpuDisplayResult<GpuDisplay> {
let display = match wayland_path {
Some(s) => gpu_display_wl::DisplayWl::new(Some(s.as_ref()))?,
None => gpu_display_wl::DisplayWl::new(None)?,
};
- let inner = Box::new(display);
- Ok(GpuDisplay { inner, is_x: false })
+
+ let wait_ctx = WaitContext::new()?;
+ wait_ctx.add(&display, DisplayPollToken::Display)?;
+
+ Ok(GpuDisplay {
+ inner: Box::new(display),
+ next_id: 1,
+ event_devices: Default::default(),
+ surfaces: Default::default(),
+ imports: Default::default(),
+ wait_ctx,
+ is_x: false,
+ })
}
- pub fn open_stub() -> Result<GpuDisplay, GpuDisplayError> {
+ pub fn open_stub() -> GpuDisplayResult<GpuDisplay> {
let display = gpu_display_stub::DisplayStub::new()?;
- let inner = Box::new(display);
- Ok(GpuDisplay { inner, is_x: false })
+ let wait_ctx = WaitContext::new()?;
+ wait_ctx.add(&display, DisplayPollToken::Display)?;
+
+ Ok(GpuDisplay {
+ inner: Box::new(display),
+ next_id: 1,
+ event_devices: Default::default(),
+ surfaces: Default::default(),
+ imports: Default::default(),
+ wait_ctx,
+ is_x: false,
+ })
}
/// Return whether this display is an X display
@@ -210,30 +366,67 @@ impl GpuDisplay {
self.is_x
}
- /// Imports a dmabuf to the compositor for use as a surface buffer and returns a handle to it.
- pub fn import_dmabuf(
- &mut self,
- fd: RawDescriptor,
- offset: u32,
- stride: u32,
- modifiers: u64,
- width: u32,
- height: u32,
- fourcc: u32,
- ) -> Result<u32, GpuDisplayError> {
- self.inner
- .import_dmabuf(fd, offset, stride, modifiers, width, height, fourcc)
+ fn handle_event_device(&mut self, event_device_id: u32) {
+ if let Some(event_device) = self.event_devices.get(&event_device_id) {
+ // TODO(zachr): decode the event and forward to the device.
+ let _ = event_device.recv_event_encoded();
+ }
}
- /// Releases a previously imported dmabuf identified by the given handle.
- pub fn release_import(&mut self, import_id: u32) {
- self.inner.release_import(import_id);
+ fn dispatch_display_events(&mut self) -> GpuDisplayResult<()> {
+ self.inner.flush();
+ while self.inner.pending_events() {
+ let surface_descriptor = self.inner.next_event()?;
+
+ for surface in self.surfaces.values_mut() {
+ if surface_descriptor != surface.surface_descriptor() {
+ continue;
+ }
+
+ if let Some(gpu_display_events) = self.inner.handle_next_event(surface) {
+ for event_device in self.event_devices.values_mut() {
+ if event_device.kind() != gpu_display_events.device_type {
+ continue;
+ }
+
+ event_device.send_report(gpu_display_events.events.iter().cloned())?;
+ }
+ }
+ }
+ }
+
+ Ok(())
}
/// Dispatches internal events that were received from the compositor since the last call to
/// `dispatch_events`.
- pub fn dispatch_events(&mut self) {
- self.inner.dispatch_events()
+ pub fn dispatch_events(&mut self) -> GpuDisplayResult<()> {
+ let wait_events = self.wait_ctx.wait_timeout(Duration::default())?;
+ for wait_event in wait_events.iter().filter(|e| e.is_writable) {
+ if let DisplayPollToken::EventDevice { event_device_id } = wait_event.token {
+ if let Some(event_device) = self.event_devices.get_mut(&event_device_id) {
+ if !event_device.flush_buffered_events()? {
+ continue;
+ }
+ self.wait_ctx.modify(
+ event_device,
+ EventType::Read,
+ DisplayPollToken::EventDevice { event_device_id },
+ )?;
+ }
+ }
+ }
+
+ for wait_event in wait_events.iter().filter(|e| e.is_readable) {
+ match wait_event.token {
+ DisplayPollToken::Display => self.dispatch_display_events()?,
+ DisplayPollToken::EventDevice { event_device_id } => {
+ self.handle_event_device(event_device_id)
+ }
+ }
+ }
+
+ Ok(())
}
/// Creates a surface on the the compositor as either a top level window, or child of another
@@ -243,18 +436,37 @@ impl GpuDisplay {
parent_surface_id: Option<u32>,
width: u32,
height: u32,
- ) -> Result<u32, GpuDisplayError> {
- self.inner.create_surface(parent_surface_id, width, height)
+ surf_type: SurfaceType,
+ ) -> GpuDisplayResult<u32> {
+ if let Some(parent_id) = parent_surface_id {
+ if !self.surfaces.contains_key(&parent_id) {
+ return Err(GpuDisplayError::InvalidSurfaceId);
+ }
+ }
+
+ let new_surface_id = self.next_id;
+ let new_surface = self.inner.create_surface(
+ parent_surface_id,
+ new_surface_id,
+ width,
+ height,
+ surf_type,
+ )?;
+
+ self.next_id += 1;
+ self.surfaces.insert(new_surface_id, new_surface);
+ Ok(new_surface_id)
}
/// Releases a previously created surface identified by the given handle.
pub fn release_surface(&mut self, surface_id: u32) {
- self.inner.release_surface(surface_id)
+ self.surfaces.remove(&surface_id);
}
/// Gets a reference to an unused framebuffer for the identified surface.
pub fn framebuffer(&mut self, surface_id: u32) -> Option<GpuDisplayFramebuffer> {
- self.inner.framebuffer(surface_id)
+ let surface = self.surfaces.get_mut(&surface_id)?;
+ surface.framebuffer()
}
/// Gets a reference to an unused framebuffer for the identified surface.
@@ -266,13 +478,8 @@ impl GpuDisplay {
width: u32,
height: u32,
) -> Option<GpuDisplayFramebuffer> {
- self.inner
- .framebuffer_region(surface_id, x, y, width, height)
- }
-
- /// Commits any pending state for the identified surface.
- pub fn commit(&mut self, surface_id: u32) {
- self.inner.commit(surface_id)
+ let framebuffer = self.framebuffer(surface_id)?;
+ framebuffer.sub_region(x, y, width, height)
}
/// Returns true if the next buffer in the buffer queue for the given surface is currently in
@@ -281,52 +488,132 @@ impl GpuDisplay {
/// If the next buffer is in use, the memory returned from `framebuffer_memory` should not be
/// written to.
pub fn next_buffer_in_use(&self, surface_id: u32) -> bool {
- self.inner.next_buffer_in_use(surface_id)
+ self.surfaces
+ .get(&surface_id)
+ .map(|s| s.next_buffer_in_use())
+ .unwrap_or(false)
}
/// Changes the visible contents of the identified surface to the contents of the framebuffer
/// last returned by `framebuffer_memory` for this surface.
pub fn flip(&mut self, surface_id: u32) {
- self.inner.flip(surface_id)
- }
-
- /// Changes the visible contents of the identified surface to that of the identified imported
- /// buffer.
- pub fn flip_to(&mut self, surface_id: u32, import_id: u32) {
- self.inner.flip_to(surface_id, import_id)
+ if let Some(surface) = self.surfaces.get_mut(&surface_id) {
+ surface.flip()
+ }
}
/// Returns true if the identified top level surface has been told to close by the compositor,
/// and by extension the user.
pub fn close_requested(&self, surface_id: u32) -> bool {
- self.inner.close_requested(surface_id)
+ self.surfaces
+ .get(&surface_id)
+ .map(|s| s.close_requested())
+ .unwrap_or(true)
}
- /// Sets the position of the identified subsurface relative to its parent.
- ///
- /// The change in position will not be visible until `commit` is called for the parent surface.
- pub fn set_position(&mut self, surface_id: u32, x: u32, y: u32) {
- self.inner.set_position(surface_id, x, y)
+ /// Imports the given `event_device` into the display, returning an event device id on success.
+ /// This device may be used to poll for input events.
+ pub fn import_event_device(&mut self, event_device: EventDevice) -> GpuDisplayResult<u32> {
+ let new_event_device_id = self.next_id;
+
+ self.wait_ctx.add(
+ &event_device,
+ DisplayPollToken::EventDevice {
+ event_device_id: new_event_device_id,
+ },
+ )?;
+
+ self.event_devices.insert(new_event_device_id, event_device);
+ self.next_id += 1;
+ Ok(new_event_device_id)
+ }
+
+ /// Release an event device from the display, given an `event_device_id`.
+ pub fn release_event_device(&mut self, event_device_id: u32) {
+ self.event_devices.remove(&event_device_id);
}
- pub fn import_event_device(
+ /// Imports memory to the compositor for use as a surface buffer and returns a handle
+ /// to it.
+ pub fn import_memory(
&mut self,
- event_device: EventDevice,
- ) -> Result<u32, GpuDisplayError> {
- self.inner.import_event_device(event_device)
+ descriptor: &dyn AsRawDescriptor,
+ offset: u32,
+ stride: u32,
+ modifiers: u64,
+ width: u32,
+ height: u32,
+ fourcc: u32,
+ ) -> GpuDisplayResult<u32> {
+ let import_id = self.next_id;
+
+ let gpu_display_memory = self.inner.import_memory(
+ import_id, descriptor, offset, stride, modifiers, width, height, fourcc,
+ )?;
+
+ self.next_id += 1;
+ self.imports.insert(import_id, gpu_display_memory);
+ Ok(import_id)
}
- pub fn release_event_device(&mut self, event_device_id: u32) {
- self.inner.release_event_device(event_device_id)
+ /// Releases a previously imported memory identified by the given handle.
+ pub fn release_import(&mut self, import_id: u32) {
+ self.imports.remove(&import_id);
}
- pub fn attach_event_device(&mut self, surface_id: u32, event_device_id: u32) {
- self.inner.attach_event_device(surface_id, event_device_id);
+ /// Commits any pending state for the identified surface.
+ pub fn commit(&mut self, surface_id: u32) -> GpuDisplayResult<()> {
+ let surface = self
+ .surfaces
+ .get_mut(&surface_id)
+ .ok_or(GpuDisplayError::InvalidSurfaceId)?;
+
+ surface.commit()
+ }
+
+ /// Changes the visible contents of the identified surface to that of the identified imported
+ /// buffer.
+ pub fn flip_to(&mut self, surface_id: u32, import_id: u32) -> GpuDisplayResult<()> {
+ let surface = self
+ .surfaces
+ .get_mut(&surface_id)
+ .ok_or(GpuDisplayError::InvalidSurfaceId)?;
+
+ if !self.imports.contains_key(&import_id) {
+ return Err(GpuDisplayError::InvalidImportId);
+ }
+
+ surface.flip_to(import_id);
+ Ok(())
+ }
+
+ /// Sets the position of the identified subsurface relative to its parent.
+ ///
+ /// The change in position will not be visible until `commit` is called for the parent surface.
+ pub fn set_position(&mut self, surface_id: u32, x: u32, y: u32) -> GpuDisplayResult<()> {
+ let surface = self
+ .surfaces
+ .get_mut(&surface_id)
+ .ok_or(GpuDisplayError::InvalidSurfaceId)?;
+
+ surface.set_position(x, y);
+ Ok(())
+ }
+
+ /// Associates the scanout id with the given surface.
+ pub fn set_scanout_id(&mut self, surface_id: u32, scanout_id: u32) -> GpuDisplayResult<()> {
+ let surface = self
+ .surfaces
+ .get_mut(&surface_id)
+ .ok_or(GpuDisplayError::InvalidSurfaceId)?;
+
+ surface.set_scanout_id(scanout_id);
+ Ok(())
}
}
impl AsRawDescriptor for GpuDisplay {
fn as_raw_descriptor(&self) -> RawDescriptor {
- self.inner.as_raw_descriptor()
+ self.wait_ctx.as_raw_descriptor()
}
}
diff --git a/hypervisor/Android.bp b/hypervisor/Android.bp
index ba7c94593..b7641ddd9 100644
--- a/hypervisor/Android.bp
+++ b/hypervisor/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -10,15 +10,20 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "hypervisor_defaults",
+rust_test {
+ name: "hypervisor_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "hypervisor",
- // has rustc warnings
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: false,
+ },
+ edition: "2021",
rustlibs: [
"libbase_rust",
"libbit_field",
@@ -27,6 +32,7 @@ rust_defaults {
"libkvm",
"libkvm_sys",
"liblibc",
+ "libmemoffset",
"libserde",
"libsync_rust",
"libvm_memory",
@@ -34,27 +40,15 @@ rust_defaults {
proc_macros: ["libenumn"],
}
-rust_test_host {
- name: "hypervisor_host_test_src_lib",
- defaults: ["hypervisor_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "hypervisor_device_test_src_lib",
- defaults: ["hypervisor_defaults"],
-}
-
rust_library {
name: "libhypervisor",
defaults: ["crosvm_defaults"],
- // has rustc warnings
host_supported: true,
crate_name: "hypervisor",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
"libbit_field",
@@ -63,57 +57,10 @@ rust_library {
"libkvm",
"libkvm_sys",
"liblibc",
+ "libmemoffset",
"libserde",
"libsync_rust",
"libvm_memory",
],
proc_macros: ["libenumn"],
}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../bit_field/bit_field_derive/bit_field_derive.rs
-// ../bit_field/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../enumn/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../kvm/src/lib.rs
-// ../kvm_sys/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// ../vm_memory/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// downcast-rs-1.2.0 "default,std"
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/hypervisor/Cargo.toml b/hypervisor/Cargo.toml
index 70c0ec41c..403f33125 100644
--- a/hypervisor/Cargo.toml
+++ b/hypervisor/Cargo.toml
@@ -2,17 +2,18 @@
name = "hypervisor"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
bit_field = { path = "../bit_field" }
-data_model = { path = "../data_model" }
+data_model = { path = "../common/data_model" }
downcast-rs = "1.2.0"
-enumn = { path = "../enumn" }
+enumn = "0.1.0"
kvm = { path = "../kvm" }
kvm_sys = { path = "../kvm_sys" }
libc = "*"
+memoffset = "0.6"
serde = { version = "1", features = [ "derive" ] }
-sync = { path = "../sync" }
+sync = { path = "../common/sync" }
base = { path = "../base" }
vm_memory = { path = "../vm_memory" }
diff --git a/hypervisor/TEST_MAPPING b/hypervisor/TEST_MAPPING
index 2fd193b0b..eda283656 100644
--- a/hypervisor/TEST_MAPPING
+++ b/hypervisor/TEST_MAPPING
@@ -4,10 +4,10 @@
// "presubmit": [
// {
// "host": true,
-// "name": "hypervisor_host_test_src_lib"
+// "name": "hypervisor_test_src_lib"
// },
// {
-// "name": "hypervisor_device_test_src_lib"
+// "name": "hypervisor_test_src_lib"
// }
// ]
}
diff --git a/hypervisor/cargo2android.json b/hypervisor/cargo2android.json
new file mode 100644
index 000000000..9a64b6c75
--- /dev/null
+++ b/hypervisor/cargo2android.json
@@ -0,0 +1,8 @@
+{
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "no_presubmit": true,
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/hypervisor/src/aarch64.rs b/hypervisor/src/aarch64.rs
index bf7bc00d2..316f08c9d 100644
--- a/hypervisor/src/aarch64.rs
+++ b/hypervisor/src/aarch64.rs
@@ -2,26 +2,53 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use base::Result;
+use std::convert::TryFrom;
+
+use base::{Error, Result};
use downcast_rs::impl_downcast;
+use libc::EINVAL;
use vm_memory::GuestAddress;
use crate::{Hypervisor, IrqRoute, IrqSource, IrqSourceChip, Vcpu, Vm};
/// Represents a version of Power State Coordination Interface (PSCI).
+#[derive(Eq, Ord, PartialEq, PartialOrd)]
pub struct PsciVersion {
- pub major: u32,
- pub minor: u32,
+ pub major: u16,
+ pub minor: u16,
+}
+
+impl PsciVersion {
+ pub fn new(major: u16, minor: u16) -> Result<Self> {
+ if (major as i16) < 0 {
+ Err(Error::new(EINVAL))
+ } else {
+ Ok(Self { major, minor })
+ }
+ }
+}
+
+impl TryFrom<u32> for PsciVersion {
+ type Error = base::Error;
+
+ fn try_from(item: u32) -> Result<Self> {
+ Self::new((item >> 16) as u16, item as u16)
+ }
}
+pub const PSCI_0_2: PsciVersion = PsciVersion { major: 0, minor: 2 };
+pub const PSCI_1_0: PsciVersion = PsciVersion { major: 1, minor: 0 };
+
/// A wrapper for using a VM on aarch64 and getting/setting its state.
pub trait VmAArch64: Vm {
/// Gets the `Hypervisor` that created this VM.
fn get_hypervisor(&self) -> &dyn Hypervisor;
- /// Enables protected mode for the VM, creating a memslot for the firmware as needed.
- /// Only works on VMs that support `VmCap::Protected`.
- fn enable_protected_vm(&mut self, fw_addr: GuestAddress, fw_max_size: u64) -> Result<()>;
+ /// Load pVM firmware for the VM, creating a memslot for it as needed.
+ ///
+ /// Only works on protected VMs (i.e. those that support `VmCap::Protected`).
+ fn load_protected_vm_firmware(&mut self, fw_addr: GuestAddress, fw_max_size: u64)
+ -> Result<()>;
/// Create a Vcpu with the specified Vcpu ID.
fn create_vcpu(&self, id: usize) -> Result<Box<dyn VcpuAArch64>>;
@@ -38,6 +65,13 @@ pub trait VcpuAArch64: Vcpu {
/// `irq`.
fn init_pmu(&self, irq: u64) -> Result<()>;
+ /// Checks if ARM ParaVirtualized Time is supported on this VCPU
+ fn has_pvtime_support(&self) -> bool;
+
+ /// Initializes the ARM ParaVirtualized Time on this VCPU, with base address of the stolen time
+ /// structure as `pvtime_ipa`.
+ fn init_pvtime(&self, pvtime_ipa: u64) -> Result<()>;
+
/// Sets the value of a register on this VCPU. `reg_id` is the register ID, as specified in the
/// KVM API documentation for KVM_SET_ONE_REG.
fn set_one_reg(&self, reg_id: u64, data: u64) -> Result<()>;
diff --git a/hypervisor/src/caps.rs b/hypervisor/src/caps.rs
index 43b80756f..1d6a28b8f 100644
--- a/hypervisor/src/caps.rs
+++ b/hypervisor/src/caps.rs
@@ -3,6 +3,7 @@
// found in the LICENSE file.
/// An enumeration of different hypervisor capabilities.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum HypervisorCap {
ArmPmuV3,
ImmediateExit,
@@ -14,7 +15,7 @@ pub enum HypervisorCap {
}
/// A capability the `Vm` can possibly expose.
-#[derive(Clone, Copy, Debug, PartialEq)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum VmCap {
/// Track dirty pages
DirtyLog,
diff --git a/hypervisor/src/kvm/aarch64.rs b/hypervisor/src/kvm/aarch64.rs
index 161b4b51b..d535d033e 100644
--- a/hypervisor/src/kvm/aarch64.rs
+++ b/hypervisor/src/kvm/aarch64.rs
@@ -2,20 +2,89 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use libc::{EINVAL, ENOMEM, ENOSYS, ENXIO};
+use std::convert::TryFrom;
+
+use libc::{EINVAL, ENOMEM, ENOTSUP, ENXIO};
use base::{
- errno_result, error, ioctl_with_mut_ref, ioctl_with_ref, Error, MemoryMappingBuilder, Result,
+ errno_result, error, ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val, warn, Error,
+ MemoryMappingBuilder, Result,
};
use kvm_sys::*;
use vm_memory::GuestAddress;
-use super::{KvmCap, KvmVcpu, KvmVm};
+use super::{Kvm, KvmCap, KvmVcpu, KvmVm};
use crate::{
- ClockState, DeviceKind, Hypervisor, IrqSourceChip, PsciVersion, VcpuAArch64, VcpuFeature, Vm,
- VmAArch64, VmCap,
+ ClockState, DeviceKind, Hypervisor, IrqSourceChip, ProtectionType, PsciVersion, VcpuAArch64,
+ VcpuExit, VcpuFeature, Vm, VmAArch64, VmCap, PSCI_0_2,
};
+/// Gives the ID for a register to be used with `set_one_reg`.
+///
+/// Pass the name of a field in `user_pt_regs` to get the corresponding register
+/// ID, e.g. `arm64_core_reg!(pstate)`
+///
+/// To get ID for registers `x0`-`x31`, refer to the `regs` field along with the
+/// register number, e.g. `arm64_core_reg!(regs, 5)` for `x5`. This is different
+/// to work around `offset_of!(kvm_sys::user_pt_regs, regs[$x])` not working.
+#[macro_export]
+macro_rules! arm64_core_reg {
+ ($reg: tt) => {{
+ let off = (memoffset::offset_of!(::kvm_sys::user_pt_regs, $reg) / 4) as u64;
+ ::kvm_sys::KVM_REG_ARM64
+ | ::kvm_sys::KVM_REG_SIZE_U64
+ | ::kvm_sys::KVM_REG_ARM_CORE as u64
+ | off
+ }};
+ (regs, $x: literal) => {{
+ let off = ((memoffset::offset_of!(::kvm_sys::user_pt_regs, regs)
+ + ($x * ::std::mem::size_of::<u64>()))
+ / 4) as u64;
+ ::kvm_sys::KVM_REG_ARM64
+ | ::kvm_sys::KVM_REG_SIZE_U64
+ | ::kvm_sys::KVM_REG_ARM_CORE as u64
+ | off
+ }};
+}
+
+impl Kvm {
+ // Compute the machine type, which should be the IPA range for the VM
+ // Ideally, this would take a description of the memory map and return
+ // the closest machine type for this VM. Here, we just return the maximum
+ // the kernel support.
+ pub fn get_vm_type(&self, protection_type: ProtectionType) -> Result<u32> {
+ // Safe because we know self is a real kvm fd
+ let ipa_size = match unsafe {
+ ioctl_with_val(self, KVM_CHECK_EXTENSION(), KVM_CAP_ARM_VM_IPA_SIZE.into())
+ } {
+ // Not supported? Use 0 as the machine type, which implies 40bit IPA
+ ret if ret < 0 => 0,
+ ipa => ipa as u32,
+ };
+ let protection_flag = match protection_type {
+ ProtectionType::Unprotected => 0,
+ ProtectionType::Protected | ProtectionType::ProtectedWithoutFirmware => {
+ KVM_VM_TYPE_ARM_PROTECTED
+ }
+ };
+ // Use the lower 8 bits representing the IPA space as the machine type
+ Ok((ipa_size & KVM_VM_TYPE_ARM_IPA_SIZE_MASK) | protection_flag)
+ }
+
+ /// Get the size of guest physical addresses (IPA) in bits.
+ pub fn get_guest_phys_addr_bits(&self) -> u8 {
+ // Safe because we know self is a real kvm fd
+ let vm_ipa_size = match unsafe {
+ ioctl_with_val(self, KVM_CHECK_EXTENSION(), KVM_CAP_ARM_VM_IPA_SIZE.into())
+ } {
+ // Default physical address size is 40 bits if the extension is not supported.
+ ret if ret <= 0 => 40,
+ ipa => ipa as u8,
+ };
+ vm_ipa_size
+ }
+}
+
impl KvmVm {
/// Checks if a particular `VmCap` is available, or returns None if arch-independent
/// Vm.check_capability() should handle the check.
@@ -67,6 +136,22 @@ impl KvmVm {
}?;
Ok(info)
}
+
+ fn set_protected_vm_firmware_ipa(&self, fw_addr: GuestAddress) -> Result<()> {
+ // Safe because none of the args are pointers.
+ unsafe {
+ self.enable_raw_capability(
+ KvmCap::ArmProtectedVm,
+ KVM_CAP_ARM_PROTECTED_VM_FLAGS_SET_FW_IPA,
+ &[fw_addr.0, 0, 0, 0],
+ )
+ }
+ }
+
+ /// Enable userspace msr. This is not available on ARM, just succeed.
+ pub fn enable_userspace_msr(&self) -> Result<()> {
+ Ok(())
+ }
}
#[repr(C)]
@@ -80,13 +165,14 @@ impl VmAArch64 for KvmVm {
&self.kvm
}
- fn enable_protected_vm(&mut self, fw_addr: GuestAddress, fw_max_size: u64) -> Result<()> {
- if !self.check_capability(VmCap::Protected) {
- return Err(Error::new(ENOSYS));
- }
+ fn load_protected_vm_firmware(
+ &mut self,
+ fw_addr: GuestAddress,
+ fw_max_size: u64,
+ ) -> Result<()> {
let info = self.get_protected_vm_info()?;
- let memslot = if info.firmware_size == 0 {
- u64::MAX
+ if info.firmware_size == 0 {
+ Err(Error::new(EINVAL))
} else {
if info.firmware_size > fw_max_size {
return Err(Error::new(ENOMEM));
@@ -94,15 +180,8 @@ impl VmAArch64 for KvmVm {
let mem = MemoryMappingBuilder::new(info.firmware_size as usize)
.build()
.map_err(|_| Error::new(EINVAL))?;
- self.add_memory_region(fw_addr, Box::new(mem), false, false)? as u64
- };
- // Safe because none of the args are pointers.
- unsafe {
- self.enable_raw_capability(
- KvmCap::ArmProtectedVm,
- KVM_CAP_ARM_PROTECTED_VM_FLAGS_ENABLE,
- &[memslot, 0, 0, 0],
- )
+ self.add_memory_region(fw_addr, Box::new(mem), false, false)?;
+ self.set_protected_vm_firmware_ipa(fw_addr)
}
}
@@ -118,6 +197,24 @@ impl KvmVcpu {
pub fn pvclock_ctrl_arch(&self) -> Result<()> {
Err(Error::new(ENXIO))
}
+
+ /// Handles a `KVM_EXIT_SYSTEM_EVENT` with event type `KVM_SYSTEM_EVENT_RESET` with the given
+ /// event flags and returns the appropriate `VcpuExit` value for the run loop to handle.
+ ///
+ /// `event_flags` should be one or more of the `KVM_SYSTEM_EVENT_RESET_FLAG_*` values defined by
+ /// KVM.
+ pub fn system_event_reset(&self, event_flags: u64) -> Result<VcpuExit> {
+ if event_flags & KVM_SYSTEM_EVENT_RESET_FLAG_PSCI_RESET2 != 0 {
+ // Read reset_type and cookie from x1 and x2.
+ let reset_type = self.get_one_reg(arm64_core_reg!(regs, 1))?;
+ let cookie = self.get_one_reg(arm64_core_reg!(regs, 2))?;
+ warn!(
+ "PSCI SYSTEM_RESET2 with reset_type={:#x}, cookie={:#x}",
+ reset_type, cookie
+ );
+ }
+ Ok(VcpuExit::SystemEventReset)
+ }
}
impl VcpuAArch64 for KvmVcpu {
@@ -194,6 +291,47 @@ impl VcpuAArch64 for KvmVcpu {
Ok(())
}
+ fn has_pvtime_support(&self) -> bool {
+ // The in-kernel PV time structure is initialized by setting the base
+ // address with KVM_ARM_VCPU_PVTIME_IPA
+ let pvtime_attr = kvm_device_attr {
+ group: KVM_ARM_VCPU_PVTIME_CTRL,
+ attr: KVM_ARM_VCPU_PVTIME_IPA as u64,
+ addr: 0,
+ flags: 0,
+ };
+ // Safe because we allocated the struct and we know the kernel will read exactly the size of
+ // the struct.
+ let ret = unsafe { ioctl_with_ref(self, kvm_sys::KVM_HAS_DEVICE_ATTR(), &pvtime_attr) };
+ if ret < 0 {
+ return false;
+ }
+
+ return true;
+ }
+
+ fn init_pvtime(&self, pvtime_ipa: u64) -> Result<()> {
+ let pvtime_ipa_addr = &pvtime_ipa as *const u64;
+
+ // The in-kernel PV time structure is initialized by setting the base
+ // address with KVM_ARM_VCPU_PVTIME_IPA
+ let pvtime_attr = kvm_device_attr {
+ group: KVM_ARM_VCPU_PVTIME_CTRL,
+ attr: KVM_ARM_VCPU_PVTIME_IPA as u64,
+ addr: pvtime_ipa_addr as u64,
+ flags: 0,
+ };
+
+ // Safe because we allocated the struct and we know the kernel will read exactly the size of
+ // the struct.
+ let ret = unsafe { ioctl_with_ref(self, kvm_sys::KVM_SET_DEVICE_ATTR(), &pvtime_attr) };
+ if ret < 0 {
+ return errno_result();
+ }
+
+ Ok(())
+ }
+
fn set_one_reg(&self, reg_id: u64, data: u64) -> Result<()> {
let data_ref = &data as *const u64;
let onereg = kvm_one_reg {
@@ -232,17 +370,20 @@ impl VcpuAArch64 for KvmVcpu {
const KVM_REG_ARM_PSCI_VERSION: u64 =
KVM_REG_ARM64 | (KVM_REG_SIZE_U64 as u64) | (KVM_REG_ARM_FW as u64);
- match self.get_one_reg(KVM_REG_ARM_PSCI_VERSION) {
- Ok(v) => {
- let major = (v >> PSCI_VERSION_MAJOR_SHIFT) as u32;
- let minor = (v as u32) & PSCI_VERSION_MINOR_MASK;
- Ok(PsciVersion { major, minor })
- }
- Err(_) => {
- // When `KVM_REG_ARM_PSCI_VERSION` is not supported, we can return PSCI 0.2, as vCPU
- // has been initialized with `KVM_ARM_VCPU_PSCI_0_2` successfully.
- Ok(PsciVersion { major: 0, minor: 2 })
- }
+ let version = if let Ok(v) = self.get_one_reg(KVM_REG_ARM_PSCI_VERSION) {
+ let v = u32::try_from(v).map_err(|_| Error::new(EINVAL))?;
+ PsciVersion::try_from(v)?
+ } else {
+ // When `KVM_REG_ARM_PSCI_VERSION` is not supported, we can return PSCI 0.2, as vCPU
+ // has been initialized with `KVM_ARM_VCPU_PSCI_0_2` successfully.
+ PSCI_0_2
+ };
+
+ if version < PSCI_0_2 {
+ // PSCI v0.1 isn't currently supported for guests
+ Err(Error::new(ENOTSUP))
+ } else {
+ Ok(version)
}
}
}
@@ -273,7 +414,7 @@ mod tests {
fn set_gsi_routing() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
vm.create_irq_chip().unwrap();
vm.set_gsi_routing(&[]).unwrap();
vm.set_gsi_routing(&[IrqRoute {
diff --git a/hypervisor/src/kvm/mod.rs b/hypervisor/src/kvm/mod.rs
index 78708beca..19d3cefe2 100644
--- a/hypervisor/src/kvm/mod.rs
+++ b/hypervisor/src/kvm/mod.rs
@@ -15,11 +15,11 @@ use x86_64::*;
use std::cell::RefCell;
use std::cmp::{min, Reverse};
use std::collections::{BTreeMap, BinaryHeap};
-use std::convert::TryFrom;
+use std::convert::{TryFrom, TryInto};
use std::ffi::CString;
use std::mem::{size_of, ManuallyDrop};
use std::os::raw::{c_int, c_ulong, c_void};
-use std::os::unix::{io::AsRawFd, prelude::OsStrExt};
+use std::os::unix::prelude::OsStrExt;
use std::path::{Path, PathBuf};
use std::ptr::copy_nonoverlapping;
use std::sync::atomic::AtomicU64;
@@ -42,7 +42,7 @@ use vm_memory::{GuestAddress, GuestMemory};
use crate::{
ClockState, Datamatch, DeviceKind, Hypervisor, HypervisorCap, IoEventAddress, IrqRoute,
- IrqSource, MPState, MemSlot, Vcpu, VcpuExit, VcpuRunHandle, Vm, VmCap,
+ IrqSource, MPState, MemSlot, ProtectionType, Vcpu, VcpuExit, VcpuRunHandle, Vm, VmCap,
};
// Wrapper around KVM_SET_USER_MEMORY_REGION ioctl, which creates, modifies, or deletes a mapping
@@ -139,7 +139,7 @@ impl Hypervisor for Kvm {
})
}
- fn check_capability(&self, cap: &HypervisorCap) -> bool {
+ fn check_capability(&self, cap: HypervisorCap) -> bool {
if let Ok(kvm_cap) = KvmCap::try_from(cap) {
// this ioctl is safe because we know this kvm descriptor is valid,
// and we are copying over the kvm capability (u32) as a c_ulong value.
@@ -163,10 +163,20 @@ pub struct KvmVm {
impl KvmVm {
/// Constructs a new `KvmVm` using the given `Kvm` instance.
- pub fn new(kvm: &Kvm, guest_mem: GuestMemory) -> Result<KvmVm> {
+ pub fn new(
+ kvm: &Kvm,
+ guest_mem: GuestMemory,
+ protection_type: ProtectionType,
+ ) -> Result<KvmVm> {
// Safe because we know kvm is a real kvm fd as this module is the only one that can make
// Kvm objects.
- let ret = unsafe { ioctl(kvm, KVM_CREATE_VM()) };
+ let ret = unsafe {
+ ioctl_with_val(
+ kvm,
+ KVM_CREATE_VM(),
+ kvm.get_vm_type(protection_type)? as c_ulong,
+ )
+ };
if ret < 0 {
return errno_result();
}
@@ -443,6 +453,10 @@ impl Vm for KvmVm {
}
}
+ fn get_guest_phys_addr_bits(&self) -> u8 {
+ self.kvm.get_guest_phys_addr_bits()
+ }
+
fn get_memory(&self) -> &GuestMemory {
&self.guest_mem
}
@@ -609,7 +623,7 @@ impl Vm for KvmVm {
slot: u32,
offset: usize,
size: usize,
- fd: &dyn AsRawFd,
+ fd: &dyn AsRawDescriptor,
fd_offset: u64,
prot: Protection,
) -> Result<()> {
@@ -815,10 +829,26 @@ impl Vcpu for KvmVcpu {
return Err(Error::new(EINVAL));
}
let hcall = unsafe { &mut hyperv.u.hcall };
- if data.len() != std::mem::size_of::<u64>() {
- return Err(Error::new(EINVAL));
+ match data.try_into() {
+ Ok(data) => {
+ hcall.result = u64::from_ne_bytes(data);
+ }
+ _ => return Err(Error::new(EINVAL)),
+ }
+ Ok(())
+ }
+ KVM_EXIT_X86_RDMSR => {
+ // Safe because the exit_reason (which comes from the kernel) told us which
+ // union field to use.
+ let msr = unsafe { &mut run.__bindgen_anon_1.msr };
+
+ match data.try_into() {
+ Ok(data) => {
+ msr.data = u64::from_ne_bytes(data);
+ msr.error = 0;
+ }
+ _ => return Err(Error::new(EINVAL)),
}
- hcall.result.to_ne_bytes().copy_from_slice(data);
Ok(())
}
_ => Err(Error::new(EINVAL)),
@@ -830,7 +860,7 @@ impl Vcpu for KvmVcpu {
}
fn set_signal_mask(&self, signals: &[c_int]) -> Result<()> {
- let sigset = signal::create_sigset(&signals)?;
+ let sigset = signal::create_sigset(signals)?;
let mut kvm_sigmask = vec_with_array_field::<kvm_signal_mask, sigset_t>(1);
// Rust definition of sigset_t takes 128 bytes, but the kernel only
@@ -898,7 +928,7 @@ impl Vcpu for KvmVcpu {
// Safe because we know we mapped enough memory to hold the kvm_run struct because the
// kernel told us how large it was.
- let run = unsafe { &*(self.run_mmap.as_ptr() as *const kvm_run) };
+ let run = unsafe { &mut *(self.run_mmap.as_ptr() as *mut kvm_run) };
match run.exit_reason {
KVM_EXIT_IO => {
// Safe because the exit_reason (which comes from the kernel) told us which
@@ -1006,7 +1036,38 @@ impl Vcpu for KvmVcpu {
// field is valid
let event_type = unsafe { run.__bindgen_anon_1.system_event.type_ };
let event_flags = unsafe { run.__bindgen_anon_1.system_event.flags };
- Ok(VcpuExit::SystemEvent(event_type, event_flags))
+ match event_type {
+ KVM_SYSTEM_EVENT_SHUTDOWN => Ok(VcpuExit::SystemEventShutdown),
+ KVM_SYSTEM_EVENT_RESET => self.system_event_reset(event_flags),
+ KVM_SYSTEM_EVENT_CRASH => Ok(VcpuExit::SystemEventCrash),
+ KVM_SYSTEM_EVENT_S2IDLE => Ok(VcpuExit::SystemEventS2Idle),
+ _ => {
+ error!(
+ "Unknown KVM system event {} with flags {}",
+ event_type, event_flags
+ );
+ Err(Error::new(EINVAL))
+ }
+ }
+ }
+ KVM_EXIT_X86_RDMSR => {
+ // Safe because the exit_reason (which comes from the kernel) told us which
+ // union field to use.
+ let msr = unsafe { &mut run.__bindgen_anon_1.msr };
+ let index = msr.index;
+ // By default fail the MSR read unless it was handled later.
+ msr.error = 1;
+ Ok(VcpuExit::RdMsr { index })
+ }
+ KVM_EXIT_X86_WRMSR => {
+ // Safe because the exit_reason (which comes from the kernel) told us which
+ // union field to use.
+ let msr = unsafe { &mut run.__bindgen_anon_1.msr };
+ // By default fail the MSR write.
+ msr.error = 1;
+ let index = msr.index;
+ let data = msr.data;
+ Ok(VcpuExit::WrMsr { index, data })
}
r => panic!("unknown kvm exit reason: {}", r),
}
@@ -1057,10 +1118,10 @@ impl AsRawDescriptor for KvmVcpu {
}
}
-impl<'a> TryFrom<&'a HypervisorCap> for KvmCap {
+impl TryFrom<HypervisorCap> for KvmCap {
type Error = Error;
- fn try_from(cap: &'a HypervisorCap) -> Result<KvmCap> {
+ fn try_from(cap: HypervisorCap) -> Result<KvmCap> {
match cap {
HypervisorCap::ArmPmuV3 => Ok(KvmCap::ArmPmuV3),
HypervisorCap::ImmediateExit => Ok(KvmCap::ImmediateExit),
@@ -1186,22 +1247,22 @@ mod tests {
#[test]
fn check_capability() {
let kvm = Kvm::new().unwrap();
- assert!(kvm.check_capability(&HypervisorCap::UserMemory));
- assert!(!kvm.check_capability(&HypervisorCap::S390UserSigp));
+ assert!(kvm.check_capability(HypervisorCap::UserMemory));
+ assert!(!kvm.check_capability(HypervisorCap::S390UserSigp));
}
#[test]
fn create_vm() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
- KvmVm::new(&kvm, gm).unwrap();
+ KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
}
#[test]
fn clone_vm() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
vm.try_clone().unwrap();
}
@@ -1209,7 +1270,7 @@ mod tests {
fn send_vm() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
thread::spawn(move || {
let _vm = vm;
})
@@ -1221,7 +1282,7 @@ mod tests {
fn check_vm_capability() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
assert!(vm.check_raw_capability(KvmCap::UserMemory));
// I assume nobody is testing this on s390
assert!(!vm.check_raw_capability(KvmCap::S390UserSigp));
@@ -1231,7 +1292,7 @@ mod tests {
fn create_vcpu() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
vm.create_vcpu(0).unwrap();
}
@@ -1239,7 +1300,7 @@ mod tests {
fn get_memory() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let obj_addr = GuestAddress(0xf0);
vm.get_memory().write_obj_at_addr(67u8, obj_addr).unwrap();
let read_val: u8 = vm.get_memory().read_obj_from_addr(obj_addr).unwrap();
@@ -1251,7 +1312,7 @@ mod tests {
let kvm = Kvm::new().unwrap();
let gm =
GuestMemory::new(&[(GuestAddress(0), 0x1000), (GuestAddress(0x5000), 0x5000)]).unwrap();
- let mut vm = KvmVm::new(&kvm, gm).unwrap();
+ let mut vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let mem_size = 0x1000;
let mem = MemoryMappingBuilder::new(mem_size).build().unwrap();
vm.add_memory_region(GuestAddress(0x1000), Box::new(mem), false, false)
@@ -1265,7 +1326,7 @@ mod tests {
fn add_memory_ro() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
- let mut vm = KvmVm::new(&kvm, gm).unwrap();
+ let mut vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let mem_size = 0x1000;
let mem = MemoryMappingBuilder::new(mem_size).build().unwrap();
vm.add_memory_region(GuestAddress(0x1000), Box::new(mem), true, false)
@@ -1276,7 +1337,7 @@ mod tests {
fn remove_memory() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
- let mut vm = KvmVm::new(&kvm, gm).unwrap();
+ let mut vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let mem_size = 0x1000;
let mem = MemoryMappingBuilder::new(mem_size).build().unwrap();
let mem_ptr = mem.as_ptr();
@@ -1292,7 +1353,7 @@ mod tests {
fn remove_invalid_memory() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
- let mut vm = KvmVm::new(&kvm, gm).unwrap();
+ let mut vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
assert!(vm.remove_memory_region(0).is_err());
}
@@ -1300,7 +1361,7 @@ mod tests {
fn overlap_memory() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let mut vm = KvmVm::new(&kvm, gm).unwrap();
+ let mut vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let mem_size = 0x2000;
let mem = MemoryMappingBuilder::new(mem_size).build().unwrap();
assert!(vm
@@ -1313,7 +1374,7 @@ mod tests {
let kvm = Kvm::new().unwrap();
let gm =
GuestMemory::new(&[(GuestAddress(0), 0x1000), (GuestAddress(0x5000), 0x5000)]).unwrap();
- let mut vm = KvmVm::new(&kvm, gm).unwrap();
+ let mut vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let mem_size = 0x1000;
let mem = MemoryMappingArena::new(mem_size).unwrap();
let slot = vm
@@ -1328,7 +1389,7 @@ mod tests {
fn register_irqfd() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let evtfd1 = Event::new().unwrap();
let evtfd2 = Event::new().unwrap();
let evtfd3 = Event::new().unwrap();
@@ -1343,7 +1404,7 @@ mod tests {
fn unregister_irqfd() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let evtfd1 = Event::new().unwrap();
let evtfd2 = Event::new().unwrap();
let evtfd3 = Event::new().unwrap();
@@ -1360,7 +1421,7 @@ mod tests {
fn irqfd_resample() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let evtfd1 = Event::new().unwrap();
let evtfd2 = Event::new().unwrap();
vm.create_irq_chip().unwrap();
@@ -1375,7 +1436,7 @@ mod tests {
fn set_signal_mask() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
vcpu.set_signal_mask(&[base::SIGRTMIN() + 0]).unwrap();
}
@@ -1393,7 +1454,7 @@ mod tests {
fn register_ioevent() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let mut vm = KvmVm::new(&kvm, gm).unwrap();
+ let mut vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let evtfd = Event::new().unwrap();
vm.register_ioevent(&evtfd, IoEventAddress::Pio(0xf4), Datamatch::AnyLength)
.unwrap();
@@ -1429,7 +1490,7 @@ mod tests {
fn unregister_ioevent() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let mut vm = KvmVm::new(&kvm, gm).unwrap();
+ let mut vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let evtfd = Event::new().unwrap();
vm.register_ioevent(&evtfd, IoEventAddress::Pio(0xf4), Datamatch::AnyLength)
.unwrap();
diff --git a/hypervisor/src/kvm/x86_64.rs b/hypervisor/src/kvm/x86_64.rs
index 6cfd394a6..92398847a 100644
--- a/hypervisor/src/kvm/x86_64.rs
+++ b/hypervisor/src/kvm/x86_64.rs
@@ -2,8 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+use std::arch::x86_64::__cpuid;
+
use base::IoctlNr;
-use std::convert::TryInto;
use libc::E2BIG;
@@ -19,7 +20,8 @@ use super::{Kvm, KvmVcpu, KvmVm};
use crate::{
ClockState, CpuId, CpuIdEntry, DebugRegs, DescriptorTable, DeviceKind, Fpu, HypervisorX86_64,
IoapicRedirectionTableEntry, IoapicState, IrqSourceChip, LapicState, PicSelect, PicState,
- PitChannelState, PitState, Register, Regs, Segment, Sregs, VcpuX86_64, VmCap, VmX86_64,
+ PitChannelState, PitState, ProtectionType, Register, Regs, Segment, Sregs, VcpuExit,
+ VcpuX86_64, VmCap, VmX86_64, MAX_IOAPIC_PINS, NUM_IOAPIC_PINS,
};
type KvmCpuId = kvm::CpuId;
@@ -64,6 +66,30 @@ impl Kvm {
const KVM_MAX_ENTRIES: usize = 256;
get_cpuid_with_initial_capacity(self, kind, KVM_MAX_ENTRIES)
}
+
+ // The x86 machine type is always 0. Protected VMs are not supported.
+ pub fn get_vm_type(&self, protection_type: ProtectionType) -> Result<u32> {
+ if protection_type == ProtectionType::Unprotected {
+ Ok(0)
+ } else {
+ error!("Protected mode is not supported on x86_64.");
+ Err(Error::new(libc::EINVAL))
+ }
+ }
+
+ /// Get the size of guest physical addresses in bits.
+ pub fn get_guest_phys_addr_bits(&self) -> u8 {
+ // Get host cpu max physical address bits.
+ // Assume the guest physical address size is the same as the host.
+ let highest_ext_function = unsafe { __cpuid(0x80000000) };
+ if highest_ext_function.eax >= 0x80000008 {
+ let addr_size = unsafe { __cpuid(0x80000008) };
+ // Low 8 bits of 0x80000008 leaf: host physical address size in bits.
+ addr_size.eax as u8
+ } else {
+ 36
+ }
+ }
}
impl HypervisorX86_64 for Kvm {
@@ -191,6 +217,17 @@ impl KvmVm {
}
}
+ /// Retrieves the KVM_IOAPIC_NUM_PINS value for emulated IO-APIC.
+ pub fn get_ioapic_num_pins(&self) -> Result<usize> {
+ // Safe because we know that our file is a KVM fd, and if the cap is invalid KVM assumes
+ // it's an unavailable extension and returns 0, producing default KVM_IOAPIC_NUM_PINS value.
+ match unsafe { ioctl_with_val(self, KVM_CHECK_EXTENSION(), KVM_CAP_IOAPIC_NUM_PINS as u64) }
+ {
+ ret if ret < 0 => errno_result(),
+ ret => Ok((ret as usize).max(NUM_IOAPIC_PINS).min(MAX_IOAPIC_PINS)),
+ }
+ }
+
/// Retrieves the state of IOAPIC by issuing KVM_GET_IRQCHIP ioctl.
///
/// Note that this call can only succeed after a call to `Vm::create_irq_chip`.
@@ -278,6 +315,29 @@ impl KvmVm {
}
}
+ /// Enable userspace msr.
+ pub fn enable_userspace_msr(&self) -> Result<()> {
+ let mut cap = kvm_enable_cap {
+ cap: KVM_CAP_X86_USER_SPACE_MSR,
+ ..Default::default()
+ };
+ cap.args[0] = (KVM_MSR_EXIT_REASON_UNKNOWN
+ | KVM_MSR_EXIT_REASON_INVAL
+ | KVM_MSR_EXIT_REASON_FILTER) as u64;
+ // TODO(b/215297064): Filter only the ones we care about with ioctl
+ // KVM_X86_SET_MSR_FILTER
+
+ // Safe because we know that our file is a VM fd, we know that the
+ // kernel will only read correct amount of memory from our pointer, and
+ // we verify the return result.
+ let ret = unsafe { ioctl_with_ref(self, KVM_ENABLE_CAP(), &cap) };
+ if ret < 0 {
+ errno_result()
+ } else {
+ Ok(())
+ }
+ }
+
/// Enable support for split-irqchip.
pub fn enable_split_irqchip(&self, ioapic_pins: usize) -> Result<()> {
let mut cap = kvm_enable_cap {
@@ -348,6 +408,12 @@ impl KvmVcpu {
errno_result()
}
}
+
+ /// Handles a `KVM_EXIT_SYSTEM_EVENT` with event type `KVM_SYSTEM_EVENT_RESET` with the given
+ /// event flags and returns the appropriate `VcpuExit` value for the run loop to handle.
+ pub fn system_event_reset(&self, _event_flags: u64) -> Result<VcpuExit> {
+ Ok(VcpuExit::SystemEventReset)
+ }
}
impl VcpuX86_64 for KvmVcpu {
@@ -760,7 +826,7 @@ impl From<&kvm_ioapic_state> for IoapicState {
ioregsel: item.ioregsel as u8,
ioapicid: item.id,
current_interrupt_level_bitmap: item.irr,
- redirect_table: [IoapicRedirectionTableEntry::default(); 24],
+ redirect_table: [IoapicRedirectionTableEntry::default(); 120],
};
for (in_state, out_state) in item.redirtbl.iter().zip(state.redirect_table.iter_mut()) {
*out_state = in_state.into();
@@ -812,14 +878,13 @@ impl From<&LapicState> for kvm_lapic_state {
for (reg, value) in item.regs.iter().enumerate() {
// Each lapic register is 16 bytes, but only the first 4 are used
let reg_offset = 16 * reg;
- let sliceu8 = unsafe {
- // This array is only accessed as parts of a u32 word, so interpret it as a u8 array.
- // to_le_bytes() produces an array of u8, not i8(c_char).
- std::mem::transmute::<&mut [i8], &mut [u8]>(
- &mut state.regs[reg_offset..reg_offset + 4],
- )
- };
- sliceu8.copy_from_slice(&value.to_le_bytes());
+ let regs_slice = &mut state.regs[reg_offset..reg_offset + 4];
+
+ // to_le_bytes() produces an array of u8, not i8(c_char), so we can't directly use
+ // copy_from_slice().
+ for (i, v) in value.to_le_bytes().iter().enumerate() {
+ regs_slice[i] = *v as i8;
+ }
}
state
}
@@ -832,12 +897,14 @@ impl From<&kvm_lapic_state> for LapicState {
for reg in 0..64 {
// Each lapic register is 16 bytes, but only the first 4 are used
let reg_offset = 16 * reg;
- let bytes = unsafe {
- // This array is only accessed as parts of a u32 word, so interpret it as a u8 array.
- // from_le_bytes() only works on arrays of u8, not i8(c_char).
- std::mem::transmute::<&[i8], &[u8]>(&item.regs[reg_offset..reg_offset + 4])
- };
- state.regs[reg] = u32::from_le_bytes(bytes.try_into().unwrap());
+
+ // from_le_bytes() only works on arrays of u8, not i8(c_char).
+ let reg_slice = &item.regs[reg_offset..reg_offset + 4];
+ let mut bytes = [0u8; 4];
+ for i in 0..4 {
+ bytes[i] = reg_slice[i] as u8;
+ }
+ state.regs[reg] = u32::from_le_bytes(bytes);
}
state
}
@@ -1232,7 +1299,7 @@ mod tests {
fn check_vm_arch_capability() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
assert!(vm.check_capability(VmCap::PvClock));
}
@@ -1283,6 +1350,8 @@ mod tests {
#[test]
fn ioapic_state() {
let mut entry = IoapicRedirectionTableEntry::default();
+ let noredir = IoapicRedirectionTableEntry::default();
+
// default entry should be 0
assert_eq!(entry.get(0, 64), 0);
@@ -1304,21 +1373,26 @@ mod tests {
assert_eq!(entry.get(0, 64), bit_repr);
- let state = IoapicState {
+ let mut state = IoapicState {
base_address: 1,
ioregsel: 2,
ioapicid: 4,
current_interrupt_level_bitmap: 8,
- redirect_table: [entry; 24],
+ redirect_table: [noredir; 120],
};
+ // Initialize first 24 (kvm_state limit) redirection entries
+ for i in 0..24 {
+ state.redirect_table[i] = entry;
+ }
+
let kvm_state = kvm_ioapic_state::from(&state);
assert_eq!(kvm_state.base_address, 1);
assert_eq!(kvm_state.ioregsel, 2);
assert_eq!(kvm_state.id, 4);
assert_eq!(kvm_state.irr, 8);
assert_eq!(kvm_state.pad, 0);
- // check our entries
+ // check first 24 entries
for i in 0..24 {
assert_eq!(unsafe { kvm_state.redirtbl[i].bits }, bit_repr);
}
@@ -1337,10 +1411,7 @@ mod tests {
// check little endian bytes in kvm_state
for i in 0..4 {
- assert_eq!(
- unsafe { std::mem::transmute::<i8, u8>(kvm_state.regs[32 + i]) } as u8,
- 2u8.pow(i as u32)
- );
+ assert_eq!(kvm_state.regs[32 + i] as u8, 2u8.pow(i as u32));
}
// Test converting back to a LapicState
@@ -1404,7 +1475,7 @@ mod tests {
fn clock_handling() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let mut clock_data = vm.get_pvclock().unwrap();
clock_data.clock += 1000;
vm.set_pvclock(&clock_data).unwrap();
@@ -1414,7 +1485,7 @@ mod tests {
fn set_gsi_routing() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
vm.create_irq_chip().unwrap();
vm.set_gsi_routing(&[]).unwrap();
vm.set_gsi_routing(&[IrqRoute {
@@ -1456,15 +1527,15 @@ mod tests {
fn set_identity_map_addr() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
vm.set_identity_map_addr(GuestAddress(0x20000)).unwrap();
}
#[test]
fn mp_state() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
vm.create_irq_chip().unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
let state = vcpu.get_mp_state().unwrap();
@@ -1475,7 +1546,7 @@ mod tests {
fn enable_feature() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
vm.create_irq_chip().unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
unsafe { vcpu.enable_raw_capability(kvm_sys::KVM_CAP_HYPERV_SYNIC, &[0; 4]) }.unwrap();
@@ -1500,7 +1571,7 @@ mod tests {
fn debugregs() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
let mut dregs = vcpu.get_debugregs().unwrap();
dregs.dr7 = 13;
@@ -1512,12 +1583,12 @@ mod tests {
#[test]
fn xcrs() {
let kvm = Kvm::new().unwrap();
- if !kvm.check_capability(&HypervisorCap::Xcrs) {
+ if !kvm.check_capability(HypervisorCap::Xcrs) {
return;
}
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
let mut xcrs = vcpu.get_xcrs().unwrap();
xcrs[0].value = 1;
@@ -1530,7 +1601,7 @@ mod tests {
fn get_msrs() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
let mut msrs = vec![
// This one should succeed
@@ -1552,7 +1623,7 @@ mod tests {
fn set_msrs() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
const MSR_TSC_AUX: u32 = 0xc0000103;
@@ -1573,7 +1644,7 @@ mod tests {
fn get_hyperv_cpuid() {
let kvm = Kvm::new().unwrap();
let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
- let vm = KvmVm::new(&kvm, gm).unwrap();
+ let vm = KvmVm::new(&kvm, gm, ProtectionType::Unprotected).unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
let cpuid = vcpu.get_hyperv_cpuid();
// Older kernels don't support so tolerate this kind of failure.
diff --git a/hypervisor/src/lib.rs b/hypervisor/src/lib.rs
index ac3642b79..e089d5771 100644
--- a/hypervisor/src/lib.rs
+++ b/hypervisor/src/lib.rs
@@ -11,11 +11,10 @@ pub mod kvm;
pub mod x86_64;
use std::os::raw::c_int;
-use std::os::unix::io::AsRawFd;
use serde::{Deserialize, Serialize};
-use base::{Event, MappedRegion, Protection, Result, SafeDescriptor};
+use base::{AsRawDescriptor, Event, MappedRegion, Protection, Result, SafeDescriptor};
use vm_memory::{GuestAddress, GuestMemory};
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
@@ -35,7 +34,7 @@ pub trait Hypervisor: Send {
Self: Sized;
/// Checks if a particular `HypervisorCap` is available.
- fn check_capability(&self, cap: &HypervisorCap) -> bool;
+ fn check_capability(&self, cap: HypervisorCap) -> bool;
}
/// A wrapper for using a VM and getting/setting its state.
@@ -52,6 +51,9 @@ pub trait Vm: Send {
/// reflects the usable capabilities.
fn check_capability(&self, c: VmCap) -> bool;
+ /// Get the guest physical address size in bits.
+ fn get_guest_phys_addr_bits(&self) -> u8;
+
/// Gets the guest-mapped memory for the Vm.
fn get_memory(&self) -> &GuestMemory;
@@ -150,7 +152,7 @@ pub trait Vm: Send {
slot: u32,
offset: usize,
size: usize,
- fd: &dyn AsRawFd,
+ fd: &dyn AsRawDescriptor,
fd_offset: u64,
prot: Protection,
) -> Result<()>;
@@ -366,11 +368,17 @@ pub enum VcpuExit {
Watchdog,
S390Tsch,
Epr,
- /// The cpu triggered a system level event which is specified by the type field.
- /// The first field is the event type and the second field is flags.
- /// The possible event types are shutdown, reset, or crash. So far there
- /// are not any flags defined.
- SystemEvent(u32 /* event_type */, u64 /* flags */),
+ SystemEventShutdown,
+ SystemEventReset,
+ SystemEventCrash,
+ SystemEventS2Idle,
+ RdMsr {
+ index: u32,
+ },
+ WrMsr {
+ index: u32,
+ data: u64,
+ },
}
/// A device type to create with `Vm.create_device`.
@@ -439,3 +447,16 @@ pub enum MPState {
/// the vcpu is stopped (arm/arm64)
Stopped,
}
+
+/// Whether the VM should be run in protected mode or not.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum ProtectionType {
+ /// The VM should be run in the unprotected mode, where the host has access to its memory.
+ Unprotected,
+ /// The VM should be run in protected mode, so the host cannot access its memory directly. It
+ /// should be booted via the protected VM firmware, so that it can access its secrets.
+ Protected,
+ /// The VM should be run in protected mode, but booted directly without pVM firmware. The host
+ /// will still be unable to access the VM memory, but it won't be given any secrets.
+ ProtectedWithoutFirmware,
+}
diff --git a/hypervisor/src/x86_64.rs b/hypervisor/src/x86_64.rs
index b880689b2..b48ee9d48 100644
--- a/hypervisor/src/x86_64.rs
+++ b/hypervisor/src/x86_64.rs
@@ -229,12 +229,15 @@ pub struct IoapicRedirectionTableEntry {
dest_id: BitField8,
}
-/// Number of pins on the IOAPIC.
+/// Number of pins on the standard KVM/IOAPIC.
pub const NUM_IOAPIC_PINS: usize = 24;
+/// Maximum number of pins on the IOAPIC.
+pub const MAX_IOAPIC_PINS: usize = 120;
+
/// Represents the state of the IOAPIC.
#[repr(C)]
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct IoapicState {
/// base_address is the memory base address for this IOAPIC. It cannot be changed.
pub base_address: u64,
@@ -245,7 +248,13 @@ pub struct IoapicState {
/// current_interrupt_level_bitmap represents a bitmap of the state of all of the irq lines
pub current_interrupt_level_bitmap: u32,
/// redirect_table contains the irq settings for each irq line
- pub redirect_table: [IoapicRedirectionTableEntry; 24],
+ pub redirect_table: [IoapicRedirectionTableEntry; 120],
+}
+
+impl Default for IoapicState {
+ fn default() -> IoapicState {
+ unsafe { std::mem::zeroed() }
+ }
}
#[repr(C)]
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
new file mode 100644
index 000000000..5c8967be9
--- /dev/null
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -0,0 +1,15 @@
+# Auto-generated by lucicfg.
+# Do not modify manually.
+#
+# For the schema of this file, see BuildbucketCfg message:
+# https://luci-config.appspot.com/schemas/projects:buildbucket.cfg
+
+buckets {
+ name: "ci"
+}
+buckets {
+ name: "prod"
+}
+buckets {
+ name: "try"
+}
diff --git a/infra/config/generated/luci-logdog.cfg b/infra/config/generated/luci-logdog.cfg
new file mode 100644
index 000000000..ac44c5dc4
--- /dev/null
+++ b/infra/config/generated/luci-logdog.cfg
@@ -0,0 +1,7 @@
+# Auto-generated by lucicfg.
+# Do not modify manually.
+#
+# For the schema of this file, see ProjectConfig message:
+# https://luci-config.appspot.com/schemas/projects:luci-logdog.cfg
+
+archive_gs_bucket: "logdog-crosvm-archive"
diff --git a/infra/config/generated/project.cfg b/infra/config/generated/project.cfg
new file mode 100644
index 000000000..07255d731
--- /dev/null
+++ b/infra/config/generated/project.cfg
@@ -0,0 +1,13 @@
+# Auto-generated by lucicfg.
+# Do not modify manually.
+#
+# For the schema of this file, see ProjectCfg message:
+# https://luci-config.appspot.com/schemas/projects:project.cfg
+
+name: "crosvm"
+lucicfg {
+ version: "1.30.9"
+ package_dir: ".."
+ config_dir: "generated"
+ entry_point: "main.star"
+}
diff --git a/infra/config/generated/realms.cfg b/infra/config/generated/realms.cfg
new file mode 100644
index 000000000..976feb328
--- /dev/null
+++ b/infra/config/generated/realms.cfg
@@ -0,0 +1,44 @@
+# Auto-generated by lucicfg.
+# Do not modify manually.
+#
+# For the schema of this file, see RealmsCfg message:
+# https://luci-config.appspot.com/schemas/projects:realms.cfg
+
+realms {
+ name: "@root"
+ bindings {
+ role: "role/configs.developer"
+ principals: "group:googlers"
+ }
+ bindings {
+ role: "role/swarming.poolOwner"
+ principals: "group:mdb/crosvm-acl-luci-admin"
+ }
+ bindings {
+ role: "role/swarming.poolUser"
+ principals: "group:mdb/crosvm-acl-luci-admin"
+ }
+ bindings {
+ role: "role/swarming.poolViewer"
+ principals: "group:googlers"
+ }
+ bindings {
+ role: "role/swarming.taskTriggerer"
+ principals: "group:mdb/crosvm-acl-luci-admin"
+ }
+}
+realms {
+ name: "ci"
+}
+realms {
+ name: "pools/ci"
+}
+realms {
+ name: "pools/try"
+}
+realms {
+ name: "prod"
+}
+realms {
+ name: "try"
+}
diff --git a/infra/config/main.star b/infra/config/main.star
new file mode 100755
index 000000000..d7576b53d
--- /dev/null
+++ b/infra/config/main.star
@@ -0,0 +1,66 @@
+#!/usr/bin/env lucicfg
+
+lucicfg.check_version("1.30.9", "Please update depot_tools")
+
+lucicfg.config(
+ config_dir = "generated",
+ tracked_files = ["*.cfg"],
+ fail_on_warnings = True,
+ lint_checks = ["default", "-module-docstring"],
+)
+
+luci.project(
+ name = "crosvm",
+ buildbucket = "cr-buildbucket.appspot.com",
+ logdog = "luci-logdog.appspot.com",
+ milo = "luci-milo.appspot.com",
+ notify = "luci-notify.appspot.com",
+ scheduler = "luci-scheduler.appspot.com",
+ swarming = "chromium-swarm.appspot.com",
+ tricium = "tricium-prod.appspot.com",
+ bindings = [
+ # Allow owners to submit any task in any pool.
+ luci.binding(
+ roles = [
+ "role/swarming.poolOwner",
+ "role/swarming.poolUser",
+ "role/swarming.taskTriggerer",
+ ],
+ groups = "mdb/crosvm-acl-luci-admin",
+ ),
+
+ # Allow any googler to see all bots and tasks there.
+ luci.binding(
+ roles = "role/swarming.poolViewer",
+ groups = "googlers",
+ ),
+
+ # Allow any googler to read/validate/reimport the project configs.
+ luci.binding(
+ roles = "role/configs.developer",
+ groups = "googlers",
+ ),
+ ],
+)
+
+# Per-service tweaks.
+luci.logdog(gs_bucket = "logdog-crosvm-archive")
+
+# Realms with ACLs for corresponding Swarming pools.
+luci.realm(name = "pools/ci")
+luci.realm(name = "pools/try")
+
+# Global recipe defaults
+luci.recipe.defaults.cipd_version.set("refs/heads/main")
+luci.recipe.defaults.use_python3.set(True)
+
+# The try bucket will include builders which work on pre-commit or pre-review
+# code.
+luci.bucket(name = "try")
+
+# The ci bucket will include builders which work on post-commit code.
+luci.bucket(name = "ci")
+
+# The prod bucket will include builders which work on post-commit code and
+# generate executable artifacts used by other users or machines.
+luci.bucket(name = "prod")
diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml
index f455f0fbf..7da71c1e6 100644
--- a/integration_tests/Cargo.toml
+++ b/integration_tests/Cargo.toml
@@ -2,10 +2,10 @@
name = "integration_tests"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dev-dependencies]
-tempfile = { path = "../tempfile" }
+tempfile = "3"
crosvm = { path = ".." }
arch = { path = "../arch" }
base = "*"
diff --git a/integration_tests/README.md b/integration_tests/README.md
index a9d95fad3..a36545415 100644
--- a/integration_tests/README.md
+++ b/integration_tests/README.md
@@ -1,31 +1,26 @@
# Crosvm Integration Tests
-These tests run a crosvm VM on the host to verify end to end behavior. They use
-a prebuilt guest kernel and rootfs, which is downloaded from google cloud
-storage.
+These tests run a crosvm VM on the host to verify end to end behavior. They use a prebuilt guest
+kernel and rootfs, which is downloaded from google cloud storage.
## Running with locally built kernel/rootfs
-If the test needs to run offline, or you want to make changes to the kernel or
-rootfs, you have to specify the environment variables
-`CROSVM_CARGO_TEST_KERNEL_BINARY` and `CROSVM_CARGO_TEST_ROOTFS_IMAGE` to point
-to the right files.
+If the test needs to run offline, or you want to make changes to the kernel or rootfs, you have to
+specify the environment variables `CROSVM_CARGO_TEST_KERNEL_BINARY` and
+`CROSVM_CARGO_TEST_ROOTFS_IMAGE` to point to the right files.
The use_local_build.sh script does this for you:
-```$ source guest_under_test/use_local_build.sh```
+`$ source guest_under_test/use_local_build.sh`
## Uploading prebuilts
-Note: Only Googlers with access to the crosvm-testing cloud storage bin can
- upload prebuilts.
+Note: Only Googlers with access to the crosvm-testing cloud storage bin can upload prebuilts.
-To upload the modified rootfs, you will have to uprev the `PREBUILT_VERSION`
-variable in:
+To upload the modified rootfs, you will have to uprev the `PREBUILT_VERSION` variable in:
- `./guest_under_test/PREBUILT_VERSION`
- `src/third_party/chromiumos-overlay/chromeos-base/crosvm/crosvm-9999.ebuild`
-Then run the upload script to build and upload the new prebuilts. **Never** try
-to modify an existing prebuilt as the new images may break tests in older
-versions.
+Then run the upload script to build and upload the new prebuilts. **Never** try to modify an
+existing prebuilt as the new images may break tests in older versions.
diff --git a/integration_tests/cargo2android.json b/integration_tests/cargo2android.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/integration_tests/cargo2android.json
@@ -0,0 +1 @@
+{}
diff --git a/integration_tests/tests/boot.rs b/integration_tests/tests/boot.rs
index 877610c2c..430599019 100644
--- a/integration_tests/tests/boot.rs
+++ b/integration_tests/tests/boot.rs
@@ -6,7 +6,13 @@ use fixture::TestVm;
#[test]
fn boot_test_vm() {
- let mut vm = TestVm::new(&[], false).unwrap();
+ let mut vm = TestVm::new(&[], false /* debug */, false /* o_direct */).unwrap();
+ assert_eq!(vm.exec_in_guest("echo 42").unwrap().trim(), "42");
+}
+
+#[test]
+fn boot_test_vm_odirect() {
+ let mut vm = TestVm::new(&[], false /* debug */, true /* o_direct */).unwrap();
assert_eq!(vm.exec_in_guest("echo 42").unwrap().trim(), "42");
}
@@ -14,7 +20,7 @@ fn boot_test_vm() {
fn boot_test_suspend_resume() {
// There is no easy way for us to check if the VM is actually suspended. But at
// least exercise the code-path.
- let mut vm = TestVm::new(&[], false).unwrap();
+ let mut vm = TestVm::new(&[], false /* debug */, false /*o_direct */).unwrap();
vm.suspend().unwrap();
vm.resume().unwrap();
assert_eq!(vm.exec_in_guest("echo 42").unwrap().trim(), "42");
diff --git a/integration_tests/tests/fixture.rs b/integration_tests/tests/fixture.rs
index 42a1604f6..c86884c92 100644
--- a/integration_tests/tests/fixture.rs
+++ b/integration_tests/tests/fixture.rs
@@ -2,8 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+use libc::O_DIRECT;
use std::ffi::CString;
+use std::fs::File;
+use std::fs::OpenOptions;
use std::io::{self, BufRead, BufReader, Write};
+use std::os::unix::fs::OpenOptionsExt;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::mpsc::sync_channel;
@@ -11,7 +15,6 @@ use std::sync::Once;
use std::thread;
use std::time::Duration;
use std::{env, process::Child};
-use std::{fs::File, process::Stdio};
use anyhow::{anyhow, Result};
use base::syslog;
@@ -173,7 +176,9 @@ impl TestVm {
/// Downloads prebuilts if needed.
fn initialize_once() {
- syslog::init().unwrap();
+ if let Err(e) = syslog::init() {
+ panic!("failed to initiailize syslog: {}", e);
+ }
// It's possible the prebuilts downloaded by crosvm-9999.ebuild differ
// from the version that crosvm was compiled for.
@@ -205,6 +210,19 @@ impl TestVm {
}
}
assert!(rootfs_path.exists(), "{:?} does not exist", rootfs_path);
+
+ // Check if the test file system is a known compatible one. Needs to support features like O_DIRECT.
+ if let Err(e) = OpenOptions::new()
+ .custom_flags(O_DIRECT)
+ .write(false)
+ .read(true)
+ .open(rootfs_path)
+ {
+ panic!(
+ "File open with O_DIRECT expected to work but did not: {}",
+ e
+ );
+ }
}
// Adds 2 serial devices:
@@ -228,18 +246,23 @@ impl TestVm {
}
/// Configures the VM kernel and rootfs to load from the guest_under_test assets.
- fn configure_kernel(command: &mut Command) {
+ fn configure_kernel(command: &mut Command, o_direct: bool) {
+ let rootfs_and_option = format!(
+ "{}{}",
+ rootfs_path().to_str().unwrap(),
+ if o_direct { ",o_direct=true" } else { "" }
+ );
command
- .args(&["--root", rootfs_path().to_str().unwrap()])
+ .args(&["--root", &rootfs_and_option])
.args(&["--params", "init=/bin/delegate"])
.arg(kernel_path());
}
/// Instanciate a new crosvm instance. The first call will trigger the download of prebuilt
/// files if necessary.
- pub fn new(additional_arguments: &[&str], debug: bool) -> Result<TestVm> {
+ pub fn new(additional_arguments: &[&str], debug: bool, o_direct: bool) -> Result<TestVm> {
static PREP_ONCE: Once = Once::new();
- PREP_ONCE.call_once(|| TestVm::initialize_once());
+ PREP_ONCE.call_once(TestVm::initialize_once);
// Create two named pipes to communicate with the guest.
let test_dir = TempDir::new()?;
@@ -253,16 +276,13 @@ impl TestVm {
let mut command = Command::new(find_crosvm_binary());
command.args(&["run", "--disable-sandbox"]);
TestVm::configure_serial_devices(&mut command, &from_guest_pipe, &to_guest_pipe);
- command.args(&["--socket", &control_socket_path.to_str().unwrap()]);
+ command.args(&["--socket", control_socket_path.to_str().unwrap()]);
command.args(additional_arguments);
- TestVm::configure_kernel(&mut command);
+ TestVm::configure_kernel(&mut command, o_direct);
println!("$ {:?}", command);
- if !debug {
- command.stdout(Stdio::null());
- command.stderr(Stdio::null());
- }
+
let process = command.spawn()?;
// Open pipes. Panic if we cannot connect after a timeout.
diff --git a/io_uring/Android.bp b/io_uring/Android.bp
index da6b76379..3fc4598e9 100644
--- a/io_uring/Android.bp
+++ b/io_uring/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -10,34 +10,29 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "io_uring_defaults",
+rust_test {
+ name: "io_uring_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "io_uring",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.1",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: false,
+ },
+ edition: "2021",
rustlibs: [
+ "libbase_rust",
"libdata_model",
"liblibc",
"libsync_rust",
- "libsys_util",
"libtempfile",
+ "libthiserror",
],
-}
-
-rust_test_host {
- name: "io_uring_host_test_src_lib",
- defaults: ["io_uring_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "io_uring_device_test_src_lib",
- defaults: ["io_uring_defaults"],
+ proc_macros: ["libremain"],
}
rust_library {
@@ -45,30 +40,16 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "io_uring",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.1",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
+ "libbase_rust",
"libdata_model",
"liblibc",
"libsync_rust",
- "libsys_util",
+ "libthiserror",
],
+ proc_macros: ["libremain"],
}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../data_model/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// syn-1.0.70 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// unicode-xid-0.2.1 "default"
diff --git a/io_uring/Cargo.toml b/io_uring/Cargo.toml
index 3b7cc7c36..406117945 100644
--- a/io_uring/Cargo.toml
+++ b/io_uring/Cargo.toml
@@ -1,16 +1,18 @@
[package]
name = "io_uring"
-version = "0.1.0"
+version = "0.1.1"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
-data_model = { path = "../data_model" } # provided by ebuild
-libc = "*"
-sync = { path = "../sync" } # provided by ebuild
-sys_util = { path = "../sys_util" } # provided by ebuild
+data_model = { path = "../common/data_model" } # provided by ebuild
+libc = "0.2.93"
+remain = "0.2"
+sync = { path = "../common/sync" } # provided by ebuild
+base = { path = "../base" } # provided by ebuild
+thiserror = "1"
[dev-dependencies]
-tempfile = { path = "../tempfile" } # provided by ebuild
+tempfile = "3"
+
-[workspace]
diff --git a/io_uring/DEPRECATED.md b/io_uring/DEPRECATED.md
new file mode 100644
index 000000000..b3496d1bc
--- /dev/null
+++ b/io_uring/DEPRECATED.md
@@ -0,0 +1,4 @@
+Use crosvm/io_uring instead.
+
+Code in this directory is not used by crosvm, it is only used in ChromeOS and will move to a
+separate ChromeOS repository soon.
diff --git a/io_uring/bindgen.sh b/io_uring/bindgen.sh
new file mode 100755
index 000000000..363a2f631
--- /dev/null
+++ b/io_uring/bindgen.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Regenerate io_uring bindgen bindings.
+
+set -euo pipefail
+cd "$(dirname "${BASH_SOURCE[0]}")/.."
+
+source tools/impl/bindgen-common.sh
+
+bindgen_generate \
+ --allowlist-type='io_uring_.*' \
+ --allowlist-var='IO_URING_.*' \
+ --allowlist-var='IORING_.*' \
+ "${BINDGEN_LINUX}/include/uapi/linux/io_uring.h" \
+ | replace_linux_int_types | rustfmt \
+ > io_uring/src/bindings.rs
diff --git a/io_uring/cargo2android.json b/io_uring/cargo2android.json
new file mode 100644
index 000000000..9a64b6c75
--- /dev/null
+++ b/io_uring/cargo2android.json
@@ -0,0 +1,8 @@
+{
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "no_presubmit": true,
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/io_uring/src/bindings.rs b/io_uring/src/bindings.rs
index b254c9fc4..c6e52f41f 100644
--- a/io_uring/src/bindings.rs
+++ b/io_uring/src/bindings.rs
@@ -1,7 +1,4 @@
-/* automatically generated by rust-bindgen
- *
- * bindgen --with-derive-default include/uapi/linux/io_uring.h
- */
+/* automatically generated by tools/bindgen-all-the-things */
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
@@ -10,19 +7,19 @@
#[repr(C)]
#[derive(Default)]
-pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>);
+pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
impl<T> __IncompleteArrayField<T> {
#[inline]
- pub fn new() -> Self {
- __IncompleteArrayField(::std::marker::PhantomData)
+ pub const fn new() -> Self {
+ __IncompleteArrayField(::std::marker::PhantomData, [])
}
#[inline]
- pub unsafe fn as_ptr(&self) -> *const T {
- ::std::mem::transmute(self)
+ pub fn as_ptr(&self) -> *const T {
+ self as *const _ as *const T
}
#[inline]
- pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
- ::std::mem::transmute(self)
+ pub fn as_mut_ptr(&mut self) -> *mut T {
+ self as *mut _ as *mut T
}
#[inline]
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
@@ -34,308 +31,37 @@ impl<T> __IncompleteArrayField<T> {
}
}
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
- fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
fmt.write_str("__IncompleteArrayField")
}
}
-impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
- #[inline]
- fn clone(&self) -> Self {
- Self::new()
- }
-}
-impl<T> ::std::marker::Copy for __IncompleteArrayField<T> {}
-pub const NR_OPEN: ::std::os::raw::c_uint = 1024;
-pub const NGROUPS_MAX: ::std::os::raw::c_uint = 65536;
-pub const ARG_MAX: ::std::os::raw::c_uint = 131072;
-pub const LINK_MAX: ::std::os::raw::c_uint = 127;
-pub const MAX_CANON: ::std::os::raw::c_uint = 255;
-pub const MAX_INPUT: ::std::os::raw::c_uint = 255;
-pub const NAME_MAX: ::std::os::raw::c_uint = 255;
-pub const PATH_MAX: ::std::os::raw::c_uint = 4096;
-pub const PIPE_BUF: ::std::os::raw::c_uint = 4096;
-pub const XATTR_NAME_MAX: ::std::os::raw::c_uint = 255;
-pub const XATTR_SIZE_MAX: ::std::os::raw::c_uint = 65536;
-pub const XATTR_LIST_MAX: ::std::os::raw::c_uint = 65536;
-pub const RTSIG_MAX: ::std::os::raw::c_uint = 32;
-pub const _IOC_NRBITS: ::std::os::raw::c_uint = 8;
-pub const _IOC_TYPEBITS: ::std::os::raw::c_uint = 8;
-pub const _IOC_SIZEBITS: ::std::os::raw::c_uint = 14;
-pub const _IOC_DIRBITS: ::std::os::raw::c_uint = 2;
-pub const _IOC_NRMASK: ::std::os::raw::c_uint = 255;
-pub const _IOC_TYPEMASK: ::std::os::raw::c_uint = 255;
-pub const _IOC_SIZEMASK: ::std::os::raw::c_uint = 16383;
-pub const _IOC_DIRMASK: ::std::os::raw::c_uint = 3;
-pub const _IOC_NRSHIFT: ::std::os::raw::c_uint = 0;
-pub const _IOC_TYPESHIFT: ::std::os::raw::c_uint = 8;
-pub const _IOC_SIZESHIFT: ::std::os::raw::c_uint = 16;
-pub const _IOC_DIRSHIFT: ::std::os::raw::c_uint = 30;
-pub const _IOC_NONE: ::std::os::raw::c_uint = 0;
-pub const _IOC_WRITE: ::std::os::raw::c_uint = 1;
-pub const _IOC_READ: ::std::os::raw::c_uint = 2;
-pub const IOC_IN: ::std::os::raw::c_uint = 1073741824;
-pub const IOC_OUT: ::std::os::raw::c_uint = 2147483648;
-pub const IOC_INOUT: ::std::os::raw::c_uint = 3221225472;
-pub const IOCSIZE_MASK: ::std::os::raw::c_uint = 1073676288;
-pub const IOCSIZE_SHIFT: ::std::os::raw::c_uint = 16;
-pub const __BITS_PER_LONG: ::std::os::raw::c_uint = 64;
-pub const __FD_SETSIZE: ::std::os::raw::c_uint = 1024;
-pub const MS_RDONLY: ::std::os::raw::c_uint = 1;
-pub const MS_NOSUID: ::std::os::raw::c_uint = 2;
-pub const MS_NODEV: ::std::os::raw::c_uint = 4;
-pub const MS_NOEXEC: ::std::os::raw::c_uint = 8;
-pub const MS_SYNCHRONOUS: ::std::os::raw::c_uint = 16;
-pub const MS_REMOUNT: ::std::os::raw::c_uint = 32;
-pub const MS_MANDLOCK: ::std::os::raw::c_uint = 64;
-pub const MS_DIRSYNC: ::std::os::raw::c_uint = 128;
-pub const MS_NOATIME: ::std::os::raw::c_uint = 1024;
-pub const MS_NODIRATIME: ::std::os::raw::c_uint = 2048;
-pub const MS_BIND: ::std::os::raw::c_uint = 4096;
-pub const MS_MOVE: ::std::os::raw::c_uint = 8192;
-pub const MS_REC: ::std::os::raw::c_uint = 16384;
-pub const MS_VERBOSE: ::std::os::raw::c_uint = 32768;
-pub const MS_SILENT: ::std::os::raw::c_uint = 32768;
-pub const MS_POSIXACL: ::std::os::raw::c_uint = 65536;
-pub const MS_UNBINDABLE: ::std::os::raw::c_uint = 131072;
-pub const MS_PRIVATE: ::std::os::raw::c_uint = 262144;
-pub const MS_SLAVE: ::std::os::raw::c_uint = 524288;
-pub const MS_SHARED: ::std::os::raw::c_uint = 1048576;
-pub const MS_RELATIME: ::std::os::raw::c_uint = 2097152;
-pub const MS_KERNMOUNT: ::std::os::raw::c_uint = 4194304;
-pub const MS_I_VERSION: ::std::os::raw::c_uint = 8388608;
-pub const MS_STRICTATIME: ::std::os::raw::c_uint = 16777216;
-pub const MS_LAZYTIME: ::std::os::raw::c_uint = 33554432;
-pub const MS_SUBMOUNT: ::std::os::raw::c_uint = 67108864;
-pub const MS_NOREMOTELOCK: ::std::os::raw::c_uint = 134217728;
-pub const MS_NOSEC: ::std::os::raw::c_uint = 268435456;
-pub const MS_BORN: ::std::os::raw::c_uint = 536870912;
-pub const MS_ACTIVE: ::std::os::raw::c_uint = 1073741824;
-pub const MS_NOUSER: ::std::os::raw::c_uint = 2147483648;
-pub const MS_RMT_MASK: ::std::os::raw::c_uint = 41943121;
-pub const MS_MGC_VAL: ::std::os::raw::c_uint = 3236757504;
-pub const MS_MGC_MSK: ::std::os::raw::c_uint = 4294901760;
-pub const OPEN_TREE_CLONE: ::std::os::raw::c_uint = 1;
-pub const MOVE_MOUNT_F_SYMLINKS: ::std::os::raw::c_uint = 1;
-pub const MOVE_MOUNT_F_AUTOMOUNTS: ::std::os::raw::c_uint = 2;
-pub const MOVE_MOUNT_F_EMPTY_PATH: ::std::os::raw::c_uint = 4;
-pub const MOVE_MOUNT_T_SYMLINKS: ::std::os::raw::c_uint = 16;
-pub const MOVE_MOUNT_T_AUTOMOUNTS: ::std::os::raw::c_uint = 32;
-pub const MOVE_MOUNT_T_EMPTY_PATH: ::std::os::raw::c_uint = 64;
-pub const MOVE_MOUNT__MASK: ::std::os::raw::c_uint = 119;
-pub const FSOPEN_CLOEXEC: ::std::os::raw::c_uint = 1;
-pub const FSPICK_CLOEXEC: ::std::os::raw::c_uint = 1;
-pub const FSPICK_SYMLINK_NOFOLLOW: ::std::os::raw::c_uint = 2;
-pub const FSPICK_NO_AUTOMOUNT: ::std::os::raw::c_uint = 4;
-pub const FSPICK_EMPTY_PATH: ::std::os::raw::c_uint = 8;
-pub const FSMOUNT_CLOEXEC: ::std::os::raw::c_uint = 1;
-pub const MOUNT_ATTR_RDONLY: ::std::os::raw::c_uint = 1;
-pub const MOUNT_ATTR_NOSUID: ::std::os::raw::c_uint = 2;
-pub const MOUNT_ATTR_NODEV: ::std::os::raw::c_uint = 4;
-pub const MOUNT_ATTR_NOEXEC: ::std::os::raw::c_uint = 8;
-pub const MOUNT_ATTR__ATIME: ::std::os::raw::c_uint = 112;
-pub const MOUNT_ATTR_RELATIME: ::std::os::raw::c_uint = 0;
-pub const MOUNT_ATTR_NOATIME: ::std::os::raw::c_uint = 16;
-pub const MOUNT_ATTR_STRICTATIME: ::std::os::raw::c_uint = 32;
-pub const MOUNT_ATTR_NODIRATIME: ::std::os::raw::c_uint = 128;
-pub const INR_OPEN_CUR: ::std::os::raw::c_uint = 1024;
-pub const INR_OPEN_MAX: ::std::os::raw::c_uint = 4096;
-pub const BLOCK_SIZE_BITS: ::std::os::raw::c_uint = 10;
-pub const BLOCK_SIZE: ::std::os::raw::c_uint = 1024;
-pub const SEEK_SET: ::std::os::raw::c_uint = 0;
-pub const SEEK_CUR: ::std::os::raw::c_uint = 1;
-pub const SEEK_END: ::std::os::raw::c_uint = 2;
-pub const SEEK_DATA: ::std::os::raw::c_uint = 3;
-pub const SEEK_HOLE: ::std::os::raw::c_uint = 4;
-pub const SEEK_MAX: ::std::os::raw::c_uint = 4;
-pub const RENAME_NOREPLACE: ::std::os::raw::c_uint = 1;
-pub const RENAME_EXCHANGE: ::std::os::raw::c_uint = 2;
-pub const RENAME_WHITEOUT: ::std::os::raw::c_uint = 4;
-pub const FILE_DEDUPE_RANGE_SAME: ::std::os::raw::c_uint = 0;
-pub const FILE_DEDUPE_RANGE_DIFFERS: ::std::os::raw::c_uint = 1;
-pub const NR_FILE: ::std::os::raw::c_uint = 8192;
-pub const FS_XFLAG_REALTIME: ::std::os::raw::c_uint = 1;
-pub const FS_XFLAG_PREALLOC: ::std::os::raw::c_uint = 2;
-pub const FS_XFLAG_IMMUTABLE: ::std::os::raw::c_uint = 8;
-pub const FS_XFLAG_APPEND: ::std::os::raw::c_uint = 16;
-pub const FS_XFLAG_SYNC: ::std::os::raw::c_uint = 32;
-pub const FS_XFLAG_NOATIME: ::std::os::raw::c_uint = 64;
-pub const FS_XFLAG_NODUMP: ::std::os::raw::c_uint = 128;
-pub const FS_XFLAG_RTINHERIT: ::std::os::raw::c_uint = 256;
-pub const FS_XFLAG_PROJINHERIT: ::std::os::raw::c_uint = 512;
-pub const FS_XFLAG_NOSYMLINKS: ::std::os::raw::c_uint = 1024;
-pub const FS_XFLAG_EXTSIZE: ::std::os::raw::c_uint = 2048;
-pub const FS_XFLAG_EXTSZINHERIT: ::std::os::raw::c_uint = 4096;
-pub const FS_XFLAG_NODEFRAG: ::std::os::raw::c_uint = 8192;
-pub const FS_XFLAG_FILESTREAM: ::std::os::raw::c_uint = 16384;
-pub const FS_XFLAG_DAX: ::std::os::raw::c_uint = 32768;
-pub const FS_XFLAG_COWEXTSIZE: ::std::os::raw::c_uint = 65536;
-pub const FS_XFLAG_HASATTR: ::std::os::raw::c_uint = 2147483648;
-pub const BMAP_IOCTL: ::std::os::raw::c_uint = 1;
-pub const FSLABEL_MAX: ::std::os::raw::c_uint = 256;
-pub const FS_KEY_DESCRIPTOR_SIZE: ::std::os::raw::c_uint = 8;
-pub const FS_POLICY_FLAGS_PAD_4: ::std::os::raw::c_uint = 0;
-pub const FS_POLICY_FLAGS_PAD_8: ::std::os::raw::c_uint = 1;
-pub const FS_POLICY_FLAGS_PAD_16: ::std::os::raw::c_uint = 2;
-pub const FS_POLICY_FLAGS_PAD_32: ::std::os::raw::c_uint = 3;
-pub const FS_POLICY_FLAGS_PAD_MASK: ::std::os::raw::c_uint = 3;
-pub const FS_POLICY_FLAG_DIRECT_KEY: ::std::os::raw::c_uint = 4;
-pub const FS_POLICY_FLAGS_VALID: ::std::os::raw::c_uint = 7;
-pub const FS_ENCRYPTION_MODE_INVALID: ::std::os::raw::c_uint = 0;
-pub const FS_ENCRYPTION_MODE_AES_256_XTS: ::std::os::raw::c_uint = 1;
-pub const FS_ENCRYPTION_MODE_AES_256_GCM: ::std::os::raw::c_uint = 2;
-pub const FS_ENCRYPTION_MODE_AES_256_CBC: ::std::os::raw::c_uint = 3;
-pub const FS_ENCRYPTION_MODE_AES_256_CTS: ::std::os::raw::c_uint = 4;
-pub const FS_ENCRYPTION_MODE_AES_128_CBC: ::std::os::raw::c_uint = 5;
-pub const FS_ENCRYPTION_MODE_AES_128_CTS: ::std::os::raw::c_uint = 6;
-pub const FS_ENCRYPTION_MODE_SPECK128_256_XTS: ::std::os::raw::c_uint = 7;
-pub const FS_ENCRYPTION_MODE_SPECK128_256_CTS: ::std::os::raw::c_uint = 8;
-pub const FS_ENCRYPTION_MODE_ADIANTUM: ::std::os::raw::c_uint = 9;
-pub const FS_KEY_DESC_PREFIX: &[u8; 9usize] = b"fscrypt:\0";
-pub const FS_KEY_DESC_PREFIX_SIZE: ::std::os::raw::c_uint = 8;
-pub const FS_MAX_KEY_SIZE: ::std::os::raw::c_uint = 64;
-pub const FS_SECRM_FL: ::std::os::raw::c_uint = 1;
-pub const FS_UNRM_FL: ::std::os::raw::c_uint = 2;
-pub const FS_COMPR_FL: ::std::os::raw::c_uint = 4;
-pub const FS_SYNC_FL: ::std::os::raw::c_uint = 8;
-pub const FS_IMMUTABLE_FL: ::std::os::raw::c_uint = 16;
-pub const FS_APPEND_FL: ::std::os::raw::c_uint = 32;
-pub const FS_NODUMP_FL: ::std::os::raw::c_uint = 64;
-pub const FS_NOATIME_FL: ::std::os::raw::c_uint = 128;
-pub const FS_DIRTY_FL: ::std::os::raw::c_uint = 256;
-pub const FS_COMPRBLK_FL: ::std::os::raw::c_uint = 512;
-pub const FS_NOCOMP_FL: ::std::os::raw::c_uint = 1024;
-pub const FS_ENCRYPT_FL: ::std::os::raw::c_uint = 2048;
-pub const FS_BTREE_FL: ::std::os::raw::c_uint = 4096;
-pub const FS_INDEX_FL: ::std::os::raw::c_uint = 4096;
-pub const FS_IMAGIC_FL: ::std::os::raw::c_uint = 8192;
-pub const FS_JOURNAL_DATA_FL: ::std::os::raw::c_uint = 16384;
-pub const FS_NOTAIL_FL: ::std::os::raw::c_uint = 32768;
-pub const FS_DIRSYNC_FL: ::std::os::raw::c_uint = 65536;
-pub const FS_TOPDIR_FL: ::std::os::raw::c_uint = 131072;
-pub const FS_HUGE_FILE_FL: ::std::os::raw::c_uint = 262144;
-pub const FS_EXTENT_FL: ::std::os::raw::c_uint = 524288;
-pub const FS_EA_INODE_FL: ::std::os::raw::c_uint = 2097152;
-pub const FS_EOFBLOCKS_FL: ::std::os::raw::c_uint = 4194304;
-pub const FS_NOCOW_FL: ::std::os::raw::c_uint = 8388608;
-pub const FS_INLINE_DATA_FL: ::std::os::raw::c_uint = 268435456;
-pub const FS_PROJINHERIT_FL: ::std::os::raw::c_uint = 536870912;
-pub const FS_RESERVED_FL: ::std::os::raw::c_uint = 2147483648;
-pub const FS_FL_USER_VISIBLE: ::std::os::raw::c_uint = 253951;
-pub const FS_FL_USER_MODIFIABLE: ::std::os::raw::c_uint = 229631;
-pub const SYNC_FILE_RANGE_WAIT_BEFORE: ::std::os::raw::c_uint = 1;
-pub const SYNC_FILE_RANGE_WRITE: ::std::os::raw::c_uint = 2;
-pub const SYNC_FILE_RANGE_WAIT_AFTER: ::std::os::raw::c_uint = 4;
-pub const SYNC_FILE_RANGE_WRITE_AND_WAIT: ::std::os::raw::c_uint = 7;
-pub const IORING_SETUP_IOPOLL: ::std::os::raw::c_uint = 1;
-pub const IORING_SETUP_SQPOLL: ::std::os::raw::c_uint = 2;
-pub const IORING_SETUP_SQ_AFF: ::std::os::raw::c_uint = 4;
-pub const IORING_SETUP_CQSIZE: ::std::os::raw::c_uint = 8;
-pub const IORING_SETUP_CLAMP: ::std::os::raw::c_uint = 16;
-pub const IORING_SETUP_ATTACH_WQ: ::std::os::raw::c_uint = 32;
-pub const IORING_FSYNC_DATASYNC: ::std::os::raw::c_uint = 1;
-pub const IORING_TIMEOUT_ABS: ::std::os::raw::c_uint = 1;
-pub const IORING_OFF_SQ_RING: ::std::os::raw::c_uint = 0;
-pub const IORING_OFF_CQ_RING: ::std::os::raw::c_uint = 134217728;
-pub const IORING_OFF_SQES: ::std::os::raw::c_uint = 268435456;
-pub const IORING_SQ_NEED_WAKEUP: ::std::os::raw::c_uint = 1;
-pub const IORING_ENTER_GETEVENTS: ::std::os::raw::c_uint = 1;
-pub const IORING_ENTER_SQ_WAKEUP: ::std::os::raw::c_uint = 2;
-pub const IORING_FEAT_SINGLE_MMAP: ::std::os::raw::c_uint = 1;
-pub const IORING_FEAT_NODROP: ::std::os::raw::c_uint = 2;
-pub const IORING_FEAT_SUBMIT_STABLE: ::std::os::raw::c_uint = 4;
-pub const IORING_FEAT_RW_CUR_POS: ::std::os::raw::c_uint = 8;
-pub const IORING_FEAT_CUR_PERSONALITY: ::std::os::raw::c_uint = 16;
-pub const IORING_REGISTER_BUFFERS: ::std::os::raw::c_uint = 0;
-pub const IORING_UNREGISTER_BUFFERS: ::std::os::raw::c_uint = 1;
-pub const IORING_REGISTER_FILES: ::std::os::raw::c_uint = 2;
-pub const IORING_UNREGISTER_FILES: ::std::os::raw::c_uint = 3;
-pub const IORING_REGISTER_EVENTFD: ::std::os::raw::c_uint = 4;
-pub const IORING_UNREGISTER_EVENTFD: ::std::os::raw::c_uint = 5;
-pub const IORING_REGISTER_FILES_UPDATE: ::std::os::raw::c_uint = 6;
-pub const IORING_REGISTER_EVENTFD_ASYNC: ::std::os::raw::c_uint = 7;
-pub const IORING_REGISTER_PROBE: ::std::os::raw::c_uint = 8;
-pub const IORING_REGISTER_PERSONALITY: ::std::os::raw::c_uint = 9;
-pub const IORING_UNREGISTER_PERSONALITY: ::std::os::raw::c_uint = 10;
-pub const IO_URING_OP_SUPPORTED: ::std::os::raw::c_uint = 1;
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct file_clone_range {
- pub src_fd: i64,
- pub src_offset: u64,
- pub src_length: u64,
- pub dest_offset: u64,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct fstrim_range {
- pub start: u64,
- pub len: u64,
- pub minlen: u64,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct file_dedupe_range_info {
- pub dest_fd: i64,
- pub dest_offset: u64,
- pub bytes_deduped: u64,
- pub status: i32,
- pub reserved: u32,
-}
-#[repr(C)]
-#[derive(Debug, Default)]
-pub struct file_dedupe_range {
- pub src_offset: u64,
- pub src_length: u64,
- pub dest_count: u16,
- pub reserved1: u16,
- pub reserved2: u32,
- pub info: __IncompleteArrayField<file_dedupe_range_info>,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct files_stat_struct {
- pub nr_files: ::std::os::raw::c_ulong,
- pub nr_free_files: ::std::os::raw::c_ulong,
- pub max_files: ::std::os::raw::c_ulong,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct inodes_stat_t {
- pub nr_inodes: ::std::os::raw::c_long,
- pub nr_unused: ::std::os::raw::c_long,
- pub dummy: [::std::os::raw::c_long; 5usize],
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct fsxattr {
- pub fsx_xflags: u32,
- pub fsx_extsize: u32,
- pub fsx_nextents: u32,
- pub fsx_projid: u32,
- pub fsx_cowextsize: u32,
- pub fsx_pad: [::std::os::raw::c_uchar; 8usize],
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct fscrypt_policy {
- pub version: u8,
- pub contents_encryption_mode: u8,
- pub filenames_encryption_mode: u8,
- pub flags: u8,
- pub master_key_descriptor: [u8; 8usize],
-}
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub struct fscrypt_key {
- pub mode: u32,
- pub raw: [u8; 64usize],
- pub size: u32,
-}
-impl Default for fscrypt_key {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
+pub const IORING_SETUP_IOPOLL: u32 = 1;
+pub const IORING_SETUP_SQPOLL: u32 = 2;
+pub const IORING_SETUP_SQ_AFF: u32 = 4;
+pub const IORING_SETUP_CQSIZE: u32 = 8;
+pub const IORING_SETUP_CLAMP: u32 = 16;
+pub const IORING_SETUP_ATTACH_WQ: u32 = 32;
+pub const IORING_SETUP_R_DISABLED: u32 = 64;
+pub const IORING_FSYNC_DATASYNC: u32 = 1;
+pub const IORING_TIMEOUT_ABS: u32 = 1;
+pub const IORING_CQE_F_BUFFER: u32 = 1;
+pub const IORING_OFF_SQ_RING: u32 = 0;
+pub const IORING_OFF_CQ_RING: u32 = 134217728;
+pub const IORING_OFF_SQES: u32 = 268435456;
+pub const IORING_SQ_NEED_WAKEUP: u32 = 1;
+pub const IORING_SQ_CQ_OVERFLOW: u32 = 2;
+pub const IORING_CQ_EVENTFD_DISABLED: u32 = 1;
+pub const IORING_ENTER_GETEVENTS: u32 = 1;
+pub const IORING_ENTER_SQ_WAKEUP: u32 = 2;
+pub const IORING_ENTER_SQ_WAIT: u32 = 4;
+pub const IORING_FEAT_SINGLE_MMAP: u32 = 1;
+pub const IORING_FEAT_NODROP: u32 = 2;
+pub const IORING_FEAT_SUBMIT_STABLE: u32 = 4;
+pub const IORING_FEAT_RW_CUR_POS: u32 = 8;
+pub const IORING_FEAT_CUR_PERSONALITY: u32 = 16;
+pub const IORING_FEAT_FAST_POLL: u32 = 32;
+pub const IORING_FEAT_POLL_32BITS: u32 = 64;
+pub const IO_URING_OP_SUPPORTED: u32 = 1;
pub type __kernel_rwf_t = ::std::os::raw::c_int;
#[repr(C)]
#[derive(Copy, Clone)]
@@ -345,30 +71,49 @@ pub struct io_uring_sqe {
pub ioprio: u16,
pub fd: i32,
pub __bindgen_anon_1: io_uring_sqe__bindgen_ty_1,
- pub addr: u64,
- pub len: u32,
pub __bindgen_anon_2: io_uring_sqe__bindgen_ty_2,
- pub user_data: u64,
+ pub len: u32,
pub __bindgen_anon_3: io_uring_sqe__bindgen_ty_3,
+ pub user_data: u64,
+ pub __bindgen_anon_4: io_uring_sqe__bindgen_ty_4,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union io_uring_sqe__bindgen_ty_1 {
pub off: u64,
pub addr2: u64,
- _bindgen_union_align: u64,
}
impl Default for io_uring_sqe__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union io_uring_sqe__bindgen_ty_2 {
+ pub addr: u64,
+ pub splice_off_in: u64,
+}
+impl Default for io_uring_sqe__bindgen_ty_2 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union io_uring_sqe__bindgen_ty_3 {
pub rw_flags: __kernel_rwf_t,
pub fsync_flags: u32,
pub poll_events: u16,
+ pub poll32_events: u32,
pub sync_range_flags: u32,
pub msg_flags: u32,
pub timeout_flags: u32,
@@ -377,73 +122,107 @@ pub union io_uring_sqe__bindgen_ty_2 {
pub open_flags: u32,
pub statx_flags: u32,
pub fadvise_advice: u32,
- _bindgen_union_align: u32,
+ pub splice_flags: u32,
}
-impl Default for io_uring_sqe__bindgen_ty_2 {
+impl Default for io_uring_sqe__bindgen_ty_3 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
-pub union io_uring_sqe__bindgen_ty_3 {
- pub __bindgen_anon_1: io_uring_sqe__bindgen_ty_3__bindgen_ty_1,
+pub union io_uring_sqe__bindgen_ty_4 {
+ pub __bindgen_anon_1: io_uring_sqe__bindgen_ty_4__bindgen_ty_1,
pub __pad2: [u64; 3usize],
- _bindgen_union_align: [u64; 3usize],
}
#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct io_uring_sqe__bindgen_ty_3__bindgen_ty_1 {
- pub buf_index: u16,
+#[derive(Copy, Clone)]
+pub struct io_uring_sqe__bindgen_ty_4__bindgen_ty_1 {
+ pub __bindgen_anon_1: io_uring_sqe__bindgen_ty_4__bindgen_ty_1__bindgen_ty_1,
pub personality: u16,
+ pub splice_fd_in: i32,
}
-impl Default for io_uring_sqe__bindgen_ty_3 {
+#[repr(C, packed)]
+#[derive(Copy, Clone)]
+pub union io_uring_sqe__bindgen_ty_4__bindgen_ty_1__bindgen_ty_1 {
+ pub buf_index: u16,
+ pub buf_group: u16,
+}
+impl Default for io_uring_sqe__bindgen_ty_4__bindgen_ty_1__bindgen_ty_1 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+impl Default for io_uring_sqe__bindgen_ty_4__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+impl Default for io_uring_sqe__bindgen_ty_4 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
impl Default for io_uring_sqe {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-pub const IOSQE_FIXED_FILE_BIT: _bindgen_ty_1 = 0;
-pub const IOSQE_IO_DRAIN_BIT: _bindgen_ty_1 = 1;
-pub const IOSQE_IO_LINK_BIT: _bindgen_ty_1 = 2;
-pub const IOSQE_IO_HARDLINK_BIT: _bindgen_ty_1 = 3;
-pub const IOSQE_ASYNC_BIT: _bindgen_ty_1 = 4;
-pub type _bindgen_ty_1 = ::std::os::raw::c_uint;
-pub const IORING_OP_NOP: _bindgen_ty_2 = 0;
-pub const IORING_OP_READV: _bindgen_ty_2 = 1;
-pub const IORING_OP_WRITEV: _bindgen_ty_2 = 2;
-pub const IORING_OP_FSYNC: _bindgen_ty_2 = 3;
-pub const IORING_OP_READ_FIXED: _bindgen_ty_2 = 4;
-pub const IORING_OP_WRITE_FIXED: _bindgen_ty_2 = 5;
-pub const IORING_OP_POLL_ADD: _bindgen_ty_2 = 6;
-pub const IORING_OP_POLL_REMOVE: _bindgen_ty_2 = 7;
-pub const IORING_OP_SYNC_FILE_RANGE: _bindgen_ty_2 = 8;
-pub const IORING_OP_SENDMSG: _bindgen_ty_2 = 9;
-pub const IORING_OP_RECVMSG: _bindgen_ty_2 = 10;
-pub const IORING_OP_TIMEOUT: _bindgen_ty_2 = 11;
-pub const IORING_OP_TIMEOUT_REMOVE: _bindgen_ty_2 = 12;
-pub const IORING_OP_ACCEPT: _bindgen_ty_2 = 13;
-pub const IORING_OP_ASYNC_CANCEL: _bindgen_ty_2 = 14;
-pub const IORING_OP_LINK_TIMEOUT: _bindgen_ty_2 = 15;
-pub const IORING_OP_CONNECT: _bindgen_ty_2 = 16;
-pub const IORING_OP_FALLOCATE: _bindgen_ty_2 = 17;
-pub const IORING_OP_OPENAT: _bindgen_ty_2 = 18;
-pub const IORING_OP_CLOSE: _bindgen_ty_2 = 19;
-pub const IORING_OP_FILES_UPDATE: _bindgen_ty_2 = 20;
-pub const IORING_OP_STATX: _bindgen_ty_2 = 21;
-pub const IORING_OP_READ: _bindgen_ty_2 = 22;
-pub const IORING_OP_WRITE: _bindgen_ty_2 = 23;
-pub const IORING_OP_FADVISE: _bindgen_ty_2 = 24;
-pub const IORING_OP_MADVISE: _bindgen_ty_2 = 25;
-pub const IORING_OP_SEND: _bindgen_ty_2 = 26;
-pub const IORING_OP_RECV: _bindgen_ty_2 = 27;
-pub const IORING_OP_OPENAT2: _bindgen_ty_2 = 28;
-pub const IORING_OP_EPOLL_CTL: _bindgen_ty_2 = 29;
-pub const IORING_OP_LAST: _bindgen_ty_2 = 30;
+pub const IORING_OP_NOP: ::std::os::raw::c_uint = 0;
+pub const IORING_OP_READV: ::std::os::raw::c_uint = 1;
+pub const IORING_OP_WRITEV: ::std::os::raw::c_uint = 2;
+pub const IORING_OP_FSYNC: ::std::os::raw::c_uint = 3;
+pub const IORING_OP_READ_FIXED: ::std::os::raw::c_uint = 4;
+pub const IORING_OP_WRITE_FIXED: ::std::os::raw::c_uint = 5;
+pub const IORING_OP_POLL_ADD: ::std::os::raw::c_uint = 6;
+pub const IORING_OP_POLL_REMOVE: ::std::os::raw::c_uint = 7;
+pub const IORING_OP_SYNC_FILE_RANGE: ::std::os::raw::c_uint = 8;
+pub const IORING_OP_SENDMSG: ::std::os::raw::c_uint = 9;
+pub const IORING_OP_RECVMSG: ::std::os::raw::c_uint = 10;
+pub const IORING_OP_TIMEOUT: ::std::os::raw::c_uint = 11;
+pub const IORING_OP_TIMEOUT_REMOVE: ::std::os::raw::c_uint = 12;
+pub const IORING_OP_ACCEPT: ::std::os::raw::c_uint = 13;
+pub const IORING_OP_ASYNC_CANCEL: ::std::os::raw::c_uint = 14;
+pub const IORING_OP_LINK_TIMEOUT: ::std::os::raw::c_uint = 15;
+pub const IORING_OP_CONNECT: ::std::os::raw::c_uint = 16;
+pub const IORING_OP_FALLOCATE: ::std::os::raw::c_uint = 17;
+pub const IORING_OP_OPENAT: ::std::os::raw::c_uint = 18;
+pub const IORING_OP_CLOSE: ::std::os::raw::c_uint = 19;
+pub const IORING_OP_FILES_UPDATE: ::std::os::raw::c_uint = 20;
+pub const IORING_OP_STATX: ::std::os::raw::c_uint = 21;
+pub const IORING_OP_READ: ::std::os::raw::c_uint = 22;
+pub const IORING_OP_WRITE: ::std::os::raw::c_uint = 23;
+pub const IORING_OP_FADVISE: ::std::os::raw::c_uint = 24;
+pub const IORING_OP_MADVISE: ::std::os::raw::c_uint = 25;
+pub const IORING_OP_SEND: ::std::os::raw::c_uint = 26;
+pub const IORING_OP_RECV: ::std::os::raw::c_uint = 27;
+pub const IORING_OP_OPENAT2: ::std::os::raw::c_uint = 28;
+pub const IORING_OP_EPOLL_CTL: ::std::os::raw::c_uint = 29;
+pub const IORING_OP_SPLICE: ::std::os::raw::c_uint = 30;
+pub const IORING_OP_PROVIDE_BUFFERS: ::std::os::raw::c_uint = 31;
+pub const IORING_OP_REMOVE_BUFFERS: ::std::os::raw::c_uint = 32;
+pub const IORING_OP_TEE: ::std::os::raw::c_uint = 33;
+pub const IORING_OP_LAST: ::std::os::raw::c_uint = 34;
pub type _bindgen_ty_2 = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
@@ -452,6 +231,8 @@ pub struct io_uring_cqe {
pub res: i32,
pub flags: u32,
}
+pub const IORING_CQE_BUFFER_SHIFT: ::std::os::raw::c_uint = 16;
+pub type _bindgen_ty_3 = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct io_sqring_offsets {
@@ -474,7 +255,9 @@ pub struct io_cqring_offsets {
pub ring_entries: u32,
pub overflow: u32,
pub cqes: u32,
- pub resv: [u64; 2usize],
+ pub flags: u32,
+ pub resv1: u32,
+ pub resv2: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
@@ -490,6 +273,21 @@ pub struct io_uring_params {
pub sq_off: io_sqring_offsets,
pub cq_off: io_cqring_offsets,
}
+pub const IORING_REGISTER_BUFFERS: ::std::os::raw::c_uint = 0;
+pub const IORING_UNREGISTER_BUFFERS: ::std::os::raw::c_uint = 1;
+pub const IORING_REGISTER_FILES: ::std::os::raw::c_uint = 2;
+pub const IORING_UNREGISTER_FILES: ::std::os::raw::c_uint = 3;
+pub const IORING_REGISTER_EVENTFD: ::std::os::raw::c_uint = 4;
+pub const IORING_UNREGISTER_EVENTFD: ::std::os::raw::c_uint = 5;
+pub const IORING_REGISTER_FILES_UPDATE: ::std::os::raw::c_uint = 6;
+pub const IORING_REGISTER_EVENTFD_ASYNC: ::std::os::raw::c_uint = 7;
+pub const IORING_REGISTER_PROBE: ::std::os::raw::c_uint = 8;
+pub const IORING_REGISTER_PERSONALITY: ::std::os::raw::c_uint = 9;
+pub const IORING_UNREGISTER_PERSONALITY: ::std::os::raw::c_uint = 10;
+pub const IORING_REGISTER_RESTRICTIONS: ::std::os::raw::c_uint = 11;
+pub const IORING_REGISTER_ENABLE_RINGS: ::std::os::raw::c_uint = 12;
+pub const IORING_REGISTER_LAST: ::std::os::raw::c_uint = 13;
+pub type _bindgen_ty_4 = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct io_uring_files_update {
@@ -514,3 +312,42 @@ pub struct io_uring_probe {
pub resv2: [u32; 3usize],
pub ops: __IncompleteArrayField<io_uring_probe_op>,
}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct io_uring_restriction {
+ pub opcode: u16,
+ pub __bindgen_anon_1: io_uring_restriction__bindgen_ty_1,
+ pub resv: u8,
+ pub resv2: [u32; 3usize],
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union io_uring_restriction__bindgen_ty_1 {
+ pub register_op: u8,
+ pub sqe_op: u8,
+ pub sqe_flags: u8,
+}
+impl Default for io_uring_restriction__bindgen_ty_1 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+impl Default for io_uring_restriction {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub const IORING_RESTRICTION_REGISTER_OP: ::std::os::raw::c_uint = 0;
+pub const IORING_RESTRICTION_SQE_OP: ::std::os::raw::c_uint = 1;
+pub const IORING_RESTRICTION_SQE_FLAGS_ALLOWED: ::std::os::raw::c_uint = 2;
+pub const IORING_RESTRICTION_SQE_FLAGS_REQUIRED: ::std::os::raw::c_uint = 3;
+pub const IORING_RESTRICTION_LAST: ::std::os::raw::c_uint = 4;
+pub type _bindgen_ty_5 = ::std::os::raw::c_uint;
diff --git a/io_uring/src/uring.rs b/io_uring/src/uring.rs
index d67447379..f730d71e8 100644
--- a/io_uring/src/uring.rs
+++ b/io_uring/src/uring.rs
@@ -7,15 +7,20 @@
#![allow(clippy::cast_ptr_alignment)]
use std::collections::BTreeMap;
-use std::fmt;
use std::fs::File;
+use std::io;
use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
use std::pin::Pin;
use std::sync::atomic::{AtomicPtr, AtomicU32, AtomicU64, AtomicUsize, Ordering};
+use base::{
+ AsRawDescriptor, MappedRegion, MemoryMapping, MemoryMappingBuilder, Protection, RawDescriptor,
+ WatchingEvents,
+};
use data_model::IoBufMut;
+use remain::sorted;
use sync::Mutex;
-use sys_util::{MappedRegion, MemoryMapping, Protection, WatchingEvents};
+use thiserror::Error as ThisError;
use crate::bindings::*;
use crate::syscalls::*;
@@ -24,37 +29,37 @@ use crate::syscalls::*;
/// for callers to identify each request.
pub type UserData = u64;
-#[derive(Debug)]
+#[sorted]
+#[derive(Debug, ThisError)]
pub enum Error {
- /// The call to `io_uring_enter` failed with the given errno.
- RingEnter(libc::c_int),
- /// The call to `io_uring_setup` failed with the given errno.
- Setup(libc::c_int),
/// Failed to map the completion ring.
- MappingCompleteRing(sys_util::MmapError),
- /// Failed to map the submit ring.
- MappingSubmitRing(sys_util::MmapError),
+ #[error("Failed to mmap completion ring {0}")]
+ MappingCompleteRing(base::MmapError),
/// Failed to map submit entries.
- MappingSubmitEntries(sys_util::MmapError),
+ #[error("Failed to mmap submit entries {0}")]
+ MappingSubmitEntries(base::MmapError),
+ /// Failed to map the submit ring.
+ #[error("Failed to mmap submit ring {0}")]
+ MappingSubmitRing(base::MmapError),
/// Too many ops are already queued.
+ #[error("No space for more ring entries, try increasing the size passed to `new`")]
NoSpace,
+ /// The call to `io_uring_enter` failed with the given errno.
+ #[error("Failed to enter io uring: {0}")]
+ RingEnter(libc::c_int),
+ /// The call to `io_uring_setup` failed with the given errno.
+ #[error("Failed to setup io uring {0}")]
+ Setup(libc::c_int),
}
pub type Result<T> = std::result::Result<T, Error>;
-impl fmt::Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- RingEnter(e) => write!(f, "Failed to enter io uring {}", e),
- Setup(e) => write!(f, "Failed to setup io uring {}", e),
- MappingCompleteRing(e) => write!(f, "Failed to mmap completion ring {}", e),
- MappingSubmitRing(e) => write!(f, "Failed to mmap submit ring {}", e),
- MappingSubmitEntries(e) => write!(f, "Failed to mmap submit entries {}", e),
- NoSpace => write!(
- f,
- "No space for more ring entries, try increasing the size passed to `new`",
- ),
+impl From<Error> for io::Error {
+ fn from(e: Error) -> Self {
+ use Error::*;
+ match e {
+ RingEnter(errno) => io::Error::from_raw_os_error(errno),
+ Setup(errno) => io::Error::from_raw_os_error(errno),
+ e => io::Error::new(io::ErrorKind::Other, e),
}
}
}
@@ -76,6 +81,31 @@ struct SubmitQueue {
num_sqes: usize, // The total number of sqes allocated in shared memory.
}
+// Helper functions to set io_uring_sqe bindgen union members in a less verbose manner.
+impl io_uring_sqe {
+ pub fn set_addr(&mut self, val: u64) {
+ self.__bindgen_anon_2.addr = val;
+ }
+ pub fn set_off(&mut self, val: u64) {
+ self.__bindgen_anon_1.off = val;
+ }
+
+ pub fn set_buf_index(&mut self, val: u16) {
+ self.__bindgen_anon_4
+ .__bindgen_anon_1
+ .__bindgen_anon_1
+ .buf_index = val;
+ }
+
+ pub fn set_rw_flags(&mut self, val: libc::c_int) {
+ self.__bindgen_anon_3.rw_flags = val;
+ }
+
+ pub fn set_poll_events(&mut self, val: u16) {
+ self.__bindgen_anon_3.poll_events = val;
+ }
+}
+
impl SubmitQueue {
// Call `f` with the next available sqe or return an error if none are available.
// After `f` returns, the sqe is appended to the kernel's queue.
@@ -149,10 +179,10 @@ impl SubmitQueue {
iovec.iov_base = ptr as *const libc::c_void as *mut _;
iovec.iov_len = len;
sqe.opcode = op;
- sqe.addr = iovec as *const _ as *const libc::c_void as u64;
+ sqe.set_addr(iovec as *const _ as *const libc::c_void as u64);
sqe.len = 1;
- sqe.__bindgen_anon_1.off = offset;
- sqe.__bindgen_anon_3.__bindgen_anon_1.buf_index = 0;
+ sqe.set_off(offset);
+ sqe.set_buf_index(0);
sqe.ioprio = 0;
sqe.user_data = user_data;
sqe.flags = 0;
@@ -175,7 +205,7 @@ impl SubmitQueue {
/// # use std::fs::File;
/// # use std::os::unix::io::AsRawFd;
/// # use std::path::Path;
-/// # use sys_util::WatchingEvents;
+/// # use base::WatchingEvents;
/// # use io_uring::URingContext;
/// let f = File::open(Path::new("/dev/zero")).unwrap();
/// let uring = URingContext::new(16).unwrap();
@@ -214,40 +244,43 @@ impl URingContext {
// Safe because we trust the kernel to set valid sizes in `io_uring_setup` and any error
// is checked.
let submit_ring = SubmitQueueState::new(
- MemoryMapping::from_fd_offset_protection_populate(
- &ring_file,
+ MemoryMappingBuilder::new(
ring_params.sq_off.array as usize
+ ring_params.sq_entries as usize * std::mem::size_of::<u32>(),
- u64::from(IORING_OFF_SQ_RING),
- Protection::read_write(),
- true,
)
+ .from_file(&ring_file)
+ .offset(u64::from(IORING_OFF_SQ_RING))
+ .protection(Protection::read_write())
+ .populate()
+ .build()
.map_err(Error::MappingSubmitRing)?,
&ring_params,
);
let num_sqe = ring_params.sq_entries as usize;
let submit_queue_entries = SubmitQueueEntries {
- mmap: MemoryMapping::from_fd_offset_protection_populate(
- &ring_file,
+ mmap: MemoryMappingBuilder::new(
ring_params.sq_entries as usize * std::mem::size_of::<io_uring_sqe>(),
- u64::from(IORING_OFF_SQES),
- Protection::read_write(),
- true,
)
+ .from_file(&ring_file)
+ .offset(u64::from(IORING_OFF_SQES))
+ .protection(Protection::read_write())
+ .populate()
+ .build()
.map_err(Error::MappingSubmitEntries)?,
len: num_sqe,
};
let complete_ring = CompleteQueueState::new(
- MemoryMapping::from_fd_offset_protection_populate(
- &ring_file,
+ MemoryMappingBuilder::new(
ring_params.cq_off.cqes as usize
+ ring_params.cq_entries as usize * std::mem::size_of::<io_uring_cqe>(),
- u64::from(IORING_OFF_CQ_RING),
- Protection::read_write(),
- true,
)
+ .from_file(&ring_file)
+ .offset(u64::from(IORING_OFF_CQ_RING))
+ .protection(Protection::read_write())
+ .populate()
+ .build()
.map_err(Error::MappingCompleteRing)?,
&ring_params,
);
@@ -310,6 +343,7 @@ impl URingContext {
.add_rw_op(ptr, len, fd, offset, user_data, IORING_OP_READV as u8)
}
+ /// # Safety
/// See 'writev' but accepts an iterator instead of a vector if there isn't already a vector in
/// existence.
pub unsafe fn add_writev_iter<I>(
@@ -355,10 +389,10 @@ impl URingContext {
) -> Result<()> {
self.submit_ring.lock().prep_next_sqe(|sqe, _iovec| {
sqe.opcode = IORING_OP_WRITEV as u8;
- sqe.addr = iovecs.as_ptr() as *const _ as *const libc::c_void as u64;
+ sqe.set_addr(iovecs.as_ptr() as *const _ as *const libc::c_void as u64);
sqe.len = iovecs.len() as u32;
- sqe.__bindgen_anon_1.off = offset;
- sqe.__bindgen_anon_3.__bindgen_anon_1.buf_index = 0;
+ sqe.set_off(offset);
+ sqe.set_buf_index(0);
sqe.ioprio = 0;
sqe.user_data = user_data;
sqe.flags = 0;
@@ -368,6 +402,7 @@ impl URingContext {
Ok(())
}
+ /// # Safety
/// See 'readv' but accepts an iterator instead of a vector if there isn't already a vector in
/// existence.
pub unsafe fn add_readv_iter<I>(
@@ -413,10 +448,10 @@ impl URingContext {
) -> Result<()> {
self.submit_ring.lock().prep_next_sqe(|sqe, _iovec| {
sqe.opcode = IORING_OP_READV as u8;
- sqe.addr = iovecs.as_ptr() as *const _ as *const libc::c_void as u64;
+ sqe.set_addr(iovecs.as_ptr() as *const _ as *const libc::c_void as u64);
sqe.len = iovecs.len() as u32;
- sqe.__bindgen_anon_1.off = offset;
- sqe.__bindgen_anon_3.__bindgen_anon_1.buf_index = 0;
+ sqe.set_off(offset);
+ sqe.set_buf_index(0);
sqe.ioprio = 0;
sqe.user_data = user_data;
sqe.flags = 0;
@@ -434,11 +469,11 @@ impl URingContext {
sqe.fd = -1;
sqe.user_data = user_data;
- sqe.addr = 0;
+ sqe.set_addr(0);
sqe.len = 0;
- sqe.__bindgen_anon_1.off = 0;
- sqe.__bindgen_anon_3.__bindgen_anon_1.buf_index = 0;
- sqe.__bindgen_anon_2.rw_flags = 0;
+ sqe.set_off(0);
+ sqe.set_buf_index(0);
+ sqe.set_rw_flags(0);
sqe.ioprio = 0;
sqe.flags = 0;
})
@@ -452,11 +487,11 @@ impl URingContext {
sqe.fd = fd;
sqe.user_data = user_data;
- sqe.addr = 0;
+ sqe.set_addr(0);
sqe.len = 0;
- sqe.__bindgen_anon_1.off = 0;
- sqe.__bindgen_anon_3.__bindgen_anon_1.buf_index = 0;
- sqe.__bindgen_anon_2.rw_flags = 0;
+ sqe.set_off(0);
+ sqe.set_buf_index(0);
+ sqe.set_rw_flags(0);
sqe.ioprio = 0;
sqe.flags = 0;
})
@@ -477,13 +512,13 @@ impl URingContext {
sqe.opcode = IORING_OP_FALLOCATE as u8;
sqe.fd = fd;
- sqe.addr = len;
+ sqe.set_addr(len);
sqe.len = mode;
- sqe.__bindgen_anon_1.off = offset;
+ sqe.set_off(offset);
sqe.user_data = user_data;
- sqe.__bindgen_anon_3.__bindgen_anon_1.buf_index = 0;
- sqe.__bindgen_anon_2.rw_flags = 0;
+ sqe.set_buf_index(0);
+ sqe.set_rw_flags(0);
sqe.ioprio = 0;
sqe.flags = 0;
})
@@ -504,12 +539,12 @@ impl URingContext {
sqe.opcode = IORING_OP_POLL_ADD as u8;
sqe.fd = fd;
sqe.user_data = user_data;
- sqe.__bindgen_anon_2.poll_events = events.get_raw() as u16;
+ sqe.set_poll_events(events.get_raw() as u16);
- sqe.addr = 0;
+ sqe.set_addr(0);
sqe.len = 0;
- sqe.__bindgen_anon_1.off = 0;
- sqe.__bindgen_anon_3.__bindgen_anon_1.buf_index = 0;
+ sqe.set_off(0);
+ sqe.set_buf_index(0);
sqe.ioprio = 0;
sqe.flags = 0;
})
@@ -526,12 +561,12 @@ impl URingContext {
sqe.opcode = IORING_OP_POLL_REMOVE as u8;
sqe.fd = fd;
sqe.user_data = user_data;
- sqe.__bindgen_anon_2.poll_events = events.get_raw() as u16;
+ sqe.set_poll_events(events.get_raw() as u16);
- sqe.addr = 0;
+ sqe.set_addr(0);
sqe.len = 0;
- sqe.__bindgen_anon_1.off = 0;
- sqe.__bindgen_anon_3.__bindgen_anon_1.buf_index = 0;
+ sqe.set_off(0);
+ sqe.set_buf_index(0);
sqe.ioprio = 0;
sqe.flags = 0;
})
@@ -627,6 +662,12 @@ impl AsRawFd for URingContext {
}
}
+impl AsRawDescriptor for URingContext {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.ring_file.as_raw_descriptor()
+ }
+}
+
struct SubmitQueueEntries {
mmap: MemoryMapping,
len: usize,
@@ -851,8 +892,8 @@ mod tests {
use std::thread;
use std::time::Duration;
+ use base::{pipe, PollContext};
use sync::{Condvar, Mutex};
- use sys_util::{pipe, PollContext};
use tempfile::{tempfile, TempDir};
use super::*;
@@ -999,8 +1040,8 @@ mod tests {
let uring = URingContext::new(16).unwrap();
let mut buf = [0u8; 4096];
let mut f = create_test_file(0);
- f.write(&buf).unwrap();
- f.write(&buf).unwrap();
+ f.write_all(&buf).unwrap();
+ f.write_all(&buf).unwrap();
unsafe {
// Safe because the `wait` call waits until the kernel is done mutating `buf`.
@@ -1018,8 +1059,8 @@ mod tests {
let uring = URingContext::new(16).unwrap();
let mut buf = [0u8; 4096];
let mut f = create_test_file(0);
- f.write(&buf).unwrap();
- f.write(&buf).unwrap();
+ f.write_all(&buf).unwrap();
+ f.write_all(&buf).unwrap();
let ctx: PollContext<u64> = PollContext::build_with(&[(&uring, 1)]).unwrap();
{
@@ -1075,11 +1116,11 @@ mod tests {
let mut read_back = [0u8; BUF_SIZE];
f.seek(SeekFrom::Start(OFFSET)).unwrap();
- f.read(&mut read_back).unwrap();
+ f.read_exact(&mut read_back).unwrap();
assert!(!read_back.iter().any(|&b| b != 0xaa));
- f.read(&mut read_back).unwrap();
+ f.read_exact(&mut read_back).unwrap();
assert!(!read_back.iter().any(|&b| b != 0xff));
- f.read(&mut read_back).unwrap();
+ f.read_exact(&mut read_back).unwrap();
assert!(!read_back.iter().any(|&b| b != 0x55));
}
@@ -1097,7 +1138,7 @@ mod tests {
.truncate(true)
.open(&file_path)
.unwrap();
- f.write(&buf).unwrap();
+ f.write_all(&buf).unwrap();
}
let init_size = std::fs::metadata(&file_path).unwrap().len() as usize;
diff --git a/kernel_cmdline/Android.bp b/kernel_cmdline/Android.bp
index 6d8b52f77..01e7b4cb2 100644
--- a/kernel_cmdline/Android.bp
+++ b/kernel_cmdline/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -10,30 +10,25 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "kernel_cmdline_defaults",
+rust_test {
+ name: "kernel_cmdline_test_src_kernel_cmdline",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "kernel_cmdline",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/kernel_cmdline.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "liblibc",
- ],
-}
-
-rust_test_host {
- name: "kernel_cmdline_host_test_src_kernel_cmdline",
- defaults: ["kernel_cmdline_defaults"],
test_options: {
unit_test: true,
},
-}
-
-rust_test {
- name: "kernel_cmdline_device_test_src_kernel_cmdline",
- defaults: ["kernel_cmdline_defaults"],
+ edition: "2021",
+ rustlibs: [
+ "liblibc",
+ "libthiserror",
+ ],
+ proc_macros: ["libremain"],
}
rust_library {
@@ -41,12 +36,13 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "kernel_cmdline",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/kernel_cmdline.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"liblibc",
+ "libthiserror",
],
+ proc_macros: ["libremain"],
}
-
-// dependent_library ["feature_list"]
-// libc-0.2.93 "default,std"
diff --git a/kernel_cmdline/Cargo.toml b/kernel_cmdline/Cargo.toml
index 0000610e3..aacbe0528 100644
--- a/kernel_cmdline/Cargo.toml
+++ b/kernel_cmdline/Cargo.toml
@@ -1,10 +1,12 @@
[package]
name = "kernel_cmdline"
version = "0.1.0"
-edition = "2018"
+edition = "2021"
[dependencies]
libc = "*"
+remain = "*"
+thiserror = "*"
[lib]
path = "src/kernel_cmdline.rs"
diff --git a/kernel_cmdline/src/kernel_cmdline.rs b/kernel_cmdline/src/kernel_cmdline.rs
index c836e1262..589fccf4f 100644
--- a/kernel_cmdline/src/kernel_cmdline.rs
+++ b/kernel_cmdline/src/kernel_cmdline.rs
@@ -4,37 +4,29 @@
//! Helper for creating valid kernel command line strings.
-use std::fmt::{self, Display};
use std::result;
+use remain::sorted;
+use thiserror::Error;
+
/// The error type for command line building operations.
-#[derive(PartialEq, Debug)]
+#[sorted]
+#[derive(Error, PartialEq, Debug)]
pub enum Error {
- /// Operation would have resulted in a non-printable ASCII character.
- InvalidAscii,
- /// Key/Value Operation would have had a space in it.
- HasSpace,
/// Key/Value Operation would have had an equals sign in it.
+ #[error("string contains an equals sign")]
HasEquals,
+ /// Key/Value Operation would have had a space in it.
+ #[error("string contains a space")]
+ HasSpace,
+ /// Operation would have resulted in a non-printable ASCII character.
+ #[error("string contains non-printable ASCII character")]
+ InvalidAscii,
/// Operation would have made the command line too large.
+ #[error("inserting string would make command line too long")]
TooLarge,
}
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- let description = match self {
- InvalidAscii => "string contains non-printable ASCII character",
- HasSpace => "string contains a space",
- HasEquals => "string contains an equals sign",
- TooLarge => "inserting string would make command line too long",
- };
-
- write!(f, "{}", description)
- }
-}
-
/// Specialized Result type for command line operations.
pub type Result<T> = result::Result<T, Error>;
@@ -139,9 +131,9 @@ impl Cmdline {
}
}
-impl Into<Vec<u8>> for Cmdline {
- fn into(self) -> Vec<u8> {
- self.line.into_bytes()
+impl From<Cmdline> for Vec<u8> {
+ fn from(c: Cmdline) -> Vec<u8> {
+ c.line.into_bytes()
}
}
diff --git a/kernel_loader/Android.bp b/kernel_loader/Android.bp
index afbdcc0b8..0a81a0509 100644
--- a/kernel_loader/Android.bp
+++ b/kernel_loader/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -10,33 +10,29 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "kernel_loader_defaults",
+rust_test {
+ name: "kernel_loader_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "kernel_loader",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2021",
rustlibs: [
"libbase_rust",
+ "libdata_model",
"liblibc",
"libtempfile",
+ "libthiserror",
"libvm_memory",
],
-}
-
-rust_test_host {
- name: "kernel_loader_host_test_src_lib",
- defaults: ["kernel_loader_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "kernel_loader_device_test_src_lib",
- defaults: ["kernel_loader_defaults"],
+ proc_macros: ["libremain"],
}
rust_library {
@@ -44,62 +40,16 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "kernel_loader",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
+ "libdata_model",
"liblibc",
- "libtempfile",
+ "libthiserror",
"libvm_memory",
],
+ proc_macros: ["libremain"],
}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// ../vm_memory/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// cfg-if-1.0.0
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// getrandom-0.2.2 "std"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.3 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.0 "std"
-// rand_core-0.6.2 "alloc,getrandom,std"
-// remove_dir_all-0.5.3
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/kernel_loader/Cargo.toml b/kernel_loader/Cargo.toml
index 38a32f00e..4a015738b 100644
--- a/kernel_loader/Cargo.toml
+++ b/kernel_loader/Cargo.toml
@@ -1,10 +1,15 @@
[package]
name = "kernel_loader"
version = "0.1.0"
-edition = "2018"
+edition = "2021"
[dependencies]
+data_model = { path = "../common/data_model" }
libc = "*"
base = { path = "../base" }
-tempfile = "*"
+remain = "*"
+thiserror = "*"
vm_memory = { path = "../vm_memory" }
+
+[dev-dependencies]
+tempfile = "3"
diff --git a/kernel_loader/bindgen.sh b/kernel_loader/bindgen.sh
new file mode 100755
index 000000000..a5542f379
--- /dev/null
+++ b/kernel_loader/bindgen.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Regenerate kernel_loader bindgen bindings.
+
+set -euo pipefail
+cd "$(dirname "${BASH_SOURCE[0]}")/.."
+
+source tools/impl/bindgen-common.sh
+
+bindgen_generate \
+ --allowlist-type='Elf64_Ehdr' \
+ --allowlist-type='Elf64_Phdr' \
+ --allowlist-var='.+' \
+ "${BINDGEN_LINUX_X86_HEADERS}/include/linux/elf.h" \
+ -- \
+ -isystem "${BINDGEN_LINUX_X86_HEADERS}/include" \
+ | replace_linux_int_types \
+ > kernel_loader/src/elf.rs
diff --git a/kernel_loader/src/elf.rs b/kernel_loader/src/elf.rs
index c591e52e3..5f75b18dc 100644
--- a/kernel_loader/src/elf.rs
+++ b/kernel_loader/src/elf.rs
@@ -1,15 +1,9 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+/* automatically generated by tools/bindgen-all-the-things */
-/*
- * automatically generated by bindgen
- * From chromeos-linux v4.19 include/uapi/linux/elf.h
- * $ bindgen \
- * --no-layout-tests --with-derive-default \
- * --whitelist-type Elf64_Ehdr --whitelist-type Elf64_Phdr --whitelist-var .+ \
- * include/uapi/linux/elf.h
- */
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
pub const __BITS_PER_LONG: u32 = 64;
pub const __FD_SETSIZE: u32 = 1024;
@@ -40,14 +34,22 @@ pub const EM_CRIS: u32 = 76;
pub const EM_M32R: u32 = 88;
pub const EM_MN10300: u32 = 89;
pub const EM_OPENRISC: u32 = 92;
+pub const EM_ARCOMPACT: u32 = 93;
+pub const EM_XTENSA: u32 = 94;
pub const EM_BLACKFIN: u32 = 106;
+pub const EM_UNICORE: u32 = 110;
pub const EM_ALTERA_NIOS2: u32 = 113;
pub const EM_TI_C6000: u32 = 140;
+pub const EM_HEXAGON: u32 = 164;
+pub const EM_NDS32: u32 = 167;
pub const EM_AARCH64: u32 = 183;
pub const EM_TILEPRO: u32 = 188;
pub const EM_MICROBLAZE: u32 = 189;
pub const EM_TILEGX: u32 = 191;
+pub const EM_ARCV2: u32 = 195;
+pub const EM_RISCV: u32 = 243;
pub const EM_BPF: u32 = 247;
+pub const EM_CSKY: u32 = 252;
pub const EM_FRV: u32 = 21569;
pub const EM_ALPHA: u32 = 36902;
pub const EM_CYGNUS_M32R: u32 = 36929;
@@ -66,6 +68,7 @@ pub const PT_HIOS: u32 = 1879048191;
pub const PT_LOPROC: u32 = 1879048192;
pub const PT_HIPROC: u32 = 2147483647;
pub const PT_GNU_EH_FRAME: u32 = 1685382480;
+pub const PT_GNU_PROPERTY: u32 = 1685382483;
pub const PT_GNU_STACK: u32 = 1685382481;
pub const PN_XNUM: u32 = 65535;
pub const ET_NONE: u32 = 0;
@@ -239,18 +242,24 @@ pub const NT_ARM_HW_BREAK: u32 = 1026;
pub const NT_ARM_HW_WATCH: u32 = 1027;
pub const NT_ARM_SYSTEM_CALL: u32 = 1028;
pub const NT_ARM_SVE: u32 = 1029;
+pub const NT_ARM_PAC_MASK: u32 = 1030;
+pub const NT_ARM_PACA_KEYS: u32 = 1031;
+pub const NT_ARM_PACG_KEYS: u32 = 1032;
+pub const NT_ARM_TAGGED_ADDR_CTRL: u32 = 1033;
+pub const NT_ARM_PAC_ENABLED_KEYS: u32 = 1034;
pub const NT_ARC_V2: u32 = 1536;
pub const NT_VMCOREDD: u32 = 1792;
pub const NT_MIPS_DSP: u32 = 2048;
pub const NT_MIPS_FP_MODE: u32 = 2049;
-pub type __u16 = ::std::os::raw::c_ushort;
-pub type __u32 = ::std::os::raw::c_uint;
-pub type __u64 = ::std::os::raw::c_ulonglong;
-pub type Elf64_Addr = __u64;
-pub type Elf64_Half = __u16;
-pub type Elf64_Off = __u64;
-pub type Elf64_Word = __u32;
-pub type Elf64_Xword = __u64;
+pub const NT_MIPS_MSA: u32 = 2050;
+pub const NT_GNU_PROPERTY_TYPE_0: u32 = 5;
+pub const GNU_PROPERTY_AARCH64_FEATURE_1_AND: u32 = 3221225472;
+pub const GNU_PROPERTY_AARCH64_FEATURE_1_BTI: u32 = 1;
+pub type Elf64_Addr = u64;
+pub type Elf64_Half = u16;
+pub type Elf64_Off = u64;
+pub type Elf64_Word = u32;
+pub type Elf64_Xword = u64;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct elf64_hdr {
diff --git a/kernel_loader/src/lib.rs b/kernel_loader/src/lib.rs
index 7f45b34d7..1d4591482 100644
--- a/kernel_loader/src/lib.rs
+++ b/kernel_loader/src/lib.rs
@@ -3,11 +3,13 @@
// found in the LICENSE file.
use std::ffi::CStr;
-use std::fmt::{self, Display};
use std::io::{Read, Seek, SeekFrom};
use std::mem;
use base::AsRawDescriptor;
+use data_model::DataInit;
+use remain::sorted;
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory};
#[allow(dead_code)]
@@ -17,52 +19,46 @@ use vm_memory::{GuestAddress, GuestMemory};
#[allow(clippy::all)]
mod elf;
-#[derive(Debug, PartialEq)]
+// Elf64_Ehdr is plain old data with no implicit padding.
+unsafe impl data_model::DataInit for elf::Elf64_Ehdr {}
+
+// Elf64_Phdr is plain old data with no implicit padding.
+unsafe impl data_model::DataInit for elf::Elf64_Phdr {}
+
+#[sorted]
+#[derive(Error, Debug, PartialEq)]
pub enum Error {
+ #[error("trying to load big-endian binary on little-endian machine")]
BigEndianElfOnLittle,
+ #[error("failed writing command line to guest memory")]
CommandLineCopy,
+ #[error("command line overflowed guest memory")]
CommandLineOverflow,
+ #[error("invalid Elf magic number")]
InvalidElfMagicNumber,
- InvalidProgramHeaderSize,
- InvalidProgramHeaderOffset,
+ #[error("invalid Program Header Address")]
InvalidProgramHeaderAddress,
+ #[error("invalid Program Header memory size")]
InvalidProgramHeaderMemSize,
+ #[error("invalid program header offset")]
+ InvalidProgramHeaderOffset,
+ #[error("invalid program header size")]
+ InvalidProgramHeaderSize,
+ #[error("unable to read elf header")]
ReadElfHeader,
+ #[error("unable to read kernel image")]
ReadKernelImage,
+ #[error("unable to read program header")]
ReadProgramHeader,
- SeekKernelStart,
+ #[error("unable to seek to elf start")]
SeekElfStart,
+ #[error("unable to seek to kernel start")]
+ SeekKernelStart,
+ #[error("unable to seek to program header")]
SeekProgramHeader,
}
pub type Result<T> = std::result::Result<T, Error>;
-impl std::error::Error for Error {}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- let description = match self {
- BigEndianElfOnLittle => "trying to load big-endian binary on little-endian machine",
- CommandLineCopy => "failed writing command line to guest memory",
- CommandLineOverflow => "command line overflowed guest memory",
- InvalidElfMagicNumber => "invalid Elf magic number",
- InvalidProgramHeaderSize => "invalid program header size",
- InvalidProgramHeaderOffset => "invalid program header offset",
- InvalidProgramHeaderAddress => "invalid Program Header Address",
- InvalidProgramHeaderMemSize => "invalid Program Header memory size",
- ReadElfHeader => "unable to read elf header",
- ReadKernelImage => "unable to read kernel image",
- ReadProgramHeader => "unable to read program header",
- SeekKernelStart => "unable to seek to kernel start",
- SeekElfStart => "unable to seek to elf start",
- SeekProgramHeader => "unable to seek to program header",
- };
-
- write!(f, "kernel loader: {}", description)
- }
-}
-
/// Loads a kernel from a vmlinux elf image to a slice
///
/// # Arguments
@@ -73,19 +69,15 @@ impl Display for Error {
pub fn load_kernel<F>(
guest_mem: &GuestMemory,
kernel_start: GuestAddress,
- kernel_image: &mut F,
+ mut kernel_image: &mut F,
) -> Result<u64>
where
F: Read + Seek + AsRawDescriptor,
{
- let mut ehdr: elf::Elf64_Ehdr = Default::default();
kernel_image
.seek(SeekFrom::Start(0))
.map_err(|_| Error::SeekElfStart)?;
- unsafe {
- // read_struct is safe when reading a POD struct. It can be used and dropped without issue.
- base::read_struct(kernel_image, &mut ehdr).map_err(|_| Error::ReadElfHeader)?;
- }
+ let ehdr = elf::Elf64_Ehdr::from_reader(&mut kernel_image).map_err(|_| Error::ReadElfHeader)?;
// Sanity checks
if ehdr.e_ident[elf::EI_MAG0 as usize] != elf::ELFMAG0 as u8
@@ -109,11 +101,12 @@ where
kernel_image
.seek(SeekFrom::Start(ehdr.e_phoff))
.map_err(|_| Error::SeekProgramHeader)?;
- let phdrs: Vec<elf::Elf64_Phdr> = unsafe {
- // Reading the structs is safe for a slice of POD structs.
- base::read_struct_slice(kernel_image, ehdr.e_phnum as usize)
- .map_err(|_| Error::ReadProgramHeader)?
- };
+ let phdrs = (0..ehdr.e_phnum)
+ .enumerate()
+ .map(|_| {
+ elf::Elf64_Phdr::from_reader(&mut kernel_image).map_err(|_| Error::ReadProgramHeader)
+ })
+ .collect::<Result<Vec<elf::Elf64_Phdr>>>()?;
let mut kernel_end = 0;
@@ -185,7 +178,7 @@ mod test {
const MEM_SIZE: u64 = 0x8000;
fn create_guest_mem() -> GuestMemory {
- GuestMemory::new(&vec![(GuestAddress(0x0), MEM_SIZE)]).unwrap()
+ GuestMemory::new(&[(GuestAddress(0x0), MEM_SIZE)]).unwrap()
}
#[test]
@@ -215,19 +208,19 @@ mod test {
)
);
let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
- assert_eq!(val, '1' as u8);
+ assert_eq!(val, b'1');
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
- assert_eq!(val, '2' as u8);
+ assert_eq!(val, b'2');
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
- assert_eq!(val, '3' as u8);
+ assert_eq!(val, b'3');
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
- assert_eq!(val, '4' as u8);
+ assert_eq!(val, b'4');
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
- assert_eq!(val, '\0' as u8);
+ assert_eq!(val, b'\0');
}
// Elf64 image that prints hello world on x86_64.
@@ -242,7 +235,7 @@ mod test {
fn mutate_elf_bin(mut f: &File, offset: u64, val: u8) {
f.seek(SeekFrom::Start(offset))
.expect("failed to seek file");
- f.write(&[val])
+ f.write_all(&[val])
.expect("failed to write mutated value to file");
}
diff --git a/kvm/Android.bp b/kvm/Android.bp
index 020c6120d..73f0fcacc 100644
--- a/kvm/Android.bp
+++ b/kvm/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -10,14 +10,19 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "kvm_defaults",
+rust_test {
+ name: "kvm_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "kvm",
+ cargo_env_compat: true,
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: false,
+ },
+ edition: "2021",
rustlibs: [
"libbase_rust",
"libdata_model",
@@ -28,26 +33,14 @@ rust_defaults {
],
}
-rust_test_host {
- name: "kvm_host_test_src_lib",
- defaults: ["kvm_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "kvm_device_test_src_lib",
- defaults: ["kvm_defaults"],
-}
-
rust_defaults {
- name: "kvm_defaults_kvm",
+ name: "kvm_test_defaults",
defaults: ["crosvm_defaults"],
crate_name: "kvm",
+ cargo_env_compat: true,
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
"libdata_model",
@@ -59,58 +52,34 @@ rust_defaults {
],
}
-rust_test_host {
- name: "kvm_host_test_tests_dirty_log",
- defaults: ["kvm_defaults_kvm"],
- srcs: ["tests/dirty_log.rs"],
- test_options: {
- unit_test: true,
- },
-}
-
rust_test {
- name: "kvm_device_test_tests_dirty_log",
- defaults: ["kvm_defaults_kvm"],
+ name: "kvm_test_tests_dirty_log",
+ defaults: ["kvm_test_defaults"],
+ host_supported: true,
srcs: ["tests/dirty_log.rs"],
-}
-
-rust_test_host {
- name: "kvm_host_test_tests_read_only_memory",
- defaults: ["kvm_defaults_kvm"],
- srcs: ["tests/read_only_memory.rs"],
test_options: {
- unit_test: true,
+ unit_test: false,
},
}
rust_test {
- name: "kvm_device_test_tests_read_only_memory",
- defaults: ["kvm_defaults_kvm"],
+ name: "kvm_test_tests_read_only_memory",
+ defaults: ["kvm_test_defaults"],
+ host_supported: true,
srcs: ["tests/read_only_memory.rs"],
-}
-
-rust_test_host {
- name: "kvm_host_test_tests_real_run_adder",
- defaults: ["kvm_defaults_kvm"],
- srcs: ["tests/real_run_adder.rs"],
test_options: {
- unit_test: true,
+ unit_test: false,
},
}
-rust_test {
- name: "kvm_device_test_tests_real_run_adder",
- defaults: ["kvm_defaults_kvm"],
- srcs: ["tests/real_run_adder.rs"],
-}
-
rust_library {
name: "libkvm",
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "kvm",
+ cargo_env_compat: true,
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
"libdata_model",
@@ -120,46 +89,3 @@ rust_library {
"libvm_memory",
],
}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../kvm_sys/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// ../vm_memory/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/kvm/Cargo.toml b/kvm/Cargo.toml
index 82cb31b6e..ec0007086 100644
--- a/kvm/Cargo.toml
+++ b/kvm/Cargo.toml
@@ -2,12 +2,12 @@
name = "kvm"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
-data_model = { path = "../data_model" }
+data_model = { path = "../common/data_model" }
kvm_sys = { path = "../kvm_sys" }
libc = "*"
base = { path = "../base" }
-sync = { path = "../sync" }
+sync = { path = "../common/sync" }
vm_memory = { path = "../vm_memory" }
diff --git a/kvm/TEST_MAPPING b/kvm/TEST_MAPPING
index c6757c63f..1d711f9d1 100644
--- a/kvm/TEST_MAPPING
+++ b/kvm/TEST_MAPPING
@@ -4,31 +4,31 @@
// "presubmit": [
// {
// "host": true,
-// "name": "kvm_host_test_src_lib"
+// "name": "kvm_test_src_lib"
// },
// {
-// "name": "kvm_device_test_src_lib"
+// "name": "kvm_test_src_lib"
// },
// {
// "host": true,
-// "name": "kvm_host_test_tests_dirty_log"
+// "name": "kvm_test_tests_dirty_log"
// },
// {
-// "name": "kvm_device_test_tests_dirty_log"
+// "name": "kvm_test_tests_dirty_log"
// },
// {
// "host": true,
-// "name": "kvm_host_test_tests_read_only_memory"
+// "name": "kvm_test_tests_read_only_memory"
// },
// {
-// "name": "kvm_device_test_tests_read_only_memory"
+// "name": "kvm_test_tests_read_only_memory"
// },
// {
// "host": true,
-// "name": "kvm_host_test_tests_real_run_adder"
+// "name": "kvm_test_tests_real_run_adder"
// },
// {
-// "name": "kvm_device_test_tests_real_run_adder"
+// "name": "kvm_test_tests_real_run_adder"
// }
// ]
}
diff --git a/kvm/cargo2android.json b/kvm/cargo2android.json
new file mode 100644
index 000000000..12242e514
--- /dev/null
+++ b/kvm/cargo2android.json
@@ -0,0 +1,9 @@
+{
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "no_presubmit": true,
+ "no-pkg-vers": true,
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/kvm/src/cap.rs b/kvm/src/cap.rs
index 8d51279ce..c724334e7 100644
--- a/kvm/src/cap.rs
+++ b/kvm/src/cap.rs
@@ -120,5 +120,6 @@ pub enum Cap {
S390UserSigp = KVM_CAP_S390_USER_SIGP,
ImmediateExit = KVM_CAP_IMMEDIATE_EXIT,
ArmPmuV3 = KVM_CAP_ARM_PMU_V3,
+ IoapicNumPins = KVM_CAP_IOAPIC_NUM_PINS,
ArmProtectedVm = KVM_CAP_ARM_PROTECTED_VM,
}
diff --git a/kvm/src/lib.rs b/kvm/src/lib.rs
index 8945fb275..a32f141ec 100644
--- a/kvm/src/lib.rs
+++ b/kvm/src/lib.rs
@@ -198,6 +198,28 @@ impl Kvm {
Ok(indices.to_vec())
}
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ // The x86 machine type is always 0
+ pub fn get_vm_type(&self) -> c_ulong {
+ 0
+ }
+
+ #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
+ // Compute the machine type, which should be the IPA range for the VM
+ // Ideally, this would take a description of the memory map and return
+ // the closest machine type for this VM. Here, we just return the maximum
+ // the kernel support.
+ pub fn get_vm_type(&self) -> c_ulong {
+ // Safe because we know self is a real kvm fd
+ match unsafe { ioctl_with_val(self, KVM_CHECK_EXTENSION(), KVM_CAP_ARM_VM_IPA_SIZE.into()) }
+ {
+ // Not supported? Use 0 as the machine type, which implies 40bit IPA
+ ret if ret < 0 => 0,
+ // Use the lower 8 bits representing the IPA space as the machine type
+ ipa => (ipa & 0xff) as c_ulong,
+ }
+ }
}
impl AsRawDescriptor for Kvm {
@@ -274,7 +296,7 @@ impl Vm {
pub fn new(kvm: &Kvm, guest_mem: GuestMemory) -> Result<Vm> {
// Safe because we know kvm is a real kvm fd as this module is the only one that can make
// Kvm objects.
- let ret = unsafe { ioctl(kvm, KVM_CREATE_VM()) };
+ let ret = unsafe { ioctl_with_val(kvm, KVM_CREATE_VM(), kvm.get_vm_type()) };
if ret >= 0 {
// Safe because we verify the value of ret and we are the owners of the fd.
let vm_file = unsafe { File::from_raw_descriptor(ret) };
@@ -1064,10 +1086,12 @@ impl Vcpu {
return Err(Error::new(EINVAL));
}
let hcall = unsafe { &mut hyperv.u.hcall };
- if data.len() != std::mem::size_of::<u64>() {
- return Err(Error::new(EINVAL));
+ match data.try_into() {
+ Ok(data) => {
+ hcall.result = u64::from_ne_bytes(data);
+ }
+ _ => return Err(Error::new(EINVAL)),
}
- hcall.result.to_ne_bytes().copy_from_slice(data);
Ok(())
}
_ => Err(Error::new(EINVAL)),
@@ -1235,7 +1259,7 @@ impl Vcpu {
// Mapping the unsized array to a slice is unsafe because the length isn't known.
// Providing the length used to create the struct guarantees the entire slice is valid.
let entries: &mut [kvm_msr_entry] = msrs[0].entries.as_mut_slice(msr_entries.len());
- entries.copy_from_slice(&msr_entries);
+ entries.copy_from_slice(msr_entries);
}
msrs[0].nmsrs = msr_entries.len() as u32;
let ret = unsafe {
@@ -1251,7 +1275,7 @@ impl Vcpu {
assert!(count <= msr_entries.len());
let entries: &mut [kvm_msr_entry] = msrs[0].entries.as_mut_slice(count);
msr_entries.truncate(count);
- msr_entries.copy_from_slice(&entries);
+ msr_entries.copy_from_slice(entries);
}
Ok(())
}
@@ -1713,7 +1737,7 @@ mod tests {
#[test]
fn create_vm() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
Vm::new(&kvm, gm).unwrap();
}
@@ -1728,7 +1752,7 @@ mod tests {
#[test]
fn check_vm_extension() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
assert!(vm.check_extension(Cap::UserMemory));
// I assume nobody is testing this on s390
@@ -1762,11 +1786,8 @@ mod tests {
#[test]
fn add_memory() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![
- (GuestAddress(0), 0x1000),
- (GuestAddress(0x5000), 0x5000),
- ])
- .unwrap();
+ let gm =
+ GuestMemory::new(&[(GuestAddress(0), 0x1000), (GuestAddress(0x5000), 0x5000)]).unwrap();
let mut vm = Vm::new(&kvm, gm).unwrap();
let mem_size = 0x1000;
let mem = MemoryMappingBuilder::new(mem_size).build().unwrap();
@@ -1780,7 +1801,7 @@ mod tests {
#[test]
fn add_memory_ro() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
let mut vm = Vm::new(&kvm, gm).unwrap();
let mem_size = 0x1000;
let mem = MemoryMappingBuilder::new(mem_size).build().unwrap();
@@ -1791,7 +1812,7 @@ mod tests {
#[test]
fn remove_memory_region() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
let mut vm = Vm::new(&kvm, gm).unwrap();
let mem_size = 0x1000;
let mem = MemoryMappingBuilder::new(mem_size).build().unwrap();
@@ -1807,7 +1828,7 @@ mod tests {
#[test]
fn remove_invalid_memory() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
let mut vm = Vm::new(&kvm, gm).unwrap();
assert!(vm.remove_memory_region(0).is_err());
}
@@ -1815,7 +1836,7 @@ mod tests {
#[test]
fn overlap_memory() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let mut vm = Vm::new(&kvm, gm).unwrap();
let mem_size = 0x2000;
let mem = MemoryMappingBuilder::new(mem_size).build().unwrap();
@@ -1827,7 +1848,7 @@ mod tests {
#[test]
fn get_memory() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x1000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x1000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
let obj_addr = GuestAddress(0xf0);
vm.get_memory().write_obj_at_addr(67u8, obj_addr).unwrap();
@@ -1839,7 +1860,7 @@ mod tests {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn clock_handling() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
let mut clock_data = vm.get_clock().unwrap();
clock_data.clock += 1000;
@@ -1850,7 +1871,7 @@ mod tests {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn pic_handling() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
vm.create_irq_chip().unwrap();
let pic_state = vm.get_pic_state(PicId::Secondary).unwrap();
@@ -1861,7 +1882,7 @@ mod tests {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn ioapic_handling() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
vm.create_irq_chip().unwrap();
let ioapic_state = vm.get_ioapic_state().unwrap();
@@ -1872,7 +1893,7 @@ mod tests {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn pit_handling() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
vm.create_irq_chip().unwrap();
vm.create_pit().unwrap();
@@ -1883,7 +1904,7 @@ mod tests {
#[test]
fn register_ioevent() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
let evtfd = Event::new().unwrap();
vm.register_ioevent(&evtfd, IoeventAddress::Pio(0xf4), Datamatch::AnyLength)
@@ -1919,7 +1940,7 @@ mod tests {
#[test]
fn unregister_ioevent() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
let evtfd = Event::new().unwrap();
vm.register_ioevent(&evtfd, IoeventAddress::Pio(0xf4), Datamatch::AnyLength)
@@ -1947,7 +1968,7 @@ mod tests {
#[test]
fn irqfd_resample() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
let evtfd1 = Event::new().unwrap();
let evtfd2 = Event::new().unwrap();
@@ -1963,7 +1984,7 @@ mod tests {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn set_gsi_routing() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
vm.create_irq_chip().unwrap();
vm.set_gsi_routing(&[]).unwrap();
@@ -2005,7 +2026,7 @@ mod tests {
#[test]
fn create_vcpu() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
Vcpu::new(0, &kvm, &vm).unwrap();
}
@@ -2014,7 +2035,7 @@ mod tests {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn debugregs() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
let vcpu = Vcpu::new(0, &kvm, &vm).unwrap();
let mut dregs = vcpu.get_debugregs().unwrap();
@@ -2032,7 +2053,7 @@ mod tests {
return;
}
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
let vcpu = Vcpu::new(0, &kvm, &vm).unwrap();
let mut xcrs = vcpu.get_xcrs().unwrap();
@@ -2046,7 +2067,7 @@ mod tests {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn get_msrs() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
let vcpu = Vcpu::new(0, &kvm, &vm).unwrap();
let mut msrs = vec![
@@ -2069,7 +2090,7 @@ mod tests {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn get_hyperv_cpuid() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
let vcpu = Vcpu::new(0, &kvm, &vm).unwrap();
let cpuid = vcpu.get_hyperv_cpuid();
@@ -2086,12 +2107,14 @@ mod tests {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn enable_feature() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
vm.create_irq_chip().unwrap();
let vcpu = Vcpu::new(0, &kvm, &vm).unwrap();
- let mut cap: kvm_enable_cap = Default::default();
- cap.cap = kvm_sys::KVM_CAP_HYPERV_SYNIC;
+ let cap: kvm_enable_cap = kvm_sys::kvm_enable_cap {
+ cap: kvm_sys::KVM_CAP_HYPERV_SYNIC,
+ ..Default::default()
+ };
unsafe { vcpu.kvm_enable_cap(&cap) }.unwrap();
}
@@ -2099,7 +2122,7 @@ mod tests {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn mp_state() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
vm.create_irq_chip().unwrap();
let vcpu = Vcpu::new(0, &kvm, &vm).unwrap();
@@ -2110,7 +2133,7 @@ mod tests {
#[test]
fn set_signal_mask() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
let vcpu = Vcpu::new(0, &kvm, &vm).unwrap();
vcpu.set_signal_mask(&[base::SIGRTMIN() + 0]).unwrap();
@@ -2129,7 +2152,7 @@ mod tests {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn set_identity_map_addr() {
let kvm = Kvm::new().unwrap();
- let gm = GuestMemory::new(&vec![(GuestAddress(0), 0x10000)]).unwrap();
+ let gm = GuestMemory::new(&[(GuestAddress(0), 0x10000)]).unwrap();
let vm = Vm::new(&kvm, gm).unwrap();
vm.set_identity_map_addr(GuestAddress(0x20000)).unwrap();
}
diff --git a/kvm/tests/real_run_adder.rs b/kvm/tests/real_run_adder.rs
index efd39d437..a87e2586d 100644
--- a/kvm/tests/real_run_adder.rs
+++ b/kvm/tests/real_run_adder.rs
@@ -14,17 +14,17 @@ fn test_run() {
let code = [
0xba, 0xf8, 0x03, /* mov $0x3f8, %dx */
0x00, 0xd8, /* add %bl, %al */
- 0x04, '0' as u8, /* add $'0', %al */
- 0xee, /* out %al, (%dx) */
- 0xb0, '\n' as u8, /* mov $'\n', %al */
- 0xee, /* out %al, (%dx) */
+ 0x04, b'0', /* add $'0', %al */
+ 0xee, /* out %al, (%dx) */
+ 0xb0, b'\n', /* mov $'\n', %al */
+ 0xee, /* out %al, (%dx) */
0x2e, 0xc6, 0x06, 0xf1, 0x10, 0x13, /* movb $0x13, %cs:0xf1 */
0xf4, /* hlt */
];
let mem_size = 0x1000;
let load_addr = GuestAddress(0x1000);
- let mem = GuestMemory::new(&vec![(load_addr, mem_size)]).unwrap();
+ let mem = GuestMemory::new(&[(load_addr, mem_size)]).unwrap();
let kvm = Kvm::new().expect("new kvm failed");
let vm = Vm::new(&kvm, mem).expect("new vm failed");
diff --git a/kvm_sys/Android.bp b/kvm_sys/Android.bp
index 7724a1195..54f09aeb0 100644
--- a/kvm_sys/Android.bp
+++ b/kvm_sys/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -10,42 +10,19 @@ package {
default_applicable_licenses: ["external_crosvm_license"],
}
-rust_defaults {
- name: "kvm_sys_defaults",
+rust_test {
+ name: "kvm_sys_test_tests_basic",
defaults: ["crosvm_defaults"],
- crate_name: "kvm_sys",
- srcs: ["src/lib.rs"],
+ host_supported: true,
+ crate_name: "basic",
+ cargo_env_compat: true,
+ srcs: ["tests/basic.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libbase_rust",
- "libdata_model",
- "liblibc",
- ],
-}
-
-rust_test_host {
- name: "kvm_sys_host_test_src_lib",
- defaults: ["kvm_sys_defaults"],
test_options: {
- unit_test: true,
+ unit_test: false,
},
-}
-
-rust_test {
- name: "kvm_sys_device_test_src_lib",
- defaults: ["kvm_sys_defaults"],
-}
-
-rust_defaults {
- name: "kvm_sys_defaults_sanity",
- defaults: ["crosvm_defaults"],
- crate_name: "sanity",
- srcs: ["tests/sanity.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
"libdata_model",
@@ -54,69 +31,17 @@ rust_defaults {
],
}
-rust_test_host {
- name: "kvm_sys_host_test_tests_sanity",
- defaults: ["kvm_sys_defaults_sanity"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "kvm_sys_device_test_tests_sanity",
- defaults: ["kvm_sys_defaults_sanity"],
-}
-
rust_library {
name: "libkvm_sys",
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "kvm_sys",
+ cargo_env_compat: true,
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
"libdata_model",
"liblibc",
],
}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/kvm_sys/Cargo.toml b/kvm_sys/Cargo.toml
index 6dd53b6ea..b5bb63e59 100644
--- a/kvm_sys/Cargo.toml
+++ b/kvm_sys/Cargo.toml
@@ -2,9 +2,9 @@
name = "kvm_sys"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
-data_model = { path = "../data_model" }
+data_model = { path = "../common/data_model" }
libc = "*"
base = { path = "../base" }
diff --git a/kvm_sys/TEST_MAPPING b/kvm_sys/TEST_MAPPING
index fb023602a..33beb7395 100644
--- a/kvm_sys/TEST_MAPPING
+++ b/kvm_sys/TEST_MAPPING
@@ -4,17 +4,17 @@
// "presubmit": [
// {
// "host": true,
-// "name": "kvm_sys_host_test_src_lib"
+// "name": "kvm_sys_test_src_lib"
// },
// {
-// "name": "kvm_sys_device_test_src_lib"
+// "name": "kvm_sys_test_src_lib"
// },
// {
// "host": true,
-// "name": "kvm_sys_host_test_tests_sanity"
+// "name": "kvm_sys_test_tests_basic"
// },
// {
-// "name": "kvm_sys_device_test_tests_sanity"
+// "name": "kvm_sys_test_tests_basic"
// }
// ]
}
diff --git a/kvm_sys/bindgen.sh b/kvm_sys/bindgen.sh
new file mode 100755
index 000000000..f63739bfa
--- /dev/null
+++ b/kvm_sys/bindgen.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Regenerate kvm_sys bindgen bindings.
+
+set -euo pipefail
+cd "$(dirname "${BASH_SOURCE[0]}")/.."
+
+source tools/impl/bindgen-common.sh
+
+KVM_EXTRAS="// Added by kvm_sys/bindgen.sh
+pub const KVM_SYSTEM_EVENT_S2IDLE: u32 = 4;
+pub const KVM_SYSTEM_EVENT_RESET_FLAG_PSCI_RESET2: u64 = 0x1;
+// TODO(tjeznach): Remove this when reporting KVM_IOAPIC_NUM_PINS is no longer required.
+pub const KVM_CAP_IOAPIC_NUM_PINS: u32 = 8191;
+// TODO(qwandor): Update this once the pKVM patches are merged upstream with a stable capability ID.
+pub const KVM_CAP_ARM_PROTECTED_VM: u32 = 0xffbadab1;
+pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_SET_FW_IPA: u32 = 0;
+pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_INFO: u32 = 1;
+pub const KVM_VM_TYPE_ARM_PROTECTED: u32 = 0x80000000;"
+
+bindgen_generate \
+ --raw-line "${KVM_EXTRAS}" \
+ --blocklist-item='__kernel.*' \
+ --blocklist-item='__BITS_PER_LONG' \
+ --blocklist-item='__FD_SETSIZE' \
+ --blocklist-item='_?IOC.*' \
+ "${BINDGEN_LINUX_X86_HEADERS}/include/linux/kvm.h" \
+ -- \
+ -isystem "${BINDGEN_LINUX_X86_HEADERS}/include" \
+ | replace_linux_int_types \
+ > kvm_sys/src/x86/bindings.rs
+
+bindgen_generate \
+ --raw-line "${KVM_EXTRAS}" \
+ --blocklist-item='__kernel.*' \
+ --blocklist-item='__BITS_PER_LONG' \
+ --blocklist-item='__FD_SETSIZE' \
+ --blocklist-item='_?IOC.*' \
+ "${BINDGEN_LINUX_ARM64_HEADERS}/include/linux/kvm.h" \
+ -- \
+ -isystem "${BINDGEN_LINUX_ARM64_HEADERS}/include" \
+ | replace_linux_int_types \
+ > kvm_sys/src/aarch64/bindings.rs
diff --git a/kvm_sys/cargo2android.json b/kvm_sys/cargo2android.json
new file mode 100644
index 000000000..083829d48
--- /dev/null
+++ b/kvm_sys/cargo2android.json
@@ -0,0 +1,10 @@
+{
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "no_presubmit": true,
+ "no-pkg-vers": true,
+ "no-subdir": true,
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/kvm_sys/src/aarch64/bindings.rs b/kvm_sys/src/aarch64/bindings.rs
index 24ae39144..052a5f6e2 100644
--- a/kvm_sys/src/aarch64/bindings.rs
+++ b/kvm_sys/src/aarch64/bindings.rs
@@ -1,31 +1,36 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+/* automatically generated by tools/bindgen-all-the-things */
-/*
- * automatically generated by bindgen 0.49.2.
- * From chromeos-linux v5.4 include/linux/kvm.h
- * $ cd /path/to/kernel/v5.4/
- * $ make headers_install ARCH=arm64 INSTALL_HDR_PATH=arm64_v5_4_headers
- * $ cd arm64_v5_4_headers
- * $ bindgen --with-derive-default -o bindings.rs include/linux/kvm.h -- -Iinclude
- */
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+
+// Added by kvm_sys/bindgen.sh
+pub const KVM_SYSTEM_EVENT_S2IDLE: u32 = 4;
+pub const KVM_SYSTEM_EVENT_RESET_FLAG_PSCI_RESET2: u64 = 0x1;
+// TODO(tjeznach): Remove this when reporting KVM_IOAPIC_NUM_PINS is no longer required.
+pub const KVM_CAP_IOAPIC_NUM_PINS: u32 = 8191;
+// TODO(qwandor): Update this once the pKVM patches are merged upstream with a stable capability ID.
+pub const KVM_CAP_ARM_PROTECTED_VM: u32 = 0xffbadab1;
+pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_SET_FW_IPA: u32 = 0;
+pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_INFO: u32 = 1;
+pub const KVM_VM_TYPE_ARM_PROTECTED: u32 = 0x80000000;
#[repr(C)]
#[derive(Default)]
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
impl<T> __IncompleteArrayField<T> {
#[inline]
- pub fn new() -> Self {
+ pub const fn new() -> Self {
__IncompleteArrayField(::std::marker::PhantomData, [])
}
#[inline]
- pub unsafe fn as_ptr(&self) -> *const T {
- ::std::mem::transmute(self)
+ pub fn as_ptr(&self) -> *const T {
+ self as *const _ as *const T
}
#[inline]
- pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
- ::std::mem::transmute(self)
+ pub fn as_mut_ptr(&mut self) -> *mut T {
+ self as *mut _ as *mut T
}
#[inline]
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
@@ -41,34 +46,6 @@ impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
fmt.write_str("__IncompleteArrayField")
}
}
-impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
- #[inline]
- fn clone(&self) -> Self {
- Self::new()
- }
-}
-pub const __BITS_PER_LONG: u32 = 64;
-pub const __FD_SETSIZE: u32 = 1024;
-pub const _IOC_NRBITS: u32 = 8;
-pub const _IOC_TYPEBITS: u32 = 8;
-pub const _IOC_SIZEBITS: u32 = 14;
-pub const _IOC_DIRBITS: u32 = 2;
-pub const _IOC_NRMASK: u32 = 255;
-pub const _IOC_TYPEMASK: u32 = 255;
-pub const _IOC_SIZEMASK: u32 = 16383;
-pub const _IOC_DIRMASK: u32 = 3;
-pub const _IOC_NRSHIFT: u32 = 0;
-pub const _IOC_TYPESHIFT: u32 = 8;
-pub const _IOC_SIZESHIFT: u32 = 16;
-pub const _IOC_DIRSHIFT: u32 = 30;
-pub const _IOC_NONE: u32 = 0;
-pub const _IOC_WRITE: u32 = 1;
-pub const _IOC_READ: u32 = 2;
-pub const IOC_IN: u32 = 1073741824;
-pub const IOC_OUT: u32 = 2147483648;
-pub const IOC_INOUT: u32 = 3221225472;
-pub const IOCSIZE_MASK: u32 = 1073676288;
-pub const IOCSIZE_SHIFT: u32 = 16;
pub const KVM_SPSR_EL1: u32 = 0;
pub const KVM_SPSR_SVC: u32 = 0;
pub const KVM_SPSR_ABT: u32 = 1;
@@ -153,7 +130,16 @@ pub const HWCAP2_SVESHA3: u32 = 32;
pub const HWCAP2_SVESM4: u32 = 64;
pub const HWCAP2_FLAGM2: u32 = 128;
pub const HWCAP2_FRINT: u32 = 256;
-pub const HWCAP2_RNG: u32 = 512;
+pub const HWCAP2_SVEI8MM: u32 = 512;
+pub const HWCAP2_SVEF32MM: u32 = 1024;
+pub const HWCAP2_SVEF64MM: u32 = 2048;
+pub const HWCAP2_SVEBF16: u32 = 4096;
+pub const HWCAP2_I8MM: u32 = 8192;
+pub const HWCAP2_BF16: u32 = 16384;
+pub const HWCAP2_DGH: u32 = 32768;
+pub const HWCAP2_RNG: u32 = 65536;
+pub const HWCAP2_BTI: u32 = 131072;
+pub const HWCAP2_MTE: u32 = 262144;
pub const __SVE_VQ_BYTES: u32 = 16;
pub const __SVE_VQ_MIN: u32 = 1;
pub const __SVE_VQ_MAX: u32 = 512;
@@ -175,20 +161,29 @@ pub const PSR_F_BIT: u32 = 64;
pub const PSR_I_BIT: u32 = 128;
pub const PSR_A_BIT: u32 = 256;
pub const PSR_D_BIT: u32 = 512;
+pub const PSR_BTYPE_MASK: u32 = 3072;
pub const PSR_SSBS_BIT: u32 = 4096;
pub const PSR_PAN_BIT: u32 = 4194304;
pub const PSR_UAO_BIT: u32 = 8388608;
pub const PSR_DIT_BIT: u32 = 16777216;
+pub const PSR_TCO_BIT: u32 = 33554432;
pub const PSR_V_BIT: u32 = 268435456;
pub const PSR_C_BIT: u32 = 536870912;
pub const PSR_Z_BIT: u32 = 1073741824;
pub const PSR_N_BIT: u32 = 2147483648;
+pub const PSR_BTYPE_SHIFT: u32 = 10;
pub const PSR_f: u32 = 4278190080;
pub const PSR_s: u32 = 16711680;
pub const PSR_x: u32 = 65280;
pub const PSR_c: u32 = 255;
+pub const PSR_BTYPE_NONE: u32 = 0;
+pub const PSR_BTYPE_JC: u32 = 1024;
+pub const PSR_BTYPE_C: u32 = 2048;
+pub const PSR_BTYPE_J: u32 = 3072;
pub const PTRACE_SYSEMU: u32 = 31;
pub const PTRACE_SYSEMU_SINGLESTEP: u32 = 32;
+pub const PTRACE_PEEKMTETAGS: u32 = 33;
+pub const PTRACE_POKEMTETAGS: u32 = 34;
pub const SVE_PT_REGS_MASK: u32 = 1;
pub const SVE_PT_REGS_FPSIMD: u32 = 0;
pub const SVE_PT_REGS_SVE: u32 = 1;
@@ -225,6 +220,8 @@ pub const KVM_ARM_VCPU_PTRAUTH_GENERIC: u32 = 6;
pub const KVM_ARM_MAX_DBG_REGS: u32 = 16;
pub const KVM_GUESTDBG_USE_SW_BP: u32 = 65536;
pub const KVM_GUESTDBG_USE_HW: u32 = 131072;
+pub const KVM_PMU_EVENT_ALLOW: u32 = 0;
+pub const KVM_PMU_EVENT_DENY: u32 = 1;
pub const KVM_REG_ARM_COPROC_MASK: u32 = 268369920;
pub const KVM_REG_ARM_COPROC_SHIFT: u32 = 16;
pub const KVM_REG_ARM_CORE: u32 = 1048576;
@@ -292,9 +289,12 @@ pub const KVM_DEV_ARM_ITS_CTRL_RESET: u32 = 4;
pub const KVM_ARM_VCPU_PMU_V3_CTRL: u32 = 0;
pub const KVM_ARM_VCPU_PMU_V3_IRQ: u32 = 0;
pub const KVM_ARM_VCPU_PMU_V3_INIT: u32 = 1;
+pub const KVM_ARM_VCPU_PMU_V3_FILTER: u32 = 2;
pub const KVM_ARM_VCPU_TIMER_CTRL: u32 = 1;
pub const KVM_ARM_VCPU_TIMER_IRQ_VTIMER: u32 = 0;
pub const KVM_ARM_VCPU_TIMER_IRQ_PTIMER: u32 = 1;
+pub const KVM_ARM_VCPU_PVTIME_CTRL: u32 = 2;
+pub const KVM_ARM_VCPU_PVTIME_IPA: u32 = 0;
pub const KVM_ARM_IRQ_VCPU2_SHIFT: u32 = 28;
pub const KVM_ARM_IRQ_VCPU2_MASK: u32 = 15;
pub const KVM_ARM_IRQ_TYPE_SHIFT: u32 = 24;
@@ -355,6 +355,7 @@ pub const KVM_PIT_SPEAKER_DUMMY: u32 = 1;
pub const KVM_S390_CMMA_PEEK: u32 = 1;
pub const KVM_EXIT_HYPERV_SYNIC: u32 = 1;
pub const KVM_EXIT_HYPERV_HCALL: u32 = 2;
+pub const KVM_EXIT_HYPERV_SYNDBG: u32 = 3;
pub const KVM_S390_GET_SKEYS_NONE: u32 = 1;
pub const KVM_S390_SKEYS_MAX: u32 = 1048576;
pub const KVM_EXIT_UNKNOWN: u32 = 0;
@@ -385,6 +386,9 @@ pub const KVM_EXIT_SYSTEM_EVENT: u32 = 24;
pub const KVM_EXIT_S390_STSI: u32 = 25;
pub const KVM_EXIT_IOAPIC_EOI: u32 = 26;
pub const KVM_EXIT_HYPERV: u32 = 27;
+pub const KVM_EXIT_ARM_NISV: u32 = 28;
+pub const KVM_EXIT_X86_RDMSR: u32 = 29;
+pub const KVM_EXIT_X86_WRMSR: u32 = 30;
pub const KVM_INTERNAL_ERROR_EMULATION: u32 = 1;
pub const KVM_INTERNAL_ERROR_SIMUL_EX: u32 = 2;
pub const KVM_INTERNAL_ERROR_DELIVERY_EV: u32 = 3;
@@ -399,9 +403,14 @@ pub const KVM_S390_RESET_IPL: u32 = 16;
pub const KVM_SYSTEM_EVENT_SHUTDOWN: u32 = 1;
pub const KVM_SYSTEM_EVENT_RESET: u32 = 2;
pub const KVM_SYSTEM_EVENT_CRASH: u32 = 3;
+pub const KVM_MSR_EXIT_REASON_INVAL: u32 = 1;
+pub const KVM_MSR_EXIT_REASON_UNKNOWN: u32 = 2;
+pub const KVM_MSR_EXIT_REASON_FILTER: u32 = 4;
pub const SYNC_REGS_SIZE_BYTES: u32 = 2048;
pub const KVM_S390_MEMOP_LOGICAL_READ: u32 = 0;
pub const KVM_S390_MEMOP_LOGICAL_WRITE: u32 = 1;
+pub const KVM_S390_MEMOP_SIDA_READ: u32 = 2;
+pub const KVM_S390_MEMOP_SIDA_WRITE: u32 = 3;
pub const KVM_S390_MEMOP_F_CHECK_ONLY: u32 = 1;
pub const KVM_S390_MEMOP_F_INJECT_EXCEPTION: u32 = 2;
pub const KVM_MP_STATE_RUNNABLE: u32 = 0;
@@ -618,10 +627,21 @@ pub const KVM_CAP_ARM_PTRAUTH_GENERIC: u32 = 172;
pub const KVM_CAP_PMU_EVENT_FILTER: u32 = 173;
pub const KVM_CAP_ARM_IRQ_LINE_LAYOUT_2: u32 = 174;
pub const KVM_CAP_HYPERV_DIRECT_TLBFLUSH: u32 = 175;
-// TODO(qwandor): Update this once the pKVM patches are merged upstream with a stable capability ID.
-pub const KVM_CAP_ARM_PROTECTED_VM: u32 = 0xffbadab1;
-pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_ENABLE: u32 = 0;
-pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_INFO: u32 = 1;
+pub const KVM_CAP_PPC_GUEST_DEBUG_SSTEP: u32 = 176;
+pub const KVM_CAP_ARM_NISV_TO_USER: u32 = 177;
+pub const KVM_CAP_ARM_INJECT_EXT_DABT: u32 = 178;
+pub const KVM_CAP_S390_VCPU_RESETS: u32 = 179;
+pub const KVM_CAP_S390_PROTECTED: u32 = 180;
+pub const KVM_CAP_PPC_SECURE_GUEST: u32 = 181;
+pub const KVM_CAP_HALT_POLL: u32 = 182;
+pub const KVM_CAP_ASYNC_PF_INT: u32 = 183;
+pub const KVM_CAP_LAST_CPU: u32 = 184;
+pub const KVM_CAP_SMALLER_MAXPHYADDR: u32 = 185;
+pub const KVM_CAP_S390_DIAG318: u32 = 186;
+pub const KVM_CAP_STEAL_TIME: u32 = 187;
+pub const KVM_CAP_X86_USER_SPACE_MSR: u32 = 188;
+pub const KVM_CAP_X86_MSR_FILTER: u32 = 189;
+pub const KVM_CAP_ENFORCE_PV_FEATURE_CPUID: u32 = 190;
pub const KVM_IRQ_ROUTING_IRQCHIP: u32 = 1;
pub const KVM_IRQ_ROUTING_MSI: u32 = 2;
pub const KVM_IRQ_ROUTING_S390_ADAPTER: u32 = 3;
@@ -679,464 +699,63 @@ pub const KVM_ARM_DEV_EL1_PTIMER: u32 = 2;
pub const KVM_ARM_DEV_PMU: u32 = 4;
pub const KVM_HYPERV_CONN_ID_MASK: u32 = 16777215;
pub const KVM_HYPERV_EVENTFD_DEASSIGN: u32 = 1;
-pub type __s8 = ::std::os::raw::c_schar;
-pub type __u8 = ::std::os::raw::c_uchar;
-pub type __s16 = ::std::os::raw::c_short;
-pub type __u16 = ::std::os::raw::c_ushort;
-pub type __s32 = ::std::os::raw::c_int;
-pub type __u32 = ::std::os::raw::c_uint;
-pub type __s64 = ::std::os::raw::c_longlong;
-pub type __u64 = ::std::os::raw::c_ulonglong;
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct __kernel_fd_set {
- pub fds_bits: [::std::os::raw::c_ulong; 16usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_fd_set() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_fd_set>(),
- 128usize,
- concat!("Size of: ", stringify!(__kernel_fd_set))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_fd_set>(),
- 8usize,
- concat!("Alignment of ", stringify!(__kernel_fd_set))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<__kernel_fd_set>())).fds_bits as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(__kernel_fd_set),
- "::",
- stringify!(fds_bits)
- )
- );
-}
-pub type __kernel_sighandler_t =
- ::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
-pub type __kernel_key_t = ::std::os::raw::c_int;
-pub type __kernel_mqd_t = ::std::os::raw::c_int;
-pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
-pub type __kernel_long_t = ::std::os::raw::c_long;
-pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
-pub type __kernel_ino_t = __kernel_ulong_t;
-pub type __kernel_mode_t = ::std::os::raw::c_uint;
-pub type __kernel_pid_t = ::std::os::raw::c_int;
-pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
-pub type __kernel_uid_t = ::std::os::raw::c_uint;
-pub type __kernel_gid_t = ::std::os::raw::c_uint;
-pub type __kernel_suseconds_t = __kernel_long_t;
-pub type __kernel_daddr_t = ::std::os::raw::c_int;
-pub type __kernel_uid32_t = ::std::os::raw::c_uint;
-pub type __kernel_gid32_t = ::std::os::raw::c_uint;
-pub type __kernel_old_dev_t = ::std::os::raw::c_uint;
-pub type __kernel_size_t = __kernel_ulong_t;
-pub type __kernel_ssize_t = __kernel_long_t;
-pub type __kernel_ptrdiff_t = __kernel_long_t;
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct __kernel_fsid_t {
- pub val: [::std::os::raw::c_int; 2usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_fsid_t() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_fsid_t>(),
- 8usize,
- concat!("Size of: ", stringify!(__kernel_fsid_t))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_fsid_t>(),
- 4usize,
- concat!("Alignment of ", stringify!(__kernel_fsid_t))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<__kernel_fsid_t>())).val as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(__kernel_fsid_t),
- "::",
- stringify!(val)
- )
- );
-}
-pub type __kernel_off_t = __kernel_long_t;
-pub type __kernel_loff_t = ::std::os::raw::c_longlong;
-pub type __kernel_time_t = __kernel_long_t;
-pub type __kernel_time64_t = ::std::os::raw::c_longlong;
-pub type __kernel_clock_t = __kernel_long_t;
-pub type __kernel_timer_t = ::std::os::raw::c_int;
-pub type __kernel_clockid_t = ::std::os::raw::c_int;
-pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
-pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
-pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
-pub type __le16 = __u16;
-pub type __be16 = __u16;
-pub type __le32 = __u32;
-pub type __be32 = __u32;
-pub type __le64 = __u64;
-pub type __be64 = __u64;
-pub type __sum16 = __u16;
-pub type __wsum = __u32;
+pub const KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE: u32 = 1;
+pub const KVM_DIRTY_LOG_INITIALLY_SET: u32 = 2;
+pub type __le16 = u16;
+pub type __be16 = u16;
+pub type __le32 = u32;
+pub type __be32 = u32;
+pub type __le64 = u64;
+pub type __be64 = u64;
+pub type __sum16 = u16;
+pub type __wsum = u32;
pub type __poll_t = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct user_pt_regs {
- pub regs: [__u64; 31usize],
- pub sp: __u64,
- pub pc: __u64,
- pub pstate: __u64,
-}
-#[test]
-fn bindgen_test_layout_user_pt_regs() {
- assert_eq!(
- ::std::mem::size_of::<user_pt_regs>(),
- 272usize,
- concat!("Size of: ", stringify!(user_pt_regs))
- );
- assert_eq!(
- ::std::mem::align_of::<user_pt_regs>(),
- 8usize,
- concat!("Alignment of ", stringify!(user_pt_regs))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_pt_regs>())).regs as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(user_pt_regs),
- "::",
- stringify!(regs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_pt_regs>())).sp as *const _ as usize },
- 248usize,
- concat!(
- "Offset of field: ",
- stringify!(user_pt_regs),
- "::",
- stringify!(sp)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_pt_regs>())).pc as *const _ as usize },
- 256usize,
- concat!(
- "Offset of field: ",
- stringify!(user_pt_regs),
- "::",
- stringify!(pc)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_pt_regs>())).pstate as *const _ as usize },
- 264usize,
- concat!(
- "Offset of field: ",
- stringify!(user_pt_regs),
- "::",
- stringify!(pstate)
- )
- );
+ pub regs: [u64; 31usize],
+ pub sp: u64,
+ pub pc: u64,
+ pub pstate: u64,
}
#[repr(C)]
#[repr(align(16))]
#[derive(Debug, Default, Copy, Clone)]
pub struct user_fpsimd_state {
pub vregs: [__uint128_t; 32usize],
- pub fpsr: __u32,
- pub fpcr: __u32,
- pub __reserved: [__u32; 2usize],
-}
-#[test]
-fn bindgen_test_layout_user_fpsimd_state() {
- assert_eq!(
- ::std::mem::size_of::<user_fpsimd_state>(),
- 528usize,
- concat!("Size of: ", stringify!(user_fpsimd_state))
- );
- assert_eq!(
- ::std::mem::align_of::<user_fpsimd_state>(),
- 16usize,
- concat!("Alignment of ", stringify!(user_fpsimd_state))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_fpsimd_state>())).vregs as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(user_fpsimd_state),
- "::",
- stringify!(vregs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_fpsimd_state>())).fpsr as *const _ as usize },
- 512usize,
- concat!(
- "Offset of field: ",
- stringify!(user_fpsimd_state),
- "::",
- stringify!(fpsr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_fpsimd_state>())).fpcr as *const _ as usize },
- 516usize,
- concat!(
- "Offset of field: ",
- stringify!(user_fpsimd_state),
- "::",
- stringify!(fpcr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_fpsimd_state>())).__reserved as *const _ as usize },
- 520usize,
- concat!(
- "Offset of field: ",
- stringify!(user_fpsimd_state),
- "::",
- stringify!(__reserved)
- )
- );
+ pub fpsr: u32,
+ pub fpcr: u32,
+ pub __reserved: [u32; 2usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct user_hwdebug_state {
- pub dbg_info: __u32,
- pub pad: __u32,
+ pub dbg_info: u32,
+ pub pad: u32,
pub dbg_regs: [user_hwdebug_state__bindgen_ty_1; 16usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct user_hwdebug_state__bindgen_ty_1 {
- pub addr: __u64,
- pub ctrl: __u32,
- pub pad: __u32,
-}
-#[test]
-fn bindgen_test_layout_user_hwdebug_state__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<user_hwdebug_state__bindgen_ty_1>(),
- 16usize,
- concat!("Size of: ", stringify!(user_hwdebug_state__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<user_hwdebug_state__bindgen_ty_1>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(user_hwdebug_state__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<user_hwdebug_state__bindgen_ty_1>())).addr as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(user_hwdebug_state__bindgen_ty_1),
- "::",
- stringify!(addr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<user_hwdebug_state__bindgen_ty_1>())).ctrl as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(user_hwdebug_state__bindgen_ty_1),
- "::",
- stringify!(ctrl)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<user_hwdebug_state__bindgen_ty_1>())).pad as *const _ as usize
- },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(user_hwdebug_state__bindgen_ty_1),
- "::",
- stringify!(pad)
- )
- );
-}
-#[test]
-fn bindgen_test_layout_user_hwdebug_state() {
- assert_eq!(
- ::std::mem::size_of::<user_hwdebug_state>(),
- 264usize,
- concat!("Size of: ", stringify!(user_hwdebug_state))
- );
- assert_eq!(
- ::std::mem::align_of::<user_hwdebug_state>(),
- 8usize,
- concat!("Alignment of ", stringify!(user_hwdebug_state))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_hwdebug_state>())).dbg_info as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(user_hwdebug_state),
- "::",
- stringify!(dbg_info)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_hwdebug_state>())).pad as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(user_hwdebug_state),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_hwdebug_state>())).dbg_regs as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(user_hwdebug_state),
- "::",
- stringify!(dbg_regs)
- )
- );
+ pub addr: u64,
+ pub ctrl: u32,
+ pub pad: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct user_sve_header {
- pub size: __u32,
- pub max_size: __u32,
- pub vl: __u16,
- pub max_vl: __u16,
- pub flags: __u16,
- pub __reserved: __u16,
-}
-#[test]
-fn bindgen_test_layout_user_sve_header() {
- assert_eq!(
- ::std::mem::size_of::<user_sve_header>(),
- 16usize,
- concat!("Size of: ", stringify!(user_sve_header))
- );
- assert_eq!(
- ::std::mem::align_of::<user_sve_header>(),
- 4usize,
- concat!("Alignment of ", stringify!(user_sve_header))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_sve_header>())).size as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(user_sve_header),
- "::",
- stringify!(size)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_sve_header>())).max_size as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(user_sve_header),
- "::",
- stringify!(max_size)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_sve_header>())).vl as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(user_sve_header),
- "::",
- stringify!(vl)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_sve_header>())).max_vl as *const _ as usize },
- 10usize,
- concat!(
- "Offset of field: ",
- stringify!(user_sve_header),
- "::",
- stringify!(max_vl)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_sve_header>())).flags as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(user_sve_header),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_sve_header>())).__reserved as *const _ as usize },
- 14usize,
- concat!(
- "Offset of field: ",
- stringify!(user_sve_header),
- "::",
- stringify!(__reserved)
- )
- );
+ pub size: u32,
+ pub max_size: u32,
+ pub vl: u16,
+ pub max_vl: u16,
+ pub flags: u16,
+ pub __reserved: u16,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct user_pac_mask {
- pub data_mask: __u64,
- pub insn_mask: __u64,
-}
-#[test]
-fn bindgen_test_layout_user_pac_mask() {
- assert_eq!(
- ::std::mem::size_of::<user_pac_mask>(),
- 16usize,
- concat!("Size of: ", stringify!(user_pac_mask))
- );
- assert_eq!(
- ::std::mem::align_of::<user_pac_mask>(),
- 8usize,
- concat!("Alignment of ", stringify!(user_pac_mask))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_pac_mask>())).data_mask as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(user_pac_mask),
- "::",
- stringify!(data_mask)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_pac_mask>())).insn_mask as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(user_pac_mask),
- "::",
- stringify!(insn_mask)
- )
- );
+ pub data_mask: u64,
+ pub insn_mask: u64,
}
#[repr(C)]
#[repr(align(16))]
@@ -1147,1202 +766,228 @@ pub struct user_pac_address_keys {
pub apdakey: __uint128_t,
pub apdbkey: __uint128_t,
}
-#[test]
-fn bindgen_test_layout_user_pac_address_keys() {
- assert_eq!(
- ::std::mem::size_of::<user_pac_address_keys>(),
- 64usize,
- concat!("Size of: ", stringify!(user_pac_address_keys))
- );
- assert_eq!(
- ::std::mem::align_of::<user_pac_address_keys>(),
- 16usize,
- concat!("Alignment of ", stringify!(user_pac_address_keys))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_pac_address_keys>())).apiakey as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(user_pac_address_keys),
- "::",
- stringify!(apiakey)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_pac_address_keys>())).apibkey as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(user_pac_address_keys),
- "::",
- stringify!(apibkey)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_pac_address_keys>())).apdakey as *const _ as usize },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(user_pac_address_keys),
- "::",
- stringify!(apdakey)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_pac_address_keys>())).apdbkey as *const _ as usize },
- 48usize,
- concat!(
- "Offset of field: ",
- stringify!(user_pac_address_keys),
- "::",
- stringify!(apdbkey)
- )
- );
-}
#[repr(C)]
#[repr(align(16))]
#[derive(Debug, Default, Copy, Clone)]
pub struct user_pac_generic_keys {
pub apgakey: __uint128_t,
}
-#[test]
-fn bindgen_test_layout_user_pac_generic_keys() {
- assert_eq!(
- ::std::mem::size_of::<user_pac_generic_keys>(),
- 16usize,
- concat!("Size of: ", stringify!(user_pac_generic_keys))
- );
- assert_eq!(
- ::std::mem::align_of::<user_pac_generic_keys>(),
- 16usize,
- concat!("Alignment of ", stringify!(user_pac_generic_keys))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<user_pac_generic_keys>())).apgakey as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(user_pac_generic_keys),
- "::",
- stringify!(apgakey)
- )
- );
-}
#[repr(C)]
#[repr(align(16))]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_regs {
pub regs: user_pt_regs,
- pub sp_el1: __u64,
- pub elr_el1: __u64,
- pub spsr: [__u64; 5usize],
+ pub sp_el1: u64,
+ pub elr_el1: u64,
+ pub spsr: [u64; 5usize],
pub __bindgen_padding_0: u64,
pub fp_regs: user_fpsimd_state,
}
-#[test]
-fn bindgen_test_layout_kvm_regs() {
- assert_eq!(
- ::std::mem::size_of::<kvm_regs>(),
- 864usize,
- concat!("Size of: ", stringify!(kvm_regs))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_regs>(),
- 16usize,
- concat!("Alignment of ", stringify!(kvm_regs))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).regs as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(regs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).sp_el1 as *const _ as usize },
- 272usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(sp_el1)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).elr_el1 as *const _ as usize },
- 280usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(elr_el1)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).spsr as *const _ as usize },
- 288usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(spsr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).fp_regs as *const _ as usize },
- 336usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(fp_regs)
- )
- );
-}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_vcpu_init {
- pub target: __u32,
- pub features: [__u32; 7usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_vcpu_init() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vcpu_init>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_vcpu_init))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vcpu_init>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_vcpu_init))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vcpu_init>())).target as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_init),
- "::",
- stringify!(target)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vcpu_init>())).features as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_init),
- "::",
- stringify!(features)
- )
- );
+ pub target: u32,
+ pub features: [u32; 7usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sregs {}
-#[test]
-fn bindgen_test_layout_kvm_sregs() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sregs>(),
- 0usize,
- concat!("Size of: ", stringify!(kvm_sregs))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sregs>(),
- 1usize,
- concat!("Alignment of ", stringify!(kvm_sregs))
- );
-}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_fpu {}
-#[test]
-fn bindgen_test_layout_kvm_fpu() {
- assert_eq!(
- ::std::mem::size_of::<kvm_fpu>(),
- 0usize,
- concat!("Size of: ", stringify!(kvm_fpu))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_fpu>(),
- 1usize,
- concat!("Alignment of ", stringify!(kvm_fpu))
- );
-}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_guest_debug_arch {
- pub dbg_bcr: [__u64; 16usize],
- pub dbg_bvr: [__u64; 16usize],
- pub dbg_wcr: [__u64; 16usize],
- pub dbg_wvr: [__u64; 16usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_guest_debug_arch() {
- assert_eq!(
- ::std::mem::size_of::<kvm_guest_debug_arch>(),
- 512usize,
- concat!("Size of: ", stringify!(kvm_guest_debug_arch))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_guest_debug_arch>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_guest_debug_arch))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_guest_debug_arch>())).dbg_bcr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_guest_debug_arch),
- "::",
- stringify!(dbg_bcr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_guest_debug_arch>())).dbg_bvr as *const _ as usize },
- 128usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_guest_debug_arch),
- "::",
- stringify!(dbg_bvr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_guest_debug_arch>())).dbg_wcr as *const _ as usize },
- 256usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_guest_debug_arch),
- "::",
- stringify!(dbg_wcr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_guest_debug_arch>())).dbg_wvr as *const _ as usize },
- 384usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_guest_debug_arch),
- "::",
- stringify!(dbg_wvr)
- )
- );
+ pub dbg_bcr: [u64; 16usize],
+ pub dbg_bvr: [u64; 16usize],
+ pub dbg_wcr: [u64; 16usize],
+ pub dbg_wvr: [u64; 16usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_debug_exit_arch {
- pub hsr: __u32,
- pub far: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_debug_exit_arch() {
- assert_eq!(
- ::std::mem::size_of::<kvm_debug_exit_arch>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_debug_exit_arch))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_debug_exit_arch>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_debug_exit_arch))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_exit_arch>())).hsr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_exit_arch),
- "::",
- stringify!(hsr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_exit_arch>())).far as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_exit_arch),
- "::",
- stringify!(far)
- )
- );
+ pub hsr: u32,
+ pub far: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sync_regs {
- pub device_irq_level: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_sync_regs() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sync_regs>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_sync_regs))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sync_regs>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sync_regs))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sync_regs>())).device_irq_level as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sync_regs),
- "::",
- stringify!(device_irq_level)
- )
- );
+ pub device_irq_level: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
-pub struct kvm_arch_memory_slot {}
-#[test]
-fn bindgen_test_layout_kvm_arch_memory_slot() {
- assert_eq!(
- ::std::mem::size_of::<kvm_arch_memory_slot>(),
- 0usize,
- concat!("Size of: ", stringify!(kvm_arch_memory_slot))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_arch_memory_slot>(),
- 1usize,
- concat!("Alignment of ", stringify!(kvm_arch_memory_slot))
- );
+pub struct kvm_pmu_event_filter {
+ pub base_event: u16,
+ pub nevents: u16,
+ pub action: u8,
+ pub pad: [u8; 3usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_vcpu_events {
pub exception: kvm_vcpu_events__bindgen_ty_1,
- pub reserved: [__u32; 12usize],
+ pub reserved: [u32; 12usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_vcpu_events__bindgen_ty_1 {
- pub serror_pending: __u8,
- pub serror_has_esr: __u8,
- pub pad: [__u8; 6usize],
- pub serror_esr: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_vcpu_events__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vcpu_events__bindgen_ty_1>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_vcpu_events__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vcpu_events__bindgen_ty_1>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_vcpu_events__bindgen_ty_1))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_1>())).serror_pending as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_1),
- "::",
- stringify!(serror_pending)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_1>())).serror_has_esr as *const _
- as usize
- },
- 1usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_1),
- "::",
- stringify!(serror_has_esr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_1>())).pad as *const _ as usize
- },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_1),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_1>())).serror_esr as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_1),
- "::",
- stringify!(serror_esr)
- )
- );
-}
-#[test]
-fn bindgen_test_layout_kvm_vcpu_events() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vcpu_events>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_vcpu_events))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vcpu_events>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_vcpu_events))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vcpu_events>())).exception as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events),
- "::",
- stringify!(exception)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vcpu_events>())).reserved as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events),
- "::",
- stringify!(reserved)
- )
- );
+ pub serror_pending: u8,
+ pub serror_has_esr: u8,
+ pub ext_dabt_pending: u8,
+ pub pad: [u8; 5usize],
+ pub serror_esr: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_user_trace_setup {
- pub buf_size: __u32,
- pub buf_nr: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_user_trace_setup() {
- assert_eq!(
- ::std::mem::size_of::<kvm_user_trace_setup>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_user_trace_setup))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_user_trace_setup>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_user_trace_setup))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_user_trace_setup>())).buf_size as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_user_trace_setup),
- "::",
- stringify!(buf_size)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_user_trace_setup>())).buf_nr as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_user_trace_setup),
- "::",
- stringify!(buf_nr)
- )
- );
+ pub buf_size: u32,
+ pub buf_nr: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_breakpoint {
- pub enabled: __u32,
- pub padding: __u32,
- pub address: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_breakpoint() {
- assert_eq!(
- ::std::mem::size_of::<kvm_breakpoint>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_breakpoint))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_breakpoint>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_breakpoint))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_breakpoint>())).enabled as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_breakpoint),
- "::",
- stringify!(enabled)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_breakpoint>())).padding as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_breakpoint),
- "::",
- stringify!(padding)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_breakpoint>())).address as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_breakpoint),
- "::",
- stringify!(address)
- )
- );
+ pub enabled: u32,
+ pub padding: u32,
+ pub address: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_debug_guest {
- pub enabled: __u32,
- pub pad: __u32,
+ pub enabled: u32,
+ pub pad: u32,
pub breakpoints: [kvm_breakpoint; 4usize],
- pub singlestep: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_debug_guest() {
- assert_eq!(
- ::std::mem::size_of::<kvm_debug_guest>(),
- 80usize,
- concat!("Size of: ", stringify!(kvm_debug_guest))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_debug_guest>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_debug_guest))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_guest>())).enabled as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_guest),
- "::",
- stringify!(enabled)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_guest>())).pad as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_guest),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_guest>())).breakpoints as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_guest),
- "::",
- stringify!(breakpoints)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_guest>())).singlestep as *const _ as usize },
- 72usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_guest),
- "::",
- stringify!(singlestep)
- )
- );
+ pub singlestep: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_memory_region {
- pub slot: __u32,
- pub flags: __u32,
- pub guest_phys_addr: __u64,
- pub memory_size: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_memory_region() {
- assert_eq!(
- ::std::mem::size_of::<kvm_memory_region>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_memory_region))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_memory_region>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_memory_region))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_memory_region>())).slot as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_memory_region),
- "::",
- stringify!(slot)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_memory_region>())).flags as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_memory_region),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_memory_region>())).guest_phys_addr as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_memory_region),
- "::",
- stringify!(guest_phys_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_memory_region>())).memory_size as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_memory_region),
- "::",
- stringify!(memory_size)
- )
- );
+ pub slot: u32,
+ pub flags: u32,
+ pub guest_phys_addr: u64,
+ pub memory_size: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_userspace_memory_region {
- pub slot: __u32,
- pub flags: __u32,
- pub guest_phys_addr: __u64,
- pub memory_size: __u64,
- pub userspace_addr: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_userspace_memory_region() {
- assert_eq!(
- ::std::mem::size_of::<kvm_userspace_memory_region>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_userspace_memory_region))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_userspace_memory_region>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_userspace_memory_region))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_userspace_memory_region>())).slot as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_userspace_memory_region),
- "::",
- stringify!(slot)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_userspace_memory_region>())).flags as *const _ as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_userspace_memory_region),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_userspace_memory_region>())).guest_phys_addr as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_userspace_memory_region),
- "::",
- stringify!(guest_phys_addr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_userspace_memory_region>())).memory_size as *const _ as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_userspace_memory_region),
- "::",
- stringify!(memory_size)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_userspace_memory_region>())).userspace_addr as *const _
- as usize
- },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_userspace_memory_region),
- "::",
- stringify!(userspace_addr)
- )
- );
+ pub slot: u32,
+ pub flags: u32,
+ pub guest_phys_addr: u64,
+ pub memory_size: u64,
+ pub userspace_addr: u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_irq_level {
pub __bindgen_anon_1: kvm_irq_level__bindgen_ty_1,
- pub level: __u32,
+ pub level: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_irq_level__bindgen_ty_1 {
- pub irq: __u32,
- pub status: __s32,
- _bindgen_union_align: u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_irq_level__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_level__bindgen_ty_1>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_irq_level__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_level__bindgen_ty_1>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_irq_level__bindgen_ty_1))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_level__bindgen_ty_1>())).irq as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_level__bindgen_ty_1),
- "::",
- stringify!(irq)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_level__bindgen_ty_1>())).status as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_level__bindgen_ty_1),
- "::",
- stringify!(status)
- )
- );
+ pub irq: u32,
+ pub status: i32,
}
impl Default for kvm_irq_level__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_irq_level() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_level>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_irq_level))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_level>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_irq_level))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_level>())).level as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_level),
- "::",
- stringify!(level)
- )
- );
-}
impl Default for kvm_irq_level {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_irqchip {
- pub chip_id: __u32,
- pub pad: __u32,
+ pub chip_id: u32,
+ pub pad: u32,
pub chip: kvm_irqchip__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_irqchip__bindgen_ty_1 {
pub dummy: [::std::os::raw::c_char; 512usize],
- _bindgen_union_align: [u8; 512usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_irqchip__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irqchip__bindgen_ty_1>(),
- 512usize,
- concat!("Size of: ", stringify!(kvm_irqchip__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irqchip__bindgen_ty_1>(),
- 1usize,
- concat!("Alignment of ", stringify!(kvm_irqchip__bindgen_ty_1))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqchip__bindgen_ty_1>())).dummy as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqchip__bindgen_ty_1),
- "::",
- stringify!(dummy)
- )
- );
}
impl Default for kvm_irqchip__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_irqchip() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irqchip>(),
- 520usize,
- concat!("Size of: ", stringify!(kvm_irqchip))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irqchip>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_irqchip))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqchip>())).chip_id as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqchip),
- "::",
- stringify!(chip_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqchip>())).pad as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqchip),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqchip>())).chip as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqchip),
- "::",
- stringify!(chip)
- )
- );
-}
impl Default for kvm_irqchip {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_pit_config {
- pub flags: __u32,
- pub pad: [__u32; 15usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_pit_config() {
- assert_eq!(
- ::std::mem::size_of::<kvm_pit_config>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_pit_config))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_pit_config>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_pit_config))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pit_config>())).flags as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_config),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pit_config>())).pad as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_config),
- "::",
- stringify!(pad)
- )
- );
+ pub flags: u32,
+ pub pad: [u32; 15usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_skeys {
- pub start_gfn: __u64,
- pub count: __u64,
- pub skeydata_addr: __u64,
- pub flags: __u32,
- pub reserved: [__u32; 9usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_skeys() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_skeys>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_s390_skeys))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_skeys>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_skeys))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_skeys>())).start_gfn as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_skeys),
- "::",
- stringify!(start_gfn)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_skeys>())).count as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_skeys),
- "::",
- stringify!(count)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_skeys>())).skeydata_addr as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_skeys),
- "::",
- stringify!(skeydata_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_skeys>())).flags as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_skeys),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_skeys>())).reserved as *const _ as usize },
- 28usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_skeys),
- "::",
- stringify!(reserved)
- )
- );
-}
-#[doc = " kvm_s390_cmma_log - Used for CMMA migration."]
-#[doc = ""]
-#[doc = " Used both for input and output."]
-#[doc = ""]
-#[doc = " @start_gfn: Guest page number to start from."]
-#[doc = " @count: Size of the result buffer."]
-#[doc = " @flags: Control operation mode via KVM_S390_CMMA_* flags"]
-#[doc = " @remaining: Used with KVM_S390_GET_CMMA_BITS. Indicates how many dirty"]
-#[doc = " pages are still remaining."]
-#[doc = " @mask: Used with KVM_S390_SET_CMMA_BITS. Bitmap of bits to actually set"]
-#[doc = " in the PGSTE."]
-#[doc = " @values: Pointer to the values buffer."]
-#[doc = ""]
-#[doc = " Used in KVM_S390_{G,S}ET_CMMA_BITS ioctls."]
+ pub start_gfn: u64,
+ pub count: u64,
+ pub skeydata_addr: u64,
+ pub flags: u32,
+ pub reserved: [u32; 9usize],
+}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_s390_cmma_log {
- pub start_gfn: __u64,
- pub count: __u32,
- pub flags: __u32,
+ pub start_gfn: u64,
+ pub count: u32,
+ pub flags: u32,
pub __bindgen_anon_1: kvm_s390_cmma_log__bindgen_ty_1,
- pub values: __u64,
+ pub values: u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_s390_cmma_log__bindgen_ty_1 {
- pub remaining: __u64,
- pub mask: __u64,
- _bindgen_union_align: u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_cmma_log__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_cmma_log__bindgen_ty_1>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_s390_cmma_log__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_cmma_log__bindgen_ty_1>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_cmma_log__bindgen_ty_1))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_cmma_log__bindgen_ty_1>())).remaining as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_cmma_log__bindgen_ty_1),
- "::",
- stringify!(remaining)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_cmma_log__bindgen_ty_1>())).mask as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_cmma_log__bindgen_ty_1),
- "::",
- stringify!(mask)
- )
- );
+ pub remaining: u64,
+ pub mask: u64,
}
impl Default for kvm_s390_cmma_log__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_s390_cmma_log() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_cmma_log>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_s390_cmma_log))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_cmma_log>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_cmma_log))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_cmma_log>())).start_gfn as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_cmma_log),
- "::",
- stringify!(start_gfn)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_cmma_log>())).count as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_cmma_log),
- "::",
- stringify!(count)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_cmma_log>())).flags as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_cmma_log),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_cmma_log>())).values as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_cmma_log),
- "::",
- stringify!(values)
- )
- );
-}
impl Default for kvm_s390_cmma_log {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_hyperv_exit {
- pub type_: __u32,
- pub pad1: __u32,
+ pub type_: u32,
+ pub pad1: u32,
pub u: kvm_hyperv_exit__bindgen_ty_1,
}
#[repr(C)]
@@ -2350,271 +995,68 @@ pub struct kvm_hyperv_exit {
pub union kvm_hyperv_exit__bindgen_ty_1 {
pub synic: kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1,
pub hcall: kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2,
- _bindgen_union_align: [u64; 4usize],
+ pub syndbg: kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_3,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1 {
- pub msr: __u32,
- pub pad2: __u32,
- pub control: __u64,
- pub evt_page: __u64,
- pub msg_page: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1>(),
- 32usize,
- concat!(
- "Size of: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1>())).msr as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(msr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1>())).pad2 as *const _
- as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(pad2)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1>())).control
- as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(control)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1>())).evt_page
- as *const _ as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(evt_page)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1>())).msg_page
- as *const _ as usize
- },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(msg_page)
- )
- );
+ pub msr: u32,
+ pub pad2: u32,
+ pub control: u64,
+ pub evt_page: u64,
+ pub msg_page: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2 {
- pub input: __u64,
- pub result: __u64,
- pub params: [__u64; 2usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2() {
- assert_eq!(
- ::std::mem::size_of::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2>(),
- 32usize,
- concat!(
- "Size of: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2>())).input
- as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2),
- "::",
- stringify!(input)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2>())).result
- as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2),
- "::",
- stringify!(result)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2>())).params
- as *const _ as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2),
- "::",
- stringify!(params)
- )
- );
-}
-#[test]
-fn bindgen_test_layout_kvm_hyperv_exit__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_hyperv_exit__bindgen_ty_1>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_hyperv_exit__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_hyperv_exit__bindgen_ty_1>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_hyperv_exit__bindgen_ty_1))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1>())).synic as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1),
- "::",
- stringify!(synic)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1>())).hcall as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1),
- "::",
- stringify!(hcall)
- )
- );
+ pub input: u64,
+ pub result: u64,
+ pub params: [u64; 2usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_3 {
+ pub msr: u32,
+ pub pad2: u32,
+ pub control: u64,
+ pub status: u64,
+ pub send_page: u64,
+ pub recv_page: u64,
+ pub pending_page: u64,
}
impl Default for kvm_hyperv_exit__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_hyperv_exit() {
- assert_eq!(
- ::std::mem::size_of::<kvm_hyperv_exit>(),
- 40usize,
- concat!("Size of: ", stringify!(kvm_hyperv_exit))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_hyperv_exit>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_hyperv_exit))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_hyperv_exit>())).type_ as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit),
- "::",
- stringify!(type_)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_hyperv_exit>())).pad1 as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit),
- "::",
- stringify!(pad1)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_hyperv_exit>())).u as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit),
- "::",
- stringify!(u)
- )
- );
-}
impl Default for kvm_hyperv_exit {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_run {
- pub request_interrupt_window: __u8,
- pub immediate_exit: __u8,
- pub padding1: [__u8; 6usize],
- pub exit_reason: __u32,
- pub ready_for_interrupt_injection: __u8,
- pub if_flag: __u8,
- pub flags: __u16,
- pub cr8: __u64,
- pub apic_base: __u64,
+ pub request_interrupt_window: u8,
+ pub immediate_exit: u8,
+ pub padding1: [u8; 6usize],
+ pub exit_reason: u32,
+ pub ready_for_interrupt_injection: u8,
+ pub if_flag: u8,
+ pub flags: u16,
+ pub cr8: u64,
+ pub apic_base: u64,
pub __bindgen_anon_1: kvm_run__bindgen_ty_1,
- pub kvm_valid_regs: __u64,
- pub kvm_dirty_regs: __u64,
+ pub kvm_valid_regs: u64,
+ pub kvm_dirty_regs: u64,
pub s: kvm_run__bindgen_ty_2,
}
#[repr(C)]
@@ -2629,7 +1071,7 @@ pub union kvm_run__bindgen_ty_1 {
pub hypercall: kvm_run__bindgen_ty_1__bindgen_ty_7,
pub tpr_access: kvm_run__bindgen_ty_1__bindgen_ty_8,
pub s390_sieic: kvm_run__bindgen_ty_1__bindgen_ty_9,
- pub s390_reset_flags: __u64,
+ pub s390_reset_flags: u64,
pub s390_ucontrol: kvm_run__bindgen_ty_1__bindgen_ty_10,
pub dcr: kvm_run__bindgen_ty_1__bindgen_ty_11,
pub internal: kvm_run__bindgen_ty_1__bindgen_ty_12,
@@ -2641,1400 +1083,162 @@ pub union kvm_run__bindgen_ty_1 {
pub s390_stsi: kvm_run__bindgen_ty_1__bindgen_ty_18,
pub eoi: kvm_run__bindgen_ty_1__bindgen_ty_19,
pub hyperv: kvm_hyperv_exit,
+ pub arm_nisv: kvm_run__bindgen_ty_1__bindgen_ty_20,
+ pub msr: kvm_run__bindgen_ty_1__bindgen_ty_21,
pub padding: [::std::os::raw::c_char; 256usize],
- _bindgen_union_align: [u64; 32usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_1 {
- pub hardware_exit_reason: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_1>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_1>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_1>())).hardware_exit_reason
- as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(hardware_exit_reason)
- )
- );
+ pub hardware_exit_reason: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_2 {
- pub hardware_entry_failure_reason: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_2() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_2>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_2))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_2>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_2)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_2>()))
- .hardware_entry_failure_reason as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_2),
- "::",
- stringify!(hardware_entry_failure_reason)
- )
- );
+ pub hardware_entry_failure_reason: u64,
+ pub cpu: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_3 {
- pub exception: __u32,
- pub error_code: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_3() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_3>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_3))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_3>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_3)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_3>())).exception as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_3),
- "::",
- stringify!(exception)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_3>())).error_code as *const _
- as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_3),
- "::",
- stringify!(error_code)
- )
- );
+ pub exception: u32,
+ pub error_code: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_4 {
- pub direction: __u8,
- pub size: __u8,
- pub port: __u16,
- pub count: __u32,
- pub data_offset: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_4() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_4>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_4))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_4>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_4)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_4>())).direction as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_4),
- "::",
- stringify!(direction)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_4>())).size as *const _
- as usize
- },
- 1usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_4),
- "::",
- stringify!(size)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_4>())).port as *const _
- as usize
- },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_4),
- "::",
- stringify!(port)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_4>())).count as *const _
- as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_4),
- "::",
- stringify!(count)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_4>())).data_offset as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_4),
- "::",
- stringify!(data_offset)
- )
- );
+ pub direction: u8,
+ pub size: u8,
+ pub port: u16,
+ pub count: u32,
+ pub data_offset: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_5 {
pub arch: kvm_debug_exit_arch,
}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_5() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_5>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_5))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_5>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_5)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_5>())).arch as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_5),
- "::",
- stringify!(arch)
- )
- );
-}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_6 {
- pub phys_addr: __u64,
- pub data: [__u8; 8usize],
- pub len: __u32,
- pub is_write: __u8,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_6() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_6>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_6))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_6>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_6)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_6>())).phys_addr as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_6),
- "::",
- stringify!(phys_addr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_6>())).data as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_6),
- "::",
- stringify!(data)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_6>())).len as *const _ as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_6),
- "::",
- stringify!(len)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_6>())).is_write as *const _
- as usize
- },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_6),
- "::",
- stringify!(is_write)
- )
- );
+ pub phys_addr: u64,
+ pub data: [u8; 8usize],
+ pub len: u32,
+ pub is_write: u8,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_7 {
- pub nr: __u64,
- pub args: [__u64; 6usize],
- pub ret: __u64,
- pub longmode: __u32,
- pub pad: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_7() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_7>(),
- 72usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_7))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_7>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_7)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_7>())).nr as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_7),
- "::",
- stringify!(nr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_7>())).args as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_7),
- "::",
- stringify!(args)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_7>())).ret as *const _ as usize
- },
- 56usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_7),
- "::",
- stringify!(ret)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_7>())).longmode as *const _
- as usize
- },
- 64usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_7),
- "::",
- stringify!(longmode)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_7>())).pad as *const _ as usize
- },
- 68usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_7),
- "::",
- stringify!(pad)
- )
- );
+ pub nr: u64,
+ pub args: [u64; 6usize],
+ pub ret: u64,
+ pub longmode: u32,
+ pub pad: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_8 {
- pub rip: __u64,
- pub is_write: __u32,
- pub pad: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_8() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_8>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_8))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_8>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_8)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_8>())).rip as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_8),
- "::",
- stringify!(rip)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_8>())).is_write as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_8),
- "::",
- stringify!(is_write)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_8>())).pad as *const _ as usize
- },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_8),
- "::",
- stringify!(pad)
- )
- );
+ pub rip: u64,
+ pub is_write: u32,
+ pub pad: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_9 {
- pub icptcode: __u8,
- pub ipa: __u16,
- pub ipb: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_9() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_9>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_9))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_9>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_9)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_9>())).icptcode as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_9),
- "::",
- stringify!(icptcode)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_9>())).ipa as *const _ as usize
- },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_9),
- "::",
- stringify!(ipa)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_9>())).ipb as *const _ as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_9),
- "::",
- stringify!(ipb)
- )
- );
+ pub icptcode: u8,
+ pub ipa: u16,
+ pub ipb: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_10 {
- pub trans_exc_code: __u64,
- pub pgm_code: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_10() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_10>(),
- 16usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_10)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_10>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_10)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_10>())).trans_exc_code
- as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_10),
- "::",
- stringify!(trans_exc_code)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_10>())).pgm_code as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_10),
- "::",
- stringify!(pgm_code)
- )
- );
+ pub trans_exc_code: u64,
+ pub pgm_code: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_11 {
- pub dcrn: __u32,
- pub data: __u32,
- pub is_write: __u8,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_11() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_11>(),
- 12usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_11)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_11>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_11)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_11>())).dcrn as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_11),
- "::",
- stringify!(dcrn)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_11>())).data as *const _
- as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_11),
- "::",
- stringify!(data)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_11>())).is_write as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_11),
- "::",
- stringify!(is_write)
- )
- );
+ pub dcrn: u32,
+ pub data: u32,
+ pub is_write: u8,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_12 {
- pub suberror: __u32,
- pub ndata: __u32,
- pub data: [__u64; 16usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_12() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_12>(),
- 136usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_12)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_12>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_12)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_12>())).suberror as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_12),
- "::",
- stringify!(suberror)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_12>())).ndata as *const _
- as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_12),
- "::",
- stringify!(ndata)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_12>())).data as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_12),
- "::",
- stringify!(data)
- )
- );
+ pub suberror: u32,
+ pub ndata: u32,
+ pub data: [u64; 16usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_13 {
- pub gprs: [__u64; 32usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_13() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_13>(),
- 256usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_13)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_13>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_13)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_13>())).gprs as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_13),
- "::",
- stringify!(gprs)
- )
- );
+ pub gprs: [u64; 32usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_14 {
- pub nr: __u64,
- pub ret: __u64,
- pub args: [__u64; 9usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_14() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_14>(),
- 88usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_14)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_14>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_14)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_14>())).nr as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_14),
- "::",
- stringify!(nr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_14>())).ret as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_14),
- "::",
- stringify!(ret)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_14>())).args as *const _
- as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_14),
- "::",
- stringify!(args)
- )
- );
+ pub nr: u64,
+ pub ret: u64,
+ pub args: [u64; 9usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_15 {
- pub subchannel_id: __u16,
- pub subchannel_nr: __u16,
- pub io_int_parm: __u32,
- pub io_int_word: __u32,
- pub ipb: __u32,
- pub dequeued: __u8,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_15() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_15>(),
- 20usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_15>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_15>())).subchannel_id
- as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15),
- "::",
- stringify!(subchannel_id)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_15>())).subchannel_nr
- as *const _ as usize
- },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15),
- "::",
- stringify!(subchannel_nr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_15>())).io_int_parm as *const _
- as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15),
- "::",
- stringify!(io_int_parm)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_15>())).io_int_word as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15),
- "::",
- stringify!(io_int_word)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_15>())).ipb as *const _
- as usize
- },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15),
- "::",
- stringify!(ipb)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_15>())).dequeued as *const _
- as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15),
- "::",
- stringify!(dequeued)
- )
- );
+ pub subchannel_id: u16,
+ pub subchannel_nr: u16,
+ pub io_int_parm: u32,
+ pub io_int_word: u32,
+ pub ipb: u32,
+ pub dequeued: u8,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_16 {
- pub epr: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_16() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_16>(),
- 4usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_16)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_16>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_16)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_16>())).epr as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_16),
- "::",
- stringify!(epr)
- )
- );
+ pub epr: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_17 {
- pub type_: __u32,
- pub flags: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_17() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_17>(),
- 16usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_17)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_17>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_17)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_17>())).type_ as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_17),
- "::",
- stringify!(type_)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_17>())).flags as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_17),
- "::",
- stringify!(flags)
- )
- );
+ pub type_: u32,
+ pub flags: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_18 {
- pub addr: __u64,
- pub ar: __u8,
- pub reserved: __u8,
- pub fc: __u8,
- pub sel1: __u8,
- pub sel2: __u16,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_18() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_18>(),
- 16usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_18>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_18>())).addr as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18),
- "::",
- stringify!(addr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_18>())).ar as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18),
- "::",
- stringify!(ar)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_18>())).reserved as *const _
- as usize
- },
- 9usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18),
- "::",
- stringify!(reserved)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_18>())).fc as *const _ as usize
- },
- 10usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18),
- "::",
- stringify!(fc)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_18>())).sel1 as *const _
- as usize
- },
- 11usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18),
- "::",
- stringify!(sel1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_18>())).sel2 as *const _
- as usize
- },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18),
- "::",
- stringify!(sel2)
- )
- );
+ pub addr: u64,
+ pub ar: u8,
+ pub reserved: u8,
+ pub fc: u8,
+ pub sel1: u8,
+ pub sel2: u16,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_19 {
- pub vector: __u8,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_19() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_19>(),
- 1usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_19)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_19>(),
- 1usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_19)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_19>())).vector as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_19),
- "::",
- stringify!(vector)
- )
- );
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1>(),
- 256usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_run__bindgen_ty_1))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).hw as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(hw)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).fail_entry as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(fail_entry)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).ex as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(ex)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).io as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(io)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).debug as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(debug)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).mmio as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(mmio)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).hypercall as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(hypercall)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).tpr_access as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(tpr_access)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).s390_sieic as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(s390_sieic)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).s390_reset_flags as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(s390_reset_flags)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).s390_ucontrol as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(s390_ucontrol)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).dcr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(dcr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).internal as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(internal)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).osi as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(osi)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).papr_hcall as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(papr_hcall)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).s390_tsch as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(s390_tsch)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).epr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(epr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).system_event as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(system_event)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).s390_stsi as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(s390_stsi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).eoi as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(eoi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).hyperv as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(hyperv)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).padding as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(padding)
- )
- );
+ pub vector: u8,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct kvm_run__bindgen_ty_1__bindgen_ty_20 {
+ pub esr_iss: u64,
+ pub fault_ipa: u64,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct kvm_run__bindgen_ty_1__bindgen_ty_21 {
+ pub error: u8,
+ pub pad: [u8; 7usize],
+ pub reason: u32,
+ pub index: u32,
+ pub data: u64,
}
impl Default for kvm_run__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
@@ -4042,1577 +1246,318 @@ impl Default for kvm_run__bindgen_ty_1 {
pub union kvm_run__bindgen_ty_2 {
pub regs: kvm_sync_regs,
pub padding: [::std::os::raw::c_char; 2048usize],
- _bindgen_union_align: [u64; 256usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_2() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_2>(),
- 2048usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_2))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_2>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_run__bindgen_ty_2))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_2>())).regs as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_2),
- "::",
- stringify!(regs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_2>())).padding as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_2),
- "::",
- stringify!(padding)
- )
- );
}
impl Default for kvm_run__bindgen_ty_2 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-#[test]
-fn bindgen_test_layout_kvm_run() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run>(),
- 2352usize,
- concat!("Size of: ", stringify!(kvm_run))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_run))
- );
- assert_eq!(
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
- &(*(::std::ptr::null::<kvm_run>())).request_interrupt_window as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(request_interrupt_window)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).immediate_exit as *const _ as usize },
- 1usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(immediate_exit)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).padding1 as *const _ as usize },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(padding1)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).exit_reason as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(exit_reason)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run>())).ready_for_interrupt_injection as *const _ as usize
- },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(ready_for_interrupt_injection)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).if_flag as *const _ as usize },
- 13usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(if_flag)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).flags as *const _ as usize },
- 14usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).cr8 as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(cr8)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).apic_base as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(apic_base)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).kvm_valid_regs as *const _ as usize },
- 288usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(kvm_valid_regs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).kvm_dirty_regs as *const _ as usize },
- 296usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(kvm_dirty_regs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).s as *const _ as usize },
- 304usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(s)
- )
- );
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
}
impl Default for kvm_run {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_coalesced_mmio_zone {
- pub addr: __u64,
- pub size: __u32,
+ pub addr: u64,
+ pub size: u32,
pub __bindgen_anon_1: kvm_coalesced_mmio_zone__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_coalesced_mmio_zone__bindgen_ty_1 {
- pub pad: __u32,
- pub pio: __u32,
- _bindgen_union_align: u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_coalesced_mmio_zone__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_coalesced_mmio_zone__bindgen_ty_1>(),
- 4usize,
- concat!(
- "Size of: ",
- stringify!(kvm_coalesced_mmio_zone__bindgen_ty_1)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_coalesced_mmio_zone__bindgen_ty_1>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_coalesced_mmio_zone__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_coalesced_mmio_zone__bindgen_ty_1>())).pad as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio_zone__bindgen_ty_1),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_coalesced_mmio_zone__bindgen_ty_1>())).pio as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio_zone__bindgen_ty_1),
- "::",
- stringify!(pio)
- )
- );
+ pub pad: u32,
+ pub pio: u32,
}
impl Default for kvm_coalesced_mmio_zone__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_coalesced_mmio_zone() {
- assert_eq!(
- ::std::mem::size_of::<kvm_coalesced_mmio_zone>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_coalesced_mmio_zone))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_coalesced_mmio_zone>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_coalesced_mmio_zone))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_coalesced_mmio_zone>())).addr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio_zone),
- "::",
- stringify!(addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_coalesced_mmio_zone>())).size as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio_zone),
- "::",
- stringify!(size)
- )
- );
-}
impl Default for kvm_coalesced_mmio_zone {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_coalesced_mmio {
- pub phys_addr: __u64,
- pub len: __u32,
+ pub phys_addr: u64,
+ pub len: u32,
pub __bindgen_anon_1: kvm_coalesced_mmio__bindgen_ty_1,
- pub data: [__u8; 8usize],
+ pub data: [u8; 8usize],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_coalesced_mmio__bindgen_ty_1 {
- pub pad: __u32,
- pub pio: __u32,
- _bindgen_union_align: u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_coalesced_mmio__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_coalesced_mmio__bindgen_ty_1>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_coalesced_mmio__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_coalesced_mmio__bindgen_ty_1>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_coalesced_mmio__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_coalesced_mmio__bindgen_ty_1>())).pad as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio__bindgen_ty_1),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_coalesced_mmio__bindgen_ty_1>())).pio as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio__bindgen_ty_1),
- "::",
- stringify!(pio)
- )
- );
+ pub pad: u32,
+ pub pio: u32,
}
impl Default for kvm_coalesced_mmio__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_coalesced_mmio() {
- assert_eq!(
- ::std::mem::size_of::<kvm_coalesced_mmio>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_coalesced_mmio))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_coalesced_mmio>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_coalesced_mmio))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_coalesced_mmio>())).phys_addr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio),
- "::",
- stringify!(phys_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_coalesced_mmio>())).len as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio),
- "::",
- stringify!(len)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_coalesced_mmio>())).data as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio),
- "::",
- stringify!(data)
- )
- );
-}
impl Default for kvm_coalesced_mmio {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
pub struct kvm_coalesced_mmio_ring {
- pub first: __u32,
- pub last: __u32,
+ pub first: u32,
+ pub last: u32,
pub coalesced_mmio: __IncompleteArrayField<kvm_coalesced_mmio>,
}
-#[test]
-fn bindgen_test_layout_kvm_coalesced_mmio_ring() {
- assert_eq!(
- ::std::mem::size_of::<kvm_coalesced_mmio_ring>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_coalesced_mmio_ring))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_coalesced_mmio_ring>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_coalesced_mmio_ring))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_coalesced_mmio_ring>())).first as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio_ring),
- "::",
- stringify!(first)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_coalesced_mmio_ring>())).last as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio_ring),
- "::",
- stringify!(last)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_coalesced_mmio_ring>())).coalesced_mmio as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio_ring),
- "::",
- stringify!(coalesced_mmio)
- )
- );
-}
impl Default for kvm_coalesced_mmio_ring {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_translation {
- pub linear_address: __u64,
- pub physical_address: __u64,
- pub valid: __u8,
- pub writeable: __u8,
- pub usermode: __u8,
- pub pad: [__u8; 5usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_translation() {
- assert_eq!(
- ::std::mem::size_of::<kvm_translation>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_translation))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_translation>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_translation))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_translation>())).linear_address as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_translation),
- "::",
- stringify!(linear_address)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_translation>())).physical_address as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_translation),
- "::",
- stringify!(physical_address)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_translation>())).valid as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_translation),
- "::",
- stringify!(valid)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_translation>())).writeable as *const _ as usize },
- 17usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_translation),
- "::",
- stringify!(writeable)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_translation>())).usermode as *const _ as usize },
- 18usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_translation),
- "::",
- stringify!(usermode)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_translation>())).pad as *const _ as usize },
- 19usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_translation),
- "::",
- stringify!(pad)
- )
- );
+ pub linear_address: u64,
+ pub physical_address: u64,
+ pub valid: u8,
+ pub writeable: u8,
+ pub usermode: u8,
+ pub pad: [u8; 5usize],
}
#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
+#[derive(Copy, Clone)]
pub struct kvm_s390_mem_op {
- pub gaddr: __u64,
- pub flags: __u64,
- pub size: __u32,
- pub op: __u32,
- pub buf: __u64,
- pub ar: __u8,
- pub reserved: [__u8; 31usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_mem_op() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_mem_op>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_s390_mem_op))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_mem_op>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_mem_op))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mem_op>())).gaddr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mem_op),
- "::",
- stringify!(gaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mem_op>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mem_op),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mem_op>())).size as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mem_op),
- "::",
- stringify!(size)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mem_op>())).op as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mem_op),
- "::",
- stringify!(op)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mem_op>())).buf as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mem_op),
- "::",
- stringify!(buf)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mem_op>())).ar as *const _ as usize },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mem_op),
- "::",
- stringify!(ar)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mem_op>())).reserved as *const _ as usize },
- 33usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mem_op),
- "::",
- stringify!(reserved)
- )
- );
+ pub gaddr: u64,
+ pub flags: u64,
+ pub size: u32,
+ pub op: u32,
+ pub buf: u64,
+ pub __bindgen_anon_1: kvm_s390_mem_op__bindgen_ty_1,
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union kvm_s390_mem_op__bindgen_ty_1 {
+ pub ar: u8,
+ pub sida_offset: u32,
+ pub reserved: [u8; 32usize],
+}
+impl Default for kvm_s390_mem_op__bindgen_ty_1 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+impl Default for kvm_s390_mem_op {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_interrupt {
- pub irq: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_interrupt() {
- assert_eq!(
- ::std::mem::size_of::<kvm_interrupt>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_interrupt))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_interrupt>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_interrupt))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_interrupt>())).irq as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_interrupt),
- "::",
- stringify!(irq)
- )
- );
+ pub irq: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_dirty_log {
- pub slot: __u32,
- pub padding1: __u32,
+ pub slot: u32,
+ pub padding1: u32,
pub __bindgen_anon_1: kvm_dirty_log__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_dirty_log__bindgen_ty_1 {
pub dirty_bitmap: *mut ::std::os::raw::c_void,
- pub padding2: __u64,
- _bindgen_union_align: u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_dirty_log__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_dirty_log__bindgen_ty_1>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_dirty_log__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_dirty_log__bindgen_ty_1>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_dirty_log__bindgen_ty_1))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_dirty_log__bindgen_ty_1>())).dirty_bitmap as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dirty_log__bindgen_ty_1),
- "::",
- stringify!(dirty_bitmap)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_dirty_log__bindgen_ty_1>())).padding2 as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dirty_log__bindgen_ty_1),
- "::",
- stringify!(padding2)
- )
- );
+ pub padding2: u64,
}
impl Default for kvm_dirty_log__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_dirty_log() {
- assert_eq!(
- ::std::mem::size_of::<kvm_dirty_log>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_dirty_log))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_dirty_log>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_dirty_log))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_dirty_log>())).slot as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dirty_log),
- "::",
- stringify!(slot)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_dirty_log>())).padding1 as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dirty_log),
- "::",
- stringify!(padding1)
- )
- );
-}
impl Default for kvm_dirty_log {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_clear_dirty_log {
- pub slot: __u32,
- pub num_pages: __u32,
- pub first_page: __u64,
+ pub slot: u32,
+ pub num_pages: u32,
+ pub first_page: u64,
pub __bindgen_anon_1: kvm_clear_dirty_log__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_clear_dirty_log__bindgen_ty_1 {
pub dirty_bitmap: *mut ::std::os::raw::c_void,
- pub padding2: __u64,
- _bindgen_union_align: u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_clear_dirty_log__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_clear_dirty_log__bindgen_ty_1>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_clear_dirty_log__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_clear_dirty_log__bindgen_ty_1>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_clear_dirty_log__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_clear_dirty_log__bindgen_ty_1>())).dirty_bitmap as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clear_dirty_log__bindgen_ty_1),
- "::",
- stringify!(dirty_bitmap)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_clear_dirty_log__bindgen_ty_1>())).padding2 as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clear_dirty_log__bindgen_ty_1),
- "::",
- stringify!(padding2)
- )
- );
+ pub padding2: u64,
}
impl Default for kvm_clear_dirty_log__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_clear_dirty_log() {
- assert_eq!(
- ::std::mem::size_of::<kvm_clear_dirty_log>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_clear_dirty_log))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_clear_dirty_log>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_clear_dirty_log))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_clear_dirty_log>())).slot as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clear_dirty_log),
- "::",
- stringify!(slot)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_clear_dirty_log>())).num_pages as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clear_dirty_log),
- "::",
- stringify!(num_pages)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_clear_dirty_log>())).first_page as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clear_dirty_log),
- "::",
- stringify!(first_page)
- )
- );
-}
impl Default for kvm_clear_dirty_log {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct kvm_signal_mask {
- pub len: __u32,
- pub sigset: __IncompleteArrayField<__u8>,
-}
-#[test]
-fn bindgen_test_layout_kvm_signal_mask() {
- assert_eq!(
- ::std::mem::size_of::<kvm_signal_mask>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_signal_mask))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_signal_mask>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_signal_mask))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_signal_mask>())).len as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_signal_mask),
- "::",
- stringify!(len)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_signal_mask>())).sigset as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_signal_mask),
- "::",
- stringify!(sigset)
- )
- );
+ pub len: u32,
+ pub sigset: __IncompleteArrayField<u8>,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_tpr_access_ctl {
- pub enabled: __u32,
- pub flags: __u32,
- pub reserved: [__u32; 8usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_tpr_access_ctl() {
- assert_eq!(
- ::std::mem::size_of::<kvm_tpr_access_ctl>(),
- 40usize,
- concat!("Size of: ", stringify!(kvm_tpr_access_ctl))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_tpr_access_ctl>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_tpr_access_ctl))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_tpr_access_ctl>())).enabled as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_tpr_access_ctl),
- "::",
- stringify!(enabled)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_tpr_access_ctl>())).flags as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_tpr_access_ctl),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_tpr_access_ctl>())).reserved as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_tpr_access_ctl),
- "::",
- stringify!(reserved)
- )
- );
+ pub enabled: u32,
+ pub flags: u32,
+ pub reserved: [u32; 8usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_vapic_addr {
- pub vapic_addr: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_vapic_addr() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vapic_addr>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_vapic_addr))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vapic_addr>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_vapic_addr))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vapic_addr>())).vapic_addr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vapic_addr),
- "::",
- stringify!(vapic_addr)
- )
- );
+ pub vapic_addr: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_mp_state {
- pub mp_state: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_mp_state() {
- assert_eq!(
- ::std::mem::size_of::<kvm_mp_state>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_mp_state))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_mp_state>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_mp_state))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_mp_state>())).mp_state as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_mp_state),
- "::",
- stringify!(mp_state)
- )
- );
+ pub mp_state: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_psw {
- pub mask: __u64,
- pub addr: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_psw() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_psw>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_s390_psw))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_psw>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_psw))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_psw>())).mask as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_psw),
- "::",
- stringify!(mask)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_psw>())).addr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_psw),
- "::",
- stringify!(addr)
- )
- );
+ pub mask: u64,
+ pub addr: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_interrupt {
- pub type_: __u32,
- pub parm: __u32,
- pub parm64: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_interrupt() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_interrupt>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_s390_interrupt))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_interrupt>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_interrupt))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_interrupt>())).type_ as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_interrupt),
- "::",
- stringify!(type_)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_interrupt>())).parm as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_interrupt),
- "::",
- stringify!(parm)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_interrupt>())).parm64 as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_interrupt),
- "::",
- stringify!(parm64)
- )
- );
+ pub type_: u32,
+ pub parm: u32,
+ pub parm64: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_io_info {
- pub subchannel_id: __u16,
- pub subchannel_nr: __u16,
- pub io_int_parm: __u32,
- pub io_int_word: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_io_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_io_info>(),
- 12usize,
- concat!("Size of: ", stringify!(kvm_s390_io_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_io_info>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_s390_io_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_io_info>())).subchannel_id as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_io_info),
- "::",
- stringify!(subchannel_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_io_info>())).subchannel_nr as *const _ as usize },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_io_info),
- "::",
- stringify!(subchannel_nr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_io_info>())).io_int_parm as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_io_info),
- "::",
- stringify!(io_int_parm)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_io_info>())).io_int_word as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_io_info),
- "::",
- stringify!(io_int_word)
- )
- );
+ pub subchannel_id: u16,
+ pub subchannel_nr: u16,
+ pub io_int_parm: u32,
+ pub io_int_word: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_ext_info {
- pub ext_params: __u32,
- pub pad: __u32,
- pub ext_params2: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_ext_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_ext_info>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_s390_ext_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_ext_info>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_ext_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_ext_info>())).ext_params as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_ext_info),
- "::",
- stringify!(ext_params)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_ext_info>())).pad as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_ext_info),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_ext_info>())).ext_params2 as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_ext_info),
- "::",
- stringify!(ext_params2)
- )
- );
+ pub ext_params: u32,
+ pub pad: u32,
+ pub ext_params2: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_pgm_info {
- pub trans_exc_code: __u64,
- pub mon_code: __u64,
- pub per_address: __u64,
- pub data_exc_code: __u32,
- pub code: __u16,
- pub mon_class_nr: __u16,
- pub per_code: __u8,
- pub per_atmid: __u8,
- pub exc_access_id: __u8,
- pub per_access_id: __u8,
- pub op_access_id: __u8,
- pub flags: __u8,
- pub pad: [__u8; 2usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_pgm_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_pgm_info>(),
- 40usize,
- concat!("Size of: ", stringify!(kvm_s390_pgm_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_pgm_info>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_pgm_info))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_pgm_info>())).trans_exc_code as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(trans_exc_code)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).mon_code as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(mon_code)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).per_address as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(per_address)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).data_exc_code as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(data_exc_code)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).code as *const _ as usize },
- 28usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(code)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).mon_class_nr as *const _ as usize },
- 30usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(mon_class_nr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).per_code as *const _ as usize },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(per_code)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).per_atmid as *const _ as usize },
- 33usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(per_atmid)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).exc_access_id as *const _ as usize },
- 34usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(exc_access_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).per_access_id as *const _ as usize },
- 35usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(per_access_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).op_access_id as *const _ as usize },
- 36usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(op_access_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).flags as *const _ as usize },
- 37usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).pad as *const _ as usize },
- 38usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(pad)
- )
- );
+ pub trans_exc_code: u64,
+ pub mon_code: u64,
+ pub per_address: u64,
+ pub data_exc_code: u32,
+ pub code: u16,
+ pub mon_class_nr: u16,
+ pub per_code: u8,
+ pub per_atmid: u8,
+ pub exc_access_id: u8,
+ pub per_access_id: u8,
+ pub op_access_id: u8,
+ pub flags: u8,
+ pub pad: [u8; 2usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_prefix_info {
- pub address: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_prefix_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_prefix_info>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_s390_prefix_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_prefix_info>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_s390_prefix_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_prefix_info>())).address as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_prefix_info),
- "::",
- stringify!(address)
- )
- );
+ pub address: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_extcall_info {
- pub code: __u16,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_extcall_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_extcall_info>(),
- 2usize,
- concat!("Size of: ", stringify!(kvm_s390_extcall_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_extcall_info>(),
- 2usize,
- concat!("Alignment of ", stringify!(kvm_s390_extcall_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_extcall_info>())).code as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_extcall_info),
- "::",
- stringify!(code)
- )
- );
+ pub code: u16,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_emerg_info {
- pub code: __u16,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_emerg_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_emerg_info>(),
- 2usize,
- concat!("Size of: ", stringify!(kvm_s390_emerg_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_emerg_info>(),
- 2usize,
- concat!("Alignment of ", stringify!(kvm_s390_emerg_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_emerg_info>())).code as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_emerg_info),
- "::",
- stringify!(code)
- )
- );
+ pub code: u16,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_stop_info {
- pub flags: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_stop_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_stop_info>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_s390_stop_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_stop_info>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_s390_stop_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_stop_info>())).flags as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_stop_info),
- "::",
- stringify!(flags)
- )
- );
+ pub flags: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_mchk_info {
- pub cr14: __u64,
- pub mcic: __u64,
- pub failing_storage_address: __u64,
- pub ext_damage_code: __u32,
- pub pad: __u32,
- pub fixed_logout: [__u8; 16usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_mchk_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_mchk_info>(),
- 48usize,
- concat!("Size of: ", stringify!(kvm_s390_mchk_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_mchk_info>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_mchk_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mchk_info>())).cr14 as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mchk_info),
- "::",
- stringify!(cr14)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mchk_info>())).mcic as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mchk_info),
- "::",
- stringify!(mcic)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_mchk_info>())).failing_storage_address as *const _
- as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mchk_info),
- "::",
- stringify!(failing_storage_address)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_mchk_info>())).ext_damage_code as *const _ as usize
- },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mchk_info),
- "::",
- stringify!(ext_damage_code)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mchk_info>())).pad as *const _ as usize },
- 28usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mchk_info),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mchk_info>())).fixed_logout as *const _ as usize },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mchk_info),
- "::",
- stringify!(fixed_logout)
- )
- );
+ pub cr14: u64,
+ pub mcic: u64,
+ pub failing_storage_address: u64,
+ pub ext_damage_code: u32,
+ pub pad: u32,
+ pub fixed_logout: [u8; 16usize],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_s390_irq {
- pub type_: __u64,
+ pub type_: u64,
pub u: kvm_s390_irq__bindgen_ty_1,
}
#[repr(C)]
@@ -5627,983 +1572,188 @@ pub union kvm_s390_irq__bindgen_ty_1 {
pub stop: kvm_s390_stop_info,
pub mchk: kvm_s390_mchk_info,
pub reserved: [::std::os::raw::c_char; 64usize],
- _bindgen_union_align: [u64; 8usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_irq__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_irq__bindgen_ty_1>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_s390_irq__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_irq__bindgen_ty_1>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_irq__bindgen_ty_1))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).io as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(io)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).ext as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(ext)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).pgm as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(pgm)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).emerg as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(emerg)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).extcall as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(extcall)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).prefix as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(prefix)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).stop as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(stop)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).mchk as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(mchk)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).reserved as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(reserved)
- )
- );
}
impl Default for kvm_s390_irq__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_s390_irq() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_irq>(),
- 72usize,
- concat!("Size of: ", stringify!(kvm_s390_irq))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_irq>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_irq))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq>())).type_ as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq),
- "::",
- stringify!(type_)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq>())).u as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq),
- "::",
- stringify!(u)
- )
- );
-}
impl Default for kvm_s390_irq {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_irq_state {
- pub buf: __u64,
- pub flags: __u32,
- pub len: __u32,
- pub reserved: [__u32; 4usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_irq_state() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_irq_state>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_s390_irq_state))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_irq_state>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_irq_state))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq_state>())).buf as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq_state),
- "::",
- stringify!(buf)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq_state>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq_state),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq_state>())).len as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq_state),
- "::",
- stringify!(len)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq_state>())).reserved as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq_state),
- "::",
- stringify!(reserved)
- )
- );
+ pub buf: u64,
+ pub flags: u32,
+ pub len: u32,
+ pub reserved: [u32; 4usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_guest_debug {
- pub control: __u32,
- pub pad: __u32,
+ pub control: u32,
+ pub pad: u32,
pub arch: kvm_guest_debug_arch,
}
-#[test]
-fn bindgen_test_layout_kvm_guest_debug() {
- assert_eq!(
- ::std::mem::size_of::<kvm_guest_debug>(),
- 520usize,
- concat!("Size of: ", stringify!(kvm_guest_debug))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_guest_debug>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_guest_debug))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_guest_debug>())).control as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_guest_debug),
- "::",
- stringify!(control)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_guest_debug>())).pad as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_guest_debug),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_guest_debug>())).arch as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_guest_debug),
- "::",
- stringify!(arch)
- )
- );
-}
-pub const kvm_ioeventfd_flag_nr_datamatch: _bindgen_ty_1 = 0;
-pub const kvm_ioeventfd_flag_nr_pio: _bindgen_ty_1 = 1;
-pub const kvm_ioeventfd_flag_nr_deassign: _bindgen_ty_1 = 2;
-pub const kvm_ioeventfd_flag_nr_virtio_ccw_notify: _bindgen_ty_1 = 3;
-pub const kvm_ioeventfd_flag_nr_fast_mmio: _bindgen_ty_1 = 4;
-pub const kvm_ioeventfd_flag_nr_max: _bindgen_ty_1 = 5;
-pub type _bindgen_ty_1 = u32;
+pub const kvm_ioeventfd_flag_nr_datamatch: ::std::os::raw::c_uint = 0;
+pub const kvm_ioeventfd_flag_nr_pio: ::std::os::raw::c_uint = 1;
+pub const kvm_ioeventfd_flag_nr_deassign: ::std::os::raw::c_uint = 2;
+pub const kvm_ioeventfd_flag_nr_virtio_ccw_notify: ::std::os::raw::c_uint = 3;
+pub const kvm_ioeventfd_flag_nr_fast_mmio: ::std::os::raw::c_uint = 4;
+pub const kvm_ioeventfd_flag_nr_max: ::std::os::raw::c_uint = 5;
+pub type _bindgen_ty_1 = ::std::os::raw::c_uint;
#[repr(C)]
-#[derive(Copy, Clone)]
+#[derive(Debug, Copy, Clone)]
pub struct kvm_ioeventfd {
- pub datamatch: __u64,
- pub addr: __u64,
- pub len: __u32,
- pub fd: __s32,
- pub flags: __u32,
- pub pad: [__u8; 36usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_ioeventfd() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ioeventfd>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_ioeventfd))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ioeventfd>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_ioeventfd))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioeventfd>())).datamatch as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioeventfd),
- "::",
- stringify!(datamatch)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioeventfd>())).addr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioeventfd),
- "::",
- stringify!(addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioeventfd>())).len as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioeventfd),
- "::",
- stringify!(len)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioeventfd>())).fd as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioeventfd),
- "::",
- stringify!(fd)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioeventfd>())).flags as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioeventfd),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioeventfd>())).pad as *const _ as usize },
- 28usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioeventfd),
- "::",
- stringify!(pad)
- )
- );
+ pub datamatch: u64,
+ pub addr: u64,
+ pub len: u32,
+ pub fd: i32,
+ pub flags: u32,
+ pub pad: [u8; 36usize],
}
impl Default for kvm_ioeventfd {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
-#[derive(Copy, Clone)]
+#[derive(Debug, Copy, Clone)]
pub struct kvm_enable_cap {
- pub cap: __u32,
- pub flags: __u32,
- pub args: [__u64; 4usize],
- pub pad: [__u8; 64usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_enable_cap() {
- assert_eq!(
- ::std::mem::size_of::<kvm_enable_cap>(),
- 104usize,
- concat!("Size of: ", stringify!(kvm_enable_cap))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_enable_cap>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_enable_cap))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_enable_cap>())).cap as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_enable_cap),
- "::",
- stringify!(cap)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_enable_cap>())).flags as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_enable_cap),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_enable_cap>())).args as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_enable_cap),
- "::",
- stringify!(args)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_enable_cap>())).pad as *const _ as usize },
- 40usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_enable_cap),
- "::",
- stringify!(pad)
- )
- );
+ pub cap: u32,
+ pub flags: u32,
+ pub args: [u64; 4usize],
+ pub pad: [u8; 64usize],
}
impl Default for kvm_enable_cap {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
-#[derive(Copy, Clone)]
+#[derive(Debug, Copy, Clone)]
pub struct kvm_ppc_pvinfo {
- pub flags: __u32,
- pub hcall: [__u32; 4usize],
- pub pad: [__u8; 108usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_ppc_pvinfo() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ppc_pvinfo>(),
- 128usize,
- concat!("Size of: ", stringify!(kvm_ppc_pvinfo))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ppc_pvinfo>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_ppc_pvinfo))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_pvinfo>())).flags as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_pvinfo),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_pvinfo>())).hcall as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_pvinfo),
- "::",
- stringify!(hcall)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_pvinfo>())).pad as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_pvinfo),
- "::",
- stringify!(pad)
- )
- );
+ pub flags: u32,
+ pub hcall: [u32; 4usize],
+ pub pad: [u8; 108usize],
}
impl Default for kvm_ppc_pvinfo {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_ppc_one_page_size {
- pub page_shift: __u32,
- pub pte_enc: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_ppc_one_page_size() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ppc_one_page_size>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_ppc_one_page_size))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ppc_one_page_size>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_ppc_one_page_size))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_ppc_one_page_size>())).page_shift as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_one_page_size),
- "::",
- stringify!(page_shift)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_one_page_size>())).pte_enc as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_one_page_size),
- "::",
- stringify!(pte_enc)
- )
- );
+ pub page_shift: u32,
+ pub pte_enc: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_ppc_one_seg_page_size {
- pub page_shift: __u32,
- pub slb_enc: __u32,
+ pub page_shift: u32,
+ pub slb_enc: u32,
pub enc: [kvm_ppc_one_page_size; 8usize],
}
-#[test]
-fn bindgen_test_layout_kvm_ppc_one_seg_page_size() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ppc_one_seg_page_size>(),
- 72usize,
- concat!("Size of: ", stringify!(kvm_ppc_one_seg_page_size))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ppc_one_seg_page_size>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_ppc_one_seg_page_size))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_ppc_one_seg_page_size>())).page_shift as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_one_seg_page_size),
- "::",
- stringify!(page_shift)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_ppc_one_seg_page_size>())).slb_enc as *const _ as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_one_seg_page_size),
- "::",
- stringify!(slb_enc)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_one_seg_page_size>())).enc as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_one_seg_page_size),
- "::",
- stringify!(enc)
- )
- );
-}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_ppc_smmu_info {
- pub flags: __u64,
- pub slb_size: __u32,
- pub data_keys: __u16,
- pub instr_keys: __u16,
+ pub flags: u64,
+ pub slb_size: u32,
+ pub data_keys: u16,
+ pub instr_keys: u16,
pub sps: [kvm_ppc_one_seg_page_size; 8usize],
}
-#[test]
-fn bindgen_test_layout_kvm_ppc_smmu_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ppc_smmu_info>(),
- 592usize,
- concat!("Size of: ", stringify!(kvm_ppc_smmu_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ppc_smmu_info>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_ppc_smmu_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_smmu_info>())).flags as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_smmu_info),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_smmu_info>())).slb_size as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_smmu_info),
- "::",
- stringify!(slb_size)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_smmu_info>())).data_keys as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_smmu_info),
- "::",
- stringify!(data_keys)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_smmu_info>())).instr_keys as *const _ as usize },
- 14usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_smmu_info),
- "::",
- stringify!(instr_keys)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_smmu_info>())).sps as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_smmu_info),
- "::",
- stringify!(sps)
- )
- );
-}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_ppc_resize_hpt {
- pub flags: __u64,
- pub shift: __u32,
- pub pad: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_ppc_resize_hpt() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ppc_resize_hpt>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_ppc_resize_hpt))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ppc_resize_hpt>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_ppc_resize_hpt))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_resize_hpt>())).flags as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_resize_hpt),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_resize_hpt>())).shift as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_resize_hpt),
- "::",
- stringify!(shift)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_resize_hpt>())).pad as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_resize_hpt),
- "::",
- stringify!(pad)
- )
- );
+ pub flags: u64,
+ pub shift: u32,
+ pub pad: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_irq_routing_irqchip {
- pub irqchip: __u32,
- pub pin: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing_irqchip() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing_irqchip>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_irq_routing_irqchip))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing_irqchip>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_irq_routing_irqchip))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_irqchip>())).irqchip as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_irqchip),
- "::",
- stringify!(irqchip)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_irqchip>())).pin as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_irqchip),
- "::",
- stringify!(pin)
- )
- );
+ pub irqchip: u32,
+ pub pin: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_irq_routing_msi {
- pub address_lo: __u32,
- pub address_hi: __u32,
- pub data: __u32,
+ pub address_lo: u32,
+ pub address_hi: u32,
+ pub data: u32,
pub __bindgen_anon_1: kvm_irq_routing_msi__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_irq_routing_msi__bindgen_ty_1 {
- pub pad: __u32,
- pub devid: __u32,
- _bindgen_union_align: u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing_msi__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing_msi__bindgen_ty_1>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_irq_routing_msi__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing_msi__bindgen_ty_1>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_irq_routing_msi__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_msi__bindgen_ty_1>())).pad as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_msi__bindgen_ty_1),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_msi__bindgen_ty_1>())).devid as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_msi__bindgen_ty_1),
- "::",
- stringify!(devid)
- )
- );
+ pub pad: u32,
+ pub devid: u32,
}
impl Default for kvm_irq_routing_msi__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing_msi() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing_msi>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_irq_routing_msi))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing_msi>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_irq_routing_msi))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_msi>())).address_lo as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_msi),
- "::",
- stringify!(address_lo)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_msi>())).address_hi as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_msi),
- "::",
- stringify!(address_hi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_msi>())).data as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_msi),
- "::",
- stringify!(data)
- )
- );
-}
impl Default for kvm_irq_routing_msi {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_irq_routing_s390_adapter {
- pub ind_addr: __u64,
- pub summary_addr: __u64,
- pub ind_offset: __u64,
- pub summary_offset: __u32,
- pub adapter_id: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing_s390_adapter() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing_s390_adapter>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_irq_routing_s390_adapter))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing_s390_adapter>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_irq_routing_s390_adapter))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_s390_adapter>())).ind_addr as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_s390_adapter),
- "::",
- stringify!(ind_addr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_s390_adapter>())).summary_addr as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_s390_adapter),
- "::",
- stringify!(summary_addr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_s390_adapter>())).ind_offset as *const _ as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_s390_adapter),
- "::",
- stringify!(ind_offset)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_s390_adapter>())).summary_offset as *const _
- as usize
- },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_s390_adapter),
- "::",
- stringify!(summary_offset)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_s390_adapter>())).adapter_id as *const _ as usize
- },
- 28usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_s390_adapter),
- "::",
- stringify!(adapter_id)
- )
- );
+ pub ind_addr: u64,
+ pub summary_addr: u64,
+ pub ind_offset: u64,
+ pub summary_offset: u32,
+ pub adapter_id: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_irq_routing_hv_sint {
- pub vcpu: __u32,
- pub sint: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing_hv_sint() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing_hv_sint>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_irq_routing_hv_sint))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing_hv_sint>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_irq_routing_hv_sint))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_hv_sint>())).vcpu as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_hv_sint),
- "::",
- stringify!(vcpu)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_hv_sint>())).sint as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_hv_sint),
- "::",
- stringify!(sint)
- )
- );
+ pub vcpu: u32,
+ pub sint: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_irq_routing_entry {
- pub gsi: __u32,
- pub type_: __u32,
- pub flags: __u32,
- pub pad: __u32,
+ pub gsi: u32,
+ pub type_: u32,
+ pub flags: u32,
+ pub pad: u32,
pub u: kvm_irq_routing_entry__bindgen_ty_1,
}
#[repr(C)]
@@ -6613,747 +1763,113 @@ pub union kvm_irq_routing_entry__bindgen_ty_1 {
pub msi: kvm_irq_routing_msi,
pub adapter: kvm_irq_routing_s390_adapter,
pub hv_sint: kvm_irq_routing_hv_sint,
- pub pad: [__u32; 8usize],
- _bindgen_union_align: [u64; 4usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing_entry__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing_entry__bindgen_ty_1>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_irq_routing_entry__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing_entry__bindgen_ty_1>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_irq_routing_entry__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_entry__bindgen_ty_1>())).irqchip as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry__bindgen_ty_1),
- "::",
- stringify!(irqchip)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_entry__bindgen_ty_1>())).msi as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry__bindgen_ty_1),
- "::",
- stringify!(msi)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_entry__bindgen_ty_1>())).adapter as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry__bindgen_ty_1),
- "::",
- stringify!(adapter)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_entry__bindgen_ty_1>())).hv_sint as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry__bindgen_ty_1),
- "::",
- stringify!(hv_sint)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_entry__bindgen_ty_1>())).pad as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry__bindgen_ty_1),
- "::",
- stringify!(pad)
- )
- );
+ pub pad: [u32; 8usize],
}
impl Default for kvm_irq_routing_entry__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing_entry() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing_entry>(),
- 48usize,
- concat!("Size of: ", stringify!(kvm_irq_routing_entry))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing_entry>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_irq_routing_entry))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_entry>())).gsi as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry),
- "::",
- stringify!(gsi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_entry>())).type_ as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry),
- "::",
- stringify!(type_)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_entry>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_entry>())).pad as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_entry>())).u as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry),
- "::",
- stringify!(u)
- )
- );
-}
impl Default for kvm_irq_routing_entry {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
pub struct kvm_irq_routing {
- pub nr: __u32,
- pub flags: __u32,
+ pub nr: u32,
+ pub flags: u32,
pub entries: __IncompleteArrayField<kvm_irq_routing_entry>,
}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_irq_routing))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_irq_routing))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing>())).nr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing),
- "::",
- stringify!(nr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing>())).flags as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing>())).entries as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing),
- "::",
- stringify!(entries)
- )
- );
-}
impl Default for kvm_irq_routing {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_irqfd {
- pub fd: __u32,
- pub gsi: __u32,
- pub flags: __u32,
- pub resamplefd: __u32,
- pub pad: [__u8; 16usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_irqfd() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irqfd>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_irqfd))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irqfd>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_irqfd))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqfd>())).fd as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqfd),
- "::",
- stringify!(fd)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqfd>())).gsi as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqfd),
- "::",
- stringify!(gsi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqfd>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqfd),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqfd>())).resamplefd as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqfd),
- "::",
- stringify!(resamplefd)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqfd>())).pad as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqfd),
- "::",
- stringify!(pad)
- )
- );
+ pub fd: u32,
+ pub gsi: u32,
+ pub flags: u32,
+ pub resamplefd: u32,
+ pub pad: [u8; 16usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_clock_data {
- pub clock: __u64,
- pub flags: __u32,
- pub pad: [__u32; 9usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_clock_data() {
- assert_eq!(
- ::std::mem::size_of::<kvm_clock_data>(),
- 48usize,
- concat!("Size of: ", stringify!(kvm_clock_data))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_clock_data>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_clock_data))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_clock_data>())).clock as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clock_data),
- "::",
- stringify!(clock)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_clock_data>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clock_data),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_clock_data>())).pad as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clock_data),
- "::",
- stringify!(pad)
- )
- );
+ pub clock: u64,
+ pub flags: u32,
+ pub pad: [u32; 9usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_config_tlb {
- pub params: __u64,
- pub array: __u64,
- pub mmu_type: __u32,
- pub array_len: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_config_tlb() {
- assert_eq!(
- ::std::mem::size_of::<kvm_config_tlb>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_config_tlb))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_config_tlb>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_config_tlb))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_config_tlb>())).params as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_config_tlb),
- "::",
- stringify!(params)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_config_tlb>())).array as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_config_tlb),
- "::",
- stringify!(array)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_config_tlb>())).mmu_type as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_config_tlb),
- "::",
- stringify!(mmu_type)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_config_tlb>())).array_len as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_config_tlb),
- "::",
- stringify!(array_len)
- )
- );
+ pub params: u64,
+ pub array: u64,
+ pub mmu_type: u32,
+ pub array_len: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_dirty_tlb {
- pub bitmap: __u64,
- pub num_dirty: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_dirty_tlb() {
- assert_eq!(
- ::std::mem::size_of::<kvm_dirty_tlb>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_dirty_tlb))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_dirty_tlb>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_dirty_tlb))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_dirty_tlb>())).bitmap as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dirty_tlb),
- "::",
- stringify!(bitmap)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_dirty_tlb>())).num_dirty as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dirty_tlb),
- "::",
- stringify!(num_dirty)
- )
- );
+ pub bitmap: u64,
+ pub num_dirty: u32,
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct kvm_reg_list {
- pub n: __u64,
- pub reg: __IncompleteArrayField<__u64>,
-}
-#[test]
-fn bindgen_test_layout_kvm_reg_list() {
- assert_eq!(
- ::std::mem::size_of::<kvm_reg_list>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_reg_list))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_reg_list>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_reg_list))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_reg_list>())).n as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_reg_list),
- "::",
- stringify!(n)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_reg_list>())).reg as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_reg_list),
- "::",
- stringify!(reg)
- )
- );
+ pub n: u64,
+ pub reg: __IncompleteArrayField<u64>,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_one_reg {
- pub id: __u64,
- pub addr: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_one_reg() {
- assert_eq!(
- ::std::mem::size_of::<kvm_one_reg>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_one_reg))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_one_reg>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_one_reg))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_one_reg>())).id as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_one_reg),
- "::",
- stringify!(id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_one_reg>())).addr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_one_reg),
- "::",
- stringify!(addr)
- )
- );
+ pub id: u64,
+ pub addr: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_msi {
- pub address_lo: __u32,
- pub address_hi: __u32,
- pub data: __u32,
- pub flags: __u32,
- pub devid: __u32,
- pub pad: [__u8; 12usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_msi() {
- assert_eq!(
- ::std::mem::size_of::<kvm_msi>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_msi))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_msi>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_msi))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msi>())).address_lo as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msi),
- "::",
- stringify!(address_lo)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msi>())).address_hi as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msi),
- "::",
- stringify!(address_hi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msi>())).data as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msi),
- "::",
- stringify!(data)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msi>())).flags as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msi),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msi>())).devid as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msi),
- "::",
- stringify!(devid)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msi>())).pad as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msi),
- "::",
- stringify!(pad)
- )
- );
+ pub address_lo: u32,
+ pub address_hi: u32,
+ pub data: u32,
+ pub flags: u32,
+ pub devid: u32,
+ pub pad: [u8; 12usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_arm_device_addr {
- pub id: __u64,
- pub addr: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_arm_device_addr() {
- assert_eq!(
- ::std::mem::size_of::<kvm_arm_device_addr>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_arm_device_addr))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_arm_device_addr>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_arm_device_addr))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_arm_device_addr>())).id as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_arm_device_addr),
- "::",
- stringify!(id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_arm_device_addr>())).addr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_arm_device_addr),
- "::",
- stringify!(addr)
- )
- );
+ pub id: u64,
+ pub addr: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_create_device {
- pub type_: __u32,
- pub fd: __u32,
- pub flags: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_create_device() {
- assert_eq!(
- ::std::mem::size_of::<kvm_create_device>(),
- 12usize,
- concat!("Size of: ", stringify!(kvm_create_device))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_create_device>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_create_device))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_create_device>())).type_ as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_create_device),
- "::",
- stringify!(type_)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_create_device>())).fd as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_create_device),
- "::",
- stringify!(fd)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_create_device>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_create_device),
- "::",
- stringify!(flags)
- )
- );
+ pub type_: u32,
+ pub fd: u32,
+ pub flags: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_device_attr {
- pub flags: __u32,
- pub group: __u32,
- pub attr: __u64,
- pub addr: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_device_attr() {
- assert_eq!(
- ::std::mem::size_of::<kvm_device_attr>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_device_attr))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_device_attr>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_device_attr))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_device_attr>())).flags as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_device_attr),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_device_attr>())).group as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_device_attr),
- "::",
- stringify!(group)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_device_attr>())).attr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_device_attr),
- "::",
- stringify!(attr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_device_attr>())).addr as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_device_attr),
- "::",
- stringify!(addr)
- )
- );
+ pub flags: u32,
+ pub group: u32,
+ pub attr: u64,
+ pub addr: u64,
}
pub const kvm_device_type_KVM_DEV_TYPE_FSL_MPIC_20: kvm_device_type = 1;
pub const kvm_device_type_KVM_DEV_TYPE_FSL_MPIC_42: kvm_device_type = 2;
@@ -7364,135 +1880,58 @@ pub const kvm_device_type_KVM_DEV_TYPE_FLIC: kvm_device_type = 6;
pub const kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3: kvm_device_type = 7;
pub const kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_ITS: kvm_device_type = 8;
pub const kvm_device_type_KVM_DEV_TYPE_XIVE: kvm_device_type = 9;
-pub const kvm_device_type_KVM_DEV_TYPE_MAX: kvm_device_type = 10;
-pub type kvm_device_type = u32;
+pub const kvm_device_type_KVM_DEV_TYPE_ARM_PV_TIME: kvm_device_type = 10;
+pub const kvm_device_type_KVM_DEV_TYPE_MAX: kvm_device_type = 11;
+pub type kvm_device_type = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_vfio_spapr_tce {
- pub groupfd: __s32,
- pub tablefd: __s32,
-}
-#[test]
-fn bindgen_test_layout_kvm_vfio_spapr_tce() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vfio_spapr_tce>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_vfio_spapr_tce))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vfio_spapr_tce>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_vfio_spapr_tce))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vfio_spapr_tce>())).groupfd as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vfio_spapr_tce),
- "::",
- stringify!(groupfd)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vfio_spapr_tce>())).tablefd as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vfio_spapr_tce),
- "::",
- stringify!(tablefd)
- )
- );
+ pub groupfd: i32,
+ pub tablefd: i32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_ucas_mapping {
- pub user_addr: __u64,
- pub vcpu_addr: __u64,
- pub length: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_ucas_mapping() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_ucas_mapping>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_s390_ucas_mapping))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_ucas_mapping>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_ucas_mapping))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_ucas_mapping>())).user_addr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_ucas_mapping),
- "::",
- stringify!(user_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_ucas_mapping>())).vcpu_addr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_ucas_mapping),
- "::",
- stringify!(vcpu_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_ucas_mapping>())).length as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_ucas_mapping),
- "::",
- stringify!(length)
- )
- );
+ pub user_addr: u64,
+ pub vcpu_addr: u64,
+ pub length: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_enc_region {
- pub addr: __u64,
- pub size: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_enc_region() {
- assert_eq!(
- ::std::mem::size_of::<kvm_enc_region>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_enc_region))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_enc_region>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_enc_region))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_enc_region>())).addr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_enc_region),
- "::",
- stringify!(addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_enc_region>())).size as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_enc_region),
- "::",
- stringify!(size)
- )
- );
+ pub addr: u64,
+ pub size: u64,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct kvm_s390_pv_sec_parm {
+ pub origin: u64,
+ pub length: u64,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct kvm_s390_pv_unp {
+ pub addr: u64,
+ pub size: u64,
+ pub tweak: u64,
+}
+pub const pv_cmd_id_KVM_PV_ENABLE: pv_cmd_id = 0;
+pub const pv_cmd_id_KVM_PV_DISABLE: pv_cmd_id = 1;
+pub const pv_cmd_id_KVM_PV_SET_SEC_PARMS: pv_cmd_id = 2;
+pub const pv_cmd_id_KVM_PV_UNPACK: pv_cmd_id = 3;
+pub const pv_cmd_id_KVM_PV_VERIFY: pv_cmd_id = 4;
+pub const pv_cmd_id_KVM_PV_PREP_RESET: pv_cmd_id = 5;
+pub const pv_cmd_id_KVM_PV_UNSHARE_ALL: pv_cmd_id = 6;
+pub type pv_cmd_id = ::std::os::raw::c_uint;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct kvm_pv_cmd {
+ pub cmd: u32,
+ pub rc: u16,
+ pub rrc: u16,
+ pub data: u64,
+ pub flags: u32,
+ pub reserved: [u32; 3usize],
}
pub const sev_cmd_id_KVM_SEV_INIT: sev_cmd_id = 0;
pub const sev_cmd_id_KVM_SEV_ES_INIT: sev_cmd_id = 1;
@@ -7515,821 +1954,147 @@ pub const sev_cmd_id_KVM_SEV_DBG_DECRYPT: sev_cmd_id = 17;
pub const sev_cmd_id_KVM_SEV_DBG_ENCRYPT: sev_cmd_id = 18;
pub const sev_cmd_id_KVM_SEV_CERT_EXPORT: sev_cmd_id = 19;
pub const sev_cmd_id_KVM_SEV_NR_MAX: sev_cmd_id = 20;
-pub type sev_cmd_id = u32;
+pub type sev_cmd_id = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sev_cmd {
- pub id: __u32,
- pub data: __u64,
- pub error: __u32,
- pub sev_fd: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_sev_cmd() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sev_cmd>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_sev_cmd))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sev_cmd>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sev_cmd))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_cmd>())).id as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_cmd),
- "::",
- stringify!(id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_cmd>())).data as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_cmd),
- "::",
- stringify!(data)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_cmd>())).error as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_cmd),
- "::",
- stringify!(error)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_cmd>())).sev_fd as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_cmd),
- "::",
- stringify!(sev_fd)
- )
- );
+ pub id: u32,
+ pub data: u64,
+ pub error: u32,
+ pub sev_fd: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sev_launch_start {
- pub handle: __u32,
- pub policy: __u32,
- pub dh_uaddr: __u64,
- pub dh_len: __u32,
- pub session_uaddr: __u64,
- pub session_len: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_sev_launch_start() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sev_launch_start>(),
- 40usize,
- concat!("Size of: ", stringify!(kvm_sev_launch_start))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sev_launch_start>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sev_launch_start))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_start>())).handle as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_start),
- "::",
- stringify!(handle)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_start>())).policy as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_start),
- "::",
- stringify!(policy)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_start>())).dh_uaddr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_start),
- "::",
- stringify!(dh_uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_start>())).dh_len as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_start),
- "::",
- stringify!(dh_len)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_sev_launch_start>())).session_uaddr as *const _ as usize
- },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_start),
- "::",
- stringify!(session_uaddr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_sev_launch_start>())).session_len as *const _ as usize
- },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_start),
- "::",
- stringify!(session_len)
- )
- );
+ pub handle: u32,
+ pub policy: u32,
+ pub dh_uaddr: u64,
+ pub dh_len: u32,
+ pub session_uaddr: u64,
+ pub session_len: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sev_launch_update_data {
- pub uaddr: __u64,
- pub len: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_sev_launch_update_data() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sev_launch_update_data>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_sev_launch_update_data))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sev_launch_update_data>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sev_launch_update_data))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_sev_launch_update_data>())).uaddr as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_update_data),
- "::",
- stringify!(uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_update_data>())).len as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_update_data),
- "::",
- stringify!(len)
- )
- );
+ pub uaddr: u64,
+ pub len: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sev_launch_secret {
- pub hdr_uaddr: __u64,
- pub hdr_len: __u32,
- pub guest_uaddr: __u64,
- pub guest_len: __u32,
- pub trans_uaddr: __u64,
- pub trans_len: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_sev_launch_secret() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sev_launch_secret>(),
- 48usize,
- concat!("Size of: ", stringify!(kvm_sev_launch_secret))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sev_launch_secret>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sev_launch_secret))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_secret>())).hdr_uaddr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_secret),
- "::",
- stringify!(hdr_uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_secret>())).hdr_len as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_secret),
- "::",
- stringify!(hdr_len)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_sev_launch_secret>())).guest_uaddr as *const _ as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_secret),
- "::",
- stringify!(guest_uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_secret>())).guest_len as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_secret),
- "::",
- stringify!(guest_len)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_sev_launch_secret>())).trans_uaddr as *const _ as usize
- },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_secret),
- "::",
- stringify!(trans_uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_secret>())).trans_len as *const _ as usize },
- 40usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_secret),
- "::",
- stringify!(trans_len)
- )
- );
+ pub hdr_uaddr: u64,
+ pub hdr_len: u32,
+ pub guest_uaddr: u64,
+ pub guest_len: u32,
+ pub trans_uaddr: u64,
+ pub trans_len: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sev_launch_measure {
- pub uaddr: __u64,
- pub len: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_sev_launch_measure() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sev_launch_measure>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_sev_launch_measure))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sev_launch_measure>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sev_launch_measure))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_measure>())).uaddr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_measure),
- "::",
- stringify!(uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_measure>())).len as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_measure),
- "::",
- stringify!(len)
- )
- );
+ pub uaddr: u64,
+ pub len: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sev_guest_status {
- pub handle: __u32,
- pub policy: __u32,
- pub state: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_sev_guest_status() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sev_guest_status>(),
- 12usize,
- concat!("Size of: ", stringify!(kvm_sev_guest_status))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sev_guest_status>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_sev_guest_status))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_guest_status>())).handle as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_guest_status),
- "::",
- stringify!(handle)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_guest_status>())).policy as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_guest_status),
- "::",
- stringify!(policy)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_guest_status>())).state as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_guest_status),
- "::",
- stringify!(state)
- )
- );
+ pub handle: u32,
+ pub policy: u32,
+ pub state: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sev_dbg {
- pub src_uaddr: __u64,
- pub dst_uaddr: __u64,
- pub len: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_sev_dbg() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sev_dbg>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_sev_dbg))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sev_dbg>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sev_dbg))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_dbg>())).src_uaddr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_dbg),
- "::",
- stringify!(src_uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_dbg>())).dst_uaddr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_dbg),
- "::",
- stringify!(dst_uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_dbg>())).len as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_dbg),
- "::",
- stringify!(len)
- )
- );
+ pub src_uaddr: u64,
+ pub dst_uaddr: u64,
+ pub len: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_assigned_pci_dev {
- pub assigned_dev_id: __u32,
- pub busnr: __u32,
- pub devfn: __u32,
- pub flags: __u32,
- pub segnr: __u32,
+ pub assigned_dev_id: u32,
+ pub busnr: u32,
+ pub devfn: u32,
+ pub flags: u32,
+ pub segnr: u32,
pub __bindgen_anon_1: kvm_assigned_pci_dev__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_assigned_pci_dev__bindgen_ty_1 {
- pub reserved: [__u32; 11usize],
- _bindgen_union_align: [u32; 11usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_assigned_pci_dev__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_assigned_pci_dev__bindgen_ty_1>(),
- 44usize,
- concat!("Size of: ", stringify!(kvm_assigned_pci_dev__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_assigned_pci_dev__bindgen_ty_1>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_assigned_pci_dev__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_assigned_pci_dev__bindgen_ty_1>())).reserved as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_pci_dev__bindgen_ty_1),
- "::",
- stringify!(reserved)
- )
- );
+ pub reserved: [u32; 11usize],
}
impl Default for kvm_assigned_pci_dev__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-#[test]
-fn bindgen_test_layout_kvm_assigned_pci_dev() {
- assert_eq!(
- ::std::mem::size_of::<kvm_assigned_pci_dev>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_assigned_pci_dev))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_assigned_pci_dev>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_assigned_pci_dev))
- );
- assert_eq!(
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
- &(*(::std::ptr::null::<kvm_assigned_pci_dev>())).assigned_dev_id as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_pci_dev),
- "::",
- stringify!(assigned_dev_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_pci_dev>())).busnr as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_pci_dev),
- "::",
- stringify!(busnr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_pci_dev>())).devfn as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_pci_dev),
- "::",
- stringify!(devfn)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_pci_dev>())).flags as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_pci_dev),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_pci_dev>())).segnr as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_pci_dev),
- "::",
- stringify!(segnr)
- )
- );
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
}
impl Default for kvm_assigned_pci_dev {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_assigned_irq {
- pub assigned_dev_id: __u32,
- pub host_irq: __u32,
- pub guest_irq: __u32,
- pub flags: __u32,
+ pub assigned_dev_id: u32,
+ pub host_irq: u32,
+ pub guest_irq: u32,
+ pub flags: u32,
pub __bindgen_anon_1: kvm_assigned_irq__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_assigned_irq__bindgen_ty_1 {
- pub reserved: [__u32; 12usize],
- _bindgen_union_align: [u32; 12usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_assigned_irq__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_assigned_irq__bindgen_ty_1>(),
- 48usize,
- concat!("Size of: ", stringify!(kvm_assigned_irq__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_assigned_irq__bindgen_ty_1>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_assigned_irq__bindgen_ty_1))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_assigned_irq__bindgen_ty_1>())).reserved as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_irq__bindgen_ty_1),
- "::",
- stringify!(reserved)
- )
- );
+ pub reserved: [u32; 12usize],
}
impl Default for kvm_assigned_irq__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-#[test]
-fn bindgen_test_layout_kvm_assigned_irq() {
- assert_eq!(
- ::std::mem::size_of::<kvm_assigned_irq>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_assigned_irq))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_assigned_irq>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_assigned_irq))
- );
- assert_eq!(
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
- &(*(::std::ptr::null::<kvm_assigned_irq>())).assigned_dev_id as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_irq),
- "::",
- stringify!(assigned_dev_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_irq>())).host_irq as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_irq),
- "::",
- stringify!(host_irq)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_irq>())).guest_irq as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_irq),
- "::",
- stringify!(guest_irq)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_irq>())).flags as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_irq),
- "::",
- stringify!(flags)
- )
- );
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
}
impl Default for kvm_assigned_irq {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_assigned_msix_nr {
- pub assigned_dev_id: __u32,
- pub entry_nr: __u16,
- pub padding: __u16,
-}
-#[test]
-fn bindgen_test_layout_kvm_assigned_msix_nr() {
- assert_eq!(
- ::std::mem::size_of::<kvm_assigned_msix_nr>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_assigned_msix_nr))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_assigned_msix_nr>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_assigned_msix_nr))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_assigned_msix_nr>())).assigned_dev_id as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_msix_nr),
- "::",
- stringify!(assigned_dev_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_msix_nr>())).entry_nr as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_msix_nr),
- "::",
- stringify!(entry_nr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_msix_nr>())).padding as *const _ as usize },
- 6usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_msix_nr),
- "::",
- stringify!(padding)
- )
- );
+ pub assigned_dev_id: u32,
+ pub entry_nr: u16,
+ pub padding: u16,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_assigned_msix_entry {
- pub assigned_dev_id: __u32,
- pub gsi: __u32,
- pub entry: __u16,
- pub padding: [__u16; 3usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_assigned_msix_entry() {
- assert_eq!(
- ::std::mem::size_of::<kvm_assigned_msix_entry>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_assigned_msix_entry))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_assigned_msix_entry>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_assigned_msix_entry))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_assigned_msix_entry>())).assigned_dev_id as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_msix_entry),
- "::",
- stringify!(assigned_dev_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_msix_entry>())).gsi as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_msix_entry),
- "::",
- stringify!(gsi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_msix_entry>())).entry as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_msix_entry),
- "::",
- stringify!(entry)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_msix_entry>())).padding as *const _ as usize },
- 10usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_msix_entry),
- "::",
- stringify!(padding)
- )
- );
+ pub assigned_dev_id: u32,
+ pub gsi: u32,
+ pub entry: u16,
+ pub padding: [u16; 3usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_hyperv_eventfd {
- pub conn_id: __u32,
- pub fd: __s32,
- pub flags: __u32,
- pub padding: [__u32; 3usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_hyperv_eventfd() {
- assert_eq!(
- ::std::mem::size_of::<kvm_hyperv_eventfd>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_hyperv_eventfd))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_hyperv_eventfd>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_hyperv_eventfd))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_hyperv_eventfd>())).conn_id as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_eventfd),
- "::",
- stringify!(conn_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_hyperv_eventfd>())).fd as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_eventfd),
- "::",
- stringify!(fd)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_hyperv_eventfd>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_eventfd),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_hyperv_eventfd>())).padding as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_eventfd),
- "::",
- stringify!(padding)
- )
- );
+ pub conn_id: u32,
+ pub fd: i32,
+ pub flags: u32,
+ pub padding: [u32; 3usize],
}
pub type __uint128_t = u128;
diff --git a/kvm_sys/src/lib.rs b/kvm_sys/src/lib.rs
index 78c2e8d7d..42363aa2b 100644
--- a/kvm_sys/src/lib.rs
+++ b/kvm_sys/src/lib.rs
@@ -7,7 +7,7 @@
#![allow(non_snake_case)]
use base::{ioctl_io_nr, ioctl_ior_nr, ioctl_iow_nr, ioctl_iowr_nr};
-use data_model::FlexibleArray;
+use data_model::{flexible_array_impl, FlexibleArray};
// Each of the below modules defines ioctls specific to their platform.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
@@ -32,8 +32,8 @@ pub mod x86 {
ioctl_iow_nr!(KVM_SET_LAPIC, KVMIO, 0x8f, kvm_lapic_state);
ioctl_iow_nr!(KVM_SET_CPUID2, KVMIO, 0x90, kvm_cpuid2);
ioctl_iowr_nr!(KVM_GET_CPUID2, KVMIO, 0x91, kvm_cpuid2);
- ioctl_iow_nr!(KVM_X86_SETUP_MCE, KVMIO, 0x9c, __u64);
- ioctl_ior_nr!(KVM_X86_GET_MCE_CAP_SUPPORTED, KVMIO, 0x9d, __u64);
+ ioctl_iow_nr!(KVM_X86_SETUP_MCE, KVMIO, 0x9c, u64);
+ ioctl_ior_nr!(KVM_X86_GET_MCE_CAP_SUPPORTED, KVMIO, 0x9d, u64);
ioctl_iow_nr!(KVM_X86_SET_MCE, KVMIO, 0x9e, kvm_x86_mce);
ioctl_ior_nr!(KVM_GET_VCPU_EVENTS, KVMIO, 0x9f, kvm_vcpu_events);
ioctl_iow_nr!(KVM_SET_VCPU_EVENTS, KVMIO, 0xa0, kvm_vcpu_events);
@@ -75,7 +75,7 @@ ioctl_iow_nr!(
kvm_userspace_memory_region
);
ioctl_io_nr!(KVM_SET_TSS_ADDR, KVMIO, 0x47);
-ioctl_iow_nr!(KVM_SET_IDENTITY_MAP_ADDR, KVMIO, 0x48, __u64);
+ioctl_iow_nr!(KVM_SET_IDENTITY_MAP_ADDR, KVMIO, 0x48, u64);
ioctl_io_nr!(KVM_CREATE_IRQCHIP, KVMIO, 0x60);
ioctl_iow_nr!(KVM_IRQ_LINE, KVMIO, 0x61, kvm_irq_level);
ioctl_iowr_nr!(KVM_GET_IRQCHIP, KVMIO, 0x62, kvm_irqchip);
@@ -156,23 +156,7 @@ ioctl_io_nr!(KVM_SMI, KVMIO, 0xb7);
pub use crate::x86::*;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
-impl FlexibleArray<kvm_cpuid_entry2> for kvm_cpuid2 {
- fn set_len(&mut self, len: usize) {
- self.nent = len as u32;
- }
-
- fn get_len(&self) -> usize {
- self.nent as usize
- }
-
- fn get_slice(&self, len: usize) -> &[kvm_cpuid_entry2] {
- unsafe { self.entries.as_slice(len) }
- }
-
- fn get_mut_slice(&mut self, len: usize) -> &mut [kvm_cpuid_entry2] {
- unsafe { self.entries.as_mut_slice(len) }
- }
-}
+flexible_array_impl!(kvm_cpuid2, kvm_cpuid_entry2, nent, entries);
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
pub use aarch64::*;
diff --git a/kvm_sys/src/x86/bindings.rs b/kvm_sys/src/x86/bindings.rs
index 80e2fc44e..78983ff88 100644
--- a/kvm_sys/src/x86/bindings.rs
+++ b/kvm_sys/src/x86/bindings.rs
@@ -1,34 +1,37 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+/* automatically generated by tools/bindgen-all-the-things */
-/*
- * automatically generated by bindgen 0.49.2.
- * From chromeos-linux v5.4 include/linux/kvm.h
- * $ cd /path/to/kernel/v5.4/
- * $ make headers_install ARCH=x86 INSTALL_HDR_PATH=x86_v5_4_headers
- * $ cd x86_v5_4_headers
- * $ bindgen --with-derive-default -o bindings.rs include/linux/kvm.h -- -Iinclude
- */
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+
+// Added by kvm_sys/bindgen.sh
+pub const KVM_SYSTEM_EVENT_S2IDLE: u32 = 4;
+pub const KVM_SYSTEM_EVENT_RESET_FLAG_PSCI_RESET2: u64 = 0x1;
+// TODO(tjeznach): Remove this when reporting KVM_IOAPIC_NUM_PINS is no longer required.
+pub const KVM_CAP_IOAPIC_NUM_PINS: u32 = 8191;
+// TODO(qwandor): Update this once the pKVM patches are merged upstream with a stable capability ID.
+pub const KVM_CAP_ARM_PROTECTED_VM: u32 = 0xffbadab1;
+pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_SET_FW_IPA: u32 = 0;
+pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_INFO: u32 = 1;
+pub const KVM_VM_TYPE_ARM_PROTECTED: u32 = 0x80000000;
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
-pub struct __BindgenBitfieldUnit<Storage, Align>
-where
- Storage: AsRef<[u8]> + AsMut<[u8]>,
-{
+pub struct __BindgenBitfieldUnit<Storage> {
storage: Storage,
- align: [Align; 0],
}
-impl<Storage, Align> __BindgenBitfieldUnit<Storage, Align>
+impl<Storage> __BindgenBitfieldUnit<Storage> {
+ #[inline]
+ pub const fn new(storage: Storage) -> Self {
+ Self { storage }
+ }
+}
+impl<Storage> __BindgenBitfieldUnit<Storage>
where
Storage: AsRef<[u8]> + AsMut<[u8]>,
{
#[inline]
- pub fn new(storage: Storage) -> Self {
- Self { storage, align: [] }
- }
- #[inline]
pub fn get_bit(&self, index: usize) -> bool {
debug_assert!(index / 8 < self.storage.as_ref().len());
let byte_index = index / 8;
@@ -98,16 +101,16 @@ where
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
impl<T> __IncompleteArrayField<T> {
#[inline]
- pub fn new() -> Self {
+ pub const fn new() -> Self {
__IncompleteArrayField(::std::marker::PhantomData, [])
}
#[inline]
- pub unsafe fn as_ptr(&self) -> *const T {
- ::std::mem::transmute(self)
+ pub fn as_ptr(&self) -> *const T {
+ self as *const _ as *const T
}
#[inline]
- pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
- ::std::mem::transmute(self)
+ pub fn as_mut_ptr(&mut self) -> *mut T {
+ self as *mut _ as *mut T
}
#[inline]
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
@@ -123,17 +126,11 @@ impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
fmt.write_str("__IncompleteArrayField")
}
}
-impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
- #[inline]
- fn clone(&self) -> Self {
- Self::new()
- }
-}
#[repr(C)]
pub struct __BindgenUnionField<T>(::std::marker::PhantomData<T>);
impl<T> __BindgenUnionField<T> {
#[inline]
- pub fn new() -> Self {
+ pub const fn new() -> Self {
__BindgenUnionField(::std::marker::PhantomData)
}
#[inline]
@@ -172,28 +169,6 @@ impl<T> ::std::cmp::PartialEq for __BindgenUnionField<T> {
}
}
impl<T> ::std::cmp::Eq for __BindgenUnionField<T> {}
-pub const __BITS_PER_LONG: u32 = 64;
-pub const __FD_SETSIZE: u32 = 1024;
-pub const _IOC_NRBITS: u32 = 8;
-pub const _IOC_TYPEBITS: u32 = 8;
-pub const _IOC_SIZEBITS: u32 = 14;
-pub const _IOC_DIRBITS: u32 = 2;
-pub const _IOC_NRMASK: u32 = 255;
-pub const _IOC_TYPEMASK: u32 = 255;
-pub const _IOC_SIZEMASK: u32 = 16383;
-pub const _IOC_DIRMASK: u32 = 3;
-pub const _IOC_NRSHIFT: u32 = 0;
-pub const _IOC_TYPESHIFT: u32 = 8;
-pub const _IOC_SIZESHIFT: u32 = 16;
-pub const _IOC_DIRSHIFT: u32 = 30;
-pub const _IOC_NONE: u32 = 0;
-pub const _IOC_WRITE: u32 = 1;
-pub const _IOC_READ: u32 = 2;
-pub const IOC_IN: u32 = 1073741824;
-pub const IOC_OUT: u32 = 2147483648;
-pub const IOC_INOUT: u32 = 3221225472;
-pub const IOCSIZE_MASK: u32 = 1073676288;
-pub const IOCSIZE_SHIFT: u32 = 16;
pub const KVM_PIO_PAGE_OFFSET: u32 = 1;
pub const KVM_COALESCED_MMIO_PAGE_OFFSET: u32 = 2;
pub const DE_VECTOR: u32 = 0;
@@ -222,6 +197,12 @@ pub const KVM_IRQCHIP_IOAPIC: u32 = 2;
pub const KVM_NR_IRQCHIPS: u32 = 3;
pub const KVM_RUN_X86_SMM: u32 = 1;
pub const KVM_APIC_REG_SIZE: u32 = 1024;
+pub const KVM_MSR_FILTER_MAX_BITMAP_SIZE: u32 = 1536;
+pub const KVM_MSR_FILTER_READ: u32 = 1;
+pub const KVM_MSR_FILTER_WRITE: u32 = 2;
+pub const KVM_MSR_FILTER_MAX_RANGES: u32 = 16;
+pub const KVM_MSR_FILTER_DEFAULT_ALLOW: u32 = 0;
+pub const KVM_MSR_FILTER_DEFAULT_DENY: u32 = 1;
pub const KVM_CPUID_FLAG_SIGNIFCANT_INDEX: u32 = 1;
pub const KVM_CPUID_FLAG_STATEFUL_FUNC: u32 = 2;
pub const KVM_CPUID_FLAG_STATE_READ_NEXT: u32 = 4;
@@ -252,9 +233,13 @@ pub const KVM_STATE_NESTED_FORMAT_SVM: u32 = 1;
pub const KVM_STATE_NESTED_GUEST_MODE: u32 = 1;
pub const KVM_STATE_NESTED_RUN_PENDING: u32 = 2;
pub const KVM_STATE_NESTED_EVMCS: u32 = 4;
+pub const KVM_STATE_NESTED_MTF_PENDING: u32 = 8;
+pub const KVM_STATE_NESTED_GIF_SET: u32 = 256;
pub const KVM_STATE_NESTED_SMM_GUEST_MODE: u32 = 1;
pub const KVM_STATE_NESTED_SMM_VMXON: u32 = 2;
pub const KVM_STATE_NESTED_VMX_VMCS_SIZE: u32 = 4096;
+pub const KVM_STATE_NESTED_SVM_VMCB_SIZE: u32 = 4096;
+pub const KVM_STATE_VMX_PREEMPTION_TIMER_DEADLINE: u32 = 1;
pub const KVM_PMU_EVENT_ALLOW: u32 = 0;
pub const KVM_PMU_EVENT_DENY: u32 = 1;
pub const KVM_API_VERSION: u32 = 12;
@@ -297,6 +282,7 @@ pub const KVM_PIT_SPEAKER_DUMMY: u32 = 1;
pub const KVM_S390_CMMA_PEEK: u32 = 1;
pub const KVM_EXIT_HYPERV_SYNIC: u32 = 1;
pub const KVM_EXIT_HYPERV_HCALL: u32 = 2;
+pub const KVM_EXIT_HYPERV_SYNDBG: u32 = 3;
pub const KVM_S390_GET_SKEYS_NONE: u32 = 1;
pub const KVM_S390_SKEYS_MAX: u32 = 1048576;
pub const KVM_EXIT_UNKNOWN: u32 = 0;
@@ -327,6 +313,9 @@ pub const KVM_EXIT_SYSTEM_EVENT: u32 = 24;
pub const KVM_EXIT_S390_STSI: u32 = 25;
pub const KVM_EXIT_IOAPIC_EOI: u32 = 26;
pub const KVM_EXIT_HYPERV: u32 = 27;
+pub const KVM_EXIT_ARM_NISV: u32 = 28;
+pub const KVM_EXIT_X86_RDMSR: u32 = 29;
+pub const KVM_EXIT_X86_WRMSR: u32 = 30;
pub const KVM_INTERNAL_ERROR_EMULATION: u32 = 1;
pub const KVM_INTERNAL_ERROR_SIMUL_EX: u32 = 2;
pub const KVM_INTERNAL_ERROR_DELIVERY_EV: u32 = 3;
@@ -341,9 +330,14 @@ pub const KVM_S390_RESET_IPL: u32 = 16;
pub const KVM_SYSTEM_EVENT_SHUTDOWN: u32 = 1;
pub const KVM_SYSTEM_EVENT_RESET: u32 = 2;
pub const KVM_SYSTEM_EVENT_CRASH: u32 = 3;
+pub const KVM_MSR_EXIT_REASON_INVAL: u32 = 1;
+pub const KVM_MSR_EXIT_REASON_UNKNOWN: u32 = 2;
+pub const KVM_MSR_EXIT_REASON_FILTER: u32 = 4;
pub const SYNC_REGS_SIZE_BYTES: u32 = 2048;
pub const KVM_S390_MEMOP_LOGICAL_READ: u32 = 0;
pub const KVM_S390_MEMOP_LOGICAL_WRITE: u32 = 1;
+pub const KVM_S390_MEMOP_SIDA_READ: u32 = 2;
+pub const KVM_S390_MEMOP_SIDA_WRITE: u32 = 3;
pub const KVM_S390_MEMOP_F_CHECK_ONLY: u32 = 1;
pub const KVM_S390_MEMOP_F_INJECT_EXCEPTION: u32 = 2;
pub const KVM_MP_STATE_RUNNABLE: u32 = 0;
@@ -568,10 +562,21 @@ pub const KVM_CAP_ARM_PTRAUTH_GENERIC: u32 = 172;
pub const KVM_CAP_PMU_EVENT_FILTER: u32 = 173;
pub const KVM_CAP_ARM_IRQ_LINE_LAYOUT_2: u32 = 174;
pub const KVM_CAP_HYPERV_DIRECT_TLBFLUSH: u32 = 175;
-// TODO(qwandor): Update this once the pKVM patches are merged upstream with a stable capability ID.
-pub const KVM_CAP_ARM_PROTECTED_VM: u32 = 0xffbadab1;
-pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_ENABLE: u32 = 0;
-pub const KVM_CAP_ARM_PROTECTED_VM_FLAGS_INFO: u32 = 1;
+pub const KVM_CAP_PPC_GUEST_DEBUG_SSTEP: u32 = 176;
+pub const KVM_CAP_ARM_NISV_TO_USER: u32 = 177;
+pub const KVM_CAP_ARM_INJECT_EXT_DABT: u32 = 178;
+pub const KVM_CAP_S390_VCPU_RESETS: u32 = 179;
+pub const KVM_CAP_S390_PROTECTED: u32 = 180;
+pub const KVM_CAP_PPC_SECURE_GUEST: u32 = 181;
+pub const KVM_CAP_HALT_POLL: u32 = 182;
+pub const KVM_CAP_ASYNC_PF_INT: u32 = 183;
+pub const KVM_CAP_LAST_CPU: u32 = 184;
+pub const KVM_CAP_SMALLER_MAXPHYADDR: u32 = 185;
+pub const KVM_CAP_S390_DIAG318: u32 = 186;
+pub const KVM_CAP_STEAL_TIME: u32 = 187;
+pub const KVM_CAP_X86_USER_SPACE_MSR: u32 = 188;
+pub const KVM_CAP_X86_MSR_FILTER: u32 = 189;
+pub const KVM_CAP_ENFORCE_PV_FEATURE_CPUID: u32 = 190;
pub const KVM_IRQ_ROUTING_IRQCHIP: u32 = 1;
pub const KVM_IRQ_ROUTING_MSI: u32 = 2;
pub const KVM_IRQ_ROUTING_S390_ADAPTER: u32 = 3;
@@ -629,551 +634,155 @@ pub const KVM_ARM_DEV_EL1_PTIMER: u32 = 2;
pub const KVM_ARM_DEV_PMU: u32 = 4;
pub const KVM_HYPERV_CONN_ID_MASK: u32 = 16777215;
pub const KVM_HYPERV_EVENTFD_DEASSIGN: u32 = 1;
-pub type __s8 = ::std::os::raw::c_schar;
-pub type __u8 = ::std::os::raw::c_uchar;
-pub type __s16 = ::std::os::raw::c_short;
-pub type __u16 = ::std::os::raw::c_ushort;
-pub type __s32 = ::std::os::raw::c_int;
-pub type __u32 = ::std::os::raw::c_uint;
-pub type __s64 = ::std::os::raw::c_longlong;
-pub type __u64 = ::std::os::raw::c_ulonglong;
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct __kernel_fd_set {
- pub fds_bits: [::std::os::raw::c_ulong; 16usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_fd_set() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_fd_set>(),
- 128usize,
- concat!("Size of: ", stringify!(__kernel_fd_set))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_fd_set>(),
- 8usize,
- concat!("Alignment of ", stringify!(__kernel_fd_set))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<__kernel_fd_set>())).fds_bits as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(__kernel_fd_set),
- "::",
- stringify!(fds_bits)
- )
- );
-}
-pub type __kernel_sighandler_t =
- ::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
-pub type __kernel_key_t = ::std::os::raw::c_int;
-pub type __kernel_mqd_t = ::std::os::raw::c_int;
-pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
-pub type __kernel_long_t = ::std::os::raw::c_long;
-pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
-pub type __kernel_ino_t = __kernel_ulong_t;
-pub type __kernel_mode_t = ::std::os::raw::c_uint;
-pub type __kernel_pid_t = ::std::os::raw::c_int;
-pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
-pub type __kernel_uid_t = ::std::os::raw::c_uint;
-pub type __kernel_gid_t = ::std::os::raw::c_uint;
-pub type __kernel_suseconds_t = __kernel_long_t;
-pub type __kernel_daddr_t = ::std::os::raw::c_int;
-pub type __kernel_uid32_t = ::std::os::raw::c_uint;
-pub type __kernel_gid32_t = ::std::os::raw::c_uint;
-pub type __kernel_size_t = __kernel_ulong_t;
-pub type __kernel_ssize_t = __kernel_long_t;
-pub type __kernel_ptrdiff_t = __kernel_long_t;
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct __kernel_fsid_t {
- pub val: [::std::os::raw::c_int; 2usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_fsid_t() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_fsid_t>(),
- 8usize,
- concat!("Size of: ", stringify!(__kernel_fsid_t))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_fsid_t>(),
- 4usize,
- concat!("Alignment of ", stringify!(__kernel_fsid_t))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<__kernel_fsid_t>())).val as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(__kernel_fsid_t),
- "::",
- stringify!(val)
- )
- );
-}
-pub type __kernel_off_t = __kernel_long_t;
-pub type __kernel_loff_t = ::std::os::raw::c_longlong;
-pub type __kernel_time_t = __kernel_long_t;
-pub type __kernel_time64_t = ::std::os::raw::c_longlong;
-pub type __kernel_clock_t = __kernel_long_t;
-pub type __kernel_timer_t = ::std::os::raw::c_int;
-pub type __kernel_clockid_t = ::std::os::raw::c_int;
-pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
-pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
-pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
-pub type __le16 = __u16;
-pub type __be16 = __u16;
-pub type __le32 = __u32;
-pub type __be32 = __u32;
-pub type __le64 = __u64;
-pub type __be64 = __u64;
-pub type __sum16 = __u16;
-pub type __wsum = __u32;
+pub const KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE: u32 = 1;
+pub const KVM_DIRTY_LOG_INITIALLY_SET: u32 = 2;
+pub type __le16 = u16;
+pub type __be16 = u16;
+pub type __le32 = u32;
+pub type __be32 = u32;
+pub type __le64 = u64;
+pub type __be64 = u64;
+pub type __sum16 = u16;
+pub type __wsum = u32;
pub type __poll_t = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_memory_alias {
- pub slot: __u32,
- pub flags: __u32,
- pub guest_phys_addr: __u64,
- pub memory_size: __u64,
- pub target_phys_addr: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_memory_alias() {
- assert_eq!(
- ::std::mem::size_of::<kvm_memory_alias>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_memory_alias))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_memory_alias>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_memory_alias))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_memory_alias>())).slot as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_memory_alias),
- "::",
- stringify!(slot)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_memory_alias>())).flags as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_memory_alias),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_memory_alias>())).guest_phys_addr as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_memory_alias),
- "::",
- stringify!(guest_phys_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_memory_alias>())).memory_size as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_memory_alias),
- "::",
- stringify!(memory_size)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_memory_alias>())).target_phys_addr as *const _ as usize
- },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_memory_alias),
- "::",
- stringify!(target_phys_addr)
- )
- );
+ pub slot: u32,
+ pub flags: u32,
+ pub guest_phys_addr: u64,
+ pub memory_size: u64,
+ pub target_phys_addr: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_pic_state {
- pub last_irr: __u8,
- pub irr: __u8,
- pub imr: __u8,
- pub isr: __u8,
- pub priority_add: __u8,
- pub irq_base: __u8,
- pub read_reg_select: __u8,
- pub poll: __u8,
- pub special_mask: __u8,
- pub init_state: __u8,
- pub auto_eoi: __u8,
- pub rotate_on_auto_eoi: __u8,
- pub special_fully_nested_mode: __u8,
- pub init4: __u8,
- pub elcr: __u8,
- pub elcr_mask: __u8,
-}
-#[test]
-fn bindgen_test_layout_kvm_pic_state() {
- assert_eq!(
- ::std::mem::size_of::<kvm_pic_state>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_pic_state))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_pic_state>(),
- 1usize,
- concat!("Alignment of ", stringify!(kvm_pic_state))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pic_state>())).last_irr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(last_irr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pic_state>())).irr as *const _ as usize },
- 1usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(irr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pic_state>())).imr as *const _ as usize },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(imr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pic_state>())).isr as *const _ as usize },
- 3usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(isr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pic_state>())).priority_add as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(priority_add)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pic_state>())).irq_base as *const _ as usize },
- 5usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(irq_base)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pic_state>())).read_reg_select as *const _ as usize },
- 6usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(read_reg_select)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pic_state>())).poll as *const _ as usize },
- 7usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(poll)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pic_state>())).special_mask as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(special_mask)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pic_state>())).init_state as *const _ as usize },
- 9usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(init_state)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pic_state>())).auto_eoi as *const _ as usize },
- 10usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(auto_eoi)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_pic_state>())).rotate_on_auto_eoi as *const _ as usize
- },
- 11usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(rotate_on_auto_eoi)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_pic_state>())).special_fully_nested_mode as *const _ as usize
- },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(special_fully_nested_mode)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pic_state>())).init4 as *const _ as usize },
- 13usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(init4)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pic_state>())).elcr as *const _ as usize },
- 14usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(elcr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pic_state>())).elcr_mask as *const _ as usize },
- 15usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pic_state),
- "::",
- stringify!(elcr_mask)
- )
- );
+ pub last_irr: u8,
+ pub irr: u8,
+ pub imr: u8,
+ pub isr: u8,
+ pub priority_add: u8,
+ pub irq_base: u8,
+ pub read_reg_select: u8,
+ pub poll: u8,
+ pub special_mask: u8,
+ pub init_state: u8,
+ pub auto_eoi: u8,
+ pub rotate_on_auto_eoi: u8,
+ pub special_fully_nested_mode: u8,
+ pub init4: u8,
+ pub elcr: u8,
+ pub elcr_mask: u8,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_ioapic_state {
- pub base_address: __u64,
- pub ioregsel: __u32,
- pub id: __u32,
- pub irr: __u32,
- pub pad: __u32,
+ pub base_address: u64,
+ pub ioregsel: u32,
+ pub id: u32,
+ pub irr: u32,
+ pub pad: u32,
pub redirtbl: [kvm_ioapic_state__bindgen_ty_1; 24usize],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_ioapic_state__bindgen_ty_1 {
- pub bits: __u64,
+ pub bits: u64,
pub fields: kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1,
- _bindgen_union_align: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1 {
- pub vector: __u8,
- pub _bitfield_1: __BindgenBitfieldUnit<[u8; 2usize], u8>,
- pub reserved: [__u8; 4usize],
- pub dest_id: __u8,
-}
-#[test]
-fn bindgen_test_layout_kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1>(),
- 8usize,
- concat!(
- "Size of: ",
- stringify!(kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1>(),
- 1usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1>())).vector
- as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(vector)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1>())).reserved
- as *const _ as usize
- },
- 3usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(reserved)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1>())).dest_id
- as *const _ as usize
- },
- 7usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(dest_id)
- )
- );
+ pub vector: u8,
+ pub _bitfield_align_1: [u8; 0],
+ pub _bitfield_1: __BindgenBitfieldUnit<[u8; 2usize]>,
+ pub reserved: [u8; 4usize],
+ pub dest_id: u8,
}
impl kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1 {
#[inline]
- pub fn delivery_mode(&self) -> __u8 {
+ pub fn delivery_mode(&self) -> u8 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 3u8) as u8) }
}
#[inline]
- pub fn set_delivery_mode(&mut self, val: __u8) {
+ pub fn set_delivery_mode(&mut self, val: u8) {
unsafe {
let val: u8 = ::std::mem::transmute(val);
self._bitfield_1.set(0usize, 3u8, val as u64)
}
}
#[inline]
- pub fn dest_mode(&self) -> __u8 {
+ pub fn dest_mode(&self) -> u8 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(3usize, 1u8) as u8) }
}
#[inline]
- pub fn set_dest_mode(&mut self, val: __u8) {
+ pub fn set_dest_mode(&mut self, val: u8) {
unsafe {
let val: u8 = ::std::mem::transmute(val);
self._bitfield_1.set(3usize, 1u8, val as u64)
}
}
#[inline]
- pub fn delivery_status(&self) -> __u8 {
+ pub fn delivery_status(&self) -> u8 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(4usize, 1u8) as u8) }
}
#[inline]
- pub fn set_delivery_status(&mut self, val: __u8) {
+ pub fn set_delivery_status(&mut self, val: u8) {
unsafe {
let val: u8 = ::std::mem::transmute(val);
self._bitfield_1.set(4usize, 1u8, val as u64)
}
}
#[inline]
- pub fn polarity(&self) -> __u8 {
+ pub fn polarity(&self) -> u8 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(5usize, 1u8) as u8) }
}
#[inline]
- pub fn set_polarity(&mut self, val: __u8) {
+ pub fn set_polarity(&mut self, val: u8) {
unsafe {
let val: u8 = ::std::mem::transmute(val);
self._bitfield_1.set(5usize, 1u8, val as u64)
}
}
#[inline]
- pub fn remote_irr(&self) -> __u8 {
+ pub fn remote_irr(&self) -> u8 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(6usize, 1u8) as u8) }
}
#[inline]
- pub fn set_remote_irr(&mut self, val: __u8) {
+ pub fn set_remote_irr(&mut self, val: u8) {
unsafe {
let val: u8 = ::std::mem::transmute(val);
self._bitfield_1.set(6usize, 1u8, val as u64)
}
}
#[inline]
- pub fn trig_mode(&self) -> __u8 {
+ pub fn trig_mode(&self) -> u8 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(7usize, 1u8) as u8) }
}
#[inline]
- pub fn set_trig_mode(&mut self, val: __u8) {
+ pub fn set_trig_mode(&mut self, val: u8) {
unsafe {
let val: u8 = ::std::mem::transmute(val);
self._bitfield_1.set(7usize, 1u8, val as u64)
}
}
#[inline]
- pub fn mask(&self) -> __u8 {
+ pub fn mask(&self) -> u8 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(8usize, 1u8) as u8) }
}
#[inline]
- pub fn set_mask(&mut self, val: __u8) {
+ pub fn set_mask(&mut self, val: u8) {
unsafe {
let val: u8 = ::std::mem::transmute(val);
self._bitfield_1.set(8usize, 1u8, val as u64)
}
}
#[inline]
- pub fn reserve(&self) -> __u8 {
+ pub fn reserve(&self) -> u8 {
unsafe { ::std::mem::transmute(self._bitfield_1.get(9usize, 7u8) as u8) }
}
#[inline]
- pub fn set_reserve(&mut self, val: __u8) {
+ pub fn set_reserve(&mut self, val: u8) {
unsafe {
let val: u8 = ::std::mem::transmute(val);
self._bitfield_1.set(9usize, 7u8, val as u64)
@@ -1181,17 +790,16 @@ impl kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1 {
}
#[inline]
pub fn new_bitfield_1(
- delivery_mode: __u8,
- dest_mode: __u8,
- delivery_status: __u8,
- polarity: __u8,
- remote_irr: __u8,
- trig_mode: __u8,
- mask: __u8,
- reserve: __u8,
- ) -> __BindgenBitfieldUnit<[u8; 2usize], u8> {
- let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 2usize], u8> =
- Default::default();
+ delivery_mode: u8,
+ dest_mode: u8,
+ delivery_status: u8,
+ polarity: u8,
+ remote_irr: u8,
+ trig_mode: u8,
+ mask: u8,
+ reserve: u8,
+ ) -> __BindgenBitfieldUnit<[u8; 2usize]> {
+ let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 2usize]> = Default::default();
__bindgen_bitfield_unit.set(0usize, 3u8, {
let delivery_mode: u8 = unsafe { ::std::mem::transmute(delivery_mode) };
delivery_mode as u64
@@ -1227,583 +835,83 @@ impl kvm_ioapic_state__bindgen_ty_1__bindgen_ty_1 {
__bindgen_bitfield_unit
}
}
-#[test]
-fn bindgen_test_layout_kvm_ioapic_state__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ioapic_state__bindgen_ty_1>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_ioapic_state__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ioapic_state__bindgen_ty_1>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_ioapic_state__bindgen_ty_1))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_ioapic_state__bindgen_ty_1>())).bits as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioapic_state__bindgen_ty_1),
- "::",
- stringify!(bits)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_ioapic_state__bindgen_ty_1>())).fields as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioapic_state__bindgen_ty_1),
- "::",
- stringify!(fields)
- )
- );
-}
impl Default for kvm_ioapic_state__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_ioapic_state() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ioapic_state>(),
- 216usize,
- concat!("Size of: ", stringify!(kvm_ioapic_state))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ioapic_state>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_ioapic_state))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioapic_state>())).base_address as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioapic_state),
- "::",
- stringify!(base_address)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioapic_state>())).ioregsel as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioapic_state),
- "::",
- stringify!(ioregsel)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioapic_state>())).id as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioapic_state),
- "::",
- stringify!(id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioapic_state>())).irr as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioapic_state),
- "::",
- stringify!(irr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioapic_state>())).pad as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioapic_state),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioapic_state>())).redirtbl as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioapic_state),
- "::",
- stringify!(redirtbl)
- )
- );
-}
impl Default for kvm_ioapic_state {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_regs {
- pub rax: __u64,
- pub rbx: __u64,
- pub rcx: __u64,
- pub rdx: __u64,
- pub rsi: __u64,
- pub rdi: __u64,
- pub rsp: __u64,
- pub rbp: __u64,
- pub r8: __u64,
- pub r9: __u64,
- pub r10: __u64,
- pub r11: __u64,
- pub r12: __u64,
- pub r13: __u64,
- pub r14: __u64,
- pub r15: __u64,
- pub rip: __u64,
- pub rflags: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_regs() {
- assert_eq!(
- ::std::mem::size_of::<kvm_regs>(),
- 144usize,
- concat!("Size of: ", stringify!(kvm_regs))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_regs>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_regs))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).rax as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(rax)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).rbx as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(rbx)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).rcx as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(rcx)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).rdx as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(rdx)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).rsi as *const _ as usize },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(rsi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).rdi as *const _ as usize },
- 40usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(rdi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).rsp as *const _ as usize },
- 48usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(rsp)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).rbp as *const _ as usize },
- 56usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(rbp)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).r8 as *const _ as usize },
- 64usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(r8)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).r9 as *const _ as usize },
- 72usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(r9)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).r10 as *const _ as usize },
- 80usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(r10)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).r11 as *const _ as usize },
- 88usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(r11)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).r12 as *const _ as usize },
- 96usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(r12)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).r13 as *const _ as usize },
- 104usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(r13)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).r14 as *const _ as usize },
- 112usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(r14)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).r15 as *const _ as usize },
- 120usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(r15)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).rip as *const _ as usize },
- 128usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(rip)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_regs>())).rflags as *const _ as usize },
- 136usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_regs),
- "::",
- stringify!(rflags)
- )
- );
-}
-#[repr(C)]
-#[derive(Copy, Clone)]
+ pub rax: u64,
+ pub rbx: u64,
+ pub rcx: u64,
+ pub rdx: u64,
+ pub rsi: u64,
+ pub rdi: u64,
+ pub rsp: u64,
+ pub rbp: u64,
+ pub r8: u64,
+ pub r9: u64,
+ pub r10: u64,
+ pub r11: u64,
+ pub r12: u64,
+ pub r13: u64,
+ pub r14: u64,
+ pub r15: u64,
+ pub rip: u64,
+ pub rflags: u64,
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
pub struct kvm_lapic_state {
pub regs: [::std::os::raw::c_char; 1024usize],
}
-#[test]
-fn bindgen_test_layout_kvm_lapic_state() {
- assert_eq!(
- ::std::mem::size_of::<kvm_lapic_state>(),
- 1024usize,
- concat!("Size of: ", stringify!(kvm_lapic_state))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_lapic_state>(),
- 1usize,
- concat!("Alignment of ", stringify!(kvm_lapic_state))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_lapic_state>())).regs as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_lapic_state),
- "::",
- stringify!(regs)
- )
- );
-}
impl Default for kvm_lapic_state {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_segment {
- pub base: __u64,
- pub limit: __u32,
- pub selector: __u16,
- pub type_: __u8,
- pub present: __u8,
- pub dpl: __u8,
- pub db: __u8,
- pub s: __u8,
- pub l: __u8,
- pub g: __u8,
- pub avl: __u8,
- pub unusable: __u8,
- pub padding: __u8,
-}
-#[test]
-fn bindgen_test_layout_kvm_segment() {
- assert_eq!(
- ::std::mem::size_of::<kvm_segment>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_segment))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_segment>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_segment))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_segment>())).base as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_segment),
- "::",
- stringify!(base)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_segment>())).limit as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_segment),
- "::",
- stringify!(limit)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_segment>())).selector as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_segment),
- "::",
- stringify!(selector)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_segment>())).type_ as *const _ as usize },
- 14usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_segment),
- "::",
- stringify!(type_)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_segment>())).present as *const _ as usize },
- 15usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_segment),
- "::",
- stringify!(present)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_segment>())).dpl as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_segment),
- "::",
- stringify!(dpl)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_segment>())).db as *const _ as usize },
- 17usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_segment),
- "::",
- stringify!(db)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_segment>())).s as *const _ as usize },
- 18usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_segment),
- "::",
- stringify!(s)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_segment>())).l as *const _ as usize },
- 19usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_segment),
- "::",
- stringify!(l)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_segment>())).g as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_segment),
- "::",
- stringify!(g)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_segment>())).avl as *const _ as usize },
- 21usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_segment),
- "::",
- stringify!(avl)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_segment>())).unusable as *const _ as usize },
- 22usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_segment),
- "::",
- stringify!(unusable)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_segment>())).padding as *const _ as usize },
- 23usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_segment),
- "::",
- stringify!(padding)
- )
- );
+ pub base: u64,
+ pub limit: u32,
+ pub selector: u16,
+ pub type_: u8,
+ pub present: u8,
+ pub dpl: u8,
+ pub db: u8,
+ pub s: u8,
+ pub l: u8,
+ pub g: u8,
+ pub avl: u8,
+ pub unusable: u8,
+ pub padding: u8,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_dtable {
- pub base: __u64,
- pub limit: __u16,
- pub padding: [__u16; 3usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_dtable() {
- assert_eq!(
- ::std::mem::size_of::<kvm_dtable>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_dtable))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_dtable>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_dtable))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_dtable>())).base as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dtable),
- "::",
- stringify!(base)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_dtable>())).limit as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dtable),
- "::",
- stringify!(limit)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_dtable>())).padding as *const _ as usize },
- 10usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dtable),
- "::",
- stringify!(padding)
- )
- );
+ pub base: u64,
+ pub limit: u16,
+ pub padding: [u16; 3usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
@@ -1818,1165 +926,166 @@ pub struct kvm_sregs {
pub ldt: kvm_segment,
pub gdt: kvm_dtable,
pub idt: kvm_dtable,
- pub cr0: __u64,
- pub cr2: __u64,
- pub cr3: __u64,
- pub cr4: __u64,
- pub cr8: __u64,
- pub efer: __u64,
- pub apic_base: __u64,
- pub interrupt_bitmap: [__u64; 4usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_sregs() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sregs>(),
- 312usize,
- concat!("Size of: ", stringify!(kvm_sregs))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sregs>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sregs))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).cs as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(cs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).ds as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(ds)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).es as *const _ as usize },
- 48usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(es)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).fs as *const _ as usize },
- 72usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(fs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).gs as *const _ as usize },
- 96usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(gs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).ss as *const _ as usize },
- 120usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(ss)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).tr as *const _ as usize },
- 144usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(tr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).ldt as *const _ as usize },
- 168usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(ldt)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).gdt as *const _ as usize },
- 192usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(gdt)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).idt as *const _ as usize },
- 208usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(idt)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).cr0 as *const _ as usize },
- 224usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(cr0)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).cr2 as *const _ as usize },
- 232usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(cr2)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).cr3 as *const _ as usize },
- 240usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(cr3)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).cr4 as *const _ as usize },
- 248usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(cr4)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).cr8 as *const _ as usize },
- 256usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(cr8)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).efer as *const _ as usize },
- 264usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(efer)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).apic_base as *const _ as usize },
- 272usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(apic_base)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sregs>())).interrupt_bitmap as *const _ as usize },
- 280usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sregs),
- "::",
- stringify!(interrupt_bitmap)
- )
- );
+ pub cr0: u64,
+ pub cr2: u64,
+ pub cr3: u64,
+ pub cr4: u64,
+ pub cr8: u64,
+ pub efer: u64,
+ pub apic_base: u64,
+ pub interrupt_bitmap: [u64; 4usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_fpu {
- pub fpr: [[__u8; 16usize]; 8usize],
- pub fcw: __u16,
- pub fsw: __u16,
- pub ftwx: __u8,
- pub pad1: __u8,
- pub last_opcode: __u16,
- pub last_ip: __u64,
- pub last_dp: __u64,
- pub xmm: [[__u8; 16usize]; 16usize],
- pub mxcsr: __u32,
- pub pad2: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_fpu() {
- assert_eq!(
- ::std::mem::size_of::<kvm_fpu>(),
- 416usize,
- concat!("Size of: ", stringify!(kvm_fpu))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_fpu>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_fpu))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_fpu>())).fpr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_fpu),
- "::",
- stringify!(fpr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_fpu>())).fcw as *const _ as usize },
- 128usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_fpu),
- "::",
- stringify!(fcw)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_fpu>())).fsw as *const _ as usize },
- 130usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_fpu),
- "::",
- stringify!(fsw)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_fpu>())).ftwx as *const _ as usize },
- 132usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_fpu),
- "::",
- stringify!(ftwx)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_fpu>())).pad1 as *const _ as usize },
- 133usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_fpu),
- "::",
- stringify!(pad1)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_fpu>())).last_opcode as *const _ as usize },
- 134usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_fpu),
- "::",
- stringify!(last_opcode)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_fpu>())).last_ip as *const _ as usize },
- 136usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_fpu),
- "::",
- stringify!(last_ip)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_fpu>())).last_dp as *const _ as usize },
- 144usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_fpu),
- "::",
- stringify!(last_dp)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_fpu>())).xmm as *const _ as usize },
- 152usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_fpu),
- "::",
- stringify!(xmm)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_fpu>())).mxcsr as *const _ as usize },
- 408usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_fpu),
- "::",
- stringify!(mxcsr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_fpu>())).pad2 as *const _ as usize },
- 412usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_fpu),
- "::",
- stringify!(pad2)
- )
- );
+ pub fpr: [[u8; 16usize]; 8usize],
+ pub fcw: u16,
+ pub fsw: u16,
+ pub ftwx: u8,
+ pub pad1: u8,
+ pub last_opcode: u16,
+ pub last_ip: u64,
+ pub last_dp: u64,
+ pub xmm: [[u8; 16usize]; 16usize],
+ pub mxcsr: u32,
+ pub pad2: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_msr_entry {
- pub index: __u32,
- pub reserved: __u32,
- pub data: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_msr_entry() {
- assert_eq!(
- ::std::mem::size_of::<kvm_msr_entry>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_msr_entry))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_msr_entry>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_msr_entry))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msr_entry>())).index as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msr_entry),
- "::",
- stringify!(index)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msr_entry>())).reserved as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msr_entry),
- "::",
- stringify!(reserved)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msr_entry>())).data as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msr_entry),
- "::",
- stringify!(data)
- )
- );
+ pub index: u32,
+ pub reserved: u32,
+ pub data: u64,
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct kvm_msrs {
- pub nmsrs: __u32,
- pub pad: __u32,
+ pub nmsrs: u32,
+ pub pad: u32,
pub entries: __IncompleteArrayField<kvm_msr_entry>,
}
-#[test]
-fn bindgen_test_layout_kvm_msrs() {
- assert_eq!(
- ::std::mem::size_of::<kvm_msrs>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_msrs))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_msrs>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_msrs))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msrs>())).nmsrs as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msrs),
- "::",
- stringify!(nmsrs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msrs>())).pad as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msrs),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msrs>())).entries as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msrs),
- "::",
- stringify!(entries)
- )
- );
-}
#[repr(C)]
#[derive(Debug, Default)]
pub struct kvm_msr_list {
- pub nmsrs: __u32,
- pub indices: __IncompleteArrayField<__u32>,
-}
-#[test]
-fn bindgen_test_layout_kvm_msr_list() {
- assert_eq!(
- ::std::mem::size_of::<kvm_msr_list>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_msr_list))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_msr_list>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_msr_list))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msr_list>())).nmsrs as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msr_list),
- "::",
- stringify!(nmsrs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msr_list>())).indices as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msr_list),
- "::",
- stringify!(indices)
- )
- );
+ pub nmsrs: u32,
+ pub indices: __IncompleteArrayField<u32>,
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct kvm_msr_filter_range {
+ pub flags: u32,
+ pub nmsrs: u32,
+ pub base: u32,
+ pub bitmap: *mut u8,
+}
+impl Default for kvm_msr_filter_range {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct kvm_msr_filter {
+ pub flags: u32,
+ pub ranges: [kvm_msr_filter_range; 16usize],
+}
+impl Default for kvm_msr_filter {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_cpuid_entry {
- pub function: __u32,
- pub eax: __u32,
- pub ebx: __u32,
- pub ecx: __u32,
- pub edx: __u32,
- pub padding: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_cpuid_entry() {
- assert_eq!(
- ::std::mem::size_of::<kvm_cpuid_entry>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_cpuid_entry))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_cpuid_entry>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_cpuid_entry))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid_entry>())).function as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid_entry),
- "::",
- stringify!(function)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid_entry>())).eax as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid_entry),
- "::",
- stringify!(eax)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid_entry>())).ebx as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid_entry),
- "::",
- stringify!(ebx)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid_entry>())).ecx as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid_entry),
- "::",
- stringify!(ecx)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid_entry>())).edx as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid_entry),
- "::",
- stringify!(edx)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid_entry>())).padding as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid_entry),
- "::",
- stringify!(padding)
- )
- );
+ pub function: u32,
+ pub eax: u32,
+ pub ebx: u32,
+ pub ecx: u32,
+ pub edx: u32,
+ pub padding: u32,
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct kvm_cpuid {
- pub nent: __u32,
- pub padding: __u32,
+ pub nent: u32,
+ pub padding: u32,
pub entries: __IncompleteArrayField<kvm_cpuid_entry>,
}
-#[test]
-fn bindgen_test_layout_kvm_cpuid() {
- assert_eq!(
- ::std::mem::size_of::<kvm_cpuid>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_cpuid))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_cpuid>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_cpuid))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid>())).nent as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid),
- "::",
- stringify!(nent)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid>())).padding as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid),
- "::",
- stringify!(padding)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid>())).entries as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid),
- "::",
- stringify!(entries)
- )
- );
-}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_cpuid_entry2 {
- pub function: __u32,
- pub index: __u32,
- pub flags: __u32,
- pub eax: __u32,
- pub ebx: __u32,
- pub ecx: __u32,
- pub edx: __u32,
- pub padding: [__u32; 3usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_cpuid_entry2() {
- assert_eq!(
- ::std::mem::size_of::<kvm_cpuid_entry2>(),
- 40usize,
- concat!("Size of: ", stringify!(kvm_cpuid_entry2))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_cpuid_entry2>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_cpuid_entry2))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid_entry2>())).function as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid_entry2),
- "::",
- stringify!(function)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid_entry2>())).index as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid_entry2),
- "::",
- stringify!(index)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid_entry2>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid_entry2),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid_entry2>())).eax as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid_entry2),
- "::",
- stringify!(eax)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid_entry2>())).ebx as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid_entry2),
- "::",
- stringify!(ebx)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid_entry2>())).ecx as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid_entry2),
- "::",
- stringify!(ecx)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid_entry2>())).edx as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid_entry2),
- "::",
- stringify!(edx)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid_entry2>())).padding as *const _ as usize },
- 28usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid_entry2),
- "::",
- stringify!(padding)
- )
- );
+ pub function: u32,
+ pub index: u32,
+ pub flags: u32,
+ pub eax: u32,
+ pub ebx: u32,
+ pub ecx: u32,
+ pub edx: u32,
+ pub padding: [u32; 3usize],
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct kvm_cpuid2 {
- pub nent: __u32,
- pub padding: __u32,
+ pub nent: u32,
+ pub padding: u32,
pub entries: __IncompleteArrayField<kvm_cpuid_entry2>,
}
-#[test]
-fn bindgen_test_layout_kvm_cpuid2() {
- assert_eq!(
- ::std::mem::size_of::<kvm_cpuid2>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_cpuid2))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_cpuid2>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_cpuid2))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid2>())).nent as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid2),
- "::",
- stringify!(nent)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid2>())).padding as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid2),
- "::",
- stringify!(padding)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_cpuid2>())).entries as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_cpuid2),
- "::",
- stringify!(entries)
- )
- );
-}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_pit_channel_state {
- pub count: __u32,
- pub latched_count: __u16,
- pub count_latched: __u8,
- pub status_latched: __u8,
- pub status: __u8,
- pub read_state: __u8,
- pub write_state: __u8,
- pub write_latch: __u8,
- pub rw_mode: __u8,
- pub mode: __u8,
- pub bcd: __u8,
- pub gate: __u8,
- pub count_load_time: __s64,
-}
-#[test]
-fn bindgen_test_layout_kvm_pit_channel_state() {
- assert_eq!(
- ::std::mem::size_of::<kvm_pit_channel_state>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_pit_channel_state))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_pit_channel_state>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_pit_channel_state))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pit_channel_state>())).count as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_channel_state),
- "::",
- stringify!(count)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_pit_channel_state>())).latched_count as *const _ as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_channel_state),
- "::",
- stringify!(latched_count)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_pit_channel_state>())).count_latched as *const _ as usize
- },
- 6usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_channel_state),
- "::",
- stringify!(count_latched)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_pit_channel_state>())).status_latched as *const _ as usize
- },
- 7usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_channel_state),
- "::",
- stringify!(status_latched)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pit_channel_state>())).status as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_channel_state),
- "::",
- stringify!(status)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_pit_channel_state>())).read_state as *const _ as usize
- },
- 9usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_channel_state),
- "::",
- stringify!(read_state)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_pit_channel_state>())).write_state as *const _ as usize
- },
- 10usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_channel_state),
- "::",
- stringify!(write_state)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_pit_channel_state>())).write_latch as *const _ as usize
- },
- 11usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_channel_state),
- "::",
- stringify!(write_latch)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pit_channel_state>())).rw_mode as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_channel_state),
- "::",
- stringify!(rw_mode)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pit_channel_state>())).mode as *const _ as usize },
- 13usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_channel_state),
- "::",
- stringify!(mode)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pit_channel_state>())).bcd as *const _ as usize },
- 14usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_channel_state),
- "::",
- stringify!(bcd)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pit_channel_state>())).gate as *const _ as usize },
- 15usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_channel_state),
- "::",
- stringify!(gate)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_pit_channel_state>())).count_load_time as *const _ as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_channel_state),
- "::",
- stringify!(count_load_time)
- )
- );
+ pub count: u32,
+ pub latched_count: u16,
+ pub count_latched: u8,
+ pub status_latched: u8,
+ pub status: u8,
+ pub read_state: u8,
+ pub write_state: u8,
+ pub write_latch: u8,
+ pub rw_mode: u8,
+ pub mode: u8,
+ pub bcd: u8,
+ pub gate: u8,
+ pub count_load_time: i64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_debug_exit_arch {
- pub exception: __u32,
- pub pad: __u32,
- pub pc: __u64,
- pub dr6: __u64,
- pub dr7: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_debug_exit_arch() {
- assert_eq!(
- ::std::mem::size_of::<kvm_debug_exit_arch>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_debug_exit_arch))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_debug_exit_arch>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_debug_exit_arch))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_exit_arch>())).exception as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_exit_arch),
- "::",
- stringify!(exception)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_exit_arch>())).pad as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_exit_arch),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_exit_arch>())).pc as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_exit_arch),
- "::",
- stringify!(pc)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_exit_arch>())).dr6 as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_exit_arch),
- "::",
- stringify!(dr6)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_exit_arch>())).dr7 as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_exit_arch),
- "::",
- stringify!(dr7)
- )
- );
+ pub exception: u32,
+ pub pad: u32,
+ pub pc: u64,
+ pub dr6: u64,
+ pub dr7: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_guest_debug_arch {
- pub debugreg: [__u64; 8usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_guest_debug_arch() {
- assert_eq!(
- ::std::mem::size_of::<kvm_guest_debug_arch>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_guest_debug_arch))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_guest_debug_arch>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_guest_debug_arch))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_guest_debug_arch>())).debugreg as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_guest_debug_arch),
- "::",
- stringify!(debugreg)
- )
- );
+ pub debugreg: [u64; 8usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_pit_state {
pub channels: [kvm_pit_channel_state; 3usize],
}
-#[test]
-fn bindgen_test_layout_kvm_pit_state() {
- assert_eq!(
- ::std::mem::size_of::<kvm_pit_state>(),
- 72usize,
- concat!("Size of: ", stringify!(kvm_pit_state))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_pit_state>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_pit_state))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pit_state>())).channels as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_state),
- "::",
- stringify!(channels)
- )
- );
-}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_pit_state2 {
pub channels: [kvm_pit_channel_state; 3usize],
- pub flags: __u32,
- pub reserved: [__u32; 9usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_pit_state2() {
- assert_eq!(
- ::std::mem::size_of::<kvm_pit_state2>(),
- 112usize,
- concat!("Size of: ", stringify!(kvm_pit_state2))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_pit_state2>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_pit_state2))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pit_state2>())).channels as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_state2),
- "::",
- stringify!(channels)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pit_state2>())).flags as *const _ as usize },
- 72usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_state2),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pit_state2>())).reserved as *const _ as usize },
- 76usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_state2),
- "::",
- stringify!(reserved)
- )
- );
+ pub flags: u32,
+ pub reserved: [u32; 9usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_reinject_control {
- pub pit_reinject: __u8,
- pub reserved: [__u8; 31usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_reinject_control() {
- assert_eq!(
- ::std::mem::size_of::<kvm_reinject_control>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_reinject_control))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_reinject_control>(),
- 1usize,
- concat!("Alignment of ", stringify!(kvm_reinject_control))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_reinject_control>())).pit_reinject as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_reinject_control),
- "::",
- stringify!(pit_reinject)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_reinject_control>())).reserved as *const _ as usize },
- 1usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_reinject_control),
- "::",
- stringify!(reserved)
- )
- );
+ pub pit_reinject: u8,
+ pub reserved: [u8; 31usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
@@ -2984,628 +1093,83 @@ pub struct kvm_vcpu_events {
pub exception: kvm_vcpu_events__bindgen_ty_1,
pub interrupt: kvm_vcpu_events__bindgen_ty_2,
pub nmi: kvm_vcpu_events__bindgen_ty_3,
- pub sipi_vector: __u32,
- pub flags: __u32,
+ pub sipi_vector: u32,
+ pub flags: u32,
pub smi: kvm_vcpu_events__bindgen_ty_4,
- pub reserved: [__u8; 27usize],
- pub exception_has_payload: __u8,
- pub exception_payload: __u64,
+ pub reserved: [u8; 27usize],
+ pub exception_has_payload: u8,
+ pub exception_payload: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_vcpu_events__bindgen_ty_1 {
- pub injected: __u8,
- pub nr: __u8,
- pub has_error_code: __u8,
- pub pending: __u8,
- pub error_code: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_vcpu_events__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vcpu_events__bindgen_ty_1>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_vcpu_events__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vcpu_events__bindgen_ty_1>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_vcpu_events__bindgen_ty_1))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_1>())).injected as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_1),
- "::",
- stringify!(injected)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_1>())).nr as *const _ as usize
- },
- 1usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_1),
- "::",
- stringify!(nr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_1>())).has_error_code as *const _
- as usize
- },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_1),
- "::",
- stringify!(has_error_code)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_1>())).pending as *const _ as usize
- },
- 3usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_1),
- "::",
- stringify!(pending)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_1>())).error_code as *const _
- as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_1),
- "::",
- stringify!(error_code)
- )
- );
+ pub injected: u8,
+ pub nr: u8,
+ pub has_error_code: u8,
+ pub pending: u8,
+ pub error_code: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_vcpu_events__bindgen_ty_2 {
- pub injected: __u8,
- pub nr: __u8,
- pub soft: __u8,
- pub shadow: __u8,
-}
-#[test]
-fn bindgen_test_layout_kvm_vcpu_events__bindgen_ty_2() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vcpu_events__bindgen_ty_2>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_vcpu_events__bindgen_ty_2))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vcpu_events__bindgen_ty_2>(),
- 1usize,
- concat!("Alignment of ", stringify!(kvm_vcpu_events__bindgen_ty_2))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_2>())).injected as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_2),
- "::",
- stringify!(injected)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_2>())).nr as *const _ as usize
- },
- 1usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_2),
- "::",
- stringify!(nr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_2>())).soft as *const _ as usize
- },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_2),
- "::",
- stringify!(soft)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_2>())).shadow as *const _ as usize
- },
- 3usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_2),
- "::",
- stringify!(shadow)
- )
- );
+ pub injected: u8,
+ pub nr: u8,
+ pub soft: u8,
+ pub shadow: u8,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_vcpu_events__bindgen_ty_3 {
- pub injected: __u8,
- pub pending: __u8,
- pub masked: __u8,
- pub pad: __u8,
-}
-#[test]
-fn bindgen_test_layout_kvm_vcpu_events__bindgen_ty_3() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vcpu_events__bindgen_ty_3>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_vcpu_events__bindgen_ty_3))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vcpu_events__bindgen_ty_3>(),
- 1usize,
- concat!("Alignment of ", stringify!(kvm_vcpu_events__bindgen_ty_3))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_3>())).injected as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_3),
- "::",
- stringify!(injected)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_3>())).pending as *const _ as usize
- },
- 1usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_3),
- "::",
- stringify!(pending)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_3>())).masked as *const _ as usize
- },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_3),
- "::",
- stringify!(masked)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_3>())).pad as *const _ as usize
- },
- 3usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_3),
- "::",
- stringify!(pad)
- )
- );
+ pub injected: u8,
+ pub pending: u8,
+ pub masked: u8,
+ pub pad: u8,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_vcpu_events__bindgen_ty_4 {
- pub smm: __u8,
- pub pending: __u8,
- pub smm_inside_nmi: __u8,
- pub latched_init: __u8,
-}
-#[test]
-fn bindgen_test_layout_kvm_vcpu_events__bindgen_ty_4() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vcpu_events__bindgen_ty_4>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_vcpu_events__bindgen_ty_4))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vcpu_events__bindgen_ty_4>(),
- 1usize,
- concat!("Alignment of ", stringify!(kvm_vcpu_events__bindgen_ty_4))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_4>())).smm as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_4),
- "::",
- stringify!(smm)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_4>())).pending as *const _ as usize
- },
- 1usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_4),
- "::",
- stringify!(pending)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_4>())).smm_inside_nmi as *const _
- as usize
- },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_4),
- "::",
- stringify!(smm_inside_nmi)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events__bindgen_ty_4>())).latched_init as *const _
- as usize
- },
- 3usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events__bindgen_ty_4),
- "::",
- stringify!(latched_init)
- )
- );
-}
-#[test]
-fn bindgen_test_layout_kvm_vcpu_events() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vcpu_events>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_vcpu_events))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vcpu_events>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_vcpu_events))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vcpu_events>())).exception as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events),
- "::",
- stringify!(exception)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vcpu_events>())).interrupt as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events),
- "::",
- stringify!(interrupt)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vcpu_events>())).nmi as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events),
- "::",
- stringify!(nmi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vcpu_events>())).sipi_vector as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events),
- "::",
- stringify!(sipi_vector)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vcpu_events>())).flags as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vcpu_events>())).smi as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events),
- "::",
- stringify!(smi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vcpu_events>())).reserved as *const _ as usize },
- 28usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events),
- "::",
- stringify!(reserved)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events>())).exception_has_payload as *const _ as usize
- },
- 55usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events),
- "::",
- stringify!(exception_has_payload)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vcpu_events>())).exception_payload as *const _ as usize
- },
- 56usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vcpu_events),
- "::",
- stringify!(exception_payload)
- )
- );
+ pub smm: u8,
+ pub pending: u8,
+ pub smm_inside_nmi: u8,
+ pub latched_init: u8,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_debugregs {
- pub db: [__u64; 4usize],
- pub dr6: __u64,
- pub dr7: __u64,
- pub flags: __u64,
- pub reserved: [__u64; 9usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_debugregs() {
- assert_eq!(
- ::std::mem::size_of::<kvm_debugregs>(),
- 128usize,
- concat!("Size of: ", stringify!(kvm_debugregs))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_debugregs>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_debugregs))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debugregs>())).db as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debugregs),
- "::",
- stringify!(db)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debugregs>())).dr6 as *const _ as usize },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debugregs),
- "::",
- stringify!(dr6)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debugregs>())).dr7 as *const _ as usize },
- 40usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debugregs),
- "::",
- stringify!(dr7)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debugregs>())).flags as *const _ as usize },
- 48usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debugregs),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debugregs>())).reserved as *const _ as usize },
- 56usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debugregs),
- "::",
- stringify!(reserved)
- )
- );
+ pub db: [u64; 4usize],
+ pub dr6: u64,
+ pub dr7: u64,
+ pub flags: u64,
+ pub reserved: [u64; 9usize],
}
#[repr(C)]
-#[derive(Copy, Clone)]
+#[derive(Debug, Copy, Clone)]
pub struct kvm_xsave {
- pub region: [__u32; 1024usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_xsave() {
- assert_eq!(
- ::std::mem::size_of::<kvm_xsave>(),
- 4096usize,
- concat!("Size of: ", stringify!(kvm_xsave))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_xsave>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_xsave))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xsave>())).region as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xsave),
- "::",
- stringify!(region)
- )
- );
+ pub region: [u32; 1024usize],
}
impl Default for kvm_xsave {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_xcr {
- pub xcr: __u32,
- pub reserved: __u32,
- pub value: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_xcr() {
- assert_eq!(
- ::std::mem::size_of::<kvm_xcr>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_xcr))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_xcr>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_xcr))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xcr>())).xcr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xcr),
- "::",
- stringify!(xcr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xcr>())).reserved as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xcr),
- "::",
- stringify!(reserved)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xcr>())).value as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xcr),
- "::",
- stringify!(value)
- )
- );
+ pub xcr: u32,
+ pub reserved: u32,
+ pub value: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_xcrs {
- pub nr_xcrs: __u32,
- pub flags: __u32,
+ pub nr_xcrs: u32,
+ pub flags: u32,
pub xcrs: [kvm_xcr; 16usize],
- pub padding: [__u64; 16usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_xcrs() {
- assert_eq!(
- ::std::mem::size_of::<kvm_xcrs>(),
- 392usize,
- concat!("Size of: ", stringify!(kvm_xcrs))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_xcrs>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_xcrs))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xcrs>())).nr_xcrs as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xcrs),
- "::",
- stringify!(nr_xcrs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xcrs>())).flags as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xcrs),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xcrs>())).xcrs as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xcrs),
- "::",
- stringify!(xcrs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xcrs>())).padding as *const _ as usize },
- 264usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xcrs),
- "::",
- stringify!(padding)
- )
- );
+ pub padding: [u64; 16usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
@@ -3614,193 +1178,59 @@ pub struct kvm_sync_regs {
pub sregs: kvm_sregs,
pub events: kvm_vcpu_events,
}
-#[test]
-fn bindgen_test_layout_kvm_sync_regs() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sync_regs>(),
- 520usize,
- concat!("Size of: ", stringify!(kvm_sync_regs))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sync_regs>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sync_regs))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sync_regs>())).regs as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sync_regs),
- "::",
- stringify!(regs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sync_regs>())).sregs as *const _ as usize },
- 144usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sync_regs),
- "::",
- stringify!(sregs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sync_regs>())).events as *const _ as usize },
- 456usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sync_regs),
- "::",
- stringify!(events)
- )
- );
-}
#[repr(C)]
-#[derive(Copy, Clone)]
+#[derive(Debug, Copy, Clone)]
pub struct kvm_vmx_nested_state_data {
- pub vmcs12: [__u8; 4096usize],
- pub shadow_vmcs12: [__u8; 4096usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_vmx_nested_state_data() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vmx_nested_state_data>(),
- 8192usize,
- concat!("Size of: ", stringify!(kvm_vmx_nested_state_data))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vmx_nested_state_data>(),
- 1usize,
- concat!("Alignment of ", stringify!(kvm_vmx_nested_state_data))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vmx_nested_state_data>())).vmcs12 as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vmx_nested_state_data),
- "::",
- stringify!(vmcs12)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vmx_nested_state_data>())).shadow_vmcs12 as *const _ as usize
- },
- 4096usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vmx_nested_state_data),
- "::",
- stringify!(shadow_vmcs12)
- )
- );
+ pub vmcs12: [u8; 4096usize],
+ pub shadow_vmcs12: [u8; 4096usize],
}
impl Default for kvm_vmx_nested_state_data {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_vmx_nested_state_hdr {
- pub vmxon_pa: __u64,
- pub vmcs12_pa: __u64,
+ pub vmxon_pa: u64,
+ pub vmcs12_pa: u64,
pub smm: kvm_vmx_nested_state_hdr__bindgen_ty_1,
+ pub flags: u32,
+ pub preemption_timer_deadline: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_vmx_nested_state_hdr__bindgen_ty_1 {
- pub flags: __u16,
-}
-#[test]
-fn bindgen_test_layout_kvm_vmx_nested_state_hdr__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vmx_nested_state_hdr__bindgen_ty_1>(),
- 2usize,
- concat!(
- "Size of: ",
- stringify!(kvm_vmx_nested_state_hdr__bindgen_ty_1)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vmx_nested_state_hdr__bindgen_ty_1>(),
- 2usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_vmx_nested_state_hdr__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vmx_nested_state_hdr__bindgen_ty_1>())).flags as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vmx_nested_state_hdr__bindgen_ty_1),
- "::",
- stringify!(flags)
- )
- );
-}
-#[test]
-fn bindgen_test_layout_kvm_vmx_nested_state_hdr() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vmx_nested_state_hdr>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_vmx_nested_state_hdr))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vmx_nested_state_hdr>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_vmx_nested_state_hdr))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_vmx_nested_state_hdr>())).vmxon_pa as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vmx_nested_state_hdr),
- "::",
- stringify!(vmxon_pa)
- )
- );
- assert_eq!(
+ pub flags: u16,
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct kvm_svm_nested_state_data {
+ pub vmcb12: [u8; 4096usize],
+}
+impl Default for kvm_svm_nested_state_data {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
- &(*(::std::ptr::null::<kvm_vmx_nested_state_hdr>())).vmcs12_pa as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vmx_nested_state_hdr),
- "::",
- stringify!(vmcs12_pa)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vmx_nested_state_hdr>())).smm as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vmx_nested_state_hdr),
- "::",
- stringify!(smm)
- )
- );
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct kvm_svm_nested_state_hdr {
+ pub vmcb_pa: u64,
}
#[repr(C)]
pub struct kvm_nested_state {
- pub flags: __u16,
- pub format: __u16,
- pub size: __u32,
+ pub flags: u16,
+ pub format: u16,
+ pub size: u32,
pub hdr: kvm_nested_state__bindgen_ty_1,
pub data: kvm_nested_state__bindgen_ty_2,
}
@@ -3808,623 +1238,125 @@ pub struct kvm_nested_state {
#[derive(Copy, Clone)]
pub union kvm_nested_state__bindgen_ty_1 {
pub vmx: kvm_vmx_nested_state_hdr,
- pub pad: [__u8; 120usize],
- _bindgen_union_align: [u64; 15usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_nested_state__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_nested_state__bindgen_ty_1>(),
- 120usize,
- concat!("Size of: ", stringify!(kvm_nested_state__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_nested_state__bindgen_ty_1>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_nested_state__bindgen_ty_1))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_nested_state__bindgen_ty_1>())).vmx as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_nested_state__bindgen_ty_1),
- "::",
- stringify!(vmx)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_nested_state__bindgen_ty_1>())).pad as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_nested_state__bindgen_ty_1),
- "::",
- stringify!(pad)
- )
- );
+ pub svm: kvm_svm_nested_state_hdr,
+ pub pad: [u8; 120usize],
}
impl Default for kvm_nested_state__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
pub struct kvm_nested_state__bindgen_ty_2 {
pub vmx: __BindgenUnionField<[kvm_vmx_nested_state_data; 0usize]>,
+ pub svm: __BindgenUnionField<[kvm_svm_nested_state_data; 0usize]>,
pub bindgen_union_field: [u8; 0usize],
}
-#[test]
-fn bindgen_test_layout_kvm_nested_state__bindgen_ty_2() {
- assert_eq!(
- ::std::mem::size_of::<kvm_nested_state__bindgen_ty_2>(),
- 0usize,
- concat!("Size of: ", stringify!(kvm_nested_state__bindgen_ty_2))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_nested_state__bindgen_ty_2>(),
- 1usize,
- concat!("Alignment of ", stringify!(kvm_nested_state__bindgen_ty_2))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_nested_state__bindgen_ty_2>())).vmx as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_nested_state__bindgen_ty_2),
- "::",
- stringify!(vmx)
- )
- );
-}
impl Default for kvm_nested_state__bindgen_ty_2 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_nested_state() {
- assert_eq!(
- ::std::mem::size_of::<kvm_nested_state>(),
- 128usize,
- concat!("Size of: ", stringify!(kvm_nested_state))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_nested_state>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_nested_state))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_nested_state>())).flags as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_nested_state),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_nested_state>())).format as *const _ as usize },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_nested_state),
- "::",
- stringify!(format)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_nested_state>())).size as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_nested_state),
- "::",
- stringify!(size)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_nested_state>())).hdr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_nested_state),
- "::",
- stringify!(hdr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_nested_state>())).data as *const _ as usize },
- 128usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_nested_state),
- "::",
- stringify!(data)
- )
- );
-}
impl Default for kvm_nested_state {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct kvm_pmu_event_filter {
- pub action: __u32,
- pub nevents: __u32,
- pub fixed_counter_bitmap: __u32,
- pub flags: __u32,
- pub pad: [__u32; 4usize],
- pub events: __IncompleteArrayField<__u64>,
-}
-#[test]
-fn bindgen_test_layout_kvm_pmu_event_filter() {
- assert_eq!(
- ::std::mem::size_of::<kvm_pmu_event_filter>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_pmu_event_filter))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_pmu_event_filter>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_pmu_event_filter))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pmu_event_filter>())).action as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pmu_event_filter),
- "::",
- stringify!(action)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pmu_event_filter>())).nevents as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pmu_event_filter),
- "::",
- stringify!(nevents)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_pmu_event_filter>())).fixed_counter_bitmap as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pmu_event_filter),
- "::",
- stringify!(fixed_counter_bitmap)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pmu_event_filter>())).flags as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pmu_event_filter),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pmu_event_filter>())).pad as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pmu_event_filter),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pmu_event_filter>())).events as *const _ as usize },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pmu_event_filter),
- "::",
- stringify!(events)
- )
- );
+ pub action: u32,
+ pub nevents: u32,
+ pub fixed_counter_bitmap: u32,
+ pub flags: u32,
+ pub pad: [u32; 4usize],
+ pub events: __IncompleteArrayField<u64>,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_user_trace_setup {
- pub buf_size: __u32,
- pub buf_nr: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_user_trace_setup() {
- assert_eq!(
- ::std::mem::size_of::<kvm_user_trace_setup>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_user_trace_setup))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_user_trace_setup>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_user_trace_setup))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_user_trace_setup>())).buf_size as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_user_trace_setup),
- "::",
- stringify!(buf_size)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_user_trace_setup>())).buf_nr as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_user_trace_setup),
- "::",
- stringify!(buf_nr)
- )
- );
+ pub buf_size: u32,
+ pub buf_nr: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_breakpoint {
- pub enabled: __u32,
- pub padding: __u32,
- pub address: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_breakpoint() {
- assert_eq!(
- ::std::mem::size_of::<kvm_breakpoint>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_breakpoint))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_breakpoint>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_breakpoint))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_breakpoint>())).enabled as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_breakpoint),
- "::",
- stringify!(enabled)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_breakpoint>())).padding as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_breakpoint),
- "::",
- stringify!(padding)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_breakpoint>())).address as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_breakpoint),
- "::",
- stringify!(address)
- )
- );
+ pub enabled: u32,
+ pub padding: u32,
+ pub address: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_debug_guest {
- pub enabled: __u32,
- pub pad: __u32,
+ pub enabled: u32,
+ pub pad: u32,
pub breakpoints: [kvm_breakpoint; 4usize],
- pub singlestep: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_debug_guest() {
- assert_eq!(
- ::std::mem::size_of::<kvm_debug_guest>(),
- 80usize,
- concat!("Size of: ", stringify!(kvm_debug_guest))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_debug_guest>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_debug_guest))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_guest>())).enabled as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_guest),
- "::",
- stringify!(enabled)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_guest>())).pad as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_guest),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_guest>())).breakpoints as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_guest),
- "::",
- stringify!(breakpoints)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_debug_guest>())).singlestep as *const _ as usize },
- 72usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_debug_guest),
- "::",
- stringify!(singlestep)
- )
- );
+ pub singlestep: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_memory_region {
- pub slot: __u32,
- pub flags: __u32,
- pub guest_phys_addr: __u64,
- pub memory_size: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_memory_region() {
- assert_eq!(
- ::std::mem::size_of::<kvm_memory_region>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_memory_region))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_memory_region>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_memory_region))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_memory_region>())).slot as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_memory_region),
- "::",
- stringify!(slot)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_memory_region>())).flags as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_memory_region),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_memory_region>())).guest_phys_addr as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_memory_region),
- "::",
- stringify!(guest_phys_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_memory_region>())).memory_size as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_memory_region),
- "::",
- stringify!(memory_size)
- )
- );
+ pub slot: u32,
+ pub flags: u32,
+ pub guest_phys_addr: u64,
+ pub memory_size: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_userspace_memory_region {
- pub slot: __u32,
- pub flags: __u32,
- pub guest_phys_addr: __u64,
- pub memory_size: __u64,
- pub userspace_addr: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_userspace_memory_region() {
- assert_eq!(
- ::std::mem::size_of::<kvm_userspace_memory_region>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_userspace_memory_region))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_userspace_memory_region>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_userspace_memory_region))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_userspace_memory_region>())).slot as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_userspace_memory_region),
- "::",
- stringify!(slot)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_userspace_memory_region>())).flags as *const _ as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_userspace_memory_region),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_userspace_memory_region>())).guest_phys_addr as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_userspace_memory_region),
- "::",
- stringify!(guest_phys_addr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_userspace_memory_region>())).memory_size as *const _ as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_userspace_memory_region),
- "::",
- stringify!(memory_size)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_userspace_memory_region>())).userspace_addr as *const _
- as usize
- },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_userspace_memory_region),
- "::",
- stringify!(userspace_addr)
- )
- );
+ pub slot: u32,
+ pub flags: u32,
+ pub guest_phys_addr: u64,
+ pub memory_size: u64,
+ pub userspace_addr: u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_irq_level {
pub __bindgen_anon_1: kvm_irq_level__bindgen_ty_1,
- pub level: __u32,
+ pub level: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_irq_level__bindgen_ty_1 {
- pub irq: __u32,
- pub status: __s32,
- _bindgen_union_align: u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_irq_level__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_level__bindgen_ty_1>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_irq_level__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_level__bindgen_ty_1>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_irq_level__bindgen_ty_1))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_level__bindgen_ty_1>())).irq as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_level__bindgen_ty_1),
- "::",
- stringify!(irq)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_level__bindgen_ty_1>())).status as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_level__bindgen_ty_1),
- "::",
- stringify!(status)
- )
- );
+ pub irq: u32,
+ pub status: i32,
}
impl Default for kvm_irq_level__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_irq_level() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_level>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_irq_level))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_level>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_irq_level))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_level>())).level as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_level),
- "::",
- stringify!(level)
- )
- );
-}
impl Default for kvm_irq_level {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_irqchip {
- pub chip_id: __u32,
- pub pad: __u32,
+ pub chip_id: u32,
+ pub pad: u32,
pub chip: kvm_irqchip__bindgen_ty_1,
}
#[repr(C)]
@@ -4433,353 +1365,78 @@ pub union kvm_irqchip__bindgen_ty_1 {
pub dummy: [::std::os::raw::c_char; 512usize],
pub pic: kvm_pic_state,
pub ioapic: kvm_ioapic_state,
- _bindgen_union_align: [u64; 64usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_irqchip__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irqchip__bindgen_ty_1>(),
- 512usize,
- concat!("Size of: ", stringify!(kvm_irqchip__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irqchip__bindgen_ty_1>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_irqchip__bindgen_ty_1))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqchip__bindgen_ty_1>())).dummy as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqchip__bindgen_ty_1),
- "::",
- stringify!(dummy)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqchip__bindgen_ty_1>())).pic as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqchip__bindgen_ty_1),
- "::",
- stringify!(pic)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irqchip__bindgen_ty_1>())).ioapic as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqchip__bindgen_ty_1),
- "::",
- stringify!(ioapic)
- )
- );
}
impl Default for kvm_irqchip__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_irqchip() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irqchip>(),
- 520usize,
- concat!("Size of: ", stringify!(kvm_irqchip))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irqchip>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_irqchip))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqchip>())).chip_id as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqchip),
- "::",
- stringify!(chip_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqchip>())).pad as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqchip),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqchip>())).chip as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqchip),
- "::",
- stringify!(chip)
- )
- );
-}
impl Default for kvm_irqchip {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_pit_config {
- pub flags: __u32,
- pub pad: [__u32; 15usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_pit_config() {
- assert_eq!(
- ::std::mem::size_of::<kvm_pit_config>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_pit_config))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_pit_config>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_pit_config))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pit_config>())).flags as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_config),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_pit_config>())).pad as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_pit_config),
- "::",
- stringify!(pad)
- )
- );
+ pub flags: u32,
+ pub pad: [u32; 15usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_skeys {
- pub start_gfn: __u64,
- pub count: __u64,
- pub skeydata_addr: __u64,
- pub flags: __u32,
- pub reserved: [__u32; 9usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_skeys() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_skeys>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_s390_skeys))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_skeys>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_skeys))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_skeys>())).start_gfn as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_skeys),
- "::",
- stringify!(start_gfn)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_skeys>())).count as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_skeys),
- "::",
- stringify!(count)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_skeys>())).skeydata_addr as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_skeys),
- "::",
- stringify!(skeydata_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_skeys>())).flags as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_skeys),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_skeys>())).reserved as *const _ as usize },
- 28usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_skeys),
- "::",
- stringify!(reserved)
- )
- );
-}
-#[doc = " kvm_s390_cmma_log - Used for CMMA migration."]
-#[doc = ""]
-#[doc = " Used both for input and output."]
-#[doc = ""]
-#[doc = " @start_gfn: Guest page number to start from."]
-#[doc = " @count: Size of the result buffer."]
-#[doc = " @flags: Control operation mode via KVM_S390_CMMA_* flags"]
-#[doc = " @remaining: Used with KVM_S390_GET_CMMA_BITS. Indicates how many dirty"]
-#[doc = " pages are still remaining."]
-#[doc = " @mask: Used with KVM_S390_SET_CMMA_BITS. Bitmap of bits to actually set"]
-#[doc = " in the PGSTE."]
-#[doc = " @values: Pointer to the values buffer."]
-#[doc = ""]
-#[doc = " Used in KVM_S390_{G,S}ET_CMMA_BITS ioctls."]
+ pub start_gfn: u64,
+ pub count: u64,
+ pub skeydata_addr: u64,
+ pub flags: u32,
+ pub reserved: [u32; 9usize],
+}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_s390_cmma_log {
- pub start_gfn: __u64,
- pub count: __u32,
- pub flags: __u32,
+ pub start_gfn: u64,
+ pub count: u32,
+ pub flags: u32,
pub __bindgen_anon_1: kvm_s390_cmma_log__bindgen_ty_1,
- pub values: __u64,
+ pub values: u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_s390_cmma_log__bindgen_ty_1 {
- pub remaining: __u64,
- pub mask: __u64,
- _bindgen_union_align: u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_cmma_log__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_cmma_log__bindgen_ty_1>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_s390_cmma_log__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_cmma_log__bindgen_ty_1>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_cmma_log__bindgen_ty_1))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_cmma_log__bindgen_ty_1>())).remaining as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_cmma_log__bindgen_ty_1),
- "::",
- stringify!(remaining)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_cmma_log__bindgen_ty_1>())).mask as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_cmma_log__bindgen_ty_1),
- "::",
- stringify!(mask)
- )
- );
+ pub remaining: u64,
+ pub mask: u64,
}
impl Default for kvm_s390_cmma_log__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_s390_cmma_log() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_cmma_log>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_s390_cmma_log))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_cmma_log>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_cmma_log))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_cmma_log>())).start_gfn as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_cmma_log),
- "::",
- stringify!(start_gfn)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_cmma_log>())).count as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_cmma_log),
- "::",
- stringify!(count)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_cmma_log>())).flags as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_cmma_log),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_cmma_log>())).values as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_cmma_log),
- "::",
- stringify!(values)
- )
- );
-}
impl Default for kvm_s390_cmma_log {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_hyperv_exit {
- pub type_: __u32,
- pub pad1: __u32,
+ pub type_: u32,
+ pub pad1: u32,
pub u: kvm_hyperv_exit__bindgen_ty_1,
}
#[repr(C)]
@@ -4787,271 +1444,68 @@ pub struct kvm_hyperv_exit {
pub union kvm_hyperv_exit__bindgen_ty_1 {
pub synic: kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1,
pub hcall: kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2,
- _bindgen_union_align: [u64; 4usize],
+ pub syndbg: kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_3,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1 {
- pub msr: __u32,
- pub pad2: __u32,
- pub control: __u64,
- pub evt_page: __u64,
- pub msg_page: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1>(),
- 32usize,
- concat!(
- "Size of: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1>())).msr as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(msr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1>())).pad2 as *const _
- as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(pad2)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1>())).control
- as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(control)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1>())).evt_page
- as *const _ as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(evt_page)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1>())).msg_page
- as *const _ as usize
- },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(msg_page)
- )
- );
+ pub msr: u32,
+ pub pad2: u32,
+ pub control: u64,
+ pub evt_page: u64,
+ pub msg_page: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2 {
- pub input: __u64,
- pub result: __u64,
- pub params: [__u64; 2usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2() {
- assert_eq!(
- ::std::mem::size_of::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2>(),
- 32usize,
- concat!(
- "Size of: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2>())).input
- as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2),
- "::",
- stringify!(input)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2>())).result
- as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2),
- "::",
- stringify!(result)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2>())).params
- as *const _ as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_2),
- "::",
- stringify!(params)
- )
- );
-}
-#[test]
-fn bindgen_test_layout_kvm_hyperv_exit__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_hyperv_exit__bindgen_ty_1>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_hyperv_exit__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_hyperv_exit__bindgen_ty_1>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_hyperv_exit__bindgen_ty_1))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1>())).synic as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1),
- "::",
- stringify!(synic)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_hyperv_exit__bindgen_ty_1>())).hcall as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit__bindgen_ty_1),
- "::",
- stringify!(hcall)
- )
- );
+ pub input: u64,
+ pub result: u64,
+ pub params: [u64; 2usize],
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct kvm_hyperv_exit__bindgen_ty_1__bindgen_ty_3 {
+ pub msr: u32,
+ pub pad2: u32,
+ pub control: u64,
+ pub status: u64,
+ pub send_page: u64,
+ pub recv_page: u64,
+ pub pending_page: u64,
}
impl Default for kvm_hyperv_exit__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_hyperv_exit() {
- assert_eq!(
- ::std::mem::size_of::<kvm_hyperv_exit>(),
- 40usize,
- concat!("Size of: ", stringify!(kvm_hyperv_exit))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_hyperv_exit>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_hyperv_exit))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_hyperv_exit>())).type_ as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit),
- "::",
- stringify!(type_)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_hyperv_exit>())).pad1 as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit),
- "::",
- stringify!(pad1)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_hyperv_exit>())).u as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_exit),
- "::",
- stringify!(u)
- )
- );
-}
impl Default for kvm_hyperv_exit {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_run {
- pub request_interrupt_window: __u8,
- pub immediate_exit: __u8,
- pub padding1: [__u8; 6usize],
- pub exit_reason: __u32,
- pub ready_for_interrupt_injection: __u8,
- pub if_flag: __u8,
- pub flags: __u16,
- pub cr8: __u64,
- pub apic_base: __u64,
+ pub request_interrupt_window: u8,
+ pub immediate_exit: u8,
+ pub padding1: [u8; 6usize],
+ pub exit_reason: u32,
+ pub ready_for_interrupt_injection: u8,
+ pub if_flag: u8,
+ pub flags: u16,
+ pub cr8: u64,
+ pub apic_base: u64,
pub __bindgen_anon_1: kvm_run__bindgen_ty_1,
- pub kvm_valid_regs: __u64,
- pub kvm_dirty_regs: __u64,
+ pub kvm_valid_regs: u64,
+ pub kvm_dirty_regs: u64,
pub s: kvm_run__bindgen_ty_2,
}
#[repr(C)]
@@ -5066,7 +1520,7 @@ pub union kvm_run__bindgen_ty_1 {
pub hypercall: kvm_run__bindgen_ty_1__bindgen_ty_7,
pub tpr_access: kvm_run__bindgen_ty_1__bindgen_ty_8,
pub s390_sieic: kvm_run__bindgen_ty_1__bindgen_ty_9,
- pub s390_reset_flags: __u64,
+ pub s390_reset_flags: u64,
pub s390_ucontrol: kvm_run__bindgen_ty_1__bindgen_ty_10,
pub dcr: kvm_run__bindgen_ty_1__bindgen_ty_11,
pub internal: kvm_run__bindgen_ty_1__bindgen_ty_12,
@@ -5078,1400 +1532,162 @@ pub union kvm_run__bindgen_ty_1 {
pub s390_stsi: kvm_run__bindgen_ty_1__bindgen_ty_18,
pub eoi: kvm_run__bindgen_ty_1__bindgen_ty_19,
pub hyperv: kvm_hyperv_exit,
+ pub arm_nisv: kvm_run__bindgen_ty_1__bindgen_ty_20,
+ pub msr: kvm_run__bindgen_ty_1__bindgen_ty_21,
pub padding: [::std::os::raw::c_char; 256usize],
- _bindgen_union_align: [u64; 32usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_1 {
- pub hardware_exit_reason: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_1>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_1>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_1>())).hardware_exit_reason
- as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_1),
- "::",
- stringify!(hardware_exit_reason)
- )
- );
+ pub hardware_exit_reason: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_2 {
- pub hardware_entry_failure_reason: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_2() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_2>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_2))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_2>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_2)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_2>()))
- .hardware_entry_failure_reason as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_2),
- "::",
- stringify!(hardware_entry_failure_reason)
- )
- );
+ pub hardware_entry_failure_reason: u64,
+ pub cpu: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_3 {
- pub exception: __u32,
- pub error_code: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_3() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_3>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_3))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_3>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_3)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_3>())).exception as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_3),
- "::",
- stringify!(exception)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_3>())).error_code as *const _
- as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_3),
- "::",
- stringify!(error_code)
- )
- );
+ pub exception: u32,
+ pub error_code: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_4 {
- pub direction: __u8,
- pub size: __u8,
- pub port: __u16,
- pub count: __u32,
- pub data_offset: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_4() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_4>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_4))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_4>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_4)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_4>())).direction as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_4),
- "::",
- stringify!(direction)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_4>())).size as *const _
- as usize
- },
- 1usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_4),
- "::",
- stringify!(size)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_4>())).port as *const _
- as usize
- },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_4),
- "::",
- stringify!(port)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_4>())).count as *const _
- as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_4),
- "::",
- stringify!(count)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_4>())).data_offset as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_4),
- "::",
- stringify!(data_offset)
- )
- );
+ pub direction: u8,
+ pub size: u8,
+ pub port: u16,
+ pub count: u32,
+ pub data_offset: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_5 {
pub arch: kvm_debug_exit_arch,
}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_5() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_5>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_5))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_5>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_5)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_5>())).arch as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_5),
- "::",
- stringify!(arch)
- )
- );
-}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_6 {
- pub phys_addr: __u64,
- pub data: [__u8; 8usize],
- pub len: __u32,
- pub is_write: __u8,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_6() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_6>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_6))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_6>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_6)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_6>())).phys_addr as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_6),
- "::",
- stringify!(phys_addr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_6>())).data as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_6),
- "::",
- stringify!(data)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_6>())).len as *const _ as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_6),
- "::",
- stringify!(len)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_6>())).is_write as *const _
- as usize
- },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_6),
- "::",
- stringify!(is_write)
- )
- );
+ pub phys_addr: u64,
+ pub data: [u8; 8usize],
+ pub len: u32,
+ pub is_write: u8,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_7 {
- pub nr: __u64,
- pub args: [__u64; 6usize],
- pub ret: __u64,
- pub longmode: __u32,
- pub pad: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_7() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_7>(),
- 72usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_7))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_7>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_7)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_7>())).nr as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_7),
- "::",
- stringify!(nr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_7>())).args as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_7),
- "::",
- stringify!(args)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_7>())).ret as *const _ as usize
- },
- 56usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_7),
- "::",
- stringify!(ret)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_7>())).longmode as *const _
- as usize
- },
- 64usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_7),
- "::",
- stringify!(longmode)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_7>())).pad as *const _ as usize
- },
- 68usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_7),
- "::",
- stringify!(pad)
- )
- );
+ pub nr: u64,
+ pub args: [u64; 6usize],
+ pub ret: u64,
+ pub longmode: u32,
+ pub pad: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_8 {
- pub rip: __u64,
- pub is_write: __u32,
- pub pad: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_8() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_8>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_8))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_8>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_8)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_8>())).rip as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_8),
- "::",
- stringify!(rip)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_8>())).is_write as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_8),
- "::",
- stringify!(is_write)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_8>())).pad as *const _ as usize
- },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_8),
- "::",
- stringify!(pad)
- )
- );
+ pub rip: u64,
+ pub is_write: u32,
+ pub pad: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_9 {
- pub icptcode: __u8,
- pub ipa: __u16,
- pub ipb: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_9() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_9>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1__bindgen_ty_9))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_9>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_9)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_9>())).icptcode as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_9),
- "::",
- stringify!(icptcode)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_9>())).ipa as *const _ as usize
- },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_9),
- "::",
- stringify!(ipa)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_9>())).ipb as *const _ as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_9),
- "::",
- stringify!(ipb)
- )
- );
+ pub icptcode: u8,
+ pub ipa: u16,
+ pub ipb: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_10 {
- pub trans_exc_code: __u64,
- pub pgm_code: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_10() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_10>(),
- 16usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_10)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_10>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_10)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_10>())).trans_exc_code
- as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_10),
- "::",
- stringify!(trans_exc_code)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_10>())).pgm_code as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_10),
- "::",
- stringify!(pgm_code)
- )
- );
+ pub trans_exc_code: u64,
+ pub pgm_code: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_11 {
- pub dcrn: __u32,
- pub data: __u32,
- pub is_write: __u8,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_11() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_11>(),
- 12usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_11)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_11>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_11)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_11>())).dcrn as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_11),
- "::",
- stringify!(dcrn)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_11>())).data as *const _
- as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_11),
- "::",
- stringify!(data)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_11>())).is_write as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_11),
- "::",
- stringify!(is_write)
- )
- );
+ pub dcrn: u32,
+ pub data: u32,
+ pub is_write: u8,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_12 {
- pub suberror: __u32,
- pub ndata: __u32,
- pub data: [__u64; 16usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_12() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_12>(),
- 136usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_12)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_12>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_12)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_12>())).suberror as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_12),
- "::",
- stringify!(suberror)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_12>())).ndata as *const _
- as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_12),
- "::",
- stringify!(ndata)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_12>())).data as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_12),
- "::",
- stringify!(data)
- )
- );
+ pub suberror: u32,
+ pub ndata: u32,
+ pub data: [u64; 16usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_13 {
- pub gprs: [__u64; 32usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_13() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_13>(),
- 256usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_13)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_13>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_13)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_13>())).gprs as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_13),
- "::",
- stringify!(gprs)
- )
- );
+ pub gprs: [u64; 32usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_14 {
- pub nr: __u64,
- pub ret: __u64,
- pub args: [__u64; 9usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_14() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_14>(),
- 88usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_14)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_14>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_14)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_14>())).nr as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_14),
- "::",
- stringify!(nr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_14>())).ret as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_14),
- "::",
- stringify!(ret)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_14>())).args as *const _
- as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_14),
- "::",
- stringify!(args)
- )
- );
+ pub nr: u64,
+ pub ret: u64,
+ pub args: [u64; 9usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_15 {
- pub subchannel_id: __u16,
- pub subchannel_nr: __u16,
- pub io_int_parm: __u32,
- pub io_int_word: __u32,
- pub ipb: __u32,
- pub dequeued: __u8,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_15() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_15>(),
- 20usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_15>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_15>())).subchannel_id
- as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15),
- "::",
- stringify!(subchannel_id)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_15>())).subchannel_nr
- as *const _ as usize
- },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15),
- "::",
- stringify!(subchannel_nr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_15>())).io_int_parm as *const _
- as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15),
- "::",
- stringify!(io_int_parm)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_15>())).io_int_word as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15),
- "::",
- stringify!(io_int_word)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_15>())).ipb as *const _
- as usize
- },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15),
- "::",
- stringify!(ipb)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_15>())).dequeued as *const _
- as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_15),
- "::",
- stringify!(dequeued)
- )
- );
+ pub subchannel_id: u16,
+ pub subchannel_nr: u16,
+ pub io_int_parm: u32,
+ pub io_int_word: u32,
+ pub ipb: u32,
+ pub dequeued: u8,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_16 {
- pub epr: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_16() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_16>(),
- 4usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_16)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_16>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_16)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_16>())).epr as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_16),
- "::",
- stringify!(epr)
- )
- );
+ pub epr: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_17 {
- pub type_: __u32,
- pub flags: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_17() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_17>(),
- 16usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_17)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_17>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_17)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_17>())).type_ as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_17),
- "::",
- stringify!(type_)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_17>())).flags as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_17),
- "::",
- stringify!(flags)
- )
- );
+ pub type_: u32,
+ pub flags: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_18 {
- pub addr: __u64,
- pub ar: __u8,
- pub reserved: __u8,
- pub fc: __u8,
- pub sel1: __u8,
- pub sel2: __u16,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_18() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_18>(),
- 16usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_18>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_18>())).addr as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18),
- "::",
- stringify!(addr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_18>())).ar as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18),
- "::",
- stringify!(ar)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_18>())).reserved as *const _
- as usize
- },
- 9usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18),
- "::",
- stringify!(reserved)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_18>())).fc as *const _ as usize
- },
- 10usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18),
- "::",
- stringify!(fc)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_18>())).sel1 as *const _
- as usize
- },
- 11usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18),
- "::",
- stringify!(sel1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_18>())).sel2 as *const _
- as usize
- },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_18),
- "::",
- stringify!(sel2)
- )
- );
+ pub addr: u64,
+ pub ar: u8,
+ pub reserved: u8,
+ pub fc: u8,
+ pub sel1: u8,
+ pub sel2: u16,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_run__bindgen_ty_1__bindgen_ty_19 {
- pub vector: __u8,
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1__bindgen_ty_19() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1__bindgen_ty_19>(),
- 1usize,
- concat!(
- "Size of: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_19)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1__bindgen_ty_19>(),
- 1usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_19)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1__bindgen_ty_19>())).vector as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1__bindgen_ty_19),
- "::",
- stringify!(vector)
- )
- );
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_1>(),
- 256usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_1>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_run__bindgen_ty_1))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).hw as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(hw)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).fail_entry as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(fail_entry)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).ex as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(ex)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).io as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(io)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).debug as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(debug)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).mmio as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(mmio)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).hypercall as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(hypercall)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).tpr_access as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(tpr_access)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).s390_sieic as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(s390_sieic)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).s390_reset_flags as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(s390_reset_flags)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).s390_ucontrol as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(s390_ucontrol)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).dcr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(dcr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).internal as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(internal)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).osi as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(osi)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).papr_hcall as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(papr_hcall)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).s390_tsch as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(s390_tsch)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).epr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(epr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).system_event as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(system_event)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).s390_stsi as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(s390_stsi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).eoi as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(eoi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).hyperv as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(hyperv)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_1>())).padding as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_1),
- "::",
- stringify!(padding)
- )
- );
+ pub vector: u8,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct kvm_run__bindgen_ty_1__bindgen_ty_20 {
+ pub esr_iss: u64,
+ pub fault_ipa: u64,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct kvm_run__bindgen_ty_1__bindgen_ty_21 {
+ pub error: u8,
+ pub pad: [u8; 7usize],
+ pub reason: u32,
+ pub index: u32,
+ pub data: u64,
}
impl Default for kvm_run__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
@@ -6479,1577 +1695,318 @@ impl Default for kvm_run__bindgen_ty_1 {
pub union kvm_run__bindgen_ty_2 {
pub regs: kvm_sync_regs,
pub padding: [::std::os::raw::c_char; 2048usize],
- _bindgen_union_align: [u64; 256usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_run__bindgen_ty_2() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run__bindgen_ty_2>(),
- 2048usize,
- concat!("Size of: ", stringify!(kvm_run__bindgen_ty_2))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run__bindgen_ty_2>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_run__bindgen_ty_2))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_2>())).regs as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_2),
- "::",
- stringify!(regs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run__bindgen_ty_2>())).padding as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run__bindgen_ty_2),
- "::",
- stringify!(padding)
- )
- );
}
impl Default for kvm_run__bindgen_ty_2 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-#[test]
-fn bindgen_test_layout_kvm_run() {
- assert_eq!(
- ::std::mem::size_of::<kvm_run>(),
- 2352usize,
- concat!("Size of: ", stringify!(kvm_run))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_run>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_run))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_run>())).request_interrupt_window as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(request_interrupt_window)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).immediate_exit as *const _ as usize },
- 1usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(immediate_exit)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).padding1 as *const _ as usize },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(padding1)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).exit_reason as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(exit_reason)
- )
- );
- assert_eq!(
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
- &(*(::std::ptr::null::<kvm_run>())).ready_for_interrupt_injection as *const _ as usize
- },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(ready_for_interrupt_injection)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).if_flag as *const _ as usize },
- 13usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(if_flag)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).flags as *const _ as usize },
- 14usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).cr8 as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(cr8)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).apic_base as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(apic_base)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).kvm_valid_regs as *const _ as usize },
- 288usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(kvm_valid_regs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).kvm_dirty_regs as *const _ as usize },
- 296usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(kvm_dirty_regs)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_run>())).s as *const _ as usize },
- 304usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_run),
- "::",
- stringify!(s)
- )
- );
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
}
impl Default for kvm_run {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_coalesced_mmio_zone {
- pub addr: __u64,
- pub size: __u32,
+ pub addr: u64,
+ pub size: u32,
pub __bindgen_anon_1: kvm_coalesced_mmio_zone__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_coalesced_mmio_zone__bindgen_ty_1 {
- pub pad: __u32,
- pub pio: __u32,
- _bindgen_union_align: u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_coalesced_mmio_zone__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_coalesced_mmio_zone__bindgen_ty_1>(),
- 4usize,
- concat!(
- "Size of: ",
- stringify!(kvm_coalesced_mmio_zone__bindgen_ty_1)
- )
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_coalesced_mmio_zone__bindgen_ty_1>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_coalesced_mmio_zone__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_coalesced_mmio_zone__bindgen_ty_1>())).pad as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio_zone__bindgen_ty_1),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_coalesced_mmio_zone__bindgen_ty_1>())).pio as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio_zone__bindgen_ty_1),
- "::",
- stringify!(pio)
- )
- );
+ pub pad: u32,
+ pub pio: u32,
}
impl Default for kvm_coalesced_mmio_zone__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_coalesced_mmio_zone() {
- assert_eq!(
- ::std::mem::size_of::<kvm_coalesced_mmio_zone>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_coalesced_mmio_zone))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_coalesced_mmio_zone>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_coalesced_mmio_zone))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_coalesced_mmio_zone>())).addr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio_zone),
- "::",
- stringify!(addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_coalesced_mmio_zone>())).size as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio_zone),
- "::",
- stringify!(size)
- )
- );
-}
impl Default for kvm_coalesced_mmio_zone {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_coalesced_mmio {
- pub phys_addr: __u64,
- pub len: __u32,
+ pub phys_addr: u64,
+ pub len: u32,
pub __bindgen_anon_1: kvm_coalesced_mmio__bindgen_ty_1,
- pub data: [__u8; 8usize],
+ pub data: [u8; 8usize],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_coalesced_mmio__bindgen_ty_1 {
- pub pad: __u32,
- pub pio: __u32,
- _bindgen_union_align: u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_coalesced_mmio__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_coalesced_mmio__bindgen_ty_1>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_coalesced_mmio__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_coalesced_mmio__bindgen_ty_1>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_coalesced_mmio__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_coalesced_mmio__bindgen_ty_1>())).pad as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio__bindgen_ty_1),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_coalesced_mmio__bindgen_ty_1>())).pio as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio__bindgen_ty_1),
- "::",
- stringify!(pio)
- )
- );
+ pub pad: u32,
+ pub pio: u32,
}
impl Default for kvm_coalesced_mmio__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_coalesced_mmio() {
- assert_eq!(
- ::std::mem::size_of::<kvm_coalesced_mmio>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_coalesced_mmio))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_coalesced_mmio>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_coalesced_mmio))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_coalesced_mmio>())).phys_addr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio),
- "::",
- stringify!(phys_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_coalesced_mmio>())).len as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio),
- "::",
- stringify!(len)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_coalesced_mmio>())).data as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio),
- "::",
- stringify!(data)
- )
- );
-}
impl Default for kvm_coalesced_mmio {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
pub struct kvm_coalesced_mmio_ring {
- pub first: __u32,
- pub last: __u32,
+ pub first: u32,
+ pub last: u32,
pub coalesced_mmio: __IncompleteArrayField<kvm_coalesced_mmio>,
}
-#[test]
-fn bindgen_test_layout_kvm_coalesced_mmio_ring() {
- assert_eq!(
- ::std::mem::size_of::<kvm_coalesced_mmio_ring>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_coalesced_mmio_ring))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_coalesced_mmio_ring>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_coalesced_mmio_ring))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_coalesced_mmio_ring>())).first as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio_ring),
- "::",
- stringify!(first)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_coalesced_mmio_ring>())).last as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio_ring),
- "::",
- stringify!(last)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_coalesced_mmio_ring>())).coalesced_mmio as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_coalesced_mmio_ring),
- "::",
- stringify!(coalesced_mmio)
- )
- );
-}
impl Default for kvm_coalesced_mmio_ring {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_translation {
- pub linear_address: __u64,
- pub physical_address: __u64,
- pub valid: __u8,
- pub writeable: __u8,
- pub usermode: __u8,
- pub pad: [__u8; 5usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_translation() {
- assert_eq!(
- ::std::mem::size_of::<kvm_translation>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_translation))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_translation>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_translation))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_translation>())).linear_address as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_translation),
- "::",
- stringify!(linear_address)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_translation>())).physical_address as *const _ as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_translation),
- "::",
- stringify!(physical_address)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_translation>())).valid as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_translation),
- "::",
- stringify!(valid)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_translation>())).writeable as *const _ as usize },
- 17usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_translation),
- "::",
- stringify!(writeable)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_translation>())).usermode as *const _ as usize },
- 18usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_translation),
- "::",
- stringify!(usermode)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_translation>())).pad as *const _ as usize },
- 19usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_translation),
- "::",
- stringify!(pad)
- )
- );
+ pub linear_address: u64,
+ pub physical_address: u64,
+ pub valid: u8,
+ pub writeable: u8,
+ pub usermode: u8,
+ pub pad: [u8; 5usize],
}
#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
+#[derive(Copy, Clone)]
pub struct kvm_s390_mem_op {
- pub gaddr: __u64,
- pub flags: __u64,
- pub size: __u32,
- pub op: __u32,
- pub buf: __u64,
- pub ar: __u8,
- pub reserved: [__u8; 31usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_mem_op() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_mem_op>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_s390_mem_op))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_mem_op>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_mem_op))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mem_op>())).gaddr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mem_op),
- "::",
- stringify!(gaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mem_op>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mem_op),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mem_op>())).size as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mem_op),
- "::",
- stringify!(size)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mem_op>())).op as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mem_op),
- "::",
- stringify!(op)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mem_op>())).buf as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mem_op),
- "::",
- stringify!(buf)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mem_op>())).ar as *const _ as usize },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mem_op),
- "::",
- stringify!(ar)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mem_op>())).reserved as *const _ as usize },
- 33usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mem_op),
- "::",
- stringify!(reserved)
- )
- );
+ pub gaddr: u64,
+ pub flags: u64,
+ pub size: u32,
+ pub op: u32,
+ pub buf: u64,
+ pub __bindgen_anon_1: kvm_s390_mem_op__bindgen_ty_1,
+}
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union kvm_s390_mem_op__bindgen_ty_1 {
+ pub ar: u8,
+ pub sida_offset: u32,
+ pub reserved: [u8; 32usize],
+}
+impl Default for kvm_s390_mem_op__bindgen_ty_1 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+impl Default for kvm_s390_mem_op {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_interrupt {
- pub irq: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_interrupt() {
- assert_eq!(
- ::std::mem::size_of::<kvm_interrupt>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_interrupt))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_interrupt>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_interrupt))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_interrupt>())).irq as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_interrupt),
- "::",
- stringify!(irq)
- )
- );
+ pub irq: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_dirty_log {
- pub slot: __u32,
- pub padding1: __u32,
+ pub slot: u32,
+ pub padding1: u32,
pub __bindgen_anon_1: kvm_dirty_log__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_dirty_log__bindgen_ty_1 {
pub dirty_bitmap: *mut ::std::os::raw::c_void,
- pub padding2: __u64,
- _bindgen_union_align: u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_dirty_log__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_dirty_log__bindgen_ty_1>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_dirty_log__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_dirty_log__bindgen_ty_1>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_dirty_log__bindgen_ty_1))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_dirty_log__bindgen_ty_1>())).dirty_bitmap as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dirty_log__bindgen_ty_1),
- "::",
- stringify!(dirty_bitmap)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_dirty_log__bindgen_ty_1>())).padding2 as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dirty_log__bindgen_ty_1),
- "::",
- stringify!(padding2)
- )
- );
+ pub padding2: u64,
}
impl Default for kvm_dirty_log__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_dirty_log() {
- assert_eq!(
- ::std::mem::size_of::<kvm_dirty_log>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_dirty_log))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_dirty_log>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_dirty_log))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_dirty_log>())).slot as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dirty_log),
- "::",
- stringify!(slot)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_dirty_log>())).padding1 as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dirty_log),
- "::",
- stringify!(padding1)
- )
- );
-}
impl Default for kvm_dirty_log {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_clear_dirty_log {
- pub slot: __u32,
- pub num_pages: __u32,
- pub first_page: __u64,
+ pub slot: u32,
+ pub num_pages: u32,
+ pub first_page: u64,
pub __bindgen_anon_1: kvm_clear_dirty_log__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_clear_dirty_log__bindgen_ty_1 {
pub dirty_bitmap: *mut ::std::os::raw::c_void,
- pub padding2: __u64,
- _bindgen_union_align: u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_clear_dirty_log__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_clear_dirty_log__bindgen_ty_1>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_clear_dirty_log__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_clear_dirty_log__bindgen_ty_1>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_clear_dirty_log__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_clear_dirty_log__bindgen_ty_1>())).dirty_bitmap as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clear_dirty_log__bindgen_ty_1),
- "::",
- stringify!(dirty_bitmap)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_clear_dirty_log__bindgen_ty_1>())).padding2 as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clear_dirty_log__bindgen_ty_1),
- "::",
- stringify!(padding2)
- )
- );
+ pub padding2: u64,
}
impl Default for kvm_clear_dirty_log__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_clear_dirty_log() {
- assert_eq!(
- ::std::mem::size_of::<kvm_clear_dirty_log>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_clear_dirty_log))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_clear_dirty_log>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_clear_dirty_log))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_clear_dirty_log>())).slot as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clear_dirty_log),
- "::",
- stringify!(slot)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_clear_dirty_log>())).num_pages as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clear_dirty_log),
- "::",
- stringify!(num_pages)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_clear_dirty_log>())).first_page as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clear_dirty_log),
- "::",
- stringify!(first_page)
- )
- );
-}
impl Default for kvm_clear_dirty_log {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct kvm_signal_mask {
- pub len: __u32,
- pub sigset: __IncompleteArrayField<__u8>,
-}
-#[test]
-fn bindgen_test_layout_kvm_signal_mask() {
- assert_eq!(
- ::std::mem::size_of::<kvm_signal_mask>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_signal_mask))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_signal_mask>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_signal_mask))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_signal_mask>())).len as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_signal_mask),
- "::",
- stringify!(len)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_signal_mask>())).sigset as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_signal_mask),
- "::",
- stringify!(sigset)
- )
- );
+ pub len: u32,
+ pub sigset: __IncompleteArrayField<u8>,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_tpr_access_ctl {
- pub enabled: __u32,
- pub flags: __u32,
- pub reserved: [__u32; 8usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_tpr_access_ctl() {
- assert_eq!(
- ::std::mem::size_of::<kvm_tpr_access_ctl>(),
- 40usize,
- concat!("Size of: ", stringify!(kvm_tpr_access_ctl))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_tpr_access_ctl>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_tpr_access_ctl))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_tpr_access_ctl>())).enabled as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_tpr_access_ctl),
- "::",
- stringify!(enabled)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_tpr_access_ctl>())).flags as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_tpr_access_ctl),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_tpr_access_ctl>())).reserved as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_tpr_access_ctl),
- "::",
- stringify!(reserved)
- )
- );
+ pub enabled: u32,
+ pub flags: u32,
+ pub reserved: [u32; 8usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_vapic_addr {
- pub vapic_addr: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_vapic_addr() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vapic_addr>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_vapic_addr))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vapic_addr>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_vapic_addr))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vapic_addr>())).vapic_addr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vapic_addr),
- "::",
- stringify!(vapic_addr)
- )
- );
+ pub vapic_addr: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_mp_state {
- pub mp_state: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_mp_state() {
- assert_eq!(
- ::std::mem::size_of::<kvm_mp_state>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_mp_state))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_mp_state>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_mp_state))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_mp_state>())).mp_state as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_mp_state),
- "::",
- stringify!(mp_state)
- )
- );
+ pub mp_state: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_psw {
- pub mask: __u64,
- pub addr: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_psw() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_psw>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_s390_psw))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_psw>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_psw))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_psw>())).mask as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_psw),
- "::",
- stringify!(mask)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_psw>())).addr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_psw),
- "::",
- stringify!(addr)
- )
- );
+ pub mask: u64,
+ pub addr: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_interrupt {
- pub type_: __u32,
- pub parm: __u32,
- pub parm64: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_interrupt() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_interrupt>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_s390_interrupt))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_interrupt>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_interrupt))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_interrupt>())).type_ as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_interrupt),
- "::",
- stringify!(type_)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_interrupt>())).parm as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_interrupt),
- "::",
- stringify!(parm)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_interrupt>())).parm64 as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_interrupt),
- "::",
- stringify!(parm64)
- )
- );
+ pub type_: u32,
+ pub parm: u32,
+ pub parm64: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_io_info {
- pub subchannel_id: __u16,
- pub subchannel_nr: __u16,
- pub io_int_parm: __u32,
- pub io_int_word: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_io_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_io_info>(),
- 12usize,
- concat!("Size of: ", stringify!(kvm_s390_io_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_io_info>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_s390_io_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_io_info>())).subchannel_id as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_io_info),
- "::",
- stringify!(subchannel_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_io_info>())).subchannel_nr as *const _ as usize },
- 2usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_io_info),
- "::",
- stringify!(subchannel_nr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_io_info>())).io_int_parm as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_io_info),
- "::",
- stringify!(io_int_parm)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_io_info>())).io_int_word as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_io_info),
- "::",
- stringify!(io_int_word)
- )
- );
+ pub subchannel_id: u16,
+ pub subchannel_nr: u16,
+ pub io_int_parm: u32,
+ pub io_int_word: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_ext_info {
- pub ext_params: __u32,
- pub pad: __u32,
- pub ext_params2: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_ext_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_ext_info>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_s390_ext_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_ext_info>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_ext_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_ext_info>())).ext_params as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_ext_info),
- "::",
- stringify!(ext_params)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_ext_info>())).pad as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_ext_info),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_ext_info>())).ext_params2 as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_ext_info),
- "::",
- stringify!(ext_params2)
- )
- );
+ pub ext_params: u32,
+ pub pad: u32,
+ pub ext_params2: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_pgm_info {
- pub trans_exc_code: __u64,
- pub mon_code: __u64,
- pub per_address: __u64,
- pub data_exc_code: __u32,
- pub code: __u16,
- pub mon_class_nr: __u16,
- pub per_code: __u8,
- pub per_atmid: __u8,
- pub exc_access_id: __u8,
- pub per_access_id: __u8,
- pub op_access_id: __u8,
- pub flags: __u8,
- pub pad: [__u8; 2usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_pgm_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_pgm_info>(),
- 40usize,
- concat!("Size of: ", stringify!(kvm_s390_pgm_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_pgm_info>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_pgm_info))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_pgm_info>())).trans_exc_code as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(trans_exc_code)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).mon_code as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(mon_code)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).per_address as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(per_address)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).data_exc_code as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(data_exc_code)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).code as *const _ as usize },
- 28usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(code)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).mon_class_nr as *const _ as usize },
- 30usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(mon_class_nr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).per_code as *const _ as usize },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(per_code)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).per_atmid as *const _ as usize },
- 33usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(per_atmid)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).exc_access_id as *const _ as usize },
- 34usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(exc_access_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).per_access_id as *const _ as usize },
- 35usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(per_access_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).op_access_id as *const _ as usize },
- 36usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(op_access_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).flags as *const _ as usize },
- 37usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_pgm_info>())).pad as *const _ as usize },
- 38usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_pgm_info),
- "::",
- stringify!(pad)
- )
- );
+ pub trans_exc_code: u64,
+ pub mon_code: u64,
+ pub per_address: u64,
+ pub data_exc_code: u32,
+ pub code: u16,
+ pub mon_class_nr: u16,
+ pub per_code: u8,
+ pub per_atmid: u8,
+ pub exc_access_id: u8,
+ pub per_access_id: u8,
+ pub op_access_id: u8,
+ pub flags: u8,
+ pub pad: [u8; 2usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_prefix_info {
- pub address: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_prefix_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_prefix_info>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_s390_prefix_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_prefix_info>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_s390_prefix_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_prefix_info>())).address as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_prefix_info),
- "::",
- stringify!(address)
- )
- );
+ pub address: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_extcall_info {
- pub code: __u16,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_extcall_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_extcall_info>(),
- 2usize,
- concat!("Size of: ", stringify!(kvm_s390_extcall_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_extcall_info>(),
- 2usize,
- concat!("Alignment of ", stringify!(kvm_s390_extcall_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_extcall_info>())).code as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_extcall_info),
- "::",
- stringify!(code)
- )
- );
+ pub code: u16,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_emerg_info {
- pub code: __u16,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_emerg_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_emerg_info>(),
- 2usize,
- concat!("Size of: ", stringify!(kvm_s390_emerg_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_emerg_info>(),
- 2usize,
- concat!("Alignment of ", stringify!(kvm_s390_emerg_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_emerg_info>())).code as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_emerg_info),
- "::",
- stringify!(code)
- )
- );
+ pub code: u16,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_stop_info {
- pub flags: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_stop_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_stop_info>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_s390_stop_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_stop_info>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_s390_stop_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_stop_info>())).flags as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_stop_info),
- "::",
- stringify!(flags)
- )
- );
+ pub flags: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_mchk_info {
- pub cr14: __u64,
- pub mcic: __u64,
- pub failing_storage_address: __u64,
- pub ext_damage_code: __u32,
- pub pad: __u32,
- pub fixed_logout: [__u8; 16usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_mchk_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_mchk_info>(),
- 48usize,
- concat!("Size of: ", stringify!(kvm_s390_mchk_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_mchk_info>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_mchk_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mchk_info>())).cr14 as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mchk_info),
- "::",
- stringify!(cr14)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mchk_info>())).mcic as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mchk_info),
- "::",
- stringify!(mcic)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_mchk_info>())).failing_storage_address as *const _
- as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mchk_info),
- "::",
- stringify!(failing_storage_address)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_mchk_info>())).ext_damage_code as *const _ as usize
- },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mchk_info),
- "::",
- stringify!(ext_damage_code)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mchk_info>())).pad as *const _ as usize },
- 28usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mchk_info),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_mchk_info>())).fixed_logout as *const _ as usize },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_mchk_info),
- "::",
- stringify!(fixed_logout)
- )
- );
+ pub cr14: u64,
+ pub mcic: u64,
+ pub failing_storage_address: u64,
+ pub ext_damage_code: u32,
+ pub pad: u32,
+ pub fixed_logout: [u8; 16usize],
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_s390_irq {
- pub type_: __u64,
+ pub type_: u64,
pub u: kvm_s390_irq__bindgen_ty_1,
}
#[repr(C)]
@@ -8064,983 +2021,188 @@ pub union kvm_s390_irq__bindgen_ty_1 {
pub stop: kvm_s390_stop_info,
pub mchk: kvm_s390_mchk_info,
pub reserved: [::std::os::raw::c_char; 64usize],
- _bindgen_union_align: [u64; 8usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_irq__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_irq__bindgen_ty_1>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_s390_irq__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_irq__bindgen_ty_1>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_irq__bindgen_ty_1))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).io as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(io)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).ext as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(ext)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).pgm as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(pgm)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).emerg as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(emerg)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).extcall as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(extcall)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).prefix as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(prefix)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).stop as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(stop)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).mchk as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(mchk)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_s390_irq__bindgen_ty_1>())).reserved as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq__bindgen_ty_1),
- "::",
- stringify!(reserved)
- )
- );
}
impl Default for kvm_s390_irq__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_s390_irq() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_irq>(),
- 72usize,
- concat!("Size of: ", stringify!(kvm_s390_irq))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_irq>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_irq))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq>())).type_ as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq),
- "::",
- stringify!(type_)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq>())).u as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq),
- "::",
- stringify!(u)
- )
- );
-}
impl Default for kvm_s390_irq {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_irq_state {
- pub buf: __u64,
- pub flags: __u32,
- pub len: __u32,
- pub reserved: [__u32; 4usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_irq_state() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_irq_state>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_s390_irq_state))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_irq_state>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_irq_state))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq_state>())).buf as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq_state),
- "::",
- stringify!(buf)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq_state>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq_state),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq_state>())).len as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq_state),
- "::",
- stringify!(len)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_irq_state>())).reserved as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_irq_state),
- "::",
- stringify!(reserved)
- )
- );
+ pub buf: u64,
+ pub flags: u32,
+ pub len: u32,
+ pub reserved: [u32; 4usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_guest_debug {
- pub control: __u32,
- pub pad: __u32,
+ pub control: u32,
+ pub pad: u32,
pub arch: kvm_guest_debug_arch,
}
-#[test]
-fn bindgen_test_layout_kvm_guest_debug() {
- assert_eq!(
- ::std::mem::size_of::<kvm_guest_debug>(),
- 72usize,
- concat!("Size of: ", stringify!(kvm_guest_debug))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_guest_debug>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_guest_debug))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_guest_debug>())).control as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_guest_debug),
- "::",
- stringify!(control)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_guest_debug>())).pad as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_guest_debug),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_guest_debug>())).arch as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_guest_debug),
- "::",
- stringify!(arch)
- )
- );
-}
-pub const kvm_ioeventfd_flag_nr_datamatch: _bindgen_ty_1 = 0;
-pub const kvm_ioeventfd_flag_nr_pio: _bindgen_ty_1 = 1;
-pub const kvm_ioeventfd_flag_nr_deassign: _bindgen_ty_1 = 2;
-pub const kvm_ioeventfd_flag_nr_virtio_ccw_notify: _bindgen_ty_1 = 3;
-pub const kvm_ioeventfd_flag_nr_fast_mmio: _bindgen_ty_1 = 4;
-pub const kvm_ioeventfd_flag_nr_max: _bindgen_ty_1 = 5;
-pub type _bindgen_ty_1 = u32;
+pub const kvm_ioeventfd_flag_nr_datamatch: ::std::os::raw::c_uint = 0;
+pub const kvm_ioeventfd_flag_nr_pio: ::std::os::raw::c_uint = 1;
+pub const kvm_ioeventfd_flag_nr_deassign: ::std::os::raw::c_uint = 2;
+pub const kvm_ioeventfd_flag_nr_virtio_ccw_notify: ::std::os::raw::c_uint = 3;
+pub const kvm_ioeventfd_flag_nr_fast_mmio: ::std::os::raw::c_uint = 4;
+pub const kvm_ioeventfd_flag_nr_max: ::std::os::raw::c_uint = 5;
+pub type _bindgen_ty_1 = ::std::os::raw::c_uint;
#[repr(C)]
-#[derive(Copy, Clone)]
+#[derive(Debug, Copy, Clone)]
pub struct kvm_ioeventfd {
- pub datamatch: __u64,
- pub addr: __u64,
- pub len: __u32,
- pub fd: __s32,
- pub flags: __u32,
- pub pad: [__u8; 36usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_ioeventfd() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ioeventfd>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_ioeventfd))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ioeventfd>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_ioeventfd))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioeventfd>())).datamatch as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioeventfd),
- "::",
- stringify!(datamatch)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioeventfd>())).addr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioeventfd),
- "::",
- stringify!(addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioeventfd>())).len as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioeventfd),
- "::",
- stringify!(len)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioeventfd>())).fd as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioeventfd),
- "::",
- stringify!(fd)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioeventfd>())).flags as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioeventfd),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ioeventfd>())).pad as *const _ as usize },
- 28usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ioeventfd),
- "::",
- stringify!(pad)
- )
- );
+ pub datamatch: u64,
+ pub addr: u64,
+ pub len: u32,
+ pub fd: i32,
+ pub flags: u32,
+ pub pad: [u8; 36usize],
}
impl Default for kvm_ioeventfd {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
-#[derive(Copy, Clone)]
+#[derive(Debug, Copy, Clone)]
pub struct kvm_enable_cap {
- pub cap: __u32,
- pub flags: __u32,
- pub args: [__u64; 4usize],
- pub pad: [__u8; 64usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_enable_cap() {
- assert_eq!(
- ::std::mem::size_of::<kvm_enable_cap>(),
- 104usize,
- concat!("Size of: ", stringify!(kvm_enable_cap))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_enable_cap>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_enable_cap))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_enable_cap>())).cap as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_enable_cap),
- "::",
- stringify!(cap)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_enable_cap>())).flags as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_enable_cap),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_enable_cap>())).args as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_enable_cap),
- "::",
- stringify!(args)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_enable_cap>())).pad as *const _ as usize },
- 40usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_enable_cap),
- "::",
- stringify!(pad)
- )
- );
+ pub cap: u32,
+ pub flags: u32,
+ pub args: [u64; 4usize],
+ pub pad: [u8; 64usize],
}
impl Default for kvm_enable_cap {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
-#[derive(Copy, Clone)]
+#[derive(Debug, Copy, Clone)]
pub struct kvm_ppc_pvinfo {
- pub flags: __u32,
- pub hcall: [__u32; 4usize],
- pub pad: [__u8; 108usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_ppc_pvinfo() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ppc_pvinfo>(),
- 128usize,
- concat!("Size of: ", stringify!(kvm_ppc_pvinfo))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ppc_pvinfo>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_ppc_pvinfo))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_pvinfo>())).flags as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_pvinfo),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_pvinfo>())).hcall as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_pvinfo),
- "::",
- stringify!(hcall)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_pvinfo>())).pad as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_pvinfo),
- "::",
- stringify!(pad)
- )
- );
+ pub flags: u32,
+ pub hcall: [u32; 4usize],
+ pub pad: [u8; 108usize],
}
impl Default for kvm_ppc_pvinfo {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_ppc_one_page_size {
- pub page_shift: __u32,
- pub pte_enc: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_ppc_one_page_size() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ppc_one_page_size>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_ppc_one_page_size))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ppc_one_page_size>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_ppc_one_page_size))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_ppc_one_page_size>())).page_shift as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_one_page_size),
- "::",
- stringify!(page_shift)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_one_page_size>())).pte_enc as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_one_page_size),
- "::",
- stringify!(pte_enc)
- )
- );
+ pub page_shift: u32,
+ pub pte_enc: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_ppc_one_seg_page_size {
- pub page_shift: __u32,
- pub slb_enc: __u32,
+ pub page_shift: u32,
+ pub slb_enc: u32,
pub enc: [kvm_ppc_one_page_size; 8usize],
}
-#[test]
-fn bindgen_test_layout_kvm_ppc_one_seg_page_size() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ppc_one_seg_page_size>(),
- 72usize,
- concat!("Size of: ", stringify!(kvm_ppc_one_seg_page_size))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ppc_one_seg_page_size>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_ppc_one_seg_page_size))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_ppc_one_seg_page_size>())).page_shift as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_one_seg_page_size),
- "::",
- stringify!(page_shift)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_ppc_one_seg_page_size>())).slb_enc as *const _ as usize
- },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_one_seg_page_size),
- "::",
- stringify!(slb_enc)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_one_seg_page_size>())).enc as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_one_seg_page_size),
- "::",
- stringify!(enc)
- )
- );
-}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_ppc_smmu_info {
- pub flags: __u64,
- pub slb_size: __u32,
- pub data_keys: __u16,
- pub instr_keys: __u16,
+ pub flags: u64,
+ pub slb_size: u32,
+ pub data_keys: u16,
+ pub instr_keys: u16,
pub sps: [kvm_ppc_one_seg_page_size; 8usize],
}
-#[test]
-fn bindgen_test_layout_kvm_ppc_smmu_info() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ppc_smmu_info>(),
- 592usize,
- concat!("Size of: ", stringify!(kvm_ppc_smmu_info))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ppc_smmu_info>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_ppc_smmu_info))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_smmu_info>())).flags as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_smmu_info),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_smmu_info>())).slb_size as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_smmu_info),
- "::",
- stringify!(slb_size)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_smmu_info>())).data_keys as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_smmu_info),
- "::",
- stringify!(data_keys)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_smmu_info>())).instr_keys as *const _ as usize },
- 14usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_smmu_info),
- "::",
- stringify!(instr_keys)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_smmu_info>())).sps as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_smmu_info),
- "::",
- stringify!(sps)
- )
- );
-}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_ppc_resize_hpt {
- pub flags: __u64,
- pub shift: __u32,
- pub pad: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_ppc_resize_hpt() {
- assert_eq!(
- ::std::mem::size_of::<kvm_ppc_resize_hpt>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_ppc_resize_hpt))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_ppc_resize_hpt>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_ppc_resize_hpt))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_resize_hpt>())).flags as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_resize_hpt),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_resize_hpt>())).shift as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_resize_hpt),
- "::",
- stringify!(shift)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_ppc_resize_hpt>())).pad as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_ppc_resize_hpt),
- "::",
- stringify!(pad)
- )
- );
+ pub flags: u64,
+ pub shift: u32,
+ pub pad: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_irq_routing_irqchip {
- pub irqchip: __u32,
- pub pin: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing_irqchip() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing_irqchip>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_irq_routing_irqchip))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing_irqchip>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_irq_routing_irqchip))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_irqchip>())).irqchip as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_irqchip),
- "::",
- stringify!(irqchip)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_irqchip>())).pin as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_irqchip),
- "::",
- stringify!(pin)
- )
- );
+ pub irqchip: u32,
+ pub pin: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_irq_routing_msi {
- pub address_lo: __u32,
- pub address_hi: __u32,
- pub data: __u32,
+ pub address_lo: u32,
+ pub address_hi: u32,
+ pub data: u32,
pub __bindgen_anon_1: kvm_irq_routing_msi__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_irq_routing_msi__bindgen_ty_1 {
- pub pad: __u32,
- pub devid: __u32,
- _bindgen_union_align: u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing_msi__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing_msi__bindgen_ty_1>(),
- 4usize,
- concat!("Size of: ", stringify!(kvm_irq_routing_msi__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing_msi__bindgen_ty_1>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_irq_routing_msi__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_msi__bindgen_ty_1>())).pad as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_msi__bindgen_ty_1),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_msi__bindgen_ty_1>())).devid as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_msi__bindgen_ty_1),
- "::",
- stringify!(devid)
- )
- );
+ pub pad: u32,
+ pub devid: u32,
}
impl Default for kvm_irq_routing_msi__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing_msi() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing_msi>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_irq_routing_msi))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing_msi>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_irq_routing_msi))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_msi>())).address_lo as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_msi),
- "::",
- stringify!(address_lo)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_msi>())).address_hi as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_msi),
- "::",
- stringify!(address_hi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_msi>())).data as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_msi),
- "::",
- stringify!(data)
- )
- );
-}
impl Default for kvm_irq_routing_msi {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_irq_routing_s390_adapter {
- pub ind_addr: __u64,
- pub summary_addr: __u64,
- pub ind_offset: __u64,
- pub summary_offset: __u32,
- pub adapter_id: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing_s390_adapter() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing_s390_adapter>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_irq_routing_s390_adapter))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing_s390_adapter>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_irq_routing_s390_adapter))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_s390_adapter>())).ind_addr as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_s390_adapter),
- "::",
- stringify!(ind_addr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_s390_adapter>())).summary_addr as *const _
- as usize
- },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_s390_adapter),
- "::",
- stringify!(summary_addr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_s390_adapter>())).ind_offset as *const _ as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_s390_adapter),
- "::",
- stringify!(ind_offset)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_s390_adapter>())).summary_offset as *const _
- as usize
- },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_s390_adapter),
- "::",
- stringify!(summary_offset)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_s390_adapter>())).adapter_id as *const _ as usize
- },
- 28usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_s390_adapter),
- "::",
- stringify!(adapter_id)
- )
- );
+ pub ind_addr: u64,
+ pub summary_addr: u64,
+ pub ind_offset: u64,
+ pub summary_offset: u32,
+ pub adapter_id: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_irq_routing_hv_sint {
- pub vcpu: __u32,
- pub sint: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing_hv_sint() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing_hv_sint>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_irq_routing_hv_sint))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing_hv_sint>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_irq_routing_hv_sint))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_hv_sint>())).vcpu as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_hv_sint),
- "::",
- stringify!(vcpu)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_hv_sint>())).sint as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_hv_sint),
- "::",
- stringify!(sint)
- )
- );
+ pub vcpu: u32,
+ pub sint: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_irq_routing_entry {
- pub gsi: __u32,
- pub type_: __u32,
- pub flags: __u32,
- pub pad: __u32,
+ pub gsi: u32,
+ pub type_: u32,
+ pub flags: u32,
+ pub pad: u32,
pub u: kvm_irq_routing_entry__bindgen_ty_1,
}
#[repr(C)]
@@ -9050,935 +2212,135 @@ pub union kvm_irq_routing_entry__bindgen_ty_1 {
pub msi: kvm_irq_routing_msi,
pub adapter: kvm_irq_routing_s390_adapter,
pub hv_sint: kvm_irq_routing_hv_sint,
- pub pad: [__u32; 8usize],
- _bindgen_union_align: [u64; 4usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing_entry__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing_entry__bindgen_ty_1>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_irq_routing_entry__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing_entry__bindgen_ty_1>(),
- 8usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_irq_routing_entry__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_entry__bindgen_ty_1>())).irqchip as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry__bindgen_ty_1),
- "::",
- stringify!(irqchip)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_entry__bindgen_ty_1>())).msi as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry__bindgen_ty_1),
- "::",
- stringify!(msi)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_entry__bindgen_ty_1>())).adapter as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry__bindgen_ty_1),
- "::",
- stringify!(adapter)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_entry__bindgen_ty_1>())).hv_sint as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry__bindgen_ty_1),
- "::",
- stringify!(hv_sint)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_irq_routing_entry__bindgen_ty_1>())).pad as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry__bindgen_ty_1),
- "::",
- stringify!(pad)
- )
- );
+ pub pad: [u32; 8usize],
}
impl Default for kvm_irq_routing_entry__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing_entry() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing_entry>(),
- 48usize,
- concat!("Size of: ", stringify!(kvm_irq_routing_entry))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing_entry>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_irq_routing_entry))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_entry>())).gsi as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry),
- "::",
- stringify!(gsi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_entry>())).type_ as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry),
- "::",
- stringify!(type_)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_entry>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_entry>())).pad as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry),
- "::",
- stringify!(pad)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing_entry>())).u as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing_entry),
- "::",
- stringify!(u)
- )
- );
-}
impl Default for kvm_irq_routing_entry {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
pub struct kvm_irq_routing {
- pub nr: __u32,
- pub flags: __u32,
+ pub nr: u32,
+ pub flags: u32,
pub entries: __IncompleteArrayField<kvm_irq_routing_entry>,
}
-#[test]
-fn bindgen_test_layout_kvm_irq_routing() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irq_routing>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_irq_routing))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irq_routing>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_irq_routing))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing>())).nr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing),
- "::",
- stringify!(nr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing>())).flags as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irq_routing>())).entries as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irq_routing),
- "::",
- stringify!(entries)
- )
- );
-}
impl Default for kvm_irq_routing {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_x86_mce {
- pub status: __u64,
- pub addr: __u64,
- pub misc: __u64,
- pub mcg_status: __u64,
- pub bank: __u8,
- pub pad1: [__u8; 7usize],
- pub pad2: [__u64; 3usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_x86_mce() {
- assert_eq!(
- ::std::mem::size_of::<kvm_x86_mce>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_x86_mce))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_x86_mce>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_x86_mce))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_x86_mce>())).status as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_x86_mce),
- "::",
- stringify!(status)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_x86_mce>())).addr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_x86_mce),
- "::",
- stringify!(addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_x86_mce>())).misc as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_x86_mce),
- "::",
- stringify!(misc)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_x86_mce>())).mcg_status as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_x86_mce),
- "::",
- stringify!(mcg_status)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_x86_mce>())).bank as *const _ as usize },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_x86_mce),
- "::",
- stringify!(bank)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_x86_mce>())).pad1 as *const _ as usize },
- 33usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_x86_mce),
- "::",
- stringify!(pad1)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_x86_mce>())).pad2 as *const _ as usize },
- 40usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_x86_mce),
- "::",
- stringify!(pad2)
- )
- );
+ pub status: u64,
+ pub addr: u64,
+ pub misc: u64,
+ pub mcg_status: u64,
+ pub bank: u8,
+ pub pad1: [u8; 7usize],
+ pub pad2: [u64; 3usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_xen_hvm_config {
- pub flags: __u32,
- pub msr: __u32,
- pub blob_addr_32: __u64,
- pub blob_addr_64: __u64,
- pub blob_size_32: __u8,
- pub blob_size_64: __u8,
- pub pad2: [__u8; 30usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_xen_hvm_config() {
- assert_eq!(
- ::std::mem::size_of::<kvm_xen_hvm_config>(),
- 56usize,
- concat!("Size of: ", stringify!(kvm_xen_hvm_config))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_xen_hvm_config>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_xen_hvm_config))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xen_hvm_config>())).flags as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xen_hvm_config),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xen_hvm_config>())).msr as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xen_hvm_config),
- "::",
- stringify!(msr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xen_hvm_config>())).blob_addr_32 as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xen_hvm_config),
- "::",
- stringify!(blob_addr_32)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xen_hvm_config>())).blob_addr_64 as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xen_hvm_config),
- "::",
- stringify!(blob_addr_64)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xen_hvm_config>())).blob_size_32 as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xen_hvm_config),
- "::",
- stringify!(blob_size_32)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xen_hvm_config>())).blob_size_64 as *const _ as usize },
- 25usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xen_hvm_config),
- "::",
- stringify!(blob_size_64)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_xen_hvm_config>())).pad2 as *const _ as usize },
- 26usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_xen_hvm_config),
- "::",
- stringify!(pad2)
- )
- );
+ pub flags: u32,
+ pub msr: u32,
+ pub blob_addr_32: u64,
+ pub blob_addr_64: u64,
+ pub blob_size_32: u8,
+ pub blob_size_64: u8,
+ pub pad2: [u8; 30usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_irqfd {
- pub fd: __u32,
- pub gsi: __u32,
- pub flags: __u32,
- pub resamplefd: __u32,
- pub pad: [__u8; 16usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_irqfd() {
- assert_eq!(
- ::std::mem::size_of::<kvm_irqfd>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_irqfd))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_irqfd>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_irqfd))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqfd>())).fd as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqfd),
- "::",
- stringify!(fd)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqfd>())).gsi as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqfd),
- "::",
- stringify!(gsi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqfd>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqfd),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqfd>())).resamplefd as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqfd),
- "::",
- stringify!(resamplefd)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_irqfd>())).pad as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_irqfd),
- "::",
- stringify!(pad)
- )
- );
+ pub fd: u32,
+ pub gsi: u32,
+ pub flags: u32,
+ pub resamplefd: u32,
+ pub pad: [u8; 16usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_clock_data {
- pub clock: __u64,
- pub flags: __u32,
- pub pad: [__u32; 9usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_clock_data() {
- assert_eq!(
- ::std::mem::size_of::<kvm_clock_data>(),
- 48usize,
- concat!("Size of: ", stringify!(kvm_clock_data))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_clock_data>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_clock_data))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_clock_data>())).clock as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clock_data),
- "::",
- stringify!(clock)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_clock_data>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clock_data),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_clock_data>())).pad as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_clock_data),
- "::",
- stringify!(pad)
- )
- );
+ pub clock: u64,
+ pub flags: u32,
+ pub pad: [u32; 9usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_config_tlb {
- pub params: __u64,
- pub array: __u64,
- pub mmu_type: __u32,
- pub array_len: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_config_tlb() {
- assert_eq!(
- ::std::mem::size_of::<kvm_config_tlb>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_config_tlb))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_config_tlb>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_config_tlb))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_config_tlb>())).params as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_config_tlb),
- "::",
- stringify!(params)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_config_tlb>())).array as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_config_tlb),
- "::",
- stringify!(array)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_config_tlb>())).mmu_type as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_config_tlb),
- "::",
- stringify!(mmu_type)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_config_tlb>())).array_len as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_config_tlb),
- "::",
- stringify!(array_len)
- )
- );
+ pub params: u64,
+ pub array: u64,
+ pub mmu_type: u32,
+ pub array_len: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_dirty_tlb {
- pub bitmap: __u64,
- pub num_dirty: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_dirty_tlb() {
- assert_eq!(
- ::std::mem::size_of::<kvm_dirty_tlb>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_dirty_tlb))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_dirty_tlb>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_dirty_tlb))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_dirty_tlb>())).bitmap as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dirty_tlb),
- "::",
- stringify!(bitmap)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_dirty_tlb>())).num_dirty as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_dirty_tlb),
- "::",
- stringify!(num_dirty)
- )
- );
+ pub bitmap: u64,
+ pub num_dirty: u32,
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct kvm_reg_list {
- pub n: __u64,
- pub reg: __IncompleteArrayField<__u64>,
-}
-#[test]
-fn bindgen_test_layout_kvm_reg_list() {
- assert_eq!(
- ::std::mem::size_of::<kvm_reg_list>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_reg_list))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_reg_list>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_reg_list))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_reg_list>())).n as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_reg_list),
- "::",
- stringify!(n)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_reg_list>())).reg as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_reg_list),
- "::",
- stringify!(reg)
- )
- );
+ pub n: u64,
+ pub reg: __IncompleteArrayField<u64>,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_one_reg {
- pub id: __u64,
- pub addr: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_one_reg() {
- assert_eq!(
- ::std::mem::size_of::<kvm_one_reg>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_one_reg))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_one_reg>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_one_reg))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_one_reg>())).id as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_one_reg),
- "::",
- stringify!(id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_one_reg>())).addr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_one_reg),
- "::",
- stringify!(addr)
- )
- );
+ pub id: u64,
+ pub addr: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_msi {
- pub address_lo: __u32,
- pub address_hi: __u32,
- pub data: __u32,
- pub flags: __u32,
- pub devid: __u32,
- pub pad: [__u8; 12usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_msi() {
- assert_eq!(
- ::std::mem::size_of::<kvm_msi>(),
- 32usize,
- concat!("Size of: ", stringify!(kvm_msi))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_msi>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_msi))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msi>())).address_lo as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msi),
- "::",
- stringify!(address_lo)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msi>())).address_hi as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msi),
- "::",
- stringify!(address_hi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msi>())).data as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msi),
- "::",
- stringify!(data)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msi>())).flags as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msi),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msi>())).devid as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msi),
- "::",
- stringify!(devid)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_msi>())).pad as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_msi),
- "::",
- stringify!(pad)
- )
- );
+ pub address_lo: u32,
+ pub address_hi: u32,
+ pub data: u32,
+ pub flags: u32,
+ pub devid: u32,
+ pub pad: [u8; 12usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_arm_device_addr {
- pub id: __u64,
- pub addr: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_arm_device_addr() {
- assert_eq!(
- ::std::mem::size_of::<kvm_arm_device_addr>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_arm_device_addr))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_arm_device_addr>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_arm_device_addr))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_arm_device_addr>())).id as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_arm_device_addr),
- "::",
- stringify!(id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_arm_device_addr>())).addr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_arm_device_addr),
- "::",
- stringify!(addr)
- )
- );
+ pub id: u64,
+ pub addr: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_create_device {
- pub type_: __u32,
- pub fd: __u32,
- pub flags: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_create_device() {
- assert_eq!(
- ::std::mem::size_of::<kvm_create_device>(),
- 12usize,
- concat!("Size of: ", stringify!(kvm_create_device))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_create_device>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_create_device))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_create_device>())).type_ as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_create_device),
- "::",
- stringify!(type_)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_create_device>())).fd as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_create_device),
- "::",
- stringify!(fd)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_create_device>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_create_device),
- "::",
- stringify!(flags)
- )
- );
+ pub type_: u32,
+ pub fd: u32,
+ pub flags: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_device_attr {
- pub flags: __u32,
- pub group: __u32,
- pub attr: __u64,
- pub addr: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_device_attr() {
- assert_eq!(
- ::std::mem::size_of::<kvm_device_attr>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_device_attr))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_device_attr>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_device_attr))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_device_attr>())).flags as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_device_attr),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_device_attr>())).group as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_device_attr),
- "::",
- stringify!(group)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_device_attr>())).attr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_device_attr),
- "::",
- stringify!(attr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_device_attr>())).addr as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_device_attr),
- "::",
- stringify!(addr)
- )
- );
+ pub flags: u32,
+ pub group: u32,
+ pub attr: u64,
+ pub addr: u64,
}
pub const kvm_device_type_KVM_DEV_TYPE_FSL_MPIC_20: kvm_device_type = 1;
pub const kvm_device_type_KVM_DEV_TYPE_FSL_MPIC_42: kvm_device_type = 2;
@@ -9989,135 +2351,58 @@ pub const kvm_device_type_KVM_DEV_TYPE_FLIC: kvm_device_type = 6;
pub const kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_V3: kvm_device_type = 7;
pub const kvm_device_type_KVM_DEV_TYPE_ARM_VGIC_ITS: kvm_device_type = 8;
pub const kvm_device_type_KVM_DEV_TYPE_XIVE: kvm_device_type = 9;
-pub const kvm_device_type_KVM_DEV_TYPE_MAX: kvm_device_type = 10;
-pub type kvm_device_type = u32;
+pub const kvm_device_type_KVM_DEV_TYPE_ARM_PV_TIME: kvm_device_type = 10;
+pub const kvm_device_type_KVM_DEV_TYPE_MAX: kvm_device_type = 11;
+pub type kvm_device_type = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_vfio_spapr_tce {
- pub groupfd: __s32,
- pub tablefd: __s32,
-}
-#[test]
-fn bindgen_test_layout_kvm_vfio_spapr_tce() {
- assert_eq!(
- ::std::mem::size_of::<kvm_vfio_spapr_tce>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_vfio_spapr_tce))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_vfio_spapr_tce>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_vfio_spapr_tce))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vfio_spapr_tce>())).groupfd as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vfio_spapr_tce),
- "::",
- stringify!(groupfd)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_vfio_spapr_tce>())).tablefd as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_vfio_spapr_tce),
- "::",
- stringify!(tablefd)
- )
- );
+ pub groupfd: i32,
+ pub tablefd: i32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_s390_ucas_mapping {
- pub user_addr: __u64,
- pub vcpu_addr: __u64,
- pub length: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_s390_ucas_mapping() {
- assert_eq!(
- ::std::mem::size_of::<kvm_s390_ucas_mapping>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_s390_ucas_mapping))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_s390_ucas_mapping>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_s390_ucas_mapping))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_ucas_mapping>())).user_addr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_ucas_mapping),
- "::",
- stringify!(user_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_ucas_mapping>())).vcpu_addr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_ucas_mapping),
- "::",
- stringify!(vcpu_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_s390_ucas_mapping>())).length as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_s390_ucas_mapping),
- "::",
- stringify!(length)
- )
- );
+ pub user_addr: u64,
+ pub vcpu_addr: u64,
+ pub length: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_enc_region {
- pub addr: __u64,
- pub size: __u64,
-}
-#[test]
-fn bindgen_test_layout_kvm_enc_region() {
- assert_eq!(
- ::std::mem::size_of::<kvm_enc_region>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_enc_region))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_enc_region>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_enc_region))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_enc_region>())).addr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_enc_region),
- "::",
- stringify!(addr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_enc_region>())).size as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_enc_region),
- "::",
- stringify!(size)
- )
- );
+ pub addr: u64,
+ pub size: u64,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct kvm_s390_pv_sec_parm {
+ pub origin: u64,
+ pub length: u64,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct kvm_s390_pv_unp {
+ pub addr: u64,
+ pub size: u64,
+ pub tweak: u64,
+}
+pub const pv_cmd_id_KVM_PV_ENABLE: pv_cmd_id = 0;
+pub const pv_cmd_id_KVM_PV_DISABLE: pv_cmd_id = 1;
+pub const pv_cmd_id_KVM_PV_SET_SEC_PARMS: pv_cmd_id = 2;
+pub const pv_cmd_id_KVM_PV_UNPACK: pv_cmd_id = 3;
+pub const pv_cmd_id_KVM_PV_VERIFY: pv_cmd_id = 4;
+pub const pv_cmd_id_KVM_PV_PREP_RESET: pv_cmd_id = 5;
+pub const pv_cmd_id_KVM_PV_UNSHARE_ALL: pv_cmd_id = 6;
+pub type pv_cmd_id = ::std::os::raw::c_uint;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct kvm_pv_cmd {
+ pub cmd: u32,
+ pub rc: u16,
+ pub rrc: u16,
+ pub data: u64,
+ pub flags: u32,
+ pub reserved: [u32; 3usize],
}
pub const sev_cmd_id_KVM_SEV_INIT: sev_cmd_id = 0;
pub const sev_cmd_id_KVM_SEV_ES_INIT: sev_cmd_id = 1;
@@ -10140,820 +2425,146 @@ pub const sev_cmd_id_KVM_SEV_DBG_DECRYPT: sev_cmd_id = 17;
pub const sev_cmd_id_KVM_SEV_DBG_ENCRYPT: sev_cmd_id = 18;
pub const sev_cmd_id_KVM_SEV_CERT_EXPORT: sev_cmd_id = 19;
pub const sev_cmd_id_KVM_SEV_NR_MAX: sev_cmd_id = 20;
-pub type sev_cmd_id = u32;
+pub type sev_cmd_id = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sev_cmd {
- pub id: __u32,
- pub data: __u64,
- pub error: __u32,
- pub sev_fd: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_sev_cmd() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sev_cmd>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_sev_cmd))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sev_cmd>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sev_cmd))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_cmd>())).id as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_cmd),
- "::",
- stringify!(id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_cmd>())).data as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_cmd),
- "::",
- stringify!(data)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_cmd>())).error as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_cmd),
- "::",
- stringify!(error)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_cmd>())).sev_fd as *const _ as usize },
- 20usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_cmd),
- "::",
- stringify!(sev_fd)
- )
- );
+ pub id: u32,
+ pub data: u64,
+ pub error: u32,
+ pub sev_fd: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sev_launch_start {
- pub handle: __u32,
- pub policy: __u32,
- pub dh_uaddr: __u64,
- pub dh_len: __u32,
- pub session_uaddr: __u64,
- pub session_len: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_sev_launch_start() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sev_launch_start>(),
- 40usize,
- concat!("Size of: ", stringify!(kvm_sev_launch_start))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sev_launch_start>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sev_launch_start))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_start>())).handle as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_start),
- "::",
- stringify!(handle)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_start>())).policy as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_start),
- "::",
- stringify!(policy)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_start>())).dh_uaddr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_start),
- "::",
- stringify!(dh_uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_start>())).dh_len as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_start),
- "::",
- stringify!(dh_len)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_sev_launch_start>())).session_uaddr as *const _ as usize
- },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_start),
- "::",
- stringify!(session_uaddr)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_sev_launch_start>())).session_len as *const _ as usize
- },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_start),
- "::",
- stringify!(session_len)
- )
- );
+ pub handle: u32,
+ pub policy: u32,
+ pub dh_uaddr: u64,
+ pub dh_len: u32,
+ pub session_uaddr: u64,
+ pub session_len: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sev_launch_update_data {
- pub uaddr: __u64,
- pub len: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_sev_launch_update_data() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sev_launch_update_data>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_sev_launch_update_data))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sev_launch_update_data>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sev_launch_update_data))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_sev_launch_update_data>())).uaddr as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_update_data),
- "::",
- stringify!(uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_update_data>())).len as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_update_data),
- "::",
- stringify!(len)
- )
- );
+ pub uaddr: u64,
+ pub len: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sev_launch_secret {
- pub hdr_uaddr: __u64,
- pub hdr_len: __u32,
- pub guest_uaddr: __u64,
- pub guest_len: __u32,
- pub trans_uaddr: __u64,
- pub trans_len: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_sev_launch_secret() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sev_launch_secret>(),
- 48usize,
- concat!("Size of: ", stringify!(kvm_sev_launch_secret))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sev_launch_secret>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sev_launch_secret))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_secret>())).hdr_uaddr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_secret),
- "::",
- stringify!(hdr_uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_secret>())).hdr_len as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_secret),
- "::",
- stringify!(hdr_len)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_sev_launch_secret>())).guest_uaddr as *const _ as usize
- },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_secret),
- "::",
- stringify!(guest_uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_secret>())).guest_len as *const _ as usize },
- 24usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_secret),
- "::",
- stringify!(guest_len)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_sev_launch_secret>())).trans_uaddr as *const _ as usize
- },
- 32usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_secret),
- "::",
- stringify!(trans_uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_secret>())).trans_len as *const _ as usize },
- 40usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_secret),
- "::",
- stringify!(trans_len)
- )
- );
+ pub hdr_uaddr: u64,
+ pub hdr_len: u32,
+ pub guest_uaddr: u64,
+ pub guest_len: u32,
+ pub trans_uaddr: u64,
+ pub trans_len: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sev_launch_measure {
- pub uaddr: __u64,
- pub len: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_sev_launch_measure() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sev_launch_measure>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_sev_launch_measure))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sev_launch_measure>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sev_launch_measure))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_measure>())).uaddr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_measure),
- "::",
- stringify!(uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_launch_measure>())).len as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_launch_measure),
- "::",
- stringify!(len)
- )
- );
+ pub uaddr: u64,
+ pub len: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sev_guest_status {
- pub handle: __u32,
- pub policy: __u32,
- pub state: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_sev_guest_status() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sev_guest_status>(),
- 12usize,
- concat!("Size of: ", stringify!(kvm_sev_guest_status))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sev_guest_status>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_sev_guest_status))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_guest_status>())).handle as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_guest_status),
- "::",
- stringify!(handle)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_guest_status>())).policy as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_guest_status),
- "::",
- stringify!(policy)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_guest_status>())).state as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_guest_status),
- "::",
- stringify!(state)
- )
- );
+ pub handle: u32,
+ pub policy: u32,
+ pub state: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_sev_dbg {
- pub src_uaddr: __u64,
- pub dst_uaddr: __u64,
- pub len: __u32,
-}
-#[test]
-fn bindgen_test_layout_kvm_sev_dbg() {
- assert_eq!(
- ::std::mem::size_of::<kvm_sev_dbg>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_sev_dbg))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_sev_dbg>(),
- 8usize,
- concat!("Alignment of ", stringify!(kvm_sev_dbg))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_dbg>())).src_uaddr as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_dbg),
- "::",
- stringify!(src_uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_dbg>())).dst_uaddr as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_dbg),
- "::",
- stringify!(dst_uaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_sev_dbg>())).len as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_sev_dbg),
- "::",
- stringify!(len)
- )
- );
+ pub src_uaddr: u64,
+ pub dst_uaddr: u64,
+ pub len: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_assigned_pci_dev {
- pub assigned_dev_id: __u32,
- pub busnr: __u32,
- pub devfn: __u32,
- pub flags: __u32,
- pub segnr: __u32,
+ pub assigned_dev_id: u32,
+ pub busnr: u32,
+ pub devfn: u32,
+ pub flags: u32,
+ pub segnr: u32,
pub __bindgen_anon_1: kvm_assigned_pci_dev__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_assigned_pci_dev__bindgen_ty_1 {
- pub reserved: [__u32; 11usize],
- _bindgen_union_align: [u32; 11usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_assigned_pci_dev__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_assigned_pci_dev__bindgen_ty_1>(),
- 44usize,
- concat!("Size of: ", stringify!(kvm_assigned_pci_dev__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_assigned_pci_dev__bindgen_ty_1>(),
- 4usize,
- concat!(
- "Alignment of ",
- stringify!(kvm_assigned_pci_dev__bindgen_ty_1)
- )
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_assigned_pci_dev__bindgen_ty_1>())).reserved as *const _
- as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_pci_dev__bindgen_ty_1),
- "::",
- stringify!(reserved)
- )
- );
+ pub reserved: [u32; 11usize],
}
impl Default for kvm_assigned_pci_dev__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-#[test]
-fn bindgen_test_layout_kvm_assigned_pci_dev() {
- assert_eq!(
- ::std::mem::size_of::<kvm_assigned_pci_dev>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_assigned_pci_dev))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_assigned_pci_dev>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_assigned_pci_dev))
- );
- assert_eq!(
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
- &(*(::std::ptr::null::<kvm_assigned_pci_dev>())).assigned_dev_id as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_pci_dev),
- "::",
- stringify!(assigned_dev_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_pci_dev>())).busnr as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_pci_dev),
- "::",
- stringify!(busnr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_pci_dev>())).devfn as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_pci_dev),
- "::",
- stringify!(devfn)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_pci_dev>())).flags as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_pci_dev),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_pci_dev>())).segnr as *const _ as usize },
- 16usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_pci_dev),
- "::",
- stringify!(segnr)
- )
- );
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
}
impl Default for kvm_assigned_pci_dev {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct kvm_assigned_irq {
- pub assigned_dev_id: __u32,
- pub host_irq: __u32,
- pub guest_irq: __u32,
- pub flags: __u32,
+ pub assigned_dev_id: u32,
+ pub host_irq: u32,
+ pub guest_irq: u32,
+ pub flags: u32,
pub __bindgen_anon_1: kvm_assigned_irq__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union kvm_assigned_irq__bindgen_ty_1 {
- pub reserved: [__u32; 12usize],
- _bindgen_union_align: [u32; 12usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_assigned_irq__bindgen_ty_1() {
- assert_eq!(
- ::std::mem::size_of::<kvm_assigned_irq__bindgen_ty_1>(),
- 48usize,
- concat!("Size of: ", stringify!(kvm_assigned_irq__bindgen_ty_1))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_assigned_irq__bindgen_ty_1>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_assigned_irq__bindgen_ty_1))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_assigned_irq__bindgen_ty_1>())).reserved as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_irq__bindgen_ty_1),
- "::",
- stringify!(reserved)
- )
- );
+ pub reserved: [u32; 12usize],
}
impl Default for kvm_assigned_irq__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-#[test]
-fn bindgen_test_layout_kvm_assigned_irq() {
- assert_eq!(
- ::std::mem::size_of::<kvm_assigned_irq>(),
- 64usize,
- concat!("Size of: ", stringify!(kvm_assigned_irq))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_assigned_irq>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_assigned_irq))
- );
- assert_eq!(
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
unsafe {
- &(*(::std::ptr::null::<kvm_assigned_irq>())).assigned_dev_id as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_irq),
- "::",
- stringify!(assigned_dev_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_irq>())).host_irq as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_irq),
- "::",
- stringify!(host_irq)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_irq>())).guest_irq as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_irq),
- "::",
- stringify!(guest_irq)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_irq>())).flags as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_irq),
- "::",
- stringify!(flags)
- )
- );
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
}
impl Default for kvm_assigned_irq {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_assigned_msix_nr {
- pub assigned_dev_id: __u32,
- pub entry_nr: __u16,
- pub padding: __u16,
-}
-#[test]
-fn bindgen_test_layout_kvm_assigned_msix_nr() {
- assert_eq!(
- ::std::mem::size_of::<kvm_assigned_msix_nr>(),
- 8usize,
- concat!("Size of: ", stringify!(kvm_assigned_msix_nr))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_assigned_msix_nr>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_assigned_msix_nr))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_assigned_msix_nr>())).assigned_dev_id as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_msix_nr),
- "::",
- stringify!(assigned_dev_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_msix_nr>())).entry_nr as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_msix_nr),
- "::",
- stringify!(entry_nr)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_msix_nr>())).padding as *const _ as usize },
- 6usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_msix_nr),
- "::",
- stringify!(padding)
- )
- );
+ pub assigned_dev_id: u32,
+ pub entry_nr: u16,
+ pub padding: u16,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_assigned_msix_entry {
- pub assigned_dev_id: __u32,
- pub gsi: __u32,
- pub entry: __u16,
- pub padding: [__u16; 3usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_assigned_msix_entry() {
- assert_eq!(
- ::std::mem::size_of::<kvm_assigned_msix_entry>(),
- 16usize,
- concat!("Size of: ", stringify!(kvm_assigned_msix_entry))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_assigned_msix_entry>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_assigned_msix_entry))
- );
- assert_eq!(
- unsafe {
- &(*(::std::ptr::null::<kvm_assigned_msix_entry>())).assigned_dev_id as *const _ as usize
- },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_msix_entry),
- "::",
- stringify!(assigned_dev_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_msix_entry>())).gsi as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_msix_entry),
- "::",
- stringify!(gsi)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_msix_entry>())).entry as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_msix_entry),
- "::",
- stringify!(entry)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_assigned_msix_entry>())).padding as *const _ as usize },
- 10usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_assigned_msix_entry),
- "::",
- stringify!(padding)
- )
- );
+ pub assigned_dev_id: u32,
+ pub gsi: u32,
+ pub entry: u16,
+ pub padding: [u16; 3usize],
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct kvm_hyperv_eventfd {
- pub conn_id: __u32,
- pub fd: __s32,
- pub flags: __u32,
- pub padding: [__u32; 3usize],
-}
-#[test]
-fn bindgen_test_layout_kvm_hyperv_eventfd() {
- assert_eq!(
- ::std::mem::size_of::<kvm_hyperv_eventfd>(),
- 24usize,
- concat!("Size of: ", stringify!(kvm_hyperv_eventfd))
- );
- assert_eq!(
- ::std::mem::align_of::<kvm_hyperv_eventfd>(),
- 4usize,
- concat!("Alignment of ", stringify!(kvm_hyperv_eventfd))
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_hyperv_eventfd>())).conn_id as *const _ as usize },
- 0usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_eventfd),
- "::",
- stringify!(conn_id)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_hyperv_eventfd>())).fd as *const _ as usize },
- 4usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_eventfd),
- "::",
- stringify!(fd)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_hyperv_eventfd>())).flags as *const _ as usize },
- 8usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_eventfd),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(::std::ptr::null::<kvm_hyperv_eventfd>())).padding as *const _ as usize },
- 12usize,
- concat!(
- "Offset of field: ",
- stringify!(kvm_hyperv_eventfd),
- "::",
- stringify!(padding)
- )
- );
+ pub conn_id: u32,
+ pub fd: i32,
+ pub flags: u32,
+ pub padding: [u32; 3usize],
}
diff --git a/kvm_sys/tests/sanity.rs b/kvm_sys/tests/basic.rs
index 7669b44e4..4e1a2f18d 100644
--- a/kvm_sys/tests/sanity.rs
+++ b/kvm_sys/tests/basic.rs
@@ -2,11 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#![cfg(not(target_arch = "arm"))]
+
use libc::{c_char, ioctl, open, O_RDWR};
use kvm_sys::*;
-const KVM_PATH: &'static str = "/dev/kvm\0";
+const KVM_PATH: &str = "/dev/kvm\0";
#[test]
fn get_version() {
diff --git a/libcras_stub/Android.bp b/libcras_stub/Android.bp
new file mode 100644
index 000000000..f5b4f9f70
--- /dev/null
+++ b/libcras_stub/Android.bp
@@ -0,0 +1,22 @@
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
+// Do not modify this file as changes will be overridden on upgrade.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_crosvm_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-BSD
+ default_applicable_licenses: ["external_crosvm_license"],
+}
+
+rust_library {
+ name: "liblibcras",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "libcras",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
+ srcs: ["src/libcras.rs"],
+ edition: "2021",
+}
diff --git a/rand_ish/Cargo.toml b/libcras_stub/Cargo.toml
index 63bb001a4..4b0961cb7 100644
--- a/rand_ish/Cargo.toml
+++ b/libcras_stub/Cargo.toml
@@ -1,5 +1,8 @@
[package]
-name = "rand_ish"
+name = "libcras"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
+
+[lib]
+path = "src/libcras.rs"
diff --git a/libcras_stub/README.md b/libcras_stub/README.md
new file mode 100644
index 000000000..8dfbfec52
--- /dev/null
+++ b/libcras_stub/README.md
@@ -0,0 +1,11 @@
+# Stub crate for libcras
+
+libcras is used by ChromeOS to play audio through the cras server.
+
+In ChromeOS builds, the `audio_cras` cargo feature is enabled and this crate is replaced with the
+actual [libcras] implementation.
+
+On other platforms, the feature flag will remain disabled and this crate is used to satisfy cargo
+dependencies on libcras.
+
+[libcras]: https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/third_party/adhd/cras/client/libcras/
diff --git a/libcras_stub/src/libcras.rs b/libcras_stub/src/libcras.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/libcras_stub/src/libcras.rs
@@ -0,0 +1 @@
+
diff --git a/libcrosvm_control/Android.bp b/libcrosvm_control/Android.bp
deleted file mode 100644
index 314461106..000000000
--- a/libcrosvm_control/Android.bp
+++ /dev/null
@@ -1,108 +0,0 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Do not modify this file as changes will be overridden on upgrade.
-
-
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "external_crosvm_license"
- // to get the below license kinds:
- // SPDX-license-identifier-BSD
- default_applicable_licenses: ["external_crosvm_license"],
-}
-
-rust_defaults {
- name: "libcrosvm_control_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "libcrosvm_control",
- srcs: ["src/lib.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libbase_rust",
- "liblibc",
- "libvm_control",
- ],
-}
-
-rust_test_host {
- name: "libcrosvm_control_host_test_src_lib",
- defaults: ["libcrosvm_control_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "libcrosvm_control_device_test_src_lib",
- defaults: ["libcrosvm_control_defaults"],
-}
-
-rust_ffi_shared {
- name: "liblibcrosvm_control_shared",
- defaults: ["crosvm_defaults"],
- stem: "liblibcrosvm_control",
- host_supported: true,
- crate_name: "libcrosvm_control",
- srcs: ["src/lib.rs"],
- edition: "2018",
- rustlibs: [
- "libbase_rust",
- "liblibc",
- "libvm_control",
- ],
-}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../bit_field/bit_field_derive/bit_field_derive.rs
-// ../bit_field/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../enumn/src/lib.rs
-// ../hypervisor/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../kvm/src/lib.rs
-// ../kvm_sys/src/lib.rs
-// ../resources/src/lib.rs
-// ../rutabaga_gfx/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// ../vm_control/src/lib.rs
-// ../vm_memory/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// downcast-rs-1.2.0 "default,std"
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/linux_input_sys/Android.bp b/linux_input_sys/Android.bp
index 033d5c36f..d1690af17 100644
--- a/linux_input_sys/Android.bp
+++ b/linux_input_sys/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -15,79 +15,13 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "linux_input_sys",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
"libdata_model",
"liblibc",
],
}
-
-rust_defaults {
- name: "linux_input_sys_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "linux_input_sys",
- srcs: ["src/lib.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libbase_rust",
- "libdata_model",
- "liblibc",
- ],
-}
-
-rust_test_host {
- name: "linux_input_sys_host_test_src_lib",
- defaults: ["linux_input_sys_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "linux_input_sys_device_test_src_lib",
- defaults: ["linux_input_sys_defaults"],
-}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/linux_input_sys/Cargo.toml b/linux_input_sys/Cargo.toml
index c892c68b8..355c44f9a 100644
--- a/linux_input_sys/Cargo.toml
+++ b/linux_input_sys/Cargo.toml
@@ -2,9 +2,9 @@
name = "linux_input_sys"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
-data_model = { path = "../data_model" }
+data_model = { path = "../common/data_model" }
libc = "*"
base = { path = "../base" }
diff --git a/linux_input_sys/src/lib.rs b/linux_input_sys/src/lib.rs
index b48e9370e..20f6d0dee 100644
--- a/linux_input_sys/src/lib.rs
+++ b/linux_input_sys/src/lib.rs
@@ -5,24 +5,36 @@
use data_model::{DataInit, Le16, SLe32};
use std::mem::size_of;
-const EV_SYN: u16 = 0x00;
-const EV_KEY: u16 = 0x01;
-#[allow(dead_code)]
-const EV_REL: u16 = 0x02;
-const EV_ABS: u16 = 0x03;
-const SYN_REPORT: u16 = 0;
-#[allow(dead_code)]
-const REL_X: u16 = 0x00;
-#[allow(dead_code)]
-const REL_Y: u16 = 0x01;
-const ABS_MT_TRACKING_ID: u16 = 0x39;
-const ABS_MT_SLOT: u16 = 0x2f;
-const ABS_MT_POSITION_X: u16 = 0x35;
-const ABS_MT_POSITION_Y: u16 = 0x36;
-const ABS_X: u16 = 0x00;
-const ABS_Y: u16 = 0x01;
-const BTN_TOUCH: u16 = 0x14a;
-const BTN_TOOL_FINGER: u16 = 0x145;
+pub const EV_SYN: u16 = 0x00;
+pub const EV_KEY: u16 = 0x01;
+pub const EV_REL: u16 = 0x02;
+pub const EV_ABS: u16 = 0x03;
+pub const SYN_REPORT: u16 = 0;
+pub const REL_X: u16 = 0x00;
+pub const REL_Y: u16 = 0x01;
+pub const ABS_X: u16 = 0x00;
+pub const ABS_Y: u16 = 0x01;
+pub const ABS_PRESSURE: u16 = 0x18;
+pub const ABS_TILT_X: u16 = 0x1a;
+pub const ABS_TILT_Y: u16 = 0x1b;
+pub const ABS_TOOL_WIDTH: u16 = 0x1c;
+pub const BTN_TOUCH: u16 = 0x14a;
+pub const BTN_TOOL_FINGER: u16 = 0x145;
+pub const ABS_MT_SLOT: u16 = 0x2f;
+pub const ABS_MT_TOUCH_MAJOR: u16 = 0x30;
+pub const ABS_MT_TOUCH_MINOR: u16 = 0x31;
+pub const ABS_MT_WIDTH_MAJOR: u16 = 0x32;
+pub const ABS_MT_WIDTH_MINOR: u16 = 0x33;
+pub const ABS_MT_ORIENTATION: u16 = 0x34;
+pub const ABS_MT_POSITION_X: u16 = 0x35;
+pub const ABS_MT_POSITION_Y: u16 = 0x36;
+pub const ABS_MT_TOOL_TYPE: u16 = 0x37;
+pub const ABS_MT_BLOB_ID: u16 = 0x38;
+pub const ABS_MT_TRACKING_ID: u16 = 0x39;
+pub const ABS_MT_PRESSURE: u16 = 0x3a;
+pub const ABS_MT_DISTANCE: u16 = 0x3b;
+pub const ABS_MT_TOOL_X: u16 = 0x3c;
+pub const ABS_MT_TOOL_Y: u16 = 0x3d;
/// Allows a raw input event of the implementor's type to be decoded into
/// a virtio_input_event.
@@ -111,6 +123,15 @@ impl virtio_input_event {
}
#[inline]
+ pub fn relative(code: u16, value: i32) -> virtio_input_event {
+ virtio_input_event {
+ type_: Le16::from(EV_REL),
+ code: Le16::from(code),
+ value: SLe32::from(value),
+ }
+ }
+
+ #[inline]
pub fn multitouch_tracking_id(id: i32) -> virtio_input_event {
Self::absolute(ABS_MT_TRACKING_ID, id)
}
@@ -141,6 +162,16 @@ impl virtio_input_event {
}
#[inline]
+ pub fn relative_x(x: i32) -> virtio_input_event {
+ Self::relative(REL_X, x)
+ }
+
+ #[inline]
+ pub fn relative_y(y: i32) -> virtio_input_event {
+ Self::relative(REL_Y, y)
+ }
+
+ #[inline]
pub fn touch(has_contact: bool) -> virtio_input_event {
Self::key(BTN_TOUCH, has_contact)
}
@@ -158,4 +189,42 @@ impl virtio_input_event {
value: SLe32::from(if pressed { 1 } else { 0 }),
}
}
+
+ #[inline]
+ pub fn is_valid_mt_event(self) -> bool {
+ match self.type_.to_native() {
+ EV_KEY => self.code.to_native() == BTN_TOUCH,
+ EV_ABS => matches!(
+ self.code.to_native(),
+ ABS_MT_SLOT
+ | ABS_MT_TOUCH_MAJOR
+ | ABS_MT_TOUCH_MINOR
+ | ABS_MT_WIDTH_MAJOR
+ | ABS_MT_WIDTH_MINOR
+ | ABS_MT_ORIENTATION
+ | ABS_MT_POSITION_X
+ | ABS_MT_POSITION_Y
+ | ABS_MT_TOOL_TYPE
+ | ABS_MT_BLOB_ID
+ | ABS_MT_TRACKING_ID
+ | ABS_MT_PRESSURE
+ | ABS_MT_DISTANCE
+ | ABS_MT_TOOL_X
+ | ABS_MT_TOOL_Y
+ ),
+ _ => false,
+ }
+ }
+
+ #[inline]
+ pub fn is_valid_st_event(self) -> bool {
+ match self.type_.to_native() {
+ EV_KEY => self.code.to_native() == BTN_TOUCH,
+ EV_ABS => matches!(
+ self.code.to_native(),
+ ABS_X | ABS_Y | ABS_PRESSURE | ABS_TILT_X | ABS_TILT_Y | ABS_TOOL_WIDTH
+ ),
+ _ => false,
+ }
+ }
}
diff --git a/logo/logo.svg b/logo/logo.svg
new file mode 100644
index 000000000..25140a9b0
--- /dev/null
+++ b/logo/logo.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 135.467 135.467" height="512" width="512">
+ <path d="M117.55 62.332l3.259-.35 3.512.478 6.928 3.007V70l-6.928 3.008-3.512.478-3.258-.35-1.435 7.675 3.165.85 3.102 1.715 5.373 5.307-1.637 4.226-7.546.302-3.448-.823-2.911-1.504-4.111 6.639 2.644 1.936 2.273 2.72 3.094 6.89-3.053 3.349-7.146-2.445-2.918-2.012-2.172-2.455-6.231 4.706 1.767 2.76 1.137 3.357.395 7.543-4.056 2.02-5.78-4.861-1.995-2.93-1.138-3.074-7.51 2.137.65 3.212-.153 3.541-2.355 7.176-4.513.418-3.634-6.62-.8-3.454.048-3.277-7.775-.72-.554 3.23-1.421 3.247-4.79 5.84-4.358-1.24-.997-7.486.5-3.51 1.23-3.037-6.99-3.48-1.683 2.811-2.499 2.514-6.575 3.716-3.616-2.73 1.774-7.342 1.735-3.09 2.244-2.39-5.261-5.77-2.586 2.013-3.237 1.442-7.474 1.09-2.386-3.853 4.307-6.204 2.734-2.256 2.955-1.417-2.82-7.281-3.139.944-3.54.175-7.363-1.684-.832-4.454 6.257-4.23 3.364-1.116 3.268-.253v-7.809l-3.268-.254-3.364-1.115-6.257-4.23.832-4.454 7.363-1.684 3.54.175 3.139.944 2.82-7.282-2.955-1.417-2.734-2.255-4.307-6.205 2.386-3.853 7.474 1.09 3.237 1.442 2.586 2.014 5.26-5.77-2.243-2.39-1.735-3.09-1.774-7.342 3.616-2.73 6.575 3.715 2.499 2.515 1.683 2.812 6.99-3.481-1.23-3.038-.5-3.509.997-7.486 4.359-1.24 4.789 5.84 1.42 3.247.555 3.23 7.775-.72-.049-3.278.8-3.453 3.635-6.62 4.513.418 2.355 7.176.153 3.54-.65 3.213 7.51 2.137 1.138-3.073 1.994-2.931 5.78-4.86 4.057 2.02-.395 7.541-1.137 3.357-1.767 2.76 6.231 4.707 2.172-2.455 2.918-2.012 7.146-2.445 3.053 3.35-3.094 6.89-2.273 2.719-2.644 1.936 4.11 6.639 2.912-1.505 3.448-.822 7.546.302 1.637 4.226-5.373 5.307-3.102 1.715-3.165.85z" fill="none" stroke-width="4.000059149999999" stroke="#045" stroke-opacity="1" />
+ <g stroke-width=".226">
+ <path d="M55.046 63.741q.75-.917 1.656-1.281.906-.375 2.23-.375 1.315 0 1.823.828.52.817.52 2.44v.067H59.55q0-.11-.01-.354-.012-.254-.023-.353 0-.1-.033-.276-.022-.188-.088-.254-.056-.077-.155-.166-.1-.1-.254-.132-.155-.033-.364-.033-.475 0-.895.11-.42.11-.916.398-.486.276-.95.872-.464.596-.862 1.48v6.692l-1.722-.01v-8.427q0-.1-.011-.188-.012-.1-.056-.188-.044-.099-.077-.165-.022-.078-.11-.166l-.122-.154q-.033-.056-.155-.166-.121-.11-.176-.155-.055-.055-.21-.187-.144-.133-.21-.188l1.149-1.557q.077.066.32.265.254.187.353.265.1.077.287.265.2.176.31.32.11.133.243.342.132.21.232.431zM69.888 71.03q.983-.928.983-3.479v-.199q-.022-1.656-.663-2.65-.64-.994-2.02-.994-.84 0-1.436.32-.586.32-.917.939-.331.618-.486 1.413-.143.785-.143 1.845 0 1.634.717 2.55.718.906 2.088.906.63-.01 1.038-.154.42-.144.84-.497zm-1.9 2.363q-2.208-.01-3.368-1.37-1.16-1.369-1.16-3.743 0-1.005.133-1.888.144-.895.464-1.701.331-.817.817-1.403.497-.585 1.248-.927.751-.354 1.69-.354 1.59 0 2.573.552.994.542 1.59 1.668.652 1.259.652 3.18 0 .994-.144 1.845-.132.84-.464 1.623-.33.773-.85 1.325-.508.542-1.314.873-.806.32-1.844.32zM77.331 67.12q-1.28-.684-1.28-2.363.01-1.347 1.082-2.032 1.07-.685 2.65-.685 1.16 0 2.363.398 1.204.386 1.834 1.16l-1.248 1.225q-.431-.497-1.248-.817-.806-.331-1.546-.331-.939 0-1.546.265-.608.265-.608.773 0 .386.155.607.165.221.508.376.475.199 1.104.43l1.071.376q.453.155.972.375.53.21.884.42.364.199.729.497.364.298.574.64.22.343.353.818.133.475.133 1.049 0 1.248-.718 2.043-.718.795-1.723 1.06-.95.221-1.91.221-2.077 0-3.159-1.016-1.082-1.027-1.082-2.85l1.734.012q0 2.275 2.474 2.275 1.104 0 1.888-.464.784-.475.784-1.336 0-.486-.22-.784-.222-.31-.73-.696-.452-.331-2.109-.85-1.646-.52-2.165-.796z" fill="#37abc8" fill-opacity="1" stroke-opacity="0" />
+ <path d="M87.768 61.83q.54.575.795 1.039.265.463.508 1.16l.784 2.086q.75 2 1.226 3.413.475 1.413.53 1.921 1.06-1.225 1.91-3.313.85-2.087.895-3.754.01-.31.01-1.072 0-.872-.021-1.07l1.678-.045q.056.298.056 2.142-.011 1.933-1.005 4.307-.994 2.375-2.275 3.976-.431.53-1.05.674-.607.143-1.678.143 0-1.005-1.05-4.152-1.048-3.147-1.855-5.146-.066-.177-.375-.608-.298-.442-.508-.64zM106.232 62.073q.564 0 .961.343.398.331.586.872.187.53.254 1.005.077.475.077.95v5.776q0 .386.31.696.308.298.706.452l-.674 1.47q-.32-.067-.63-.222-.309-.154-.607-.42-.287-.264-.475-.706-.176-.442-.176-.994v-6.427q0-1.105-.707-1.105-.872 0-1.79 1.999v7.642h-1.556v-8.9q0-.21-.022-.343-.022-.133-.155-.265-.121-.133-.353-.133-.453 0-.983.486-.52.486-.994 1.524v7.631h-1.557l-.033-9q0-1.17-.332-2.12l1.657-.442q.121.243.22.85.1.607.122 1.027.398-.685 1.038-1.16.652-.486 1.237-.486.762 0 1.226.508.475.508.475 1.138.894-1.646 2.175-1.646z" fill="#164450" fill-opacity="1" stroke-opacity="0" />
+ </g>
+ <g>
+ <path d="M76.46 80.897h9.666q-1.107 10.422-7.853 17.37-6.747 6.947-19.937 6.947-12.788 0-20.641-9.113-7.804-9.162-7.955-24.316V63.93q0-15.406 7.904-24.72 7.955-9.313 21.548-9.313 12.435 0 19.13 6.847 6.697 6.847 7.804 17.67H76.46q-1.107-7.652-4.934-12.082-3.826-4.48-12.334-4.48-9.717 0-14.751 7.148-5.035 7.15-5.035 18.83v7.4q0 10.824 4.582 18.477 4.581 7.602 14.348 7.602 9.263 0 13.04-4.33 3.826-4.33 5.084-12.083z" fill="#37abc8" fill-opacity="1" stroke-width="2.578" stroke-opacity="0" />
+ </g>
+</svg>
diff --git a/logo/logo_512.png b/logo/logo_512.png
new file mode 100644
index 000000000..7acf8dbca
--- /dev/null
+++ b/logo/logo_512.png
Binary files differ
diff --git a/media/libvda/Android.bp b/media/libvda/Android.bp
new file mode 100644
index 000000000..eeacbc9d0
--- /dev/null
+++ b/media/libvda/Android.bp
@@ -0,0 +1 @@
+// Manually removed, this doesn't build on Android.
diff --git a/media/libvda/Cargo.toml b/media/libvda/Cargo.toml
new file mode 100644
index 000000000..551d558c8
--- /dev/null
+++ b/media/libvda/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "libvda"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2021"
+links = "vda"
+
+[dependencies]
+enumn = "0.1.0"
+libc = "*"
+
+[build-dependencies]
+pkg-config = "*"
diff --git a/media/libvda/README.md b/media/libvda/README.md
new file mode 100644
index 000000000..94206a239
--- /dev/null
+++ b/media/libvda/README.md
@@ -0,0 +1,31 @@
+# Libvda Rust wrapper
+
+Note: This crate is specific to ChromeOS and requires the native
+(libvda)\[https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform2/arc/vm/libvda\]
+library at link time.
+
+Rust wrapper for libvda. This library is used to enable communication with Chrome's GPU process to
+perform hardware accelerated decoding and encoding. It is currently in development to be used by
+crosvm's virtio-video device.
+
+### Building for the host environment
+
+You can also execute `cargo` directly for faster build and tests. This would be useful when you are
+developing this crate. Since this crate depends on libvda.so, you need to install it to host
+environment first.
+
+```shell
+(chroot)$ sudo emerge chromeos-base/libvda # Install libvda.so to host.
+# Build
+(chroot)$ cargo build
+# Unit tests
+(chroot)$ cargo test
+```
+
+## Updating generated bindings
+
+`src/bindings.rs` is automatically generated from `libvda_common.h`. `src/decode/bindings.rs` is
+automatically generated from `libvda_decode.h`. `src/encode/bindings.rs` is automatically generated
+from `libvda_encode.h`.
+
+See the header of the bindings file for the generation command.
diff --git a/media/libvda/bindgen.sh b/media/libvda/bindgen.sh
new file mode 100755
index 000000000..69d245c95
--- /dev/null
+++ b/media/libvda/bindgen.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Regenerate libvda bindgen bindings.
+
+set -euo pipefail
+cd "$(dirname "${BASH_SOURCE[0]}")/../.."
+
+source tools/impl/bindgen-common.sh
+
+bindgen_generate \
+ --allowlist-type='video_.*' \
+ "${BINDGEN_PLATFORM2}/arc/vm/libvda/libvda_common.h" \
+ > media/libvda/src/bindings.rs
+
+bindgen_generate \
+ --raw-line 'pub use crate::bindings::*;' \
+ --allowlist-function 'initialize' \
+ --allowlist-function 'deinitialize' \
+ --allowlist-function 'get_vda_capabilities' \
+ --allowlist-function 'init_decode_session' \
+ --allowlist-function 'close_decode_session' \
+ --allowlist-function 'vda_.*' \
+ --allowlist-type 'vda_.*' \
+ --blocklist-type 'video_.*' \
+ "${BINDGEN_PLATFORM2}/arc/vm/libvda/libvda_decode.h" \
+ -- \
+ -I "${BINDGEN_PLATFORM2}" \
+ > media/libvda/src/decode/bindings.rs
+
+bindgen_generate \
+ --raw-line 'pub use crate::bindings::*;' \
+ --allowlist-function 'initialize_encode' \
+ --allowlist-function 'deinitialize_encode' \
+ --allowlist-function 'get_vea_capabilities' \
+ --allowlist-function 'init_encode_session' \
+ --allowlist-function 'close_encode_session' \
+ --allowlist-function 'vea_.*' \
+ --allowlist-type 'vea_.*' \
+ --blocklist-type 'video_.*' \
+ "${BINDGEN_PLATFORM2}/arc/vm/libvda/libvda_encode.h" \
+ -- \
+ -I "${BINDGEN_PLATFORM2}" \
+ > media/libvda/src/encode/bindings.rs
diff --git a/media/libvda/build.rs b/media/libvda/build.rs
new file mode 100644
index 000000000..47fa015b4
--- /dev/null
+++ b/media/libvda/build.rs
@@ -0,0 +1,18 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+fn main() {
+ #[allow(clippy::single_match)]
+ match pkg_config::probe_library("libvda") {
+ Ok(_) => (),
+ // Ignore pkg-config failures on non-chromeos platforms to allow cargo-clippy to run even
+ // if libvda.pc doesn't exist.
+ #[cfg(not(feature = "chromeos"))]
+ Err(_) => (),
+ #[cfg(feature = "chromeos")]
+ Err(e) => panic!("{}", e),
+ };
+
+ println!("cargo:rustc-link-lib=dylib=vda");
+}
diff --git a/media/libvda/cargo2android.json b/media/libvda/cargo2android.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/media/libvda/cargo2android.json
@@ -0,0 +1 @@
+{} \ No newline at end of file
diff --git a/media/libvda/src/bindings.rs b/media/libvda/src/bindings.rs
new file mode 100644
index 000000000..80c25d6fe
--- /dev/null
+++ b/media/libvda/src/bindings.rs
@@ -0,0 +1,66 @@
+/* automatically generated by tools/bindgen-all-the-things */
+
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+
+pub type __int32_t = ::std::os::raw::c_int;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct video_frame_plane {
+ pub offset: i32,
+ pub stride: i32,
+}
+pub type video_frame_plane_t = video_frame_plane;
+pub const video_codec_profile_VIDEO_CODEC_PROFILE_UNKNOWN: video_codec_profile = -1;
+pub const video_codec_profile_VIDEO_CODEC_PROFILE_MIN: video_codec_profile = -1;
+pub const video_codec_profile_H264PROFILE_MIN: video_codec_profile = 0;
+pub const video_codec_profile_H264PROFILE_BASELINE: video_codec_profile = 0;
+pub const video_codec_profile_H264PROFILE_MAIN: video_codec_profile = 1;
+pub const video_codec_profile_H264PROFILE_EXTENDED: video_codec_profile = 2;
+pub const video_codec_profile_H264PROFILE_HIGH: video_codec_profile = 3;
+pub const video_codec_profile_H264PROFILE_HIGH10PROFILE: video_codec_profile = 4;
+pub const video_codec_profile_H264PROFILE_HIGH422PROFILE: video_codec_profile = 5;
+pub const video_codec_profile_H264PROFILE_HIGH444PREDICTIVEPROFILE: video_codec_profile = 6;
+pub const video_codec_profile_H264PROFILE_SCALABLEBASELINE: video_codec_profile = 7;
+pub const video_codec_profile_H264PROFILE_SCALABLEHIGH: video_codec_profile = 8;
+pub const video_codec_profile_H264PROFILE_STEREOHIGH: video_codec_profile = 9;
+pub const video_codec_profile_H264PROFILE_MULTIVIEWHIGH: video_codec_profile = 10;
+pub const video_codec_profile_H264PROFILE_MAX: video_codec_profile = 10;
+pub const video_codec_profile_VP8PROFILE_MIN: video_codec_profile = 11;
+pub const video_codec_profile_VP8PROFILE_ANY: video_codec_profile = 11;
+pub const video_codec_profile_VP8PROFILE_MAX: video_codec_profile = 11;
+pub const video_codec_profile_VP9PROFILE_MIN: video_codec_profile = 12;
+pub const video_codec_profile_VP9PROFILE_PROFILE0: video_codec_profile = 12;
+pub const video_codec_profile_VP9PROFILE_PROFILE1: video_codec_profile = 13;
+pub const video_codec_profile_VP9PROFILE_PROFILE2: video_codec_profile = 14;
+pub const video_codec_profile_VP9PROFILE_PROFILE3: video_codec_profile = 15;
+pub const video_codec_profile_VP9PROFILE_MAX: video_codec_profile = 15;
+pub const video_codec_profile_HEVCPROFILE_MIN: video_codec_profile = 16;
+pub const video_codec_profile_HEVCPROFILE_MAIN: video_codec_profile = 16;
+pub const video_codec_profile_HEVCPROFILE_MAIN10: video_codec_profile = 17;
+pub const video_codec_profile_HEVCPROFILE_MAIN_STILL_PICTURE: video_codec_profile = 18;
+pub const video_codec_profile_HEVCPROFILE_MAX: video_codec_profile = 18;
+pub const video_codec_profile_DOLBYVISION_MIN: video_codec_profile = 19;
+pub const video_codec_profile_DOLBYVISION_PROFILE0: video_codec_profile = 19;
+pub const video_codec_profile_DOLBYVISION_PROFILE4: video_codec_profile = 20;
+pub const video_codec_profile_DOLBYVISION_PROFILE5: video_codec_profile = 21;
+pub const video_codec_profile_DOLBYVISION_PROFILE7: video_codec_profile = 22;
+pub const video_codec_profile_DOLBYVISION_MAX: video_codec_profile = 22;
+pub const video_codec_profile_THEORAPROFILE_MIN: video_codec_profile = 23;
+pub const video_codec_profile_THEORAPROFILE_ANY: video_codec_profile = 23;
+pub const video_codec_profile_THEORAPROFILE_MAX: video_codec_profile = 23;
+pub const video_codec_profile_AV1PROFILE_MIN: video_codec_profile = 24;
+pub const video_codec_profile_AV1PROFILE_PROFILE_MAIN: video_codec_profile = 24;
+pub const video_codec_profile_AV1PROFILE_PROFILE_HIGH: video_codec_profile = 25;
+pub const video_codec_profile_AV1PROFILE_PROFILE_PRO: video_codec_profile = 26;
+pub const video_codec_profile_AV1PROFILE_MAX: video_codec_profile = 26;
+pub const video_codec_profile_VIDEO_CODEC_PROFILE_MAX: video_codec_profile = 26;
+pub type video_codec_profile = ::std::os::raw::c_int;
+pub use self::video_codec_profile as video_codec_profile_t;
+pub const video_pixel_format_YV12: video_pixel_format = 0;
+pub const video_pixel_format_NV12: video_pixel_format = 1;
+pub const video_pixel_format_PIXEL_FORMAT_MAX: video_pixel_format = 1;
+pub type video_pixel_format = ::std::os::raw::c_uint;
+pub use self::video_pixel_format as video_pixel_format_t;
diff --git a/media/libvda/src/decode/bindings.rs b/media/libvda/src/decode/bindings.rs
new file mode 100644
index 000000000..b79009421
--- /dev/null
+++ b/media/libvda/src/decode/bindings.rs
@@ -0,0 +1,205 @@
+/* automatically generated by tools/bindgen-all-the-things */
+
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+
+pub use crate::bindings::*;
+
+pub type __int32_t = ::std::os::raw::c_int;
+pub type __uint32_t = ::std::os::raw::c_uint;
+pub type __uint64_t = ::std::os::raw::c_ulong;
+pub const vda_impl_type_FAKE: vda_impl_type = 0;
+pub const vda_impl_type_GAVDA: vda_impl_type = 1;
+pub const vda_impl_type_GAVD: vda_impl_type = 2;
+pub type vda_impl_type = ::std::os::raw::c_uint;
+pub use self::vda_impl_type as vda_impl_type_t;
+pub const vda_result_SUCCESS: vda_result = 0;
+pub const vda_result_ILLEGAL_STATE: vda_result = 1;
+pub const vda_result_INVALID_ARGUMENT: vda_result = 2;
+pub const vda_result_UNREADABLE_INPUT: vda_result = 3;
+pub const vda_result_PLATFORM_FAILURE: vda_result = 4;
+pub const vda_result_INSUFFICIENT_RESOURCES: vda_result = 5;
+pub const vda_result_CANCELLED: vda_result = 6;
+pub type vda_result = ::std::os::raw::c_uint;
+pub use self::vda_result as vda_result_t;
+pub use self::video_codec_profile_t as vda_profile_t;
+pub use self::video_pixel_format_t as vda_pixel_format_t;
+pub const vda_event_type_UNKNOWN: vda_event_type = 0;
+pub const vda_event_type_PROVIDE_PICTURE_BUFFERS: vda_event_type = 1;
+pub const vda_event_type_PICTURE_READY: vda_event_type = 2;
+pub const vda_event_type_NOTIFY_END_OF_BITSTREAM_BUFFER: vda_event_type = 3;
+pub const vda_event_type_NOTIFY_ERROR: vda_event_type = 4;
+pub const vda_event_type_RESET_RESPONSE: vda_event_type = 5;
+pub const vda_event_type_FLUSH_RESPONSE: vda_event_type = 6;
+pub type vda_event_type = ::std::os::raw::c_uint;
+pub use self::vda_event_type as vda_event_type_t;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct provide_picture_buffers_event_data {
+ pub min_num_buffers: u32,
+ pub width: i32,
+ pub height: i32,
+ pub visible_rect_left: i32,
+ pub visible_rect_top: i32,
+ pub visible_rect_right: i32,
+ pub visible_rect_bottom: i32,
+}
+pub type provide_picture_buffers_event_data_t = provide_picture_buffers_event_data;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct picture_ready_event_data {
+ pub picture_buffer_id: i32,
+ pub bitstream_id: i32,
+ pub crop_left: i32,
+ pub crop_top: i32,
+ pub crop_right: i32,
+ pub crop_bottom: i32,
+}
+pub type picture_ready_event_data_t = picture_ready_event_data;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union vda_event_data {
+ pub provide_picture_buffers: provide_picture_buffers_event_data_t,
+ pub picture_ready: picture_ready_event_data_t,
+ pub bitstream_id: i32,
+ pub result: vda_result_t,
+}
+impl Default for vda_event_data {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub type vda_event_data_t = vda_event_data;
+#[repr(C)]
+pub struct vda_input_format {
+ pub profile: vda_profile_t,
+ pub min_width: u32,
+ pub min_height: u32,
+ pub max_width: u32,
+ pub max_height: u32,
+}
+impl Default for vda_input_format {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub type vda_input_format_t = vda_input_format;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct vda_event {
+ pub event_type: vda_event_type_t,
+ pub event_data: vda_event_data_t,
+}
+impl Default for vda_event {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub type vda_event_t = vda_event;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vda_capabilities {
+ pub num_input_formats: usize,
+ pub input_formats: *const vda_input_format_t,
+ pub num_output_formats: usize,
+ pub output_formats: *const vda_pixel_format_t,
+}
+impl Default for vda_capabilities {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub type vda_capabilities_t = vda_capabilities;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vda_session_info {
+ pub ctx: *mut ::std::os::raw::c_void,
+ pub event_pipe_fd: ::std::os::raw::c_int,
+}
+impl Default for vda_session_info {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub type vda_session_info_t = vda_session_info;
+extern "C" {
+ pub fn initialize(impl_type: vda_impl_type_t) -> *mut ::std::os::raw::c_void;
+}
+extern "C" {
+ pub fn deinitialize(impl_: *mut ::std::os::raw::c_void);
+}
+extern "C" {
+ pub fn get_vda_capabilities(impl_: *mut ::std::os::raw::c_void) -> *const vda_capabilities_t;
+}
+extern "C" {
+ pub fn init_decode_session(
+ impl_: *mut ::std::os::raw::c_void,
+ profile: vda_profile_t,
+ ) -> *mut vda_session_info_t;
+}
+extern "C" {
+ pub fn close_decode_session(
+ impl_: *mut ::std::os::raw::c_void,
+ session_info: *mut vda_session_info_t,
+ );
+}
+extern "C" {
+ pub fn vda_decode(
+ ctx: *mut ::std::os::raw::c_void,
+ bitstream_id: i32,
+ fd: ::std::os::raw::c_int,
+ offset: u32,
+ bytes_used: u32,
+ ) -> vda_result_t;
+}
+extern "C" {
+ pub fn vda_set_output_buffer_count(
+ ctx: *mut ::std::os::raw::c_void,
+ num_output_buffers: usize,
+ ) -> vda_result_t;
+}
+extern "C" {
+ pub fn vda_use_output_buffer(
+ ctx: *mut ::std::os::raw::c_void,
+ picture_buffer_id: i32,
+ format: vda_pixel_format_t,
+ fd: ::std::os::raw::c_int,
+ num_planes: usize,
+ planes: *mut video_frame_plane_t,
+ modifier: u64,
+ ) -> vda_result_t;
+}
+extern "C" {
+ pub fn vda_reuse_output_buffer(
+ ctx: *mut ::std::os::raw::c_void,
+ picture_buffer_id: i32,
+ ) -> vda_result_t;
+}
+extern "C" {
+ pub fn vda_flush(ctx: *mut ::std::os::raw::c_void) -> vda_result_t;
+}
+extern "C" {
+ pub fn vda_reset(ctx: *mut ::std::os::raw::c_void) -> vda_result_t;
+}
diff --git a/media/libvda/src/decode/event.rs b/media/libvda/src/decode/event.rs
new file mode 100644
index 000000000..4e365b741
--- /dev/null
+++ b/media/libvda/src/decode/event.rs
@@ -0,0 +1,138 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Events reported by VDA over pipe FD.
+
+use enumn::N;
+use std::fmt::{self, Display};
+
+use super::bindings;
+use crate::error::*;
+
+/// Represents a response from libvda.
+///
+/// Each value corresponds to a value of [`VideoDecodeAccelerator::Result`](https://cs.chromium.org/chromium/src/components/arc/common/video_decode_accelerator.mojom?rcl=128dc1f18791dc4593b9fd671aab84cb72bf6830&l=84).
+#[derive(Debug, Clone, Copy, N)]
+#[repr(u32)]
+pub enum Response {
+ Success = bindings::vda_result_SUCCESS,
+ IllegalState = bindings::vda_result_ILLEGAL_STATE,
+ InvalidArgument = bindings::vda_result_INVALID_ARGUMENT,
+ UnreadableInput = bindings::vda_result_UNREADABLE_INPUT,
+ PlatformFailure = bindings::vda_result_PLATFORM_FAILURE,
+ InsufficientResources = bindings::vda_result_INSUFFICIENT_RESOURCES,
+ Cancelled = bindings::vda_result_CANCELLED,
+}
+
+impl Response {
+ pub(crate) fn new(res: bindings::vda_result_t) -> Response {
+ Response::n(res).unwrap_or_else(|| panic!("Unknown response is reported from VDA: {}", res))
+ }
+}
+
+impl Display for Response {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use self::Response::*;
+ match self {
+ Success => write!(f, "success"),
+ IllegalState => write!(f, "illegal state"),
+ InvalidArgument => write!(f, "invalid argument"),
+ UnreadableInput => write!(f, "unreadable input"),
+ PlatformFailure => write!(f, "platform failure"),
+ InsufficientResources => write!(f, "insufficient resources"),
+ Cancelled => write!(f, "cancelled"),
+ }
+ }
+}
+
+impl From<Response> for Result<()> {
+ fn from(r: Response) -> Self {
+ match r {
+ Response::Success => Ok(()),
+ _ => Err(Error::LibVdaFailure(r)),
+ }
+ }
+}
+
+/// Represents a notified event from libvda.
+#[derive(Debug)]
+pub enum Event {
+ /// Requests the users to provide output buffers.
+ ProvidePictureBuffers {
+ min_num_buffers: u32,
+ width: i32,
+ height: i32,
+ visible_rect_left: i32,
+ visible_rect_top: i32,
+ visible_rect_right: i32,
+ visible_rect_bottom: i32,
+ },
+ /// Notifies the user of a decoded frame ready for display.
+ /// These events will arrive in display order.
+ PictureReady {
+ buffer_id: i32,
+ bitstream_id: i32,
+ left: i32,
+ top: i32,
+ right: i32,
+ bottom: i32,
+ },
+ /// Notifies the end of bitstream buffer.
+ NotifyEndOfBitstreamBuffer {
+ bitstream_id: i32,
+ },
+ NotifyError(Response),
+ /// Notifies the result of operation issued by `Session::reset`.
+ ResetResponse(Response),
+ /// Notifies the result of operation issued by `Session::flush`.
+ FlushResponse(Response),
+}
+
+impl Event {
+ /// Creates a new `Event` from a `vda_event_t` instance.
+ /// This function is safe if `event` was a value read from libvda's pipe.
+ pub(crate) unsafe fn new(event: bindings::vda_event_t) -> Result<Event> {
+ use self::Event::*;
+
+ let data = event.event_data;
+ match event.event_type {
+ bindings::vda_event_type_PROVIDE_PICTURE_BUFFERS => {
+ let d = data.provide_picture_buffers;
+ Ok(ProvidePictureBuffers {
+ min_num_buffers: d.min_num_buffers,
+ width: d.width,
+ height: d.height,
+ visible_rect_left: d.visible_rect_left,
+ visible_rect_top: d.visible_rect_top,
+ visible_rect_right: d.visible_rect_right,
+ visible_rect_bottom: d.visible_rect_bottom,
+ })
+ }
+ bindings::vda_event_type_PICTURE_READY => {
+ let d = data.picture_ready;
+ Ok(PictureReady {
+ buffer_id: d.picture_buffer_id,
+ bitstream_id: d.bitstream_id,
+ left: d.crop_left,
+ top: d.crop_top,
+ right: d.crop_right,
+ bottom: d.crop_bottom,
+ })
+ }
+ bindings::vda_event_type_NOTIFY_END_OF_BITSTREAM_BUFFER => {
+ Ok(NotifyEndOfBitstreamBuffer {
+ bitstream_id: data.bitstream_id,
+ })
+ }
+ bindings::vda_event_type_NOTIFY_ERROR => Ok(NotifyError(Response::new(data.result))),
+ bindings::vda_event_type_RESET_RESPONSE => {
+ Ok(ResetResponse(Response::new(data.result)))
+ }
+ bindings::vda_event_type_FLUSH_RESPONSE => {
+ Ok(FlushResponse(Response::new(data.result)))
+ }
+ t => panic!("Unknown event is reported from VDA: {}", t),
+ }
+ }
+}
diff --git a/media/libvda/src/decode/format.rs b/media/libvda/src/decode/format.rs
new file mode 100644
index 000000000..0acdc3f93
--- /dev/null
+++ b/media/libvda/src/decode/format.rs
@@ -0,0 +1,39 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::bindings;
+use crate::error::*;
+use crate::format::*;
+
+/// Represents an input video format for VDA.
+pub struct InputFormat {
+ pub profile: Profile,
+ pub min_width: u32,
+ pub min_height: u32,
+ pub max_width: u32,
+ pub max_height: u32,
+}
+
+impl InputFormat {
+ pub(crate) fn new(f: &bindings::vda_input_format_t) -> Result<InputFormat> {
+ let profile = Profile::n(f.profile).ok_or(Error::UnknownProfile(f.profile))?;
+
+ Ok(InputFormat {
+ profile,
+ min_width: f.min_width,
+ min_height: f.min_height,
+ max_width: f.max_width,
+ max_height: f.max_height,
+ })
+ }
+
+ // The callers must guarantee that `data` is valid for |`len`| elements when
+ // both `data` and `len` are valid.
+ pub(crate) unsafe fn from_raw_parts(
+ data: *const bindings::vda_input_format_t,
+ len: usize,
+ ) -> Result<Vec<Self>> {
+ validate_formats(data, len, Self::new)
+ }
+}
diff --git a/media/libvda/src/decode/mod.rs b/media/libvda/src/decode/mod.rs
new file mode 100644
index 000000000..9a02e5a87
--- /dev/null
+++ b/media/libvda/src/decode/mod.rs
@@ -0,0 +1,14 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod bindings;
+mod event;
+mod format;
+mod session;
+mod vda_instance;
+
+pub use event::*;
+pub use format::*;
+pub use session::*;
+pub use vda_instance::*;
diff --git a/media/libvda/src/decode/session.rs b/media/libvda/src/decode/session.rs
new file mode 100644
index 000000000..dada37b5e
--- /dev/null
+++ b/media/libvda/src/decode/session.rs
@@ -0,0 +1,177 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io::Read;
+use std::mem;
+use std::os::unix::io::FromRawFd;
+use std::{fs::File, rc::Rc};
+
+use super::event::*;
+use super::{bindings, VdaConnection};
+use crate::error::*;
+use crate::format::{BufferFd, FramePlane, PixelFormat, Profile};
+
+/// Represents a decode session.
+pub struct Session {
+ // Ensures the VDA connection remains open for as long as there are active sessions.
+ connection: Rc<VdaConnection>,
+ // Pipe file to be notified decode session events.
+ pipe: File,
+ session_ptr: *mut bindings::vda_session_info_t,
+}
+
+impl Session {
+ /// Creates a new `Session`.
+ pub(super) fn new(connection: &Rc<VdaConnection>, profile: Profile) -> Option<Self> {
+ // Safe because `conn_ptr()` is valid and won't be invalidated by `init_decode_session()`.
+ let session_ptr: *mut bindings::vda_session_info_t = unsafe {
+ bindings::init_decode_session(connection.conn_ptr(), profile.to_raw_profile())
+ };
+
+ if session_ptr.is_null() {
+ return None;
+ }
+
+ // Dereferencing `session_ptr` is safe because it is a valid pointer to a FD provided by
+ // libvda. We need to dup() the `event_pipe_fd` because File object close() the FD while
+ // libvda also close() it when `close_decode_session` is called.
+ let pipe = unsafe { File::from_raw_fd(libc::dup((*session_ptr).event_pipe_fd)) };
+
+ Some(Session {
+ connection: Rc::clone(connection),
+ pipe,
+ session_ptr,
+ })
+ }
+
+ /// Gets a reference of pipe that notifies events from VDA session.
+ pub fn pipe(&self) -> &File {
+ &self.pipe
+ }
+
+ /// Reads an `Event` object from a pipe provided a decode session.
+ pub fn read_event(&mut self) -> Result<Event> {
+ const BUF_SIZE: usize = mem::size_of::<bindings::vda_event_t>();
+ let mut buf = [0u8; BUF_SIZE];
+
+ self.pipe
+ .read_exact(&mut buf)
+ .map_err(Error::ReadEventFailure)?;
+
+ // Safe because libvda must have written vda_event_t to the pipe.
+ let vda_event = unsafe { mem::transmute::<[u8; BUF_SIZE], bindings::vda_event_t>(buf) };
+
+ // Safe because `vda_event` is a value read from `self.pipe`.
+ unsafe { Event::new(vda_event) }
+ }
+
+ /// Sends a decode request for a bitstream buffer given as `fd`.
+ ///
+ /// `fd` will be closed by Chrome after decoding has occurred.
+ pub fn decode(
+ &self,
+ bitstream_id: i32,
+ fd: BufferFd,
+ offset: u32,
+ bytes_used: u32,
+ ) -> Result<()> {
+ // Safe because `session_ptr` is valid and a libvda's API is called properly.
+ let r = unsafe {
+ bindings::vda_decode(
+ (*self.session_ptr).ctx,
+ bitstream_id,
+ fd,
+ offset,
+ bytes_used,
+ )
+ };
+ Response::new(r).into()
+ }
+
+ /// Sets the number of expected output buffers.
+ ///
+ /// This function must be called after `Event::ProvidePictureBuffers` are notified.
+ /// After calling this function, `user_output_buffer` must be called `num_output_buffers` times.
+ pub fn set_output_buffer_count(&self, num_output_buffers: usize) -> Result<()> {
+ // Safe because `session_ptr` is valid and a libvda's API is called properly.
+ let r = unsafe {
+ bindings::vda_set_output_buffer_count((*self.session_ptr).ctx, num_output_buffers)
+ };
+ Response::new(r).into()
+ }
+
+ /// Provides an output buffer that will be filled with decoded frames.
+ ///
+ /// Users calls this function after `set_output_buffer_count`. Then, libvda
+ /// will fill next frames in the buffer and noitify `Event::PictureReady`.
+ ///
+ /// This function is also used to notify that they consumed decoded frames
+ /// in the output buffer.
+ ///
+ /// This function takes ownership of `output_buffer`.
+ pub fn use_output_buffer(
+ &self,
+ picture_buffer_id: i32,
+ format: PixelFormat,
+ output_buffer: BufferFd,
+ planes: &[FramePlane],
+ modifier: u64,
+ ) -> Result<()> {
+ let mut planes: Vec<_> = planes.iter().map(FramePlane::to_raw_frame_plane).collect();
+
+ // Safe because `session_ptr` is valid and a libvda's API is called properly.
+ let r = unsafe {
+ bindings::vda_use_output_buffer(
+ (*self.session_ptr).ctx,
+ picture_buffer_id,
+ format.to_raw_pixel_format(),
+ output_buffer,
+ planes.len(),
+ planes.as_mut_ptr(),
+ modifier,
+ )
+ };
+ Response::new(r).into()
+ }
+
+ /// Returns an output buffer for reuse.
+ ///
+ /// `picture_buffer_id` must be a value for which `use_output_buffer` has been called already.
+ pub fn reuse_output_buffer(&self, picture_buffer_id: i32) -> Result<()> {
+ // Safe because `session_ptr` is valid and a libvda's API is called properly.
+ let r = unsafe {
+ bindings::vda_reuse_output_buffer((*self.session_ptr).ctx, picture_buffer_id)
+ };
+ Response::new(r).into()
+ }
+
+ /// Flushes the decode session.
+ ///
+ /// When this operation has completed, `Event::FlushResponse` will be notified.
+ pub fn flush(&self) -> Result<()> {
+ // Safe because `session_ptr` is valid and a libvda's API is called properly.
+ let r = unsafe { bindings::vda_flush((*self.session_ptr).ctx) };
+ Response::new(r).into()
+ }
+
+ /// Resets the decode session.
+ ///
+ /// When this operation has completed, Event::ResetResponse will be notified.
+ pub fn reset(&self) -> Result<()> {
+ // Safe because `session_ptr` is valid and a libvda's API is called properly.
+ let r = unsafe { bindings::vda_reset((*self.session_ptr).ctx) };
+ Response::new(r).into()
+ }
+}
+
+impl Drop for Session {
+ fn drop(&mut self) {
+ // Safe because `session_ptr` is unchanged from the time `new` was called, and
+ // `connection` also guarantees that the pointer returned by `conn_ptr()` is a valid
+ // connection to a VDA instance.
+ unsafe {
+ bindings::close_decode_session(self.connection.conn_ptr(), self.session_ptr);
+ }
+ }
+}
diff --git a/media/libvda/src/decode/vda_instance.rs b/media/libvda/src/decode/vda_instance.rs
new file mode 100644
index 000000000..1789677d5
--- /dev/null
+++ b/media/libvda/src/decode/vda_instance.rs
@@ -0,0 +1,113 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! This module provides type safe interfaces for each operation exposed by Chrome's
+//! VideoDecodeAccelerator.
+
+use std::{os::raw::c_void, rc::Rc};
+
+use super::bindings;
+use super::format::*;
+use super::session::*;
+use crate::error::*;
+use crate::format::*;
+
+/// Represents a backend implementation of libvda.
+pub enum VdaImplType {
+ Fake,
+ Gavda, // GpuArcVideoDecodeAccelerator
+ Gavd, // GpuArcVideoDecoder
+}
+
+/// Represents decoding capabilities of libvda instances.
+pub struct Capabilities {
+ pub input_formats: Vec<InputFormat>,
+ pub output_formats: Vec<PixelFormat>,
+}
+
+// An active connection to the VDA, which closes automatically as it is dropped.
+pub struct VdaConnection {
+ // `conn_ptr` must be a valid pointer obtained from `decode_bindings::initialize`.
+ conn_ptr: *mut c_void,
+}
+
+impl VdaConnection {
+ fn new(typ: VdaImplType) -> Result<Self> {
+ let impl_type = match typ {
+ VdaImplType::Fake => bindings::vda_impl_type_FAKE,
+ VdaImplType::Gavda => bindings::vda_impl_type_GAVDA,
+ VdaImplType::Gavd => bindings::vda_impl_type_GAVD,
+ };
+
+ // Safe because libvda's API is called properly.
+ match unsafe { bindings::initialize(impl_type) } {
+ ptr if ptr.is_null() => Err(Error::InstanceInitFailure),
+ conn_ptr => Ok(VdaConnection { conn_ptr }),
+ }
+ }
+
+ // Returns the raw pointer to the VDA connection instance that can be passed
+ // to bindings functions that require it.
+ pub(super) fn conn_ptr(&self) -> *mut c_void {
+ self.conn_ptr
+ }
+}
+
+impl Drop for VdaConnection {
+ fn drop(&mut self) {
+ // Safe because libvda's API is called properly.
+ unsafe { bindings::deinitialize(self.conn_ptr) }
+ }
+}
+
+/// Represents a libvda instance.
+pub struct VdaInstance {
+ connection: Rc<VdaConnection>,
+ caps: Capabilities,
+}
+
+impl VdaInstance {
+ /// Creates VdaInstance. `typ` specifies which backend will be used.
+ pub fn new(typ: VdaImplType) -> Result<Self> {
+ let connection = VdaConnection::new(typ)?;
+
+ // Get available input/output formats.
+ // Safe because `conn_ptr` is valid and `get_vda_capabilities()` won't invalidate it.
+ let vda_cap_ptr = unsafe { bindings::get_vda_capabilities(connection.conn_ptr) };
+ if vda_cap_ptr.is_null() {
+ return Err(Error::GetCapabilitiesFailure);
+ }
+ // Safe because `vda_cap_ptr` is not NULL.
+ let vda_cap = unsafe { *vda_cap_ptr };
+
+ // Safe because `input_formats` is valid for |`num_input_formats`| elements if both are valid.
+ let input_formats = unsafe {
+ InputFormat::from_raw_parts(vda_cap.input_formats, vda_cap.num_input_formats)?
+ };
+
+ // Output formats
+ // Safe because `output_formats` is valid for |`num_output_formats`| elements if both are valid.
+ let output_formats = unsafe {
+ PixelFormat::from_raw_parts(vda_cap.output_formats, vda_cap.num_output_formats)?
+ };
+
+ Ok(VdaInstance {
+ connection: Rc::new(connection),
+ caps: Capabilities {
+ input_formats,
+ output_formats,
+ },
+ })
+ }
+
+ /// Get media capabilities.
+ pub fn get_capabilities(&self) -> &Capabilities {
+ &self.caps
+ }
+
+ /// Opens a new `Session` for a given `Profile`.
+ pub fn open_session(&self, profile: Profile) -> Result<Session> {
+ Session::new(&self.connection, profile).ok_or(Error::SessionInitFailure(profile))
+ }
+}
diff --git a/media/libvda/src/encode/bindings.rs b/media/libvda/src/encode/bindings.rs
new file mode 100644
index 000000000..951c2bb32
--- /dev/null
+++ b/media/libvda/src/encode/bindings.rs
@@ -0,0 +1,230 @@
+/* automatically generated by tools/bindgen-all-the-things */
+
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+
+pub use crate::bindings::*;
+
+pub type __uint8_t = ::std::os::raw::c_uchar;
+pub type __int32_t = ::std::os::raw::c_int;
+pub type __uint32_t = ::std::os::raw::c_uint;
+pub type __int64_t = ::std::os::raw::c_long;
+pub const vea_impl_type_VEA_FAKE: vea_impl_type = 0;
+pub const vea_impl_type_GAVEA: vea_impl_type = 1;
+pub type vea_impl_type = ::std::os::raw::c_uint;
+pub use self::vea_impl_type as vea_impl_type_t;
+#[repr(C)]
+pub struct vea_profile {
+ pub profile: video_codec_profile_t,
+ pub max_width: u32,
+ pub max_height: u32,
+ pub max_framerate_numerator: u32,
+ pub max_framerate_denominator: u32,
+}
+impl Default for vea_profile {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub type vea_profile_t = vea_profile;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vea_capabilities {
+ pub num_input_formats: usize,
+ pub input_formats: *const video_pixel_format_t,
+ pub num_output_formats: usize,
+ pub output_formats: *const vea_profile_t,
+}
+impl Default for vea_capabilities {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub type vea_capabilities_t = vea_capabilities;
+pub const vea_bitrate_mode_VBR: vea_bitrate_mode = 0;
+pub const vea_bitrate_mode_CBR: vea_bitrate_mode = 1;
+pub type vea_bitrate_mode = ::std::os::raw::c_uint;
+pub use self::vea_bitrate_mode as vea_bitrate_mode_t;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vea_bitrate {
+ pub mode: vea_bitrate_mode_t,
+ pub target: u32,
+ pub peak: u32,
+}
+impl Default for vea_bitrate {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub type vea_bitrate_t = vea_bitrate;
+#[repr(C)]
+pub struct vea_config {
+ pub input_format: video_pixel_format_t,
+ pub input_visible_width: u32,
+ pub input_visible_height: u32,
+ pub output_profile: video_codec_profile_t,
+ pub bitrate: vea_bitrate_t,
+ pub initial_framerate: u32,
+ pub has_initial_framerate: u8,
+ pub h264_output_level: u8,
+ pub has_h264_output_level: u8,
+}
+impl Default for vea_config {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub type vea_config_t = vea_config;
+pub const vea_error_ILLEGAL_STATE_ERROR: vea_error = 0;
+pub const vea_error_INVALID_ARGUMENT_ERROR: vea_error = 1;
+pub const vea_error_PLATFORM_FAILURE_ERROR: vea_error = 2;
+pub type vea_error = ::std::os::raw::c_uint;
+pub use self::vea_error as vea_error_t;
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vea_session_info {
+ pub ctx: *mut ::std::os::raw::c_void,
+ pub event_pipe_fd: ::std::os::raw::c_int,
+}
+impl Default for vea_session_info {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub type vea_session_info_t = vea_session_info;
+pub type vea_input_buffer_id_t = i32;
+pub type vea_output_buffer_id_t = i32;
+pub const vea_event_type_REQUIRE_INPUT_BUFFERS: vea_event_type = 0;
+pub const vea_event_type_PROCESSED_INPUT_BUFFER: vea_event_type = 1;
+pub const vea_event_type_PROCESSED_OUTPUT_BUFFER: vea_event_type = 2;
+pub const vea_event_type_VEA_FLUSH_RESPONSE: vea_event_type = 3;
+pub const vea_event_type_VEA_NOTIFY_ERROR: vea_event_type = 4;
+pub type vea_event_type = ::std::os::raw::c_uint;
+pub use self::vea_event_type as vea_event_type_t;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vea_require_input_buffers_event_data {
+ pub input_count: u32,
+ pub input_frame_width: u32,
+ pub input_frame_height: u32,
+ pub output_buffer_size: u32,
+}
+pub type vea_require_input_buffers_event_data_t = vea_require_input_buffers_event_data;
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vea_processed_output_buffer_event_data {
+ pub output_buffer_id: vea_output_buffer_id_t,
+ pub payload_size: u32,
+ pub key_frame: u8,
+ pub timestamp: i64,
+}
+pub type vea_processed_output_buffer_event_data_t = vea_processed_output_buffer_event_data;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub union vea_event_data {
+ pub require_input_buffers: vea_require_input_buffers_event_data_t,
+ pub processed_input_buffer_id: vea_input_buffer_id_t,
+ pub processed_output_buffer: vea_processed_output_buffer_event_data_t,
+ pub flush_done: u8,
+ pub error: vea_error_t,
+}
+impl Default for vea_event_data {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub type vea_event_data_t = vea_event_data;
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct vea_event {
+ pub event_type: vea_event_type_t,
+ pub event_data: vea_event_data_t,
+}
+impl Default for vea_event {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+pub type vea_event_t = vea_event;
+extern "C" {
+ pub fn initialize_encode(type_: vea_impl_type_t) -> *mut ::std::os::raw::c_void;
+}
+extern "C" {
+ pub fn deinitialize_encode(impl_: *mut ::std::os::raw::c_void);
+}
+extern "C" {
+ pub fn get_vea_capabilities(impl_: *mut ::std::os::raw::c_void) -> *const vea_capabilities_t;
+}
+extern "C" {
+ pub fn init_encode_session(
+ impl_: *mut ::std::os::raw::c_void,
+ config: *mut vea_config_t,
+ ) -> *mut vea_session_info_t;
+}
+extern "C" {
+ pub fn close_encode_session(
+ impl_: *mut ::std::os::raw::c_void,
+ session_info: *mut vea_session_info_t,
+ );
+}
+extern "C" {
+ pub fn vea_encode(
+ ctx: *mut ::std::os::raw::c_void,
+ input_buffer_id: vea_input_buffer_id_t,
+ fd: ::std::os::raw::c_int,
+ num_planes: usize,
+ planes: *mut video_frame_plane_t,
+ timestamp: i64,
+ force_keyframe: u8,
+ ) -> ::std::os::raw::c_int;
+}
+extern "C" {
+ pub fn vea_use_output_buffer(
+ ctx: *mut ::std::os::raw::c_void,
+ output_buffer_id: vea_output_buffer_id_t,
+ fd: ::std::os::raw::c_int,
+ offset: u32,
+ size: u32,
+ ) -> ::std::os::raw::c_int;
+}
+extern "C" {
+ pub fn vea_request_encoding_params_change(
+ ctx: *mut ::std::os::raw::c_void,
+ bitrate: vea_bitrate_t,
+ framerate: u32,
+ ) -> ::std::os::raw::c_int;
+}
+extern "C" {
+ pub fn vea_flush(ctx: *mut ::std::os::raw::c_void) -> ::std::os::raw::c_int;
+}
diff --git a/media/libvda/src/encode/event.rs b/media/libvda/src/encode/event.rs
new file mode 100644
index 000000000..93c8aa11c
--- /dev/null
+++ b/media/libvda/src/encode/event.rs
@@ -0,0 +1,110 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Events reported by VDA encode API over pipe FD.
+
+use enumn::N;
+use std::error;
+use std::fmt::{self, Display};
+
+use super::bindings;
+use super::session::{VeaInputBufferId, VeaOutputBufferId};
+use crate::error::*;
+
+/// Represents an error from a libvda encode session.
+#[derive(Debug, Clone, Copy, N)]
+#[repr(u32)]
+pub enum VeaError {
+ IllegalState = bindings::vea_error_ILLEGAL_STATE_ERROR,
+ InvalidArgument = bindings::vea_error_INVALID_ARGUMENT_ERROR,
+ PlatformFailure = bindings::vea_error_PLATFORM_FAILURE_ERROR,
+}
+
+impl error::Error for VeaError {}
+
+impl VeaError {
+ pub(crate) fn new(res: bindings::vea_error_t) -> VeaError {
+ VeaError::n(res).unwrap_or_else(|| panic!("Unknown error is reported from VEA: {}", res))
+ }
+}
+
+impl Display for VeaError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ use self::VeaError::*;
+ match self {
+ IllegalState => write!(f, "illegal state"),
+ InvalidArgument => write!(f, "invalid argument"),
+ PlatformFailure => write!(f, "platform failure"),
+ }
+ }
+}
+
+/// Represents a notified event from libvda.
+#[derive(Debug)]
+pub enum Event {
+ /// Requests the user to provide input buffers.
+ RequireInputBuffers {
+ input_count: u32,
+ input_frame_width: u32,
+ input_frame_height: u32,
+ output_buffer_size: u32,
+ },
+ /// Notifies the user that an input buffer has been processed.
+ ProcessedInputBuffer(VeaInputBufferId),
+ /// Notifies the user that an output buffer has been processed.
+ ProcessedOutputBuffer {
+ output_buffer_id: VeaOutputBufferId,
+ payload_size: u32,
+ key_frame: bool,
+ timestamp: i64,
+ },
+ /// Notifies the result of operation issued by `Session::flush`.
+ FlushResponse { flush_done: bool },
+ /// Notifies the user of an error.
+ NotifyError(VeaError),
+}
+
+impl Event {
+ /// Creates a new `Event` from a `vea_event_t` instance.
+ /// This function is safe if `event` was a value read from libvda's pipe.
+ pub(crate) unsafe fn new(event: bindings::vea_event_t) -> Result<Self> {
+ use self::Event::*;
+
+ let bindings::vea_event_t {
+ event_data,
+ event_type,
+ } = event;
+
+ match event_type {
+ bindings::vea_event_type_REQUIRE_INPUT_BUFFERS => {
+ let d = event_data.require_input_buffers;
+ Ok(RequireInputBuffers {
+ input_count: d.input_count,
+ input_frame_width: d.input_frame_width,
+ input_frame_height: d.input_frame_height,
+ output_buffer_size: d.output_buffer_size,
+ })
+ }
+ bindings::vea_event_type_PROCESSED_INPUT_BUFFER => {
+ Ok(ProcessedInputBuffer(event_data.processed_input_buffer_id))
+ }
+ bindings::vea_event_type_PROCESSED_OUTPUT_BUFFER => {
+ let d = event_data.processed_output_buffer;
+ Ok(ProcessedOutputBuffer {
+ output_buffer_id: d.output_buffer_id,
+ payload_size: d.payload_size,
+ key_frame: d.key_frame == 1,
+ timestamp: d.timestamp,
+ })
+ }
+ bindings::vea_event_type_VEA_FLUSH_RESPONSE => Ok(FlushResponse {
+ flush_done: event_data.flush_done == 1,
+ }),
+ bindings::vea_event_type_VEA_NOTIFY_ERROR => {
+ Ok(NotifyError(VeaError::new(event_data.error)))
+ }
+ t => panic!("Unknown event is reported from VEA: {}", t),
+ }
+ }
+}
diff --git a/media/libvda/src/encode/format.rs b/media/libvda/src/encode/format.rs
new file mode 100644
index 000000000..7b802f1db
--- /dev/null
+++ b/media/libvda/src/encode/format.rs
@@ -0,0 +1,66 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use super::bindings;
+use crate::error::Result;
+use crate::format::*;
+use enumn::N;
+
+/// Represents an output profile for VEA.
+#[derive(Debug, Clone, Copy)]
+pub struct OutputProfile {
+ pub profile: Profile,
+ pub max_width: u32,
+ pub max_height: u32,
+ pub max_framerate_numerator: u32,
+ pub max_framerate_denominator: u32,
+}
+
+impl OutputProfile {
+ pub(crate) fn new(p: &bindings::vea_profile_t) -> Result<Self> {
+ Ok(Self {
+ profile: Profile::new(p.profile)?,
+ max_width: p.max_width,
+ max_height: p.max_height,
+ max_framerate_numerator: p.max_framerate_numerator,
+ max_framerate_denominator: p.max_framerate_denominator,
+ })
+ }
+
+ pub(crate) unsafe fn from_raw_parts(
+ data: *const bindings::vea_profile_t,
+ len: usize,
+ ) -> Result<Vec<Self>> {
+ validate_formats(data, len, Self::new)
+ }
+}
+
+/// Represents a bitrate mode for the VEA.
+#[derive(Debug, Clone, Copy, N)]
+#[repr(u32)]
+pub enum BitrateMode {
+ VBR = bindings::vea_bitrate_mode_VBR,
+ CBR = bindings::vea_bitrate_mode_CBR,
+}
+
+/// Represents a bitrate for the VEA.
+#[derive(Debug, Clone, Copy)]
+pub struct Bitrate {
+ pub mode: BitrateMode,
+ pub target: u32,
+ pub peak: u32,
+}
+
+impl Bitrate {
+ pub fn to_raw_bitrate(&self) -> bindings::vea_bitrate_t {
+ bindings::vea_bitrate_t {
+ mode: match self.mode {
+ BitrateMode::VBR => bindings::vea_bitrate_mode_VBR,
+ BitrateMode::CBR => bindings::vea_bitrate_mode_CBR,
+ },
+ target: self.target,
+ peak: self.peak,
+ }
+ }
+}
diff --git a/media/libvda/src/encode/mod.rs b/media/libvda/src/encode/mod.rs
new file mode 100644
index 000000000..ddb9f477d
--- /dev/null
+++ b/media/libvda/src/encode/mod.rs
@@ -0,0 +1,14 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+mod bindings;
+mod event;
+mod format;
+mod session;
+mod vea_instance;
+
+pub use event::*;
+pub use format::*;
+pub use session::*;
+pub use vea_instance::*;
diff --git a/media/libvda/src/encode/session.rs b/media/libvda/src/encode/session.rs
new file mode 100644
index 000000000..7e5168aac
--- /dev/null
+++ b/media/libvda/src/encode/session.rs
@@ -0,0 +1,186 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::io::Read;
+use std::mem;
+use std::os::unix::io::FromRawFd;
+use std::{fs::File, rc::Rc};
+
+use super::event::*;
+use super::format::Bitrate;
+use super::vea_instance::Config;
+use super::{bindings, VeaConnection};
+use crate::error::*;
+use crate::format::{BufferFd, FramePlane};
+
+pub type VeaInputBufferId = bindings::vea_input_buffer_id_t;
+pub type VeaOutputBufferId = bindings::vea_output_buffer_id_t;
+
+/// Represents an encode session.
+pub struct Session {
+ // Pipe file to be notified encode session events.
+ pipe: File,
+ // Ensures the VEA connection remains open for as long as there are active sessions.
+ connection: Rc<VeaConnection>,
+ session_ptr: *mut bindings::vea_session_info_t,
+}
+
+fn convert_error_code(code: i32) -> Result<()> {
+ if code == 0 {
+ Ok(())
+ } else {
+ Err(Error::EncodeSessionFailure(code))
+ }
+}
+
+impl Session {
+ /// Creates a new `Session`.
+ pub(super) fn new(connection: &Rc<VeaConnection>, config: Config) -> Option<Self> {
+ // Safe because `conn_ptr()` is valid and won't be invalidated by `init_encode_session()`.
+ let session_ptr: *mut bindings::vea_session_info_t = unsafe {
+ bindings::init_encode_session(connection.conn_ptr(), &mut config.to_raw_config())
+ };
+
+ if session_ptr.is_null() {
+ return None;
+ }
+
+ // Dereferencing `session_ptr` is safe because it is a valid pointer to a FD provided by
+ // libvda. We need to dup() the `event_pipe_fd` because File object close() the FD while
+ // libvda also close() it when `close_encode_session` is called.
+ // Calling `from_raw_fd` here is safe because the dup'ed FD is not going to be used by
+ // anything else and `pipe` has full ownership of it.
+ let pipe = unsafe { File::from_raw_fd(libc::dup((*session_ptr).event_pipe_fd)) };
+
+ Some(Session {
+ connection: Rc::clone(connection),
+ pipe,
+ session_ptr,
+ })
+ }
+
+ /// Returns a reference for the pipe that notifies of encode events.
+ pub fn pipe(&self) -> &File {
+ &self.pipe
+ }
+
+ /// Reads an `Event` object from a pipe provided by an encode session.
+ pub fn read_event(&mut self) -> Result<Event> {
+ const BUF_SIZE: usize = mem::size_of::<bindings::vea_event_t>();
+ let mut buf = [0u8; BUF_SIZE];
+
+ self.pipe
+ .read_exact(&mut buf)
+ .map_err(Error::ReadEventFailure)?;
+
+ // Safe because libvda must have written vea_event_t to the pipe.
+ let vea_event = unsafe { mem::transmute::<[u8; BUF_SIZE], bindings::vea_event_t>(buf) };
+
+ // Safe because `vea_event` is a value read from `self.pipe`.
+ unsafe { Event::new(vea_event) }
+ }
+
+ /// Sends an encode request for an input buffer given as `fd` with planes described
+ /// by `planes. The timestamp of the frame to encode is typically provided in
+ /// milliseconds by `timestamp`. `force_keyframe` indicates to the encoder that
+ /// the frame should be encoded as a keyframe.
+ ///
+ /// When the input buffer has been filled, an `EncoderEvent::ProcessedInputBuffer`
+ /// event can be read from the event pipe.
+ ///
+ /// The caller is responsible for passing in a unique value for `input_buffer_id`
+ /// which can be referenced when the event is received.
+ ///
+ /// `fd` will be closed after encoding has occurred.
+ pub fn encode(
+ &self,
+ input_buffer_id: VeaInputBufferId,
+ fd: BufferFd,
+ planes: &[FramePlane],
+ timestamp: i64,
+ force_keyframe: bool,
+ ) -> Result<()> {
+ let mut planes: Vec<_> = planes.iter().map(FramePlane::to_raw_frame_plane).collect();
+
+ // Safe because `session_ptr` is valid and libvda's encode API is called properly.
+ let r = unsafe {
+ bindings::vea_encode(
+ (*self.session_ptr).ctx,
+ input_buffer_id,
+ fd,
+ planes.len(),
+ planes.as_mut_ptr(),
+ timestamp,
+ if force_keyframe { 1 } else { 0 },
+ )
+ };
+ convert_error_code(r)
+ }
+
+ /// Provides a buffer for storing encoded output.
+ ///
+ /// When the output buffer has been filled, an `EncoderEvent::ProcessedOutputBuffer`
+ /// event can be read from the event pipe.
+ ///
+ /// The caller is responsible for passing in a unique value for `output_buffer_id`
+ /// which can be referenced when the event is received.
+ ///
+ /// This function takes ownership of `fd`.
+ pub fn use_output_buffer(
+ &self,
+ output_buffer_id: VeaOutputBufferId,
+ fd: BufferFd,
+ offset: u32,
+ size: u32,
+ ) -> Result<()> {
+ // Safe because `session_ptr` is valid and libvda's encode API is called properly.
+ let r = unsafe {
+ bindings::vea_use_output_buffer(
+ (*self.session_ptr).ctx,
+ output_buffer_id,
+ fd,
+ offset,
+ size,
+ )
+ };
+ convert_error_code(r)
+ }
+
+ /// Requests encoding parameter changes.
+ ///
+ /// The request is not guaranteed to be honored by libvda and could be ignored
+ /// by the backing encoder implementation.
+ pub fn request_encoding_params_change(&self, bitrate: Bitrate, framerate: u32) -> Result<()> {
+ // Safe because `session_ptr` is valid and libvda's encode API is called properly.
+ let r = unsafe {
+ bindings::vea_request_encoding_params_change(
+ (*self.session_ptr).ctx,
+ bitrate.to_raw_bitrate(),
+ framerate,
+ )
+ };
+ convert_error_code(r)
+ }
+
+ /// Flushes the encode session.
+ ///
+ /// When this operation has completed, Event::FlushResponse can be read from
+ /// the event pipe.
+ pub fn flush(&self) -> Result<()> {
+ // Safe because `session_ptr` is valid and libvda's encode API is called properly.
+ let r = unsafe { bindings::vea_flush((*self.session_ptr).ctx) };
+ convert_error_code(r)
+ }
+}
+
+impl Drop for Session {
+ fn drop(&mut self) {
+ // Safe because `session_ptr` is unchanged from the time `new` was called, and
+ // `connection` also guarantees that the pointer returned by `conn_ptr()` is a valid
+ // connection to a VEA instance.
+ unsafe {
+ bindings::close_encode_session(self.connection.conn_ptr(), self.session_ptr);
+ }
+ }
+}
diff --git a/media/libvda/src/encode/vea_instance.rs b/media/libvda/src/encode/vea_instance.rs
new file mode 100644
index 000000000..c628e7a3d
--- /dev/null
+++ b/media/libvda/src/encode/vea_instance.rs
@@ -0,0 +1,156 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::{os::raw::c_void, rc::Rc};
+
+use super::bindings;
+use super::format::{Bitrate, OutputProfile};
+use super::session::*;
+use crate::error::*;
+use crate::format::*;
+
+/// Represents a backend implementation of libvda.
+pub enum VeaImplType {
+ Fake,
+ Gavea, // GpuArcVideoEncoderAccelerator
+}
+
+/// Represents encoding capabilities of libvda encode instances.
+pub struct EncodeCapabilities {
+ pub input_formats: Vec<PixelFormat>,
+ pub output_formats: Vec<OutputProfile>,
+}
+
+pub struct VeaConnection {
+ // `conn_ptr` must be a valid pointer obtained from `decode_bindings::initialize_encode`.
+ conn_ptr: *mut c_void,
+}
+
+impl VeaConnection {
+ fn new(typ: VeaImplType) -> Result<Self> {
+ let impl_type = match typ {
+ VeaImplType::Fake => bindings::vea_impl_type_VEA_FAKE,
+ VeaImplType::Gavea => bindings::vea_impl_type_GAVEA,
+ };
+
+ // Safe because libvda's API is called properly.
+ match unsafe { bindings::initialize_encode(impl_type) } {
+ ptr if ptr.is_null() => Err(Error::InstanceInitFailure),
+ conn_ptr => Ok(VeaConnection { conn_ptr }),
+ }
+ }
+
+ // Returns the raw pointer to the VEA connection instance that can be passed
+ // to bindings functions that require it.
+ pub(super) fn conn_ptr(&self) -> *mut c_void {
+ self.conn_ptr
+ }
+}
+
+impl Drop for VeaConnection {
+ fn drop(&mut self) {
+ // Safe because libvda's API is called properly.
+ unsafe { bindings::deinitialize_encode(self.conn_ptr) }
+ }
+}
+
+/// Represents a libvda encode instance.
+pub struct VeaInstance {
+ connection: Rc<VeaConnection>,
+ caps: EncodeCapabilities,
+}
+
+/// Represents an encoding configuration for a libvda encode session.
+#[derive(Debug, Clone, Copy)]
+pub struct Config {
+ pub input_format: PixelFormat,
+ pub input_visible_width: u32,
+ pub input_visible_height: u32,
+ pub output_profile: Profile,
+ pub bitrate: Bitrate,
+ pub initial_framerate: Option<u32>,
+ pub h264_output_level: Option<u8>,
+}
+
+impl Config {
+ pub(crate) fn to_raw_config(self) -> bindings::vea_config_t {
+ bindings::vea_config_t {
+ input_format: self.input_format.to_raw_pixel_format(),
+ input_visible_width: self.input_visible_width,
+ input_visible_height: self.input_visible_height,
+ output_profile: self.output_profile.to_raw_profile(),
+ bitrate: self.bitrate.to_raw_bitrate(),
+ initial_framerate: self.initial_framerate.unwrap_or(0),
+ has_initial_framerate: if self.initial_framerate.is_some() {
+ 1
+ } else {
+ 0
+ },
+ h264_output_level: self.h264_output_level.unwrap_or(0),
+ has_h264_output_level: if self.h264_output_level.is_some() {
+ 1
+ } else {
+ 0
+ },
+ }
+ }
+}
+
+impl VeaInstance {
+ /// Creates VeaInstance. `impl_type` specifies which backend will be used.
+ pub fn new(impl_type: VeaImplType) -> Result<Self> {
+ let connection = VeaConnection::new(impl_type)?;
+
+ // Get available input/output formats.
+ // Safe because libvda's API is called properly.
+ let vea_caps_ptr = unsafe { bindings::get_vea_capabilities(connection.conn_ptr()) };
+ if vea_caps_ptr.is_null() {
+ return Err(Error::GetCapabilitiesFailure);
+ }
+ // Safe because `vea_cap_ptr` is not NULL and provided by get_vea_capabilities().
+ let bindings::vea_capabilities_t {
+ num_input_formats,
+ input_formats,
+ num_output_formats,
+ output_formats,
+ } = unsafe { *vea_caps_ptr };
+
+ if num_input_formats == 0 || input_formats.is_null() {
+ return Err(Error::InvalidCapabilities(
+ "invalid input formats".to_string(),
+ ));
+ }
+ if num_output_formats == 0 || output_formats.is_null() {
+ return Err(Error::InvalidCapabilities(
+ "invalid output formats".to_string(),
+ ));
+ }
+
+ // Safe because `input_formats` is valid for |`num_input_formats`| elements.
+ let input_formats =
+ unsafe { PixelFormat::from_raw_parts(input_formats, num_input_formats)? };
+
+ // Safe because `output_formats` is valid for |`num_output_formats`| elements.
+ let output_formats =
+ unsafe { OutputProfile::from_raw_parts(output_formats, num_output_formats)? };
+
+ Ok(Self {
+ connection: Rc::new(connection),
+ caps: EncodeCapabilities {
+ input_formats,
+ output_formats,
+ },
+ })
+ }
+
+ /// Gets encoder capabilities.
+ pub fn get_capabilities(&self) -> &EncodeCapabilities {
+ &self.caps
+ }
+
+ /// Opens a new `Session` for a given `Config`.
+ pub fn open_session(&self, config: Config) -> Result<Session> {
+ Session::new(&self.connection, config).ok_or(Error::EncodeSessionInitFailure(config))
+ }
+}
diff --git a/media/libvda/src/error.rs b/media/libvda/src/error.rs
new file mode 100644
index 000000000..791ca396a
--- /dev/null
+++ b/media/libvda/src/error.rs
@@ -0,0 +1,57 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Errors that can happen in LibVDA.
+
+use std::error;
+use std::fmt::{self, Display};
+
+use super::format;
+use crate::decode;
+use crate::encode;
+
+#[derive(Debug)]
+pub enum Error {
+ // Encode session error. The error code provided is specific
+ // to the implementation type (`VeaImplType`).
+ EncodeSessionFailure(i32),
+ EncodeSessionInitFailure(encode::Config),
+ GetCapabilitiesFailure,
+ InstanceInitFailure,
+ InvalidCapabilities(String),
+ LibVdaFailure(decode::Response),
+ ReadEventFailure(std::io::Error),
+ SessionIdAlreadyUsed(u32),
+ SessionInitFailure(format::Profile),
+ SessionNotFound(u32),
+ UnknownPixelFormat(u32),
+ UnknownProfile(i32),
+}
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+impl error::Error for Error {}
+
+impl Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::Error::*;
+
+ match self {
+ EncodeSessionFailure(e) => write!(f, "encode session error: {}", e),
+ EncodeSessionInitFailure(c) => {
+ write!(f, "failed to initialize encode session with config {:?}", c)
+ }
+ GetCapabilitiesFailure => write!(f, "failed to get capabilities"),
+ InstanceInitFailure => write!(f, "failed to initialize VDA instance"),
+ InvalidCapabilities(e) => write!(f, "obtained capabilities are invalid: {}", e),
+ LibVdaFailure(e) => write!(f, "error happened in libvda: {}", e),
+ ReadEventFailure(e) => write!(f, "failed to read event: {}", e),
+ SessionInitFailure(p) => write!(f, "failed to initialize decode session with {:?}", p),
+ SessionIdAlreadyUsed(id) => write!(f, "session_id {} is already used", id),
+ SessionNotFound(id) => write!(f, "no session has session_id {}", id),
+ UnknownPixelFormat(p) => write!(f, "unknown pixel format: {}", p),
+ UnknownProfile(p) => write!(f, "unknown profile: {}", p),
+ }
+ }
+}
diff --git a/media/libvda/src/format.rs b/media/libvda/src/format.rs
new file mode 100644
index 000000000..6a77cf953
--- /dev/null
+++ b/media/libvda/src/format.rs
@@ -0,0 +1,123 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Data structures representing coded/raw formats.
+
+use enumn::N;
+use std::os::unix::io::RawFd;
+
+use super::bindings;
+use super::error::*;
+
+/// Represents a FD for bitstream/frame buffer.
+/// Files described by BufferFd must be accessed from outside of this crate.
+pub type BufferFd = RawFd;
+
+/// Represents a video frame plane.
+pub struct FramePlane {
+ pub offset: i32,
+ pub stride: i32,
+}
+
+impl FramePlane {
+ pub fn to_raw_frame_plane(&self) -> bindings::video_frame_plane_t {
+ bindings::video_frame_plane_t {
+ offset: self.offset,
+ stride: self.stride,
+ }
+ }
+}
+
+// The callers must guarantee that `ptr` is valid for |`num`| elements when both `ptr` and `num`
+// are valid.
+pub(crate) unsafe fn validate_formats<T, U, F>(ptr: *const T, num: usize, f: F) -> Result<Vec<U>>
+where
+ F: FnMut(&T) -> Result<U>,
+{
+ if num == 0 {
+ return Err(Error::InvalidCapabilities("num must not be 0".to_string()));
+ }
+ if ptr.is_null() {
+ return Err(Error::InvalidCapabilities(
+ "pointer must not be NULL".to_string(),
+ ));
+ }
+
+ std::slice::from_raw_parts(ptr, num)
+ .iter()
+ .map(f)
+ .collect::<Result<Vec<_>>>()
+}
+
+/// Represents a video codec.
+#[derive(Debug, Clone, Copy, N)]
+#[repr(i32)]
+pub enum Profile {
+ H264ProfileBaseline = bindings::video_codec_profile_H264PROFILE_BASELINE,
+ H264ProfileMain = bindings::video_codec_profile_H264PROFILE_MAIN,
+ H264ProfileExtended = bindings::video_codec_profile_H264PROFILE_EXTENDED,
+ H264ProfileHigh = bindings::video_codec_profile_H264PROFILE_HIGH,
+ H264ProfileHigh10Profile = bindings::video_codec_profile_H264PROFILE_HIGH10PROFILE,
+ H264ProfileHigh422Profile = bindings::video_codec_profile_H264PROFILE_HIGH422PROFILE,
+ H264ProfileHigh444PredictiveProfile =
+ bindings::video_codec_profile_H264PROFILE_HIGH444PREDICTIVEPROFILE,
+ H264ProfileScalableBaseline = bindings::video_codec_profile_H264PROFILE_SCALABLEBASELINE,
+ H264ProfileScalableHigh = bindings::video_codec_profile_H264PROFILE_SCALABLEHIGH,
+ H264ProfileStereoHigh = bindings::video_codec_profile_H264PROFILE_STEREOHIGH,
+ H264ProfileMultiviewHigh = bindings::video_codec_profile_H264PROFILE_MULTIVIEWHIGH,
+ VP8 = bindings::video_codec_profile_VP8PROFILE_MIN,
+ VP9Profile0 = bindings::video_codec_profile_VP9PROFILE_PROFILE0,
+ VP9Profile1 = bindings::video_codec_profile_VP9PROFILE_PROFILE1,
+ VP9Profile2 = bindings::video_codec_profile_VP9PROFILE_PROFILE2,
+ VP9Profile3 = bindings::video_codec_profile_VP9PROFILE_PROFILE3,
+ HevcProfileMain = bindings::video_codec_profile_HEVCPROFILE_MAIN,
+ HevcProfileMain10 = bindings::video_codec_profile_HEVCPROFILE_MAIN10,
+ HevcProfileMainStillPicture = bindings::video_codec_profile_HEVCPROFILE_MAIN_STILL_PICTURE,
+ DolbyVisionProfile0 = bindings::video_codec_profile_DOLBYVISION_PROFILE0,
+ DolbyVisionProfile4 = bindings::video_codec_profile_DOLBYVISION_PROFILE4,
+ DolbyVisionProfile5 = bindings::video_codec_profile_DOLBYVISION_PROFILE5,
+ DolbyVisionProfile7 = bindings::video_codec_profile_DOLBYVISION_PROFILE7,
+ Theora = bindings::video_codec_profile_THEORAPROFILE_ANY,
+ Av1ProfileMain = bindings::video_codec_profile_AV1PROFILE_PROFILE_MAIN,
+ Av1ProfileHigh = bindings::video_codec_profile_AV1PROFILE_PROFILE_HIGH,
+ Av1ProfilePro = bindings::video_codec_profile_AV1PROFILE_PROFILE_PRO,
+}
+
+impl Profile {
+ pub(crate) fn new(p: bindings::video_codec_profile_t) -> Result<Self> {
+ Self::n(p).ok_or(Error::UnknownProfile(p))
+ }
+
+ pub(crate) fn to_raw_profile(self) -> bindings::video_codec_profile_t {
+ self as bindings::video_codec_profile_t
+ }
+}
+
+/// Represents a raw pixel format.
+#[derive(Debug, Clone, Copy, N)]
+#[repr(u32)]
+pub enum PixelFormat {
+ YV12 = bindings::video_pixel_format_YV12,
+ NV12 = bindings::video_pixel_format_NV12,
+}
+
+impl PixelFormat {
+ pub(crate) fn new(f: bindings::video_pixel_format_t) -> Result<PixelFormat> {
+ PixelFormat::n(f).ok_or(Error::UnknownPixelFormat(f))
+ }
+
+ pub(crate) fn to_raw_pixel_format(self) -> bindings::video_pixel_format_t {
+ match self {
+ PixelFormat::YV12 => bindings::video_pixel_format_YV12,
+ PixelFormat::NV12 => bindings::video_pixel_format_NV12,
+ }
+ }
+
+ pub(crate) unsafe fn from_raw_parts(
+ data: *const bindings::video_pixel_format_t,
+ len: usize,
+ ) -> Result<Vec<Self>> {
+ validate_formats(data, len, |f| Self::new(*f))
+ }
+}
diff --git a/media/libvda/src/lib.rs b/media/libvda/src/lib.rs
new file mode 100644
index 000000000..ba62434d7
--- /dev/null
+++ b/media/libvda/src/lib.rs
@@ -0,0 +1,13 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+pub mod decode;
+pub mod encode;
+
+mod bindings;
+mod error;
+mod format;
+
+pub use error::*;
+pub use format::*;
diff --git a/media/libvda/tests/decode_tests.rs b/media/libvda/tests/decode_tests.rs
new file mode 100644
index 000000000..751bfefbb
--- /dev/null
+++ b/media/libvda/tests/decode_tests.rs
@@ -0,0 +1,58 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Integration tests using LibVDA fake decode implemenation.
+
+use libvda::decode::*;
+use libvda::*;
+
+fn create_vda_instance() -> VdaInstance {
+ VdaInstance::new(VdaImplType::Fake).expect("failed to create VDAInstance")
+}
+
+#[test]
+fn test_create_instance() {
+ let instance = create_vda_instance();
+ let caps = instance.get_capabilities();
+
+ assert_ne!(caps.input_formats.len(), 0);
+ assert_ne!(caps.output_formats.len(), 0);
+}
+
+#[test]
+fn test_initialize_decode_session() {
+ let instance = create_vda_instance();
+ let _session = instance
+ .open_session(Profile::VP8)
+ .expect("failed to open a session for VP8");
+}
+
+#[test]
+fn test_decode_and_get_picture_ready_fake() {
+ let instance = create_vda_instance();
+ let mut session = instance
+ .open_session(Profile::VP8)
+ .expect("failed to open a session");
+
+ // Call decode() with dummy arguments.
+ let fake_bitstream_id = 12345;
+ session
+ .decode(
+ fake_bitstream_id,
+ 1, // fd
+ 0, // offset
+ 0, // bytes_used
+ )
+ .expect("failed to send a decode request");
+
+ // Since we are using the fake backend,
+ // we must get a event immediately after calling decode().
+ match session.read_event() {
+ Ok(Event::PictureReady { bitstream_id, .. }) => {
+ assert_eq!(bitstream_id, fake_bitstream_id);
+ }
+ Ok(event) => panic!("Obtained event is not PictureReady but {:?}", event),
+ Err(msg) => panic!("{}", msg),
+ }
+}
diff --git a/media/libvda/tests/encode_tests.rs b/media/libvda/tests/encode_tests.rs
new file mode 100644
index 000000000..e2bc0027c
--- /dev/null
+++ b/media/libvda/tests/encode_tests.rs
@@ -0,0 +1,115 @@
+// Copyright 2020 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Integration tests using LibVDA fake encode implementation.
+
+use libvda::encode::*;
+use libvda::*;
+
+fn create_vea_instance() -> VeaInstance {
+ VeaInstance::new(VeaImplType::Fake).expect("failed to create VeaInstance")
+}
+
+fn create_config() -> Config {
+ Config {
+ input_format: PixelFormat::YV12,
+ input_visible_height: 320,
+ input_visible_width: 192,
+ output_profile: Profile::H264ProfileBaseline,
+ bitrate: Bitrate {
+ mode: BitrateMode::CBR,
+ target: 100,
+ peak: 0,
+ },
+ initial_framerate: None,
+ h264_output_level: None,
+ }
+}
+
+#[test]
+fn test_create_instance() {
+ let instance = create_vea_instance();
+ let caps = instance.get_capabilities();
+
+ assert_ne!(caps.input_formats.len(), 0);
+ assert_ne!(caps.output_formats.len(), 0);
+}
+
+#[test]
+fn test_initialize_encode_session() {
+ let instance = create_vea_instance();
+ let config = create_config();
+
+ let _session = instance
+ .open_session(config)
+ .expect("failed to open a session");
+}
+
+#[test]
+fn test_encode_and_get_buffer_back() {
+ let instance = create_vea_instance();
+ let config = create_config();
+ let mut session = instance
+ .open_session(config)
+ .expect("failed to open a session");
+
+ // Call encode() with dummy arguments.
+ let fake_input_buffer_id = 12345;
+ let fake_planes = vec![];
+ session
+ .encode(
+ fake_input_buffer_id,
+ 1, // fd
+ &fake_planes, // planes
+ 0, // timestamp
+ false, // force_keyframe
+ )
+ .expect("failed to send an encode request");
+
+ // Since we are using the fake backend, we should get back
+ // the input buffer right away.
+ match session.read_event() {
+ Ok(Event::ProcessedInputBuffer(returned_input_buffer_id)) => {
+ assert_eq!(fake_input_buffer_id, returned_input_buffer_id);
+ }
+ Ok(event) => panic!("Obtained event is not ProcessedInputBuffer but {:?}", event),
+ Err(msg) => panic!("{}", msg),
+ }
+}
+
+#[test]
+fn test_use_output_buffer_and_get_buffer_back() {
+ let instance = create_vea_instance();
+ let config = create_config();
+ let mut session = instance
+ .open_session(config)
+ .expect("failed to open a session");
+
+ // Call use_output_buffer with dummy arguments.
+ let fake_output_buffer_id = 12345;
+ session
+ .use_output_buffer(
+ fake_output_buffer_id,
+ 2, // fd
+ 0, // offset
+ 0, // size
+ )
+ .expect("failed to send use_output_buffer request");
+
+ // Since we are using the fake backend, we should get back
+ // the input buffer right away.
+ match session.read_event() {
+ Ok(Event::ProcessedOutputBuffer {
+ output_buffer_id: returned_output_buffer_id,
+ ..
+ }) => {
+ assert_eq!(fake_output_buffer_id, returned_output_buffer_id);
+ }
+ Ok(event) => panic!(
+ "Obtained event is not ProcessedOutputBuffer but {:?}",
+ event
+ ),
+ Err(msg) => panic!("{}", msg),
+ }
+}
diff --git a/navbar.md b/navbar.md
index 292dd1b83..cc1d7cd78 100644
--- a/navbar.md
+++ b/navbar.md
@@ -1,8 +1,6 @@
# crosvm
-* [Home][home]
-* [CONTRIBUTING](/CONTRIBUTING.md)
-* [Architectural Overview](/docs/architecture.md)
-* [rust-vmm integration](/docs/rust-vmm.md)
-
-[home]: /README.md
+- [Home](/README.md)
+- [Contribution guide](/CONTRIBUTING.md)
+- [Architecture](/ARCHITECTURE.md)
+- [Book of crosvm](https://google.github.io/crosvm/)
diff --git a/net_sys/Android.bp b/net_sys/Android.bp
index 1adaed067..73838d399 100644
--- a/net_sys/Android.bp
+++ b/net_sys/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -15,75 +15,12 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "net_sys",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
+ "liblibc",
],
}
-
-rust_defaults {
- name: "net_sys_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "net_sys",
- srcs: ["src/lib.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libbase_rust",
- ],
-}
-
-rust_test_host {
- name: "net_sys_host_test_src_lib",
- defaults: ["net_sys_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "net_sys_device_test_src_lib",
- defaults: ["net_sys_defaults"],
-}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/net_sys/Cargo.toml b/net_sys/Cargo.toml
index 205e567ec..5da75afec 100644
--- a/net_sys/Cargo.toml
+++ b/net_sys/Cargo.toml
@@ -2,7 +2,8 @@
name = "net_sys"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
base = { path = "../base" }
+libc = "*"
diff --git a/net_sys/bindgen.sh b/net_sys/bindgen.sh
new file mode 100755
index 000000000..221cc680e
--- /dev/null
+++ b/net_sys/bindgen.sh
@@ -0,0 +1,55 @@
+#!/usr/bin/env bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Regenerate net_sys bindgen bindings.
+
+set -euo pipefail
+cd "$(dirname "${BASH_SOURCE[0]}")/.."
+
+source tools/impl/bindgen-common.sh
+
+# Replace definition of struct sockaddr with an import of libc::sockaddr.
+replace_sockaddr_libc() {
+ # Match the following structure definition across multiple lines:
+ #
+ # #[repr(C)]
+ # #[derive(Debug, Default, Copy, Clone)]
+ # pub struct sockaddr {
+ # pub sa_family: sa_family_t,
+ # pub sa_data: [::std::os::raw::c_char; 14usize],
+ # }
+ sed -E \
+ -e '1h;2,$H;$!d;g' \
+ -e 's/#\[repr\(C\)\]\n#\[derive\([^)]+\)\]\npub struct sockaddr \{\n( pub [^\n]+\n)+\}/\nuse libc::sockaddr;\n/'
+}
+
+# Remove custom sa_family_t type in favor of libc::sa_family_t.
+remove_sa_family_t() {
+ grep -v "pub type sa_family_t = "
+}
+
+bindgen_generate \
+ --allowlist-type='ifreq' \
+ --allowlist-type='net_device_flags' \
+ --bitfield-enum='net_device_flags' \
+ "${BINDGEN_LINUX_X86_HEADERS}/include/linux/if.h" \
+ | replace_linux_int_types \
+ | replace_sockaddr_libc \
+ | remove_sa_family_t \
+ | rustfmt \
+ > net_sys/src/iff.rs
+
+bindgen_generate \
+ --allowlist-type='sock_fprog' \
+ --allowlist-var='TUN_.*' \
+ "${BINDGEN_LINUX}/include/uapi/linux/if_tun.h" \
+ | replace_linux_int_types \
+ > net_sys/src/if_tun.rs
+
+bindgen_generate \
+ --allowlist-var='SIOC.*' \
+ "${BINDGEN_LINUX}/include/uapi/linux/sockios.h" \
+ | replace_linux_int_types \
+ > net_sys/src/sockios.rs
diff --git a/net_sys/src/if_tun.rs b/net_sys/src/if_tun.rs
index 0874b771d..d960c7e1f 100644
--- a/net_sys/src/if_tun.rs
+++ b/net_sys/src/if_tun.rs
@@ -1,599 +1,40 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+/* automatically generated by tools/bindgen-all-the-things */
-/* automatically generated by rust-bindgen */
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+pub const TUN_READQ_SIZE: u32 = 500;
+pub const TUN_TYPE_MASK: u32 = 15;
+pub const TUN_TX_TIMESTAMP: u32 = 1;
+pub const TUN_F_CSUM: u32 = 1;
+pub const TUN_F_TSO4: u32 = 2;
+pub const TUN_F_TSO6: u32 = 4;
+pub const TUN_F_TSO_ECN: u32 = 8;
+pub const TUN_F_UFO: u32 = 16;
+pub const TUN_PKT_STRIP: u32 = 1;
+pub const TUN_FLT_ALLMULTI: u32 = 1;
#[repr(C)]
-#[derive(Default)]
-pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>);
-impl<T> __IncompleteArrayField<T> {
- #[inline]
- pub fn new() -> Self {
- __IncompleteArrayField(::std::marker::PhantomData)
- }
- #[inline]
- pub unsafe fn as_ptr(&self) -> *const T {
- ::std::mem::transmute(self)
- }
- #[inline]
- pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
- ::std::mem::transmute(self)
- }
- #[inline]
- pub unsafe fn as_slice(&self, len: usize) -> &[T] {
- ::std::slice::from_raw_parts(self.as_ptr(), len)
- }
- #[inline]
- pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
- ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
- }
-}
-impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
- fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
- fmt.write_str("__IncompleteArrayField")
- }
-}
-impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
- #[inline]
- fn clone(&self) -> Self {
- Self::new()
- }
-}
-impl<T> ::std::marker::Copy for __IncompleteArrayField<T> {}
-pub const __BITS_PER_LONG: ::std::os::raw::c_uint = 64;
-pub const __FD_SETSIZE: ::std::os::raw::c_uint = 1024;
-pub const ETH_ALEN: ::std::os::raw::c_uint = 6;
-pub const ETH_HLEN: ::std::os::raw::c_uint = 14;
-pub const ETH_ZLEN: ::std::os::raw::c_uint = 60;
-pub const ETH_DATA_LEN: ::std::os::raw::c_uint = 1500;
-pub const ETH_FRAME_LEN: ::std::os::raw::c_uint = 1514;
-pub const ETH_FCS_LEN: ::std::os::raw::c_uint = 4;
-pub const ETH_P_LOOP: ::std::os::raw::c_uint = 96;
-pub const ETH_P_PUP: ::std::os::raw::c_uint = 512;
-pub const ETH_P_PUPAT: ::std::os::raw::c_uint = 513;
-pub const ETH_P_TSN: ::std::os::raw::c_uint = 8944;
-pub const ETH_P_IP: ::std::os::raw::c_uint = 2048;
-pub const ETH_P_X25: ::std::os::raw::c_uint = 2053;
-pub const ETH_P_ARP: ::std::os::raw::c_uint = 2054;
-pub const ETH_P_BPQ: ::std::os::raw::c_uint = 2303;
-pub const ETH_P_IEEEPUP: ::std::os::raw::c_uint = 2560;
-pub const ETH_P_IEEEPUPAT: ::std::os::raw::c_uint = 2561;
-pub const ETH_P_BATMAN: ::std::os::raw::c_uint = 17157;
-pub const ETH_P_DEC: ::std::os::raw::c_uint = 24576;
-pub const ETH_P_DNA_DL: ::std::os::raw::c_uint = 24577;
-pub const ETH_P_DNA_RC: ::std::os::raw::c_uint = 24578;
-pub const ETH_P_DNA_RT: ::std::os::raw::c_uint = 24579;
-pub const ETH_P_LAT: ::std::os::raw::c_uint = 24580;
-pub const ETH_P_DIAG: ::std::os::raw::c_uint = 24581;
-pub const ETH_P_CUST: ::std::os::raw::c_uint = 24582;
-pub const ETH_P_SCA: ::std::os::raw::c_uint = 24583;
-pub const ETH_P_TEB: ::std::os::raw::c_uint = 25944;
-pub const ETH_P_RARP: ::std::os::raw::c_uint = 32821;
-pub const ETH_P_ATALK: ::std::os::raw::c_uint = 32923;
-pub const ETH_P_AARP: ::std::os::raw::c_uint = 33011;
-pub const ETH_P_8021Q: ::std::os::raw::c_uint = 33024;
-pub const ETH_P_IPX: ::std::os::raw::c_uint = 33079;
-pub const ETH_P_IPV6: ::std::os::raw::c_uint = 34525;
-pub const ETH_P_PAUSE: ::std::os::raw::c_uint = 34824;
-pub const ETH_P_SLOW: ::std::os::raw::c_uint = 34825;
-pub const ETH_P_WCCP: ::std::os::raw::c_uint = 34878;
-pub const ETH_P_MPLS_UC: ::std::os::raw::c_uint = 34887;
-pub const ETH_P_MPLS_MC: ::std::os::raw::c_uint = 34888;
-pub const ETH_P_ATMMPOA: ::std::os::raw::c_uint = 34892;
-pub const ETH_P_PPP_DISC: ::std::os::raw::c_uint = 34915;
-pub const ETH_P_PPP_SES: ::std::os::raw::c_uint = 34916;
-pub const ETH_P_LINK_CTL: ::std::os::raw::c_uint = 34924;
-pub const ETH_P_ATMFATE: ::std::os::raw::c_uint = 34948;
-pub const ETH_P_PAE: ::std::os::raw::c_uint = 34958;
-pub const ETH_P_AOE: ::std::os::raw::c_uint = 34978;
-pub const ETH_P_8021AD: ::std::os::raw::c_uint = 34984;
-pub const ETH_P_802_EX1: ::std::os::raw::c_uint = 34997;
-pub const ETH_P_TIPC: ::std::os::raw::c_uint = 35018;
-pub const ETH_P_8021AH: ::std::os::raw::c_uint = 35047;
-pub const ETH_P_MVRP: ::std::os::raw::c_uint = 35061;
-pub const ETH_P_1588: ::std::os::raw::c_uint = 35063;
-pub const ETH_P_PRP: ::std::os::raw::c_uint = 35067;
-pub const ETH_P_FCOE: ::std::os::raw::c_uint = 35078;
-pub const ETH_P_TDLS: ::std::os::raw::c_uint = 35085;
-pub const ETH_P_FIP: ::std::os::raw::c_uint = 35092;
-pub const ETH_P_80221: ::std::os::raw::c_uint = 35095;
-pub const ETH_P_LOOPBACK: ::std::os::raw::c_uint = 36864;
-pub const ETH_P_QINQ1: ::std::os::raw::c_uint = 37120;
-pub const ETH_P_QINQ2: ::std::os::raw::c_uint = 37376;
-pub const ETH_P_QINQ3: ::std::os::raw::c_uint = 37632;
-pub const ETH_P_EDSA: ::std::os::raw::c_uint = 56026;
-pub const ETH_P_AF_IUCV: ::std::os::raw::c_uint = 64507;
-pub const ETH_P_802_3_MIN: ::std::os::raw::c_uint = 1536;
-pub const ETH_P_802_3: ::std::os::raw::c_uint = 1;
-pub const ETH_P_AX25: ::std::os::raw::c_uint = 2;
-pub const ETH_P_ALL: ::std::os::raw::c_uint = 3;
-pub const ETH_P_802_2: ::std::os::raw::c_uint = 4;
-pub const ETH_P_SNAP: ::std::os::raw::c_uint = 5;
-pub const ETH_P_DDCMP: ::std::os::raw::c_uint = 6;
-pub const ETH_P_WAN_PPP: ::std::os::raw::c_uint = 7;
-pub const ETH_P_PPP_MP: ::std::os::raw::c_uint = 8;
-pub const ETH_P_LOCALTALK: ::std::os::raw::c_uint = 9;
-pub const ETH_P_CAN: ::std::os::raw::c_uint = 12;
-pub const ETH_P_CANFD: ::std::os::raw::c_uint = 13;
-pub const ETH_P_PPPTALK: ::std::os::raw::c_uint = 16;
-pub const ETH_P_TR_802_2: ::std::os::raw::c_uint = 17;
-pub const ETH_P_MOBITEX: ::std::os::raw::c_uint = 21;
-pub const ETH_P_CONTROL: ::std::os::raw::c_uint = 22;
-pub const ETH_P_IRDA: ::std::os::raw::c_uint = 23;
-pub const ETH_P_ECONET: ::std::os::raw::c_uint = 24;
-pub const ETH_P_HDLC: ::std::os::raw::c_uint = 25;
-pub const ETH_P_ARCNET: ::std::os::raw::c_uint = 26;
-pub const ETH_P_DSA: ::std::os::raw::c_uint = 27;
-pub const ETH_P_TRAILER: ::std::os::raw::c_uint = 28;
-pub const ETH_P_PHONET: ::std::os::raw::c_uint = 245;
-pub const ETH_P_IEEE802154: ::std::os::raw::c_uint = 246;
-pub const ETH_P_CAIF: ::std::os::raw::c_uint = 247;
-pub const ETH_P_XDSA: ::std::os::raw::c_uint = 248;
-pub const BPF_LD: ::std::os::raw::c_uint = 0;
-pub const BPF_LDX: ::std::os::raw::c_uint = 1;
-pub const BPF_ST: ::std::os::raw::c_uint = 2;
-pub const BPF_STX: ::std::os::raw::c_uint = 3;
-pub const BPF_ALU: ::std::os::raw::c_uint = 4;
-pub const BPF_JMP: ::std::os::raw::c_uint = 5;
-pub const BPF_RET: ::std::os::raw::c_uint = 6;
-pub const BPF_MISC: ::std::os::raw::c_uint = 7;
-pub const BPF_W: ::std::os::raw::c_uint = 0;
-pub const BPF_H: ::std::os::raw::c_uint = 8;
-pub const BPF_B: ::std::os::raw::c_uint = 16;
-pub const BPF_IMM: ::std::os::raw::c_uint = 0;
-pub const BPF_ABS: ::std::os::raw::c_uint = 32;
-pub const BPF_IND: ::std::os::raw::c_uint = 64;
-pub const BPF_MEM: ::std::os::raw::c_uint = 96;
-pub const BPF_LEN: ::std::os::raw::c_uint = 128;
-pub const BPF_MSH: ::std::os::raw::c_uint = 160;
-pub const BPF_ADD: ::std::os::raw::c_uint = 0;
-pub const BPF_SUB: ::std::os::raw::c_uint = 16;
-pub const BPF_MUL: ::std::os::raw::c_uint = 32;
-pub const BPF_DIV: ::std::os::raw::c_uint = 48;
-pub const BPF_OR: ::std::os::raw::c_uint = 64;
-pub const BPF_AND: ::std::os::raw::c_uint = 80;
-pub const BPF_LSH: ::std::os::raw::c_uint = 96;
-pub const BPF_RSH: ::std::os::raw::c_uint = 112;
-pub const BPF_NEG: ::std::os::raw::c_uint = 128;
-pub const BPF_MOD: ::std::os::raw::c_uint = 144;
-pub const BPF_XOR: ::std::os::raw::c_uint = 160;
-pub const BPF_JA: ::std::os::raw::c_uint = 0;
-pub const BPF_JEQ: ::std::os::raw::c_uint = 16;
-pub const BPF_JGT: ::std::os::raw::c_uint = 32;
-pub const BPF_JGE: ::std::os::raw::c_uint = 48;
-pub const BPF_JSET: ::std::os::raw::c_uint = 64;
-pub const BPF_K: ::std::os::raw::c_uint = 0;
-pub const BPF_X: ::std::os::raw::c_uint = 8;
-pub const BPF_MAXINSNS: ::std::os::raw::c_uint = 4096;
-pub const BPF_MAJOR_VERSION: ::std::os::raw::c_uint = 1;
-pub const BPF_MINOR_VERSION: ::std::os::raw::c_uint = 1;
-pub const BPF_A: ::std::os::raw::c_uint = 16;
-pub const BPF_TAX: ::std::os::raw::c_uint = 0;
-pub const BPF_TXA: ::std::os::raw::c_uint = 128;
-pub const BPF_MEMWORDS: ::std::os::raw::c_uint = 16;
-pub const SKF_AD_OFF: ::std::os::raw::c_int = -4096;
-pub const SKF_AD_PROTOCOL: ::std::os::raw::c_uint = 0;
-pub const SKF_AD_PKTTYPE: ::std::os::raw::c_uint = 4;
-pub const SKF_AD_IFINDEX: ::std::os::raw::c_uint = 8;
-pub const SKF_AD_NLATTR: ::std::os::raw::c_uint = 12;
-pub const SKF_AD_NLATTR_NEST: ::std::os::raw::c_uint = 16;
-pub const SKF_AD_MARK: ::std::os::raw::c_uint = 20;
-pub const SKF_AD_QUEUE: ::std::os::raw::c_uint = 24;
-pub const SKF_AD_HATYPE: ::std::os::raw::c_uint = 28;
-pub const SKF_AD_RXHASH: ::std::os::raw::c_uint = 32;
-pub const SKF_AD_CPU: ::std::os::raw::c_uint = 36;
-pub const SKF_AD_ALU_XOR_X: ::std::os::raw::c_uint = 40;
-pub const SKF_AD_VLAN_TAG: ::std::os::raw::c_uint = 44;
-pub const SKF_AD_VLAN_TAG_PRESENT: ::std::os::raw::c_uint = 48;
-pub const SKF_AD_PAY_OFFSET: ::std::os::raw::c_uint = 52;
-pub const SKF_AD_RANDOM: ::std::os::raw::c_uint = 56;
-pub const SKF_AD_VLAN_TPID: ::std::os::raw::c_uint = 60;
-pub const SKF_AD_MAX: ::std::os::raw::c_uint = 64;
-pub const SKF_NET_OFF: ::std::os::raw::c_int = -1048576;
-pub const SKF_LL_OFF: ::std::os::raw::c_int = -2097152;
-pub const BPF_NET_OFF: ::std::os::raw::c_int = -1048576;
-pub const BPF_LL_OFF: ::std::os::raw::c_int = -2097152;
-pub const TUN_READQ_SIZE: ::std::os::raw::c_uint = 500;
-pub const TUN_TYPE_MASK: ::std::os::raw::c_uint = 15;
-pub const IFF_TUN: ::std::os::raw::c_uint = 1;
-pub const IFF_TAP: ::std::os::raw::c_uint = 2;
-pub const IFF_NO_PI: ::std::os::raw::c_uint = 4096;
-pub const IFF_ONE_QUEUE: ::std::os::raw::c_uint = 8192;
-pub const IFF_VNET_HDR: ::std::os::raw::c_uint = 16384;
-pub const IFF_TUN_EXCL: ::std::os::raw::c_uint = 32768;
-pub const IFF_MULTI_QUEUE: ::std::os::raw::c_uint = 256;
-pub const IFF_ATTACH_QUEUE: ::std::os::raw::c_uint = 512;
-pub const IFF_DETACH_QUEUE: ::std::os::raw::c_uint = 1024;
-pub const IFF_PERSIST: ::std::os::raw::c_uint = 2048;
-pub const IFF_NOFILTER: ::std::os::raw::c_uint = 4096;
-pub const TUN_TX_TIMESTAMP: ::std::os::raw::c_uint = 1;
-pub const TUN_F_CSUM: ::std::os::raw::c_uint = 1;
-pub const TUN_F_TSO4: ::std::os::raw::c_uint = 2;
-pub const TUN_F_TSO6: ::std::os::raw::c_uint = 4;
-pub const TUN_F_TSO_ECN: ::std::os::raw::c_uint = 8;
-pub const TUN_F_UFO: ::std::os::raw::c_uint = 16;
-pub const TUN_PKT_STRIP: ::std::os::raw::c_uint = 1;
-pub const TUN_FLT_ALLMULTI: ::std::os::raw::c_uint = 1;
-pub type __s8 = ::std::os::raw::c_schar;
-pub type __u8 = ::std::os::raw::c_uchar;
-pub type __s16 = ::std::os::raw::c_short;
-pub type __u16 = ::std::os::raw::c_ushort;
-pub type __s32 = ::std::os::raw::c_int;
-pub type __u32 = ::std::os::raw::c_uint;
-pub type __s64 = ::std::os::raw::c_longlong;
-pub type __u64 = ::std::os::raw::c_ulonglong;
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct __kernel_fd_set {
- pub fds_bits: [::std::os::raw::c_ulong; 16usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_fd_set() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_fd_set>(),
- 128usize,
- concat!("Size of: ", stringify!(__kernel_fd_set))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_fd_set>(),
- 8usize,
- concat!("Alignment of ", stringify!(__kernel_fd_set))
- );
- assert_eq!(
- unsafe { &(*(0 as *const __kernel_fd_set)).fds_bits as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(__kernel_fd_set),
- "::",
- stringify!(fds_bits)
- )
- );
-}
-impl Clone for __kernel_fd_set {
- fn clone(&self) -> Self {
- *self
- }
-}
-pub type __kernel_sighandler_t =
- ::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
-pub type __kernel_key_t = ::std::os::raw::c_int;
-pub type __kernel_mqd_t = ::std::os::raw::c_int;
-pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
-pub type __kernel_long_t = ::std::os::raw::c_long;
-pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
-pub type __kernel_ino_t = __kernel_ulong_t;
-pub type __kernel_mode_t = ::std::os::raw::c_uint;
-pub type __kernel_pid_t = ::std::os::raw::c_int;
-pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
-pub type __kernel_uid_t = ::std::os::raw::c_uint;
-pub type __kernel_gid_t = ::std::os::raw::c_uint;
-pub type __kernel_suseconds_t = __kernel_long_t;
-pub type __kernel_daddr_t = ::std::os::raw::c_int;
-pub type __kernel_uid32_t = ::std::os::raw::c_uint;
-pub type __kernel_gid32_t = ::std::os::raw::c_uint;
-pub type __kernel_size_t = __kernel_ulong_t;
-pub type __kernel_ssize_t = __kernel_long_t;
-pub type __kernel_ptrdiff_t = __kernel_long_t;
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct __kernel_fsid_t {
- pub val: [::std::os::raw::c_int; 2usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_fsid_t() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_fsid_t>(),
- 8usize,
- concat!("Size of: ", stringify!(__kernel_fsid_t))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_fsid_t>(),
- 4usize,
- concat!("Alignment of ", stringify!(__kernel_fsid_t))
- );
- assert_eq!(
- unsafe { &(*(0 as *const __kernel_fsid_t)).val as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(__kernel_fsid_t),
- "::",
- stringify!(val)
- )
- );
-}
-impl Clone for __kernel_fsid_t {
- fn clone(&self) -> Self {
- *self
- }
-}
-pub type __kernel_off_t = __kernel_long_t;
-pub type __kernel_loff_t = ::std::os::raw::c_longlong;
-pub type __kernel_time_t = __kernel_long_t;
-pub type __kernel_clock_t = __kernel_long_t;
-pub type __kernel_timer_t = ::std::os::raw::c_int;
-pub type __kernel_clockid_t = ::std::os::raw::c_int;
-pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
-pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
-pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
-pub type __le16 = __u16;
-pub type __be16 = __u16;
-pub type __le32 = __u32;
-pub type __be32 = __u32;
-pub type __le64 = __u64;
-pub type __be64 = __u64;
-pub type __sum16 = __u16;
-pub type __wsum = __u32;
-#[repr(C, packed)]
-#[derive(Debug, Default, Copy)]
-pub struct ethhdr {
- pub h_dest: [::std::os::raw::c_uchar; 6usize],
- pub h_source: [::std::os::raw::c_uchar; 6usize],
- pub h_proto: __be16,
-}
-#[test]
-fn bindgen_test_layout_ethhdr() {
- assert_eq!(
- ::std::mem::size_of::<ethhdr>(),
- 14usize,
- concat!("Size of: ", stringify!(ethhdr))
- );
- assert_eq!(
- ::std::mem::align_of::<ethhdr>(),
- 1usize,
- concat!("Alignment of ", stringify!(ethhdr))
- );
- assert_eq!(
- unsafe { &(*(0 as *const ethhdr)).h_dest as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(ethhdr),
- "::",
- stringify!(h_dest)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const ethhdr)).h_source as *const _ as usize },
- 6usize,
- concat!(
- "Alignment of field: ",
- stringify!(ethhdr),
- "::",
- stringify!(h_source)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const ethhdr)).h_proto as *const _ as usize },
- 12usize,
- concat!(
- "Alignment of field: ",
- stringify!(ethhdr),
- "::",
- stringify!(h_proto)
- )
- );
-}
-impl Clone for ethhdr {
- fn clone(&self) -> Self {
- *self
- }
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
+#[derive(Debug, Default, Copy, Clone)]
pub struct sock_filter {
- pub code: __u16,
- pub jt: __u8,
- pub jf: __u8,
- pub k: __u32,
-}
-#[test]
-fn bindgen_test_layout_sock_filter() {
- assert_eq!(
- ::std::mem::size_of::<sock_filter>(),
- 8usize,
- concat!("Size of: ", stringify!(sock_filter))
- );
- assert_eq!(
- ::std::mem::align_of::<sock_filter>(),
- 4usize,
- concat!("Alignment of ", stringify!(sock_filter))
- );
- assert_eq!(
- unsafe { &(*(0 as *const sock_filter)).code as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(sock_filter),
- "::",
- stringify!(code)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const sock_filter)).jt as *const _ as usize },
- 2usize,
- concat!(
- "Alignment of field: ",
- stringify!(sock_filter),
- "::",
- stringify!(jt)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const sock_filter)).jf as *const _ as usize },
- 3usize,
- concat!(
- "Alignment of field: ",
- stringify!(sock_filter),
- "::",
- stringify!(jf)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const sock_filter)).k as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(sock_filter),
- "::",
- stringify!(k)
- )
- );
-}
-impl Clone for sock_filter {
- fn clone(&self) -> Self {
- *self
- }
+ pub code: u16,
+ pub jt: u8,
+ pub jf: u8,
+ pub k: u32,
}
#[repr(C)]
-#[derive(Debug, Copy)]
+#[derive(Debug, Copy, Clone)]
pub struct sock_fprog {
pub len: ::std::os::raw::c_ushort,
pub filter: *mut sock_filter,
}
-#[test]
-fn bindgen_test_layout_sock_fprog() {
- assert_eq!(
- ::std::mem::size_of::<sock_fprog>(),
- 16usize,
- concat!("Size of: ", stringify!(sock_fprog))
- );
- assert_eq!(
- ::std::mem::align_of::<sock_fprog>(),
- 8usize,
- concat!("Alignment of ", stringify!(sock_fprog))
- );
- assert_eq!(
- unsafe { &(*(0 as *const sock_fprog)).len as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(sock_fprog),
- "::",
- stringify!(len)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const sock_fprog)).filter as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(sock_fprog),
- "::",
- stringify!(filter)
- )
- );
-}
-impl Clone for sock_fprog {
- fn clone(&self) -> Self {
- *self
- }
-}
impl Default for sock_fprog {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct tun_pi {
- pub flags: __u16,
- pub proto: __be16,
-}
-#[test]
-fn bindgen_test_layout_tun_pi() {
- assert_eq!(
- ::std::mem::size_of::<tun_pi>(),
- 4usize,
- concat!("Size of: ", stringify!(tun_pi))
- );
- assert_eq!(
- ::std::mem::align_of::<tun_pi>(),
- 2usize,
- concat!("Alignment of ", stringify!(tun_pi))
- );
- assert_eq!(
- unsafe { &(*(0 as *const tun_pi)).flags as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(tun_pi),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const tun_pi)).proto as *const _ as usize },
- 2usize,
- concat!(
- "Alignment of field: ",
- stringify!(tun_pi),
- "::",
- stringify!(proto)
- )
- );
-}
-impl Clone for tun_pi {
- fn clone(&self) -> Self {
- *self
- }
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct tun_filter {
- pub flags: __u16,
- pub count: __u16,
- pub addr: __IncompleteArrayField<[__u8; 6usize]>,
-}
-#[test]
-fn bindgen_test_layout_tun_filter() {
- assert_eq!(
- ::std::mem::size_of::<tun_filter>(),
- 4usize,
- concat!("Size of: ", stringify!(tun_filter))
- );
- assert_eq!(
- ::std::mem::align_of::<tun_filter>(),
- 2usize,
- concat!("Alignment of ", stringify!(tun_filter))
- );
- assert_eq!(
- unsafe { &(*(0 as *const tun_filter)).flags as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(tun_filter),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const tun_filter)).count as *const _ as usize },
- 2usize,
- concat!(
- "Alignment of field: ",
- stringify!(tun_filter),
- "::",
- stringify!(count)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const tun_filter)).addr as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(tun_filter),
- "::",
- stringify!(addr)
- )
- );
-}
-impl Clone for tun_filter {
- fn clone(&self) -> Self {
- *self
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
diff --git a/net_sys/src/iff.rs b/net_sys/src/iff.rs
index 928a2cc9f..1b5bf1c1b 100644
--- a/net_sys/src/iff.rs
+++ b/net_sys/src/iff.rs
@@ -1,1150 +1,157 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+/* automatically generated by tools/bindgen-all-the-things */
-/* automatically generated by rust-bindgen */
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+
+use libc::sockaddr;
-#[repr(C)]
-#[derive(Default)]
-pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
-impl<T> __IncompleteArrayField<T> {
- #[inline]
- pub fn new() -> Self {
- __IncompleteArrayField(::std::marker::PhantomData, [])
- }
- #[inline]
- pub unsafe fn as_ptr(&self) -> *const T {
- ::std::mem::transmute(self)
- }
- #[inline]
- pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
- ::std::mem::transmute(self)
- }
- #[inline]
- pub unsafe fn as_slice(&self, len: usize) -> &[T] {
- ::std::slice::from_raw_parts(self.as_ptr(), len)
- }
- #[inline]
- pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
- ::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
- }
-}
-impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
- fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
- fmt.write_str("__IncompleteArrayField")
- }
-}
-impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
- #[inline]
- fn clone(&self) -> Self {
- Self::new()
- }
-}
-pub const __UAPI_DEF_IF_IFCONF: u32 = 1;
-pub const __UAPI_DEF_IF_IFMAP: u32 = 1;
-pub const __UAPI_DEF_IF_IFNAMSIZ: u32 = 1;
-pub const __UAPI_DEF_IF_IFREQ: u32 = 1;
-pub const __UAPI_DEF_IF_NET_DEVICE_FLAGS: u32 = 1;
-pub const __UAPI_DEF_IF_NET_DEVICE_FLAGS_LOWER_UP_DORMANT_ECHO: u32 = 1;
-pub const __UAPI_DEF_IN_ADDR: u32 = 1;
-pub const __UAPI_DEF_IN_IPPROTO: u32 = 1;
-pub const __UAPI_DEF_IN_PKTINFO: u32 = 1;
-pub const __UAPI_DEF_IP_MREQ: u32 = 1;
-pub const __UAPI_DEF_SOCKADDR_IN: u32 = 1;
-pub const __UAPI_DEF_IN_CLASS: u32 = 1;
-pub const __UAPI_DEF_IN6_ADDR: u32 = 1;
-pub const __UAPI_DEF_IN6_ADDR_ALT: u32 = 1;
-pub const __UAPI_DEF_SOCKADDR_IN6: u32 = 1;
-pub const __UAPI_DEF_IPV6_MREQ: u32 = 1;
-pub const __UAPI_DEF_IPPROTO_V6: u32 = 1;
-pub const __UAPI_DEF_IPV6_OPTIONS: u32 = 1;
-pub const __UAPI_DEF_IN6_PKTINFO: u32 = 1;
-pub const __UAPI_DEF_IP6_MTUINFO: u32 = 1;
-pub const __UAPI_DEF_SOCKADDR_IPX: u32 = 1;
-pub const __UAPI_DEF_IPX_ROUTE_DEFINITION: u32 = 1;
-pub const __UAPI_DEF_IPX_INTERFACE_DEFINITION: u32 = 1;
-pub const __UAPI_DEF_IPX_CONFIG_DATA: u32 = 1;
-pub const __UAPI_DEF_IPX_ROUTE_DEF: u32 = 1;
-pub const __UAPI_DEF_XATTR: u32 = 1;
-pub const __BITS_PER_LONG: u32 = 64;
-pub const __FD_SETSIZE: u32 = 1024;
-pub const _K_SS_MAXSIZE: u32 = 128;
-pub const _SYS_SOCKET_H: u32 = 1;
-pub const _FEATURES_H: u32 = 1;
-pub const _DEFAULT_SOURCE: u32 = 1;
-pub const __USE_ISOC11: u32 = 1;
-pub const __USE_ISOC99: u32 = 1;
-pub const __USE_ISOC95: u32 = 1;
-pub const __USE_POSIX_IMPLICITLY: u32 = 1;
-pub const _POSIX_SOURCE: u32 = 1;
-pub const _POSIX_C_SOURCE: u32 = 200809;
-pub const __USE_POSIX: u32 = 1;
-pub const __USE_POSIX2: u32 = 1;
-pub const __USE_POSIX199309: u32 = 1;
-pub const __USE_POSIX199506: u32 = 1;
-pub const __USE_XOPEN2K: u32 = 1;
-pub const __USE_XOPEN2K8: u32 = 1;
-pub const _ATFILE_SOURCE: u32 = 1;
-pub const __USE_MISC: u32 = 1;
-pub const __USE_ATFILE: u32 = 1;
-pub const __USE_FORTIFY_LEVEL: u32 = 0;
-pub const __GLIBC_USE_DEPRECATED_GETS: u32 = 0;
-pub const _STDC_PREDEF_H: u32 = 1;
-pub const __STDC_IEC_559__: u32 = 1;
-pub const __STDC_IEC_559_COMPLEX__: u32 = 1;
-pub const __STDC_ISO_10646__: u32 = 201706;
-pub const __STDC_NO_THREADS__: u32 = 1;
-pub const __GNU_LIBRARY__: u32 = 6;
-pub const __GLIBC__: u32 = 2;
-pub const __GLIBC_MINOR__: u32 = 27;
-pub const _SYS_CDEFS_H: u32 = 1;
-pub const __glibc_c99_flexarr_available: u32 = 1;
-pub const __WORDSIZE: u32 = 64;
-pub const __WORDSIZE_TIME64_COMPAT32: u32 = 1;
-pub const __SYSCALL_WORDSIZE: u32 = 64;
-pub const __HAVE_GENERIC_SELECTION: u32 = 1;
-pub const __iovec_defined: u32 = 1;
-pub const _SYS_TYPES_H: u32 = 1;
-pub const _BITS_TYPES_H: u32 = 1;
-pub const _BITS_TYPESIZES_H: u32 = 1;
-pub const __OFF_T_MATCHES_OFF64_T: u32 = 1;
-pub const __INO_T_MATCHES_INO64_T: u32 = 1;
-pub const __RLIM_T_MATCHES_RLIM64_T: u32 = 1;
-pub const __clock_t_defined: u32 = 1;
-pub const __clockid_t_defined: u32 = 1;
-pub const __time_t_defined: u32 = 1;
-pub const __timer_t_defined: u32 = 1;
-pub const _BITS_STDINT_INTN_H: u32 = 1;
-pub const __BIT_TYPES_DEFINED__: u32 = 1;
-pub const _ENDIAN_H: u32 = 1;
-pub const __LITTLE_ENDIAN: u32 = 1234;
-pub const __BIG_ENDIAN: u32 = 4321;
-pub const __PDP_ENDIAN: u32 = 3412;
-pub const __BYTE_ORDER: u32 = 1234;
-pub const __FLOAT_WORD_ORDER: u32 = 1234;
-pub const LITTLE_ENDIAN: u32 = 1234;
-pub const BIG_ENDIAN: u32 = 4321;
-pub const PDP_ENDIAN: u32 = 3412;
-pub const BYTE_ORDER: u32 = 1234;
-pub const _BITS_BYTESWAP_H: u32 = 1;
-pub const _BITS_UINTN_IDENTITY_H: u32 = 1;
-pub const _SYS_SELECT_H: u32 = 1;
-pub const __FD_ZERO_STOS: &'static [u8; 6usize] = b"stosq\0";
-pub const __sigset_t_defined: u32 = 1;
-pub const __timeval_defined: u32 = 1;
-pub const _STRUCT_TIMESPEC: u32 = 1;
-pub const FD_SETSIZE: u32 = 1024;
-pub const _BITS_PTHREADTYPES_COMMON_H: u32 = 1;
-pub const _THREAD_SHARED_TYPES_H: u32 = 1;
-pub const _BITS_PTHREADTYPES_ARCH_H: u32 = 1;
-pub const __SIZEOF_PTHREAD_MUTEX_T: u32 = 40;
-pub const __SIZEOF_PTHREAD_ATTR_T: u32 = 56;
-pub const __SIZEOF_PTHREAD_RWLOCK_T: u32 = 56;
-pub const __SIZEOF_PTHREAD_BARRIER_T: u32 = 32;
-pub const __SIZEOF_PTHREAD_MUTEXATTR_T: u32 = 4;
-pub const __SIZEOF_PTHREAD_COND_T: u32 = 48;
-pub const __SIZEOF_PTHREAD_CONDATTR_T: u32 = 4;
-pub const __SIZEOF_PTHREAD_RWLOCKATTR_T: u32 = 8;
-pub const __SIZEOF_PTHREAD_BARRIERATTR_T: u32 = 4;
-pub const __PTHREAD_MUTEX_LOCK_ELISION: u32 = 1;
-pub const __PTHREAD_MUTEX_NUSERS_AFTER_KIND: u32 = 0;
-pub const __PTHREAD_MUTEX_USE_UNION: u32 = 0;
-pub const __PTHREAD_RWLOCK_INT_FLAGS_SHARED: u32 = 1;
-pub const __PTHREAD_MUTEX_HAVE_PREV: u32 = 1;
-pub const __have_pthread_attr_t: u32 = 1;
-pub const PF_UNSPEC: u32 = 0;
-pub const PF_LOCAL: u32 = 1;
-pub const PF_UNIX: u32 = 1;
-pub const PF_FILE: u32 = 1;
-pub const PF_INET: u32 = 2;
-pub const PF_AX25: u32 = 3;
-pub const PF_IPX: u32 = 4;
-pub const PF_APPLETALK: u32 = 5;
-pub const PF_NETROM: u32 = 6;
-pub const PF_BRIDGE: u32 = 7;
-pub const PF_ATMPVC: u32 = 8;
-pub const PF_X25: u32 = 9;
-pub const PF_INET6: u32 = 10;
-pub const PF_ROSE: u32 = 11;
-pub const PF_DECnet: u32 = 12;
-pub const PF_NETBEUI: u32 = 13;
-pub const PF_SECURITY: u32 = 14;
-pub const PF_KEY: u32 = 15;
-pub const PF_NETLINK: u32 = 16;
-pub const PF_ROUTE: u32 = 16;
-pub const PF_PACKET: u32 = 17;
-pub const PF_ASH: u32 = 18;
-pub const PF_ECONET: u32 = 19;
-pub const PF_ATMSVC: u32 = 20;
-pub const PF_RDS: u32 = 21;
-pub const PF_SNA: u32 = 22;
-pub const PF_IRDA: u32 = 23;
-pub const PF_PPPOX: u32 = 24;
-pub const PF_WANPIPE: u32 = 25;
-pub const PF_LLC: u32 = 26;
-pub const PF_IB: u32 = 27;
-pub const PF_MPLS: u32 = 28;
-pub const PF_CAN: u32 = 29;
-pub const PF_TIPC: u32 = 30;
-pub const PF_BLUETOOTH: u32 = 31;
-pub const PF_IUCV: u32 = 32;
-pub const PF_RXRPC: u32 = 33;
-pub const PF_ISDN: u32 = 34;
-pub const PF_PHONET: u32 = 35;
-pub const PF_IEEE802154: u32 = 36;
-pub const PF_CAIF: u32 = 37;
-pub const PF_ALG: u32 = 38;
-pub const PF_NFC: u32 = 39;
-pub const PF_VSOCK: u32 = 40;
-pub const PF_KCM: u32 = 41;
-pub const PF_QIPCRTR: u32 = 42;
-pub const PF_SMC: u32 = 43;
-pub const PF_MAX: u32 = 44;
-pub const AF_UNSPEC: u32 = 0;
-pub const AF_LOCAL: u32 = 1;
-pub const AF_UNIX: u32 = 1;
-pub const AF_FILE: u32 = 1;
-pub const AF_INET: u32 = 2;
-pub const AF_AX25: u32 = 3;
-pub const AF_IPX: u32 = 4;
-pub const AF_APPLETALK: u32 = 5;
-pub const AF_NETROM: u32 = 6;
-pub const AF_BRIDGE: u32 = 7;
-pub const AF_ATMPVC: u32 = 8;
-pub const AF_X25: u32 = 9;
-pub const AF_INET6: u32 = 10;
-pub const AF_ROSE: u32 = 11;
-pub const AF_DECnet: u32 = 12;
-pub const AF_NETBEUI: u32 = 13;
-pub const AF_SECURITY: u32 = 14;
-pub const AF_KEY: u32 = 15;
-pub const AF_NETLINK: u32 = 16;
-pub const AF_ROUTE: u32 = 16;
-pub const AF_PACKET: u32 = 17;
-pub const AF_ASH: u32 = 18;
-pub const AF_ECONET: u32 = 19;
-pub const AF_ATMSVC: u32 = 20;
-pub const AF_RDS: u32 = 21;
-pub const AF_SNA: u32 = 22;
-pub const AF_IRDA: u32 = 23;
-pub const AF_PPPOX: u32 = 24;
-pub const AF_WANPIPE: u32 = 25;
-pub const AF_LLC: u32 = 26;
-pub const AF_IB: u32 = 27;
-pub const AF_MPLS: u32 = 28;
-pub const AF_CAN: u32 = 29;
-pub const AF_TIPC: u32 = 30;
-pub const AF_BLUETOOTH: u32 = 31;
-pub const AF_IUCV: u32 = 32;
-pub const AF_RXRPC: u32 = 33;
-pub const AF_ISDN: u32 = 34;
-pub const AF_PHONET: u32 = 35;
-pub const AF_IEEE802154: u32 = 36;
-pub const AF_CAIF: u32 = 37;
-pub const AF_ALG: u32 = 38;
-pub const AF_NFC: u32 = 39;
-pub const AF_VSOCK: u32 = 40;
-pub const AF_KCM: u32 = 41;
-pub const AF_QIPCRTR: u32 = 42;
-pub const AF_SMC: u32 = 43;
-pub const AF_MAX: u32 = 44;
-pub const SOL_RAW: u32 = 255;
-pub const SOL_DECNET: u32 = 261;
-pub const SOL_X25: u32 = 262;
-pub const SOL_PACKET: u32 = 263;
-pub const SOL_ATM: u32 = 264;
-pub const SOL_AAL: u32 = 265;
-pub const SOL_IRDA: u32 = 266;
-pub const SOL_NETBEUI: u32 = 267;
-pub const SOL_LLC: u32 = 268;
-pub const SOL_DCCP: u32 = 269;
-pub const SOL_NETLINK: u32 = 270;
-pub const SOL_TIPC: u32 = 271;
-pub const SOL_RXRPC: u32 = 272;
-pub const SOL_PPPOL2TP: u32 = 273;
-pub const SOL_BLUETOOTH: u32 = 274;
-pub const SOL_PNPIPE: u32 = 275;
-pub const SOL_RDS: u32 = 276;
-pub const SOL_IUCV: u32 = 277;
-pub const SOL_CAIF: u32 = 278;
-pub const SOL_ALG: u32 = 279;
-pub const SOL_NFC: u32 = 280;
-pub const SOL_KCM: u32 = 281;
-pub const SOL_TLS: u32 = 282;
-pub const SOMAXCONN: u32 = 128;
-pub const _BITS_SOCKADDR_H: u32 = 1;
-pub const _SS_SIZE: u32 = 128;
-pub const FIOSETOWN: u32 = 35073;
-pub const SIOCSPGRP: u32 = 35074;
-pub const FIOGETOWN: u32 = 35075;
-pub const SIOCGPGRP: u32 = 35076;
-pub const SIOCATMARK: u32 = 35077;
-pub const SIOCGSTAMP: u32 = 35078;
-pub const SIOCGSTAMPNS: u32 = 35079;
-pub const SOL_SOCKET: u32 = 1;
-pub const SO_DEBUG: u32 = 1;
-pub const SO_REUSEADDR: u32 = 2;
-pub const SO_TYPE: u32 = 3;
-pub const SO_ERROR: u32 = 4;
-pub const SO_DONTROUTE: u32 = 5;
-pub const SO_BROADCAST: u32 = 6;
-pub const SO_SNDBUF: u32 = 7;
-pub const SO_RCVBUF: u32 = 8;
-pub const SO_SNDBUFFORCE: u32 = 32;
-pub const SO_RCVBUFFORCE: u32 = 33;
-pub const SO_KEEPALIVE: u32 = 9;
-pub const SO_OOBINLINE: u32 = 10;
-pub const SO_NO_CHECK: u32 = 11;
-pub const SO_PRIORITY: u32 = 12;
-pub const SO_LINGER: u32 = 13;
-pub const SO_BSDCOMPAT: u32 = 14;
-pub const SO_REUSEPORT: u32 = 15;
-pub const SO_PASSCRED: u32 = 16;
-pub const SO_PEERCRED: u32 = 17;
-pub const SO_RCVLOWAT: u32 = 18;
-pub const SO_SNDLOWAT: u32 = 19;
-pub const SO_RCVTIMEO: u32 = 20;
-pub const SO_SNDTIMEO: u32 = 21;
-pub const SO_SECURITY_AUTHENTICATION: u32 = 22;
-pub const SO_SECURITY_ENCRYPTION_TRANSPORT: u32 = 23;
-pub const SO_SECURITY_ENCRYPTION_NETWORK: u32 = 24;
-pub const SO_BINDTODEVICE: u32 = 25;
-pub const SO_ATTACH_FILTER: u32 = 26;
-pub const SO_DETACH_FILTER: u32 = 27;
-pub const SO_GET_FILTER: u32 = 26;
-pub const SO_PEERNAME: u32 = 28;
-pub const SO_TIMESTAMP: u32 = 29;
-pub const SCM_TIMESTAMP: u32 = 29;
-pub const SO_ACCEPTCONN: u32 = 30;
-pub const SO_PEERSEC: u32 = 31;
-pub const SO_PASSSEC: u32 = 34;
-pub const SO_TIMESTAMPNS: u32 = 35;
-pub const SCM_TIMESTAMPNS: u32 = 35;
-pub const SO_MARK: u32 = 36;
-pub const SO_TIMESTAMPING: u32 = 37;
-pub const SCM_TIMESTAMPING: u32 = 37;
-pub const SO_PROTOCOL: u32 = 38;
-pub const SO_DOMAIN: u32 = 39;
-pub const SO_RXQ_OVFL: u32 = 40;
-pub const SO_WIFI_STATUS: u32 = 41;
-pub const SCM_WIFI_STATUS: u32 = 41;
-pub const SO_PEEK_OFF: u32 = 42;
-pub const SO_NOFCS: u32 = 43;
-pub const SO_LOCK_FILTER: u32 = 44;
-pub const SO_SELECT_ERR_QUEUE: u32 = 45;
-pub const SO_BUSY_POLL: u32 = 46;
-pub const SO_MAX_PACING_RATE: u32 = 47;
-pub const SO_BPF_EXTENSIONS: u32 = 48;
-pub const SO_INCOMING_CPU: u32 = 49;
-pub const SO_ATTACH_BPF: u32 = 50;
-pub const SO_DETACH_BPF: u32 = 27;
-pub const SO_ATTACH_REUSEPORT_CBPF: u32 = 51;
-pub const SO_ATTACH_REUSEPORT_EBPF: u32 = 52;
-pub const SO_CNX_ADVICE: u32 = 53;
-pub const SCM_TIMESTAMPING_OPT_STATS: u32 = 54;
-pub const SO_MEMINFO: u32 = 55;
-pub const SO_INCOMING_NAPI_ID: u32 = 56;
-pub const SO_COOKIE: u32 = 57;
-pub const SCM_TIMESTAMPING_PKTINFO: u32 = 58;
-pub const SO_PEERGROUPS: u32 = 59;
-pub const SO_ZEROCOPY: u32 = 60;
-pub const __osockaddr_defined: u32 = 1;
-pub const IFNAMSIZ: u32 = 16;
-pub const IFALIASZ: u32 = 256;
-pub const GENERIC_HDLC_VERSION: u32 = 4;
-pub const CLOCK_DEFAULT: u32 = 0;
-pub const CLOCK_EXT: u32 = 1;
-pub const CLOCK_INT: u32 = 2;
-pub const CLOCK_TXINT: u32 = 3;
-pub const CLOCK_TXFROMRX: u32 = 4;
-pub const ENCODING_DEFAULT: u32 = 0;
-pub const ENCODING_NRZ: u32 = 1;
-pub const ENCODING_NRZI: u32 = 2;
-pub const ENCODING_FM_MARK: u32 = 3;
-pub const ENCODING_FM_SPACE: u32 = 4;
-pub const ENCODING_MANCHESTER: u32 = 5;
-pub const PARITY_DEFAULT: u32 = 0;
-pub const PARITY_NONE: u32 = 1;
-pub const PARITY_CRC16_PR0: u32 = 2;
-pub const PARITY_CRC16_PR1: u32 = 3;
-pub const PARITY_CRC16_PR0_CCITT: u32 = 4;
-pub const PARITY_CRC16_PR1_CCITT: u32 = 5;
-pub const PARITY_CRC32_PR0_CCITT: u32 = 6;
-pub const PARITY_CRC32_PR1_CCITT: u32 = 7;
-pub const LMI_DEFAULT: u32 = 0;
-pub const LMI_NONE: u32 = 1;
-pub const LMI_ANSI: u32 = 2;
-pub const LMI_CCITT: u32 = 3;
-pub const LMI_CISCO: u32 = 4;
-pub const IF_GET_IFACE: u32 = 1;
-pub const IF_GET_PROTO: u32 = 2;
-pub const IF_IFACE_V35: u32 = 4096;
-pub const IF_IFACE_V24: u32 = 4097;
-pub const IF_IFACE_X21: u32 = 4098;
-pub const IF_IFACE_T1: u32 = 4099;
-pub const IF_IFACE_E1: u32 = 4100;
-pub const IF_IFACE_SYNC_SERIAL: u32 = 4101;
-pub const IF_IFACE_X21D: u32 = 4102;
-pub const IF_PROTO_HDLC: u32 = 8192;
-pub const IF_PROTO_PPP: u32 = 8193;
-pub const IF_PROTO_CISCO: u32 = 8194;
-pub const IF_PROTO_FR: u32 = 8195;
-pub const IF_PROTO_FR_ADD_PVC: u32 = 8196;
-pub const IF_PROTO_FR_DEL_PVC: u32 = 8197;
-pub const IF_PROTO_X25: u32 = 8198;
-pub const IF_PROTO_HDLC_ETH: u32 = 8199;
-pub const IF_PROTO_FR_ADD_ETH_PVC: u32 = 8200;
-pub const IF_PROTO_FR_DEL_ETH_PVC: u32 = 8201;
-pub const IF_PROTO_FR_PVC: u32 = 8202;
-pub const IF_PROTO_FR_ETH_PVC: u32 = 8203;
-pub const IF_PROTO_RAW: u32 = 8204;
-pub const IFHWADDRLEN: u32 = 6;
-pub type __s8 = ::std::os::raw::c_schar;
-pub type __u8 = ::std::os::raw::c_uchar;
-pub type __s16 = ::std::os::raw::c_short;
-pub type __u16 = ::std::os::raw::c_ushort;
-pub type __s32 = ::std::os::raw::c_int;
-pub type __u32 = ::std::os::raw::c_uint;
-pub type __s64 = ::std::os::raw::c_longlong;
-pub type __u64 = ::std::os::raw::c_ulonglong;
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct __kernel_fd_set {
- pub fds_bits: [::std::os::raw::c_ulong; 16usize],
-}
-pub type __kernel_sighandler_t =
- ::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
-pub type __kernel_key_t = ::std::os::raw::c_int;
-pub type __kernel_mqd_t = ::std::os::raw::c_int;
-pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
-pub type __kernel_long_t = ::std::os::raw::c_long;
-pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
-pub type __kernel_ino_t = __kernel_ulong_t;
-pub type __kernel_mode_t = ::std::os::raw::c_uint;
-pub type __kernel_pid_t = ::std::os::raw::c_int;
-pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
-pub type __kernel_uid_t = ::std::os::raw::c_uint;
-pub type __kernel_gid_t = ::std::os::raw::c_uint;
-pub type __kernel_suseconds_t = __kernel_long_t;
-pub type __kernel_daddr_t = ::std::os::raw::c_int;
-pub type __kernel_uid32_t = ::std::os::raw::c_uint;
-pub type __kernel_gid32_t = ::std::os::raw::c_uint;
-pub type __kernel_size_t = __kernel_ulong_t;
-pub type __kernel_ssize_t = __kernel_long_t;
-pub type __kernel_ptrdiff_t = __kernel_long_t;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
-pub struct __kernel_fsid_t {
- pub val: [::std::os::raw::c_int; 2usize],
-}
-pub type __kernel_off_t = __kernel_long_t;
-pub type __kernel_loff_t = ::std::os::raw::c_longlong;
-pub type __kernel_time_t = __kernel_long_t;
-pub type __kernel_clock_t = __kernel_long_t;
-pub type __kernel_timer_t = ::std::os::raw::c_int;
-pub type __kernel_clockid_t = ::std::os::raw::c_int;
-pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
-pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
-pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
-pub type __le16 = __u16;
-pub type __be16 = __u16;
-pub type __le32 = __u32;
-pub type __be32 = __u32;
-pub type __le64 = __u64;
-pub type __be64 = __u64;
-pub type __sum16 = __u16;
-pub type __wsum = __u32;
-pub type __kernel_sa_family_t = ::std::os::raw::c_ushort;
-#[repr(C)]
-#[repr(align(8))]
-#[derive(Copy, Clone)]
-pub struct __kernel_sockaddr_storage {
- pub ss_family: __kernel_sa_family_t,
- pub __data: [::std::os::raw::c_char; 126usize],
-}
-impl Default for __kernel_sockaddr_storage {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-#[repr(C)]
-#[derive(Debug, Copy, Clone)]
-pub struct iovec {
- pub iov_base: *mut ::std::os::raw::c_void,
- pub iov_len: usize,
-}
-impl Default for iovec {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
+pub struct sync_serial_settings {
+ pub clock_rate: ::std::os::raw::c_uint,
+ pub clock_type: ::std::os::raw::c_uint,
+ pub loopback: ::std::os::raw::c_ushort,
}
-pub type __u_char = ::std::os::raw::c_uchar;
-pub type __u_short = ::std::os::raw::c_ushort;
-pub type __u_int = ::std::os::raw::c_uint;
-pub type __u_long = ::std::os::raw::c_ulong;
-pub type __int8_t = ::std::os::raw::c_schar;
-pub type __uint8_t = ::std::os::raw::c_uchar;
-pub type __int16_t = ::std::os::raw::c_short;
-pub type __uint16_t = ::std::os::raw::c_ushort;
-pub type __int32_t = ::std::os::raw::c_int;
-pub type __uint32_t = ::std::os::raw::c_uint;
-pub type __int64_t = ::std::os::raw::c_long;
-pub type __uint64_t = ::std::os::raw::c_ulong;
-pub type __quad_t = ::std::os::raw::c_long;
-pub type __u_quad_t = ::std::os::raw::c_ulong;
-pub type __intmax_t = ::std::os::raw::c_long;
-pub type __uintmax_t = ::std::os::raw::c_ulong;
-pub type __dev_t = ::std::os::raw::c_ulong;
-pub type __uid_t = ::std::os::raw::c_uint;
-pub type __gid_t = ::std::os::raw::c_uint;
-pub type __ino_t = ::std::os::raw::c_ulong;
-pub type __ino64_t = ::std::os::raw::c_ulong;
-pub type __mode_t = ::std::os::raw::c_uint;
-pub type __nlink_t = ::std::os::raw::c_ulong;
-pub type __off_t = ::std::os::raw::c_long;
-pub type __off64_t = ::std::os::raw::c_long;
-pub type __pid_t = ::std::os::raw::c_int;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
-pub struct __fsid_t {
- pub __val: [::std::os::raw::c_int; 2usize],
+pub struct te1_settings {
+ pub clock_rate: ::std::os::raw::c_uint,
+ pub clock_type: ::std::os::raw::c_uint,
+ pub loopback: ::std::os::raw::c_ushort,
+ pub slot_map: ::std::os::raw::c_uint,
}
-pub type __clock_t = ::std::os::raw::c_long;
-pub type __rlim_t = ::std::os::raw::c_ulong;
-pub type __rlim64_t = ::std::os::raw::c_ulong;
-pub type __id_t = ::std::os::raw::c_uint;
-pub type __time_t = ::std::os::raw::c_long;
-pub type __useconds_t = ::std::os::raw::c_uint;
-pub type __suseconds_t = ::std::os::raw::c_long;
-pub type __daddr_t = ::std::os::raw::c_int;
-pub type __key_t = ::std::os::raw::c_int;
-pub type __clockid_t = ::std::os::raw::c_int;
-pub type __timer_t = *mut ::std::os::raw::c_void;
-pub type __blksize_t = ::std::os::raw::c_long;
-pub type __blkcnt_t = ::std::os::raw::c_long;
-pub type __blkcnt64_t = ::std::os::raw::c_long;
-pub type __fsblkcnt_t = ::std::os::raw::c_ulong;
-pub type __fsblkcnt64_t = ::std::os::raw::c_ulong;
-pub type __fsfilcnt_t = ::std::os::raw::c_ulong;
-pub type __fsfilcnt64_t = ::std::os::raw::c_ulong;
-pub type __fsword_t = ::std::os::raw::c_long;
-pub type __ssize_t = ::std::os::raw::c_long;
-pub type __syscall_slong_t = ::std::os::raw::c_long;
-pub type __syscall_ulong_t = ::std::os::raw::c_ulong;
-pub type __loff_t = __off64_t;
-pub type __caddr_t = *mut ::std::os::raw::c_char;
-pub type __intptr_t = ::std::os::raw::c_long;
-pub type __socklen_t = ::std::os::raw::c_uint;
-pub type __sig_atomic_t = ::std::os::raw::c_int;
-pub type u_char = __u_char;
-pub type u_short = __u_short;
-pub type u_int = __u_int;
-pub type u_long = __u_long;
-pub type quad_t = __quad_t;
-pub type u_quad_t = __u_quad_t;
-pub type fsid_t = __fsid_t;
-pub type loff_t = __loff_t;
-pub type ino_t = __ino_t;
-pub type dev_t = __dev_t;
-pub type gid_t = __gid_t;
-pub type mode_t = __mode_t;
-pub type nlink_t = __nlink_t;
-pub type uid_t = __uid_t;
-pub type off_t = __off_t;
-pub type pid_t = __pid_t;
-pub type id_t = __id_t;
-pub type daddr_t = __daddr_t;
-pub type caddr_t = __caddr_t;
-pub type key_t = __key_t;
-pub type clock_t = __clock_t;
-pub type clockid_t = __clockid_t;
-pub type time_t = __time_t;
-pub type timer_t = __timer_t;
-pub type ulong = ::std::os::raw::c_ulong;
-pub type ushort = ::std::os::raw::c_ushort;
-pub type uint = ::std::os::raw::c_uint;
-pub type u_int8_t = ::std::os::raw::c_uchar;
-pub type u_int16_t = ::std::os::raw::c_ushort;
-pub type u_int32_t = ::std::os::raw::c_uint;
-pub type u_int64_t = ::std::os::raw::c_ulong;
-pub type register_t = ::std::os::raw::c_long;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
-pub struct __sigset_t {
- pub __val: [::std::os::raw::c_ulong; 16usize],
+pub struct raw_hdlc_proto {
+ pub encoding: ::std::os::raw::c_ushort,
+ pub parity: ::std::os::raw::c_ushort,
}
-pub type sigset_t = __sigset_t;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
-pub struct timeval {
- pub tv_sec: __time_t,
- pub tv_usec: __suseconds_t,
+pub struct fr_proto {
+ pub t391: ::std::os::raw::c_uint,
+ pub t392: ::std::os::raw::c_uint,
+ pub n391: ::std::os::raw::c_uint,
+ pub n392: ::std::os::raw::c_uint,
+ pub n393: ::std::os::raw::c_uint,
+ pub lmi: ::std::os::raw::c_ushort,
+ pub dce: ::std::os::raw::c_ushort,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
-pub struct timespec {
- pub tv_sec: __time_t,
- pub tv_nsec: __syscall_slong_t,
+pub struct fr_proto_pvc {
+ pub dlci: ::std::os::raw::c_uint,
}
-pub type suseconds_t = __suseconds_t;
-pub type __fd_mask = ::std::os::raw::c_long;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
-pub struct fd_set {
- pub __fds_bits: [__fd_mask; 16usize],
-}
-pub type fd_mask = __fd_mask;
-extern "C" {
- pub fn select(
- __nfds: ::std::os::raw::c_int,
- __readfds: *mut fd_set,
- __writefds: *mut fd_set,
- __exceptfds: *mut fd_set,
- __timeout: *mut timeval,
- ) -> ::std::os::raw::c_int;
-}
-extern "C" {
- pub fn pselect(
- __nfds: ::std::os::raw::c_int,
- __readfds: *mut fd_set,
- __writefds: *mut fd_set,
- __exceptfds: *mut fd_set,
- __timeout: *const timespec,
- __sigmask: *const __sigset_t,
- ) -> ::std::os::raw::c_int;
+pub struct fr_proto_pvc_info {
+ pub dlci: ::std::os::raw::c_uint,
+ pub master: [::std::os::raw::c_char; 16usize],
}
-pub type blksize_t = __blksize_t;
-pub type blkcnt_t = __blkcnt_t;
-pub type fsblkcnt_t = __fsblkcnt_t;
-pub type fsfilcnt_t = __fsfilcnt_t;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
-pub struct __pthread_rwlock_arch_t {
- pub __readers: ::std::os::raw::c_uint,
- pub __writers: ::std::os::raw::c_uint,
- pub __wrphase_futex: ::std::os::raw::c_uint,
- pub __writers_futex: ::std::os::raw::c_uint,
- pub __pad3: ::std::os::raw::c_uint,
- pub __pad4: ::std::os::raw::c_uint,
- pub __cur_writer: ::std::os::raw::c_int,
- pub __shared: ::std::os::raw::c_int,
- pub __rwelision: ::std::os::raw::c_schar,
- pub __pad1: [::std::os::raw::c_uchar; 7usize],
- pub __pad2: ::std::os::raw::c_ulong,
- pub __flags: ::std::os::raw::c_uint,
-}
-#[repr(C)]
-#[derive(Debug, Copy, Clone)]
-pub struct __pthread_internal_list {
- pub __prev: *mut __pthread_internal_list,
- pub __next: *mut __pthread_internal_list,
-}
-impl Default for __pthread_internal_list {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-pub type __pthread_list_t = __pthread_internal_list;
-#[repr(C)]
-#[derive(Debug, Copy, Clone)]
-pub struct __pthread_mutex_s {
- pub __lock: ::std::os::raw::c_int,
- pub __count: ::std::os::raw::c_uint,
- pub __owner: ::std::os::raw::c_int,
- pub __nusers: ::std::os::raw::c_uint,
- pub __kind: ::std::os::raw::c_int,
- pub __spins: ::std::os::raw::c_short,
- pub __elision: ::std::os::raw::c_short,
- pub __list: __pthread_list_t,
-}
-impl Default for __pthread_mutex_s {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub struct __pthread_cond_s {
- pub __bindgen_anon_1: __pthread_cond_s__bindgen_ty_1,
- pub __bindgen_anon_2: __pthread_cond_s__bindgen_ty_2,
- pub __g_refs: [::std::os::raw::c_uint; 2usize],
- pub __g_size: [::std::os::raw::c_uint; 2usize],
- pub __g1_orig_size: ::std::os::raw::c_uint,
- pub __wrefs: ::std::os::raw::c_uint,
- pub __g_signals: [::std::os::raw::c_uint; 2usize],
-}
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub union __pthread_cond_s__bindgen_ty_1 {
- pub __wseq: ::std::os::raw::c_ulonglong,
- pub __wseq32: __pthread_cond_s__bindgen_ty_1__bindgen_ty_1,
- _bindgen_union_align: u64,
+pub struct cisco_proto {
+ pub interval: ::std::os::raw::c_uint,
+ pub timeout: ::std::os::raw::c_uint,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
-pub struct __pthread_cond_s__bindgen_ty_1__bindgen_ty_1 {
- pub __low: ::std::os::raw::c_uint,
- pub __high: ::std::os::raw::c_uint,
+pub struct x25_hdlc_proto {
+ pub dce: ::std::os::raw::c_ushort,
+ pub modulo: ::std::os::raw::c_uint,
+ pub window: ::std::os::raw::c_uint,
+ pub t1: ::std::os::raw::c_uint,
+ pub t2: ::std::os::raw::c_uint,
+ pub n2: ::std::os::raw::c_uint,
}
-impl Default for __pthread_cond_s__bindgen_ty_1 {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
+impl net_device_flags {
+ pub const IFF_UP: net_device_flags = net_device_flags(1);
}
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub union __pthread_cond_s__bindgen_ty_2 {
- pub __g1_start: ::std::os::raw::c_ulonglong,
- pub __g1_start32: __pthread_cond_s__bindgen_ty_2__bindgen_ty_1,
- _bindgen_union_align: u64,
+impl net_device_flags {
+ pub const IFF_BROADCAST: net_device_flags = net_device_flags(2);
}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct __pthread_cond_s__bindgen_ty_2__bindgen_ty_1 {
- pub __low: ::std::os::raw::c_uint,
- pub __high: ::std::os::raw::c_uint,
+impl net_device_flags {
+ pub const IFF_DEBUG: net_device_flags = net_device_flags(4);
}
-impl Default for __pthread_cond_s__bindgen_ty_2 {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
+impl net_device_flags {
+ pub const IFF_LOOPBACK: net_device_flags = net_device_flags(8);
}
-impl Default for __pthread_cond_s {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
+impl net_device_flags {
+ pub const IFF_POINTOPOINT: net_device_flags = net_device_flags(16);
}
-pub type pthread_t = ::std::os::raw::c_ulong;
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub union pthread_mutexattr_t {
- pub __size: [::std::os::raw::c_char; 4usize],
- pub __align: ::std::os::raw::c_int,
- _bindgen_union_align: u32,
-}
-impl Default for pthread_mutexattr_t {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
+impl net_device_flags {
+ pub const IFF_NOTRAILERS: net_device_flags = net_device_flags(32);
}
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub union pthread_condattr_t {
- pub __size: [::std::os::raw::c_char; 4usize],
- pub __align: ::std::os::raw::c_int,
- _bindgen_union_align: u32,
+impl net_device_flags {
+ pub const IFF_RUNNING: net_device_flags = net_device_flags(64);
}
-impl Default for pthread_condattr_t {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
+impl net_device_flags {
+ pub const IFF_NOARP: net_device_flags = net_device_flags(128);
}
-pub type pthread_key_t = ::std::os::raw::c_uint;
-pub type pthread_once_t = ::std::os::raw::c_int;
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub union pthread_attr_t {
- pub __size: [::std::os::raw::c_char; 56usize],
- pub __align: ::std::os::raw::c_long,
- _bindgen_union_align: [u64; 7usize],
+impl net_device_flags {
+ pub const IFF_PROMISC: net_device_flags = net_device_flags(256);
}
-impl Default for pthread_attr_t {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
+impl net_device_flags {
+ pub const IFF_ALLMULTI: net_device_flags = net_device_flags(512);
}
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub union pthread_mutex_t {
- pub __data: __pthread_mutex_s,
- pub __size: [::std::os::raw::c_char; 40usize],
- pub __align: ::std::os::raw::c_long,
- _bindgen_union_align: [u64; 5usize],
+impl net_device_flags {
+ pub const IFF_MASTER: net_device_flags = net_device_flags(1024);
}
-impl Default for pthread_mutex_t {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
+impl net_device_flags {
+ pub const IFF_SLAVE: net_device_flags = net_device_flags(2048);
}
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub union pthread_cond_t {
- pub __data: __pthread_cond_s,
- pub __size: [::std::os::raw::c_char; 48usize],
- pub __align: ::std::os::raw::c_longlong,
- _bindgen_union_align: [u64; 6usize],
+impl net_device_flags {
+ pub const IFF_MULTICAST: net_device_flags = net_device_flags(4096);
}
-impl Default for pthread_cond_t {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
+impl net_device_flags {
+ pub const IFF_PORTSEL: net_device_flags = net_device_flags(8192);
}
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub union pthread_rwlock_t {
- pub __data: __pthread_rwlock_arch_t,
- pub __size: [::std::os::raw::c_char; 56usize],
- pub __align: ::std::os::raw::c_long,
- _bindgen_union_align: [u64; 7usize],
+impl net_device_flags {
+ pub const IFF_AUTOMEDIA: net_device_flags = net_device_flags(16384);
}
-impl Default for pthread_rwlock_t {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
+impl net_device_flags {
+ pub const IFF_DYNAMIC: net_device_flags = net_device_flags(32768);
}
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub union pthread_rwlockattr_t {
- pub __size: [::std::os::raw::c_char; 8usize],
- pub __align: ::std::os::raw::c_long,
- _bindgen_union_align: u64,
+impl net_device_flags {
+ pub const IFF_LOWER_UP: net_device_flags = net_device_flags(65536);
}
-impl Default for pthread_rwlockattr_t {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
+impl net_device_flags {
+ pub const IFF_DORMANT: net_device_flags = net_device_flags(131072);
}
-pub type pthread_spinlock_t = ::std::os::raw::c_int;
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub union pthread_barrier_t {
- pub __size: [::std::os::raw::c_char; 32usize],
- pub __align: ::std::os::raw::c_long,
- _bindgen_union_align: [u64; 4usize],
+impl net_device_flags {
+ pub const IFF_ECHO: net_device_flags = net_device_flags(262144);
}
-impl Default for pthread_barrier_t {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+impl ::std::ops::BitOr<net_device_flags> for net_device_flags {
+ type Output = Self;
+ #[inline]
+ fn bitor(self, other: Self) -> Self {
+ net_device_flags(self.0 | other.0)
}
}
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub union pthread_barrierattr_t {
- pub __size: [::std::os::raw::c_char; 4usize],
- pub __align: ::std::os::raw::c_int,
- _bindgen_union_align: u32,
-}
-impl Default for pthread_barrierattr_t {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+impl ::std::ops::BitOrAssign for net_device_flags {
+ #[inline]
+ fn bitor_assign(&mut self, rhs: net_device_flags) {
+ self.0 |= rhs.0;
}
}
-pub type socklen_t = __socklen_t;
-pub const __socket_type_SOCK_STREAM: __socket_type = 1;
-pub const __socket_type_SOCK_DGRAM: __socket_type = 2;
-pub const __socket_type_SOCK_RAW: __socket_type = 3;
-pub const __socket_type_SOCK_RDM: __socket_type = 4;
-pub const __socket_type_SOCK_SEQPACKET: __socket_type = 5;
-pub const __socket_type_SOCK_DCCP: __socket_type = 6;
-pub const __socket_type_SOCK_PACKET: __socket_type = 10;
-pub const __socket_type_SOCK_CLOEXEC: __socket_type = 524288;
-pub const __socket_type_SOCK_NONBLOCK: __socket_type = 2048;
-pub type __socket_type = u32;
-pub type sa_family_t = ::std::os::raw::c_ushort;
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct sockaddr {
- pub sa_family: sa_family_t,
- pub sa_data: [::std::os::raw::c_char; 14usize],
-}
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub struct sockaddr_storage {
- pub ss_family: sa_family_t,
- pub __ss_padding: [::std::os::raw::c_char; 118usize],
- pub __ss_align: ::std::os::raw::c_ulong,
-}
-impl Default for sockaddr_storage {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+impl ::std::ops::BitAnd<net_device_flags> for net_device_flags {
+ type Output = Self;
+ #[inline]
+ fn bitand(self, other: Self) -> Self {
+ net_device_flags(self.0 & other.0)
}
}
-pub const MSG_OOB: _bindgen_ty_1 = 1;
-pub const MSG_PEEK: _bindgen_ty_1 = 2;
-pub const MSG_DONTROUTE: _bindgen_ty_1 = 4;
-pub const MSG_CTRUNC: _bindgen_ty_1 = 8;
-pub const MSG_PROXY: _bindgen_ty_1 = 16;
-pub const MSG_TRUNC: _bindgen_ty_1 = 32;
-pub const MSG_DONTWAIT: _bindgen_ty_1 = 64;
-pub const MSG_EOR: _bindgen_ty_1 = 128;
-pub const MSG_WAITALL: _bindgen_ty_1 = 256;
-pub const MSG_FIN: _bindgen_ty_1 = 512;
-pub const MSG_SYN: _bindgen_ty_1 = 1024;
-pub const MSG_CONFIRM: _bindgen_ty_1 = 2048;
-pub const MSG_RST: _bindgen_ty_1 = 4096;
-pub const MSG_ERRQUEUE: _bindgen_ty_1 = 8192;
-pub const MSG_NOSIGNAL: _bindgen_ty_1 = 16384;
-pub const MSG_MORE: _bindgen_ty_1 = 32768;
-pub const MSG_WAITFORONE: _bindgen_ty_1 = 65536;
-pub const MSG_BATCH: _bindgen_ty_1 = 262144;
-pub const MSG_ZEROCOPY: _bindgen_ty_1 = 67108864;
-pub const MSG_FASTOPEN: _bindgen_ty_1 = 536870912;
-pub const MSG_CMSG_CLOEXEC: _bindgen_ty_1 = 1073741824;
-pub type _bindgen_ty_1 = u32;
-#[repr(C)]
-#[derive(Debug, Copy, Clone)]
-pub struct msghdr {
- pub msg_name: *mut ::std::os::raw::c_void,
- pub msg_namelen: socklen_t,
- pub msg_iov: *mut iovec,
- pub msg_iovlen: usize,
- pub msg_control: *mut ::std::os::raw::c_void,
- pub msg_controllen: usize,
- pub msg_flags: ::std::os::raw::c_int,
-}
-impl Default for msghdr {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+impl ::std::ops::BitAndAssign for net_device_flags {
+ #[inline]
+ fn bitand_assign(&mut self, rhs: net_device_flags) {
+ self.0 &= rhs.0;
}
}
-#[repr(C)]
-#[derive(Debug, Default)]
-pub struct cmsghdr {
- pub cmsg_len: usize,
- pub cmsg_level: ::std::os::raw::c_int,
- pub cmsg_type: ::std::os::raw::c_int,
- pub __cmsg_data: __IncompleteArrayField<::std::os::raw::c_uchar>,
-}
-extern "C" {
- pub fn __cmsg_nxthdr(__mhdr: *mut msghdr, __cmsg: *mut cmsghdr) -> *mut cmsghdr;
-}
-pub const SCM_RIGHTS: _bindgen_ty_2 = 1;
-pub type _bindgen_ty_2 = u32;
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct linger {
- pub l_onoff: ::std::os::raw::c_int,
- pub l_linger: ::std::os::raw::c_int,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct osockaddr {
- pub sa_family: ::std::os::raw::c_ushort,
- pub sa_data: [::std::os::raw::c_uchar; 14usize],
-}
-pub const SHUT_RD: _bindgen_ty_3 = 0;
-pub const SHUT_WR: _bindgen_ty_3 = 1;
-pub const SHUT_RDWR: _bindgen_ty_3 = 2;
-pub type _bindgen_ty_3 = u32;
-extern "C" {
- pub fn socket(
- __domain: ::std::os::raw::c_int,
- __type: ::std::os::raw::c_int,
- __protocol: ::std::os::raw::c_int,
- ) -> ::std::os::raw::c_int;
-}
-extern "C" {
- pub fn socketpair(
- __domain: ::std::os::raw::c_int,
- __type: ::std::os::raw::c_int,
- __protocol: ::std::os::raw::c_int,
- __fds: *mut ::std::os::raw::c_int,
- ) -> ::std::os::raw::c_int;
-}
-extern "C" {
- pub fn bind(
- __fd: ::std::os::raw::c_int,
- __addr: *const sockaddr,
- __len: socklen_t,
- ) -> ::std::os::raw::c_int;
-}
-extern "C" {
- pub fn getsockname(
- __fd: ::std::os::raw::c_int,
- __addr: *mut sockaddr,
- __len: *mut socklen_t,
- ) -> ::std::os::raw::c_int;
-}
-extern "C" {
- pub fn connect(
- __fd: ::std::os::raw::c_int,
- __addr: *const sockaddr,
- __len: socklen_t,
- ) -> ::std::os::raw::c_int;
-}
-extern "C" {
- pub fn getpeername(
- __fd: ::std::os::raw::c_int,
- __addr: *mut sockaddr,
- __len: *mut socklen_t,
- ) -> ::std::os::raw::c_int;
-}
-extern "C" {
- pub fn send(
- __fd: ::std::os::raw::c_int,
- __buf: *const ::std::os::raw::c_void,
- __n: usize,
- __flags: ::std::os::raw::c_int,
- ) -> isize;
-}
-extern "C" {
- pub fn recv(
- __fd: ::std::os::raw::c_int,
- __buf: *mut ::std::os::raw::c_void,
- __n: usize,
- __flags: ::std::os::raw::c_int,
- ) -> isize;
-}
-extern "C" {
- pub fn sendto(
- __fd: ::std::os::raw::c_int,
- __buf: *const ::std::os::raw::c_void,
- __n: usize,
- __flags: ::std::os::raw::c_int,
- __addr: *const sockaddr,
- __addr_len: socklen_t,
- ) -> isize;
-}
-extern "C" {
- pub fn recvfrom(
- __fd: ::std::os::raw::c_int,
- __buf: *mut ::std::os::raw::c_void,
- __n: usize,
- __flags: ::std::os::raw::c_int,
- __addr: *mut sockaddr,
- __addr_len: *mut socklen_t,
- ) -> isize;
-}
-extern "C" {
- pub fn sendmsg(
- __fd: ::std::os::raw::c_int,
- __message: *const msghdr,
- __flags: ::std::os::raw::c_int,
- ) -> isize;
-}
-extern "C" {
- pub fn recvmsg(
- __fd: ::std::os::raw::c_int,
- __message: *mut msghdr,
- __flags: ::std::os::raw::c_int,
- ) -> isize;
-}
-extern "C" {
- pub fn getsockopt(
- __fd: ::std::os::raw::c_int,
- __level: ::std::os::raw::c_int,
- __optname: ::std::os::raw::c_int,
- __optval: *mut ::std::os::raw::c_void,
- __optlen: *mut socklen_t,
- ) -> ::std::os::raw::c_int;
-}
-extern "C" {
- pub fn setsockopt(
- __fd: ::std::os::raw::c_int,
- __level: ::std::os::raw::c_int,
- __optname: ::std::os::raw::c_int,
- __optval: *const ::std::os::raw::c_void,
- __optlen: socklen_t,
- ) -> ::std::os::raw::c_int;
-}
-extern "C" {
- pub fn listen(__fd: ::std::os::raw::c_int, __n: ::std::os::raw::c_int)
- -> ::std::os::raw::c_int;
-}
-extern "C" {
- pub fn accept(
- __fd: ::std::os::raw::c_int,
- __addr: *mut sockaddr,
- __addr_len: *mut socklen_t,
- ) -> ::std::os::raw::c_int;
-}
-extern "C" {
- pub fn shutdown(
- __fd: ::std::os::raw::c_int,
- __how: ::std::os::raw::c_int,
- ) -> ::std::os::raw::c_int;
-}
-extern "C" {
- pub fn sockatmark(__fd: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
-}
-extern "C" {
- pub fn isfdtype(
- __fd: ::std::os::raw::c_int,
- __fdtype: ::std::os::raw::c_int,
- ) -> ::std::os::raw::c_int;
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct sync_serial_settings {
- pub clock_rate: ::std::os::raw::c_uint,
- pub clock_type: ::std::os::raw::c_uint,
- pub loopback: ::std::os::raw::c_ushort,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct te1_settings {
- pub clock_rate: ::std::os::raw::c_uint,
- pub clock_type: ::std::os::raw::c_uint,
- pub loopback: ::std::os::raw::c_ushort,
- pub slot_map: ::std::os::raw::c_uint,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct raw_hdlc_proto {
- pub encoding: ::std::os::raw::c_ushort,
- pub parity: ::std::os::raw::c_ushort,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct fr_proto {
- pub t391: ::std::os::raw::c_uint,
- pub t392: ::std::os::raw::c_uint,
- pub n391: ::std::os::raw::c_uint,
- pub n392: ::std::os::raw::c_uint,
- pub n393: ::std::os::raw::c_uint,
- pub lmi: ::std::os::raw::c_ushort,
- pub dce: ::std::os::raw::c_ushort,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct fr_proto_pvc {
- pub dlci: ::std::os::raw::c_uint,
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct fr_proto_pvc_info {
- pub dlci: ::std::os::raw::c_uint,
- pub master: [::std::os::raw::c_char; 16usize],
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct cisco_proto {
- pub interval: ::std::os::raw::c_uint,
- pub timeout: ::std::os::raw::c_uint,
-}
-pub const net_device_flags_IFF_UP: net_device_flags = 1;
-pub const net_device_flags_IFF_BROADCAST: net_device_flags = 2;
-pub const net_device_flags_IFF_DEBUG: net_device_flags = 4;
-pub const net_device_flags_IFF_LOOPBACK: net_device_flags = 8;
-pub const net_device_flags_IFF_POINTOPOINT: net_device_flags = 16;
-pub const net_device_flags_IFF_NOTRAILERS: net_device_flags = 32;
-pub const net_device_flags_IFF_RUNNING: net_device_flags = 64;
-pub const net_device_flags_IFF_NOARP: net_device_flags = 128;
-pub const net_device_flags_IFF_PROMISC: net_device_flags = 256;
-pub const net_device_flags_IFF_ALLMULTI: net_device_flags = 512;
-pub const net_device_flags_IFF_MASTER: net_device_flags = 1024;
-pub const net_device_flags_IFF_SLAVE: net_device_flags = 2048;
-pub const net_device_flags_IFF_MULTICAST: net_device_flags = 4096;
-pub const net_device_flags_IFF_PORTSEL: net_device_flags = 8192;
-pub const net_device_flags_IFF_AUTOMEDIA: net_device_flags = 16384;
-pub const net_device_flags_IFF_DYNAMIC: net_device_flags = 32768;
-pub const net_device_flags_IFF_LOWER_UP: net_device_flags = 65536;
-pub const net_device_flags_IFF_DORMANT: net_device_flags = 131072;
-pub const net_device_flags_IFF_ECHO: net_device_flags = 262144;
-pub type net_device_flags = u32;
-pub const IF_OPER_UNKNOWN: _bindgen_ty_4 = 0;
-pub const IF_OPER_NOTPRESENT: _bindgen_ty_4 = 1;
-pub const IF_OPER_DOWN: _bindgen_ty_4 = 2;
-pub const IF_OPER_LOWERLAYERDOWN: _bindgen_ty_4 = 3;
-pub const IF_OPER_TESTING: _bindgen_ty_4 = 4;
-pub const IF_OPER_DORMANT: _bindgen_ty_4 = 5;
-pub const IF_OPER_UP: _bindgen_ty_4 = 6;
-pub type _bindgen_ty_4 = u32;
-pub const IF_LINK_MODE_DEFAULT: _bindgen_ty_5 = 0;
-pub const IF_LINK_MODE_DORMANT: _bindgen_ty_5 = 1;
-pub type _bindgen_ty_5 = u32;
+#[repr(transparent)]
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct net_device_flags(pub ::std::os::raw::c_uint);
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct ifmap {
@@ -1170,18 +177,26 @@ pub union if_settings__bindgen_ty_1 {
pub fr: *mut fr_proto,
pub fr_pvc: *mut fr_proto_pvc,
pub fr_pvc_info: *mut fr_proto_pvc_info,
+ pub x25: *mut x25_hdlc_proto,
pub sync: *mut sync_serial_settings,
pub te1: *mut te1_settings,
- _bindgen_union_align: u64,
}
impl Default for if_settings__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
impl Default for if_settings {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
@@ -1194,11 +209,14 @@ pub struct ifreq {
#[derive(Copy, Clone)]
pub union ifreq__bindgen_ty_1 {
pub ifrn_name: [::std::os::raw::c_char; 16usize],
- _bindgen_union_align: [u8; 16usize],
}
impl Default for ifreq__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
@@ -1217,38 +235,22 @@ pub union ifreq__bindgen_ty_2 {
pub ifru_newname: [::std::os::raw::c_char; 16usize],
pub ifru_data: *mut ::std::os::raw::c_void,
pub ifru_settings: if_settings,
- _bindgen_union_align: [u64; 3usize],
}
impl Default for ifreq__bindgen_ty_2 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
impl Default for ifreq {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub struct ifconf {
- pub ifc_len: ::std::os::raw::c_int,
- pub ifc_ifcu: ifconf__bindgen_ty_1,
-}
-#[repr(C)]
-#[derive(Copy, Clone)]
-pub union ifconf__bindgen_ty_1 {
- pub ifcu_buf: *mut ::std::os::raw::c_char,
- pub ifcu_req: *mut ifreq,
- _bindgen_union_align: u64,
-}
-impl Default for ifconf__bindgen_ty_1 {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-impl Default for ifconf {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
diff --git a/net_sys/src/inn.rs b/net_sys/src/inn.rs
deleted file mode 100644
index 2214fdd37..000000000
--- a/net_sys/src/inn.rs
+++ /dev/null
@@ -1,843 +0,0 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/* automatically generated by rust-bindgen */
-
-pub const __BITS_PER_LONG: ::std::os::raw::c_uint = 64;
-pub const __FD_SETSIZE: ::std::os::raw::c_uint = 1024;
-pub const __UAPI_DEF_IN_ADDR: ::std::os::raw::c_uint = 1;
-pub const __UAPI_DEF_IN_IPPROTO: ::std::os::raw::c_uint = 1;
-pub const __UAPI_DEF_IN_PKTINFO: ::std::os::raw::c_uint = 1;
-pub const __UAPI_DEF_IP_MREQ: ::std::os::raw::c_uint = 1;
-pub const __UAPI_DEF_SOCKADDR_IN: ::std::os::raw::c_uint = 1;
-pub const __UAPI_DEF_IN_CLASS: ::std::os::raw::c_uint = 1;
-pub const __UAPI_DEF_IN6_ADDR: ::std::os::raw::c_uint = 1;
-pub const __UAPI_DEF_IN6_ADDR_ALT: ::std::os::raw::c_uint = 1;
-pub const __UAPI_DEF_SOCKADDR_IN6: ::std::os::raw::c_uint = 1;
-pub const __UAPI_DEF_IPV6_MREQ: ::std::os::raw::c_uint = 1;
-pub const __UAPI_DEF_IPPROTO_V6: ::std::os::raw::c_uint = 1;
-pub const __UAPI_DEF_IPV6_OPTIONS: ::std::os::raw::c_uint = 1;
-pub const __UAPI_DEF_IN6_PKTINFO: ::std::os::raw::c_uint = 1;
-pub const __UAPI_DEF_IP6_MTUINFO: ::std::os::raw::c_uint = 1;
-pub const __UAPI_DEF_XATTR: ::std::os::raw::c_uint = 1;
-pub const _K_SS_MAXSIZE: ::std::os::raw::c_uint = 128;
-pub const IP_TOS: ::std::os::raw::c_uint = 1;
-pub const IP_TTL: ::std::os::raw::c_uint = 2;
-pub const IP_HDRINCL: ::std::os::raw::c_uint = 3;
-pub const IP_OPTIONS: ::std::os::raw::c_uint = 4;
-pub const IP_ROUTER_ALERT: ::std::os::raw::c_uint = 5;
-pub const IP_RECVOPTS: ::std::os::raw::c_uint = 6;
-pub const IP_RETOPTS: ::std::os::raw::c_uint = 7;
-pub const IP_PKTINFO: ::std::os::raw::c_uint = 8;
-pub const IP_PKTOPTIONS: ::std::os::raw::c_uint = 9;
-pub const IP_MTU_DISCOVER: ::std::os::raw::c_uint = 10;
-pub const IP_RECVERR: ::std::os::raw::c_uint = 11;
-pub const IP_RECVTTL: ::std::os::raw::c_uint = 12;
-pub const IP_RECVTOS: ::std::os::raw::c_uint = 13;
-pub const IP_MTU: ::std::os::raw::c_uint = 14;
-pub const IP_FREEBIND: ::std::os::raw::c_uint = 15;
-pub const IP_IPSEC_POLICY: ::std::os::raw::c_uint = 16;
-pub const IP_XFRM_POLICY: ::std::os::raw::c_uint = 17;
-pub const IP_PASSSEC: ::std::os::raw::c_uint = 18;
-pub const IP_TRANSPARENT: ::std::os::raw::c_uint = 19;
-pub const IP_RECVRETOPTS: ::std::os::raw::c_uint = 7;
-pub const IP_ORIGDSTADDR: ::std::os::raw::c_uint = 20;
-pub const IP_RECVORIGDSTADDR: ::std::os::raw::c_uint = 20;
-pub const IP_MINTTL: ::std::os::raw::c_uint = 21;
-pub const IP_NODEFRAG: ::std::os::raw::c_uint = 22;
-pub const IP_CHECKSUM: ::std::os::raw::c_uint = 23;
-pub const IP_BIND_ADDRESS_NO_PORT: ::std::os::raw::c_uint = 24;
-pub const IP_RECVFRAGSIZE: ::std::os::raw::c_uint = 25;
-pub const IP_PMTUDISC_DONT: ::std::os::raw::c_uint = 0;
-pub const IP_PMTUDISC_WANT: ::std::os::raw::c_uint = 1;
-pub const IP_PMTUDISC_DO: ::std::os::raw::c_uint = 2;
-pub const IP_PMTUDISC_PROBE: ::std::os::raw::c_uint = 3;
-pub const IP_PMTUDISC_INTERFACE: ::std::os::raw::c_uint = 4;
-pub const IP_PMTUDISC_OMIT: ::std::os::raw::c_uint = 5;
-pub const IP_MULTICAST_IF: ::std::os::raw::c_uint = 32;
-pub const IP_MULTICAST_TTL: ::std::os::raw::c_uint = 33;
-pub const IP_MULTICAST_LOOP: ::std::os::raw::c_uint = 34;
-pub const IP_ADD_MEMBERSHIP: ::std::os::raw::c_uint = 35;
-pub const IP_DROP_MEMBERSHIP: ::std::os::raw::c_uint = 36;
-pub const IP_UNBLOCK_SOURCE: ::std::os::raw::c_uint = 37;
-pub const IP_BLOCK_SOURCE: ::std::os::raw::c_uint = 38;
-pub const IP_ADD_SOURCE_MEMBERSHIP: ::std::os::raw::c_uint = 39;
-pub const IP_DROP_SOURCE_MEMBERSHIP: ::std::os::raw::c_uint = 40;
-pub const IP_MSFILTER: ::std::os::raw::c_uint = 41;
-pub const MCAST_JOIN_GROUP: ::std::os::raw::c_uint = 42;
-pub const MCAST_BLOCK_SOURCE: ::std::os::raw::c_uint = 43;
-pub const MCAST_UNBLOCK_SOURCE: ::std::os::raw::c_uint = 44;
-pub const MCAST_LEAVE_GROUP: ::std::os::raw::c_uint = 45;
-pub const MCAST_JOIN_SOURCE_GROUP: ::std::os::raw::c_uint = 46;
-pub const MCAST_LEAVE_SOURCE_GROUP: ::std::os::raw::c_uint = 47;
-pub const MCAST_MSFILTER: ::std::os::raw::c_uint = 48;
-pub const IP_MULTICAST_ALL: ::std::os::raw::c_uint = 49;
-pub const IP_UNICAST_IF: ::std::os::raw::c_uint = 50;
-pub const MCAST_EXCLUDE: ::std::os::raw::c_uint = 0;
-pub const MCAST_INCLUDE: ::std::os::raw::c_uint = 1;
-pub const IP_DEFAULT_MULTICAST_TTL: ::std::os::raw::c_uint = 1;
-pub const IP_DEFAULT_MULTICAST_LOOP: ::std::os::raw::c_uint = 1;
-pub const __SOCK_SIZE__: ::std::os::raw::c_uint = 16;
-pub const IN_CLASSA_NET: ::std::os::raw::c_uint = 4278190080;
-pub const IN_CLASSA_NSHIFT: ::std::os::raw::c_uint = 24;
-pub const IN_CLASSA_HOST: ::std::os::raw::c_uint = 16777215;
-pub const IN_CLASSA_MAX: ::std::os::raw::c_uint = 128;
-pub const IN_CLASSB_NET: ::std::os::raw::c_uint = 4294901760;
-pub const IN_CLASSB_NSHIFT: ::std::os::raw::c_uint = 16;
-pub const IN_CLASSB_HOST: ::std::os::raw::c_uint = 65535;
-pub const IN_CLASSB_MAX: ::std::os::raw::c_uint = 65536;
-pub const IN_CLASSC_NET: ::std::os::raw::c_uint = 4294967040;
-pub const IN_CLASSC_NSHIFT: ::std::os::raw::c_uint = 8;
-pub const IN_CLASSC_HOST: ::std::os::raw::c_uint = 255;
-pub const IN_MULTICAST_NET: ::std::os::raw::c_uint = 4026531840;
-pub const IN_LOOPBACKNET: ::std::os::raw::c_uint = 127;
-pub const INADDR_LOOPBACK: ::std::os::raw::c_uint = 2130706433;
-pub const INADDR_UNSPEC_GROUP: ::std::os::raw::c_uint = 3758096384;
-pub const INADDR_ALLHOSTS_GROUP: ::std::os::raw::c_uint = 3758096385;
-pub const INADDR_ALLRTRS_GROUP: ::std::os::raw::c_uint = 3758096386;
-pub const INADDR_MAX_LOCAL_GROUP: ::std::os::raw::c_uint = 3758096639;
-pub const __LITTLE_ENDIAN: ::std::os::raw::c_uint = 1234;
-pub type __s8 = ::std::os::raw::c_schar;
-pub type __u8 = ::std::os::raw::c_uchar;
-pub type __s16 = ::std::os::raw::c_short;
-pub type __u16 = ::std::os::raw::c_ushort;
-pub type __s32 = ::std::os::raw::c_int;
-pub type __u32 = ::std::os::raw::c_uint;
-pub type __s64 = ::std::os::raw::c_longlong;
-pub type __u64 = ::std::os::raw::c_ulonglong;
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct __kernel_fd_set {
- pub fds_bits: [::std::os::raw::c_ulong; 16usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_fd_set() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_fd_set>(),
- 128usize,
- concat!("Size of: ", stringify!(__kernel_fd_set))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_fd_set>(),
- 8usize,
- concat!("Alignment of ", stringify!(__kernel_fd_set))
- );
- assert_eq!(
- unsafe { &(*(0 as *const __kernel_fd_set)).fds_bits as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(__kernel_fd_set),
- "::",
- stringify!(fds_bits)
- )
- );
-}
-impl Clone for __kernel_fd_set {
- fn clone(&self) -> Self {
- *self
- }
-}
-pub type __kernel_sighandler_t =
- ::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
-pub type __kernel_key_t = ::std::os::raw::c_int;
-pub type __kernel_mqd_t = ::std::os::raw::c_int;
-pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
-pub type __kernel_long_t = ::std::os::raw::c_long;
-pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
-pub type __kernel_ino_t = __kernel_ulong_t;
-pub type __kernel_mode_t = ::std::os::raw::c_uint;
-pub type __kernel_pid_t = ::std::os::raw::c_int;
-pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
-pub type __kernel_uid_t = ::std::os::raw::c_uint;
-pub type __kernel_gid_t = ::std::os::raw::c_uint;
-pub type __kernel_suseconds_t = __kernel_long_t;
-pub type __kernel_daddr_t = ::std::os::raw::c_int;
-pub type __kernel_uid32_t = ::std::os::raw::c_uint;
-pub type __kernel_gid32_t = ::std::os::raw::c_uint;
-pub type __kernel_size_t = __kernel_ulong_t;
-pub type __kernel_ssize_t = __kernel_long_t;
-pub type __kernel_ptrdiff_t = __kernel_long_t;
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct __kernel_fsid_t {
- pub val: [::std::os::raw::c_int; 2usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_fsid_t() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_fsid_t>(),
- 8usize,
- concat!("Size of: ", stringify!(__kernel_fsid_t))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_fsid_t>(),
- 4usize,
- concat!("Alignment of ", stringify!(__kernel_fsid_t))
- );
- assert_eq!(
- unsafe { &(*(0 as *const __kernel_fsid_t)).val as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(__kernel_fsid_t),
- "::",
- stringify!(val)
- )
- );
-}
-impl Clone for __kernel_fsid_t {
- fn clone(&self) -> Self {
- *self
- }
-}
-pub type __kernel_off_t = __kernel_long_t;
-pub type __kernel_loff_t = ::std::os::raw::c_longlong;
-pub type __kernel_time_t = __kernel_long_t;
-pub type __kernel_clock_t = __kernel_long_t;
-pub type __kernel_timer_t = ::std::os::raw::c_int;
-pub type __kernel_clockid_t = ::std::os::raw::c_int;
-pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
-pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
-pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
-pub type __le16 = __u16;
-pub type __be16 = __u16;
-pub type __le32 = __u32;
-pub type __be32 = __u32;
-pub type __le64 = __u64;
-pub type __be64 = __u64;
-pub type __sum16 = __u16;
-pub type __wsum = __u32;
-pub type __kernel_sa_family_t = ::std::os::raw::c_ushort;
-#[repr(C)]
-pub struct __kernel_sockaddr_storage {
- pub ss_family: __kernel_sa_family_t,
- pub __data: [::std::os::raw::c_char; 126usize],
- pub __bindgen_align: [u64; 0usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_sockaddr_storage() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_sockaddr_storage>(),
- 128usize,
- concat!("Size of: ", stringify!(__kernel_sockaddr_storage))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_sockaddr_storage>(),
- 8usize,
- concat!("Alignment of ", stringify!(__kernel_sockaddr_storage))
- );
- assert_eq!(
- unsafe { &(*(0 as *const __kernel_sockaddr_storage)).ss_family as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(__kernel_sockaddr_storage),
- "::",
- stringify!(ss_family)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const __kernel_sockaddr_storage)).__data as *const _ as usize },
- 2usize,
- concat!(
- "Alignment of field: ",
- stringify!(__kernel_sockaddr_storage),
- "::",
- stringify!(__data)
- )
- );
-}
-impl Default for __kernel_sockaddr_storage {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-pub const IPPROTO_IP: _bindgen_ty_1 = 0;
-pub const IPPROTO_ICMP: _bindgen_ty_1 = 1;
-pub const IPPROTO_IGMP: _bindgen_ty_1 = 2;
-pub const IPPROTO_IPIP: _bindgen_ty_1 = 4;
-pub const IPPROTO_TCP: _bindgen_ty_1 = 6;
-pub const IPPROTO_EGP: _bindgen_ty_1 = 8;
-pub const IPPROTO_PUP: _bindgen_ty_1 = 12;
-pub const IPPROTO_UDP: _bindgen_ty_1 = 17;
-pub const IPPROTO_IDP: _bindgen_ty_1 = 22;
-pub const IPPROTO_TP: _bindgen_ty_1 = 29;
-pub const IPPROTO_DCCP: _bindgen_ty_1 = 33;
-pub const IPPROTO_IPV6: _bindgen_ty_1 = 41;
-pub const IPPROTO_RSVP: _bindgen_ty_1 = 46;
-pub const IPPROTO_GRE: _bindgen_ty_1 = 47;
-pub const IPPROTO_ESP: _bindgen_ty_1 = 50;
-pub const IPPROTO_AH: _bindgen_ty_1 = 51;
-pub const IPPROTO_MTP: _bindgen_ty_1 = 92;
-pub const IPPROTO_BEETPH: _bindgen_ty_1 = 94;
-pub const IPPROTO_ENCAP: _bindgen_ty_1 = 98;
-pub const IPPROTO_PIM: _bindgen_ty_1 = 103;
-pub const IPPROTO_COMP: _bindgen_ty_1 = 108;
-pub const IPPROTO_SCTP: _bindgen_ty_1 = 132;
-pub const IPPROTO_UDPLITE: _bindgen_ty_1 = 136;
-pub const IPPROTO_MPLS: _bindgen_ty_1 = 137;
-pub const IPPROTO_RAW: _bindgen_ty_1 = 255;
-pub const IPPROTO_MAX: _bindgen_ty_1 = 256;
-pub type _bindgen_ty_1 = ::std::os::raw::c_uint;
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct in_addr {
- pub s_addr: __be32,
-}
-#[test]
-fn bindgen_test_layout_in_addr() {
- assert_eq!(
- ::std::mem::size_of::<in_addr>(),
- 4usize,
- concat!("Size of: ", stringify!(in_addr))
- );
- assert_eq!(
- ::std::mem::align_of::<in_addr>(),
- 4usize,
- concat!("Alignment of ", stringify!(in_addr))
- );
- assert_eq!(
- unsafe { &(*(0 as *const in_addr)).s_addr as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(in_addr),
- "::",
- stringify!(s_addr)
- )
- );
-}
-impl Clone for in_addr {
- fn clone(&self) -> Self {
- *self
- }
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct ip_mreq {
- pub imr_multiaddr: in_addr,
- pub imr_interface: in_addr,
-}
-#[test]
-fn bindgen_test_layout_ip_mreq() {
- assert_eq!(
- ::std::mem::size_of::<ip_mreq>(),
- 8usize,
- concat!("Size of: ", stringify!(ip_mreq))
- );
- assert_eq!(
- ::std::mem::align_of::<ip_mreq>(),
- 4usize,
- concat!("Alignment of ", stringify!(ip_mreq))
- );
- assert_eq!(
- unsafe { &(*(0 as *const ip_mreq)).imr_multiaddr as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(ip_mreq),
- "::",
- stringify!(imr_multiaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const ip_mreq)).imr_interface as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(ip_mreq),
- "::",
- stringify!(imr_interface)
- )
- );
-}
-impl Clone for ip_mreq {
- fn clone(&self) -> Self {
- *self
- }
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct ip_mreqn {
- pub imr_multiaddr: in_addr,
- pub imr_address: in_addr,
- pub imr_ifindex: ::std::os::raw::c_int,
-}
-#[test]
-fn bindgen_test_layout_ip_mreqn() {
- assert_eq!(
- ::std::mem::size_of::<ip_mreqn>(),
- 12usize,
- concat!("Size of: ", stringify!(ip_mreqn))
- );
- assert_eq!(
- ::std::mem::align_of::<ip_mreqn>(),
- 4usize,
- concat!("Alignment of ", stringify!(ip_mreqn))
- );
- assert_eq!(
- unsafe { &(*(0 as *const ip_mreqn)).imr_multiaddr as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(ip_mreqn),
- "::",
- stringify!(imr_multiaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const ip_mreqn)).imr_address as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(ip_mreqn),
- "::",
- stringify!(imr_address)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const ip_mreqn)).imr_ifindex as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(ip_mreqn),
- "::",
- stringify!(imr_ifindex)
- )
- );
-}
-impl Clone for ip_mreqn {
- fn clone(&self) -> Self {
- *self
- }
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct ip_mreq_source {
- pub imr_multiaddr: __be32,
- pub imr_interface: __be32,
- pub imr_sourceaddr: __be32,
-}
-#[test]
-fn bindgen_test_layout_ip_mreq_source() {
- assert_eq!(
- ::std::mem::size_of::<ip_mreq_source>(),
- 12usize,
- concat!("Size of: ", stringify!(ip_mreq_source))
- );
- assert_eq!(
- ::std::mem::align_of::<ip_mreq_source>(),
- 4usize,
- concat!("Alignment of ", stringify!(ip_mreq_source))
- );
- assert_eq!(
- unsafe { &(*(0 as *const ip_mreq_source)).imr_multiaddr as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(ip_mreq_source),
- "::",
- stringify!(imr_multiaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const ip_mreq_source)).imr_interface as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(ip_mreq_source),
- "::",
- stringify!(imr_interface)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const ip_mreq_source)).imr_sourceaddr as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(ip_mreq_source),
- "::",
- stringify!(imr_sourceaddr)
- )
- );
-}
-impl Clone for ip_mreq_source {
- fn clone(&self) -> Self {
- *self
- }
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct ip_msfilter {
- pub imsf_multiaddr: __be32,
- pub imsf_interface: __be32,
- pub imsf_fmode: __u32,
- pub imsf_numsrc: __u32,
- pub imsf_slist: [__be32; 1usize],
-}
-#[test]
-fn bindgen_test_layout_ip_msfilter() {
- assert_eq!(
- ::std::mem::size_of::<ip_msfilter>(),
- 20usize,
- concat!("Size of: ", stringify!(ip_msfilter))
- );
- assert_eq!(
- ::std::mem::align_of::<ip_msfilter>(),
- 4usize,
- concat!("Alignment of ", stringify!(ip_msfilter))
- );
- assert_eq!(
- unsafe { &(*(0 as *const ip_msfilter)).imsf_multiaddr as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(ip_msfilter),
- "::",
- stringify!(imsf_multiaddr)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const ip_msfilter)).imsf_interface as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(ip_msfilter),
- "::",
- stringify!(imsf_interface)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const ip_msfilter)).imsf_fmode as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(ip_msfilter),
- "::",
- stringify!(imsf_fmode)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const ip_msfilter)).imsf_numsrc as *const _ as usize },
- 12usize,
- concat!(
- "Alignment of field: ",
- stringify!(ip_msfilter),
- "::",
- stringify!(imsf_numsrc)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const ip_msfilter)).imsf_slist as *const _ as usize },
- 16usize,
- concat!(
- "Alignment of field: ",
- stringify!(ip_msfilter),
- "::",
- stringify!(imsf_slist)
- )
- );
-}
-impl Clone for ip_msfilter {
- fn clone(&self) -> Self {
- *self
- }
-}
-#[repr(C)]
-pub struct group_req {
- pub gr_interface: __u32,
- pub gr_group: __kernel_sockaddr_storage,
-}
-#[test]
-fn bindgen_test_layout_group_req() {
- assert_eq!(
- ::std::mem::size_of::<group_req>(),
- 136usize,
- concat!("Size of: ", stringify!(group_req))
- );
- assert_eq!(
- ::std::mem::align_of::<group_req>(),
- 8usize,
- concat!("Alignment of ", stringify!(group_req))
- );
- assert_eq!(
- unsafe { &(*(0 as *const group_req)).gr_interface as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(group_req),
- "::",
- stringify!(gr_interface)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const group_req)).gr_group as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(group_req),
- "::",
- stringify!(gr_group)
- )
- );
-}
-impl Default for group_req {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-#[repr(C)]
-pub struct group_source_req {
- pub gsr_interface: __u32,
- pub gsr_group: __kernel_sockaddr_storage,
- pub gsr_source: __kernel_sockaddr_storage,
-}
-#[test]
-fn bindgen_test_layout_group_source_req() {
- assert_eq!(
- ::std::mem::size_of::<group_source_req>(),
- 264usize,
- concat!("Size of: ", stringify!(group_source_req))
- );
- assert_eq!(
- ::std::mem::align_of::<group_source_req>(),
- 8usize,
- concat!("Alignment of ", stringify!(group_source_req))
- );
- assert_eq!(
- unsafe { &(*(0 as *const group_source_req)).gsr_interface as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(group_source_req),
- "::",
- stringify!(gsr_interface)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const group_source_req)).gsr_group as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(group_source_req),
- "::",
- stringify!(gsr_group)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const group_source_req)).gsr_source as *const _ as usize },
- 136usize,
- concat!(
- "Alignment of field: ",
- stringify!(group_source_req),
- "::",
- stringify!(gsr_source)
- )
- );
-}
-impl Default for group_source_req {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-#[repr(C)]
-pub struct group_filter {
- pub gf_interface: __u32,
- pub gf_group: __kernel_sockaddr_storage,
- pub gf_fmode: __u32,
- pub gf_numsrc: __u32,
- pub gf_slist: [__kernel_sockaddr_storage; 1usize],
-}
-#[test]
-fn bindgen_test_layout_group_filter() {
- assert_eq!(
- ::std::mem::size_of::<group_filter>(),
- 272usize,
- concat!("Size of: ", stringify!(group_filter))
- );
- assert_eq!(
- ::std::mem::align_of::<group_filter>(),
- 8usize,
- concat!("Alignment of ", stringify!(group_filter))
- );
- assert_eq!(
- unsafe { &(*(0 as *const group_filter)).gf_interface as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(group_filter),
- "::",
- stringify!(gf_interface)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const group_filter)).gf_group as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(group_filter),
- "::",
- stringify!(gf_group)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const group_filter)).gf_fmode as *const _ as usize },
- 136usize,
- concat!(
- "Alignment of field: ",
- stringify!(group_filter),
- "::",
- stringify!(gf_fmode)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const group_filter)).gf_numsrc as *const _ as usize },
- 140usize,
- concat!(
- "Alignment of field: ",
- stringify!(group_filter),
- "::",
- stringify!(gf_numsrc)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const group_filter)).gf_slist as *const _ as usize },
- 144usize,
- concat!(
- "Alignment of field: ",
- stringify!(group_filter),
- "::",
- stringify!(gf_slist)
- )
- );
-}
-impl Default for group_filter {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct in_pktinfo {
- pub ipi_ifindex: ::std::os::raw::c_int,
- pub ipi_spec_dst: in_addr,
- pub ipi_addr: in_addr,
-}
-#[test]
-fn bindgen_test_layout_in_pktinfo() {
- assert_eq!(
- ::std::mem::size_of::<in_pktinfo>(),
- 12usize,
- concat!("Size of: ", stringify!(in_pktinfo))
- );
- assert_eq!(
- ::std::mem::align_of::<in_pktinfo>(),
- 4usize,
- concat!("Alignment of ", stringify!(in_pktinfo))
- );
- assert_eq!(
- unsafe { &(*(0 as *const in_pktinfo)).ipi_ifindex as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(in_pktinfo),
- "::",
- stringify!(ipi_ifindex)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const in_pktinfo)).ipi_spec_dst as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(in_pktinfo),
- "::",
- stringify!(ipi_spec_dst)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const in_pktinfo)).ipi_addr as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(in_pktinfo),
- "::",
- stringify!(ipi_addr)
- )
- );
-}
-impl Clone for in_pktinfo {
- fn clone(&self) -> Self {
- *self
- }
-}
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct sockaddr_in {
- pub sin_family: __kernel_sa_family_t,
- pub sin_port: __be16,
- pub sin_addr: in_addr,
- pub __pad: [::std::os::raw::c_uchar; 8usize],
-}
-#[test]
-fn bindgen_test_layout_sockaddr_in() {
- assert_eq!(
- ::std::mem::size_of::<sockaddr_in>(),
- 16usize,
- concat!("Size of: ", stringify!(sockaddr_in))
- );
- assert_eq!(
- ::std::mem::align_of::<sockaddr_in>(),
- 4usize,
- concat!("Alignment of ", stringify!(sockaddr_in))
- );
- assert_eq!(
- unsafe { &(*(0 as *const sockaddr_in)).sin_family as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(sockaddr_in),
- "::",
- stringify!(sin_family)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const sockaddr_in)).sin_port as *const _ as usize },
- 2usize,
- concat!(
- "Alignment of field: ",
- stringify!(sockaddr_in),
- "::",
- stringify!(sin_port)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const sockaddr_in)).sin_addr as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(sockaddr_in),
- "::",
- stringify!(sin_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const sockaddr_in)).__pad as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(sockaddr_in),
- "::",
- stringify!(__pad)
- )
- );
-}
-impl Clone for sockaddr_in {
- fn clone(&self) -> Self {
- *self
- }
-}
diff --git a/net_sys/src/lib.rs b/net_sys/src/lib.rs
index 1db9a6d33..13515a141 100644
--- a/net_sys/src/lib.rs
+++ b/net_sys/src/lib.rs
@@ -8,34 +8,14 @@
use base::{ioctl_ior_nr, ioctl_iow_nr};
-// generated with bindgen /usr/include/linux/if.h --no-unstable-rust
-// --constified-enum '*' --with-derive-default -- -D __UAPI_DEF_IF_IFNAMSIZ -D
-// __UAPI_DEF_IF_NET_DEVICE_FLAGS -D __UAPI_DEF_IF_IFREQ -D __UAPI_DEF_IF_IFMAP
-// Name is "iff" to avoid conflicting with "if" keyword.
-// Generated against Linux 4.11 to include fix "uapi: fix linux/if.h userspace
-// compilation errors".
-// Manual fixup of ifrn_name to be of type c_uchar instead of c_char.
-#[allow(clippy::all)]
-pub mod iff;
-// generated with bindgen /usr/include/linux/if_tun.h --no-unstable-rust
-// --constified-enum '*' --with-derive-default
pub mod if_tun;
-// generated with bindgen /usr/include/linux/in.h --no-unstable-rust
-// --constified-enum '*' --with-derive-default
-// Name is "inn" to avoid conflicting with "in" keyword.
-pub mod inn;
-// generated with bindgen /usr/include/linux/sockios.h --no-unstable-rust
-// --constified-enum '*' --with-derive-default
+pub mod iff; // Named "iff" to avoid conflicting with "if" keyword.
pub mod sockios;
-pub use crate::if_tun::*;
-pub use crate::iff::*;
-pub use crate::inn::*;
-pub use crate::sockios::*;
+pub use crate::if_tun::{sock_fprog, TUN_F_CSUM, TUN_F_TSO4, TUN_F_TSO6, TUN_F_TSO_ECN, TUN_F_UFO};
+pub use crate::iff::{ifreq, net_device_flags};
pub const TUNTAP: ::std::os::raw::c_uint = 84;
-pub const ARPHRD_ETHER: sa_family_t = 1;
-
ioctl_iow_nr!(TUNSETNOCSUM, TUNTAP, 200, ::std::os::raw::c_int);
ioctl_iow_nr!(TUNSETDEBUG, TUNTAP, 201, ::std::os::raw::c_int);
ioctl_iow_nr!(TUNSETIFF, TUNTAP, 202, ::std::os::raw::c_int);
diff --git a/net_sys/src/sockios.rs b/net_sys/src/sockios.rs
index 0c0af7678..6207044af 100644
--- a/net_sys/src/sockios.rs
+++ b/net_sys/src/sockios.rs
@@ -1,89 +1,88 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+/* automatically generated by tools/bindgen-all-the-things */
-/* automatically generated by rust-bindgen */
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
-pub const FIOSETOWN: ::std::os::raw::c_uint = 35073;
-pub const SIOCSPGRP: ::std::os::raw::c_uint = 35074;
-pub const FIOGETOWN: ::std::os::raw::c_uint = 35075;
-pub const SIOCGPGRP: ::std::os::raw::c_uint = 35076;
-pub const SIOCATMARK: ::std::os::raw::c_uint = 35077;
-pub const SIOCGSTAMP: ::std::os::raw::c_uint = 35078;
-pub const SIOCGSTAMPNS: ::std::os::raw::c_uint = 35079;
-pub const SOCK_IOC_TYPE: ::std::os::raw::c_uint = 137;
-pub const SIOCADDRT: ::std::os::raw::c_uint = 35083;
-pub const SIOCDELRT: ::std::os::raw::c_uint = 35084;
-pub const SIOCRTMSG: ::std::os::raw::c_uint = 35085;
-pub const SIOCGIFNAME: ::std::os::raw::c_uint = 35088;
-pub const SIOCSIFLINK: ::std::os::raw::c_uint = 35089;
-pub const SIOCGIFCONF: ::std::os::raw::c_uint = 35090;
-pub const SIOCGIFFLAGS: ::std::os::raw::c_uint = 35091;
-pub const SIOCSIFFLAGS: ::std::os::raw::c_uint = 35092;
-pub const SIOCGIFADDR: ::std::os::raw::c_uint = 35093;
-pub const SIOCSIFADDR: ::std::os::raw::c_uint = 35094;
-pub const SIOCGIFDSTADDR: ::std::os::raw::c_uint = 35095;
-pub const SIOCSIFDSTADDR: ::std::os::raw::c_uint = 35096;
-pub const SIOCGIFBRDADDR: ::std::os::raw::c_uint = 35097;
-pub const SIOCSIFBRDADDR: ::std::os::raw::c_uint = 35098;
-pub const SIOCGIFNETMASK: ::std::os::raw::c_uint = 35099;
-pub const SIOCSIFNETMASK: ::std::os::raw::c_uint = 35100;
-pub const SIOCGIFMETRIC: ::std::os::raw::c_uint = 35101;
-pub const SIOCSIFMETRIC: ::std::os::raw::c_uint = 35102;
-pub const SIOCGIFMEM: ::std::os::raw::c_uint = 35103;
-pub const SIOCSIFMEM: ::std::os::raw::c_uint = 35104;
-pub const SIOCGIFMTU: ::std::os::raw::c_uint = 35105;
-pub const SIOCSIFMTU: ::std::os::raw::c_uint = 35106;
-pub const SIOCSIFNAME: ::std::os::raw::c_uint = 35107;
-pub const SIOCSIFHWADDR: ::std::os::raw::c_uint = 35108;
-pub const SIOCGIFENCAP: ::std::os::raw::c_uint = 35109;
-pub const SIOCSIFENCAP: ::std::os::raw::c_uint = 35110;
-pub const SIOCGIFHWADDR: ::std::os::raw::c_uint = 35111;
-pub const SIOCGIFSLAVE: ::std::os::raw::c_uint = 35113;
-pub const SIOCSIFSLAVE: ::std::os::raw::c_uint = 35120;
-pub const SIOCADDMULTI: ::std::os::raw::c_uint = 35121;
-pub const SIOCDELMULTI: ::std::os::raw::c_uint = 35122;
-pub const SIOCGIFINDEX: ::std::os::raw::c_uint = 35123;
-pub const SIOGIFINDEX: ::std::os::raw::c_uint = 35123;
-pub const SIOCSIFPFLAGS: ::std::os::raw::c_uint = 35124;
-pub const SIOCGIFPFLAGS: ::std::os::raw::c_uint = 35125;
-pub const SIOCDIFADDR: ::std::os::raw::c_uint = 35126;
-pub const SIOCSIFHWBROADCAST: ::std::os::raw::c_uint = 35127;
-pub const SIOCGIFCOUNT: ::std::os::raw::c_uint = 35128;
-pub const SIOCGIFBR: ::std::os::raw::c_uint = 35136;
-pub const SIOCSIFBR: ::std::os::raw::c_uint = 35137;
-pub const SIOCGIFTXQLEN: ::std::os::raw::c_uint = 35138;
-pub const SIOCSIFTXQLEN: ::std::os::raw::c_uint = 35139;
-pub const SIOCETHTOOL: ::std::os::raw::c_uint = 35142;
-pub const SIOCGMIIPHY: ::std::os::raw::c_uint = 35143;
-pub const SIOCGMIIREG: ::std::os::raw::c_uint = 35144;
-pub const SIOCSMIIREG: ::std::os::raw::c_uint = 35145;
-pub const SIOCWANDEV: ::std::os::raw::c_uint = 35146;
-pub const SIOCOUTQNSD: ::std::os::raw::c_uint = 35147;
-pub const SIOCGSKNS: ::std::os::raw::c_uint = 35148;
-pub const SIOCDARP: ::std::os::raw::c_uint = 35155;
-pub const SIOCGARP: ::std::os::raw::c_uint = 35156;
-pub const SIOCSARP: ::std::os::raw::c_uint = 35157;
-pub const SIOCDRARP: ::std::os::raw::c_uint = 35168;
-pub const SIOCGRARP: ::std::os::raw::c_uint = 35169;
-pub const SIOCSRARP: ::std::os::raw::c_uint = 35170;
-pub const SIOCGIFMAP: ::std::os::raw::c_uint = 35184;
-pub const SIOCSIFMAP: ::std::os::raw::c_uint = 35185;
-pub const SIOCADDDLCI: ::std::os::raw::c_uint = 35200;
-pub const SIOCDELDLCI: ::std::os::raw::c_uint = 35201;
-pub const SIOCGIFVLAN: ::std::os::raw::c_uint = 35202;
-pub const SIOCSIFVLAN: ::std::os::raw::c_uint = 35203;
-pub const SIOCBONDENSLAVE: ::std::os::raw::c_uint = 35216;
-pub const SIOCBONDRELEASE: ::std::os::raw::c_uint = 35217;
-pub const SIOCBONDSETHWADDR: ::std::os::raw::c_uint = 35218;
-pub const SIOCBONDSLAVEINFOQUERY: ::std::os::raw::c_uint = 35219;
-pub const SIOCBONDINFOQUERY: ::std::os::raw::c_uint = 35220;
-pub const SIOCBONDCHANGEACTIVE: ::std::os::raw::c_uint = 35221;
-pub const SIOCBRADDBR: ::std::os::raw::c_uint = 35232;
-pub const SIOCBRDELBR: ::std::os::raw::c_uint = 35233;
-pub const SIOCBRADDIF: ::std::os::raw::c_uint = 35234;
-pub const SIOCBRDELIF: ::std::os::raw::c_uint = 35235;
-pub const SIOCSHWTSTAMP: ::std::os::raw::c_uint = 35248;
-pub const SIOCGHWTSTAMP: ::std::os::raw::c_uint = 35249;
-pub const SIOCDEVPRIVATE: ::std::os::raw::c_uint = 35312;
-pub const SIOCPROTOPRIVATE: ::std::os::raw::c_uint = 35296;
+pub const SIOCSPGRP: u32 = 35074;
+pub const SIOCGPGRP: u32 = 35076;
+pub const SIOCATMARK: u32 = 35077;
+pub const SIOCGSTAMP_OLD: u32 = 35078;
+pub const SIOCGSTAMPNS_OLD: u32 = 35079;
+pub const SIOCGSTAMP: u32 = 35078;
+pub const SIOCGSTAMPNS: u32 = 35079;
+pub const SIOCADDRT: u32 = 35083;
+pub const SIOCDELRT: u32 = 35084;
+pub const SIOCRTMSG: u32 = 35085;
+pub const SIOCGIFNAME: u32 = 35088;
+pub const SIOCSIFLINK: u32 = 35089;
+pub const SIOCGIFCONF: u32 = 35090;
+pub const SIOCGIFFLAGS: u32 = 35091;
+pub const SIOCSIFFLAGS: u32 = 35092;
+pub const SIOCGIFADDR: u32 = 35093;
+pub const SIOCSIFADDR: u32 = 35094;
+pub const SIOCGIFDSTADDR: u32 = 35095;
+pub const SIOCSIFDSTADDR: u32 = 35096;
+pub const SIOCGIFBRDADDR: u32 = 35097;
+pub const SIOCSIFBRDADDR: u32 = 35098;
+pub const SIOCGIFNETMASK: u32 = 35099;
+pub const SIOCSIFNETMASK: u32 = 35100;
+pub const SIOCGIFMETRIC: u32 = 35101;
+pub const SIOCSIFMETRIC: u32 = 35102;
+pub const SIOCGIFMEM: u32 = 35103;
+pub const SIOCSIFMEM: u32 = 35104;
+pub const SIOCGIFMTU: u32 = 35105;
+pub const SIOCSIFMTU: u32 = 35106;
+pub const SIOCSIFNAME: u32 = 35107;
+pub const SIOCSIFHWADDR: u32 = 35108;
+pub const SIOCGIFENCAP: u32 = 35109;
+pub const SIOCSIFENCAP: u32 = 35110;
+pub const SIOCGIFHWADDR: u32 = 35111;
+pub const SIOCGIFSLAVE: u32 = 35113;
+pub const SIOCSIFSLAVE: u32 = 35120;
+pub const SIOCADDMULTI: u32 = 35121;
+pub const SIOCDELMULTI: u32 = 35122;
+pub const SIOCGIFINDEX: u32 = 35123;
+pub const SIOCSIFPFLAGS: u32 = 35124;
+pub const SIOCGIFPFLAGS: u32 = 35125;
+pub const SIOCDIFADDR: u32 = 35126;
+pub const SIOCSIFHWBROADCAST: u32 = 35127;
+pub const SIOCGIFCOUNT: u32 = 35128;
+pub const SIOCGIFBR: u32 = 35136;
+pub const SIOCSIFBR: u32 = 35137;
+pub const SIOCGIFTXQLEN: u32 = 35138;
+pub const SIOCSIFTXQLEN: u32 = 35139;
+pub const SIOCETHTOOL: u32 = 35142;
+pub const SIOCGMIIPHY: u32 = 35143;
+pub const SIOCGMIIREG: u32 = 35144;
+pub const SIOCSMIIREG: u32 = 35145;
+pub const SIOCWANDEV: u32 = 35146;
+pub const SIOCOUTQNSD: u32 = 35147;
+pub const SIOCGSKNS: u32 = 35148;
+pub const SIOCDARP: u32 = 35155;
+pub const SIOCGARP: u32 = 35156;
+pub const SIOCSARP: u32 = 35157;
+pub const SIOCDRARP: u32 = 35168;
+pub const SIOCGRARP: u32 = 35169;
+pub const SIOCSRARP: u32 = 35170;
+pub const SIOCGIFMAP: u32 = 35184;
+pub const SIOCSIFMAP: u32 = 35185;
+pub const SIOCADDDLCI: u32 = 35200;
+pub const SIOCDELDLCI: u32 = 35201;
+pub const SIOCGIFVLAN: u32 = 35202;
+pub const SIOCSIFVLAN: u32 = 35203;
+pub const SIOCBONDENSLAVE: u32 = 35216;
+pub const SIOCBONDRELEASE: u32 = 35217;
+pub const SIOCBONDSETHWADDR: u32 = 35218;
+pub const SIOCBONDSLAVEINFOQUERY: u32 = 35219;
+pub const SIOCBONDINFOQUERY: u32 = 35220;
+pub const SIOCBONDCHANGEACTIVE: u32 = 35221;
+pub const SIOCBRADDBR: u32 = 35232;
+pub const SIOCBRDELBR: u32 = 35233;
+pub const SIOCBRADDIF: u32 = 35234;
+pub const SIOCBRDELIF: u32 = 35235;
+pub const SIOCSHWTSTAMP: u32 = 35248;
+pub const SIOCGHWTSTAMP: u32 = 35249;
+pub const SIOCDEVPRIVATE: u32 = 35312;
+pub const SIOCPROTOPRIVATE: u32 = 35296;
diff --git a/net_util/Android.bp b/net_util/Android.bp
index ba0dca600..0a7456e0a 100644
--- a/net_util/Android.bp
+++ b/net_util/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -15,82 +15,42 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "net_util",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
+ "libcros_async",
"libdata_model",
"liblibc",
"libnet_sys",
+ "libthiserror",
],
+ proc_macros: ["libremain"],
}
-rust_defaults {
- name: "net_util_defaults",
+rust_test {
+ name: "net_util_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "net_util",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: false,
+ },
+ edition: "2021",
rustlibs: [
"libbase_rust",
+ "libcros_async",
"libdata_model",
"liblibc",
"libnet_sys",
+ "libthiserror",
],
+ proc_macros: ["libremain"],
}
-
-rust_test_host {
- name: "net_util_host_test_src_lib",
- defaults: ["net_util_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "net_util_device_test_src_lib",
- defaults: ["net_util_defaults"],
-}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../net_sys/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/net_util/Cargo.toml b/net_util/Cargo.toml
index 66a646649..972500edf 100644
--- a/net_util/Cargo.toml
+++ b/net_util/Cargo.toml
@@ -2,10 +2,13 @@
name = "net_util"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
libc = "*"
-data_model = { path = "../data_model" }
+data_model = { path = "../common/data_model" }
net_sys = { path = "../net_sys" }
base = { path = "../base" }
+cros_async = { path = "../cros_async" }
+remain = "*"
+thiserror = "*"
diff --git a/net_util/TEST_MAPPING b/net_util/TEST_MAPPING
index 8e5f77ea8..59313bf26 100644
--- a/net_util/TEST_MAPPING
+++ b/net_util/TEST_MAPPING
@@ -4,10 +4,10 @@
// "presubmit": [
// {
// "host": true,
-// "name": "net_util_host_test_src_lib"
+// "name": "net_util_test_src_lib"
// },
// {
-// "name": "net_util_device_test_src_lib"
+// "name": "net_util_test_src_lib"
// }
// ]
}
diff --git a/net_util/cargo2android.json b/net_util/cargo2android.json
new file mode 100644
index 000000000..9a64b6c75
--- /dev/null
+++ b/net_util/cargo2android.json
@@ -0,0 +1,8 @@
+{
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "no_presubmit": true,
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/net_util/src/lib.rs b/net_util/src/lib.rs
index bb17cb48d..42e465898 100644
--- a/net_util/src/lib.rs
+++ b/net_util/src/lib.rs
@@ -20,39 +20,38 @@ use base::{
ioctl_with_mut_ref, ioctl_with_ref, ioctl_with_val, volatile_impl, AsRawDescriptor,
FromRawDescriptor, IoctlNr, RawDescriptor,
};
+use cros_async::IntoAsync;
+use remain::sorted;
+use thiserror::Error as ThisError;
-#[derive(Debug)]
+#[sorted]
+#[derive(ThisError, Debug)]
pub enum Error {
+ /// Unable to clone tap interface.
+ #[error("failed to clone tap interface: {0}")]
+ CloneTap(SysError),
/// Failed to create a socket.
+ #[error("failed to create a socket: {0}")]
CreateSocket(SysError),
- /// Couldn't open /dev/net/tun.
- OpenTun(SysError),
/// Unable to create tap interface.
+ #[error("failed to create tap interface: {0}")]
CreateTap(SysError),
/// ioctl failed.
+ #[error("ioctl failed: {0}")]
IoctlError(SysError),
+ /// Couldn't open /dev/net/tun.
+ #[error("failed to open /dev/net/tun: {0}")]
+ OpenTun(SysError),
}
pub type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- CreateSocket(e) => write!(f, "failed to create a socket: {}", e),
- OpenTun(e) => write!(f, "failed to open /dev/net/tun: {}", e),
- CreateTap(e) => write!(f, "failed to create tap interface: {}", e),
- IoctlError(e) => write!(f, "ioctl failed: {}", e),
- }
- }
-}
-
impl Error {
pub fn sys_error(&self) -> SysError {
match *self {
Error::CreateSocket(e) => e,
Error::OpenTun(e) => e,
Error::CreateTap(e) => e,
+ Error::CloneTap(e) => e,
Error::IoctlError(e) => e,
}
}
@@ -60,25 +59,25 @@ impl Error {
/// Create a sockaddr_in from an IPv4 address, and expose it as
/// an opaque sockaddr suitable for usage by socket ioctls.
-fn create_sockaddr(ip_addr: net::Ipv4Addr) -> net_sys::sockaddr {
+fn create_sockaddr(ip_addr: net::Ipv4Addr) -> libc::sockaddr {
// IPv4 addresses big-endian (network order), but Ipv4Addr will give us
// a view of those bytes directly so we can avoid any endian trickiness.
- let addr_in = net_sys::sockaddr_in {
- sin_family: net_sys::AF_INET as u16,
+ let addr_in = libc::sockaddr_in {
+ sin_family: libc::AF_INET as u16,
sin_port: 0,
sin_addr: unsafe { mem::transmute(ip_addr.octets()) },
- __pad: [0; 8usize],
+ sin_zero: [0; 8usize],
};
unsafe { mem::transmute(addr_in) }
}
/// Extract the IPv4 address from a sockaddr. Assumes the sockaddr is a sockaddr_in.
-fn read_ipv4_addr(addr: &net_sys::sockaddr) -> net::Ipv4Addr {
- debug_assert_eq!(addr.sa_family as u32, net_sys::AF_INET);
+fn read_ipv4_addr(addr: &libc::sockaddr) -> net::Ipv4Addr {
+ debug_assert_eq!(addr.sa_family as libc::c_int, libc::AF_INET);
// This is safe because sockaddr and sockaddr_in are the same size, and we've checked that
// this address is AF_INET.
- let in_addr: net_sys::sockaddr_in = unsafe { mem::transmute(*addr) };
+ let in_addr: libc::sockaddr_in = unsafe { mem::transmute(*addr) };
net::Ipv4Addr::from(in_addr.sin_addr.s_addr)
}
@@ -93,30 +92,22 @@ fn create_socket() -> Result<net::UdpSocket> {
Ok(unsafe { net::UdpSocket::from_raw_fd(sock) })
}
-#[derive(Debug)]
+#[sorted]
+#[derive(ThisError, Debug)]
pub enum MacAddressError {
/// Invalid number of octets.
+ #[error("invalid number of octets: {0}")]
InvalidNumOctets(usize),
/// Failed to parse octet.
+ #[error("failed to parse octet: {0}")]
ParseOctet(ParseIntError),
}
-impl Display for MacAddressError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::MacAddressError::*;
-
- match self {
- InvalidNumOctets(n) => write!(f, "invalid number of octets: {}", n),
- ParseOctet(e) => write!(f, "failed to parse octet: {}", e),
- }
- }
-}
-
/// An Ethernet mac address. This struct is compatible with the C `struct sockaddr`.
#[repr(C)]
#[derive(Clone, Copy)]
pub struct MacAddress {
- family: net_sys::sa_family_t,
+ family: libc::sa_family_t,
addr: [u8; 6usize],
__pad: [u8; 8usize],
}
@@ -137,7 +128,7 @@ impl FromStr for MacAddress {
}
let mut result = MacAddress {
- family: net_sys::ARPHRD_ETHER,
+ family: libc::ARPHRD_ETHER,
addr: [0; 6usize],
__pad: [0; 8usize],
};
@@ -174,25 +165,7 @@ pub struct Tap {
}
impl Tap {
- pub unsafe fn from_raw_descriptor(fd: RawDescriptor) -> Result<Tap> {
- let tap_file = File::from_raw_descriptor(fd);
-
- // Get the interface name since we will need it for some ioctls.
- let mut ifreq: net_sys::ifreq = Default::default();
- let ret = ioctl_with_mut_ref(&tap_file, net_sys::TUNGETIFF(), &mut ifreq);
-
- if ret < 0 {
- return Err(Error::IoctlError(SysError::last()));
- }
-
- Ok(Tap {
- tap_file,
- if_name: ifreq.ifr_ifrn.ifrn_name,
- if_flags: ifreq.ifr_ifru.ifru_flags,
- })
- }
-
- fn create_tap_with_ifreq(ifreq: &mut net_sys::ifreq) -> Result<Tap> {
+ pub fn create_tap_with_ifreq(ifreq: &mut net_sys::ifreq) -> Result<Tap> {
// Open calls are safe because we give a constant nul-terminated
// string and verify the result.
let fd = unsafe {
@@ -230,11 +203,24 @@ impl Tap {
}
pub trait TapT: FileReadWriteVolatile + Read + Write + AsRawDescriptor + Send + Sized {
- /// Create a new tap interface. Set the `vnet_hdr` flag to true to allow offloading on this tap,
+ /// Create a new tap interface named `name`, or open it if it already exists with the same
+ /// parameters.
+ ///
+ /// Set the `vnet_hdr` flag to true to allow offloading on this tap, which will add an extra 12
+ /// byte virtio net header to incoming frames. Offloading cannot be used if `vnet_hdr` is false.
+ /// Set 'multi_vq' to true, if tap have multi virt queue pairs
+ fn new_with_name(name: &[u8], vnet_hdr: bool, multi_vq: bool) -> Result<Self>;
+
+ /// Create a new tap interface.
+ ///
+ /// Set the `vnet_hdr` flag to true to allow offloading on this tap,
/// which will add an extra 12 byte virtio net header to incoming frames. Offloading cannot
/// be used if `vnet_hdr` is false.
- /// set 'multi_vq' to ture, if tap have multi virt queue pairs
- fn new(vnet_hdr: bool, multi_vq: bool) -> Result<Self>;
+ /// Set 'multi_vq' to true, if tap have multi virt queue pairs
+ fn new(vnet_hdr: bool, multi_vq: bool) -> Result<Self> {
+ const TUNTAP_DEV_FORMAT: &[u8] = b"vmtap%d";
+ Self::new_with_name(TUNTAP_DEV_FORMAT, vnet_hdr, multi_vq)
+ }
/// Change the origin tap into multiqueue taps, this means create other taps based on the
/// origin tap.
@@ -252,6 +238,12 @@ pub trait TapT: FileReadWriteVolatile + Read + Write + AsRawDescriptor + Send +
/// Set the netmask for the subnet that the tap interface will exist on.
fn set_netmask(&self, netmask: net::Ipv4Addr) -> Result<()>;
+ /// Get the MTU for the tap interface.
+ fn mtu(&self) -> Result<u16>;
+
+ /// Set the MTU for the tap interface.
+ fn set_mtu(&self, mtu: u16) -> Result<()>;
+
/// Get the mac address for the tap interface.
fn mac_address(&self) -> Result<MacAddress>;
@@ -270,29 +262,35 @@ pub trait TapT: FileReadWriteVolatile + Read + Write + AsRawDescriptor + Send +
fn get_ifreq(&self) -> net_sys::ifreq;
/// Get the interface flags
- fn if_flags(&self) -> u32;
+ fn if_flags(&self) -> i32;
+
+ /// Try to clone
+ fn try_clone(&self) -> Result<Self>;
+
+ /// Convert raw descriptor to TapT.
+ unsafe fn from_raw_descriptor(descriptor: RawDescriptor) -> Result<Self>;
}
impl TapT for Tap {
- fn new(vnet_hdr: bool, multi_vq: bool) -> Result<Tap> {
- const TUNTAP_DEV_FORMAT: &[u8; 8usize] = b"vmtap%d\0";
-
+ fn new_with_name(name: &[u8], vnet_hdr: bool, multi_vq: bool) -> Result<Tap> {
// This is pretty messy because of the unions used by ifreq. Since we
// don't call as_mut on the same union field more than once, this block
// is safe.
let mut ifreq: net_sys::ifreq = Default::default();
unsafe {
let ifrn_name = ifreq.ifr_ifrn.ifrn_name.as_mut();
- let name_slice = &mut ifrn_name[..TUNTAP_DEV_FORMAT.len()];
- for (dst, src) in name_slice.iter_mut().zip(TUNTAP_DEV_FORMAT.iter()) {
+ for (dst, src) in ifrn_name
+ .iter_mut()
+ // Add a zero terminator to the source string.
+ .zip(name.iter().chain(std::iter::once(&0)))
+ {
*dst = *src as c_char;
}
- ifreq.ifr_ifru.ifru_flags = (net_sys::IFF_TAP
- | net_sys::IFF_NO_PI
- | if vnet_hdr { net_sys::IFF_VNET_HDR } else { 0 })
- as c_short;
+ ifreq.ifr_ifru.ifru_flags =
+ (libc::IFF_TAP | libc::IFF_NO_PI | if vnet_hdr { libc::IFF_VNET_HDR } else { 0 })
+ as c_short;
if multi_vq {
- ifreq.ifr_ifru.ifru_flags |= net_sys::IFF_MULTI_QUEUE as c_short;
+ ifreq.ifr_ifru.ifru_flags |= libc::IFF_MULTI_QUEUE as c_short;
}
}
@@ -395,6 +393,38 @@ impl TapT for Tap {
Ok(())
}
+ fn mtu(&self) -> Result<u16> {
+ let sock = create_socket()?;
+ let mut ifreq = self.get_ifreq();
+
+ // ioctl is safe. Called with a valid sock fd, and we check the return.
+ let ret = unsafe {
+ ioctl_with_mut_ref(&sock, net_sys::sockios::SIOCGIFMTU as IoctlNr, &mut ifreq)
+ };
+ if ret < 0 {
+ return Err(Error::IoctlError(SysError::last()));
+ }
+
+ // We only access one field of the ifru union, hence this is safe.
+ let mtu = unsafe { ifreq.ifr_ifru.ifru_mtu } as u16;
+ Ok(mtu)
+ }
+
+ fn set_mtu(&self, mtu: u16) -> Result<()> {
+ let sock = create_socket()?;
+
+ let mut ifreq = self.get_ifreq();
+ ifreq.ifr_ifru.ifru_mtu = i32::from(mtu);
+
+ // ioctl is safe. Called with a valid sock fd, and we check the return.
+ let ret = unsafe { ioctl_with_ref(&sock, net_sys::sockios::SIOCSIFMTU as IoctlNr, &ifreq) };
+ if ret < 0 {
+ return Err(Error::IoctlError(SysError::last()));
+ }
+
+ Ok(())
+ }
+
fn mac_address(&self) -> Result<MacAddress> {
let sock = create_socket()?;
let mut ifreq = self.get_ifreq();
@@ -455,7 +485,7 @@ impl TapT for Tap {
let mut ifreq = self.get_ifreq();
ifreq.ifr_ifru.ifru_flags =
- (net_sys::net_device_flags_IFF_UP | net_sys::net_device_flags_IFF_RUNNING) as i16;
+ (net_sys::net_device_flags::IFF_UP | net_sys::net_device_flags::IFF_RUNNING).0 as i16;
// ioctl is safe. Called with a valid sock fd, and we check the return.
let ret =
@@ -494,8 +524,40 @@ impl TapT for Tap {
ifreq
}
- fn if_flags(&self) -> u32 {
- self.if_flags as u32
+ fn if_flags(&self) -> i32 {
+ self.if_flags.into()
+ }
+
+ fn try_clone(&self) -> Result<Tap> {
+ self.tap_file
+ .try_clone()
+ .map(|tap_file| Tap {
+ tap_file,
+ if_name: self.if_name,
+ if_flags: self.if_flags,
+ })
+ .map_err(SysError::from)
+ .map_err(Error::CloneTap)
+ }
+
+ /// # Safety: fd is a valid FD and ownership of it is transferred when
+ /// calling this function.
+ unsafe fn from_raw_descriptor(fd: RawDescriptor) -> Result<Tap> {
+ let tap_file = File::from_raw_descriptor(fd);
+
+ // Get the interface name since we will need it for some ioctls.
+ let mut ifreq: net_sys::ifreq = Default::default();
+ let ret = ioctl_with_mut_ref(&tap_file, net_sys::TUNGETIFF(), &mut ifreq);
+
+ if ret < 0 {
+ return Err(Error::IoctlError(SysError::last()));
+ }
+
+ Ok(Tap {
+ tap_file,
+ if_name: ifreq.ifr_ifrn.ifrn_name,
+ if_flags: ifreq.ifr_ifru.ifru_flags,
+ })
}
}
@@ -507,7 +569,7 @@ impl Read for Tap {
impl Write for Tap {
fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
- self.tap_file.write(&buf)
+ self.tap_file.write(buf)
}
fn flush(&mut self) -> IoResult<()> {
@@ -527,6 +589,8 @@ impl AsRawDescriptor for Tap {
}
}
+impl IntoAsync for Tap {}
+
volatile_impl!(Tap);
pub mod fakes {
@@ -541,7 +605,7 @@ pub mod fakes {
}
impl TapT for FakeTap {
- fn new(_: bool, _: bool) -> Result<FakeTap> {
+ fn new_with_name(_: &[u8], _: bool, _: bool) -> Result<FakeTap> {
Ok(FakeTap {
tap_file: OpenOptions::new()
.read(true)
@@ -572,6 +636,14 @@ pub mod fakes {
Ok(())
}
+ fn mtu(&self) -> Result<u16> {
+ Ok(1500)
+ }
+
+ fn set_mtu(&self, _: u16) -> Result<()> {
+ Ok(())
+ }
+
fn mac_address(&self) -> Result<MacAddress> {
Ok("01:02:03:04:05:06".parse().unwrap())
}
@@ -597,8 +669,17 @@ pub mod fakes {
ifreq
}
- fn if_flags(&self) -> u32 {
- net_sys::IFF_TAP
+ fn if_flags(&self) -> i32 {
+ libc::IFF_TAP
+ }
+
+ fn try_clone(&self) -> Result<Self> {
+ unimplemented!()
+ }
+
+ /// # Safety: panics on call / does nothing.
+ unsafe fn from_raw_descriptor(_descriptor: RawDescriptor) -> Result<Self> {
+ unimplemented!()
}
}
diff --git a/patches/Android.bp.patch b/patches/Android.bp.patch
new file mode 100644
index 000000000..bce551a74
--- /dev/null
+++ b/patches/Android.bp.patch
@@ -0,0 +1,38 @@
+diff --git b/Android.bp a/Android.bp
+index d3d46248..bc1191a5 100644
+--- b/Android.bp
++++ a/Android.bp
+@@ -36,6 +36,7 @@ rust_binary {
+ name: "crosvm",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
++ prefer_rlib: true,
+ crate_name: "crosvm",
+ cargo_env_compat: true,
+ srcs: ["src/main.rs"],
+@@ -94,6 +94,9 @@ rust_binary {
+ "gpu",
+ ],
+ },
++ darwin: {
++ enabled: false,
++ },
+ host_linux: {
+ features: [
+ "gdb",
+@@ -107,6 +108,15 @@ rust_binary {
+ "libthiserror",
+ ],
+ },
++ linux_bionic_arm64: {
++ relative_install_path: "aarch64-linux-bionic",
++ },
++ linux_glibc_x86_64: {
++ relative_install_path: "x86_64-linux-gnu",
++ },
++ linux_musl_x86_64: {
++ relative_install_path: "x86_64-linux-musl",
++ },
+ },
+ ld_flags: [
+ "-Wl,--rpath,\\$$ORIGIN",
diff --git a/power_monitor/Android.bp b/power_monitor/Android.bp
index 681d872e4..0f733dc40 100644
--- a/power_monitor/Android.bp
+++ b/power_monitor/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -15,75 +15,13 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "power_monitor",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
+ "libthiserror",
],
+ proc_macros: ["libremain"],
}
-
-rust_defaults {
- name: "power_monitor_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "power_monitor",
- srcs: ["src/lib.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libbase_rust",
- ],
-}
-
-rust_test_host {
- name: "power_monitor_host_test_src_lib",
- defaults: ["power_monitor_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "power_monitor_device_test_src_lib",
- defaults: ["power_monitor_defaults"],
-}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/power_monitor/Cargo.toml b/power_monitor/Cargo.toml
index de1fc069b..206470b58 100644
--- a/power_monitor/Cargo.toml
+++ b/power_monitor/Cargo.toml
@@ -2,15 +2,17 @@
name = "power_monitor"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[features]
powerd = ["dbus", "protobuf", "protoc-rust"]
[dependencies]
base = { path = "../base" }
-dbus = { version = "0.6.1", optional = true }
+dbus = { version = "0.9", optional = true }
protobuf = { version = "2.8.1", optional = true }
+remain = "0.2"
+thiserror = "1.0.20"
[build-dependencies]
protoc-rust = { version = "2.8.1", optional = true }
diff --git a/power_monitor/build.rs b/power_monitor/build.rs
index b925c2879..174389d74 100644
--- a/power_monitor/build.rs
+++ b/power_monitor/build.rs
@@ -31,13 +31,12 @@ fn main() {
let input_files = [power_manager_dir.join("power_supply_properties.proto")];
let include_dirs = [power_manager_dir];
- protoc_rust::run(protoc_rust::Args {
- out_dir: out_dir.as_os_str().to_str().unwrap(),
- input: &paths_to_strs(&input_files),
- includes: &paths_to_strs(&include_dirs),
- customize: Default::default(),
- })
- .expect("protoc");
+ protoc_rust::Codegen::new()
+ .inputs(&paths_to_strs(&input_files))
+ .includes(&paths_to_strs(&include_dirs))
+ .out_dir(out_dir.as_os_str().to_str().unwrap())
+ .run()
+ .expect("protoc");
let mut path_include_mods = String::new();
for input_file in input_files.iter() {
diff --git a/power_monitor/src/powerd/mod.rs b/power_monitor/src/powerd/mod.rs
index ea8eafc7c..ce008d91d 100644
--- a/power_monitor/src/powerd/mod.rs
+++ b/power_monitor/src/powerd/mod.rs
@@ -9,13 +9,14 @@ use proto::system_api::power_supply_properties::{
PowerSupplyProperties, PowerSupplyProperties_BatteryState, PowerSupplyProperties_ExternalPower,
};
-use dbus::{BusType, Connection, ConnectionItem, WatchEvent};
+use dbus::ffidisp::{BusType, Connection, ConnectionItem, WatchEvent};
use protobuf::error::ProtobufError;
use protobuf::Message;
+use remain::sorted;
+use thiserror::Error;
use std::error::Error;
-use std::fmt;
use std::os::unix::io::RawFd;
// Interface name from power_manager/dbus_bindings/org.chromium.PowerManager.xml.
@@ -67,30 +68,21 @@ impl From<PowerSupplyProperties> for PowerData {
}
}
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum DBusMonitorError {
- DBusConnect(dbus::Error),
+ #[error("failed to convert protobuf message: {0}")]
+ ConvertProtobuf(ProtobufError),
+ #[error("failed to add D-Bus match rule: {0}")]
DBusAddMatch(dbus::Error),
- NoDBusFd,
- MultipleDBusFd,
+ #[error("failed to connect to D-Bus: {0}")]
+ DBusConnect(dbus::Error),
+ #[error("failed to read D-Bus message: {0}")]
DBusRead(dbus::arg::TypeMismatchError),
- ConvertProtobuf(ProtobufError),
-}
-
-impl Error for DBusMonitorError {}
-
-impl fmt::Display for DBusMonitorError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::DBusMonitorError::*;
- match self {
- DBusConnect(e) => write!(f, "failed to connect to D-Bus: {}", e),
- DBusAddMatch(e) => write!(f, "failed to add D-Bus match rule: {}", e),
- NoDBusFd => write!(f, "no D-Bus fd"),
- MultipleDBusFd => write!(f, "multiple D-Bus fds"),
- DBusRead(e) => write!(f, "failed to read D-Bus message: {}", e),
- ConvertProtobuf(e) => write!(f, "failed to convert protobuf message: {}", e),
- }
- }
+ #[error("multiple D-Bus fds")]
+ MultipleDBusFd,
+ #[error("no D-Bus fd")]
+ NoDBusFd,
}
pub struct DBusMonitor {
@@ -152,14 +144,7 @@ impl PowerMonitor for DBusMonitor {
}
};
- let interface_name = match interface.as_cstr().to_str() {
- Ok(s) => s,
- Err(_) => {
- return last;
- }
- };
-
- if interface_name != POWER_INTERFACE_NAME {
+ if &*interface != POWER_INTERFACE_NAME {
return last;
}
@@ -170,14 +155,7 @@ impl PowerMonitor for DBusMonitor {
}
};
- let member_name = match member.as_cstr().to_str() {
- Ok(s) => s,
- Err(_) => {
- return last;
- }
- };
-
- if member_name != POLL_SIGNAL_NAME {
+ if &*member != POLL_SIGNAL_NAME {
return last;
}
diff --git a/protos/Android.bp b/protos/Android.bp
index ca1aa7288..913d54bc6 100644
--- a/protos/Android.bp
+++ b/protos/Android.bp
@@ -1,4 +1,6 @@
-// Only enable composite-disk and include cdisk_spec.rs for now.
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
+
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
@@ -11,17 +13,16 @@ package {
rust_library {
name: "libprotos",
defaults: ["crosvm_defaults"],
- // has rustc warnings
host_supported: true,
crate_name: "protos",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- features: [
- "composite-disk",
- ],
- edition: "2018",
+ edition: "2021",
+ features: ["composite-disk"],
rustlibs: [
- "libprotobuf",
"libcdisk_spec_proto",
+ "libprotobuf",
],
}
@@ -44,21 +45,3 @@ rust_protobuf {
source_stem: "plugin",
host_supported: true,
}
-
-// dependent_library ["feature_list"]
-// cfg-if-1.0.0
-// either-1.6.1 "default,use_std"
-// getrandom-0.2.2 "std"
-// libc-0.2.93 "default,std"
-// log-0.4.14
-// ppv-lite86-0.2.10 "simd,std"
-// protobuf-2.22.1
-// protobuf-codegen-2.22.1
-// protoc-2.22.1
-// protoc-rust-2.22.1
-// rand-0.8.3 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.0 "std"
-// rand_core-0.6.2 "alloc,getrandom,std"
-// remove_dir_all-0.5.3
-// tempfile-3.2.0
-// which-4.1.0
diff --git a/protos/Cargo.toml b/protos/Cargo.toml
index 98cd4ae2d..736d7555e 100644
--- a/protos/Cargo.toml
+++ b/protos/Cargo.toml
@@ -2,12 +2,11 @@
name = "protos"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[features]
plugin = ["kvm_sys"]
composite-disk = []
-trunks = []
[dependencies]
kvm_sys = { path = "../kvm_sys", optional = true }
diff --git a/protos/build.rs b/protos/build.rs
index 2e870b48f..5aa3ca255 100644
--- a/protos/build.rs
+++ b/protos/build.rs
@@ -29,15 +29,7 @@ struct ExternalProto {
// Rustfmt bug: https://github.com/rust-lang/rustfmt/issues/3498
#[rustfmt::skip]
-static EXTERNAL_PROTOS: &[ExternalProto] = &[
- #[cfg(feature = "trunks")]
- ExternalProto {
- dir_relative_to_sysroot: "usr/include/chromeos/dbus/trunks",
- dir_relative_to_us: "../../../platform2/trunks",
- proto_file_name: "interface.proto",
- module: "trunks",
- },
-];
+static EXTERNAL_PROTOS: &[ExternalProto] = &[];
struct LocalProto {
// Corresponding to the input file src/$module.proto.
@@ -92,12 +84,12 @@ fn protoc<P: AsRef<Path>>(module: &str, input_path: P, mut out: &File) -> Result
fs::create_dir_all(&out_dir)?;
// Invoke protobuf compiler.
- protoc_rust::run(protoc_rust::Args {
- out_dir: &out_dir,
- includes: &[input_dir.as_os_str().to_str().unwrap()],
- input: &[input_path.as_os_str().to_str().unwrap()],
- ..Default::default()
- })?;
+ protoc_rust::Codegen::new()
+ .input(input_path.as_os_str().to_str().unwrap())
+ .include(input_dir.as_os_str().to_str().unwrap())
+ .out_dir(&out_dir)
+ .run()
+ .expect("protoc");
// Write out a `mod` that refers to the generated module.
//
diff --git a/protos/cargo2android.json b/protos/cargo2android.json
new file mode 100644
index 000000000..8dac0dcfe
--- /dev/null
+++ b/protos/cargo2android.json
@@ -0,0 +1,9 @@
+{
+ "add-toplevel-block": "cargo2android_protobuf.bp",
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "patch": "patches/Android.bp.patch",
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/protos/cargo2android_protobuf.bp b/protos/cargo2android_protobuf.bp
new file mode 100644
index 000000000..a23e06175
--- /dev/null
+++ b/protos/cargo2android_protobuf.bp
@@ -0,0 +1,19 @@
+rust_protobuf {
+ name: "libcdisk_spec_proto",
+ crate_name: "cdisk_spec_proto",
+ protos: ["src/cdisk_spec.proto"],
+ source_stem: "cdisk_spec",
+ host_supported: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
+}
+
+rust_protobuf {
+ name: "libcrosvm_plugin_proto",
+ crate_name: "crosvm_plugin_proto",
+ protos: ["src/plugin.proto"],
+ source_stem: "plugin",
+ host_supported: true,
+} \ No newline at end of file
diff --git a/protos/patches/Android.bp.patch b/protos/patches/Android.bp.patch
new file mode 100644
index 000000000..523f551d7
--- /dev/null
+++ b/protos/patches/Android.bp.patch
@@ -0,0 +1,14 @@
+diff --git a/protos/Android.bp b/protos/Android.bp
+index eaf06cf1..32a8f81b 100644
+--- a/protos/Android.bp
++++ b/protos/Android.bp
+@@ -17,7 +17,9 @@ rust_library {
+ crate_name: "protos",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
++ features: ["composite-disk"],
+ rustlibs: [
++ "libcdisk_spec_proto",
+ "libprotobuf",
+ ],
+ }
diff --git a/protos/src/lib.rs b/protos/src/lib.rs
index 785a5e3c0..5c2a751c1 100644
--- a/protos/src/lib.rs
+++ b/protos/src/lib.rs
@@ -5,8 +5,5 @@
#[cfg(feature = "plugin")]
pub use crosvm_plugin_proto::plugin;
-#[cfg(feature = "trunks")]
-pub mod trunks;
-
#[cfg(feature = "composite-disk")]
pub use cdisk_spec_proto::cdisk_spec;
diff --git a/protos/tests/trunks.rs b/protos/tests/trunks.rs
deleted file mode 100644
index 39723ae74..000000000
--- a/protos/tests/trunks.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#![cfg(feature = "trunks")]
-
-mod common;
-
-use crate::common::test_round_trip;
-use protos::trunks::{SendCommandRequest, SendCommandResponse};
-
-#[test]
-fn send_command_request() {
- let mut request = SendCommandRequest::new();
- request.set_command(b"...".to_vec());
- test_round_trip(request);
-}
-
-#[test]
-fn send_command_response() {
- let mut response = SendCommandResponse::new();
- response.set_response(b"...".to_vec());
- test_round_trip(response);
-}
diff --git a/qcow_utils/Android.bp b/qcow_utils/Android.bp
index f8d68d31d..25e7b3e3d 100644
--- a/qcow_utils/Android.bp
+++ b/qcow_utils/Android.bp
@@ -1,4 +1 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Do not modify this file as changes will be overridden on upgrade.
-
// Feature is disabled for Android
diff --git a/qcow_utils/Cargo.toml b/qcow_utils/Cargo.toml
index d47b18761..38cad0712 100644
--- a/qcow_utils/Cargo.toml
+++ b/qcow_utils/Cargo.toml
@@ -2,18 +2,13 @@
name = "qcow_utils"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[lib]
path = "src/qcow_utils.rs"
crate-type = ["cdylib"]
-[[bin]]
-name = "qcow_img"
-path = "src/qcow_img.rs"
-
[dependencies]
-getopts = "*"
libc = "*"
disk = { path = "../disk" }
base = { path = "../base" }
diff --git a/qcow_utils/cargo2android.json b/qcow_utils/cargo2android.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/qcow_utils/cargo2android.json
@@ -0,0 +1 @@
+{} \ No newline at end of file
diff --git a/qcow_utils/src/qcow_img.rs b/qcow_utils/src/qcow_img.rs
deleted file mode 100644
index 3fdd26973..000000000
--- a/qcow_utils/src/qcow_img.rs
+++ /dev/null
@@ -1,322 +0,0 @@
-// Copyright 2018 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use std::fs::OpenOptions;
-use std::io::{Read, Write};
-
-use getopts::Options;
-
-use base::WriteZeroes;
-use disk::QcowFile;
-
-fn show_usage(program_name: &str) {
- println!("Usage: {} [subcommand] <subcommand args>", program_name);
- println!("\nSubcommands:");
- println!(
- "{} header <file name> - Show the qcow2 header for a file.",
- program_name
- );
- println!(
- "{} l1_table <file name> - Show the L1 table entries for a file.",
- program_name
- );
- println!(
- "{} l22table <file name> <l1 index> - Show the L2 table pointed to by the nth L1 entry.",
- program_name
- );
- println!(
- "{} ref_table <file name> - Show the refblock table for the file.",
- program_name
- );
- println!(
- "{} ref_block <file_name> <table index> - Show the nth reblock in the file.",
- program_name
- );
- println!(
- "{} dd <file_name> <source_file> - Write bytes from the raw source_file to the file.",
- program_name
- );
- println!(
- "{} convert <src_file> <dst_file> - Convert from src_file to dst_file.",
- program_name
- );
-}
-
-fn main() -> std::result::Result<(), ()> {
- let args: Vec<String> = std::env::args().collect();
- let opts = Options::new();
-
- let matches = match opts.parse(&args[1..]) {
- Ok(m) => m,
- Err(f) => panic!(f.to_string()),
- };
-
- if matches.free.len() < 2 {
- println!("Must specify the subcommand and the QCOW file to operate on.");
- show_usage(&args[0]);
- return Err(());
- }
-
- match matches.free[0].as_ref() {
- "header" => show_header(&matches.free[1]),
- "help" => {
- show_usage(&args[0]);
- Ok(())
- }
- "l1_table" => show_l1_table(&matches.free[1]),
- "l2_table" => {
- if matches.free.len() < 2 {
- println!("Filename and table index are required.");
- show_usage(&args[0]);
- return Err(());
- }
- show_l2_table(&matches.free[1], matches.free[2].parse().unwrap())
- }
- "ref_table" => show_ref_table(&matches.free[1]),
- "ref_block" => {
- if matches.free.len() < 2 {
- println!("Filename and block index are required.");
- show_usage(&args[0]);
- return Err(());
- }
- show_ref_block(&matches.free[1], matches.free[2].parse().unwrap())
- }
- "dd" => {
- if matches.free.len() < 2 {
- println!("Qcow and source file are required.");
- show_usage(&args[0]);
- return Err(());
- }
- let count = if matches.free.len() > 3 {
- Some(matches.free[3].parse().unwrap())
- } else {
- None
- };
- dd(&matches.free[1], &matches.free[2], count)
- }
- "convert" => {
- if matches.free.len() < 2 {
- println!("Source and destination files are required.");
- show_usage(&args[0]);
- return Err(());
- }
- convert(&matches.free[1], &matches.free[2])
- }
- c => {
- println!("invalid subcommand: {:?}", c);
- Err(())
- }
- }
-}
-
-fn show_header(file_path: &str) -> std::result::Result<(), ()> {
- let file = match OpenOptions::new().read(true).open(file_path) {
- Ok(f) => f,
- Err(_) => {
- println!("Failed to open {}", file_path);
- return Err(());
- }
- };
-
- let qcow_file = QcowFile::from(file).map_err(|_| ())?;
- let header = qcow_file.header();
-
- println!("magic {:x}", header.magic);
- println!("version {:x}", header.version);
- println!("backing_file_offset {:x}", header.backing_file_offset);
- println!("backing_file_size {:x}", header.backing_file_size);
- println!("cluster_bits {:x}", header.cluster_bits);
- println!("size {:x}", header.size);
- println!("crypt_method {:x}", header.crypt_method);
- println!("l1_size {:x}", header.l1_size);
- println!("l1_table_offset {:x}", header.l1_table_offset);
- println!("refcount_table_offset {:x}", header.refcount_table_offset);
- println!(
- "refcount_table_clusters {:x}",
- header.refcount_table_clusters
- );
- println!("nb_snapshots {:x}", header.nb_snapshots);
- println!("snapshots_offset {:x}", header.snapshots_offset);
- println!("incompatible_features {:x}", header.incompatible_features);
- println!("compatible_features {:x}", header.compatible_features);
- println!("autoclear_features {:x}", header.autoclear_features);
- println!("refcount_order {:x}", header.refcount_order);
- println!("header_size {:x}", header.header_size);
- Ok(())
-}
-
-fn show_l1_table(file_path: &str) -> std::result::Result<(), ()> {
- let file = match OpenOptions::new().read(true).open(file_path) {
- Ok(f) => f,
- Err(_) => {
- println!("Failed to open {}", file_path);
- return Err(());
- }
- };
-
- let qcow_file = QcowFile::from(file).map_err(|_| ())?;
- let l1_table = qcow_file.l1_table();
-
- for (i, l2_offset) in l1_table.iter().enumerate() {
- println!("{}: {:x}", i, l2_offset);
- }
-
- Ok(())
-}
-
-fn show_l2_table(file_path: &str, index: usize) -> std::result::Result<(), ()> {
- let file = match OpenOptions::new().read(true).open(file_path) {
- Ok(f) => f,
- Err(_) => {
- println!("Failed to open {}", file_path);
- return Err(());
- }
- };
-
- let mut qcow_file = QcowFile::from(file).map_err(|_| ())?;
- let l2_table = qcow_file.l2_table(index).unwrap();
-
- if let Some(cluster_addrs) = l2_table {
- for (i, addr) in cluster_addrs.iter().enumerate() {
- if i % 16 == 0 {
- print!("\n{:x}:", i);
- }
- print!(" {:x}", addr);
- }
- }
-
- Ok(())
-}
-
-fn show_ref_table(file_path: &str) -> std::result::Result<(), ()> {
- let file = match OpenOptions::new().read(true).open(file_path) {
- Ok(f) => f,
- Err(_) => {
- println!("Failed to open {}", file_path);
- return Err(());
- }
- };
-
- let qcow_file = QcowFile::from(file).map_err(|_| ())?;
- let ref_table = qcow_file.ref_table();
-
- for (i, block_offset) in ref_table.iter().enumerate() {
- println!("{}: {:x}", i, block_offset);
- }
-
- Ok(())
-}
-
-fn show_ref_block(file_path: &str, index: usize) -> std::result::Result<(), ()> {
- let file = match OpenOptions::new().read(true).open(file_path) {
- Ok(f) => f,
- Err(_) => {
- println!("Failed to open {}", file_path);
- return Err(());
- }
- };
-
- let mut qcow_file = QcowFile::from(file).map_err(|_| ())?;
- let ref_table = qcow_file.refcount_block(index).unwrap();
-
- if let Some(counts) = ref_table {
- for (i, count) in counts.iter().enumerate() {
- if i % 16 == 0 {
- print!("\n{:x}:", i);
- }
- print!(" {:x}", count);
- }
- }
-
- Ok(())
-}
-
-// Transfers from a raw file specifiec in `source_path` to the qcow file specified in `file_path`.
-fn dd(file_path: &str, source_path: &str, count: Option<usize>) -> std::result::Result<(), ()> {
- let file = match OpenOptions::new().read(true).write(true).open(file_path) {
- Ok(f) => f,
- Err(_) => {
- println!("Failed to open {}", file_path);
- return Err(());
- }
- };
-
- let mut qcow_file = QcowFile::from(file).map_err(|_| ())?;
-
- let mut src_file = match OpenOptions::new().read(true).open(source_path) {
- Ok(f) => f,
- Err(_) => {
- println!("Failed to open {}", file_path);
- return Err(());
- }
- };
-
- let mut read_count = 0;
- const CHUNK_SIZE: usize = 65536;
- let mut buf = [0; CHUNK_SIZE];
- loop {
- let this_count = if let Some(count) = count {
- std::cmp::min(CHUNK_SIZE, count - read_count)
- } else {
- CHUNK_SIZE
- };
- let nread = src_file.read(&mut buf[..this_count]).map_err(|_| ())?;
- // If this block is all zeros, then use write_zeros so the output file is sparse.
- if buf.iter().all(|b| *b == 0) {
- qcow_file.write_zeroes_all(CHUNK_SIZE).map_err(|_| ())?;
- } else {
- qcow_file.write(&buf).map_err(|_| ())?;
- }
- read_count = read_count + nread;
- if nread == 0 || Some(read_count) == count {
- break;
- }
- }
-
- println!("wrote {} bytes", read_count);
-
- Ok(())
-}
-
-// Reads the file at `src_path` and writes it to `dst_path`.
-// The output format is detected based on the `dst_path` file extension.
-fn convert(src_path: &str, dst_path: &str) -> std::result::Result<(), ()> {
- let src_file = match OpenOptions::new().read(true).open(src_path) {
- Ok(f) => f,
- Err(_) => {
- println!("Failed to open source file {}", src_path);
- return Err(());
- }
- };
-
- let dst_file = match OpenOptions::new()
- .read(true)
- .write(true)
- .create(true)
- .open(dst_path)
- {
- Ok(f) => f,
- Err(_) => {
- println!("Failed to open destination file {}", dst_path);
- return Err(());
- }
- };
-
- let dst_type = if dst_path.ends_with("qcow2") {
- disk::ImageType::Qcow2
- } else {
- disk::ImageType::Raw
- };
-
- match disk::convert(src_file, dst_file, dst_type) {
- Ok(_) => {
- println!("Converted {} to {}", src_path, dst_path);
- Ok(())
- }
- Err(_) => {
- println!("Failed to copy from {} to {}", src_path, dst_path);
- Err(())
- }
- }
-}
diff --git a/qcow_utils/src/qcow_utils.rs b/qcow_utils/src/qcow_utils.rs
index a6c96cc6b..a3eb3ec35 100644
--- a/qcow_utils/src/qcow_utils.rs
+++ b/qcow_utils/src/qcow_utils.rs
@@ -10,7 +10,7 @@ use std::fs::OpenOptions;
use std::os::raw::{c_char, c_int};
use base::{flock, FlockOperation};
-use disk::{DiskFile, ImageType, QcowFile};
+use disk::{self, DiskFile, ImageType, QcowFile};
#[no_mangle]
pub unsafe extern "C" fn create_qcow_with_size(path: *const c_char, virtual_size: u64) -> c_int {
@@ -60,7 +60,7 @@ pub unsafe extern "C" fn expand_disk_image(path: *const c_char, virtual_size: u6
};
// Lock the disk image to prevent other processes from using it.
- if let Err(_) = flock(&raw_image, FlockOperation::LockExclusive, true) {
+ if flock(&raw_image, FlockOperation::LockExclusive, true).is_err() {
return -EIO;
}
@@ -71,7 +71,7 @@ pub unsafe extern "C" fn expand_disk_image(path: *const c_char, virtual_size: u6
let disk_image: Box<dyn DiskFile> = match image_type {
ImageType::Raw => Box::new(raw_image),
- ImageType::Qcow2 => match QcowFile::from(raw_image) {
+ ImageType::Qcow2 => match QcowFile::from(raw_image, disk::MAX_NESTING_DEPTH) {
Ok(f) => Box::new(f),
Err(_) => return -EINVAL,
},
diff --git a/rand_ish/src/lib.rs b/rand_ish/src/lib.rs
deleted file mode 100644
index c7b0ff494..000000000
--- a/rand_ish/src/lib.rs
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use std::fs::File;
-use std::io::{self, Read};
-
-/// A simple prng based on a Linear congruential generator
-/// https://en.wikipedia.org/wiki/Linear_congruential_generator
-pub struct SimpleRng {
- seed: u64,
-}
-
-impl SimpleRng {
- /// Create a new SimpleRng
- pub fn new(seed: u64) -> SimpleRng {
- SimpleRng { seed }
- }
-
- /// Generate random u64
- pub fn rng(&mut self) -> u64 {
- // a simple Linear congruential generator
- let a: u64 = 6364136223846793005;
- let c: u64 = 1442695040888963407;
- self.seed = a.wrapping_mul(self.seed).wrapping_add(c);
- self.seed
- }
-
- /// Generate a random alphanumeric string.
- pub fn str(&mut self, len: usize) -> String {
- self.filter_map(|v| uniform_sample_ascii_alphanumeric(v as u8))
- .take(len)
- .collect()
- }
-}
-
-impl Iterator for SimpleRng {
- type Item = u64;
-
- fn next(&mut self) -> Option<Self::Item> {
- Some(self.rng())
- }
-}
-
-// Uniformly samples the ASCII alphanumeric characters given a random variable. If an `Err` is
-// passed in, the error is returned as `Some(Err(...))`. If the the random variable can not be used
-// to uniformly sample, `None` is returned.
-fn uniform_sample_ascii_alphanumeric(b: u8) -> Option<char> {
- const ASCII_CHARS: &[u8] = b"\
- ABCDEFGHIJKLMNOPQRSTUVWXYZ\
- abcdefghijklmnopqrstuvwxyz\
- 0123456789";
- let char_index = b as usize;
- // Throw away numbers that would cause sampling bias.
- if char_index >= ASCII_CHARS.len() * 4 {
- None
- } else {
- Some(ASCII_CHARS[char_index % ASCII_CHARS.len()] as char)
- }
-}
-
-/// Samples `/dev/urandom` and generates a random ASCII string of length `len`
-pub fn urandom_str(len: usize) -> io::Result<String> {
- File::open("/dev/urandom")?
- .bytes()
- .filter_map(|b| b.map(uniform_sample_ascii_alphanumeric).transpose())
- .take(len)
- .collect::<io::Result<String>>()
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn check_100() {
- for i in 0..100 {
- let s = urandom_str(i).unwrap();
- assert_eq!(s.len(), i);
- assert!(s.is_ascii());
- assert!(!s.contains(' '));
- assert!(!s.contains(|c: char| !c.is_ascii_alphanumeric()));
- }
- }
-}
diff --git a/resources/Android.bp b/resources/Android.bp
index 5bb5b5cb7..1c1531cd4 100644
--- a/resources/Android.bp
+++ b/resources/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -15,79 +15,38 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "resources",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
"liblibc",
"libserde",
+ "libthiserror",
],
+ proc_macros: ["libremain"],
}
-rust_defaults {
- name: "resources_defaults",
+rust_test {
+ name: "resources_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "resources",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2021",
rustlibs: [
"libbase_rust",
"liblibc",
"libserde",
+ "libthiserror",
],
+ proc_macros: ["libremain"],
}
-
-rust_test_host {
- name: "resources_host_test_src_lib",
- defaults: ["resources_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "resources_device_test_src_lib",
- defaults: ["resources_defaults"],
-}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/resources/Cargo.toml b/resources/Cargo.toml
index ca1d4b882..02fc164a3 100644
--- a/resources/Cargo.toml
+++ b/resources/Cargo.toml
@@ -2,9 +2,11 @@
name = "resources"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
libc = "*"
base = { path = "../base" }
serde = { version = "1", features = ["derive"] }
+remain = "*"
+thiserror = "*"
diff --git a/resources/src/address_allocator.rs b/resources/src/address_allocator.rs
index 9d8b28d7a..6b15ffd28 100644
--- a/resources/src/address_allocator.rs
+++ b/resources/src/address_allocator.rs
@@ -4,6 +4,7 @@
use std::cmp;
use std::collections::{BTreeSet, HashMap};
+use std::ops::RangeInclusive;
use crate::{Alloc, Error, Result};
@@ -11,68 +12,98 @@ use crate::{Alloc, Error, Result};
/// Use `AddressAllocator` whenever an address range needs to be allocated to different users.
/// Allocations must be uniquely tagged with an Alloc enum, which can be used for lookup.
/// An human-readable tag String must also be provided for debugging / reference.
-///
-/// # Examples
-///
-/// ```
-/// // Anon is used for brevity. Don't manually instantiate Anon allocs!
-/// # use resources::{Alloc, AddressAllocator};
-/// AddressAllocator::new(0x1000, 0x10000, Some(0x100)).map(|mut pool| {
-/// assert_eq!(pool.allocate(0x110, Alloc::Anon(0), "caps".to_string()), Ok(0x1000));
-/// assert_eq!(pool.allocate(0x100, Alloc::Anon(1), "cache".to_string()), Ok(0x1200));
-/// assert_eq!(pool.allocate(0x100, Alloc::Anon(2), "etc".to_string()), Ok(0x1300));
-/// assert_eq!(pool.get(&Alloc::Anon(1)), Some(&(0x1200, 0x100, "cache".to_string())));
-/// });
-/// ```
#[derive(Debug, Eq, PartialEq)]
pub struct AddressAllocator {
- alignment: u64,
+ /// The list of pools from which address are allocated. The union
+ /// of all regions from |allocs| and |regions| equals the pools.
+ pools: Vec<RangeInclusive<u64>>,
+ min_align: u64,
+ preferred_align: u64,
+ /// The region that is allocated.
allocs: HashMap<Alloc, (u64, u64, String)>,
+ /// The region that is not allocated yet.
regions: BTreeSet<(u64, u64)>,
}
impl AddressAllocator {
/// Creates a new `AddressAllocator` for managing a range of addresses.
- /// Can return `None` if `pool_base` + `pool_size` overflows a u64 or if alignment isn't a power
- /// of two.
+ /// Can return an error if `pool` is empty or if alignment isn't a power of two.
///
- /// * `pool_base` - The starting address of the range to manage.
- /// * `pool_size` - The size of the address range in bytes.
- /// * `align_size` - The minimum size of an address region to align to, defaults to four.
- pub fn new(pool_base: u64, pool_size: u64, align_size: Option<u64>) -> Result<Self> {
- if pool_size == 0 {
+ /// * `pool` - The address range to allocate from.
+ /// * `min_align` - The minimum size of an address region to align to, defaults to four.
+ /// * `preferred_align` - The preferred alignment of an address region, used if possible.
+ ///
+ /// If an allocation cannot be satisfied with the preferred alignment, the minimum alignment
+ /// will be used instead.
+ pub fn new(
+ pool: RangeInclusive<u64>,
+ min_align: Option<u64>,
+ preferred_align: Option<u64>,
+ ) -> Result<Self> {
+ Self::new_from_list(vec![pool], min_align, preferred_align)
+ }
+
+ /// Creates a new `AddressAllocator` for managing a range of addresses.
+ /// Can return `None` if all pools are empty alignment isn't a power of two.
+ ///
+ /// * `pools` - The list of pools to initialize the allocator with.
+ /// * `min_align` - The minimum size of an address region to align to, defaults to four.
+ /// * `preferred_align` - The preferred alignment of an address region, used if possible.
+ ///
+ /// If an allocation cannot be satisfied with the preferred alignment, the minimum alignment
+ /// will be used instead.
+ pub fn new_from_list<T>(
+ pools: T,
+ min_align: Option<u64>,
+ preferred_align: Option<u64>,
+ ) -> Result<Self>
+ where
+ T: IntoIterator<Item = RangeInclusive<u64>>,
+ {
+ let pools: Vec<RangeInclusive<u64>> = pools.into_iter().filter(|p| !p.is_empty()).collect();
+ if pools.is_empty() {
return Err(Error::PoolSizeZero);
}
- let pool_end = pool_base
- .checked_add(pool_size - 1)
- .ok_or(Error::PoolOverflow {
- base: pool_base,
- size: pool_size,
- })?;
- let alignment = align_size.unwrap_or(4);
- if !alignment.is_power_of_two() || alignment == 0 {
+
+ let min_align = min_align.unwrap_or(4);
+ if !min_align.is_power_of_two() || min_align == 0 {
return Err(Error::BadAlignment);
}
+
+ let preferred_align = preferred_align.unwrap_or(min_align);
+ if !preferred_align.is_power_of_two() || preferred_align < min_align {
+ return Err(Error::BadAlignment);
+ }
+
let mut regions = BTreeSet::new();
- regions.insert((pool_base, pool_end));
+ for r in pools.iter() {
+ regions.insert((*r.start(), *r.end()));
+ }
Ok(AddressAllocator {
- alignment,
+ pools,
+ min_align,
+ preferred_align,
allocs: HashMap::new(),
regions,
})
}
- /// Allocates a range of addresses from the managed region with an optional tag
- /// and minimal alignment. Returns allocated_address. (allocated_address, size, tag)
- /// can be retrieved through the `get` method.
- pub fn allocate_with_align(
+ /// Gets the regions managed by the allocator.
+ ///
+ /// This returns the original `pools` value provided to `AddressAllocator::new()`.
+ pub fn pools(&self) -> &Vec<RangeInclusive<u64>> {
+ &self.pools
+ }
+
+ fn internal_allocate_with_align(
&mut self,
size: u64,
alloc: Alloc,
tag: String,
alignment: u64,
+ reverse: bool,
) -> Result<u64> {
- let alignment = cmp::max(self.alignment, alignment);
+ let alignment = cmp::max(self.min_align, alignment);
if self.allocs.contains_key(&alloc) {
return Err(Error::ExistingAlloc(alloc));
@@ -84,24 +115,42 @@ impl AddressAllocator {
return Err(Error::BadAlignment);
}
- // finds first region matching alignment and size.
- match self
- .regions
- .iter()
- .find(|range| {
- match range.0 % alignment {
- 0 => range.0.checked_add(size - 1),
- r => range.0.checked_add(size - 1 + alignment - r),
- }
- .map_or(false, |end| end <= range.1)
- })
- .cloned()
- {
+ let region = if !reverse {
+ // finds first region matching alignment and size.
+ self.regions
+ .iter()
+ .find(|range| {
+ match range.0 % alignment {
+ 0 => range.0.checked_add(size - 1),
+ r => range.0.checked_add(size - 1 + alignment - r),
+ }
+ .map_or(false, |end| end <= range.1)
+ })
+ .cloned()
+ } else {
+ // finds last region matching alignment and size.
+ self.regions
+ .iter()
+ .rev()
+ .find(|range| {
+ range
+ .1
+ .checked_sub(size - 1)
+ .map_or(false, |start| start & !(alignment - 1) >= range.0)
+ })
+ .cloned()
+ };
+
+ match region {
Some(slot) => {
self.regions.remove(&slot);
- let start = match slot.0 % alignment {
- 0 => slot.0,
- r => slot.0 + alignment - r,
+ let start = if !reverse {
+ match slot.0 % alignment {
+ 0 => slot.0,
+ r => slot.0 + alignment - r,
+ }
+ } else {
+ (slot.1 - (size - 1)) & !(alignment - 1)
};
let end = start + size - 1;
if slot.0 < start {
@@ -118,14 +167,45 @@ impl AddressAllocator {
}
}
+ /// Allocates a range of addresses from the reverse managed region with an optional tag
+ /// and minimal alignment. Returns allocated_address. (allocated_address, size, tag)
+ /// can be retrieved through the `get` method.
+ pub fn reverse_allocate_with_align(
+ &mut self,
+ size: u64,
+ alloc: Alloc,
+ tag: String,
+ alignment: u64,
+ ) -> Result<u64> {
+ self.internal_allocate_with_align(size, alloc, tag, alignment, true)
+ }
+
+ /// Allocates a range of addresses from the managed region with an optional tag
+ /// and minimal alignment. Returns allocated_address. (allocated_address, size, tag)
+ /// can be retrieved through the `get` method.
+ pub fn allocate_with_align(
+ &mut self,
+ size: u64,
+ alloc: Alloc,
+ tag: String,
+ alignment: u64,
+ ) -> Result<u64> {
+ self.internal_allocate_with_align(size, alloc, tag, alignment, false)
+ }
+
pub fn allocate(&mut self, size: u64, alloc: Alloc, tag: String) -> Result<u64> {
- self.allocate_with_align(size, alloc, tag, self.alignment)
+ if let Ok(pref_alloc) =
+ self.allocate_with_align(size, alloc, tag.clone(), self.preferred_align)
+ {
+ return Ok(pref_alloc);
+ }
+ self.allocate_with_align(size, alloc, tag, self.min_align)
}
/// Allocates a range of addresses from the managed region with an optional tag
/// and required location. Allocation alignment is not enforced.
- /// Returns OutOfSpace if requested range is not available (e.g. already allocated
- /// with a different alloc tag).
+ /// Returns OutOfSpace if requested range is not available or ExistingAlloc if the requested
+ /// range overlaps an existing allocation.
pub fn allocate_at(&mut self, start: u64, size: u64, alloc: Alloc, tag: String) -> Result<()> {
if self.allocs.contains_key(&alloc) {
return Err(Error::ExistingAlloc(alloc));
@@ -153,7 +233,13 @@ impl AddressAllocator {
Ok(())
}
- None => Err(Error::OutOfSpace),
+ None => {
+ if let Some(existing_alloc) = self.find_overlapping(start, size) {
+ Err(Error::ExistingAlloc(existing_alloc))
+ } else {
+ Err(Error::OutOfSpace)
+ }
+ }
}
}
@@ -164,6 +250,33 @@ impl AddressAllocator {
.map_or_else(|| Err(Error::BadAlloc(alloc)), |v| self.insert_at(v.0, v.1))
}
+ /// Release a allocation contains the value.
+ pub fn release_containing(&mut self, value: u64) -> Result<()> {
+ if let Some(alloc) = self.find_overlapping(value, 1) {
+ self.release(alloc)
+ } else {
+ Err(Error::OutOfSpace)
+ }
+ }
+
+ // Find an existing allocation that overlaps the region defined by `address` and `size`. If more
+ // than one allocation overlaps the given region, any of them may be returned, since the HashMap
+ // iterator is not ordered in any particular way.
+ fn find_overlapping(&self, start: u64, size: u64) -> Option<Alloc> {
+ if size == 0 {
+ return None;
+ }
+
+ let end = start.saturating_add(size - 1);
+ self.allocs
+ .iter()
+ .find(|(_, &(alloc_start, alloc_size, _))| {
+ let alloc_end = alloc_start + alloc_size;
+ start < alloc_end && end >= alloc_start
+ })
+ .map(|(&alloc, _)| alloc)
+ }
+
/// Returns allocation associated with `alloc`, or None if no such allocation exists.
pub fn get(&self, alloc: &Alloc) -> Option<&(u64, u64, String)> {
self.allocs.get(alloc)
@@ -328,28 +441,49 @@ mod tests {
use super::*;
#[test]
- fn new_fails_overflow() {
- assert!(AddressAllocator::new(u64::max_value(), 0x100, None).is_err());
+ fn example() {
+ // Anon is used for brevity. Don't manually instantiate Anon allocs!
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0xFFFF), Some(0x100), None).unwrap();
+ assert_eq!(
+ pool.allocate(0x110, Alloc::Anon(0), "caps".to_string()),
+ Ok(0x1000)
+ );
+ assert_eq!(
+ pool.allocate(0x100, Alloc::Anon(1), "cache".to_string()),
+ Ok(0x1200)
+ );
+ assert_eq!(
+ pool.allocate(0x100, Alloc::Anon(2), "etc".to_string()),
+ Ok(0x1300)
+ );
+ assert_eq!(
+ pool.get(&Alloc::Anon(1)),
+ Some(&(0x1200, 0x100, "cache".to_string()))
+ );
}
#[test]
fn new_fails_size_zero() {
- assert!(AddressAllocator::new(0x1000, 0, None).is_err());
+ assert!(AddressAllocator::new(RangeInclusive::new(0x1000, 0), None, None).is_err());
}
#[test]
fn new_fails_alignment_zero() {
- assert!(AddressAllocator::new(0x1000, 0x10000, Some(0)).is_err());
+ assert!(AddressAllocator::new(RangeInclusive::new(0x1000, 0xFFFF), Some(0), None).is_err());
}
#[test]
fn new_fails_alignment_non_power_of_two() {
- assert!(AddressAllocator::new(0x1000, 0x10000, Some(200)).is_err());
+ assert!(
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0xFFFF), Some(200), None).is_err()
+ );
}
#[test]
fn allocate_fails_exising_alloc() {
- let mut pool = AddressAllocator::new(0x1000, 0x1000, Some(0x100)).unwrap();
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0x1FFF), Some(0x100), None).unwrap();
assert_eq!(
pool.allocate(0x800, Alloc::Anon(0), String::from("bar0")),
Ok(0x1000)
@@ -362,7 +496,8 @@ mod tests {
#[test]
fn allocate_fails_not_enough_space() {
- let mut pool = AddressAllocator::new(0x1000, 0x1000, Some(0x100)).unwrap();
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0x1FFF), Some(0x100), None).unwrap();
assert_eq!(
pool.allocate(0x800, Alloc::Anon(0), String::from("bar0")),
Ok(0x1000)
@@ -379,7 +514,8 @@ mod tests {
#[test]
fn allocate_with_special_alignment() {
- let mut pool = AddressAllocator::new(0x1000, 0x1000, Some(0x100)).unwrap();
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0x1FFF), Some(0x100), None).unwrap();
assert_eq!(
pool.allocate(0x10, Alloc::Anon(0), String::from("bar0")),
Ok(0x1000)
@@ -396,7 +532,9 @@ mod tests {
#[test]
fn allocate_and_split_allocate_at() {
- let mut pool = AddressAllocator::new(0x1000, 0x1000, Some(0x100)).unwrap();
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0x1FFF), Some(1), None).unwrap();
+ // 0x1200..0x1a00
assert_eq!(
pool.allocate_at(0x1200, 0x800, Alloc::Anon(0), String::from("bar0")),
Ok(())
@@ -405,19 +543,47 @@ mod tests {
pool.allocate(0x800, Alloc::Anon(1), String::from("bar1")),
Err(Error::OutOfSpace)
);
+ // 0x600..0x2000
assert_eq!(
pool.allocate(0x600, Alloc::Anon(2), String::from("bar2")),
Ok(0x1a00)
);
+ // 0x1000..0x1200
assert_eq!(
pool.allocate(0x200, Alloc::Anon(3), String::from("bar3")),
Ok(0x1000)
);
+ // 0x1b00..0x1c00 (overlaps with 0x600..0x2000)
+ assert_eq!(
+ pool.allocate_at(0x1b00, 0x100, Alloc::Anon(4), String::from("bar4")),
+ Err(Error::ExistingAlloc(Alloc::Anon(2)))
+ );
+ // 0x1fff..0x2000 (overlaps with 0x600..0x2000)
+ assert_eq!(
+ pool.allocate_at(0x1fff, 1, Alloc::Anon(5), String::from("bar5")),
+ Err(Error::ExistingAlloc(Alloc::Anon(2)))
+ );
+ // 0x1200..0x1201 (overlaps with 0x1200..0x1a00)
+ assert_eq!(
+ pool.allocate_at(0x1200, 1, Alloc::Anon(6), String::from("bar6")),
+ Err(Error::ExistingAlloc(Alloc::Anon(0)))
+ );
+ // 0x11ff..0x1200 (overlaps with 0x1000..0x1200)
+ assert_eq!(
+ pool.allocate_at(0x11ff, 1, Alloc::Anon(7), String::from("bar7")),
+ Err(Error::ExistingAlloc(Alloc::Anon(3)))
+ );
+ // 0x1100..0x1300 (overlaps with 0x1000..0x1200 and 0x1200..0x1a00)
+ match pool.allocate_at(0x1100, 0x200, Alloc::Anon(8), String::from("bar8")) {
+ Err(Error::ExistingAlloc(Alloc::Anon(0) | Alloc::Anon(3))) => {}
+ x => panic!("unexpected result {:?}", x),
+ }
}
#[test]
fn allocate_alignment() {
- let mut pool = AddressAllocator::new(0x1000, 0x10000, Some(0x100)).unwrap();
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0xFFFF), Some(0x100), None).unwrap();
assert_eq!(
pool.allocate(0x110, Alloc::Anon(0), String::from("bar0")),
Ok(0x1000)
@@ -430,7 +596,8 @@ mod tests {
#[test]
fn allocate_retrieve_alloc() {
- let mut pool = AddressAllocator::new(0x1000, 0x10000, Some(0x100)).unwrap();
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0xFFFF), Some(0x100), None).unwrap();
assert_eq!(
pool.allocate(0x110, Alloc::Anon(0), String::from("bar0")),
Ok(0x1000)
@@ -443,7 +610,8 @@ mod tests {
#[test]
fn allocate_with_alignment_allocator_alignment() {
- let mut pool = AddressAllocator::new(0x1000, 0x10000, Some(0x100)).unwrap();
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0xFFFF), Some(0x100), None).unwrap();
assert_eq!(
pool.allocate_with_align(0x110, Alloc::Anon(0), String::from("bar0"), 0x1),
Ok(0x1000)
@@ -456,7 +624,8 @@ mod tests {
#[test]
fn allocate_with_alignment_custom_alignment() {
- let mut pool = AddressAllocator::new(0x1000, 0x10000, Some(0x4)).unwrap();
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0xFFFF), Some(0x4), None).unwrap();
assert_eq!(
pool.allocate_with_align(0x110, Alloc::Anon(0), String::from("bar0"), 0x100),
Ok(0x1000)
@@ -469,7 +638,8 @@ mod tests {
#[test]
fn allocate_with_alignment_no_allocator_alignment() {
- let mut pool = AddressAllocator::new(0x1000, 0x10000, None).unwrap();
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0xFFFF), None, None).unwrap();
assert_eq!(
pool.allocate_with_align(0x110, Alloc::Anon(0), String::from("bar0"), 0x100),
Ok(0x1000)
@@ -482,7 +652,8 @@ mod tests {
#[test]
fn allocate_with_alignment_alignment_non_power_of_two() {
- let mut pool = AddressAllocator::new(0x1000, 0x10000, None).unwrap();
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0xFFFF), None, None).unwrap();
assert!(pool
.allocate_with_align(0x110, Alloc::Anon(0), String::from("bar0"), 200)
.is_err());
@@ -490,7 +661,8 @@ mod tests {
#[test]
fn allocate_with_release() {
- let mut pool = AddressAllocator::new(0x1000, 0x1000, None).unwrap();
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0x1FFF), None, None).unwrap();
assert_eq!(
pool.allocate_with_align(0x100, Alloc::Anon(0), String::from("bar0"), 0x100),
Ok(0x1000)
@@ -504,7 +676,8 @@ mod tests {
#[test]
fn coalescing_and_overlap() {
- let mut pool = AddressAllocator::new(0x1000, 0x1000, None).unwrap();
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0x1FFF), None, None).unwrap();
assert!(pool.insert_at(0x3000, 0x1000).is_ok());
assert!(pool.insert_at(0x1fff, 0x20).is_err());
assert!(pool.insert_at(0x2ff1, 0x10).is_err());
@@ -518,7 +691,8 @@ mod tests {
#[test]
fn coalescing_single_addresses() {
- let mut pool = AddressAllocator::new(0x1000, 0x1000, None).unwrap();
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0x1FFF), None, None).unwrap();
assert!(pool.insert_at(0x2001, 1).is_ok());
assert!(pool.insert_at(0x2003, 1).is_ok());
assert!(pool.insert_at(0x2000, 1).is_ok());
@@ -531,7 +705,8 @@ mod tests {
#[test]
fn allocate_and_verify_pci_offset() {
- let mut pool = AddressAllocator::new(0x1000, 0x10000, None).unwrap();
+ let mut pool =
+ AddressAllocator::new(RangeInclusive::new(0x1000, 0xFFFF), None, None).unwrap();
let pci_bar0 = Alloc::PciBar {
bus: 1,
dev: 2,
diff --git a/resources/src/lib.rs b/resources/src/lib.rs
index 0983ffaac..574f4e75e 100644
--- a/resources/src/lib.rs
+++ b/resources/src/lib.rs
@@ -4,14 +4,15 @@
//! Manages system resources that can be allocated to VMs and their devices.
-use std::fmt::Display;
+use std::ops::RangeInclusive;
+use remain::sorted;
use serde::{Deserialize, Serialize};
+use thiserror::Error;
-pub use crate::address_allocator::AddressAllocator;
-pub use crate::system_allocator::{MmioType, SystemAllocator};
+pub use crate::system_allocator::{MemRegion, MmioType, SystemAllocator, SystemAllocatorConfig};
-mod address_allocator;
+pub mod address_allocator;
mod system_allocator;
/// Used to tag SystemAllocator allocations.
@@ -29,46 +30,51 @@ pub enum Alloc {
PmemDevice(usize),
/// pstore region.
Pstore,
+ /// A PCI bridge window with associated bus, dev, function.
+ PciBridgeWindow { bus: u8, dev: u8, func: u8 },
+ /// A PCI bridge prefetch window with associated bus, dev, function.
+ PciBridgePrefetchWindow { bus: u8, dev: u8, func: u8 },
+ /// File-backed memory mapping.
+ FileBacked(u64),
+ /// virtio vhost user queue with queue id
+ VvuQueue(u8),
}
-#[derive(Debug, Eq, PartialEq)]
+#[sorted]
+#[derive(Error, Debug, Eq, PartialEq)]
pub enum Error {
+ #[error("Allocation cannot have size of 0")]
AllocSizeZero,
+ #[error("Pool alignment must be a power of 2")]
BadAlignment,
+ #[error("Alloc does not exist: {0:?}")]
+ BadAlloc(Alloc),
+ #[error("Alloc already exists: {0:?}")]
ExistingAlloc(Alloc),
+ #[error("Invalid Alloc: {0:?}")]
InvalidAlloc(Alloc),
- MissingHighMMIOAddresses,
- MissingLowMMIOAddresses,
+ #[error("IO port out of range: base:{0} size:{1}")]
+ IOPortOutOfRange(u64, u64),
+ #[error("Platform MMIO address range not specified")]
+ MissingPlatformMMIOAddresses,
+ #[error("No IO address range specified")]
NoIoAllocator,
- OutOfSpace,
+ #[error("Out of bounds")]
OutOfBounds,
+ #[error("Out of space")]
+ OutOfSpace,
+ #[error("base={base} + size={size} overflows")]
PoolOverflow { base: u64, size: u64 },
+ #[error("Pool cannot have size of 0")]
PoolSizeZero,
+ #[error("Overlapping region base={base} size={size}")]
RegionOverlap { base: u64, size: u64 },
- BadAlloc(Alloc),
}
pub type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- use self::Error::*;
- match self {
- AllocSizeZero => write!(f, "Allocation cannot have size of 0"),
- BadAlignment => write!(f, "Pool alignment must be a power of 2"),
- ExistingAlloc(tag) => write!(f, "Alloc already exists: {:?}", tag),
- InvalidAlloc(tag) => write!(f, "Invalid Alloc: {:?}", tag),
- MissingHighMMIOAddresses => write!(f, "High MMIO address range not specified"),
- MissingLowMMIOAddresses => write!(f, "Low MMIO address range not specified"),
- NoIoAllocator => write!(f, "No IO address range specified"),
- OutOfSpace => write!(f, "Out of space"),
- OutOfBounds => write!(f, "Out of bounds"),
- PoolOverflow { base, size } => write!(f, "base={} + size={} overflows", base, size),
- PoolSizeZero => write!(f, "Pool cannot have size of 0"),
- RegionOverlap { base, size } => {
- write!(f, "Overlapping region base={} size={}", base, size)
- }
- BadAlloc(tag) => write!(f, "Alloc does not exists: {:?}", tag),
- }
- }
+/// Computes the length of a RangeInclusive value. Returns None
+/// if the range is 0..=u64::MAX.
+pub fn range_inclusive_len(r: &RangeInclusive<u64>) -> Option<u64> {
+ (r.end() - r.start()).checked_add(1)
}
diff --git a/resources/src/system_allocator.rs b/resources/src/system_allocator.rs
index 7368e9155..cd75368b9 100644
--- a/resources/src/system_allocator.rs
+++ b/resources/src/system_allocator.rs
@@ -1,6 +1,8 @@
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+use std::collections::BTreeMap;
+use std::ops::RangeInclusive;
use base::pagesize;
@@ -8,106 +10,175 @@ use crate::address_allocator::{AddressAllocator, AddressAllocatorSet};
use crate::{Alloc, Error, Result};
/// Manages allocating system resources such as address space and interrupt numbers.
-///
-/// # Example - Use the `SystemAddress` builder.
-///
-/// ```
-/// # use resources::{Alloc, MmioType, SystemAllocator};
-/// if let Ok(mut a) = SystemAllocator::builder()
-/// .add_io_addresses(0x1000, 0x10000)
-/// .add_high_mmio_addresses(0x10000000, 0x10000000)
-/// .add_low_mmio_addresses(0x30000000, 0x10000)
-/// .create_allocator(5) {
-/// assert_eq!(a.allocate_irq(), Some(5));
-/// assert_eq!(a.allocate_irq(), Some(6));
-/// assert_eq!(
-/// a.mmio_allocator(MmioType::High)
-/// .allocate(
-/// 0x100,
-/// Alloc::PciBar { bus: 0, dev: 0, func: 0, bar: 0 },
-/// "bar0".to_string()
-/// ),
-/// Ok(0x10000000)
-/// );
-/// assert_eq!(
-/// a.mmio_allocator(MmioType::High)
-/// .get(&Alloc::PciBar { bus: 0, dev: 0, func: 0, bar: 0 }),
-/// Some(&(0x10000000, 0x100, "bar0".to_string()))
-/// );
-/// }
-/// ```
/// MMIO address Type
/// Low: address allocated from low_address_space
/// High: address allocated from high_address_space
+#[derive(Copy, Clone)]
pub enum MmioType {
Low,
High,
}
+/// Region of memory.
+#[derive(Debug)]
+pub struct MemRegion {
+ pub base: u64,
+ pub size: u64,
+}
+
+pub struct SystemAllocatorConfig {
+ /// IO ports. Only for x86_64.
+ pub io: Option<MemRegion>,
+ /// Low (<=4GB) MMIO region.
+ ///
+ /// Parts of this region may be reserved or otherwise excluded from the
+ /// created SystemAllocator's MmioType::Low allocator. However, no new
+ /// regions will be added.
+ pub low_mmio: MemRegion,
+ /// High (>4GB) MMIO region.
+ ///
+ /// Parts of this region may be reserved or otherwise excluded from the
+ /// created SystemAllocator's MmioType::High allocator. However, no new
+ /// regions will be added.
+ pub high_mmio: MemRegion,
+ /// Platform MMIO space. Only for ARM.
+ pub platform_mmio: Option<MemRegion>,
+ /// The first IRQ number to give out.
+ pub first_irq: u32,
+}
+
#[derive(Debug)]
pub struct SystemAllocator {
io_address_space: Option<AddressAllocator>,
// Indexed by MmioType::Low and MmioType::High.
mmio_address_spaces: [AddressAllocator; 2],
+ mmio_platform_address_spaces: Option<AddressAllocator>,
+
+ reserved_region: Option<MemRegion>,
- pci_allocator: AddressAllocator,
+ // Each bus number has a AddressAllocator
+ pci_allocator: BTreeMap<u8, AddressAllocator>,
irq_allocator: AddressAllocator,
next_anon_id: usize,
}
+fn to_range_inclusive(base: u64, size: u64) -> Result<RangeInclusive<u64>> {
+ let end = base
+ .checked_add(size.checked_sub(1).ok_or(Error::PoolSizeZero)?)
+ .ok_or(Error::PoolOverflow { base, size })?;
+ Ok(RangeInclusive::new(base, end))
+}
+
+fn range_intersect(r1: &RangeInclusive<u64>, r2: &RangeInclusive<u64>) -> RangeInclusive<u64> {
+ RangeInclusive::new(
+ u64::max(*r1.start(), *r2.start()),
+ u64::min(*r1.end(), *r2.end()),
+ )
+}
+
impl SystemAllocator {
- /// Creates a new `SystemAllocator` for managing addresses and irq numvers.
- /// Can return `None` if `base` + `size` overflows a u64 or if alignment isn't a power
- /// of two.
+ /// Creates a new `SystemAllocator` for managing addresses and irq numbers.
+ /// Will return an error if `base` + `size` overflows u64 (or allowed
+ /// maximum for the specific type), or if alignment isn't a power of two.
///
- /// * `io_base` - The starting address of IO memory.
- /// * `io_size` - The size of IO memory.
- /// * `high_base` - The starting address of high MMIO space.
- /// * `high_size` - The size of high MMIO space.
- /// * `low_base` - The starting address of low MMIO space.
- /// * `low_size` - The size of low MMIO space.
- /// * `first_irq` - The first irq number to give out.
- fn new(
- io_base: Option<u64>,
- io_size: Option<u64>,
- high_base: u64,
- high_size: u64,
- low_base: u64,
- low_size: u64,
- first_irq: u32,
+ /// If `reserve_region_size` is not None, then a region is reserved from
+ /// the start of `config.high_mmio` before the mmio allocator is created.
+ ///
+ /// If `mmio_address_ranges` is not empty, then `config.low_mmio` and
+ /// `config.high_mmio` are intersected with the ranges specified.
+ pub fn new(
+ config: SystemAllocatorConfig,
+ reserve_region_size: Option<u64>,
+ mmio_address_ranges: &[RangeInclusive<u64>],
) -> Result<Self> {
let page_size = pagesize() as u64;
+
+ let (high_mmio, reserved_region) = match reserve_region_size {
+ Some(len) => {
+ if len > config.high_mmio.size {
+ return Err(Error::PoolSizeZero);
+ }
+ (
+ MemRegion {
+ base: config.high_mmio.base + len,
+ size: config.high_mmio.size - len,
+ },
+ Some(MemRegion {
+ base: config.high_mmio.base,
+ size: len,
+ }),
+ )
+ }
+ None => (config.high_mmio, None),
+ };
+
+ let intersect_mmio_range = |src: MemRegion| -> Result<Vec<RangeInclusive<u64>>> {
+ let src_range = to_range_inclusive(src.base, src.size)?;
+ Ok(if mmio_address_ranges.is_empty() {
+ vec![src_range]
+ } else {
+ mmio_address_ranges
+ .iter()
+ .map(|r| range_intersect(r, &src_range))
+ .collect()
+ })
+ };
+
Ok(SystemAllocator {
- io_address_space: if let (Some(b), Some(s)) = (io_base, io_size) {
- Some(AddressAllocator::new(b, s, Some(0x400))?)
+ io_address_space: if let Some(io) = config.io {
+ // TODO make sure we don't overlap with existing well known
+ // ports such as 0xcf8 (serial ports).
+ if io.base > 0x1_0000 || io.size + io.base > 0x1_0000 {
+ return Err(Error::IOPortOutOfRange(io.base, io.size));
+ }
+ Some(AddressAllocator::new(
+ to_range_inclusive(io.base, io.size)?,
+ Some(0x400),
+ None,
+ )?)
} else {
None
},
mmio_address_spaces: [
// MmioType::Low
- AddressAllocator::new(low_base, low_size, Some(page_size))?,
+ AddressAllocator::new_from_list(
+ intersect_mmio_range(config.low_mmio)?,
+ Some(page_size),
+ None,
+ )?,
// MmioType::High
- AddressAllocator::new(high_base, high_size, Some(page_size))?,
+ AddressAllocator::new_from_list(
+ intersect_mmio_range(high_mmio)?,
+ Some(page_size),
+ None,
+ )?,
],
- // Support up to 256(buses) x 32(devices) x 8(functions) with default
- // alignment allocating device with mandatory function number zero.
- pci_allocator: AddressAllocator::new(8, (256 * 32 * 8) - 8, Some(8))?,
+
+ pci_allocator: BTreeMap::new(),
+
+ mmio_platform_address_spaces: if let Some(platform) = config.platform_mmio {
+ Some(AddressAllocator::new(
+ to_range_inclusive(platform.base, platform.size)?,
+ Some(page_size),
+ None,
+ )?)
+ } else {
+ None
+ },
+
+ reserved_region,
+
irq_allocator: AddressAllocator::new(
- first_irq as u64,
- 1024 - first_irq as u64,
+ RangeInclusive::new(config.first_irq as u64, 1023),
Some(1),
+ None,
)?,
next_anon_id: 0,
})
}
- /// Returns a `SystemAllocatorBuilder` that can create a new `SystemAllocator`.
- pub fn builder() -> SystemAllocatorBuilder {
- SystemAllocatorBuilder::new()
- }
-
/// Reserves the next available system irq number.
pub fn allocate_irq(&mut self) -> Option<u32> {
let id = self.get_anon_alloc();
@@ -117,6 +188,11 @@ impl SystemAllocator {
.ok()
}
+ /// release irq to system irq number pool
+ pub fn release_irq(&mut self, irq: u32) {
+ let _ = self.irq_allocator.release_containing(irq.into());
+ }
+
/// Reserves the next available system irq number.
pub fn reserve_irq(&mut self, irq: u32) -> bool {
let id = self.get_anon_alloc();
@@ -125,14 +201,44 @@ impl SystemAllocator {
.is_ok()
}
+ fn get_pci_allocator_mut(&mut self, bus: u8) -> Option<&mut AddressAllocator> {
+ // pci root is 00:00.0, Bus 0 next device is 00:01.0 with mandatory function
+ // number zero.
+ if self.pci_allocator.get(&bus).is_none() {
+ let base = if bus == 0 { 8 } else { 0 };
+
+ // Each bus supports up to 32 (devices) x 8 (functions).
+ // Prefer allocating at device granularity (preferred_align = 8), but fall back to
+ // allocating individual functions (min_align = 1) when we run out of devices.
+ match AddressAllocator::new(RangeInclusive::new(base, (32 * 8) - 1), Some(1), Some(8)) {
+ Ok(v) => self.pci_allocator.insert(bus, v),
+ Err(_) => return None,
+ };
+ }
+ self.pci_allocator.get_mut(&bus)
+ }
+
+ // Check whether devices exist or not on the specified bus
+ pub fn pci_bus_empty(&self, bus: u8) -> bool {
+ if self.pci_allocator.get(&bus).is_none() {
+ true
+ } else {
+ false
+ }
+ }
+
/// Allocate PCI slot location.
- pub fn allocate_pci(&mut self, tag: String) -> Option<Alloc> {
+ pub fn allocate_pci(&mut self, bus: u8, tag: String) -> Option<Alloc> {
let id = self.get_anon_alloc();
- self.pci_allocator
+ let allocator = match self.get_pci_allocator_mut(bus) {
+ Some(v) => v,
+ None => return None,
+ };
+ allocator
.allocate(1, id, tag)
.map(|v| Alloc::PciBar {
- bus: ((v >> 8) & 255) as u8,
- dev: ((v >> 3) & 31) as u8,
+ bus,
+ dev: (v >> 3) as u8,
func: (v & 7) as u8,
bar: 0,
})
@@ -149,13 +255,32 @@ impl SystemAllocator {
func,
bar: _,
} => {
- let bdf = ((bus as u64) << 8) | ((dev as u64) << 3) | (func as u64);
- self.pci_allocator.allocate_at(bdf, 1, id, tag).is_ok()
+ let allocator = match self.get_pci_allocator_mut(bus) {
+ Some(v) => v,
+ None => return false,
+ };
+ let df = ((dev as u64) << 3) | (func as u64);
+ allocator.allocate_at(df, 1, id, tag).is_ok()
}
_ => false,
}
}
+ /// release PCI slot location.
+ pub fn release_pci(&mut self, bus: u8, dev: u8, func: u8) -> bool {
+ let allocator = match self.get_pci_allocator_mut(bus) {
+ Some(v) => v,
+ None => return false,
+ };
+ let df = ((dev as u64) << 3) | (func as u64);
+ allocator.release_containing(df).is_ok()
+ }
+
+ /// Gets an allocator to be used for platform device MMIO allocation.
+ pub fn mmio_platform_allocator(&mut self) -> Option<&mut AddressAllocator> {
+ self.mmio_platform_address_spaces.as_mut()
+ }
+
/// Gets an allocator to be used for IO memory.
pub fn io_allocator(&mut self) -> Option<&mut AddressAllocator> {
self.io_address_space.as_mut()
@@ -174,6 +299,19 @@ impl SystemAllocator {
AddressAllocatorSet::new(&mut self.mmio_address_spaces)
}
+ /// Gets the pools of all mmio allocators.
+ pub fn mmio_pools(&self) -> Vec<&RangeInclusive<u64>> {
+ self.mmio_address_spaces
+ .iter()
+ .flat_map(|mmio_as| mmio_as.pools())
+ .collect()
+ }
+
+ /// Gets the reserved address space region.
+ pub fn reserved_region(&self) -> Option<&MemRegion> {
+ self.reserved_region.as_ref()
+ }
+
/// Gets a unique anonymous allocation
pub fn get_anon_alloc(&mut self) -> Alloc {
self.next_anon_id += 1;
@@ -181,55 +319,57 @@ impl SystemAllocator {
}
}
-/// Used to build a system address map for use in creating a `SystemAllocator`.
-pub struct SystemAllocatorBuilder {
- io_base: Option<u64>,
- io_size: Option<u64>,
- low_mmio_base: Option<u64>,
- low_mmio_size: Option<u64>,
- high_mmio_base: Option<u64>,
- high_mmio_size: Option<u64>,
-}
-
-impl SystemAllocatorBuilder {
- pub fn new() -> Self {
- SystemAllocatorBuilder {
- io_base: None,
- io_size: None,
- low_mmio_base: None,
- low_mmio_size: None,
- high_mmio_base: None,
- high_mmio_size: None,
- }
- }
-
- pub fn add_io_addresses(mut self, base: u64, size: u64) -> Self {
- self.io_base = Some(base);
- self.io_size = Some(size);
- self
- }
-
- pub fn add_low_mmio_addresses(mut self, base: u64, size: u64) -> Self {
- self.low_mmio_base = Some(base);
- self.low_mmio_size = Some(size);
- self
- }
+#[cfg(test)]
+mod tests {
+ use super::*;
- pub fn add_high_mmio_addresses(mut self, base: u64, size: u64) -> Self {
- self.high_mmio_base = Some(base);
- self.high_mmio_size = Some(size);
- self
- }
-
- pub fn create_allocator(&self, first_irq: u32) -> Result<SystemAllocator> {
- SystemAllocator::new(
- self.io_base,
- self.io_size,
- self.high_mmio_base.ok_or(Error::MissingHighMMIOAddresses)?,
- self.high_mmio_size.ok_or(Error::MissingHighMMIOAddresses)?,
- self.low_mmio_base.ok_or(Error::MissingLowMMIOAddresses)?,
- self.low_mmio_size.ok_or(Error::MissingLowMMIOAddresses)?,
- first_irq,
+ #[test]
+ fn example() {
+ let mut a = SystemAllocator::new(
+ SystemAllocatorConfig {
+ io: Some(MemRegion {
+ base: 0x1000,
+ size: 0xf000,
+ }),
+ low_mmio: MemRegion {
+ base: 0x3000_0000,
+ size: 0x1_0000,
+ },
+ high_mmio: MemRegion {
+ base: 0x1000_0000,
+ size: 0x1000_0000,
+ },
+ platform_mmio: None,
+ first_irq: 5,
+ },
+ None,
+ &[],
)
+ .unwrap();
+
+ assert_eq!(a.allocate_irq(), Some(5));
+ assert_eq!(a.allocate_irq(), Some(6));
+ assert_eq!(
+ a.mmio_allocator(MmioType::High).allocate(
+ 0x100,
+ Alloc::PciBar {
+ bus: 0,
+ dev: 0,
+ func: 0,
+ bar: 0
+ },
+ "bar0".to_string()
+ ),
+ Ok(0x10000000)
+ );
+ assert_eq!(
+ a.mmio_allocator(MmioType::High).get(&Alloc::PciBar {
+ bus: 0,
+ dev: 0,
+ func: 0,
+ bar: 0
+ }),
+ Some(&(0x10000000, 0x100, "bar0".to_string()))
+ );
}
}
diff --git a/run_c2a.sh b/run_c2a.sh
new file mode 100755
index 000000000..5140545d9
--- /dev/null
+++ b/run_c2a.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+
+# Run cargo2android.py with the appropriate arguments for the crate in the
+# supplied directory.
+
+set -e
+
+if [ "$#" -ne 1 ]; then
+ echo "Usage: $0 CRATE_DIRECTORY"
+ exit 1
+fi
+cd $1
+
+if ! [ -x "$(command -v bpfmt)" ]; then
+ echo 'Error: bpfmt not found.' >&2
+ exit 1
+fi
+
+C2A_ARGS=""
+if [[ -f "cargo2android.json" ]]; then
+ # If the crate has a cargo2android config, let it handle all the flags.
+ C2A_ARGS+=" --config cargo2android.json"
+else
+ # Otherwise, set common flags.
+ C2A_ARGS+=" --run --device --tests --global_defaults=crosvm_defaults --add_workspace"
+ # If there are subdirectories with crates, then pass --no-subdir.
+ if [ -n "$(find . -mindepth 2 -name "Cargo.toml")" ]; then
+ C2A_ARGS+=" --no-subdir"
+ fi
+fi
+
+C2A=${C2A:-cargo2android.py}
+echo "Using $C2A to run this script."
+$C2A $C2A_ARGS
+rm -f cargo.out
+rm -rf target.tmp || /bin/true
+
+if [[ -f "Android.bp" ]]; then
+ bpfmt -w Android.bp || /bin/true
+fi
diff --git a/run_tests b/run_tests
index 95c92b8d0..6424764d2 100755
--- a/run_tests
+++ b/run_tests
@@ -1,93 +1,5 @@
-#!/usr/bin/env python3
+#!/bin/bash
# Copyright 2021 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-#
-# Runs tests for crosvm.
-#
-# See `ci/README.md` or `./run_tests -h` for more details.
-
-from typing import List, Dict
-from ci.test_runner import Requirements, main
-
-# A list of all crates and their requirements
-# See ci/test_runner.py for documentation on the requirements
-CRATE_REQUIREMENTS: Dict[str, List[Requirements]] = {
- "aarch64": [Requirements.AARCH64],
- "acpi_tables": [],
- "arch": [],
- "assertions": [],
- "base": [],
- "bit_field": [],
- "bit_field_derive": [],
- "cros_async": [Requirements.DISABLED],
- "crosvm": [Requirements.DO_NOT_RUN],
- "crosvm_plugin": [Requirements.X86_64],
- "data_model": [],
- "devices": [
- Requirements.SINGLE_THREADED,
- Requirements.PRIVILEGED,
- Requirements.X86_64,
- ],
- "disk": [Requirements.PRIVILEGED],
- "enumn": [],
- "fuse": [],
- "fuzz": [Requirements.DISABLED],
- "gpu_display": [],
- "hypervisor": [Requirements.PRIVILEGED, Requirements.X86_64],
- "integration_tests": [Requirements.PRIVILEGED, Requirements.X86_64],
- "io_uring": [
- Requirements.SEPARATE_WORKSPACE,
- Requirements.PRIVILEGED,
- Requirements.SINGLE_THREADED,
- ],
- "kernel_cmdline": [],
- "kernel_loader": [Requirements.PRIVILEGED],
- "kvm_sys": [Requirements.PRIVILEGED],
- "kvm": [Requirements.PRIVILEGED, Requirements.X86_64],
- "libcrosvm_control": [],
- "linux_input_sys": [],
- "net_sys": [],
- "net_util": [Requirements.PRIVILEGED],
- "power_monitor": [],
- "protos": [],
- "qcow_utils": [],
- "rand_ish": [],
- "resources": [],
- "rutabaga_gfx": [Requirements.CROS_BUILD, Requirements.PRIVILEGED],
- "sync": [],
- "sys_util": [Requirements.SINGLE_THREADED, Requirements.PRIVILEGED],
- "poll_token_derive": [],
- "tempfile": [],
- "tpm2-sys": [],
- "tpm2": [],
- "usb_sys": [],
- "usb_util": [],
- "vfio_sys": [],
- "vhost": [Requirements.PRIVILEGED],
- "virtio_sys": [],
- "vm_control": [],
- "vm_memory": [Requirements.PRIVILEGED],
- "x86_64": [Requirements.X86_64, Requirements.PRIVILEGED],
-}
-
-# Just like for crates, lists requirements for each cargo feature flag.
-FEATURE_REQUIREMENTS: Dict[str, List[Requirements]] = {
- "chromeos": [],
- "audio": [],
- "gpu": [Requirements.CROS_BUILD],
- "plugin": [Requirements.PRIVILEGED, Requirements.X86_64],
- "power-monitor-powerd": [],
- "tpm": [Requirements.CROS_BUILD],
- "video-decoder": [Requirements.DISABLED],
- "video-encoder": [Requirements.DISABLED],
- "wl-dmabuf": [Requirements.DISABLED],
- "x": [],
- "virgl_renderer_next": [Requirements.CROS_BUILD],
- "composite-disk": [],
- "virgl_renderer": [Requirements.CROS_BUILD],
- "gfxstream": [Requirements.DISABLED],
- "gdb": [],
-}
-
-main(CRATE_REQUIREMENTS, FEATURE_REQUIREMENTS)
+echo "./run_tests is deprecated. Please run ./tools/run_tests"
diff --git a/rust-toolchain b/rust-toolchain
index 2b748d863..b83c5b217 120000..100644
--- a/rust-toolchain
+++ b/rust-toolchain
@@ -1 +1,3 @@
-ci/crosvm_base/rust-toolchain \ No newline at end of file
+[toolchain]
+channel = "1.58.1"
+components = [ "rustfmt", "clippy" ]
diff --git a/rutabaga_gfx/Android.bp b/rutabaga_gfx/Android.bp
index e356ff64c..ba134cc20 100644
--- a/rutabaga_gfx/Android.bp
+++ b/rutabaga_gfx/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --config cargo2android.json.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -15,10 +15,11 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "rutabaga_gfx",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
features: [
- "gfxstream",
"virgl_renderer",
"virgl_renderer_next",
],
@@ -27,18 +28,21 @@ rust_library {
"libdata_model",
"liblibc",
"libsync_rust",
+ "libthiserror",
],
-
- // added manually
- shared_libs: ["libgfxstream_backend"],
+ proc_macros: ["libremain"],
target: {
host: {
- shared_libs: ["libvirglrenderer"],
+ features: ["gfxstream"],
+ shared_libs: [
+ "libgfxstream_backend",
+ "libvirglrenderer",
+ ],
},
android: {
shared_libs: [
- "libdrm",
- ],
+ "libdrm",
+ ],
static_libs: [
"libepoxy",
"libgbm",
@@ -48,17 +52,21 @@ rust_library {
},
}
-rust_defaults {
- name: "rutabaga_gfx_defaults",
+rust_test {
+ name: "rutabaga_gfx_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "rutabaga_gfx",
- // has rustc warnings
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2021",
features: [
- "gfxstream",
"virgl_renderer",
"virgl_renderer_next",
],
@@ -67,18 +75,21 @@ rust_defaults {
"libdata_model",
"liblibc",
"libsync_rust",
+ "libthiserror",
],
-
- // added manually
- shared_libs: ["libgfxstream_backend"],
+ proc_macros: ["libremain"],
target: {
host: {
- shared_libs: ["libvirglrenderer"],
+ features: ["gfxstream"],
+ shared_libs: [
+ "libgfxstream_backend",
+ "libvirglrenderer",
+ ],
},
android: {
shared_libs: [
- "libdrm",
- ],
+ "libdrm",
+ ],
static_libs: [
"libepoxy",
"libgbm",
@@ -87,56 +98,3 @@ rust_defaults {
},
},
}
-
-rust_test_host {
- name: "rutabaga_gfx_host_test_src_lib",
- defaults: ["rutabaga_gfx_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "rutabaga_gfx_device_test_src_lib",
- defaults: ["rutabaga_gfx_defaults"],
-}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/rutabaga_gfx/Cargo.toml b/rutabaga_gfx/Cargo.toml
index 27dd2b611..9310ba5b0 100644
--- a/rutabaga_gfx/Cargo.toml
+++ b/rutabaga_gfx/Cargo.toml
@@ -2,7 +2,7 @@
name = "rutabaga_gfx"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[features]
gfxstream = []
@@ -12,10 +12,17 @@ minigbm = []
# To try out Vulkano, delete the following line and uncomment the line in "dependencies". vulkano
# features are just a prototype and not integrated yet into the ChromeOS build system.
vulkano = []
+x = []
[dependencies]
-data_model = { path = "../data_model" }
+data_model = { path = "../common/data_model" }
libc = "*"
base = { path = "../base" }
-sync = { path = "../sync" }
+remain = "*"
+sync = { path = "../common/sync" }
+thiserror = "*"
#vulkano = {git = "https:/github.com/vulkano-rs/vulkano.git", optional = true}
+
+[build-dependencies]
+pkg-config = "*"
+anyhow = "*"
diff --git a/rutabaga_gfx/TEST_MAPPING b/rutabaga_gfx/TEST_MAPPING
index 6c6448b69..b788d6d9e 100644
--- a/rutabaga_gfx/TEST_MAPPING
+++ b/rutabaga_gfx/TEST_MAPPING
@@ -4,10 +4,10 @@
// "presubmit": [
// {
// "host": true,
-// "name": "rutabaga_gfx_host_test_src_lib"
+// "name": "rutabaga_gfx_test_src_lib"
// },
// {
-// "name": "rutabaga_gfx_device_test_src_lib"
+// "name": "rutabaga_gfx_test_src_lib"
// }
// ]
}
diff --git a/rutabaga_gfx/build.rs b/rutabaga_gfx/build.rs
new file mode 100644
index 000000000..c12e0113c
--- /dev/null
+++ b/rutabaga_gfx/build.rs
@@ -0,0 +1,172 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use anyhow::Result;
+
+#[cfg(feature = "virgl_renderer")]
+use anyhow::bail;
+#[cfg(feature = "virgl_renderer")]
+use std::env;
+#[cfg(feature = "virgl_renderer")]
+use std::fs;
+#[cfg(feature = "virgl_renderer")]
+use std::path::Path;
+#[cfg(feature = "virgl_renderer")]
+use std::path::PathBuf;
+#[cfg(feature = "virgl_renderer")]
+use std::process::Command;
+
+#[cfg(feature = "virgl_renderer")]
+const MINIGBM_SRC: &str = "../../minigbm";
+#[cfg(feature = "virgl_renderer")]
+const VIRGLRENDERER_SRC: &str = "../../virglrenderer";
+
+#[cfg(feature = "virgl_renderer")]
+fn is_native_build() -> bool {
+ env::var("HOST").unwrap() == env::var("TARGET").unwrap()
+}
+
+/// Returns the target triplet prefix for gcc commands. No prefix is required
+/// for native builds.
+#[cfg(feature = "virgl_renderer")]
+fn get_cross_compile_prefix() -> String {
+ if is_native_build() {
+ return String::from("");
+ }
+
+ let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
+ let os = env::var("CARGO_CFG_TARGET_OS").unwrap();
+ let env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
+ return format!("{}-{}-{}-", arch, os, env);
+}
+
+/// For cross-compilation with meson, we need to pick a cross-file, which
+/// live in /usr/local/share/meson/cross.
+#[cfg(feature = "virgl_renderer")]
+fn get_meson_cross_args() -> Vec<String> {
+ if is_native_build() {
+ Vec::new()
+ } else {
+ vec![
+ "--cross-file".to_string(),
+ env::var("CARGO_CFG_TARGET_ARCH").unwrap(),
+ ]
+ }
+}
+
+#[cfg(feature = "virgl_renderer")]
+fn build_minigbm(out_dir: &Path) -> Result<()> {
+ let lib_path = out_dir.join("libgbm.a");
+ if lib_path.exists() {
+ return Ok(());
+ }
+
+ if !Path::new(MINIGBM_SRC).join(".git").exists() {
+ bail!(
+ "{} source does not exist, did you forget to \
+ `git submodule update --init`?",
+ MINIGBM_SRC
+ );
+ }
+
+ let make_flags = env::var("CARGO_MAKEFLAGS").unwrap();
+ let status = Command::new("make")
+ .env("MAKEFLAGS", make_flags)
+ .env("CROSS_COMPILE", get_cross_compile_prefix())
+ .arg(format!("OUT={}", out_dir.display()))
+ .arg("CC_STATIC_LIBRARY(libminigbm.pie.a)")
+ .current_dir(MINIGBM_SRC)
+ .status()?;
+ if !status.success() {
+ bail!("make failed with status: {}", status);
+ }
+
+ // minigbm will be linked using the name gbm, make sure it can be found.
+ fs::copy(out_dir.join("libminigbm.pie.a"), out_dir.join("libgbm.a"))?;
+ Ok(())
+}
+
+#[cfg(feature = "virgl_renderer")]
+fn build_virglrenderer(out_dir: &Path) -> Result<()> {
+ let lib_path = out_dir.join("src/libvirglrenderer.a");
+ if lib_path.exists() {
+ return Ok(());
+ }
+
+ if !Path::new(VIRGLRENDERER_SRC).join(".git").exists() {
+ bail!(
+ "{} source does not exist, did you forget to \
+ `git submodule update --init`?",
+ VIRGLRENDERER_SRC
+ );
+ }
+
+ let platforms = [
+ "egl",
+ #[cfg(feature = "x")]
+ "glx",
+ ];
+
+ let minigbm_src_abs = PathBuf::from(MINIGBM_SRC).canonicalize()?;
+ let status = Command::new("meson")
+ .env("PKG_CONFIG_PATH", &minigbm_src_abs)
+ .arg("setup")
+ .arg(format!("-Dplatforms={}", platforms.join(",")))
+ .arg("-Ddefault_library=static")
+ .args(get_meson_cross_args())
+ .arg(out_dir.as_os_str())
+ .current_dir(VIRGLRENDERER_SRC)
+ .status()?;
+ if !status.success() {
+ bail!("meson setup failed with status: {}", status);
+ }
+
+ // Add local minigbm paths to make sure virglrenderer can build against it.
+ let mut cmd = Command::new("meson");
+ cmd.env("CPATH", &minigbm_src_abs)
+ .arg("compile")
+ .arg("src/virglrenderer")
+ .current_dir(out_dir);
+
+ let status = cmd.status()?;
+ if !status.success() {
+ bail!("meson compile failed with status: {}", status);
+ }
+ Ok(())
+}
+
+#[cfg(feature = "virgl_renderer")]
+fn virglrenderer() -> Result<()> {
+ // System provided runtime dependencies.
+ pkg_config::Config::new().probe("epoxy")?;
+ pkg_config::Config::new().probe("libdrm")?;
+
+ // Use virglrenderer package from the standard system location if available.
+ if pkg_config::Config::new().probe("virglrenderer").is_ok() {
+ return Ok(());
+ }
+
+ // Otherwise build from source.
+ let out_dir = PathBuf::from(env::var("OUT_DIR")?);
+ let minigbm_out = out_dir.join("minigbm");
+ let virglrenderer_out = out_dir.join("virglrenderer");
+ build_minigbm(&minigbm_out)?;
+ build_virglrenderer(&virglrenderer_out)?;
+
+ println!(
+ "cargo:rustc-link-search={}/src",
+ virglrenderer_out.display()
+ );
+ println!("cargo:rustc-link-search={}", minigbm_out.display());
+ println!("cargo:rustc-link-lib=static=virglrenderer");
+ println!("cargo:rustc-link-lib=static=gbm");
+ Ok(())
+}
+
+fn main() -> Result<()> {
+ #[cfg(feature = "virgl_renderer")]
+ virglrenderer()?;
+
+ Ok(())
+}
diff --git a/rutabaga_gfx/cargo2android.json b/rutabaga_gfx/cargo2android.json
new file mode 100644
index 000000000..05e4b9c83
--- /dev/null
+++ b/rutabaga_gfx/cargo2android.json
@@ -0,0 +1,9 @@
+{
+ "add_workspace": true,
+ "device": true,
+ "features": "virgl_renderer,virgl_renderer_next",
+ "global_defaults": "crosvm_defaults",
+ "patch": "patches/Android.bp.patch",
+ "run": true,
+ "tests": true
+}
diff --git a/rutabaga_gfx/ffi/Android.bp b/rutabaga_gfx/ffi/Android.bp
new file mode 100644
index 000000000..018904f25
--- /dev/null
+++ b/rutabaga_gfx/ffi/Android.bp
@@ -0,0 +1,32 @@
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
+// Do not modify this file as changes will be overridden on upgrade.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_crosvm_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-BSD
+ default_applicable_licenses: ["external_crosvm_license"],
+}
+
+rust_ffi_shared {
+ name: "librutabaga_gfx_ffi_shared",
+ defaults: ["crosvm_defaults"],
+ stem: "librutabaga_gfx_ffi",
+ host_supported: true,
+ crate_name: "rutabaga_gfx_ffi",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ flags: [
+ "-C lto",
+ ],
+ rustlibs: [
+ "libbase_rust",
+ "libdata_model",
+ "liblibc",
+ "librutabaga_gfx",
+ ],
+}
diff --git a/rutabaga_gfx/ffi/Cargo.toml b/rutabaga_gfx/ffi/Cargo.toml
new file mode 100644
index 000000000..a4160a72c
--- /dev/null
+++ b/rutabaga_gfx/ffi/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "rutabaga_gfx_ffi"
+version = "0.1.0"
+authors = ["The Chromium OS Authors + Android Open Source Project"]
+edition = "2021"
+
+[lib]
+name = "rutabaga_gfx_ffi"
+crate-type = ["cdylib"]
+
+[dependencies]
+rutabaga_gfx = { path = "../" }
+base = {path = "../../base" }
+data_model = {path = "../../common/data_model" }
+libc = "0.2.93"
+
+[features]
+minigbm = ["rutabaga_gfx/minigbm"]
+gfxstream = ["rutabaga_gfx/gfxstream"]
+virgl_renderer = ["rutabaga_gfx/virgl_renderer"]
+virgl_renderer_next = ["rutabaga_gfx/virgl_renderer_next"]
+vulkano = ["rutabaga_gfx/vulkano"]
+
+[profile.dev]
+lto = true
+incremental = false
+
+[workspace]
diff --git a/rutabaga_gfx/ffi/src/.clang-format b/rutabaga_gfx/ffi/src/.clang-format
new file mode 100644
index 000000000..b3aabdec8
--- /dev/null
+++ b/rutabaga_gfx/ffi/src/.clang-format
@@ -0,0 +1,15 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+BasedOnStyle: LLVM
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+BreakBeforeBraces: Linux
+ColumnLimit: 100
+IndentWidth: 8
+TabWidth: 8
+UseTab: Always
+Cpp11BracedListStyle: false
+IndentCaseLabels: false
diff --git a/rutabaga_gfx/ffi/src/include/rutabaga_gfx_ffi.h b/rutabaga_gfx/ffi/src/include/rutabaga_gfx_ffi.h
new file mode 100644
index 000000000..281c89150
--- /dev/null
+++ b/rutabaga_gfx/ffi/src/include/rutabaga_gfx_ffi.h
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2021 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <sys/uio.h>
+
+#ifndef RUTABAGA_GFX_FFI_H
+#define RUTABAGA_GFX_FFI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Rutabaga component types
+ */
+#define RUTABAGA_COMPONENT_2D 1
+#define RUTABAGA_COMPONENT_VIRGL_RENDERER 2
+#define RUTABAGA_COMPONENT_GFXSTREAM 3
+#define RUTABAGA_COMPONENT_CROSS_DOMAIN 4
+
+/**
+ * Blob resource creation parameters.
+ */
+#define RUTABAGA_BLOB_MEM_GUEST 1
+#define RUTABAGA_BLOB_MEM_HOST3D 2
+#define RUTABAGA_BLOB_MEM_HOST3D_GUEST 3
+
+#define RUTABAGA_BLOB_FLAG_USE_MAPPABLE 1
+#define RUTABAGA_BLOB_FLAG_USE_SHAREABLE 2
+#define RUTABAGA_BLOB_FLAG_USE_CROSS_DEVICE 4
+
+/**
+ * Rutabaga capsets.
+ */
+#define RUTABAGA_CAPSET_VIRGL 1
+#define RUTABAGA_CAPSET_VIRGL2 2
+#define RUTABAGA_CAPSET_GFXSTREAM 3
+#define RUTABAGA_CAPSET_VENUS 4
+#define RUTABAGA_CAPSET_CROSS_DOMAIN 5
+
+/**
+ * Mapped memory caching flags (see virtio_gpu spec)
+ */
+#define RUTABAGA_MAP_CACHE_CACHED 1
+#define RUTABAGA_MAP_CACHE_UNCACHED 2
+#define RUTABAGA_MAP_CACHE_WC 3
+
+/**
+ * Rutabaga flags for creating fences.
+ */
+#define RUTABAGA_FLAG_FENCE (1 << 0)
+#define RUTABAGA_FLAG_INFO_RING_IDX (1 << 1)
+
+/**
+ * Rutabaga channel types
+ */
+#define RUTABAGA_CHANNEL_TYPE_WAYLAND 1
+#define RUTABAGA_CHANNEL_TYPE_CAMERA 2
+
+/**
+ * Rutabaga handle types
+ */
+#define RUTABAGA_MEM_HANDLE_TYPE_OPAQUE_FD 0x1
+#define RUTABAGA_MEM_HANDLE_TYPE_DMABUF 0x2
+#define RUTABAGE_MEM_HANDLE_TYPE_OPAQUE_WIN32 0x3
+#define RUTABAGA_MEM_HANDLE_TYPE_SHM 0x4
+#define RUTABAGA_FENCE_HANDLE_TYPE_OPAQUE_FD 0x10
+#define RUTABAGA_FENCE_HANDLE_TYPE_SYNC_FD 0x11
+#define RUTABAGE_FENCE_HANDLE_TYPE_OPAQUE_WIN32 0x12
+
+struct rutabaga;
+
+struct rutabaga_fence {
+ uint32_t flags;
+ uint64_t fence_id;
+ uint32_t ctx_id;
+ uint32_t ring_idx;
+};
+
+struct rutabaga_create_blob {
+ uint32_t blob_mem;
+ uint32_t blob_flags;
+ uint64_t blob_id;
+ uint64_t size;
+};
+
+struct rutabaga_create_3d {
+ uint32_t target;
+ uint32_t format;
+ uint32_t bind;
+ uint32_t width;
+ uint32_t height;
+ uint32_t depth;
+ uint32_t array_size;
+ uint32_t last_level;
+ uint32_t nr_samples;
+ uint32_t flags;
+};
+
+struct rutabaga_transfer {
+ uint32_t x;
+ uint32_t y;
+ uint32_t z;
+ uint32_t w;
+ uint32_t h;
+ uint32_t d;
+ uint32_t level;
+ uint32_t stride;
+ uint32_t layer_stride;
+ uint64_t offset;
+};
+
+struct rutabaga_iovecs {
+ struct iovec *iovecs;
+ size_t num_iovecs;
+};
+
+struct rutabaga_handle {
+ int64_t os_handle;
+ uint32_t handle_type;
+};
+
+/**
+ * Assumes null-terminated C-string.
+ */
+struct rutabaga_channel {
+ char *channel_name;
+ uint32_t channel_type;
+};
+
+struct rutabaga_channels {
+ struct rutabaga_channel *channels;
+ size_t num_channels;
+};
+
+/**
+ * Throwing an exception inside this callback is not allowed.
+ */
+typedef void (*write_fence_cb)(uint64_t user_data, struct rutabaga_fence fence_data);
+
+struct rutabaga_builder {
+ uint64_t user_data;
+ uint32_t rutabaga_component;
+ write_fence_cb fence_cb;
+ struct rutabaga_channels *channels;
+};
+
+/**
+ * # Safety
+ * - If `(*builder).channels` is not null, the caller must ensure `(*channels).channels` points to
+ * a valid array of `struct rutabaga_channel` of size `(*channels).num_channels`.
+ * - The `channel_name` field of `struct rutabaga_channel` must be a null-terminated C-string.
+ */
+int32_t rutabaga_init(const struct rutabaga_builder *builder, struct rutabaga **ptr);
+
+/**
+ * # Safety
+ * - `ptr` must have been created by `rutabaga_init`.
+ */
+int32_t rutabaga_finish(struct rutabaga **ptr);
+
+uint32_t rutabaga_get_num_capsets(void);
+
+int32_t rutabaga_get_capset_info(struct rutabaga *ptr, uint32_t capset_index, uint32_t *capset_id,
+ uint32_t *capset_version, uint32_t *capset_size);
+
+/**
+ * # Safety
+ * - `capset` must point an array of bytes of size `capset_size`.
+ */
+int32_t rutabaga_get_capset(struct rutabaga *ptr, uint32_t capset_id, uint32_t version,
+ uint8_t *capset, uint32_t capset_size);
+
+int32_t rutabaga_context_create(struct rutabaga *ptr, uint32_t ctx_id, uint32_t context_init);
+
+int32_t rutabaga_context_destroy(struct rutabaga *ptr, uint32_t ctx_id);
+
+int32_t rutabaga_context_attach_resource(struct rutabaga *ptr, uint32_t ctx_id,
+ uint32_t resource_id);
+
+int32_t rutabaga_context_detach_resource(struct rutabaga *ptr, uint32_t ctx_id,
+ uint32_t resource_id);
+
+int32_t rutabaga_resource_create_3d(struct rutabaga *ptr, uint32_t resource_id,
+ const struct rutabaga_create_3d *create_3d);
+
+/**
+ * # Safety
+ * - If `iovecs` is not null, the caller must ensure `(*iovecs).iovecs` points to a valid array of
+ * iovecs of size `(*iovecs).num_iovecs`.
+ * - Each iovec must point to valid memory starting at `iov_base` with length `iov_len`.
+ * - Each iovec must valid until the resource's backing is explictly detached or the resource is
+ * is unreferenced.
+ */
+int32_t rutabaga_resource_attach_backing(struct rutabaga *ptr, uint32_t resource_id,
+ const struct rutabaga_iovecs *iovecs);
+
+int32_t rutabaga_resource_detach_backing(struct rutabaga *ptr, uint32_t resource_id);
+
+/**
+ * # Safety
+ * - If `iovecs` is not null, the caller must ensure `(*iovecs).iovecs` points to a valid array of
+ * iovecs of size `(*iovecs).num_iovecs`.
+ */
+int32_t rutabaga_resource_transfer_read(struct rutabaga *ptr, uint32_t ctx_id, uint32_t resource_id,
+ const struct rutabaga_transfer *transfer,
+ const struct iovec *iovec);
+
+int32_t rutabaga_resource_transfer_write(struct rutabaga *ptr, uint32_t ctx_id,
+ uint32_t resource_id,
+ const struct rutabaga_transfer *transfer);
+
+/**
+ * # Safety
+ * - If `iovecs` is not null, the caller must ensure `(*iovecs).iovecs` points to a valid array of
+ * iovecs of size `(*iovecs).num_iovecs`.
+ * - If `handle` is not null, the caller must ensure it is a valid OS-descriptor. Ownership is
+ * transfered to rutabaga.
+ * - Each iovec must valid until the resource's backing is explictly detached or the resource is
+ * is unreferenced.
+ */
+int32_t rutabaga_resource_create_blob(struct rutabaga *ptr, uint32_t ctx_id, uint32_t resource_id,
+ const struct rutabaga_create_blob *rutabaga_create_blob,
+ const struct rutabaga_iovecs *iovecs,
+ const struct rutabaga_handle *handle);
+
+int32_t rutabaga_resource_unref(struct rutabaga *ptr, uint32_t resource_id);
+
+/**
+ * # Safety
+ * Caller owns raw descriptor on success and is responsible for closing it.
+ */
+int32_t rutabaga_resource_export_blob(struct rutabaga *ptr, uint32_t resource_id,
+ struct rutabaga_handle *handle);
+
+int32_t rutabaga_resource_map_info(struct rutabaga *ptr, uint32_t resource_id, uint32_t *map_info);
+
+/**
+ * # Safety
+ * - `commands` must point to a contiguous memory region of `size` bytes.
+ */
+int32_t rutabaga_submit_command(struct rutabaga *ptr, uint32_t ctx_id, uint8_t *commands,
+ size_t size);
+
+int32_t rutabaga_create_fence(struct rutabaga *ptr, const struct rutabaga_fence *fence);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/rutabaga_gfx/ffi/src/lib.rs b/rutabaga_gfx/ffi/src/lib.rs
new file mode 100644
index 000000000..fe4d7b05d
--- /dev/null
+++ b/rutabaga_gfx/ffi/src/lib.rs
@@ -0,0 +1,495 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+///! C-bindings for the rutabaga_gfx crate
+extern crate rutabaga_gfx;
+
+use std::convert::TryInto;
+use std::ffi::CStr;
+use std::os::raw::c_char;
+use std::panic::{catch_unwind, AssertUnwindSafe};
+use std::path::PathBuf;
+use std::ptr::{copy_nonoverlapping, null_mut};
+use std::slice::{from_raw_parts, from_raw_parts_mut};
+
+use base::{error, FromRawDescriptor, IntoRawDescriptor, SafeDescriptor};
+use data_model::VolatileSlice;
+
+use libc::{iovec, EINVAL, ESRCH};
+
+use rutabaga_gfx::*;
+
+const NO_ERROR: i32 = 0;
+
+fn return_result<T>(result: RutabagaResult<T>) -> i32 {
+ if let Err(e) = result {
+ error!("Received an error {}", e);
+ -EINVAL
+ } else {
+ NO_ERROR
+ }
+}
+
+macro_rules! return_on_error {
+ ($result:expr) => {
+ match $result {
+ Ok(t) => t,
+ Err(e) => {
+ error!("Received an error {}", e);
+ return -EINVAL;
+ }
+ }
+ };
+}
+
+const RUTABAGA_COMPONENT_2D: u32 = 1;
+const RUTABAGA_COMPONENT_VIRGL_RENDERER: u32 = 2;
+const RUTABAGA_COMPONENT_GFXSTREAM: u32 = 3;
+const RUTABAGA_COMPONENT_CROSS_DOMAIN: u32 = 4;
+
+#[allow(non_camel_case_types)]
+type rutabaga = Rutabaga;
+
+#[allow(non_camel_case_types)]
+type rutabaga_create_blob = ResourceCreateBlob;
+
+#[allow(non_camel_case_types)]
+type rutabaga_create_3d = ResourceCreate3D;
+
+#[allow(non_camel_case_types)]
+type rutabaga_transfer = Transfer3D;
+
+#[allow(non_camel_case_types)]
+type rutabaga_fence = RutabagaFence;
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct rutabaga_iovecs {
+ pub iovecs: *mut iovec,
+ pub num_iovecs: usize,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct rutabaga_handle {
+ pub os_handle: i32,
+ pub handle_type: u32,
+}
+
+#[repr(C)]
+pub struct rutabaga_channel {
+ pub channel_name: *const c_char,
+ pub channel_type: u32,
+}
+
+#[repr(C)]
+pub struct rutabaga_channels {
+ pub channels: *const rutabaga_channel,
+ pub num_channels: usize,
+}
+
+#[allow(non_camel_case_types)]
+pub type write_fence_cb = extern "C" fn(user_data: u64, fence_data: rutabaga_fence);
+
+#[repr(C)]
+pub struct rutabaga_builder<'a> {
+ pub user_data: u64,
+ pub default_component: u32,
+ pub fence_cb: write_fence_cb,
+ pub channels: Option<&'a rutabaga_channels>,
+}
+
+fn create_ffi_fence_handler(user_data: u64, fence_cb: write_fence_cb) -> RutabagaFenceHandler {
+ RutabagaFenceClosure::new(move |completed_fence| fence_cb(user_data, completed_fence))
+}
+
+/// # Safety
+/// - If `(*builder).channels` is not null, the caller must ensure `(*channels).channels` points to
+/// a valid array of `struct rutabaga_channel` of size `(*channels).num_channels`.
+/// - The `channel_name` field of `struct rutabaga_channel` must be a null-terminated C-string.
+#[no_mangle]
+pub unsafe extern "C" fn rutabaga_init(builder: &rutabaga_builder, ptr: &mut *mut rutabaga) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let fence_handler = create_ffi_fence_handler((*builder).user_data, (*builder).fence_cb);
+
+ let component = match (*builder).default_component {
+ RUTABAGA_COMPONENT_2D => RutabagaComponentType::Rutabaga2D,
+ RUTABAGA_COMPONENT_VIRGL_RENDERER => RutabagaComponentType::VirglRenderer,
+ RUTABAGA_COMPONENT_GFXSTREAM => RutabagaComponentType::Gfxstream,
+ RUTABAGA_COMPONENT_CROSS_DOMAIN => RutabagaComponentType::CrossDomain,
+ _ => {
+ error!("unknown component type");
+ return -EINVAL;
+ }
+ };
+
+ let virglrenderer_flags = VirglRendererFlags::new()
+ .use_egl(true)
+ .use_surfaceless(true)
+ .use_external_blob(true);
+
+ let gfxstream_flags = GfxstreamFlags::new()
+ .use_egl(true)
+ .use_surfaceless(true)
+ .use_guest_angle(true)
+ .use_syncfd(true)
+ .use_vulkan(true);
+
+ let mut rutabaga_channels_opt = None;
+ if let Some(channels) = (*builder).channels {
+ let mut rutabaga_channels: Vec<RutabagaChannel> = Vec::new();
+ let channels_slice = from_raw_parts(channels.channels, channels.num_channels);
+
+ for channel in channels_slice {
+ let c_str_slice = CStr::from_ptr(channel.channel_name);
+ let result = c_str_slice.to_str();
+ let str_slice = return_on_error!(result);
+ let string = str_slice.to_owned();
+ let path = PathBuf::from(&string);
+
+ rutabaga_channels.push(RutabagaChannel {
+ base_channel: path,
+ channel_type: channel.channel_type,
+ });
+ }
+
+ rutabaga_channels_opt = Some(rutabaga_channels);
+ }
+ let result = RutabagaBuilder::new(component)
+ .set_virglrenderer_flags(virglrenderer_flags)
+ .set_gfxstream_flags(gfxstream_flags)
+ .set_rutabaga_channels(rutabaga_channels_opt)
+ .build(fence_handler, None);
+
+ let rtbg = return_on_error!(result);
+ *ptr = Box::into_raw(Box::new(rtbg)) as _;
+ NO_ERROR
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+/// # Safety
+/// - `ptr` must have been created by `rutabaga_init`.
+#[no_mangle]
+pub extern "C" fn rutabaga_finish(ptr: &mut *mut rutabaga) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ unsafe { Box::from_raw(*ptr) };
+ *ptr = null_mut();
+ NO_ERROR
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+#[no_mangle]
+pub extern "C" fn rutabaga_get_num_capsets() -> u32 {
+ let mut num_capsets = 0;
+
+ // Cross-domain (like virtio_wl with llvmpipe) is always available.
+ num_capsets += 1;
+
+ // Three capsets for virgl_renderer
+ #[cfg(feature = "virgl_renderer")]
+ {
+ num_capsets += 3;
+ }
+
+ // One capset for gfxstream
+ #[cfg(feature = "gfxstream")]
+ {
+ num_capsets += 1;
+ }
+
+ num_capsets
+}
+
+#[no_mangle]
+pub extern "C" fn rutabaga_get_capset_info(
+ ptr: &mut rutabaga,
+ capset_index: u32,
+ capset_id: &mut u32,
+ capset_version: &mut u32,
+ capset_size: &mut u32,
+) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let result = ptr.get_capset_info(capset_index);
+ let info = return_on_error!(result);
+ *capset_id = info.0;
+ *capset_version = info.1;
+ *capset_size = info.2;
+ NO_ERROR
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+/// # Safety
+/// - `capset` must point an array of bytes of size `capset_size`.
+#[no_mangle]
+pub unsafe extern "C" fn rutabaga_get_capset(
+ ptr: &mut rutabaga,
+ capset_id: u32,
+ version: u32,
+ capset: *mut u8,
+ capset_size: u32,
+) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let size: usize = capset_size.try_into().map_err(|_e| -EINVAL).unwrap();
+ let result = ptr.get_capset(capset_id, version);
+ let vec = return_on_error!(result);
+ copy_nonoverlapping(vec.as_ptr(), capset, size);
+ NO_ERROR
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+#[no_mangle]
+pub extern "C" fn rutabaga_context_create(
+ ptr: &mut rutabaga,
+ ctx_id: u32,
+ context_init: u32,
+) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let result = ptr.create_context(ctx_id, context_init);
+ return_result(result)
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+#[no_mangle]
+pub extern "C" fn rutabaga_context_destroy(ptr: &mut rutabaga, ctx_id: u32) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let result = ptr.destroy_context(ctx_id);
+ return_result(result)
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+#[no_mangle]
+pub extern "C" fn rutabaga_context_attach_resource(
+ ptr: &mut rutabaga,
+ ctx_id: u32,
+ resource_id: u32,
+) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let result = ptr.context_attach_resource(ctx_id, resource_id);
+ return_result(result)
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+#[no_mangle]
+pub extern "C" fn rutabaga_context_detach_resource(
+ ptr: &mut rutabaga,
+ ctx_id: u32,
+ resource_id: u32,
+) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let result = ptr.context_detach_resource(ctx_id, resource_id);
+ return_result(result)
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+#[no_mangle]
+pub extern "C" fn rutabaga_resource_create_3d(
+ ptr: &mut rutabaga,
+ resource_id: u32,
+ create_3d: &rutabaga_create_3d,
+) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let result = ptr.resource_create_3d(resource_id, *create_3d);
+ return_result(result)
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+/// # Safety
+/// - If `iovecs` is not null, the caller must ensure `(*iovecs).iovecs` points to a valid array of
+/// iovecs of size `(*iovecs).num_iovecs`.
+/// - Each iovec must point to valid memory starting at `iov_base` with length `iov_len`.
+/// - Each iovec must valid until the resource's backing is explictly detached or the resource is
+/// is unreferenced.
+#[no_mangle]
+pub unsafe extern "C" fn rutabaga_resource_attach_backing(
+ ptr: &mut rutabaga,
+ resource_id: u32,
+ iovecs: &rutabaga_iovecs,
+) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let slice = from_raw_parts((*iovecs).iovecs, (*iovecs).num_iovecs);
+ let vecs = slice
+ .iter()
+ .map(|iov| RutabagaIovec {
+ base: iov.iov_base,
+ len: iov.iov_len,
+ })
+ .collect();
+
+ let result = ptr.attach_backing(resource_id, vecs);
+ return_result(result)
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+#[no_mangle]
+pub extern "C" fn rutabaga_resource_detach_backing(ptr: &mut rutabaga, resource_id: u32) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let result = ptr.detach_backing(resource_id);
+ return_result(result)
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+/// # Safety
+/// - If `iovecs` is not null, the caller must ensure `(*iovecs).iovecs` points to a valid array of
+/// iovecs of size `(*iovecs).num_iovecs`.
+#[no_mangle]
+pub unsafe extern "C" fn rutabaga_resource_transfer_read(
+ ptr: &mut rutabaga,
+ ctx_id: u32,
+ resource_id: u32,
+ transfer: &rutabaga_transfer,
+ buf: Option<&iovec>,
+) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let mut slice_opt = None;
+ if let Some(iovec) = buf {
+ slice_opt = Some(VolatileSlice::from_raw_parts(
+ iovec.iov_base as *mut u8,
+ iovec.iov_len,
+ ));
+ }
+
+ let result = ptr.transfer_read(ctx_id, resource_id, *transfer, slice_opt);
+ return_result(result)
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+#[no_mangle]
+pub extern "C" fn rutabaga_resource_transfer_write(
+ ptr: &mut rutabaga,
+ ctx_id: u32,
+ resource_id: u32,
+ transfer: &rutabaga_transfer,
+) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let result = ptr.transfer_write(ctx_id, resource_id, *transfer);
+ return_result(result)
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+/// # Safety
+/// - If `iovecs` is not null, the caller must ensure `(*iovecs).iovecs` points to a valid array of
+/// iovecs of size `(*iovecs).num_iovecs`.
+/// - If `handle` is not null, the caller must ensure it is a valid OS-descriptor. Ownership is
+/// transfered to rutabaga.
+/// - Each iovec must valid until the resource's backing is explictly detached or the resource is
+/// is unreferenced.
+#[no_mangle]
+pub unsafe extern "C" fn rutabaga_resource_create_blob(
+ ptr: &mut rutabaga,
+ ctx_id: u32,
+ resource_id: u32,
+ create_blob: &rutabaga_create_blob,
+ iovecs: Option<&rutabaga_iovecs>,
+ handle: Option<&rutabaga_handle>,
+) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let mut iovecs_opt: Option<Vec<RutabagaIovec>> = None;
+ if let Some(iovs) = iovecs {
+ let slice = from_raw_parts((*iovs).iovecs, (*iovs).num_iovecs);
+ let vecs = slice
+ .iter()
+ .map(|iov| RutabagaIovec {
+ base: iov.iov_base,
+ len: iov.iov_len,
+ })
+ .collect();
+ iovecs_opt = Some(vecs);
+ }
+
+ let mut handle_opt: Option<RutabagaHandle> = None;
+ if let Some(hnd) = handle {
+ handle_opt = Some(RutabagaHandle {
+ os_handle: SafeDescriptor::from_raw_descriptor((*hnd).os_handle),
+ handle_type: (*hnd).handle_type,
+ });
+ }
+
+ let result =
+ ptr.resource_create_blob(ctx_id, resource_id, *create_blob, iovecs_opt, handle_opt);
+
+ return_result(result)
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+#[no_mangle]
+pub extern "C" fn rutabaga_resource_unref(ptr: &mut rutabaga, resource_id: u32) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let result = ptr.unref_resource(resource_id);
+ return_result(result)
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+/// # Safety
+/// Caller owns raw descriptor on success and is responsible for closing it.
+#[no_mangle]
+pub extern "C" fn rutabaga_resource_export_blob(
+ ptr: &mut rutabaga,
+ resource_id: u32,
+ handle: &mut rutabaga_handle,
+) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let result = ptr.export_blob(resource_id);
+ let hnd = return_on_error!(result);
+
+ (*handle).handle_type = hnd.handle_type;
+ (*handle).os_handle = hnd.os_handle.into_raw_descriptor();
+ NO_ERROR
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+#[no_mangle]
+pub extern "C" fn rutabaga_resource_map_info(
+ ptr: &mut rutabaga,
+ resource_id: u32,
+ map_info: &mut u32,
+) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let result = ptr.map_info(resource_id);
+ *map_info = return_on_error!(result);
+ NO_ERROR
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+/// # Safety
+/// - `commands` must point to a contiguous memory region of `size` bytes.
+#[no_mangle]
+pub unsafe extern "C" fn rutabaga_submit_command(
+ ptr: &mut rutabaga,
+ ctx_id: u32,
+ commands: *mut u8,
+ size: usize,
+) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let cmd_slice = from_raw_parts_mut(commands, size);
+ let result = ptr.submit_command(ctx_id, cmd_slice);
+ return_result(result)
+ }))
+ .unwrap_or(-ESRCH)
+}
+
+#[no_mangle]
+pub extern "C" fn rutabaga_create_fence(ptr: &mut rutabaga, fence: &rutabaga_fence) -> i32 {
+ catch_unwind(AssertUnwindSafe(|| {
+ let result = ptr.create_fence(*fence);
+ return_result(result)
+ }))
+ .unwrap_or(-ESRCH)
+}
diff --git a/rutabaga_gfx/ffi/src/tests/Makefile b/rutabaga_gfx/ffi/src/tests/Makefile
new file mode 100644
index 000000000..2fa3b61fa
--- /dev/null
+++ b/rutabaga_gfx/ffi/src/tests/Makefile
@@ -0,0 +1,24 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+RUTABAGA_TEST = rutabaga_test
+SOURCES += rutabaga_test.c
+OBJS = $(SOURCES:.c=.o)
+DEPS = $(SOURCES:.c=.d)
+PKG_CONFIG ?= pkg-config
+CFLAGS += -O0 -ggdb3
+CCFLAGS += $(shell $(PKG_CONFIG) --cflags rutabaga_gfx_ffi)
+LDLIBS += $(PC_LIBS)
+LDLIBS += $(shell $(PKG_CONFIG) --libs rutabaga_gfx_ffi)
+.PHONY: all clean
+all: $(RUTABAGA_TEST)
+$(RUTABAGA_TEST): $(OBJS)
+clean:
+ $(RM) $(RUTABAGA_TEST)
+ $(RM) $(OBJS) $(DEPS)
+ $(RM) *.o *.d .version
+$(RUTABAGA_TEST):
+ $(CC) $(CCFLAGS) $(CFLAGS) $(LDFLAGS) $^ -o $@ $(LDLIBS)
+$(OBJS): %.o: %.c
+ $(CC) $(CCFLAGS) $(CFLAGS) -c $< -o $@ -MMD
+-include $(DEPS)
diff --git a/rutabaga_gfx/ffi/src/tests/rutabaga_test.c b/rutabaga_gfx/ffi/src/tests/rutabaga_test.c
new file mode 100644
index 000000000..356b6fc67
--- /dev/null
+++ b/rutabaga_gfx/ffi/src/tests/rutabaga_test.c
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2021 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#define _GNU_SOURCE
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../include/rutabaga_gfx_ffi.h"
+#include "virtgpu_cross_domain_protocol.h"
+
+#define CHECK_RESULT(result) \
+ do { \
+ if (result) { \
+ printf("CHECK_RESULT failed in %s() %s:%d\n", __func__, __FILE__, \
+ __LINE__); \
+ return result; \
+ } \
+ } while (0)
+
+#define CHECK(cond) \
+ do { \
+ if (!(cond)) { \
+ printf("CHECK failed in %s() %s:%d\n", __func__, __FILE__, __LINE__); \
+ return -EINVAL; \
+ } \
+ } while (0)
+
+#define DEFAULT_BUFFER_SIZE 4096
+#define WIDTH 512
+#define HEIGHT 512
+#define NUM_ITERATIONS 4
+
+#define GBM_BO_USE_LINEAR (1 << 4)
+#define GBM_BO_USE_SCANOUT (1 << 5)
+#define fourcc_code(a, b, c, d) \
+ ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))
+#define DRM_FORMAT_XRGB8888 fourcc_code('X', 'R', '2', '4');
+
+#define PIPE_TEXTURE_2D 2
+#define PIPE_BIND_RENDER_TARGET 2
+#define VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM 1
+
+static int s_resource_id = 1;
+static int s_fence_id = 1;
+
+#if defined(__linux__)
+static char *s_wayland_path = "/run/user/1000/wayland-0";
+#elif defined(__Fuchsia__)
+#endif
+
+struct rutabaga_test {
+ struct rutabaga *rutabaga;
+ uint32_t ctx_id;
+ uint64_t value;
+ uint32_t guest_blob_resource_id;
+ struct iovec *guest_iovecs;
+};
+
+static void rutabaga_test_write_fence(uint64_t user_data, struct rutabaga_fence fence_data)
+{
+ struct rutabaga_test *test = (void *)(uintptr_t)user_data;
+ test->value = fence_data.fence_id;
+}
+
+static int test_rutabaga_init(struct rutabaga_test *test, uint32_t component)
+{
+ int result;
+ struct rutabaga_builder builder = { 0 };
+ struct rutabaga_channels channels = { 0 };
+
+ builder.fence_cb = rutabaga_test_write_fence;
+ builder.rutabaga_component = component;
+ if (component == RUTABAGA_COMPONENT_CROSS_DOMAIN) {
+ builder.user_data = (uint64_t)(uintptr_t *)(void *)test;
+ channels.channels =
+ (struct rutabaga_channel *)calloc(1, sizeof(struct rutabaga_channel));
+ channels.num_channels = 1;
+
+ channels.channels[0].channel_name = s_wayland_path;
+ channels.channels[0].channel_type = RUTABAGA_CHANNEL_TYPE_WAYLAND;
+
+ builder.channels = &channels;
+ }
+
+ result = rutabaga_init(&builder, &test->rutabaga);
+
+ if (component == RUTABAGA_COMPONENT_CROSS_DOMAIN)
+ free(channels.channels);
+
+ CHECK_RESULT(result);
+ return 0;
+}
+
+static int test_create_context(struct rutabaga_test *test)
+{
+ int result;
+ uint32_t num_capsets;
+ uint32_t capset_id, capset_version, capset_size;
+ bool found_cross_domain = false;
+ struct CrossDomainCapabilities *capset;
+
+ num_capsets = rutabaga_get_num_capsets();
+ CHECK(num_capsets > 0);
+
+ for (uint32_t i = 0; i < num_capsets; i++) {
+ result = rutabaga_get_capset_info(test->rutabaga, i, &capset_id, &capset_version,
+ &capset_size);
+ CHECK_RESULT(result);
+ if (capset_id == RUTABAGA_CAPSET_CROSS_DOMAIN) {
+ found_cross_domain = true;
+ CHECK(capset_size == (uint32_t)sizeof(struct CrossDomainCapabilities));
+ }
+ }
+
+ CHECK(found_cross_domain);
+ CHECK_RESULT(result);
+
+ capset = (struct CrossDomainCapabilities *)calloc(1, capset_size);
+ result = rutabaga_get_capset(test->rutabaga, RUTABAGA_CAPSET_CROSS_DOMAIN, 0,
+ (uint8_t *)capset, capset_size);
+ CHECK_RESULT(result);
+
+ CHECK(capset->version == 1);
+ free(capset);
+
+ result =
+ rutabaga_context_create(test->rutabaga, test->ctx_id, RUTABAGA_CAPSET_CROSS_DOMAIN);
+ CHECK_RESULT(result);
+
+ return 0;
+}
+
+static int test_init_context(struct rutabaga_test *test)
+{
+ int result;
+ struct rutabaga_create_blob rc_blob = { 0 };
+ struct rutabaga_iovecs vecs = { 0 };
+ struct CrossDomainInit cmd_init = { { 0 } };
+
+ struct iovec *iovecs = (struct iovec *)calloc(1, sizeof(struct iovec));
+ iovecs[0].iov_base = calloc(1, DEFAULT_BUFFER_SIZE);
+ iovecs[0].iov_len = DEFAULT_BUFFER_SIZE;
+
+ test->guest_iovecs = iovecs;
+ rc_blob.blob_mem = RUTABAGA_BLOB_MEM_GUEST;
+ rc_blob.blob_flags = RUTABAGA_BLOB_FLAG_USE_MAPPABLE;
+ rc_blob.size = DEFAULT_BUFFER_SIZE;
+
+ vecs.iovecs = iovecs;
+ vecs.num_iovecs = 1;
+
+ result = rutabaga_resource_create_blob(test->rutabaga, 0, test->guest_blob_resource_id,
+ &rc_blob, &vecs, NULL);
+ CHECK_RESULT(result);
+
+ result = rutabaga_context_attach_resource(test->rutabaga, test->ctx_id,
+ test->guest_blob_resource_id);
+ CHECK_RESULT(result);
+
+ cmd_init.hdr.cmd = CROSS_DOMAIN_CMD_INIT;
+ cmd_init.hdr.cmd_size = sizeof(struct CrossDomainInit);
+ cmd_init.ring_id = test->guest_blob_resource_id;
+ cmd_init.channel_type = CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND;
+
+ result = rutabaga_submit_command(test->rutabaga, test->ctx_id, (uint8_t *)&cmd_init,
+ cmd_init.hdr.cmd_size);
+ CHECK_RESULT(result);
+ return 0;
+}
+
+static int test_command_submission(struct rutabaga_test *test)
+{
+ int result;
+ struct CrossDomainGetImageRequirements cmd_get_reqs = { 0 };
+ struct CrossDomainImageRequirements *image_reqs = (void *)test->guest_iovecs[0].iov_base;
+ struct rutabaga_create_blob rc_blob = { 0 };
+ struct rutabaga_fence fence;
+ struct rutabaga_handle handle = { 0 };
+ uint32_t map_info;
+
+ fence.flags = RUTABAGA_FLAG_FENCE | RUTABAGA_FLAG_INFO_RING_IDX;
+ fence.ctx_id = test->ctx_id;
+ fence.ring_idx = 0;
+
+ cmd_get_reqs.hdr.cmd = CROSS_DOMAIN_CMD_GET_IMAGE_REQUIREMENTS;
+ cmd_get_reqs.hdr.cmd_size = sizeof(struct CrossDomainGetImageRequirements);
+
+ for (uint32_t i = 0; i < NUM_ITERATIONS; i++) {
+ for (uint32_t j = 0; j < NUM_ITERATIONS; j++) {
+ fence.fence_id = s_fence_id;
+ map_info = 0;
+
+ cmd_get_reqs.width = WIDTH * i;
+ cmd_get_reqs.height = HEIGHT * j;
+ cmd_get_reqs.drm_format = DRM_FORMAT_XRGB8888;
+
+ cmd_get_reqs.flags = GBM_BO_USE_LINEAR | GBM_BO_USE_SCANOUT;
+
+ result = rutabaga_submit_command(test->rutabaga, test->ctx_id,
+ (uint8_t *)&cmd_get_reqs,
+ cmd_get_reqs.hdr.cmd_size);
+
+ CHECK(test->value < fence.fence_id);
+ result = rutabaga_create_fence(test->rutabaga, &fence);
+
+ CHECK_RESULT(result);
+ for (;;) {
+ if (fence.fence_id == test->value)
+ break;
+ }
+
+ CHECK(image_reqs->strides[0] >= cmd_get_reqs.width * 4);
+ CHECK(image_reqs->size >= (cmd_get_reqs.width * 4) * cmd_get_reqs.height);
+
+ rc_blob.blob_mem = RUTABAGA_BLOB_MEM_HOST3D;
+ rc_blob.blob_flags =
+ RUTABAGA_BLOB_FLAG_USE_MAPPABLE | RUTABAGA_BLOB_FLAG_USE_SHAREABLE;
+ rc_blob.blob_id = image_reqs->blob_id;
+ rc_blob.size = image_reqs->size;
+
+ result = rutabaga_resource_create_blob(test->rutabaga, test->ctx_id,
+ s_resource_id, &rc_blob, NULL, NULL);
+ CHECK_RESULT(result);
+
+ result = rutabaga_context_attach_resource(test->rutabaga, test->ctx_id,
+ s_resource_id);
+ CHECK_RESULT(result);
+
+ result =
+ rutabaga_resource_map_info(test->rutabaga, s_resource_id, &map_info);
+ CHECK_RESULT(result);
+ CHECK(map_info > 0);
+
+ result =
+ rutabaga_resource_export_blob(test->rutabaga, s_resource_id, &handle);
+ CHECK_RESULT(result);
+ CHECK(handle.os_handle >= 0);
+
+ result = close(handle.os_handle);
+ CHECK_RESULT(result);
+
+ result = rutabaga_context_detach_resource(test->rutabaga, test->ctx_id,
+ s_resource_id);
+ CHECK_RESULT(result);
+
+ result = rutabaga_resource_unref(test->rutabaga, s_resource_id);
+ CHECK_RESULT(result);
+
+ s_resource_id++;
+ s_fence_id++;
+ }
+ }
+
+ return 0;
+}
+
+static int test_context_finish(struct rutabaga_test *test)
+{
+ int result;
+
+ result = rutabaga_context_detach_resource(test->rutabaga, test->ctx_id,
+ test->guest_blob_resource_id);
+ CHECK_RESULT(result);
+
+ result = rutabaga_resource_unref(test->rutabaga, test->guest_blob_resource_id);
+ CHECK_RESULT(result);
+
+ free(test->guest_iovecs[0].iov_base);
+
+ result = rutabaga_context_destroy(test->rutabaga, test->ctx_id);
+ CHECK_RESULT(result);
+
+ return 0;
+}
+
+static int test_rutabaga_2d(struct rutabaga_test *test)
+{
+ struct rutabaga_create_3d rc_3d = { 0 };
+ struct rutabaga_transfer transfer = { 0 };
+ int result;
+ uint32_t resource_id = s_resource_id++;
+
+ struct rutabaga_iovecs vecs = { 0 };
+ struct iovec *iovecs = (struct iovec *)calloc(1, sizeof(struct iovec));
+ uint8_t *test_data;
+ struct iovec result_iovec;
+
+ iovecs[0].iov_base = calloc(1, DEFAULT_BUFFER_SIZE);
+ iovecs[0].iov_len = DEFAULT_BUFFER_SIZE;
+ result_iovec.iov_base = calloc(1, DEFAULT_BUFFER_SIZE);
+ result_iovec.iov_len = DEFAULT_BUFFER_SIZE;
+ test_data = (uint8_t *)result_iovec.iov_base;
+
+ vecs.iovecs = iovecs;
+ vecs.num_iovecs = 1;
+
+ rc_3d.target = PIPE_TEXTURE_2D;
+ rc_3d.bind = PIPE_BIND_RENDER_TARGET;
+ rc_3d.format = VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM;
+ rc_3d.width = DEFAULT_BUFFER_SIZE / 16;
+ rc_3d.height = 4;
+
+ transfer.w = DEFAULT_BUFFER_SIZE / 16;
+ transfer.h = 4;
+ transfer.d = 1;
+
+ result = rutabaga_resource_create_3d(test->rutabaga, resource_id, &rc_3d);
+ CHECK_RESULT(result);
+
+ result = rutabaga_resource_attach_backing(test->rutabaga, resource_id, &vecs);
+ CHECK_RESULT(result);
+
+ memset(iovecs[0].iov_base, 8, DEFAULT_BUFFER_SIZE);
+
+ result = rutabaga_resource_transfer_read(test->rutabaga, 0, resource_id, &transfer,
+ &result_iovec);
+ CHECK_RESULT(result);
+
+ CHECK(test_data[0] == 0);
+
+ result = rutabaga_resource_transfer_write(test->rutabaga, 0, resource_id, &transfer);
+ CHECK_RESULT(result);
+
+ result = rutabaga_resource_transfer_read(test->rutabaga, 0, resource_id, &transfer,
+ &result_iovec);
+ CHECK_RESULT(result);
+
+ CHECK(test_data[0] == 8);
+
+ result = rutabaga_resource_detach_backing(test->rutabaga, resource_id);
+ CHECK_RESULT(result);
+
+ result = rutabaga_resource_unref(test->rutabaga, resource_id);
+ CHECK_RESULT(result);
+
+ free(iovecs[0].iov_base);
+ free(iovecs);
+ free(test_data);
+ return 0;
+}
+
+static int test_rutabaga_finish(struct rutabaga_test *test)
+{
+ int result;
+
+ result = rutabaga_finish(&test->rutabaga);
+ CHECK_RESULT(result);
+ CHECK(test->rutabaga == NULL);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct rutabaga_test test = { 0 };
+ test.ctx_id = 1;
+ test.guest_blob_resource_id = s_resource_id++;
+
+ int result;
+
+ for (uint32_t i = 0; i < NUM_ITERATIONS; i++) {
+ result = test_rutabaga_init(&test, RUTABAGA_COMPONENT_CROSS_DOMAIN);
+ CHECK_RESULT(result);
+
+ result |= test_create_context(&test);
+ CHECK_RESULT(result);
+
+ result |= test_init_context(&test);
+ CHECK_RESULT(result);
+
+ result |= test_command_submission(&test);
+ CHECK_RESULT(result);
+
+ result |= test_context_finish(&test);
+ CHECK_RESULT(result);
+
+ result |= test_rutabaga_finish(&test);
+ CHECK_RESULT(result);
+ }
+
+ for (uint32_t i = 0; i < NUM_ITERATIONS; i++) {
+ result = test_rutabaga_init(&test, RUTABAGA_COMPONENT_2D);
+ CHECK_RESULT(result);
+
+ result |= test_rutabaga_2d(&test);
+ CHECK_RESULT(result);
+
+ result |= test_rutabaga_finish(&test);
+ CHECK_RESULT(result);
+ }
+
+ printf("[ PASSED ] rutabaga_test success\n");
+ return 0;
+}
diff --git a/rutabaga_gfx/ffi/src/tests/virtgpu_cross_domain_protocol.h b/rutabaga_gfx/ffi/src/tests/virtgpu_cross_domain_protocol.h
new file mode 100644
index 000000000..afae541d0
--- /dev/null
+++ b/rutabaga_gfx/ffi/src/tests/virtgpu_cross_domain_protocol.h
@@ -0,0 +1,111 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef VIRTGPU_CROSS_DOMAIN_PROTOCOL_H
+#define VIRTGPU_CROSS_DOMAIN_PROTOCOL_H
+
+#include <stdint.h>
+
+// Cross-domain commands (only a maximum of 255 supported)
+#define CROSS_DOMAIN_CMD_INIT 1
+#define CROSS_DOMAIN_CMD_GET_IMAGE_REQUIREMENTS 2
+#define CROSS_DOMAIN_CMD_POLL 3
+#define CROSS_DOMAIN_CMD_SEND 4
+#define CROSS_DOMAIN_CMD_RECEIVE 5
+#define CROSS_DOMAIN_CMD_READ 6
+#define CROSS_DOMAIN_CMD_WRITE 7
+
+// Channel types (must match rutabaga channel types)
+#define CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND 0x0001
+#define CROSS_DOMAIN_CHANNEL_TYPE_CAMERA 0x0002
+
+// The maximum number of identifiers (value based on wp_linux_dmabuf)
+#define CROSS_DOMAIN_MAX_IDENTIFIERS 4
+
+// virtgpu memory resource ID. Also works with non-blob memory resources,
+// despite the name.
+#define CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB 1
+
+// virtgpu synchronization resource id.
+#define CROSS_DOMAIN_ID_TYPE_VIRTGPU_SYNC 2
+
+// ID for Wayland pipe used for reading. The reading is done by the guest proxy
+// and the host proxy. The host sends the write end of the proxied pipe over
+// the host Wayland socket.
+#define CROSS_DOMAIN_ID_TYPE_READ_PIPE 3
+
+// ID for Wayland pipe used for writing. The writing is done by the guest and
+// the host proxy. The host receives the write end of the pipe over the host
+// Wayland socket.
+#define CROSS_DOMAIN_ID_TYPE_WRITE_PIPE 4
+
+// No ring used
+#define CROSS_DOMAIN_RING_NONE 0xffffffff
+// A ring for metadata queries.
+#define CROSS_DOMAIN_QUERY_RING 0
+// A ring based on this particular context's channel.
+#define CROSS_DOMAIN_CHANNEL_RING 1
+
+struct CrossDomainCapabilities {
+ uint32_t version;
+ uint32_t supported_channels;
+ uint32_t supports_dmabuf;
+ uint32_t supports_external_gpu_memory;
+};
+
+struct CrossDomainImageRequirements {
+ uint32_t strides[4];
+ uint32_t offsets[4];
+ uint64_t modifier;
+ uint64_t size;
+ uint32_t blob_id;
+ uint32_t map_info;
+ int32_t memory_idx;
+ int32_t physical_device_idx;
+};
+
+struct CrossDomainHeader {
+ uint8_t cmd;
+ uint8_t fence_ctx_idx;
+ uint16_t cmd_size;
+ uint32_t pad;
+};
+
+struct CrossDomainInit {
+ struct CrossDomainHeader hdr;
+ uint32_t ring_id;
+ uint32_t channel_type;
+};
+
+struct CrossDomainGetImageRequirements {
+ struct CrossDomainHeader hdr;
+ uint32_t width;
+ uint32_t height;
+ uint32_t drm_format;
+ uint32_t flags;
+};
+
+struct CrossDomainPoll {
+ struct CrossDomainHeader hdr;
+ uint64_t pad;
+};
+
+struct CrossDomainSendReceive {
+ struct CrossDomainHeader hdr;
+ uint32_t num_identifiers;
+ uint32_t opaque_data_size;
+ uint32_t identifiers[CROSS_DOMAIN_MAX_IDENTIFIERS];
+ uint32_t identifier_types[CROSS_DOMAIN_MAX_IDENTIFIERS];
+ uint32_t identifier_sizes[CROSS_DOMAIN_MAX_IDENTIFIERS];
+};
+
+struct CrossDomainReadWrite {
+ struct CrossDomainHeader hdr;
+ uint32_t identifier;
+ uint32_t hang_up;
+ uint32_t opaque_data_size;
+ uint32_t pad;
+};
+
+#endif
diff --git a/rutabaga_gfx/patches/Android.bp.patch b/rutabaga_gfx/patches/Android.bp.patch
new file mode 100644
index 000000000..f6993f206
--- /dev/null
+++ b/rutabaga_gfx/patches/Android.bp.patch
@@ -0,0 +1,72 @@
+diff --git a/rutabaga_gfx/Android.bp b/rutabaga_gfx/Android.bp
+index 40b9b3f5..c368d22f 100644
+--- a/rutabaga_gfx/Android.bp
++++ b/rutabaga_gfx/Android.bp
+@@ -32,14 +32,25 @@ rust_library {
+ "libthiserror",
+ ],
+ proc_macros: ["libremain"],
+- static_libs: [
+- "libgbm",
+- "libvirglrenderer",
+- ],
+- shared_libs: [
+- "libdrm",
+- "libepoxy",
+- ],
++ target: {
++ host: {
++ features: ["gfxstream"],
++ shared_libs: [
++ "libgfxstream_backend",
++ "libvirglrenderer",
++ ],
++ },
++ android: {
++ shared_libs: [
++ "libdrm",
++ ],
++ static_libs: [
++ "libepoxy",
++ "libgbm",
++ "libvirglrenderer",
++ ],
++ },
++ },
+ }
+
+ rust_defaults {
+@@ -65,14 +73,25 @@ rust_defaults {
+ "libthiserror",
+ ],
+ proc_macros: ["libremain"],
+- static_libs: [
+- "libgbm",
+- "libvirglrenderer",
+- ],
+- shared_libs: [
+- "libdrm",
+- "libepoxy",
+- ],
++ target: {
++ host: {
++ features: ["gfxstream"],
++ shared_libs: [
++ "libgfxstream_backend",
++ "libvirglrenderer",
++ ],
++ },
++ android: {
++ shared_libs: [
++ "libdrm",
++ ],
++ static_libs: [
++ "libepoxy",
++ "libgbm",
++ "libvirglrenderer",
++ ],
++ },
++ },
+ }
+
+ rust_test_host {
diff --git a/rutabaga_gfx/src/cross_domain/cross_domain.rs b/rutabaga_gfx/src/cross_domain/cross_domain.rs
index a7b3ac999..08d340397 100644
--- a/rutabaga_gfx/src/cross_domain/cross_domain.rs
+++ b/rutabaga_gfx/src/cross_domain/cross_domain.rs
@@ -6,9 +6,20 @@
//! boundaries.
use std::collections::BTreeMap as Map;
+use std::collections::VecDeque;
+use std::convert::TryInto;
+use std::fs::File;
+use std::io::{IoSliceMut, Seek, SeekFrom};
use std::mem::size_of;
use std::os::unix::net::UnixStream;
+use std::ptr::copy_nonoverlapping;
use std::sync::Arc;
+use std::thread;
+
+use base::{
+ error, pipe, AsRawDescriptor, Event, FileFlags, FileReadWriteVolatile, FromRawDescriptor,
+ PollToken, SafeDescriptor, ScmSocket, WaitContext,
+};
use crate::cross_domain::cross_domain_protocol::*;
use crate::rutabaga_core::{RutabagaComponent, RutabagaContext, RutabagaResource};
@@ -19,13 +30,79 @@ use crate::{
use data_model::{DataInit, VolatileMemory, VolatileSlice};
-use sync::Mutex;
+use sync::{Condvar, Mutex};
+
+const CROSS_DOMAIN_DEFAULT_BUFFER_SIZE: usize = 4096;
+const CROSS_DOMAIN_MAX_SEND_RECV_SIZE: usize =
+ CROSS_DOMAIN_DEFAULT_BUFFER_SIZE - size_of::<CrossDomainSendReceive>();
+
+#[derive(PollToken)]
+enum CrossDomainToken {
+ ContextChannel,
+ WaylandReadPipe(u32),
+ Resample,
+ Kill,
+}
+
+enum CrossDomainItem {
+ ImageRequirements(ImageMemoryRequirements),
+ WaylandKeymap(SafeDescriptor),
+ WaylandReadPipe(File),
+ WaylandWritePipe(File),
+}
+
+enum CrossDomainJob {
+ HandleFence(RutabagaFence),
+ AddReadPipe(u32),
+}
+
+enum RingWrite<'a, T> {
+ Write(T, Option<&'a [u8]>),
+ WriteFromFile(CrossDomainReadWrite, &'a mut File, bool),
+}
+
+type CrossDomainResources = Arc<Mutex<Map<u32, CrossDomainResource>>>;
+type CrossDomainJobs = Mutex<Option<VecDeque<CrossDomainJob>>>;
+type CrossDomainItemState = Arc<Mutex<CrossDomainItems>>;
struct CrossDomainResource {
pub handle: Option<Arc<RutabagaHandle>>,
pub backing_iovecs: Option<Vec<RutabagaIovec>>,
}
+struct CrossDomainItems {
+ descriptor_id: u32,
+ requirements_blob_id: u32,
+ table: Map<u32, CrossDomainItem>,
+}
+
+struct CrossDomainState {
+ context_resources: CrossDomainResources,
+ ring_id: u32,
+ connection: Option<UnixStream>,
+ jobs: CrossDomainJobs,
+ jobs_cvar: Condvar,
+}
+
+struct CrossDomainWorker {
+ wait_ctx: WaitContext<CrossDomainToken>,
+ state: Arc<CrossDomainState>,
+ item_state: CrossDomainItemState,
+ fence_handler: RutabagaFenceHandler,
+}
+
+struct CrossDomainContext {
+ channels: Option<Vec<RutabagaChannel>>,
+ gralloc: Arc<Mutex<RutabagaGralloc>>,
+ state: Option<Arc<CrossDomainState>>,
+ context_resources: CrossDomainResources,
+ item_state: CrossDomainItemState,
+ fence_handler: RutabagaFenceHandler,
+ worker_thread: Option<thread::JoinHandle<RutabagaResult<()>>>,
+ resample_evt: Option<Event>,
+ kill_evt: Option<Event>,
+}
+
/// The CrossDomain component contains a list of channels that the guest may connect to and the
/// ability to allocate memory.
pub struct CrossDomain {
@@ -33,15 +110,363 @@ pub struct CrossDomain {
gralloc: Arc<Mutex<RutabagaGralloc>>,
}
-struct CrossDomainContext {
- channels: Option<Vec<RutabagaChannel>>,
- gralloc: Arc<Mutex<RutabagaGralloc>>,
- connection: Option<UnixStream>,
- ring_id: u32,
- requirements_blobs: Map<u64, ImageMemoryRequirements>,
- context_resources: Map<u32, CrossDomainResource>,
- last_fence_data: Option<RutabagaFenceData>,
- blob_id: u64,
+// TODO(gurchetansingh): optimize the item tracker. Each requirements blob is long-lived and can
+// be stored in a Slab or vector. Descriptors received from the Wayland socket *seem* to come one
+// at a time, and can be stored as options. Need to confirm.
+fn add_item(item_state: &CrossDomainItemState, item: CrossDomainItem) -> u32 {
+ let mut items = item_state.lock();
+
+ let item_id = match item {
+ CrossDomainItem::ImageRequirements(_) => {
+ items.requirements_blob_id += 2;
+ items.requirements_blob_id
+ }
+ _ => {
+ items.descriptor_id += 2;
+ items.descriptor_id
+ }
+ };
+
+ items.table.insert(item_id, item);
+
+ item_id
+}
+
+// Determine type of OS-specific descriptor. See `from_file` in wl.rs for explantation on the
+// current, Linux-based method.
+fn descriptor_analysis(
+ descriptor: &mut File,
+ descriptor_type: &mut u32,
+ size: &mut u32,
+) -> RutabagaResult<()> {
+ match descriptor.seek(SeekFrom::End(0)) {
+ Ok(seek_size) => {
+ *descriptor_type = CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB;
+ *size = seek_size.try_into()?;
+ Ok(())
+ }
+ _ => {
+ *descriptor_type = match FileFlags::from_file(descriptor) {
+ Ok(FileFlags::Write) => CROSS_DOMAIN_ID_TYPE_WRITE_PIPE,
+ _ => return Err(RutabagaError::InvalidCrossDomainItemType),
+ };
+ Ok(())
+ }
+ }
+}
+
+impl Default for CrossDomainItems {
+ fn default() -> Self {
+ // Odd for descriptors, and even for requirement blobs.
+ CrossDomainItems {
+ descriptor_id: 1,
+ requirements_blob_id: 2,
+ table: Default::default(),
+ }
+ }
+}
+
+impl CrossDomainState {
+ fn new(
+ ring_id: u32,
+ context_resources: CrossDomainResources,
+ connection: Option<UnixStream>,
+ ) -> CrossDomainState {
+ CrossDomainState {
+ ring_id,
+ context_resources,
+ connection,
+ jobs: Mutex::new(Some(VecDeque::new())),
+ jobs_cvar: Condvar::new(),
+ }
+ }
+
+ fn add_job(&self, job: CrossDomainJob) {
+ let mut jobs = self.jobs.lock();
+ if let Some(queue) = jobs.as_mut() {
+ queue.push_back(job);
+ self.jobs_cvar.notify_one();
+ }
+ }
+
+ fn end_jobs(&self) {
+ let mut jobs = self.jobs.lock();
+ *jobs = None;
+ // Only one worker thread in the current implementation.
+ self.jobs_cvar.notify_one();
+ }
+
+ fn wait_for_job(&self) -> Option<CrossDomainJob> {
+ let mut jobs = self.jobs.lock();
+ loop {
+ match jobs.as_mut()?.pop_front() {
+ Some(job) => return Some(job),
+ None => jobs = self.jobs_cvar.wait(jobs),
+ }
+ }
+ }
+
+ fn write_to_ring<T>(&self, mut ring_write: RingWrite<T>) -> RutabagaResult<usize>
+ where
+ T: DataInit,
+ {
+ let mut context_resources = self.context_resources.lock();
+ let mut bytes_read: usize = 0;
+
+ let resource = context_resources
+ .get_mut(&self.ring_id)
+ .ok_or(RutabagaError::InvalidResourceId)?;
+
+ let iovecs = resource
+ .backing_iovecs
+ .as_mut()
+ .ok_or(RutabagaError::InvalidIovec)?;
+
+ // Safe because we've verified the iovecs are attached and owned only by this context.
+ let slice =
+ unsafe { VolatileSlice::from_raw_parts(iovecs[0].base as *mut u8, iovecs[0].len) };
+
+ match ring_write {
+ RingWrite::Write(cmd, opaque_data_opt) => {
+ slice.copy_from(&[cmd]);
+ if let Some(opaque_data) = opaque_data_opt {
+ let offset = size_of::<T>();
+ let sub_slice = slice.sub_slice(offset, opaque_data.len())?;
+ let dst_ptr = sub_slice.as_mut_ptr();
+ let src_ptr = opaque_data.as_ptr();
+
+ // Safe because:
+ //
+ // (1) The volatile slice has atleast `opaque_data.len()' bytes.
+ // (2) The both the destination and source are non-overlapping.
+ unsafe {
+ copy_nonoverlapping(src_ptr, dst_ptr, opaque_data.len());
+ }
+ }
+ }
+ RingWrite::WriteFromFile(mut cmd_read, ref mut file, readable) => {
+ let offset = size_of::<CrossDomainReadWrite>();
+ let sub_slice = slice.offset(offset)?;
+
+ if readable {
+ bytes_read = file.read_volatile(sub_slice)?;
+ }
+
+ if bytes_read == 0 {
+ cmd_read.hang_up = 1;
+ }
+
+ cmd_read.opaque_data_size = bytes_read.try_into()?;
+ slice.copy_from(&[cmd_read]);
+ }
+ }
+
+ Ok(bytes_read)
+ }
+
+ fn send_msg(
+ &self,
+ opaque_data: &[VolatileSlice],
+ descriptors: &[i32],
+ ) -> RutabagaResult<usize> {
+ self.connection
+ .as_ref()
+ .ok_or(RutabagaError::InvalidCrossDomainChannel)
+ .and_then(|conn| Ok(conn.send_with_fds(opaque_data, descriptors)?))
+ }
+
+ fn receive_msg(
+ &self,
+ opaque_data: &mut [u8],
+ descriptors: &mut [i32; CROSS_DOMAIN_MAX_IDENTIFIERS],
+ ) -> RutabagaResult<(usize, Vec<File>)> {
+ // If any errors happen, the socket will get dropped, preventing more reading.
+ if let Some(connection) = &self.connection {
+ let mut files: Vec<File> = Vec::new();
+ let (len, file_count) =
+ connection.recv_with_fds(IoSliceMut::new(opaque_data), descriptors)?;
+
+ for descriptor in descriptors.iter_mut().take(file_count) {
+ // Safe since the descriptors from recv_with_fds(..) are owned by us and valid.
+ let file = unsafe { File::from_raw_descriptor(*descriptor) };
+ files.push(file);
+ }
+
+ Ok((len, files))
+ } else {
+ Err(RutabagaError::InvalidCrossDomainChannel)
+ }
+ }
+}
+
+impl CrossDomainWorker {
+ fn new(
+ wait_ctx: WaitContext<CrossDomainToken>,
+ state: Arc<CrossDomainState>,
+ item_state: CrossDomainItemState,
+ fence_handler: RutabagaFenceHandler,
+ ) -> CrossDomainWorker {
+ CrossDomainWorker {
+ wait_ctx,
+ state,
+ item_state,
+ fence_handler,
+ }
+ }
+
+ // Handles the fence according the the token according to the event token. On success, a
+ // boolean value indicating whether the worker thread should be stopped is returned.
+ fn handle_fence(
+ &mut self,
+ fence: RutabagaFence,
+ resample_evt: &Event,
+ receive_buf: &mut [u8],
+ ) -> RutabagaResult<bool> {
+ let events = self.wait_ctx.wait()?;
+ let mut stop_thread = false;
+
+ for event in &events {
+ match event.token {
+ CrossDomainToken::ContextChannel => {
+ let mut descriptors = [0; CROSS_DOMAIN_MAX_IDENTIFIERS];
+
+ let (len, files) = self.state.receive_msg(receive_buf, &mut descriptors)?;
+ if len != 0 || files.len() != 0 {
+ let mut cmd_receive: CrossDomainSendReceive = Default::default();
+
+ let num_files = files.len();
+ cmd_receive.hdr.cmd = CROSS_DOMAIN_CMD_RECEIVE;
+ cmd_receive.num_identifiers = files.len().try_into()?;
+ cmd_receive.opaque_data_size = len.try_into()?;
+
+ let iter = cmd_receive
+ .identifiers
+ .iter_mut()
+ .zip(cmd_receive.identifier_types.iter_mut())
+ .zip(cmd_receive.identifier_sizes.iter_mut())
+ .zip(files.into_iter())
+ .take(num_files);
+
+ for (((identifier, identifier_type), identifier_size), mut file) in iter {
+ // Safe since the descriptors from receive_msg(..) are owned by us and valid.
+ descriptor_analysis(&mut file, identifier_type, identifier_size)?;
+
+ *identifier = match *identifier_type {
+ CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB => add_item(
+ &self.item_state,
+ CrossDomainItem::WaylandKeymap(file.into()),
+ ),
+ CROSS_DOMAIN_ID_TYPE_WRITE_PIPE => add_item(
+ &self.item_state,
+ CrossDomainItem::WaylandWritePipe(file),
+ ),
+ _ => return Err(RutabagaError::InvalidCrossDomainItemType),
+ };
+ }
+
+ self.state.write_to_ring(RingWrite::Write(
+ cmd_receive,
+ Some(&receive_buf[0..len]),
+ ))?;
+ self.fence_handler.call(fence);
+ }
+ }
+ CrossDomainToken::Resample => {
+ // The resample event is triggered when the job queue is in the following state:
+ //
+ // [CrossDomain::AddReadPipe(..)] -> END
+ //
+ // After this event, the job queue will be the following state:
+ //
+ // [CrossDomain::AddReadPipe(..)] -> [CrossDomain::HandleFence(..)] -> END
+ //
+ // Fence handling is tied to some new data transfer across a pollable
+ // descriptor. When we're adding new descriptors, we stop polling.
+ resample_evt.read()?;
+ self.state.add_job(CrossDomainJob::HandleFence(fence));
+ }
+ CrossDomainToken::WaylandReadPipe(pipe_id) => {
+ let mut items = self.item_state.lock();
+ let mut cmd_read: CrossDomainReadWrite = Default::default();
+ let bytes_read;
+
+ cmd_read.hdr.cmd = CROSS_DOMAIN_CMD_READ;
+ cmd_read.identifier = pipe_id;
+
+ let item = items
+ .table
+ .get_mut(&pipe_id)
+ .ok_or(RutabagaError::InvalidCrossDomainItemId)?;
+
+ match item {
+ CrossDomainItem::WaylandReadPipe(ref mut file) => {
+ let ring_write =
+ RingWrite::WriteFromFile(cmd_read, file, event.is_readable);
+ bytes_read = self
+ .state
+ .write_to_ring::<CrossDomainReadWrite>(ring_write)?;
+
+ // Zero bytes read indicates end-of-file on POSIX.
+ if event.is_hungup && bytes_read == 0 {
+ self.wait_ctx.delete(file)?;
+ }
+ }
+ _ => return Err(RutabagaError::InvalidCrossDomainItemType),
+ }
+
+ if event.is_hungup && bytes_read == 0 {
+ items.table.remove(&pipe_id);
+ }
+
+ self.fence_handler.call(fence);
+ }
+ CrossDomainToken::Kill => {
+ self.fence_handler.call(fence);
+ stop_thread = true;
+ }
+ }
+ }
+
+ Ok(stop_thread)
+ }
+
+ fn run(&mut self, kill_evt: Event, resample_evt: Event) -> RutabagaResult<()> {
+ self.wait_ctx
+ .add(&resample_evt, CrossDomainToken::Resample)?;
+ self.wait_ctx.add(&kill_evt, CrossDomainToken::Kill)?;
+ let mut receive_buf: Vec<u8> = vec![0; CROSS_DOMAIN_MAX_SEND_RECV_SIZE];
+
+ while let Some(job) = self.state.wait_for_job() {
+ match job {
+ CrossDomainJob::HandleFence(fence) => {
+ match self.handle_fence(fence, &resample_evt, &mut receive_buf) {
+ Ok(true) => return Ok(()),
+ Ok(false) => (),
+ Err(e) => {
+ error!("Worker halting due to: {}", e);
+ return Err(e);
+ }
+ }
+ }
+ CrossDomainJob::AddReadPipe(read_pipe_id) => {
+ let items = self.item_state.lock();
+ let item = items
+ .table
+ .get(&read_pipe_id)
+ .ok_or(RutabagaError::InvalidCrossDomainItemId)?;
+
+ match item {
+ CrossDomainItem::WaylandReadPipe(file) => self
+ .wait_ctx
+ .add(file, CrossDomainToken::WaylandReadPipe(read_pipe_id))?,
+ _ => return Err(RutabagaError::InvalidCrossDomainItemType),
+ }
+ }
+ }
+ }
+
+ Ok(())
+ }
}
impl CrossDomain {
@@ -60,21 +485,70 @@ impl CrossDomain {
impl CrossDomainContext {
fn initialize(&mut self, cmd_init: &CrossDomainInit) -> RutabagaResult<()> {
- if !self.context_resources.contains_key(&cmd_init.ring_id) {
+ if !self
+ .context_resources
+ .lock()
+ .contains_key(&cmd_init.ring_id)
+ {
return Err(RutabagaError::InvalidResourceId);
}
- self.ring_id = cmd_init.ring_id;
+ let ring_id = cmd_init.ring_id;
+ let context_resources = self.context_resources.clone();
+
// Zero means no requested channel.
if cmd_init.channel_type != 0 {
- let channels = self.channels.take().ok_or(RutabagaError::SpecViolation)?;
+ let channels = self
+ .channels
+ .take()
+ .ok_or(RutabagaError::InvalidCrossDomainChannel)?;
let base_channel = &channels
.iter()
.find(|channel| channel.channel_type == cmd_init.channel_type)
- .ok_or(RutabagaError::SpecViolation)?
+ .ok_or(RutabagaError::InvalidCrossDomainChannel)?
.base_channel;
- self.connection = Some(UnixStream::connect(base_channel)?);
+ let connection = UnixStream::connect(base_channel)?;
+
+ let (kill_evt, thread_kill_evt) = Event::new().and_then(|e| Ok((e.try_clone()?, e)))?;
+ let (resample_evt, thread_resample_evt) =
+ Event::new().and_then(|e| Ok((e.try_clone()?, e)))?;
+
+ let wait_ctx =
+ WaitContext::build_with(&[(&connection, CrossDomainToken::ContextChannel)])?;
+
+ let state = Arc::new(CrossDomainState::new(
+ ring_id,
+ context_resources,
+ Some(connection),
+ ));
+
+ let thread_state = state.clone();
+ let thread_items = self.item_state.clone();
+ let thread_fence_handler = self.fence_handler.clone();
+
+ let worker_result = thread::Builder::new()
+ .name("cross domain".to_string())
+ .spawn(move || -> RutabagaResult<()> {
+ CrossDomainWorker::new(
+ wait_ctx,
+ thread_state,
+ thread_items,
+ thread_fence_handler,
+ )
+ .run(thread_kill_evt, thread_resample_evt)
+ });
+
+ self.worker_thread = Some(worker_result.unwrap());
+ self.state = Some(state);
+ self.resample_evt = Some(resample_evt);
+ self.kill_evt = Some(kill_evt);
+ } else {
+ self.state = Some(Arc::new(CrossDomainState::new(
+ ring_id,
+ context_resources,
+ None,
+ )));
}
Ok(())
@@ -91,7 +565,6 @@ impl CrossDomainContext {
flags: RutabagaGrallocFlags::new(cmd_get_reqs.flags),
};
- self.blob_id += 1;
let reqs = self.gralloc.lock().get_image_memory_requirements(info)?;
let mut response = CrossDomainImageRequirements {
@@ -99,9 +572,8 @@ impl CrossDomainContext {
offsets: reqs.offsets,
modifier: reqs.modifier,
size: reqs.size,
- blob_id: self.blob_id,
+ blob_id: 0,
map_info: reqs.map_info,
- pad: 0,
memory_idx: -1,
physical_device_idx: -1,
};
@@ -111,26 +583,158 @@ impl CrossDomainContext {
response.physical_device_idx = vk_info.physical_device_idx as i32;
}
- let resource = self
- .context_resources
- .get_mut(&self.ring_id)
- .ok_or(RutabagaError::InvalidResourceId)?;
+ if let Some(state) = &self.state {
+ response.blob_id = add_item(&self.item_state, CrossDomainItem::ImageRequirements(reqs));
+ state.write_to_ring(RingWrite::Write(response, None))?;
+ Ok(())
+ } else {
+ Err(RutabagaError::InvalidCrossDomainState)
+ }
+ }
- let iovecs = resource
- .backing_iovecs
- .take()
- .ok_or(RutabagaError::InvalidIovec)?;
+ fn send(
+ &self,
+ cmd_send: &CrossDomainSendReceive,
+ opaque_data: &[VolatileSlice],
+ ) -> RutabagaResult<()> {
+ let mut descriptors = [0; CROSS_DOMAIN_MAX_IDENTIFIERS];
- // Safe because we've verified the iovecs are attached and owned only by this context.
- let slice =
- unsafe { VolatileSlice::from_raw_parts(iovecs[0].base as *mut u8, iovecs[0].len) };
+ let mut write_pipe_opt: Option<File> = None;
+ let mut read_pipe_id_opt: Option<u32> = None;
+
+ let num_identifiers = cmd_send.num_identifiers.try_into()?;
+
+ if num_identifiers > CROSS_DOMAIN_MAX_IDENTIFIERS {
+ return Err(RutabagaError::SpecViolation(
+ "max cross domain identifiers exceeded",
+ ));
+ }
+
+ let iter = cmd_send
+ .identifiers
+ .iter()
+ .zip(cmd_send.identifier_types.iter())
+ .zip(descriptors.iter_mut())
+ .take(num_identifiers);
+
+ for ((identifier, identifier_type), descriptor) in iter {
+ if *identifier_type == CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB {
+ let context_resources = self.context_resources.lock();
+
+ let context_resource = context_resources
+ .get(identifier)
+ .ok_or(RutabagaError::InvalidResourceId)?;
+
+ if let Some(ref handle) = context_resource.handle {
+ *descriptor = handle.os_handle.as_raw_descriptor();
+ } else {
+ return Err(RutabagaError::InvalidRutabagaHandle);
+ }
+ } else if *identifier_type == CROSS_DOMAIN_ID_TYPE_READ_PIPE {
+ // In practice, just 1 pipe pair per send is observed. If we encounter
+ // more, this can be changed later.
+ if write_pipe_opt.is_some() {
+ return Err(RutabagaError::SpecViolation("expected just one pipe pair"));
+ }
+
+ let (read_pipe, write_pipe) = pipe(true)?;
+
+ *descriptor = write_pipe.as_raw_descriptor();
+ let read_pipe_id: u32 = add_item(
+ &self.item_state,
+ CrossDomainItem::WaylandReadPipe(read_pipe),
+ );
+
+ // For Wayland read pipes, the guest guesses which identifier the host will use to
+ // avoid waiting for the host to generate one. Validate guess here. This works
+ // because of the way Sommelier copy + paste works. If the Sommelier sequence of events
+ // changes, it's always possible to wait for the host response.
+ if read_pipe_id != *identifier {
+ return Err(RutabagaError::InvalidCrossDomainItemId);
+ }
+
+ // The write pipe needs to be dropped after the send_msg(..) call is complete, so the read pipe
+ // can receive subsequent hang-up events.
+ write_pipe_opt = Some(write_pipe);
+ read_pipe_id_opt = Some(read_pipe_id);
+ } else {
+ // Don't know how to handle anything else yet.
+ return Err(RutabagaError::InvalidCrossDomainItemType);
+ }
+ }
+
+ if let (Some(state), Some(resample_evt)) = (&self.state, &self.resample_evt) {
+ state.send_msg(opaque_data, &descriptors[..num_identifiers])?;
+
+ if let Some(read_pipe_id) = read_pipe_id_opt {
+ state.add_job(CrossDomainJob::AddReadPipe(read_pipe_id));
+ resample_evt.write(1)?;
+ }
+ } else {
+ return Err(RutabagaError::InvalidCrossDomainState);
+ }
- // The copy_from(..) method guarantees out of bounds buffer accesses will not occur.
- slice.copy_from(&[response]);
- resource.backing_iovecs = Some(iovecs);
- self.requirements_blobs.insert(self.blob_id, reqs);
Ok(())
}
+
+ fn write(
+ &self,
+ cmd_write: &CrossDomainReadWrite,
+ opaque_data: VolatileSlice,
+ ) -> RutabagaResult<()> {
+ let mut items = self.item_state.lock();
+
+ // Most of the time, hang-up and writing will be paired. In lieu of this, remove the
+ // item rather than getting a reference. In case of an error, there's not much to do
+ // besides reporting it.
+ let item = items
+ .table
+ .remove(&cmd_write.identifier)
+ .ok_or(RutabagaError::InvalidCrossDomainItemId)?;
+
+ let len: usize = cmd_write.opaque_data_size.try_into()?;
+ match item {
+ CrossDomainItem::WaylandWritePipe(mut file) => {
+ if len != 0 {
+ file.write_all_volatile(opaque_data)?;
+ }
+
+ if cmd_write.hang_up == 0 {
+ items.table.insert(
+ cmd_write.identifier,
+ CrossDomainItem::WaylandWritePipe(file),
+ );
+ }
+
+ Ok(())
+ }
+ _ => Err(RutabagaError::InvalidCrossDomainItemType),
+ }
+ }
+}
+
+impl Drop for CrossDomainContext {
+ fn drop(&mut self) {
+ if let Some(state) = &self.state {
+ state.end_jobs();
+ }
+
+ if let Some(kill_evt) = self.kill_evt.take() {
+ // Don't join the the worker thread unless the write to `kill_evt` is successful.
+ // Otherwise, this may block indefinitely.
+ match kill_evt.write(1) {
+ Ok(_) => (),
+ Err(e) => {
+ error!("failed to write cross domain kill event: {}", e);
+ return;
+ }
+ }
+
+ if let Some(worker_thread) = self.worker_thread.take() {
+ let _ = worker_thread.join();
+ }
+ }
+ }
}
impl RutabagaContext for CrossDomainContext {
@@ -138,47 +742,90 @@ impl RutabagaContext for CrossDomainContext {
&mut self,
resource_id: u32,
resource_create_blob: ResourceCreateBlob,
- handle: Option<RutabagaHandle>,
+ handle_opt: Option<RutabagaHandle>,
) -> RutabagaResult<RutabagaResource> {
- let reqs = self
- .requirements_blobs
- .get(&resource_create_blob.blob_id)
- .ok_or(RutabagaError::SpecViolation)?;
-
- if reqs.size != resource_create_blob.size {
- return Err(RutabagaError::SpecViolation);
+ let item_id = resource_create_blob.blob_id as u32;
+
+ // We don't want to remove requirements blobs, since they can be used for subsequent
+ // allocations. We do want to remove Wayland keymaps, since they are mapped the guest
+ // and then never used again. The current protocol encodes this as divisiblity by 2.
+ if item_id % 2 == 0 {
+ let items = self.item_state.lock();
+ let item = items
+ .table
+ .get(&item_id)
+ .ok_or(RutabagaError::InvalidCrossDomainItemId)?;
+
+ match item {
+ CrossDomainItem::ImageRequirements(reqs) => {
+ if reqs.size != resource_create_blob.size {
+ return Err(RutabagaError::SpecViolation("blob size mismatch"));
+ }
+
+ // Strictly speaking, it's against the virtio-gpu spec to allocate memory in the context
+ // create blob function, which says "the actual allocation is done via
+ // VIRTIO_GPU_CMD_SUBMIT_3D." However, atomic resource creation is easiest for the
+ // cross-domain use case, so whatever.
+ let hnd = match handle_opt {
+ Some(handle) => handle,
+ None => self.gralloc.lock().allocate_memory(*reqs)?,
+ };
+
+ let info_3d = Resource3DInfo {
+ width: reqs.info.width,
+ height: reqs.info.height,
+ drm_fourcc: reqs.info.drm_format.into(),
+ strides: reqs.strides,
+ offsets: reqs.offsets,
+ modifier: reqs.modifier,
+ };
+
+ Ok(RutabagaResource {
+ resource_id,
+ handle: Some(Arc::new(hnd)),
+ blob: true,
+ blob_mem: resource_create_blob.blob_mem,
+ blob_flags: resource_create_blob.blob_flags,
+ map_info: Some(reqs.map_info),
+ info_2d: None,
+ info_3d: Some(info_3d),
+ vulkan_info: reqs.vulkan_info,
+ backing_iovecs: None,
+ })
+ }
+ _ => Err(RutabagaError::InvalidCrossDomainItemType),
+ }
+ } else {
+ let item = self
+ .item_state
+ .lock()
+ .table
+ .remove(&item_id)
+ .ok_or(RutabagaError::InvalidCrossDomainItemId)?;
+
+ match item {
+ CrossDomainItem::WaylandKeymap(descriptor) => {
+ let hnd = RutabagaHandle {
+ os_handle: descriptor,
+ handle_type: RUTABAGA_MEM_HANDLE_TYPE_SHM,
+ };
+
+ Ok(RutabagaResource {
+ resource_id,
+ handle: Some(Arc::new(hnd)),
+ blob: true,
+ blob_mem: resource_create_blob.blob_mem,
+ blob_flags: resource_create_blob.blob_flags,
+ map_info: Some(RUTABAGA_MAP_CACHE_CACHED),
+ info_2d: None,
+ info_3d: None,
+ vulkan_info: None,
+ backing_iovecs: None,
+ })
+ }
+ _ => Err(RutabagaError::InvalidCrossDomainItemType),
+ }
}
-
- // Strictly speaking, it's against the virtio-gpu spec to allocate memory in the context
- // create blob function, which says "the actual allocation is done via
- // VIRTIO_GPU_CMD_SUBMIT_3D." However, atomic resource creation is easiest for the
- // cross-domain use case, so whatever.
- let hnd = match handle {
- Some(handle) => handle,
- None => self.gralloc.lock().allocate_memory(*reqs)?,
- };
-
- let info_3d = Resource3DInfo {
- width: reqs.info.width,
- height: reqs.info.height,
- drm_fourcc: reqs.info.drm_format.into(),
- strides: reqs.strides,
- offsets: reqs.offsets,
- modifier: reqs.modifier,
- };
-
- Ok(RutabagaResource {
- resource_id,
- handle: Some(Arc::new(hnd)),
- blob: true,
- blob_mem: resource_create_blob.blob_mem,
- blob_flags: resource_create_blob.blob_flags,
- map_info: Some(reqs.map_info),
- info_2d: None,
- info_3d: Some(info_3d),
- vulkan_info: reqs.vulkan_info,
- backing_iovecs: None,
- })
}
fn submit_cmd(&mut self, commands: &mut [u8]) -> RutabagaResult<()> {
@@ -201,7 +848,28 @@ impl RutabagaContext for CrossDomainContext {
self.get_image_requirements(&cmd_get_reqs)?;
}
- _ => return Err(RutabagaError::Unsupported),
+ CROSS_DOMAIN_CMD_SEND => {
+ let opaque_data_offset = size_of::<CrossDomainSendReceive>();
+ let cmd_send: CrossDomainSendReceive = slice.get_ref(offset)?.load();
+
+ let opaque_data =
+ slice.sub_slice(opaque_data_offset, cmd_send.opaque_data_size as usize)?;
+
+ self.send(&cmd_send, &[opaque_data])?;
+ }
+ CROSS_DOMAIN_CMD_POLL => {
+ // Actual polling is done in the subsequent when creating a fence.
+ }
+ CROSS_DOMAIN_CMD_WRITE => {
+ let opaque_data_offset = size_of::<CrossDomainReadWrite>();
+ let cmd_write: CrossDomainReadWrite = slice.get_ref(offset)?.load();
+
+ let opaque_data =
+ slice.sub_slice(opaque_data_offset, cmd_write.opaque_data_size as usize)?;
+
+ self.write(&cmd_write, opaque_data)?;
+ }
+ _ => return Err(RutabagaError::SpecViolation("invalid cross domain command")),
}
offset += hdr.cmd_size as usize;
@@ -211,64 +879,60 @@ impl RutabagaContext for CrossDomainContext {
}
fn attach(&mut self, resource: &mut RutabagaResource) {
- match resource.blob_mem {
- RUTABAGA_BLOB_MEM_GUEST => {
- self.context_resources.insert(
- resource.resource_id,
- CrossDomainResource {
- handle: None,
- backing_iovecs: resource.backing_iovecs.take(),
- },
- );
- }
- _ => match resource.handle {
- Some(ref handle) => {
- self.context_resources.insert(
- resource.resource_id,
- CrossDomainResource {
- handle: Some(handle.clone()),
- backing_iovecs: None,
- },
- );
- }
- _ => (),
- },
+ if resource.blob_mem == RUTABAGA_BLOB_MEM_GUEST {
+ self.context_resources.lock().insert(
+ resource.resource_id,
+ CrossDomainResource {
+ handle: None,
+ backing_iovecs: resource.backing_iovecs.take(),
+ },
+ );
+ } else if let Some(ref handle) = resource.handle {
+ self.context_resources.lock().insert(
+ resource.resource_id,
+ CrossDomainResource {
+ handle: Some(handle.clone()),
+ backing_iovecs: None,
+ },
+ );
}
}
fn detach(&mut self, resource: &RutabagaResource) {
- self.context_resources.remove(&resource.resource_id);
+ self.context_resources.lock().remove(&resource.resource_id);
}
- fn context_create_fence(&mut self, fence_data: RutabagaFenceData) -> RutabagaResult<()> {
- self.last_fence_data = Some(fence_data);
+ fn context_create_fence(&mut self, fence: RutabagaFence) -> RutabagaResult<()> {
+ match fence.ring_idx as u32 {
+ CROSS_DOMAIN_QUERY_RING => self.fence_handler.call(fence),
+ CROSS_DOMAIN_CHANNEL_RING => {
+ if let Some(state) = &self.state {
+ state.add_job(CrossDomainJob::HandleFence(fence));
+ }
+ }
+ _ => return Err(RutabagaError::SpecViolation("unexpected ring type")),
+ }
+
Ok(())
}
- fn context_poll(&mut self) -> Option<Vec<RutabagaFenceData>> {
- let fence_data = self.last_fence_data.take();
- match fence_data {
- Some(fence_data) => Some(vec![fence_data]),
- None => None,
- }
+ fn component_type(&self) -> RutabagaComponentType {
+ RutabagaComponentType::CrossDomain
}
}
impl RutabagaComponent for CrossDomain {
fn get_capset_info(&self, _capset_id: u32) -> (u32, u32) {
- return (0u32, size_of::<CrossDomainCapabilities>() as u32);
+ (0u32, size_of::<CrossDomainCapabilities>() as u32)
}
fn get_capset(&self, _capset_id: u32, _version: u32) -> Vec<u8> {
let mut caps: CrossDomainCapabilities = Default::default();
- match self.channels {
- Some(ref channels) => {
- for channel in channels {
- caps.supported_channels = 1 << channel.channel_type;
- }
+ if let Some(ref channels) = self.channels {
+ for channel in channels {
+ caps.supported_channels = 1 << channel.channel_type;
}
- None => (),
- };
+ }
if self.gralloc.lock().supports_dmabuf() {
caps.supports_dmabuf = 1;
@@ -278,25 +942,57 @@ impl RutabagaComponent for CrossDomain {
caps.supports_external_gpu_memory = 1;
}
- // Version 1 supports all commands up to and including CROSS_DOMAIN_CMD_RECEIVE.
+ // Version 1 supports all commands up to and including CROSS_DOMAIN_CMD_WRITE.
caps.version = 1;
caps.as_slice().to_vec()
}
+ fn create_blob(
+ &mut self,
+ _ctx_id: u32,
+ resource_id: u32,
+ resource_create_blob: ResourceCreateBlob,
+ iovec_opt: Option<Vec<RutabagaIovec>>,
+ _handle_opt: Option<RutabagaHandle>,
+ ) -> RutabagaResult<RutabagaResource> {
+ if resource_create_blob.blob_mem != RUTABAGA_BLOB_MEM_GUEST
+ && resource_create_blob.blob_flags != RUTABAGA_BLOB_FLAG_USE_MAPPABLE
+ {
+ return Err(RutabagaError::SpecViolation(
+ "expected only guest memory blobs",
+ ));
+ }
+
+ Ok(RutabagaResource {
+ resource_id,
+ handle: None,
+ blob: true,
+ blob_mem: resource_create_blob.blob_mem,
+ blob_flags: resource_create_blob.blob_flags,
+ map_info: None,
+ info_2d: None,
+ info_3d: None,
+ vulkan_info: None,
+ backing_iovecs: iovec_opt,
+ })
+ }
+
fn create_context(
&self,
_ctx_id: u32,
_context_init: u32,
+ fence_handler: RutabagaFenceHandler,
) -> RutabagaResult<Box<dyn RutabagaContext>> {
Ok(Box::new(CrossDomainContext {
channels: self.channels.clone(),
gralloc: self.gralloc.clone(),
- connection: None,
- ring_id: 0,
- requirements_blobs: Default::default(),
- context_resources: Default::default(),
- last_fence_data: None,
- blob_id: 0,
+ state: None,
+ context_resources: Arc::new(Mutex::new(Default::default())),
+ item_state: Arc::new(Mutex::new(Default::default())),
+ fence_handler,
+ worker_thread: None,
+ resample_evt: None,
+ kill_evt: None,
}))
}
}
diff --git a/rutabaga_gfx/src/cross_domain/cross_domain_protocol.rs b/rutabaga_gfx/src/cross_domain/cross_domain_protocol.rs
index 25dd6d309..d3f6865ca 100644
--- a/rutabaga_gfx/src/cross_domain/cross_domain_protocol.rs
+++ b/rutabaga_gfx/src/cross_domain/cross_domain_protocol.rs
@@ -12,11 +12,37 @@ use data_model::DataInit;
/// Cross-domain commands (only a maximum of 255 supported)
pub const CROSS_DOMAIN_CMD_INIT: u8 = 1;
pub const CROSS_DOMAIN_CMD_GET_IMAGE_REQUIREMENTS: u8 = 2;
+pub const CROSS_DOMAIN_CMD_POLL: u8 = 3;
+pub const CROSS_DOMAIN_CMD_SEND: u8 = 4;
+pub const CROSS_DOMAIN_CMD_RECEIVE: u8 = 5;
+pub const CROSS_DOMAIN_CMD_READ: u8 = 6;
+pub const CROSS_DOMAIN_CMD_WRITE: u8 = 7;
/// Channel types (must match rutabaga channel types)
pub const CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND: u32 = 0x0001;
pub const CROSS_DOMAIN_CHANNEL_TYPE_CAMERA: u32 = 0x0002;
+/// The maximum number of identifiers (value inspired by wp_linux_dmabuf)
+pub const CROSS_DOMAIN_MAX_IDENTIFIERS: usize = 4;
+
+/// virtgpu memory resource ID. Also works with non-blob memory resources, despite the name.
+pub const CROSS_DOMAIN_ID_TYPE_VIRTGPU_BLOB: u32 = 1;
+/// virtgpu synchronization resource id.
+pub const CROSS_DOMAIN_ID_TYPE_VIRTGPU_SYNC: u32 = 2;
+/// ID for Wayland pipe used for reading. The reading is done by the guest proxy and the host
+/// proxy. The host sends the write end of the proxied pipe over the host Wayland socket.
+pub const CROSS_DOMAIN_ID_TYPE_READ_PIPE: u32 = 3;
+/// ID for Wayland pipe used for writing. The writing is done by the guest and the host proxy.
+/// The host receives the write end of the pipe over the host Wayland socket.
+pub const CROSS_DOMAIN_ID_TYPE_WRITE_PIPE: u32 = 4;
+
+/// No ring
+pub const CROSS_DOMAIN_RING_NONE: u32 = 0xffffffff;
+/// A ring for metadata queries.
+pub const CROSS_DOMAIN_QUERY_RING: u32 = 0;
+/// A ring based on this particular context's channel.
+pub const CROSS_DOMAIN_CHANNEL_RING: u32 = 1;
+
#[repr(C)]
#[derive(Copy, Clone, Default)]
pub struct CrossDomainCapabilities {
@@ -35,9 +61,8 @@ pub struct CrossDomainImageRequirements {
pub offsets: [u32; 4],
pub modifier: u64,
pub size: u64,
- pub blob_id: u64,
+ pub blob_id: u32,
pub map_info: u32,
- pub pad: u32,
pub memory_idx: i32,
pub physical_device_idx: i32,
}
@@ -48,7 +73,7 @@ unsafe impl DataInit for CrossDomainImageRequirements {}
#[derive(Copy, Clone, Default)]
pub struct CrossDomainHeader {
pub cmd: u8,
- pub fence_ctx_idx: u8,
+ pub ring_idx: u8,
pub cmd_size: u16,
pub pad: u32,
}
@@ -76,3 +101,30 @@ pub struct CrossDomainGetImageRequirements {
}
unsafe impl DataInit for CrossDomainGetImageRequirements {}
+
+#[repr(C)]
+#[derive(Copy, Clone, Default)]
+pub struct CrossDomainSendReceive {
+ pub hdr: CrossDomainHeader,
+ pub num_identifiers: u32,
+ pub opaque_data_size: u32,
+ pub identifiers: [u32; CROSS_DOMAIN_MAX_IDENTIFIERS],
+ pub identifier_types: [u32; CROSS_DOMAIN_MAX_IDENTIFIERS],
+ pub identifier_sizes: [u32; CROSS_DOMAIN_MAX_IDENTIFIERS],
+ // Data of size "opaque data size follows"
+}
+
+unsafe impl DataInit for CrossDomainSendReceive {}
+
+#[repr(C)]
+#[derive(Copy, Clone, Default)]
+pub struct CrossDomainReadWrite {
+ pub hdr: CrossDomainHeader,
+ pub identifier: u32,
+ pub hang_up: u32,
+ pub opaque_data_size: u32,
+ pub pad: u32,
+ // Data of size "opaque data size follows"
+}
+
+unsafe impl DataInit for CrossDomainReadWrite {}
diff --git a/rutabaga_gfx/src/generated/generate.py b/rutabaga_gfx/src/generated/generate.py
index 366110a09..3bebbdf4c 100755
--- a/rutabaga_gfx/src/generated/generate.py
+++ b/rutabaga_gfx/src/generated/generate.py
@@ -26,16 +26,18 @@ END_COLOR = '\033[0m'
verbose = False
-def generate_module(module_name, whitelist, header, clang_args, lib_name,
- derive_default):
+def generate_module(module_name, allowlist, blocklist, header, clang_args,
+ lib_name, derive_default):
args = [
'bindgen',
'--no-layout-tests',
- '--whitelist-function', whitelist,
- '--whitelist-var', whitelist,
- '--whitelist-type', whitelist,
+ '--allowlist-function', allowlist,
+ '--allowlist-var', allowlist,
+ '--allowlist-type', allowlist,
+ '--blocklist-function', blocklist,
+ '--blocklist-item', blocklist,
+ '--blocklist-type', blocklist,
'--no-prepend-enum-name',
- '--no-rustfmt-bindings',
'-o', module_name + '_bindings.rs',
];
@@ -87,7 +89,6 @@ def get_parser():
default='/',
help='sysroot directory (default=%(default)s)')
parser.add_argument('--virglrenderer',
- default='git://git.freedesktop.org/git/virglrenderer',
help='virglrenderer src dir/repo (default=%(default)s)')
parser.add_argument('--virgl_branch',
default='master',
@@ -105,23 +106,30 @@ def main(argv):
if opts.verbose:
verbose = True
- virgl_src_dir = opts.virglrenderer
- virgl_src_dir_temp = None
- if '://' in opts.virglrenderer:
- virgl_src_dir_temp = tempfile.TemporaryDirectory(prefix='virglrenderer-src')
- virgl_src_dir = virgl_src_dir_temp.name
- if not download_virgl(opts.virglrenderer, virgl_src_dir, opts.virgl_branch):
- print('failed to clone \'{}\' to \'{}\''.format(virgl_src_dir,
- opts.virgl_branch))
- sys.exit(1)
+ if opts.virglrenderer:
+ if '://' in opts.virglrenderer:
+ virgl_src_dir_temp = tempfile.TemporaryDirectory(prefix='virglrenderer-src')
+ virgl_src_dir = virgl_src_dir_temp.name
+ if not download_virgl(opts.virglrenderer, virgl_src_dir, opts.virgl_branch):
+ print('failed to clone \'{}\' to \'{}\''.format(virgl_src_dir,
+ opts.virgl_branch))
+ sys.exit(1)
+ else:
+ virgl_src_dir = opts.virglrenderer
+
+ header = os.path.join(virgl_src_dir, 'src/virglrenderer.h')
+ else:
+ header = os.path.join(opts.sysroot, 'usr/include/virgl/virglrenderer.h')
- clang_args = ['-I', os.path.join(opts.sysroot, 'usr/include')]
+ clang_args = ['-I', os.path.join(opts.sysroot, 'usr/include'),
+ '-D', 'VIRGL_RENDERER_UNSTABLE_APIS']
modules = (
(
'virgl_renderer',
- '(virgl|VIRGL)_.+',
- os.path.join(opts.sysroot, 'usr/include/virgl/virglrenderer.h'),
+ '(virgl|VIRGL)_.+', # allowlist
+ '.*(va_list|debug_callback).*', # blocklist
+ header,
clang_args,
'virglrenderer',
True,
@@ -153,6 +161,7 @@ def main(argv):
print('#![allow(non_camel_case_types)]', file=f)
print('#![allow(non_snake_case)]', file=f)
print('#![allow(non_upper_case_globals)]', file=f)
+ print('pub mod virgl_debug_callback_bindings;', file=f)
for module in modules:
print('pub mod', module[0] + '_bindings;', file=f)
diff --git a/rutabaga_gfx/src/generated/mod.rs b/rutabaga_gfx/src/generated/mod.rs
index 979f06f6c..159f71707 100644
--- a/rutabaga_gfx/src/generated/mod.rs
+++ b/rutabaga_gfx/src/generated/mod.rs
@@ -3,4 +3,5 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
+pub mod virgl_debug_callback_bindings;
pub mod virgl_renderer_bindings;
diff --git a/rutabaga_gfx/src/generated/virgl_debug_callback_bindings.rs b/rutabaga_gfx/src/generated/virgl_debug_callback_bindings.rs
new file mode 100644
index 000000000..f691a8995
--- /dev/null
+++ b/rutabaga_gfx/src/generated/virgl_debug_callback_bindings.rs
@@ -0,0 +1,58 @@
+/*
+ * automatically generated by rust-bindgen
+ * $ bindgen /usr/include/stdio.h \
+ * --no-layout-tests \
+ * --allowlist-function vsnprintf \
+ * -- \
+ * -target <target>
+ */
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+pub mod stdio {
+ extern "C" {
+ pub fn vsnprintf(
+ __s: *mut ::std::os::raw::c_char,
+ __maxlen: ::std::os::raw::c_ulong,
+ __format: *const ::std::os::raw::c_char,
+ __arg: *mut __va_list_tag,
+ ) -> ::std::os::raw::c_int;
+ }
+ #[repr(C)]
+ #[derive(Debug, Copy, Clone)]
+ pub struct __va_list_tag {
+ pub gp_offset: ::std::os::raw::c_uint,
+ pub fp_offset: ::std::os::raw::c_uint,
+ pub overflow_arg_area: *mut ::std::os::raw::c_void,
+ pub reg_save_area: *mut ::std::os::raw::c_void,
+ }
+
+ pub type va_list = *mut __va_list_tag;
+}
+#[cfg(target_arch = "arm")]
+pub mod stdio {
+ extern "C" {
+ pub fn vsnprintf(
+ __s: *mut ::std::os::raw::c_char,
+ __maxlen: ::std::os::raw::c_uint,
+ __format: *const ::std::os::raw::c_char,
+ __arg: __builtin_va_list,
+ ) -> ::std::os::raw::c_int;
+ }
+ pub type __builtin_va_list = __va_list;
+ #[repr(C)]
+ #[derive(Debug, Copy, Clone)]
+ pub struct __va_list {
+ pub __ap: *mut ::std::os::raw::c_void,
+ }
+
+ pub type va_list = __builtin_va_list;
+}
+
+#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm"))]
+pub type virgl_debug_callback_type = ::std::option::Option<
+ unsafe extern "C" fn(fmt: *const ::std::os::raw::c_char, ap: stdio::va_list),
+>;
+
+#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm"))]
+extern "C" {
+ pub fn virgl_set_debug_callback(cb: virgl_debug_callback_type) -> virgl_debug_callback_type;
+}
diff --git a/rutabaga_gfx/src/generated/virgl_renderer_bindings.rs b/rutabaga_gfx/src/generated/virgl_renderer_bindings.rs
index 08b8f7a3e..80feee717 100644
--- a/rutabaga_gfx/src/generated/virgl_renderer_bindings.rs
+++ b/rutabaga_gfx/src/generated/virgl_renderer_bindings.rs
@@ -1,10 +1,10 @@
-/* automatically generated by rust-bindgen 0.54.1 */
+/* automatically generated by rust-bindgen 0.59.2 */
#[cfg(feature = "virgl_renderer")]
#[link(name = "virglrenderer")]
extern "C" {}
-pub const VIRGL_RENDERER_CALLBACKS_VERSION: u32 = 2;
+pub const VIRGL_RENDERER_CALLBACKS_VERSION: u32 = 3;
pub const VIRGL_RENDERER_USE_EGL: u32 = 1;
pub const VIRGL_RENDERER_THREAD_SYNC: u32 = 2;
pub const VIRGL_RENDERER_USE_GLX: u32 = 4;
@@ -13,6 +13,8 @@ pub const VIRGL_RENDERER_USE_GLES: u32 = 16;
pub const VIRGL_RENDERER_USE_EXTERNAL_BLOB: u32 = 32;
pub const VIRGL_RENDERER_VENUS: u32 = 64;
pub const VIRGL_RENDERER_NO_VIRGL: u32 = 128;
+pub const VIRGL_RENDERER_ASYNC_FENCE_CB: u32 = 256;
+pub const VIRGL_RENDERER_RENDER_SERVER: u32 = 512;
pub const VIRGL_RES_BIND_DEPTH_STENCIL: u32 = 1;
pub const VIRGL_RES_BIND_RENDER_TARGET: u32 = 2;
pub const VIRGL_RES_BIND_SAMPLER_VIEW: u32 = 8;
@@ -24,6 +26,7 @@ pub const VIRGL_RES_BIND_CURSOR: u32 = 65536;
pub const VIRGL_RES_BIND_CUSTOM: u32 = 131072;
pub const VIRGL_RES_BIND_SCANOUT: u32 = 262144;
pub const VIRGL_RES_BIND_SHARED: u32 = 1048576;
+pub const VIRGL_RENDERER_CONTEXT_FLAG_CAPSET_ID_MASK: u32 = 255;
pub const VIRGL_RENDERER_BLOB_MEM_GUEST: u32 = 1;
pub const VIRGL_RENDERER_BLOB_MEM_HOST3D: u32 = 2;
pub const VIRGL_RENDERER_BLOB_MEM_HOST3D_GUEST: u32 = 3;
@@ -37,10 +40,11 @@ pub const VIRGL_RENDERER_MAP_CACHE_UNCACHED: u32 = 2;
pub const VIRGL_RENDERER_MAP_CACHE_WC: u32 = 3;
pub const VIRGL_RENDERER_BLOB_FD_TYPE_DMABUF: u32 = 1;
pub const VIRGL_RENDERER_BLOB_FD_TYPE_OPAQUE: u32 = 2;
+pub const VIRGL_RENDERER_BLOB_FD_TYPE_SHM: u32 = 3;
+pub const VIRGL_RENDERER_FENCE_FLAG_MERGEABLE: u32 = 1;
pub type __int32_t = ::std::os::raw::c_int;
pub type __uint32_t = ::std::os::raw::c_uint;
pub type __uint64_t = ::std::os::raw::c_ulong;
-pub type va_list = __builtin_va_list;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct virgl_box {
@@ -87,6 +91,20 @@ pub struct virgl_renderer_callbacks {
pub get_drm_fd: ::std::option::Option<
unsafe extern "C" fn(cookie: *mut ::std::os::raw::c_void) -> ::std::os::raw::c_int,
>,
+ pub write_context_fence: ::std::option::Option<
+ unsafe extern "C" fn(
+ cookie: *mut ::std::os::raw::c_void,
+ ctx_id: u32,
+ queue_id: u64,
+ fence_cookie: *mut ::std::os::raw::c_void,
+ ),
+ >,
+ pub get_server_fd: ::std::option::Option<
+ unsafe extern "C" fn(
+ cookie: *mut ::std::os::raw::c_void,
+ version: u32,
+ ) -> ::std::os::raw::c_int,
+ >,
}
extern "C" {
pub fn virgl_renderer_init(
@@ -134,7 +152,7 @@ extern "C" {
pub const VIRGL_RENDERER_STRUCTURE_TYPE_NONE: virgl_renderer_structure_type_v0 = 0;
pub const VIRGL_RENDERER_STRUCTURE_TYPE_EXPORT_QUERY: virgl_renderer_structure_type_v0 = 1;
pub const VIRGL_RENDERER_STRUCTURE_TYPE_SUPPORTED_STRUCTURES: virgl_renderer_structure_type_v0 = 2;
-pub type virgl_renderer_structure_type_v0 = u32;
+pub type virgl_renderer_structure_type_v0 = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct virgl_renderer_resource_create_args {
@@ -178,9 +196,6 @@ pub struct virgl_renderer_supported_structures {
pub in_stype_version: u32,
pub out_supported_structures_mask: u32,
}
-pub type virgl_debug_callback_type = ::std::option::Option<
- unsafe extern "C" fn(fmt: *const ::std::os::raw::c_char, ap: *mut __va_list_tag),
->;
extern "C" {
pub fn virgl_renderer_resource_create(
args: *mut virgl_renderer_resource_create_args,
@@ -287,9 +302,6 @@ extern "C" {
res_handle: ::std::os::raw::c_int,
);
}
-extern "C" {
- pub fn virgl_set_debug_callback(cb: virgl_debug_callback_type) -> virgl_debug_callback_type;
-}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct virgl_renderer_resource_info {
@@ -346,7 +358,11 @@ pub struct virgl_renderer_resource_create_blob_args {
}
impl Default for virgl_renderer_resource_create_blob_args {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
extern "C" {
@@ -377,23 +393,37 @@ extern "C" {
fd: *mut ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virgl_renderer_resource_import_blob_args {
+ pub res_handle: u32,
+ pub blob_mem: u32,
+ pub fd_type: u32,
+ pub fd: ::std::os::raw::c_int,
+ pub size: u64,
+}
+extern "C" {
+ pub fn virgl_renderer_resource_import_blob(
+ args: *const virgl_renderer_resource_import_blob_args,
+ ) -> ::std::os::raw::c_int;
+}
extern "C" {
pub fn virgl_renderer_export_fence(
client_fence_id: u32,
fd: *mut ::std::os::raw::c_int,
) -> ::std::os::raw::c_int;
}
-pub type __builtin_va_list = [__va_list_tag; 1usize];
-#[repr(C)]
-#[derive(Debug, Copy, Clone)]
-pub struct __va_list_tag {
- pub gp_offset: ::std::os::raw::c_uint,
- pub fp_offset: ::std::os::raw::c_uint,
- pub overflow_arg_area: *mut ::std::os::raw::c_void,
- pub reg_save_area: *mut ::std::os::raw::c_void,
+extern "C" {
+ pub fn virgl_renderer_context_create_fence(
+ ctx_id: u32,
+ flags: u32,
+ queue_id: u64,
+ fence_cookie: *mut ::std::os::raw::c_void,
+ ) -> ::std::os::raw::c_int;
}
-impl Default for __va_list_tag {
- fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
- }
+extern "C" {
+ pub fn virgl_renderer_context_poll(ctx_id: u32);
+}
+extern "C" {
+ pub fn virgl_renderer_context_get_poll_fd(ctx_id: u32) -> ::std::os::raw::c_int;
}
diff --git a/rutabaga_gfx/src/gfxstream.rs b/rutabaga_gfx/src/gfxstream.rs
index 81d3b8a20..47d5cce92 100644
--- a/rutabaga_gfx/src/gfxstream.rs
+++ b/rutabaga_gfx/src/gfxstream.rs
@@ -4,17 +4,20 @@
//! gfxstream: Handles 3D virtio-gpu hypercalls using gfxstream.
//!
-//! External code found at https://android.googlesource.com/device/generic/vulkan-cereal/.
+//! External code found at <https://android.googlesource.com/device/generic/vulkan-cereal/>.
#![cfg(feature = "gfxstream")]
-use std::cell::RefCell;
+use std::convert::TryInto;
use std::mem::{size_of, transmute};
use std::os::raw::{c_char, c_int, c_uint, c_void};
-use std::ptr::null_mut;
-use std::rc::Rc;
+use std::ptr::{null, null_mut};
+use std::sync::Arc;
-use base::{ExternalMapping, ExternalMappingError, ExternalMappingResult};
+use base::{
+ ExternalMapping, ExternalMappingError, ExternalMappingResult, FromRawDescriptor,
+ IntoRawDescriptor, SafeDescriptor,
+};
use crate::generated::virgl_renderer_bindings::{
iovec, virgl_box, virgl_renderer_resource_create_args,
@@ -26,14 +29,47 @@ use crate::rutabaga_utils::*;
use data_model::VolatileSlice;
+#[repr(C)]
+pub struct VirglRendererGlCtxParam {
+ pub version: c_int,
+ pub shared: bool,
+ pub major_ver: c_int,
+ pub minor_ver: c_int,
+}
+
// In gfxstream, only write_fence is used (for synchronization of commands delivered)
#[repr(C)]
#[derive(Debug, Copy, Clone)]
-pub struct GfxstreamRendererCallbacks {
+pub struct VirglRendererCallbacks {
pub version: c_int,
pub write_fence: unsafe extern "C" fn(cookie: *mut c_void, fence: u32),
+ pub create_gl_context: Option<
+ unsafe extern "C" fn(
+ cookie: *mut c_void,
+ scanout_idx: c_int,
+ param: *mut VirglRendererGlCtxParam,
+ ) -> *mut c_void,
+ >,
+ pub destroy_gl_context: Option<unsafe extern "C" fn(cookie: *mut c_void, ctx: *mut c_void)>,
+ pub make_current: Option<
+ unsafe extern "C" fn(cookie: *mut c_void, scanout_idx: c_int, ctx: *mut c_void) -> c_int,
+ >,
+
+ pub get_drm_fd: Option<unsafe extern "C" fn(cookie: *mut c_void) -> c_int>,
+ pub write_context_fence:
+ unsafe extern "C" fn(cookie: *mut c_void, fence_id: u64, ctx_id: u32, ring_idx: u8),
}
+#[repr(C)]
+#[derive(Copy, Clone, Default)]
+pub struct stream_renderer_handle {
+ pub os_handle: i64,
+ pub handle_type: u32,
+}
+
+#[allow(non_camel_case_types)]
+type stream_renderer_create_blob = ResourceCreateBlob;
+
#[link(name = "gfxstream_backend")]
extern "C" {
@@ -45,7 +81,8 @@ extern "C" {
display_type: u32,
renderer_cookie: *mut c_void,
renderer_flags: i32,
- renderer_callbacks: *mut GfxstreamRendererCallbacks,
+ renderer_callbacks: *mut VirglRendererCallbacks,
+ gfxstream_callbacks: *mut c_void,
);
// virtio-gpu-3d ioctl functions (begin)
@@ -54,7 +91,6 @@ extern "C" {
// forwarding and the notification of new API calls forwarded by the guest, unless they
// correspond to minigbm resource targets (PIPE_TEXTURE_2D), in which case they create globally
// visible shared GL textures to support gralloc.
- fn pipe_virgl_renderer_poll();
fn pipe_virgl_renderer_resource_create(
args: *mut virgl_renderer_resource_create_args,
iov: *mut iovec,
@@ -105,19 +141,28 @@ extern "C" {
fn pipe_virgl_renderer_ctx_attach_resource(ctx_id: c_int, res_handle: c_int);
fn pipe_virgl_renderer_ctx_detach_resource(ctx_id: c_int, res_handle: c_int);
- fn stream_renderer_resource_create_v2(res_handle: u32, hostmemId: u64);
+ fn stream_renderer_create_blob(
+ ctx_id: u32,
+ res_handle: u32,
+ create_blob: *const stream_renderer_create_blob,
+ iovecs: *const iovec,
+ num_iovs: u32,
+ handle: *const stream_renderer_handle,
+ ) -> c_int;
+
+ fn stream_renderer_export_blob(res_handle: u32, handle: *mut stream_renderer_handle) -> c_int;
fn stream_renderer_resource_map(
res_handle: u32,
map: *mut *mut c_void,
out_size: *mut u64,
) -> c_int;
fn stream_renderer_resource_unmap(res_handle: u32) -> c_int;
+ fn stream_renderer_resource_map_info(res_handle: u32, map_info: *mut u32) -> c_int;
+ fn stream_renderer_context_create_fence(fence_id: u64, ctx_id: u32, ring_idx: u8) -> c_int;
}
/// The virtio-gpu backend state tracker which supports accelerated rendering.
-pub struct Gfxstream {
- fence_state: Rc<RefCell<FenceState>>,
-}
+pub struct Gfxstream;
struct GfxstreamContext {
ctx_id: u32,
@@ -162,6 +207,19 @@ impl RutabagaContext for GfxstreamContext {
);
}
}
+
+ fn component_type(&self) -> RutabagaComponentType {
+ RutabagaComponentType::Gfxstream
+ }
+
+ fn context_create_fence(&mut self, fence: RutabagaFence) -> RutabagaResult<()> {
+ // Safe becase only integers are given to gfxstream, not memory.
+ let ret = unsafe {
+ stream_renderer_context_create_fence(fence.fence_id, fence.ctx_id, fence.ring_idx)
+ };
+
+ ret_to_res(ret)
+ }
}
impl Drop for GfxstreamContext {
@@ -173,9 +231,14 @@ impl Drop for GfxstreamContext {
}
}
-const GFXSTREAM_RENDERER_CALLBACKS: &GfxstreamRendererCallbacks = &GfxstreamRendererCallbacks {
- version: 1,
+const GFXSTREAM_RENDERER_CALLBACKS: &VirglRendererCallbacks = &VirglRendererCallbacks {
+ version: 3,
write_fence,
+ create_gl_context: None,
+ destroy_gl_context: None,
+ make_current: None,
+ get_drm_fd: None,
+ write_context_fence,
};
fn map_func(resource_id: u32) -> ExternalMappingResult<(u64, usize)> {
@@ -200,11 +263,11 @@ impl Gfxstream {
display_width: u32,
display_height: u32,
gfxstream_flags: GfxstreamFlags,
+ fence_handler: RutabagaFenceHandler,
) -> RutabagaResult<Box<dyn RutabagaComponent>> {
- let fence_state = Rc::new(RefCell::new(FenceState { latest_fence: 0 }));
-
let cookie: *mut VirglCookie = Box::into_raw(Box::new(VirglCookie {
- fence_state: Rc::clone(&fence_state),
+ render_server_fd: None,
+ fence_handler: Some(fence_handler),
}));
unsafe {
@@ -215,15 +278,35 @@ impl Gfxstream {
cookie as *mut c_void,
gfxstream_flags.into(),
transmute(GFXSTREAM_RENDERER_CALLBACKS),
+ null_mut(),
);
}
- Ok(Box::new(Gfxstream { fence_state }))
+ Ok(Box::new(Gfxstream))
+ }
+
+ fn map_info(&self, resource_id: u32) -> RutabagaResult<u32> {
+ let mut map_info = 0;
+ let ret = unsafe { stream_renderer_resource_map_info(resource_id, &mut map_info) };
+ ret_to_res(ret)?;
+
+ Ok(map_info)
}
- #[allow(clippy::unnecessary_wraps)]
- fn map_info(&self, _resource_id: u32) -> RutabagaResult<u32> {
- Ok(RUTABAGA_MAP_CACHE_WC)
+ fn export_blob(&self, resource_id: u32) -> RutabagaResult<Arc<RutabagaHandle>> {
+ let mut stream_handle: stream_renderer_handle = Default::default();
+ let ret = unsafe { stream_renderer_export_blob(resource_id as u32, &mut stream_handle) };
+ ret_to_res(ret)?;
+
+ // Safe because the handle was just returned by a successful gfxstream call so it must be
+ // valid and owned by us.
+ let raw_descriptor = stream_handle.os_handle.try_into()?;
+ let handle = unsafe { SafeDescriptor::from_raw_descriptor(raw_descriptor) };
+
+ Ok(Arc::new(RutabagaHandle {
+ os_handle: handle,
+ handle_type: stream_handle.handle_type,
+ }))
}
}
@@ -236,16 +319,10 @@ impl RutabagaComponent for Gfxstream {
Vec::new()
}
- fn create_fence(&mut self, fence_data: RutabagaFenceData) -> RutabagaResult<()> {
- let ret = unsafe {
- pipe_virgl_renderer_create_fence(fence_data.fence_id as i32, fence_data.ctx_id)
- };
- ret_to_res(ret)
- }
+ fn create_fence(&mut self, fence: RutabagaFence) -> RutabagaResult<()> {
+ let ret = unsafe { pipe_virgl_renderer_create_fence(fence.fence_id as i32, fence.ctx_id) };
- fn poll(&self) -> u32 {
- unsafe { pipe_virgl_renderer_poll() };
- self.fence_state.borrow().latest_fence
+ ret_to_res(ret)
}
fn create_3d(
@@ -408,17 +485,43 @@ impl RutabagaComponent for Gfxstream {
fn create_blob(
&mut self,
- _ctx_id: u32,
+ ctx_id: u32,
resource_id: u32,
resource_create_blob: ResourceCreateBlob,
- _iovec_opt: Option<Vec<RutabagaIovec>>,
+ mut iovec_opt: Option<Vec<RutabagaIovec>>,
+ handle_opt: Option<RutabagaHandle>,
) -> RutabagaResult<RutabagaResource> {
- unsafe {
- stream_renderer_resource_create_v2(resource_id, resource_create_blob.blob_id);
+ let mut iovec_ptr = null_mut();
+ let mut num_iovecs = 0;
+ if let Some(ref mut iovecs) = iovec_opt {
+ iovec_ptr = iovecs.as_mut_ptr();
+ num_iovecs = iovecs.len() as u32;
}
+
+ let mut handle_ptr = null();
+ let mut stream_handle: stream_renderer_handle = Default::default();
+ if let Some(handle) = handle_opt {
+ stream_handle.handle_type = handle.handle_type;
+ stream_handle.os_handle = handle.os_handle.into_raw_descriptor() as i64;
+ handle_ptr = &stream_handle;
+ }
+
+ let ret = unsafe {
+ stream_renderer_create_blob(
+ ctx_id,
+ resource_id,
+ &resource_create_blob as *const stream_renderer_create_blob,
+ iovec_ptr as *const iovec,
+ num_iovecs,
+ handle_ptr as *const stream_renderer_handle,
+ )
+ };
+
+ ret_to_res(ret)?;
+
Ok(RutabagaResource {
resource_id,
- handle: None,
+ handle: self.export_blob(resource_id).ok(),
blob: true,
blob_mem: resource_create_blob.blob_mem,
blob_flags: resource_create_blob.blob_flags,
@@ -426,7 +529,7 @@ impl RutabagaComponent for Gfxstream {
info_2d: None,
info_3d: None,
vulkan_info: None,
- backing_iovecs: None,
+ backing_iovecs: iovec_opt,
})
}
@@ -442,6 +545,7 @@ impl RutabagaComponent for Gfxstream {
&self,
ctx_id: u32,
_context_init: u32,
+ _fence_handler: RutabagaFenceHandler,
) -> RutabagaResult<Box<dyn RutabagaContext>> {
const CONTEXT_NAME: &[u8] = b"gpu_renderer";
// Safe because virglrenderer is initialized by now and the context name is statically
diff --git a/rutabaga_gfx/src/renderer_utils.rs b/rutabaga_gfx/src/renderer_utils.rs
index 94a271a72..ecb4acf29 100644
--- a/rutabaga_gfx/src/renderer_utils.rs
+++ b/rutabaga_gfx/src/renderer_utils.rs
@@ -4,12 +4,18 @@
//! renderer_utils: Utility functions and structs used by virgl_renderer and gfxstream.
-use std::cell::RefCell;
-use std::os::raw::c_void;
-use std::rc::Rc;
+use std::os::raw::{c_int, c_void};
+use std::panic::catch_unwind;
+use std::process::abort;
-use crate::generated::virgl_renderer_bindings::__va_list_tag;
-use crate::rutabaga_utils::{RutabagaError, RutabagaResult};
+use base::{IntoRawDescriptor, SafeDescriptor};
+
+use crate::rutabaga_utils::{
+ RutabagaError, RutabagaFence, RutabagaFenceHandler, RutabagaResult, RUTABAGA_FLAG_FENCE,
+};
+
+#[cfg(feature = "gfxstream")]
+use crate::rutabaga_utils::RUTABAGA_FLAG_INFO_RING_IDX;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
@@ -22,25 +28,6 @@ pub struct VirglBox {
pub d: u32,
}
-/*
- * automatically generated by rust-bindgen
- * $ bindgen /usr/include/stdio.h \
- * --no-layout-tests \
- * --whitelist-function vsnprintf \
- * -o vsnprintf.rs
- */
-
-#[allow(non_snake_case, non_camel_case_types)]
-#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
-extern "C" {
- pub fn vsnprintf(
- __s: *mut ::std::os::raw::c_char,
- __maxlen: ::std::os::raw::c_ulong,
- __format: *const ::std::os::raw::c_char,
- __arg: *mut __va_list_tag,
- ) -> ::std::os::raw::c_int;
-}
-
pub fn ret_to_res(ret: i32) -> RutabagaResult<()> {
match ret {
0 => Ok(()),
@@ -48,27 +35,69 @@ pub fn ret_to_res(ret: i32) -> RutabagaResult<()> {
}
}
-pub struct FenceState {
- pub latest_fence: u32,
+pub struct VirglCookie {
+ pub render_server_fd: Option<SafeDescriptor>,
+ pub fence_handler: Option<RutabagaFenceHandler>,
}
-impl FenceState {
- pub fn write(&mut self, latest_fence: u32) {
- if latest_fence > self.latest_fence {
- self.latest_fence = latest_fence;
+pub unsafe extern "C" fn write_fence(cookie: *mut c_void, fence: u32) {
+ catch_unwind(|| {
+ assert!(!cookie.is_null());
+ let cookie = &*(cookie as *mut VirglCookie);
+
+ // Call fence completion callback
+ if let Some(handler) = &cookie.fence_handler {
+ handler.call(RutabagaFence {
+ flags: RUTABAGA_FLAG_FENCE,
+ fence_id: fence as u64,
+ ctx_id: 0,
+ ring_idx: 0,
+ });
}
- }
+ })
+ .unwrap_or_else(|_| abort())
}
-pub struct VirglCookie {
- pub fence_state: Rc<RefCell<FenceState>>,
+#[cfg(feature = "gfxstream")]
+pub extern "C" fn write_context_fence(
+ cookie: *mut c_void,
+ fence_id: u64,
+ ctx_id: u32,
+ ring_idx: u8,
+) {
+ catch_unwind(|| {
+ assert!(!cookie.is_null());
+ let cookie = unsafe { &*(cookie as *mut VirglCookie) };
+
+ // Call fence completion callback
+ if let Some(handler) = &cookie.fence_handler {
+ handler.call(RutabagaFence {
+ flags: RUTABAGA_FLAG_FENCE | RUTABAGA_FLAG_INFO_RING_IDX,
+ fence_id,
+ ctx_id,
+ ring_idx,
+ });
+ }
+ })
+ .unwrap_or_else(|_| abort())
}
-pub extern "C" fn write_fence(cookie: *mut c_void, fence: u32) {
- assert!(!cookie.is_null());
- let cookie = unsafe { &*(cookie as *mut VirglCookie) };
+#[allow(dead_code)]
+pub unsafe extern "C" fn get_server_fd(cookie: *mut c_void, version: u32) -> c_int {
+ catch_unwind(|| {
+ assert!(!cookie.is_null());
+ let cookie = &mut *(cookie as *mut VirglCookie);
+
+ if version != 0 {
+ return -1;
+ }
- // Track the most recent fence.
- let mut fence_state = cookie.fence_state.borrow_mut();
- fence_state.write(fence);
+ // Transfer the fd ownership to virglrenderer.
+ cookie
+ .render_server_fd
+ .take()
+ .map(SafeDescriptor::into_raw_descriptor)
+ .unwrap_or(-1)
+ })
+ .unwrap_or_else(|_| abort())
}
diff --git a/rutabaga_gfx/src/rutabaga_2d.rs b/rutabaga_gfx/src/rutabaga_2d.rs
index b0e59ce90..a4956f1de 100644
--- a/rutabaga_gfx/src/rutabaga_2d.rs
+++ b/rutabaga_gfx/src/rutabaga_2d.rs
@@ -4,7 +4,7 @@
//! rutabaga_2d: Handles 2D virtio-gpu hypercalls.
-use std::cmp::{max, min};
+use std::cmp::{max, min, Ordering};
use data_model::*;
@@ -84,15 +84,19 @@ pub fn transfer_2d<'a, S: Iterator<Item = VolatileSlice<'a>>>(
let offset_within_src = src_copyable_start_offset.saturating_sub(src_start_offset);
- if src_line_end_offset > src_end_offset {
- next_src = true;
- next_line = false;
- } else if src_line_end_offset == src_end_offset {
- next_src = true;
- next_line = true;
- } else {
- next_src = false;
- next_line = true;
+ match src_line_end_offset.cmp(&src_end_offset) {
+ Ordering::Greater => {
+ next_src = true;
+ next_line = false;
+ }
+ Ordering::Equal => {
+ next_src = true;
+ next_line = true;
+ }
+ Ordering::Less => {
+ next_src = false;
+ next_line = true;
+ }
}
let src_subslice = src.get_slice(offset_within_src as usize, copyable_size as usize)?;
@@ -107,14 +111,12 @@ pub fn transfer_2d<'a, S: Iterator<Item = VolatileSlice<'a>>>(
let dst_subslice = dst.get_slice(dst_start_offset as usize, copyable_size as usize)?;
src_subslice.copy_to_volatile_slice(dst_subslice);
+ } else if src_line_start_offset >= src_start_offset {
+ next_src = true;
+ next_line = false;
} else {
- if src_line_start_offset >= src_start_offset {
- next_src = true;
- next_line = false;
- } else {
- next_src = false;
- next_line = true;
- }
+ next_src = false;
+ next_line = true;
};
if next_src {
@@ -131,27 +133,21 @@ pub fn transfer_2d<'a, S: Iterator<Item = VolatileSlice<'a>>>(
}
pub struct Rutabaga2D {
- latest_created_fence_id: u32,
+ fence_handler: RutabagaFenceHandler,
}
impl Rutabaga2D {
- pub fn init() -> RutabagaResult<Box<dyn RutabagaComponent>> {
- Ok(Box::new(Rutabaga2D {
- latest_created_fence_id: 0,
- }))
+ pub fn init(fence_handler: RutabagaFenceHandler) -> RutabagaResult<Box<dyn RutabagaComponent>> {
+ Ok(Box::new(Rutabaga2D { fence_handler }))
}
}
impl RutabagaComponent for Rutabaga2D {
- fn create_fence(&mut self, fence_data: RutabagaFenceData) -> RutabagaResult<()> {
- self.latest_created_fence_id = fence_data.fence_id as u32;
+ fn create_fence(&mut self, fence: RutabagaFence) -> RutabagaResult<()> {
+ self.fence_handler.call(fence);
Ok(())
}
- fn poll(&self) -> u32 {
- self.latest_created_fence_id
- }
-
fn create_3d(
&self,
resource_id: u32,
@@ -191,12 +187,15 @@ impl RutabagaComponent for Rutabaga2D {
return Ok(());
}
- let mut info_2d = resource.info_2d.take().ok_or(RutabagaError::Unsupported)?;
+ let mut info_2d = resource
+ .info_2d
+ .take()
+ .ok_or(RutabagaError::Invalid2DInfo)?;
let iovecs = resource
.backing_iovecs
.take()
- .ok_or(RutabagaError::Unsupported)?;
+ .ok_or(RutabagaError::InvalidIovec)?;
// All offical virtio_gpu formats are 4 bytes per pixel.
let resource_bpp = 4;
@@ -240,7 +239,10 @@ impl RutabagaComponent for Rutabaga2D {
transfer: Transfer3D,
buf: Option<VolatileSlice>,
) -> RutabagaResult<()> {
- let mut info_2d = resource.info_2d.take().ok_or(RutabagaError::Unsupported)?;
+ let mut info_2d = resource
+ .info_2d
+ .take()
+ .ok_or(RutabagaError::Invalid2DInfo)?;
// All offical virtio_gpu formats are 4 bytes per pixel.
let resource_bpp = 4;
@@ -248,7 +250,9 @@ impl RutabagaComponent for Rutabaga2D {
let src_offset = 0;
let dst_offset = 0;
- let dst_slice = buf.ok_or(RutabagaError::Unsupported)?;
+ let dst_slice = buf.ok_or(RutabagaError::SpecViolation(
+ "need a destination slice for transfer read",
+ ))?;
transfer_2d(
info_2d.width,
diff --git a/rutabaga_gfx/src/rutabaga_core.rs b/rutabaga_gfx/src/rutabaga_core.rs
index 23b30aca9..8729a80cd 100644
--- a/rutabaga_gfx/src/rutabaga_core.rs
+++ b/rutabaga_gfx/src/rutabaga_core.rs
@@ -7,7 +7,7 @@
use std::collections::BTreeMap as Map;
use std::sync::Arc;
-use base::ExternalMapping;
+use base::{ExternalMapping, SafeDescriptor};
use data_model::VolatileSlice;
use crate::cross_domain::CrossDomain;
@@ -67,13 +67,17 @@ pub trait RutabagaComponent {
/// Implementations must create a fence that represents the completion of prior work. This is
/// required for synchronization with the guest kernel.
- fn create_fence(&mut self, _fence_data: RutabagaFenceData) -> RutabagaResult<()> {
+ fn create_fence(&mut self, _fence: RutabagaFence) -> RutabagaResult<()> {
Err(RutabagaError::Unsupported)
}
- /// Implementations must return the last completed fence_id.
- fn poll(&self) -> u32 {
- 0
+ /// Used only by VirglRenderer to poll when its poll_descriptor is signaled.
+ fn poll(&self) {}
+
+ /// Used only by VirglRenderer to return a poll_descriptor that is signaled when a poll() is
+ /// necessary.
+ fn poll_descriptor(&self) -> Option<SafeDescriptor> {
+ None
}
/// Implementations must create a resource with the given metadata. For 2D rutabaga components,
@@ -133,6 +137,7 @@ pub trait RutabagaComponent {
_resource_id: u32,
_resource_create_blob: ResourceCreateBlob,
_iovec_opt: Option<Vec<RutabagaIovec>>,
+ _handle_opt: Option<RutabagaHandle>,
) -> RutabagaResult<RutabagaResource> {
Err(RutabagaError::Unsupported)
}
@@ -155,6 +160,7 @@ pub trait RutabagaComponent {
&self,
_ctx_id: u32,
_context_init: u32,
+ _fence_handler: RutabagaFenceHandler,
) -> RutabagaResult<Box<dyn RutabagaContext>> {
Err(RutabagaError::Unsupported)
}
@@ -166,7 +172,7 @@ pub trait RutabagaContext {
&mut self,
_resource_id: u32,
_resource_create_blob: ResourceCreateBlob,
- _handle: Option<RutabagaHandle>,
+ _handle_opt: Option<RutabagaHandle>,
) -> RutabagaResult<RutabagaResource> {
Err(RutabagaError::Unsupported)
}
@@ -180,17 +186,14 @@ pub trait RutabagaContext {
/// Implementations must stop using `resource` in this context's command stream.
fn detach(&mut self, _resource: &RutabagaResource);
- /// Implementations must create a fence on specified `fence_ctx_idx` in `fence_data`. This
- /// allows for multiple syncrhonizations timelines per RutabagaContext.
- fn context_create_fence(&mut self, _fence_data: RutabagaFenceData) -> RutabagaResult<()> {
+ /// Implementations must create a fence on specified `ring_idx` in `fence`. This
+ /// allows for multiple synchronizations timelines per RutabagaContext.
+ fn context_create_fence(&mut self, _fence: RutabagaFence) -> RutabagaResult<()> {
Err(RutabagaError::Unsupported)
}
- /// Implementations must return an array of fences that have completed. This will be used by
- /// the cross-domain context for asynchronous Tx/Rx.
- fn context_poll(&mut self) -> Option<Vec<RutabagaFenceData>> {
- None
- }
+ /// Implementations must return the component type associated with the context.
+ fn component_type(&self) -> RutabagaComponentType;
}
#[derive(Copy, Clone)]
@@ -208,10 +211,12 @@ struct RutabagaCapsetInfo {
/// thread-safe is more difficult.
pub struct Rutabaga {
resources: Map<u32, RutabagaResource>,
- components: Map<RutabagaComponentType, Box<dyn RutabagaComponent>>,
contexts: Map<u32, Box<dyn RutabagaContext>>,
+ // Declare components after resources and contexts such that it is dropped last.
+ components: Map<RutabagaComponentType, Box<dyn RutabagaComponent>>,
default_component: RutabagaComponentType,
capset_info: Vec<RutabagaCapsetInfo>,
+ fence_handler: RutabagaFenceHandler,
}
impl Rutabaga {
@@ -273,55 +278,42 @@ impl Rutabaga {
}
}
- /// Creates a fence with the given `fence_data`.
- /// If the flags include RUTABAGA_FLAG_PARAM_FENCE_CTX_IDX, then the fence is created on a
+ /// Creates a fence with the given `fence`.
+ /// If the flags include RUTABAGA_FLAG_INFO_RING_IDX, then the fence is created on a
/// specific timeline on the specific context.
- pub fn create_fence(&mut self, fence_data: RutabagaFenceData) -> RutabagaResult<()> {
- if fence_data.flags & RUTABAGA_FLAG_INFO_FENCE_CTX_IDX != 0 {
+ pub fn create_fence(&mut self, fence: RutabagaFence) -> RutabagaResult<()> {
+ if fence.flags & RUTABAGA_FLAG_INFO_RING_IDX != 0 {
let ctx = self
.contexts
- .get_mut(&fence_data.ctx_id)
+ .get_mut(&fence.ctx_id)
.ok_or(RutabagaError::InvalidContextId)?;
- ctx.context_create_fence(fence_data)?;
+ ctx.context_create_fence(fence)?;
} else {
let component = self
.components
.get_mut(&self.default_component)
.ok_or(RutabagaError::InvalidComponent)?;
- component.create_fence(fence_data)?;
+ component.create_fence(fence)?;
}
Ok(())
}
- /// Polls all rutabaga components and contexts, and returns a vector of RutabagaFenceData
- /// describing which fences have completed.
- pub fn poll(&mut self) -> Vec<RutabagaFenceData> {
- let mut completed_fences: Vec<RutabagaFenceData> = Vec::new();
- // Poll the default component -- this the global timeline which does not take into account
- // `ctx_id` or `fence_ctx_idx`. This path exists for OpenGL legacy reasons and 2D mode.
- let component = self
- .components
- .get_mut(&self.default_component)
- .ok_or(0)
- .unwrap();
-
- let global_fence_id = component.poll();
- completed_fences.push(RutabagaFenceData {
- flags: RUTABAGA_FLAG_FENCE,
- fence_id: global_fence_id as u64,
- ctx_id: 0,
- fence_ctx_idx: 0,
- });
-
- for ctx in self.contexts.values_mut() {
- if let Some(ref mut ctx_completed_fences) = ctx.context_poll() {
- completed_fences.append(ctx_completed_fences);
- }
+ /// Polls the default rutabaga component. In practice, only called if the default component is
+ /// virglrenderer.
+ pub fn poll(&self) {
+ if let Some(component) = self.components.get(&self.default_component) {
+ component.poll();
}
- completed_fences
+ }
+
+ /// Returns a pollable descriptor for the default rutabaga component. In practice, it is only
+ /// not None if the default component is virglrenderer.
+ pub fn poll_descriptor(&self) -> Option<SafeDescriptor> {
+ let component = self.components.get(&self.default_component).or(None)?;
+ component.poll_descriptor()
}
/// Creates a resource with the `resource_create_3d` metadata.
@@ -457,28 +449,33 @@ impl Rutabaga {
return Err(RutabagaError::InvalidResourceId);
}
+ let component = self
+ .components
+ .get_mut(&self.default_component)
+ .ok_or(RutabagaError::InvalidComponent)?;
+
+ let mut context = None;
// For the cross-domain context, we'll need to create the blob resource via a home-grown
- // rutabaga context rather than one from an external C/C++ component. Use `ctx_id` to check
- // if it happens to be a cross-domain context.
+ // rutabaga context rather than one from an external C/C++ component. Use `ctx_id` and
+ // the component type if it happens to be a cross-domain context.
if ctx_id > 0 {
let ctx = self
.contexts
.get_mut(&ctx_id)
.ok_or(RutabagaError::InvalidContextId)?;
- if let Ok(resource) = ctx.context_create_blob(resource_id, resource_create_blob, handle)
- {
- self.resources.insert(resource_id, resource);
- return Ok(());
+ if ctx.component_type() == RutabagaComponentType::CrossDomain {
+ context = Some(ctx);
}
}
- let component = self
- .components
- .get_mut(&self.default_component)
- .ok_or(RutabagaError::InvalidComponent)?;
+ let resource = match context {
+ Some(ctx) => ctx.context_create_blob(resource_id, resource_create_blob, handle)?,
+ None => {
+ component.create_blob(ctx_id, resource_id, resource_create_blob, iovecs, handle)?
+ }
+ };
- let resource = component.create_blob(ctx_id, resource_id, resource_create_blob, iovecs)?;
self.resources.insert(resource_id, resource);
Ok(())
}
@@ -505,7 +502,9 @@ impl Rutabaga {
.get(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
- resource.map_info.ok_or(RutabagaError::SpecViolation)
+ resource
+ .map_info
+ .ok_or(RutabagaError::SpecViolation("no map info available"))
}
/// Returns the `vulkan_info` of the blob resource, which consists of the physical device
@@ -516,7 +515,7 @@ impl Rutabaga {
.get(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
- resource.vulkan_info.ok_or(RutabagaError::Unsupported)
+ resource.vulkan_info.ok_or(RutabagaError::InvalidVulkanInfo)
}
/// Returns the 3D info associated with the resource, if any.
@@ -526,7 +525,9 @@ impl Rutabaga {
.get(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
- resource.info_3d.ok_or(RutabagaError::Unsupported)
+ resource
+ .info_3d
+ .ok_or(RutabagaError::SpecViolation("no 3d info available"))
}
/// Exports a blob resource. See virtio-gpu spec for blob flag use flags.
@@ -550,10 +551,11 @@ impl Rutabaga {
}
(Some(handle), false) => {
// Exactly one strong reference in this case.
- let hnd = Arc::try_unwrap(handle).map_err(|_| RutabagaError::SpecViolation)?;
+ let hnd =
+ Arc::try_unwrap(handle).map_err(|_| RutabagaError::InvalidRutabagaHandle)?;
Ok(hnd)
}
- _ => Err(RutabagaError::Unsupported),
+ _ => Err(RutabagaError::InvalidRutabagaHandle),
}
}
@@ -586,7 +588,7 @@ impl Rutabaga {
return Err(RutabagaError::InvalidContextId);
}
- let ctx = component.create_context(ctx_id, context_init)?;
+ let ctx = component.create_context(ctx_id, context_init, self.fence_handler.clone())?;
self.contexts.insert(ctx_id, ctx);
Ok(())
}
@@ -606,12 +608,12 @@ impl Rutabaga {
.get_mut(&ctx_id)
.ok_or(RutabagaError::InvalidContextId)?;
- let mut resource = self
+ let resource = self
.resources
.get_mut(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
- ctx.attach(&mut resource);
+ ctx.attach(resource);
Ok(())
}
@@ -627,7 +629,7 @@ impl Rutabaga {
.get_mut(&resource_id)
.ok_or(RutabagaError::InvalidResourceId)?;
- ctx.detach(&resource);
+ ctx.detach(resource);
Ok(())
}
@@ -706,23 +708,54 @@ impl RutabagaBuilder {
/// This should be only called once per every virtual machine instance. Rutabaga tries to
/// intialize all 3D components which have been built. In 2D mode, only the 2D component is
/// initialized.
- pub fn build(self) -> RutabagaResult<Rutabaga> {
+ pub fn build(
+ self,
+ fence_handler: RutabagaFenceHandler,
+ render_server_fd: Option<SafeDescriptor>,
+ ) -> RutabagaResult<Rutabaga> {
let mut rutabaga_components: Map<RutabagaComponentType, Box<dyn RutabagaComponent>> =
Default::default();
let mut rutabaga_capsets: Vec<RutabagaCapsetInfo> = Default::default();
+ // Make sure that disabled components are not used as default.
+ #[cfg(not(feature = "virgl_renderer"))]
+ if self.default_component == RutabagaComponentType::VirglRenderer {
+ return Err(RutabagaError::InvalidRutabagaBuild(
+ "virgl renderer feature not enabled",
+ ));
+ }
+ #[cfg(not(feature = "gfxstream"))]
+ if self.default_component == RutabagaComponentType::Gfxstream {
+ return Err(RutabagaError::InvalidRutabagaBuild(
+ "gfxstream feature not enabled",
+ ));
+ }
+
+ #[cfg(not(feature = "virgl_renderer_next"))]
+ if render_server_fd.is_some() {
+ return Err(RutabagaError::InvalidRutabagaBuild(
+ "render server FD is not supported with virgl_renderer_next feature",
+ ));
+ }
+
if self.default_component == RutabagaComponentType::Rutabaga2D {
- let rutabaga_2d = Rutabaga2D::init()?;
+ let rutabaga_2d = Rutabaga2D::init(fence_handler.clone())?;
rutabaga_components.insert(RutabagaComponentType::Rutabaga2D, rutabaga_2d);
} else {
#[cfg(feature = "virgl_renderer")]
if self.default_component == RutabagaComponentType::VirglRenderer {
- let virglrenderer_flags = self
- .virglrenderer_flags
- .ok_or(RutabagaError::InvalidRutabagaBuild)?;
-
- let virgl = VirglRenderer::init(virglrenderer_flags)?;
+ let virglrenderer_flags =
+ self.virglrenderer_flags
+ .ok_or(RutabagaError::InvalidRutabagaBuild(
+ "missing virgl renderer flags",
+ ))?;
+
+ let virgl = VirglRenderer::init(
+ virglrenderer_flags,
+ fence_handler.clone(),
+ render_server_fd,
+ )?;
rutabaga_components.insert(RutabagaComponentType::VirglRenderer, virgl);
rutabaga_capsets.push(RutabagaCapsetInfo {
@@ -743,16 +776,25 @@ impl RutabagaBuilder {
if self.default_component == RutabagaComponentType::Gfxstream {
let display_width = self
.display_width
- .ok_or(RutabagaError::InvalidRutabagaBuild)?;
- let display_height = self
- .display_height
- .ok_or(RutabagaError::InvalidRutabagaBuild)?;
-
- let gfxstream_flags = self
- .gfxstream_flags
- .ok_or(RutabagaError::InvalidRutabagaBuild)?;
-
- let gfxstream = Gfxstream::init(display_width, display_height, gfxstream_flags)?;
+ .ok_or(RutabagaError::InvalidRutabagaBuild("missing display width"))?;
+ let display_height =
+ self.display_height
+ .ok_or(RutabagaError::InvalidRutabagaBuild(
+ "missing display height",
+ ))?;
+
+ let gfxstream_flags =
+ self.gfxstream_flags
+ .ok_or(RutabagaError::InvalidRutabagaBuild(
+ "missing gfxstream flags",
+ ))?;
+
+ let gfxstream = Gfxstream::init(
+ display_width,
+ display_height,
+ gfxstream_flags,
+ fence_handler.clone(),
+ )?;
rutabaga_components.insert(RutabagaComponentType::Gfxstream, gfxstream);
rutabaga_capsets.push(RutabagaCapsetInfo {
@@ -771,11 +813,12 @@ impl RutabagaBuilder {
}
Ok(Rutabaga {
- components: rutabaga_components,
resources: Default::default(),
contexts: Default::default(),
+ components: rutabaga_components,
default_component: self.default_component,
capset_info: rutabaga_capsets,
+ fence_handler,
})
}
}
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/Cargo.toml b/rutabaga_gfx/src/rutabaga_gralloc/Cargo.toml
index 1ab814c57..a541c9c7c 100644
--- a/rutabaga_gfx/src/rutabaga_gralloc/Cargo.toml
+++ b/rutabaga_gfx/src/rutabaga_gralloc/Cargo.toml
@@ -2,7 +2,7 @@
name = "gpu_buffer"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
data_model = { path = "../data_model" }
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/TEST_MAPPING b/rutabaga_gfx/src/rutabaga_gralloc/TEST_MAPPING
index db3d148f5..032949854 100644
--- a/rutabaga_gfx/src/rutabaga_gralloc/TEST_MAPPING
+++ b/rutabaga_gfx/src/rutabaga_gralloc/TEST_MAPPING
@@ -4,10 +4,10 @@
// "presubmit": [
// {
// "host": true,
-// "name": "gpu_buffer_host_test_src_lib"
+// "name": "gpu_buffer_test_src_lib"
// },
// {
-// "name": "gpu_buffer_device_test_src_lib"
+// "name": "gpu_buffer_test_src_lib"
// }
// ]
}
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/cargo2android.json b/rutabaga_gfx/src/rutabaga_gralloc/cargo2android.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/rutabaga_gfx/src/rutabaga_gralloc/cargo2android.json
@@ -0,0 +1 @@
+{}
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/formats.rs b/rutabaga_gfx/src/rutabaga_gralloc/formats.rs
index 31abfcf03..8bb93f7e1 100644
--- a/rutabaga_gfx/src/rutabaga_gralloc/formats.rs
+++ b/rutabaga_gfx/src/rutabaga_gralloc/formats.rs
@@ -136,7 +136,7 @@ impl DrmFormat {
DRM_FORMAT_ABGR16161616F => Ok(PACKED_8BPP),
DRM_FORMAT_NV12 => Ok(BIPLANAR_YUV420),
DRM_FORMAT_YVU420 => Ok(TRIPLANAR_YUV420),
- _ => Err(RutabagaError::Unsupported),
+ _ => Err(RutabagaError::InvalidGrallocDrmFormat),
}
}
@@ -144,21 +144,21 @@ impl DrmFormat {
/// Returns the Vulkan format from the DrmFormat.
pub fn vulkan_format(&self) -> RutabagaResult<VulkanFormat> {
match self.to_bytes() {
- DRM_FORMAT_R8 => Ok(VulkanFormat::R8Unorm),
- DRM_FORMAT_RGB565 => Ok(VulkanFormat::R5G6B5UnormPack16),
- DRM_FORMAT_BGR888 => Ok(VulkanFormat::R8G8B8Unorm),
+ DRM_FORMAT_R8 => Ok(VulkanFormat::R8_UNORM),
+ DRM_FORMAT_RGB565 => Ok(VulkanFormat::R5G6B5_UNORM_PACK16),
+ DRM_FORMAT_BGR888 => Ok(VulkanFormat::R8G8B8_UNORM),
DRM_FORMAT_ABGR2101010 | DRM_FORMAT_XBGR2101010 => {
- Ok(VulkanFormat::A2R10G10B10UnormPack32)
+ Ok(VulkanFormat::A2R10G10B10_UNORM_PACK32)
}
- DRM_FORMAT_ABGR8888 | DRM_FORMAT_XBGR8888 => Ok(VulkanFormat::R8G8B8A8Unorm),
+ DRM_FORMAT_ABGR8888 | DRM_FORMAT_XBGR8888 => Ok(VulkanFormat::R8G8B8A8_UNORM),
DRM_FORMAT_ARGB2101010 | DRM_FORMAT_XRGB2101010 => {
- Ok(VulkanFormat::A2B10G10R10UnormPack32)
+ Ok(VulkanFormat::A2B10G10R10_UNORM_PACK32)
}
- DRM_FORMAT_ARGB8888 | DRM_FORMAT_XRGB8888 => Ok(VulkanFormat::B8G8R8A8Unorm),
- DRM_FORMAT_ABGR16161616F => Ok(VulkanFormat::R16G16B16A16Sfloat),
- DRM_FORMAT_NV12 => Ok(VulkanFormat::G8B8R8_2PLANE420Unorm),
- DRM_FORMAT_YVU420 => Ok(VulkanFormat::G8B8R8_3PLANE420Unorm),
- _ => Err(RutabagaError::Unsupported),
+ DRM_FORMAT_ARGB8888 | DRM_FORMAT_XRGB8888 => Ok(VulkanFormat::B8G8R8A8_UNORM),
+ DRM_FORMAT_ABGR16161616F => Ok(VulkanFormat::R16G16B16A16_SFLOAT),
+ DRM_FORMAT_NV12 => Ok(VulkanFormat::G8_B8R8_2PLANE_420_UNORM),
+ DRM_FORMAT_YVU420 => Ok(VulkanFormat::G8_B8_R8_3PLANE_420_UNORM),
+ _ => Err(RutabagaError::InvalidGrallocDrmFormat),
}
}
@@ -176,37 +176,19 @@ impl DrmFormat {
| DRM_FORMAT_ARGB2101010
| DRM_FORMAT_ARGB8888
| DRM_FORMAT_XRGB2101010
- | DRM_FORMAT_XRGB8888 => Ok(VulkanImageAspect {
- color: true,
- ..VulkanImageAspect::none()
- }),
+ | DRM_FORMAT_XRGB8888 => Ok(VulkanImageAspect::Color),
DRM_FORMAT_NV12 => match plane {
- 0 => Ok(VulkanImageAspect {
- plane0: true,
- ..VulkanImageAspect::none()
- }),
- 1 => Ok(VulkanImageAspect {
- plane1: true,
- ..VulkanImageAspect::none()
- }),
- _ => Err(RutabagaError::Unsupported),
+ 0 => Ok(VulkanImageAspect::Plane0),
+ 1 => Ok(VulkanImageAspect::Plane1),
+ _ => Err(RutabagaError::InvalidGrallocNumberOfPlanes),
},
DRM_FORMAT_YVU420 => match plane {
- 0 => Ok(VulkanImageAspect {
- plane0: true,
- ..VulkanImageAspect::none()
- }),
- 1 => Ok(VulkanImageAspect {
- plane1: true,
- ..VulkanImageAspect::none()
- }),
- 2 => Ok(VulkanImageAspect {
- plane2: true,
- ..VulkanImageAspect::none()
- }),
- _ => Err(RutabagaError::Unsupported),
+ 0 => Ok(VulkanImageAspect::Plane0),
+ 1 => Ok(VulkanImageAspect::Plane1),
+ 2 => Ok(VulkanImageAspect::Plane2),
+ _ => Err(RutabagaError::InvalidGrallocNumberOfPlanes),
},
- _ => Err(RutabagaError::Unsupported),
+ _ => Err(RutabagaError::InvalidGrallocDrmFormat),
}
}
}
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/gralloc.rs b/rutabaga_gfx/src/rutabaga_gralloc/gralloc.rs
index 9b3f25f03..2d7cfa401 100644
--- a/rutabaga_gfx/src/rutabaga_gralloc/gralloc.rs
+++ b/rutabaga_gfx/src/rutabaga_gralloc/gralloc.rs
@@ -275,6 +275,7 @@ impl RutabagaGralloc {
// towards the Vulkan api. This function allows for a variety of quirks, but for now just
// choose the most shiny backend that the user has built. The rationale is "why would you
// build it if you don't want to use it".
+ #[allow(clippy::let_and_return)]
let mut _backend = GrallocBackend::System;
#[cfg(feature = "minigbm")]
@@ -303,7 +304,7 @@ impl RutabagaGralloc {
let gralloc = self
.grallocs
.get_mut(&backend)
- .ok_or(RutabagaError::Unsupported)?;
+ .ok_or(RutabagaError::InvalidGrallocBackend)?;
let mut reqs = gralloc.get_image_memory_requirements(info)?;
reqs.size = round_up_to_page_size(reqs.size as usize) as u64;
@@ -320,7 +321,7 @@ impl RutabagaGralloc {
let gralloc = self
.grallocs
.get_mut(&backend)
- .ok_or(RutabagaError::Unsupported)?;
+ .ok_or(RutabagaError::InvalidGrallocBackend)?;
gralloc.allocate_memory(reqs)
}
@@ -336,7 +337,7 @@ impl RutabagaGralloc {
let gralloc = self
.grallocs
.get_mut(&GrallocBackend::Vulkano)
- .ok_or(RutabagaError::Unsupported)?;
+ .ok_or(RutabagaError::InvalidGrallocBackend)?;
gralloc.import_and_map(handle, vulkan_info, size)
}
@@ -365,8 +366,8 @@ mod tests {
let reqs = gralloc.get_image_memory_requirements(info).unwrap();
let min_reqs = canonical_image_requirements(info).unwrap();
- assert_eq!(reqs.strides[0] >= min_reqs.strides[0], true);
- assert_eq!(reqs.size >= min_reqs.size, true);
+ assert!(reqs.strides[0] >= min_reqs.strides[0]);
+ assert!(reqs.size >= min_reqs.size);
let _handle = gralloc.allocate_memory(reqs).unwrap();
@@ -393,17 +394,17 @@ mod tests {
let reqs = gralloc.get_image_memory_requirements(info).unwrap();
let min_reqs = canonical_image_requirements(info).unwrap();
- assert_eq!(reqs.strides[0] >= min_reqs.strides[0], true);
- assert_eq!(reqs.strides[1] >= min_reqs.strides[1], true);
+ assert!(reqs.strides[0] >= min_reqs.strides[0]);
+ assert!(reqs.strides[1] >= min_reqs.strides[1]);
assert_eq!(reqs.strides[2], 0);
assert_eq!(reqs.strides[3], 0);
- assert_eq!(reqs.offsets[0] >= min_reqs.offsets[0], true);
- assert_eq!(reqs.offsets[1] >= min_reqs.offsets[1], true);
+ assert!(reqs.offsets[0] >= min_reqs.offsets[0]);
+ assert!(reqs.offsets[1] >= min_reqs.offsets[1]);
assert_eq!(reqs.offsets[2], 0);
assert_eq!(reqs.offsets[3], 0);
- assert_eq!(reqs.size >= min_reqs.size, true);
+ assert!(reqs.size >= min_reqs.size);
let _handle = gralloc.allocate_memory(reqs).unwrap();
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/minigbm.rs b/rutabaga_gfx/src/rutabaga_gralloc/minigbm.rs
index c20438de3..b12a4eedd 100644
--- a/rutabaga_gfx/src/rutabaga_gralloc/minigbm.rs
+++ b/rutabaga_gfx/src/rutabaga_gralloc/minigbm.rs
@@ -4,7 +4,7 @@
//! minigbm: implements swapchain allocation using ChromeOS's minigbm library.
//!
-//! External code found at https://chromium.googlesource.com/chromiumos/platform/minigbm.
+//! External code found at <https://chromium.googlesource.com/chromiumos/platform/minigbm>.
#![cfg(feature = "minigbm")]
@@ -14,7 +14,7 @@ use std::io::{Seek, SeekFrom};
use std::os::raw::c_char;
use std::rc::Rc;
-use base::{AsRawDescriptor, Error as SysError, FromRawDescriptor};
+use base::{AsRawDescriptor, Error as BaseError, FromRawDescriptor};
use crate::rutabaga_gralloc::formats::DrmFormat;
use crate::rutabaga_gralloc::gralloc::{Gralloc, ImageAllocationInfo, ImageMemoryRequirements};
@@ -55,7 +55,7 @@ impl MinigbmDevice {
// returned. If the fd does not refer to a DRM device, gbm_create_device will reject it.
let gbm = unsafe { gbm_create_device(fd.as_raw_descriptor()) };
if gbm.is_null() {
- return Err(RutabagaError::SysError(SysError::last()));
+ return Err(RutabagaError::BaseError(BaseError::last()));
}
// Safe because a valid minigbm device has a statically allocated string associated with
@@ -95,7 +95,7 @@ impl Gralloc for MinigbmDevice {
)
};
if bo.is_null() {
- return Err(RutabagaError::SysError(SysError::last()));
+ return Err(RutabagaError::BaseError(BaseError::last()));
}
let mut reqs: ImageMemoryRequirements = Default::default();
@@ -140,7 +140,7 @@ impl Gralloc for MinigbmDevice {
|| gbm_buffer.height() != reqs.info.height
|| gbm_buffer.format() != reqs.info.drm_format
{
- return Err(RutabagaError::SpecViolation);
+ return Err(RutabagaError::InvalidGrallocDimensions);
}
let dmabuf = gbm_buffer.export()?.into();
@@ -161,7 +161,7 @@ impl Gralloc for MinigbmDevice {
};
if bo.is_null() {
- return Err(RutabagaError::SysError(SysError::last()));
+ return Err(RutabagaError::BaseError(BaseError::last()));
}
let gbm_buffer = MinigbmBuffer(bo, self.clone());
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/minigbm_bindings.rs b/rutabaga_gfx/src/rutabaga_gralloc/minigbm_bindings.rs
index cf8f74cd8..366b3ad74 100644
--- a/rutabaga_gfx/src/rutabaga_gralloc/minigbm_bindings.rs
+++ b/rutabaga_gfx/src/rutabaga_gralloc/minigbm_bindings.rs
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// Generated with bindgen --whitelist-function='gbm_.*' --whitelist-type='gbm_.*' minigbm/gbm.h
+// Generated with bindgen --allowlist-function='gbm_.*' --allowlist-type='gbm_.*' minigbm/gbm.h
// Then modified manually
#![cfg(feature = "minigbm")]
@@ -39,7 +39,6 @@ pub union gbm_bo_handle {
}
pub const GBM_BO_FORMAT_XRGB8888: gbm_bo_format = 0;
pub const GBM_BO_FORMAT_ARGB8888: gbm_bo_format = 1;
-#[allow(non_camel_case_types)]
pub type gbm_bo_format = u32;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/mod.rs b/rutabaga_gfx/src/rutabaga_gralloc/mod.rs
index 4de7cceb7..560a193e4 100644
--- a/rutabaga_gfx/src/rutabaga_gralloc/mod.rs
+++ b/rutabaga_gfx/src/rutabaga_gralloc/mod.rs
@@ -6,7 +6,7 @@
//! In addition, it may perform mappings of GPU buffers. This is based on
//! "gralloc", a well-known Android hardware abstaction layer (HAL).
//!
-//! https://source.android.com/devices/graphics/arch-bq-gralloc
+//! <https://source.android.com/devices/graphics/arch-bq-gralloc>
mod formats;
mod gralloc;
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/rendernode.rs b/rutabaga_gfx/src/rutabaga_gralloc/rendernode.rs
index bfef3679f..d7fe1d37a 100644
--- a/rutabaga_gfx/src/rutabaga_gralloc/rendernode.rs
+++ b/rutabaga_gfx/src/rutabaga_gralloc/rendernode.rs
@@ -80,10 +80,10 @@ fn get_drm_device_name(fd: &File) -> Result<String, ()> {
return Err(());
}
- Ok(CString::new(&name_bytes[..(version.name_len as usize)])
+ CString::new(&name_bytes[..(version.name_len as usize)])
.map_err(|_| ())?
.into_string()
- .map_err(|_| ())?)
+ .map_err(|_| ())
}
/// Returns a `fd` for an opened rendernode device, while filtering out specified
@@ -105,5 +105,5 @@ pub fn open_device(undesired: &[&str]) -> RutabagaResult<File> {
}
}
- Err(RutabagaError::Unsupported)
+ Err(RutabagaError::SpecViolation("no DRM rendernode opened"))
}
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/system_gralloc.rs b/rutabaga_gfx/src/rutabaga_gralloc/system_gralloc.rs
index 0baea51ac..fdba419f3 100644
--- a/rutabaga_gfx/src/rutabaga_gralloc/system_gralloc.rs
+++ b/rutabaga_gfx/src/rutabaga_gralloc/system_gralloc.rs
@@ -47,7 +47,7 @@ impl Gralloc for SystemGralloc {
let shm = SharedMemory::named("rutabaga_gralloc", reqs.size)?;
Ok(RutabagaHandle {
os_handle: shm.into(),
- handle_type: RUTABAGA_MEM_HANDLE_TYPE_OPAQUE_FD,
+ handle_type: RUTABAGA_MEM_HANDLE_TYPE_SHM,
})
}
}
diff --git a/rutabaga_gfx/src/rutabaga_gralloc/vulkano_gralloc.rs b/rutabaga_gfx/src/rutabaga_gralloc/vulkano_gralloc.rs
index 7c66d210b..874ba66db 100644
--- a/rutabaga_gfx/src/rutabaga_gralloc/vulkano_gralloc.rs
+++ b/rutabaga_gfx/src/rutabaga_gralloc/vulkano_gralloc.rs
@@ -9,32 +9,29 @@
#![cfg(feature = "vulkano")]
-use std::collections::BTreeMap as Map;
-use std::convert::TryInto;
-use std::iter::Empty;
-use std::sync::Arc;
+use std::{collections::BTreeMap as Map, convert::TryInto, sync::Arc};
use base::MappedRegion;
use crate::rutabaga_gralloc::gralloc::{Gralloc, ImageAllocationInfo, ImageMemoryRequirements};
use crate::rutabaga_utils::*;
-use vulkano::device::{Device, DeviceCreationError, DeviceExtensions};
-use vulkano::image::{sys, ImageCreateFlags, ImageCreationError, ImageDimensions, ImageUsage};
-
-use vulkano::instance::{
- Instance, InstanceCreationError, InstanceExtensions, MemoryType, PhysicalDevice,
- PhysicalDeviceType,
-};
-
-use vulkano::memory::{
- DedicatedAlloc, DeviceMemoryAllocError, DeviceMemoryBuilder, DeviceMemoryMapping,
- ExternalMemoryHandleType, MemoryRequirements,
+use vulkano::{
+ device::{
+ physical::{MemoryType, PhysicalDevice, PhysicalDeviceType},
+ Device, DeviceCreateInfo, DeviceCreationError, DeviceExtensions, QueueCreateInfo,
+ },
+ image::{sys, ImageCreationError, ImageDimensions, ImageUsage, SampleCount},
+ instance::{Instance, InstanceCreateInfo, InstanceCreationError, InstanceExtensions, Version},
+ memory::{
+ pool::AllocFromRequirementsFilter, DedicatedAllocation, DeviceMemory,
+ DeviceMemoryAllocationError, DeviceMemoryExportError, ExternalMemoryHandleType,
+ ExternalMemoryHandleTypes, MappedDeviceMemory, MemoryAllocateInfo, MemoryMapError,
+ MemoryRequirements,
+ },
+ sync::Sharing,
};
-use vulkano::memory::pool::AllocFromRequirementsFilter;
-use vulkano::sync::Sharing;
-
/// A gralloc implementation capable of allocation `VkDeviceMemory`.
pub struct VulkanoGralloc {
devices: Map<PhysicalDeviceType, Arc<Device>>,
@@ -42,13 +39,16 @@ pub struct VulkanoGralloc {
}
struct VulkanoMapping {
- mapping: DeviceMemoryMapping,
+ mapped_memory: MappedDeviceMemory,
size: usize,
}
impl VulkanoMapping {
- pub fn new(mapping: DeviceMemoryMapping, size: usize) -> VulkanoMapping {
- VulkanoMapping { mapping, size }
+ pub fn new(mapped_memory: MappedDeviceMemory, size: usize) -> VulkanoMapping {
+ VulkanoMapping {
+ mapped_memory,
+ size,
+ }
}
}
@@ -56,7 +56,12 @@ unsafe impl MappedRegion for VulkanoMapping {
/// Used for passing this region for hypervisor memory mappings. We trust crosvm to use this
/// safely.
fn as_ptr(&self) -> *mut u8 {
- unsafe { self.mapping.as_ptr() }
+ unsafe {
+ // Will not panic since the requested range of the device memory was verified on
+ // creation
+ let x = self.mapped_memory.write(0..self.size as u64).unwrap();
+ x.as_mut_ptr()
+ }
}
/// Returns the size of the memory region in bytes.
@@ -70,7 +75,16 @@ impl VulkanoGralloc {
pub fn init() -> RutabagaResult<Box<dyn Gralloc>> {
// Initialization copied from triangle.rs in Vulkano. Look there for a more detailed
// explanation of VK initialization.
- let instance = Instance::new(None, &InstanceExtensions::none(), None)?;
+ let instance_extensions = InstanceExtensions {
+ khr_external_memory_capabilities: true,
+ khr_get_physical_device_properties2: true,
+ ..InstanceExtensions::none()
+ };
+ let instance = Instance::new(InstanceCreateInfo {
+ enabled_extensions: instance_extensions,
+ max_api_version: Some(Version::V1_1),
+ ..Default::default()
+ })?;
let mut devices: Map<PhysicalDeviceType, Arc<Device>> = Default::default();
let mut has_integrated_gpu = false;
@@ -82,36 +96,48 @@ impl VulkanoGralloc {
// We take the first queue family that supports graphics.
q.supports_graphics()
})
- .ok_or(RutabagaError::Unsupported)?;
+ .ok_or(RutabagaError::SpecViolation(
+ "need graphics queue family to proceed",
+ ))?;
- let supported_extensions = DeviceExtensions::supported_by_device(physical);
+ let supported_extensions = physical.supported_extensions();
let desired_extensions = DeviceExtensions {
khr_dedicated_allocation: true,
khr_get_memory_requirements2: true,
khr_external_memory: true,
khr_external_memory_fd: true,
- ext_external_memory_dmabuf: true,
+ ext_external_memory_dma_buf: true,
..DeviceExtensions::none()
};
let intersection = supported_extensions.intersection(&desired_extensions);
- let (device, mut _queues) = Device::new(
+ if let Ok((device, mut _queues)) = Device::new(
physical,
- physical.supported_features(),
- &intersection,
- [(queue_family, 0.5)].iter().cloned(),
- )?;
+ DeviceCreateInfo {
+ enabled_features: physical.supported_features().clone(),
+ enabled_extensions: intersection,
+ queue_create_infos: vec![QueueCreateInfo::family(queue_family)],
+ ..Default::default()
+ },
+ ) {
+ let device_type = device.physical_device().properties().device_type;
+ if device_type == PhysicalDeviceType::IntegratedGpu {
+ has_integrated_gpu = true
+ }
- if device.physical_device().ty() == PhysicalDeviceType::IntegratedGpu {
- has_integrated_gpu = true
- }
+ // If we have two devices of the same type (two integrated GPUs), the old value is
+ // dropped. Vulkano is verbose enough such that a keener selection algorithm may
+ // be used, but the need for such complexity does not seem to exist now.
+ devices.insert(device_type, device);
+ };
+ }
- // If we have two devices of the same type (two integrated GPUs), the old value is
- // dropped. Vulkano is verbose enough such that a keener selection algorithm may
- // be used, but the need for such complexity does not seem to exist now.
- devices.insert(device.physical_device().ty(), device);
+ if devices.is_empty() {
+ return Err(RutabagaError::SpecViolation(
+ "no matching VK devices available",
+ ));
}
Ok(Box::new(VulkanoGralloc {
@@ -127,6 +153,7 @@ impl VulkanoGralloc {
// (3) transfer ownership of images between queues.
//
// In addition, we trust Vulkano to validate image parameters are within the Vulkan spec.
+ // TODO(tutankhamen): Do we still need a separate MemoryRequirements?
unsafe fn create_image(
&mut self,
info: ImageAllocationInfo,
@@ -134,11 +161,11 @@ impl VulkanoGralloc {
let device = if self.has_integrated_gpu {
self.devices
.get(&PhysicalDeviceType::IntegratedGpu)
- .ok_or(RutabagaError::Unsupported)?
+ .ok_or(RutabagaError::InvalidGrallocGpuType)?
} else {
self.devices
.get(&PhysicalDeviceType::DiscreteGpu)
- .ok_or(RutabagaError::Unsupported)?
+ .ok_or(RutabagaError::InvalidGrallocGpuType)?
};
let usage = match info.flags.uses_rendering() {
@@ -154,32 +181,34 @@ impl VulkanoGralloc {
// Reasonable bounds on image width.
if info.width == 0 || info.width > 4096 {
- return Err(RutabagaError::SpecViolation);
+ return Err(RutabagaError::InvalidGrallocDimensions);
}
// Reasonable bounds on image height.
if info.height == 0 || info.height > 4096 {
- return Err(RutabagaError::SpecViolation);
+ return Err(RutabagaError::InvalidGrallocDimensions);
}
let vulkan_format = info.drm_format.vulkan_format()?;
- let (unsafe_image, memory_requirements) = sys::UnsafeImage::new(
+ let unsafe_image = sys::UnsafeImage::new(
device.clone(),
- usage,
- vulkan_format,
- ImageCreateFlags::none(),
- ImageDimensions::Dim2d {
- width: info.width,
- height: info.height,
- array_layers: 1,
+ sys::UnsafeImageCreateInfo {
+ dimensions: ImageDimensions::Dim2d {
+ width: info.width,
+ height: info.height,
+ array_layers: 1,
+ },
+ format: Some(vulkan_format),
+ samples: SampleCount::Sample1,
+ usage,
+ mip_levels: 1,
+ sharing: Sharing::Exclusive,
+ ..Default::default()
},
- 1, /* number of samples */
- 1, /* mipmap count */
- Sharing::Exclusive::<Empty<_>>,
- true, /* linear images only currently */
- false, /* not preinitialized */
)?;
+ let memory_requirements = unsafe_image.memory_requirements();
+
Ok((unsafe_image, memory_requirements))
}
}
@@ -187,7 +216,7 @@ impl VulkanoGralloc {
impl Gralloc for VulkanoGralloc {
fn supports_external_gpu_memory(&self) -> bool {
for device in self.devices.values() {
- if !device.loaded_extensions().khr_external_memory {
+ if !device.enabled_extensions().khr_external_memory {
return false;
}
}
@@ -197,7 +226,7 @@ impl Gralloc for VulkanoGralloc {
fn supports_dmabuf(&self) -> bool {
for device in self.devices.values() {
- if !device.loaded_extensions().ext_external_memory_dmabuf {
+ if !device.enabled_extensions().ext_external_memory_dma_buf {
return false;
}
}
@@ -216,11 +245,11 @@ impl Gralloc for VulkanoGralloc {
let device = if self.has_integrated_gpu {
self.devices
.get(&PhysicalDeviceType::IntegratedGpu)
- .ok_or(RutabagaError::Unsupported)?
+ .ok_or(RutabagaError::InvalidGrallocGpuType)?
} else {
self.devices
.get(&PhysicalDeviceType::DiscreteGpu)
- .ok_or(RutabagaError::Unsupported)?
+ .ok_or(RutabagaError::InvalidGrallocGpuType)?
};
let planar_layout = info.drm_format.planar_layout()?;
@@ -275,7 +304,9 @@ impl Gralloc for VulkanoGralloc {
.chain(second_loop)
.filter(|&(t, _)| (memory_requirements.memory_type_bits & (1 << t.id())) != 0)
.find(|&(t, rq)| filter(t) == rq)
- .ok_or(RutabagaError::Unsupported)?
+ .ok_or(RutabagaError::SpecViolation(
+ "unable to find required memory type",
+ ))?
.0
};
@@ -301,59 +332,66 @@ impl Gralloc for VulkanoGralloc {
fn allocate_memory(&mut self, reqs: ImageMemoryRequirements) -> RutabagaResult<RutabagaHandle> {
let (unsafe_image, memory_requirements) = unsafe { self.create_image(reqs.info)? };
- let vulkan_info = reqs.vulkan_info.ok_or(RutabagaError::SpecViolation)?;
+ let vulkan_info = reqs.vulkan_info.ok_or(RutabagaError::InvalidVulkanInfo)?;
let device = if self.has_integrated_gpu {
self.devices
.get(&PhysicalDeviceType::IntegratedGpu)
- .ok_or(RutabagaError::Unsupported)?
+ .ok_or(RutabagaError::InvalidGrallocGpuType)?
} else {
self.devices
.get(&PhysicalDeviceType::DiscreteGpu)
- .ok_or(RutabagaError::Unsupported)?
+ .ok_or(RutabagaError::InvalidGrallocGpuType)?
};
let memory_type = device
.physical_device()
.memory_type_by_id(vulkan_info.memory_idx)
- .ok_or(RutabagaError::SpecViolation)?;
+ .ok_or(RutabagaError::InvalidVulkanInfo)?;
- let (handle_type, rutabaga_type) =
- match device.loaded_extensions().ext_external_memory_dmabuf {
+ let (export_handle_type, export_handle_types, rutabaga_type) =
+ match device.enabled_extensions().ext_external_memory_dma_buf {
true => (
- ExternalMemoryHandleType {
+ ExternalMemoryHandleType::DmaBuf,
+ ExternalMemoryHandleTypes {
dma_buf: true,
- ..ExternalMemoryHandleType::none()
+ ..ExternalMemoryHandleTypes::none()
},
RUTABAGA_MEM_HANDLE_TYPE_DMABUF,
),
false => (
- ExternalMemoryHandleType {
+ ExternalMemoryHandleType::OpaqueFd,
+ ExternalMemoryHandleTypes {
opaque_fd: true,
- ..ExternalMemoryHandleType::none()
+ ..ExternalMemoryHandleTypes::none()
},
RUTABAGA_MEM_HANDLE_TYPE_OPAQUE_FD,
),
};
- let dedicated = match device.loaded_extensions().khr_dedicated_allocation {
+ let dedicated_allocation = match device.enabled_extensions().khr_dedicated_allocation {
true => {
if memory_requirements.prefer_dedicated {
- DedicatedAlloc::Image(&unsafe_image)
+ Some(DedicatedAllocation::Image(&unsafe_image))
} else {
- DedicatedAlloc::None
+ None
}
}
- false => DedicatedAlloc::None,
+ false => None,
};
- let device_memory =
- DeviceMemoryBuilder::new(device.clone(), memory_type.id(), reqs.size as usize)
- .dedicated_info(dedicated)
- .export_info(handle_type)
- .build()?;
+ let device_memory = DeviceMemory::allocate(
+ device.clone(),
+ MemoryAllocateInfo {
+ allocation_size: reqs.size,
+ memory_type_index: memory_type.id(),
+ dedicated_allocation,
+ export_handle_types,
+ ..Default::default()
+ },
+ )?;
- let descriptor = device_memory.export_fd(handle_type)?.into();
+ let descriptor = device_memory.export_fd(export_handle_type)?.into();
Ok(RutabagaHandle {
os_handle: descriptor,
@@ -374,28 +412,36 @@ impl Gralloc for VulkanoGralloc {
.find(|device| {
device.physical_device().index() as u32 == vulkan_info.physical_device_idx
})
- .ok_or(RutabagaError::Unsupported)?;
+ .ok_or(RutabagaError::InvalidVulkanInfo)?;
- let handle_type = match handle.handle_type {
- RUTABAGA_MEM_HANDLE_TYPE_DMABUF => ExternalMemoryHandleType {
+ let export_handle_types = match handle.handle_type {
+ RUTABAGA_MEM_HANDLE_TYPE_DMABUF => ExternalMemoryHandleTypes {
dma_buf: true,
- ..ExternalMemoryHandleType::none()
+ ..ExternalMemoryHandleTypes::none()
},
- RUTABAGA_MEM_HANDLE_TYPE_OPAQUE_FD => ExternalMemoryHandleType {
+ RUTABAGA_MEM_HANDLE_TYPE_OPAQUE_FD => ExternalMemoryHandleTypes {
opaque_fd: true,
- ..ExternalMemoryHandleType::none()
+ ..ExternalMemoryHandleTypes::none()
},
- _ => return Err(RutabagaError::Unsupported),
+ _ => return Err(RutabagaError::InvalidRutabagaHandle),
};
- let valid_size: usize = size.try_into()?;
- let device_memory =
- DeviceMemoryBuilder::new(device.clone(), vulkan_info.memory_idx, valid_size)
- .import_info(handle.os_handle.into(), handle_type)
- .build()?;
- let mapping = DeviceMemoryMapping::new(device.clone(), device_memory.clone(), 0, size, 0)?;
+ let device_memory = DeviceMemory::allocate(
+ device.clone(),
+ MemoryAllocateInfo {
+ allocation_size: size,
+ memory_type_index: vulkan_info.memory_idx,
+ export_handle_types,
+ ..Default::default()
+ },
+ )?;
+
+ let mapped_memory = MappedDeviceMemory::new(device_memory, 0..size)?;
- Ok(Box::new(VulkanoMapping::new(mapping, valid_size)))
+ Ok(Box::new(VulkanoMapping::new(
+ mapped_memory,
+ size.try_into()?,
+ )))
}
}
@@ -419,8 +465,20 @@ impl From<DeviceCreationError> for RutabagaError {
}
}
-impl From<DeviceMemoryAllocError> for RutabagaError {
- fn from(e: DeviceMemoryAllocError) -> RutabagaError {
- RutabagaError::VkDeviceMemoryAllocError(e)
+impl From<DeviceMemoryAllocationError> for RutabagaError {
+ fn from(e: DeviceMemoryAllocationError) -> RutabagaError {
+ RutabagaError::VkDeviceMemoryAllocationError(e)
+ }
+}
+
+impl From<DeviceMemoryExportError> for RutabagaError {
+ fn from(e: DeviceMemoryExportError) -> RutabagaError {
+ RutabagaError::VkDeviceMemoryExportError(e)
+ }
+}
+
+impl From<MemoryMapError> for RutabagaError {
+ fn from(e: MemoryMapError) -> RutabagaError {
+ RutabagaError::VkMemoryMapError(e)
}
}
diff --git a/rutabaga_gfx/src/rutabaga_utils.rs b/rutabaga_gfx/src/rutabaga_utils.rs
index 06f6fe7d6..04f7b48b0 100644
--- a/rutabaga_gfx/src/rutabaga_utils.rs
+++ b/rutabaga_gfx/src/rutabaga_utils.rs
@@ -4,15 +4,16 @@
//! rutabaga_utils: Utility enums, structs, and implementations needed by the rest of the crate.
-use std::fmt::{self, Display};
use std::io::Error as IoError;
use std::num::TryFromIntError;
use std::os::raw::c_void;
use std::path::PathBuf;
use std::str::Utf8Error;
-use base::{Error as SysError, ExternalMappingError, SafeDescriptor};
+use base::{Error as BaseError, ExternalMappingError, SafeDescriptor};
use data_model::VolatileMemoryError;
+use remain::sorted;
+use thiserror::Error;
#[cfg(feature = "vulkano")]
use vulkano::device::DeviceCreationError;
@@ -21,21 +22,30 @@ use vulkano::image::ImageCreationError;
#[cfg(feature = "vulkano")]
use vulkano::instance::InstanceCreationError;
#[cfg(feature = "vulkano")]
-use vulkano::memory::DeviceMemoryAllocError;
+use vulkano::memory::DeviceMemoryAllocationError;
+#[cfg(feature = "vulkano")]
+use vulkano::memory::DeviceMemoryExportError;
+#[cfg(feature = "vulkano")]
+use vulkano::memory::MemoryMapError;
/// Represents a buffer. `base` contains the address of a buffer, while `len` contains the length
/// of the buffer.
+#[repr(C)]
#[derive(Copy, Clone)]
pub struct RutabagaIovec {
pub base: *mut c_void,
pub len: usize,
}
+unsafe impl Send for RutabagaIovec {}
+unsafe impl Sync for RutabagaIovec {}
+
/// 3D resource creation parameters. Also used to create 2D resource. Constants based on Mesa's
/// (internal) Gallium interface. Not in the virtio-gpu spec, but should be since dumb resources
/// can't work with gfxstream/virglrenderer without this.
pub const RUTABAGA_PIPE_TEXTURE_2D: u32 = 2;
pub const RUTABAGA_PIPE_BIND_RENDER_TARGET: u32 = 2;
+#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct ResourceCreate3D {
pub target: u32,
@@ -58,6 +68,7 @@ pub const RUTABAGA_BLOB_MEM_HOST3D_GUEST: u32 = 0x0003;
pub const RUTABAGA_BLOB_FLAG_USE_MAPPABLE: u32 = 0x0001;
pub const RUTABAGA_BLOB_FLAG_USE_SHAREABLE: u32 = 0x0002;
pub const RUTABAGA_BLOB_FLAG_USE_CROSS_DEVICE: u32 = 0x0004;
+#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct ResourceCreateBlob {
pub blob_mem: u32,
@@ -84,19 +95,21 @@ pub struct VulkanInfo {
pub physical_device_idx: u32,
}
-/// Rutabaga context init capset id mask (not upstreamed).
+/// Rutabaga context init capset id mask.
pub const RUTABAGA_CONTEXT_INIT_CAPSET_ID_MASK: u32 = 0x00ff;
-/// Rutabaga flags for creating fences (fence ctx idx info not upstreamed).
+/// Rutabaga flags for creating fences.
pub const RUTABAGA_FLAG_FENCE: u32 = 1 << 0;
-pub const RUTABAGA_FLAG_INFO_FENCE_CTX_IDX: u32 = 1 << 1;
+pub const RUTABAGA_FLAG_INFO_RING_IDX: u32 = 1 << 1;
/// Convenience struct for Rutabaga fences
-pub struct RutabagaFenceData {
+#[repr(C)]
+#[derive(Copy, Clone)]
+pub struct RutabagaFence {
pub flags: u32,
pub fence_id: u64,
pub ctx_id: u32,
- pub fence_ctx_idx: u32,
+ pub ring_idx: u8,
}
/// Mapped memory caching flags (see virtio_gpu spec)
@@ -107,127 +120,139 @@ pub const RUTABAGA_MAP_CACHE_WC: u32 = 0x03;
/// Rutabaga capsets.
pub const RUTABAGA_CAPSET_VIRGL: u32 = 1;
pub const RUTABAGA_CAPSET_VIRGL2: u32 = 2;
-/// The following capsets are not upstreamed.
pub const RUTABAGA_CAPSET_GFXSTREAM: u32 = 3;
pub const RUTABAGA_CAPSET_VENUS: u32 = 4;
pub const RUTABAGA_CAPSET_CROSS_DOMAIN: u32 = 5;
/// An error generated while using this crate.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum RutabagaError {
/// Indicates `Rutabaga` was already initialized since only one Rutabaga instance per process
/// is allowed.
+ #[error("attempted to use a rutabaga asset already in use")]
AlreadyInUse,
+ /// Base error returned as a result of rutabaga library operation.
+ #[error("rutabaga received a base error: {0}")]
+ BaseError(BaseError),
/// Checked Arithmetic error
+ #[error("arithmetic failed: {}({}) {op} {}({})", .field1.0, .field1.1, .field2.0, .field2.1)]
CheckedArithmetic {
field1: (&'static str, usize),
field2: (&'static str, usize),
op: &'static str,
},
/// Checked Range error
+ #[error("range check failed: {}({}) vs {}({})", .field1.0, .field1.1, .field2.0, .field2.1)]
CheckedRange {
field1: (&'static str, usize),
field2: (&'static str, usize),
},
- /// The Rutabaga component failed to export a RutabagaHandle.
- ExportedRutabagaHandle,
+ /// An internal Rutabaga component error was returned.
+ #[error("rutabaga component failed with error {0}")]
+ ComponentError(i32),
+ /// Invalid 2D info
+ #[error("invalid 2D info")]
+ Invalid2DInfo,
/// Invalid Capset
+ #[error("invalid capset")]
InvalidCapset,
/// A command size was submitted that was invalid.
+ #[error("command buffer submitted with invalid size: {0}")]
InvalidCommandSize(usize),
/// Invalid RutabagaComponent
+ #[error("invalid rutabaga component")]
InvalidComponent,
/// Invalid Context ID
+ #[error("invalid context id")]
InvalidContextId,
+ /// Invalid cross domain channel
+ #[error("invalid cross domain channel")]
+ InvalidCrossDomainChannel,
+ /// Invalid cross domain item ID
+ #[error("invalid cross domain item id")]
+ InvalidCrossDomainItemId,
+ /// Invalid cross domain item type
+ #[error("invalid cross domain item type")]
+ InvalidCrossDomainItemType,
+ /// Invalid cross domain state
+ #[error("invalid cross domain state")]
+ InvalidCrossDomainState,
+ /// Invalid gralloc backend.
+ #[error("invalid gralloc backend")]
+ InvalidGrallocBackend,
+ /// Invalid gralloc dimensions.
+ #[error("invalid gralloc dimensions")]
+ InvalidGrallocDimensions,
+ /// Invalid gralloc DRM format.
+ #[error("invalid gralloc DRM format")]
+ InvalidGrallocDrmFormat,
+ /// Invalid GPU type.
+ #[error("invalid GPU type for gralloc")]
+ InvalidGrallocGpuType,
+ /// Invalid number of YUV planes.
+ #[error("invalid number of YUV planes")]
+ InvalidGrallocNumberOfPlanes,
/// The indicated region of guest memory is invalid.
+ #[error("an iovec is outside of guest memory's range")]
InvalidIovec,
/// Invalid Resource ID.
+ #[error("invalid resource id")]
InvalidResourceId,
/// Indicates an error in the RutabagaBuilder.
- InvalidRutabagaBuild,
+ #[error("invalid rutabaga build parameters: {0}")]
+ InvalidRutabagaBuild(&'static str),
+ /// An error with the RutabagaHandle
+ #[error("invalid rutabaga handle")]
+ InvalidRutabagaHandle,
+ /// Invalid Vulkan info
+ #[error("invalid vulkan info")]
+ InvalidVulkanInfo,
/// An input/output error occured.
+ #[error("an input/output error occur: {0}")]
IoError(IoError),
/// The mapping failed.
+ #[error("The mapping failed for the following reason: {0}")]
MappingFailed(ExternalMappingError),
- /// An internal Rutabaga component error was returned.
- ComponentError(i32),
/// Violation of the Rutabaga spec occured.
- SpecViolation,
- /// System error returned as a result of rutabaga library operation.
- SysError(SysError),
+ #[error("violation of the rutabaga spec: {0}")]
+ SpecViolation(&'static str),
/// An attempted integer conversion failed.
+ #[error("int conversion failed: {0}")]
TryFromIntError(TryFromIntError),
/// The command is unsupported.
+ #[error("the requested function is not implemented")]
Unsupported,
/// Utf8 error.
+ #[error("an utf8 error occured: {0}")]
Utf8Error(Utf8Error),
- /// Volatile memory error
- VolatileMemoryError(VolatileMemoryError),
+ /// Device creation error
+ #[cfg(feature = "vulkano")]
+ #[error("vulkano device creation failure {0}")]
+ VkDeviceCreationError(DeviceCreationError),
+ /// Device memory allocation error
+ #[cfg(feature = "vulkano")]
+ #[error("vulkano device memory allocation failure {0}")]
+ VkDeviceMemoryAllocationError(DeviceMemoryAllocationError),
+ /// Device memory export error
+ #[cfg(feature = "vulkano")]
+ #[error("vulkano device memory export failure {0}")]
+ VkDeviceMemoryExportError(DeviceMemoryExportError),
/// Image creation error
#[cfg(feature = "vulkano")]
+ #[error("vulkano image creation failure {0}")]
VkImageCreationError(ImageCreationError),
/// Instance creation error
#[cfg(feature = "vulkano")]
+ #[error("vulkano instance creation failure {0}")]
VkInstanceCreationError(InstanceCreationError),
- /// Device creation error
+ /// Memory map error
#[cfg(feature = "vulkano")]
- VkDeviceCreationError(DeviceCreationError),
- /// Device memory allocation error
- #[cfg(feature = "vulkano")]
- VkDeviceMemoryAllocError(DeviceMemoryAllocError),
-}
-
-impl Display for RutabagaError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::RutabagaError::*;
- match self {
- AlreadyInUse => write!(f, "attempted to use a rutabaga asset already in use"),
- CheckedArithmetic {
- field1: (label1, value1),
- field2: (label2, value2),
- op,
- } => write!(
- f,
- "arithmetic failed: {}({}) {} {}({})",
- label1, value1, op, label2, value2
- ),
- CheckedRange {
- field1: (label1, value1),
- field2: (label2, value2),
- } => write!(
- f,
- "range check failed: {}({}) vs {}({})",
- label1, value1, label2, value2
- ),
- ExportedRutabagaHandle => write!(f, "failed to export Rutabaga handle"),
- InvalidCapset => write!(f, "invalid capset"),
- InvalidCommandSize(s) => write!(f, "command buffer submitted with invalid size: {}", s),
- InvalidComponent => write!(f, "invalid rutabaga component"),
- InvalidContextId => write!(f, "invalid context id"),
- InvalidIovec => write!(f, "an iovec is outside of guest memory's range"),
- InvalidResourceId => write!(f, "invalid resource id"),
- InvalidRutabagaBuild => write!(f, "invalid rutabaga build parameters"),
- IoError(e) => write!(f, "an input/output error occur: {}", e),
- MappingFailed(s) => write!(f, "The mapping failed for the following reason: {}", s),
- ComponentError(ret) => write!(f, "rutabaga component failed with error {}", ret),
- SpecViolation => write!(f, "violation of the rutabaga spec"),
- SysError(e) => write!(f, "rutabaga received a system error: {}", e),
- TryFromIntError(e) => write!(f, "int conversion failed: {}", e),
- Unsupported => write!(f, "feature or function unsupported"),
- Utf8Error(e) => write!(f, "an utf8 error occured: {}", e),
- VolatileMemoryError(e) => write!(f, "noticed a volatile memory error {}", e),
- #[cfg(feature = "vulkano")]
- VkDeviceCreationError(e) => write!(f, "vulkano device creation failure {}", e),
- #[cfg(feature = "vulkano")]
- VkDeviceMemoryAllocError(e) => {
- write!(f, "vulkano device memory allocation failure {}", e)
- }
- #[cfg(feature = "vulkano")]
- VkImageCreationError(e) => write!(f, "vulkano image creation failure {}", e),
- #[cfg(feature = "vulkano")]
- VkInstanceCreationError(e) => write!(f, "vulkano instance creation failure {}", e),
- }
- }
+ #[error("vullano memory map failure {0}")]
+ VkMemoryMapError(MemoryMapError),
+ /// Volatile memory error
+ #[error("noticed a volatile memory error {0}")]
+ VolatileMemoryError(VolatileMemoryError),
}
impl From<IoError> for RutabagaError {
@@ -236,9 +261,9 @@ impl From<IoError> for RutabagaError {
}
}
-impl From<SysError> for RutabagaError {
- fn from(e: SysError) -> RutabagaError {
- RutabagaError::SysError(e)
+impl From<BaseError> for RutabagaError {
+ fn from(e: BaseError) -> RutabagaError {
+ RutabagaError::BaseError(e)
}
}
@@ -265,7 +290,6 @@ pub type RutabagaResult<T> = std::result::Result<T, RutabagaError>;
/// Flags for virglrenderer. Copied from virglrenderer bindings.
const VIRGLRENDERER_USE_EGL: u32 = 1 << 0;
-#[allow(dead_code)]
const VIRGLRENDERER_THREAD_SYNC: u32 = 1 << 1;
const VIRGLRENDERER_USE_GLX: u32 = 1 << 2;
const VIRGLRENDERER_USE_SURFACELESS: u32 = 1 << 3;
@@ -273,6 +297,8 @@ const VIRGLRENDERER_USE_GLES: u32 = 1 << 4;
const VIRGLRENDERER_USE_EXTERNAL_BLOB: u32 = 1 << 5;
const VIRGLRENDERER_VENUS: u32 = 1 << 6;
const VIRGLRENDERER_NO_VIRGL: u32 = 1 << 7;
+const VIRGLRENDERER_USE_ASYNC_FENCE_CB: u32 = 1 << 8;
+const VIRGLRENDERER_RENDER_SERVER: u32 = 1 << 9;
/// virglrenderer flag struct.
#[derive(Copy, Clone)]
@@ -286,6 +312,7 @@ impl Default for VirglRendererFlags {
.use_egl(true)
.use_surfaceless(true)
.use_gles(true)
+ .use_render_server(false)
}
}
@@ -324,6 +351,11 @@ impl VirglRendererFlags {
self.set_flag(VIRGLRENDERER_USE_EGL, v)
}
+ /// Use a dedicated thread for fence synchronization.
+ pub fn use_thread_sync(self, v: bool) -> VirglRendererFlags {
+ self.set_flag(VIRGLRENDERER_THREAD_SYNC, v)
+ }
+
/// Use GLX for context creation.
pub fn use_glx(self, v: bool) -> VirglRendererFlags {
self.set_flag(VIRGLRENDERER_USE_GLX, v)
@@ -343,6 +375,15 @@ impl VirglRendererFlags {
pub fn use_external_blob(self, v: bool) -> VirglRendererFlags {
self.set_flag(VIRGLRENDERER_USE_EXTERNAL_BLOB, v)
}
+
+ /// Retire fence directly from sync thread.
+ pub fn use_async_fence_cb(self, v: bool) -> VirglRendererFlags {
+ self.set_flag(VIRGLRENDERER_USE_ASYNC_FENCE_CB, v)
+ }
+
+ pub fn use_render_server(self, v: bool) -> VirglRendererFlags {
+ self.set_flag(VIRGLRENDERER_RENDER_SERVER, v)
+ }
}
/// Flags for the gfxstream renderer.
@@ -355,6 +396,7 @@ const GFXSTREAM_RENDERER_FLAGS_USE_GLES: u32 = 1 << 4;
const GFXSTREAM_RENDERER_FLAGS_NO_VK_BIT: u32 = 1 << 5;
const GFXSTREAM_RENDERER_FLAGS_NO_SYNCFD_BIT: u32 = 1 << 20;
const GFXSTREAM_RENDERER_FLAGS_GUEST_USES_ANGLE: u32 = 1 << 21;
+const GFXSTREAM_RENDERER_FLAGS_ASYNC_FENCE_CB: u32 = 1 << 23;
/// gfxstream flag struct.
#[derive(Copy, Clone, Default)]
@@ -408,6 +450,11 @@ impl GfxstreamFlags {
pub fn use_guest_angle(self, v: bool) -> GfxstreamFlags {
self.set_flag(GFXSTREAM_RENDERER_FLAGS_GUEST_USES_ANGLE, v)
}
+
+ /// Use async fence completion callback.
+ pub fn use_async_fence_cb(self, v: bool) -> GfxstreamFlags {
+ self.set_flag(GFXSTREAM_RENDERER_FLAGS_ASYNC_FENCE_CB, v)
+ }
}
impl From<GfxstreamFlags> for i32 {
@@ -417,7 +464,8 @@ impl From<GfxstreamFlags> for i32 {
}
/// Transfers {to, from} 1D buffers, 2D textures, 3D textures, and cubemaps.
-#[derive(Debug)]
+#[repr(C)]
+#[derive(Copy, Clone, Debug)]
pub struct Transfer3D {
pub x: u32,
pub y: u32,
@@ -468,7 +516,7 @@ pub struct RutabagaChannel {
}
/// Enumeration of possible rutabaga components.
-#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum RutabagaComponentType {
Rutabaga2D,
VirglRenderer,
@@ -480,9 +528,10 @@ pub enum RutabagaComponentType {
pub const RUTABAGA_MEM_HANDLE_TYPE_OPAQUE_FD: u32 = 0x0001;
pub const RUTABAGA_MEM_HANDLE_TYPE_DMABUF: u32 = 0x0002;
pub const RUTABAGE_MEM_HANDLE_TYPE_OPAQUE_WIN32: u32 = 0x0003;
-pub const RUTABAGA_FENCE_HANDLE_TYPE_OPAQUE_FD: u32 = 0x0004;
-pub const RUTABAGA_FENCE_HANDLE_TYPE_SYNC_FD: u32 = 0x0005;
-pub const RUTABAGE_FENCE_HANDLE_TYPE_OPAQUE_WIN32: u32 = 0x0006;
+pub const RUTABAGA_MEM_HANDLE_TYPE_SHM: u32 = 0x0004;
+pub const RUTABAGA_FENCE_HANDLE_TYPE_OPAQUE_FD: u32 = 0x0010;
+pub const RUTABAGA_FENCE_HANDLE_TYPE_SYNC_FD: u32 = 0x0011;
+pub const RUTABAGE_FENCE_HANDLE_TYPE_OPAQUE_WIN32: u32 = 0x0012;
/// Handle to OS-specific memory or synchronization objects.
pub struct RutabagaHandle {
@@ -496,10 +545,53 @@ impl RutabagaHandle {
let clone = self
.os_handle
.try_clone()
- .map_err(|_| RutabagaError::Unsupported)?;
+ .map_err(|_| RutabagaError::InvalidRutabagaHandle)?;
Ok(RutabagaHandle {
os_handle: clone,
handle_type: self.handle_type,
})
}
}
+
+/// Trait for fence completion handlers
+pub trait RutabagaFenceCallback: Send {
+ fn call(&self, data: RutabagaFence);
+ fn clone_box(&self) -> RutabagaFenceHandler;
+}
+
+/// Wrapper type to allow cloning while respecting object-safety
+pub type RutabagaFenceHandler = Box<dyn RutabagaFenceCallback>;
+
+impl Clone for RutabagaFenceHandler {
+ fn clone(&self) -> Self {
+ self.clone_box()
+ }
+}
+
+/// Fence handler implementation that wraps a closure
+#[derive(Clone)]
+pub struct RutabagaFenceClosure<T> {
+ closure: T,
+}
+
+impl<T> RutabagaFenceClosure<T>
+where
+ T: Fn(RutabagaFence) + Clone + Send + 'static,
+{
+ pub fn new(closure: T) -> RutabagaFenceHandler {
+ Box::new(RutabagaFenceClosure { closure })
+ }
+}
+
+impl<T> RutabagaFenceCallback for RutabagaFenceClosure<T>
+where
+ T: Fn(RutabagaFence) + Clone + Send + 'static,
+{
+ fn call(&self, data: RutabagaFence) {
+ (self.closure)(data)
+ }
+
+ fn clone_box(&self) -> RutabagaFenceHandler {
+ Box::new(self.clone())
+ }
+}
diff --git a/rutabaga_gfx/src/virgl_renderer.rs b/rutabaga_gfx/src/virgl_renderer.rs
index cdea147a3..5bd7a66da 100644
--- a/rutabaga_gfx/src/virgl_renderer.rs
+++ b/rutabaga_gfx/src/virgl_renderer.rs
@@ -3,16 +3,18 @@
// found in the LICENSE file.
//! virgl_renderer: Handles 3D virtio-gpu hypercalls using virglrenderer.
-//! External code found at https://gitlab.freedesktop.org/virgl/virglrenderer/.
+//! External code found at <https://gitlab.freedesktop.org/virgl/virglrenderer/>.
#![cfg(feature = "virgl_renderer")]
-use std::cell::RefCell;
-use std::ffi::CString;
+use std::cmp::min;
+use std::convert::TryFrom;
use std::mem::{size_of, transmute};
use std::os::raw::{c_char, c_void};
+use std::os::unix::io::AsRawFd;
+use std::panic::catch_unwind;
+use std::process::abort;
use std::ptr::null_mut;
-use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
@@ -21,6 +23,7 @@ use base::{
FromRawDescriptor, SafeDescriptor,
};
+use crate::generated::virgl_debug_callback_bindings::*;
use crate::generated::virgl_renderer_bindings::*;
use crate::renderer_utils::*;
use crate::rutabaga_core::{RutabagaComponent, RutabagaContext, RutabagaResource};
@@ -28,14 +31,10 @@ use crate::rutabaga_utils::*;
use data_model::VolatileSlice;
-use libc::close;
-
type Query = virgl_renderer_export_query;
/// The virtio-gpu backend state tracker which supports accelerated rendering.
-pub struct VirglRenderer {
- fence_state: Rc<RefCell<FenceState>>,
-}
+pub struct VirglRenderer;
struct VirglRendererContext {
ctx_id: u32,
@@ -74,6 +73,22 @@ impl RutabagaContext for VirglRendererContext {
virgl_renderer_ctx_detach_resource(self.ctx_id as i32, resource.resource_id as i32);
}
}
+
+ fn component_type(&self) -> RutabagaComponentType {
+ RutabagaComponentType::VirglRenderer
+ }
+
+ fn context_create_fence(&mut self, fence: RutabagaFence) -> RutabagaResult<()> {
+ let ret = unsafe {
+ virgl_renderer_context_create_fence(
+ fence.ctx_id,
+ fence.flags,
+ fence.ring_idx as u64,
+ fence.fence_id as *mut ::std::os::raw::c_void,
+ )
+ };
+ ret_to_res(ret)
+ }
}
impl Drop for VirglRendererContext {
@@ -85,32 +100,80 @@ impl Drop for VirglRendererContext {
}
}
-#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
-extern "C" fn debug_callback(fmt: *const ::std::os::raw::c_char, ap: *mut __va_list_tag) {
- let len: u32 = 256;
- let mut c_str = CString::new(vec![' ' as u8; len as usize]).unwrap();
- unsafe {
- let mut varargs = __va_list_tag {
- gp_offset: (*ap).gp_offset,
- fp_offset: (*ap).fp_offset,
- overflow_arg_area: (*ap).overflow_arg_area,
- reg_save_area: (*ap).reg_save_area,
- };
+#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm"))]
+extern "C" fn debug_callback(fmt: *const ::std::os::raw::c_char, ap: stdio::va_list) {
+ const BUF_LEN: usize = 256;
+ let mut v = [b' '; BUF_LEN];
- let raw = c_str.into_raw();
- vsnprintf(raw, len.into(), fmt, &mut varargs);
- c_str = CString::from_raw(raw);
+ let printed_len = unsafe {
+ let ptr = v.as_mut_ptr() as *mut ::std::os::raw::c_char;
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ let size = BUF_LEN as ::std::os::raw::c_ulong;
+ #[cfg(target_arch = "arm")]
+ let size = BUF_LEN as ::std::os::raw::c_uint;
+
+ stdio::vsnprintf(ptr, size, fmt, ap)
+ };
+
+ if printed_len < 0 {
+ base::debug!(
+ "rutabaga_gfx::virgl_renderer::debug_callback: vsnprintf returned {}",
+ printed_len
+ );
+ } else {
+ // vsnprintf returns the number of chars that *would* have been printed
+ let len = min(printed_len as usize, BUF_LEN - 1);
+ base::debug!("{}", String::from_utf8_lossy(&v[..len]));
}
- base::debug!("{}", c_str.to_string_lossy());
+}
+
+/// Virglrenderer's vtest renderer currently expects an opaque "void *fence_cookie" rather than a
+/// bare "u64 fence_id", so we cannot use the common implementation from renderer_utils yet.
+///
+/// TODO(ryanneph): re-evaluate if vtest can be modified so this can be unified with
+/// write_context_fence() from renderer_utils before promoting to cfg(feature = "virgl_renderer").
+#[cfg(feature = "virgl_renderer_next")]
+extern "C" fn write_context_fence(
+ cookie: *mut c_void,
+ ctx_id: u32,
+ ring_idx: u64,
+ fence_cookie: *mut c_void,
+) {
+ catch_unwind(|| {
+ assert!(!cookie.is_null());
+ let cookie = unsafe { &*(cookie as *mut VirglCookie) };
+
+ // Call fence completion callback
+ if let Some(handler) = &cookie.fence_handler {
+ handler.call(RutabagaFence {
+ flags: RUTABAGA_FLAG_FENCE | RUTABAGA_FLAG_INFO_RING_IDX,
+ fence_id: fence_cookie as u64,
+ ctx_id,
+ ring_idx: ring_idx as u8,
+ });
+ }
+ })
+ .unwrap_or_else(|_| abort())
}
const VIRGL_RENDERER_CALLBACKS: &virgl_renderer_callbacks = &virgl_renderer_callbacks {
+ #[cfg(not(feature = "virgl_renderer_next"))]
version: 1,
+ #[cfg(feature = "virgl_renderer_next")]
+ version: 3,
write_fence: Some(write_fence),
create_gl_context: None,
destroy_gl_context: None,
make_current: None,
get_drm_fd: None,
+ #[cfg(not(feature = "virgl_renderer_next"))]
+ write_context_fence: None,
+ #[cfg(feature = "virgl_renderer_next")]
+ write_context_fence: Some(write_context_fence),
+ #[cfg(not(feature = "virgl_renderer_next"))]
+ get_server_fd: None,
+ #[cfg(feature = "virgl_renderer_next")]
+ get_server_fd: Some(get_server_fd),
};
/// Retrieves metadata suitable for export about this resource. If "export_fd" is true,
@@ -168,6 +231,8 @@ fn unmap_func(resource_id: u32) {
impl VirglRenderer {
pub fn init(
virglrenderer_flags: VirglRendererFlags,
+ fence_handler: RutabagaFenceHandler,
+ render_server_fd: Option<SafeDescriptor>,
) -> RutabagaResult<Box<dyn RutabagaComponent>> {
if cfg!(debug_assertions) {
let ret = unsafe { libc::dup2(libc::STDOUT_FILENO, libc::STDERR_FILENO) };
@@ -191,14 +256,12 @@ impl VirglRenderer {
// Otherwise, Resource and Context would become invalid because their lifetime is not tied
// to the Renderer instance. Doing so greatly simplifies the ownership for users of this
// library.
-
- let fence_state = Rc::new(RefCell::new(FenceState { latest_fence: 0 }));
-
let cookie: *mut VirglCookie = Box::into_raw(Box::new(VirglCookie {
- fence_state: Rc::clone(&fence_state),
+ render_server_fd,
+ fence_handler: Some(fence_handler),
}));
- #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ #[cfg(any(target_arch = "arm", target_arch = "x86", target_arch = "x86_64"))]
unsafe {
virgl_set_debug_callback(Some(debug_callback))
};
@@ -214,7 +277,7 @@ impl VirglRenderer {
};
ret_to_res(ret)?;
- Ok(Box::new(VirglRenderer { fence_state }))
+ Ok(Box::new(VirglRenderer))
}
#[allow(unused_variables)]
@@ -260,18 +323,21 @@ impl VirglRenderer {
};
ret_to_res(ret)?;
- /* Only support dma-bufs until someone wants opaque fds too. */
- if fd_type != VIRGL_RENDERER_BLOB_FD_TYPE_DMABUF {
- // Safe because the FD was just returned by a successful virglrenderer
- // call so it must be valid and owned by us.
- unsafe { close(fd) };
- return Err(RutabagaError::Unsupported);
- }
+ // Safe because the FD was just returned by a successful virglrenderer
+ // call so it must be valid and owned by us.
+ let handle = unsafe { SafeDescriptor::from_raw_descriptor(fd) };
+
+ let handle_type = match fd_type {
+ VIRGL_RENDERER_BLOB_FD_TYPE_DMABUF => RUTABAGA_MEM_HANDLE_TYPE_DMABUF,
+ VIRGL_RENDERER_BLOB_FD_TYPE_SHM => RUTABAGA_MEM_HANDLE_TYPE_SHM,
+ _ => {
+ return Err(RutabagaError::Unsupported);
+ }
+ };
- let dmabuf = unsafe { SafeDescriptor::from_raw_descriptor(fd) };
Ok(Arc::new(RutabagaHandle {
- os_handle: dmabuf,
- handle_type: RUTABAGA_MEM_HANDLE_TYPE_DMABUF,
+ os_handle: handle,
+ handle_type,
}))
}
#[cfg(not(feature = "virgl_renderer_next"))]
@@ -279,6 +345,19 @@ impl VirglRenderer {
}
}
+impl Drop for VirglRenderer {
+ fn drop(&mut self) {
+ // Safe because virglrenderer is initialized.
+ //
+ // This invalidates all context ids and resource ids. It is fine because struct Rutabaga
+ // makes sure contexts and resources are dropped before this is reached. Even if it did
+ // not, virglrenderer is designed to deal with invalid ids safely.
+ unsafe {
+ virgl_renderer_cleanup(null_mut());
+ }
+ }
+}
+
impl RutabagaComponent for VirglRenderer {
fn get_capset_info(&self, capset_id: u32) -> (u32, u32) {
let mut version = 0;
@@ -306,15 +385,24 @@ impl RutabagaComponent for VirglRenderer {
unsafe { virgl_renderer_force_ctx_0() };
}
- fn create_fence(&mut self, fence_data: RutabagaFenceData) -> RutabagaResult<()> {
- let ret =
- unsafe { virgl_renderer_create_fence(fence_data.fence_id as i32, fence_data.ctx_id) };
+ fn create_fence(&mut self, fence: RutabagaFence) -> RutabagaResult<()> {
+ let ret = unsafe { virgl_renderer_create_fence(fence.fence_id as i32, fence.ctx_id) };
ret_to_res(ret)
}
- fn poll(&self) -> u32 {
+ fn poll(&self) {
unsafe { virgl_renderer_poll() };
- self.fence_state.borrow().latest_fence
+ }
+
+ fn poll_descriptor(&self) -> Option<SafeDescriptor> {
+ // Safe because it can be called anytime and returns -1 in the event of an error.
+ let fd = unsafe { virgl_renderer_get_poll_fd() };
+ if fd >= 0 {
+ if let Ok(dup_fd) = SafeDescriptor::try_from(&fd as &dyn AsRawFd) {
+ return Some(dup_fd);
+ }
+ }
+ None
}
fn create_3d(
@@ -481,6 +569,7 @@ impl RutabagaComponent for VirglRenderer {
resource_id: u32,
resource_create_blob: ResourceCreateBlob,
mut iovec_opt: Option<Vec<RutabagaIovec>>,
+ _handle_opt: Option<RutabagaHandle>,
) -> RutabagaResult<RutabagaResource> {
#[cfg(feature = "virgl_renderer_next")]
{
@@ -556,6 +645,7 @@ impl RutabagaComponent for VirglRenderer {
&self,
ctx_id: u32,
context_init: u32,
+ _fence_handler: RutabagaFenceHandler,
) -> RutabagaResult<Box<dyn RutabagaContext>> {
const CONTEXT_NAME: &[u8] = b"gpu_renderer";
// Safe because virglrenderer is initialized by now and the context name is statically
diff --git a/seccomp/Android.bp b/seccomp/Android.bp
index e0dc8d716..4a35212fe 100644
--- a/seccomp/Android.bp
+++ b/seccomp/Android.bp
@@ -26,19 +26,21 @@ package {
genrule_defaults {
name: "crosvm_inline_seccomp_policy_x86_64",
- cmd: "$(location policy-inliner.sh) $(location x86_64/common_device.policy) < $(in) > $(out)",
+ cmd: "$(location policy-inliner.sh) $(location x86_64/common_device.policy) $(location x86_64/gpu_common.policy) < $(in) > $(out)",
tool_files: [
"policy-inliner.sh",
"x86_64/common_device.policy",
+ "x86_64/gpu_common.policy",
],
}
genrule_defaults {
name: "crosvm_inline_seccomp_policy_aarch64",
- cmd: "$(location policy-inliner.sh) $(location aarch64/common_device.policy) < $(in) > $(out)",
+ cmd: "$(location policy-inliner.sh) $(location aarch64/common_device.policy) $(location aarch64/gpu_common.policy) < $(in) > $(out)",
tool_files: [
"policy-inliner.sh",
"aarch64/common_device.policy",
+ "aarch64/gpu_common.policy",
],
}
@@ -421,6 +423,55 @@ prebuilt_etc {
}
genrule {
+ name: "gpu_render_server.policy_inline_x86_64",
+ defaults: ["crosvm_inline_seccomp_policy_x86_64"],
+ out: ["gpu_render_server.policy"],
+ srcs: ["x86_64/gpu_render_server.policy"],
+}
+
+prebuilt_usr_share_host {
+ name: "gpu_render_server.policy_x86_64",
+ filename: "gpu_render_server.policy",
+ relative_install_path: "crosvm/x86_64-linux-gnu/seccomp",
+ src: ":gpu_render_server.policy_inline_x86_64",
+}
+
+genrule {
+ name: "gpu_render_server.policy_inline_aarch64",
+ defaults: ["crosvm_inline_seccomp_policy_aarch64"],
+ out: ["gpu_render_server.policy"],
+ srcs: ["aarch64/gpu_render_server.policy"],
+}
+
+prebuilt_usr_share_host {
+ name: "gpu_render_server.policy_aarch64",
+ filename: "gpu_render_server.policy",
+ relative_install_path: "crosvm/aarch64-linux-gnu/seccomp",
+ src: ":gpu_render_server.policy_inline_aarch64",
+}
+
+prebuilt_etc {
+ name: "gpu_render_server.policy",
+ relative_install_path: "seccomp_policy/crosvm",
+ arch: {
+ x86_64: {
+ src: ":gpu_render_server.policy_inline_x86_64",
+ },
+ arm64: {
+ src: ":gpu_render_server.policy_inline_aarch64",
+ },
+ },
+ target: {
+ android_arm: {
+ enabled: false,
+ },
+ android_x86: {
+ enabled: false,
+ },
+ },
+}
+
+genrule {
name: "block_device.policy_inline_x86_64",
defaults: ["crosvm_inline_seccomp_policy_x86_64"],
out: ["block_device.policy"],
@@ -568,6 +619,41 @@ prebuilt_etc {
}
genrule {
+ name: "iommu_device.policy_inline_x86_64",
+ defaults: ["crosvm_inline_seccomp_policy_x86_64"],
+ out: ["iommu_device.policy"],
+ srcs: ["x86_64/iommu_device.policy"],
+}
+
+prebuilt_usr_share_host {
+ name: "iommu_device.policy_x86_64",
+ filename: "iommu_device.policy",
+ relative_install_path: "crosvm/x86_64-linux-gnu/seccomp",
+ src: ":iommu_device.policy_inline_x86_64",
+}
+
+prebuilt_etc {
+ name: "iommu_device.policy",
+ relative_install_path: "seccomp_policy/crosvm",
+ arch: {
+ x86_64: {
+ src: ":iommu_device.policy_inline_x86_64",
+ },
+ },
+ target: {
+ android_arm64: {
+ enabled: false,
+ },
+ android_arm: {
+ enabled: false,
+ },
+ android_x86: {
+ enabled: false,
+ },
+ },
+}
+
+genrule {
name: "rng_device.policy_inline_x86_64",
defaults: ["crosvm_inline_seccomp_policy_x86_64"],
out: ["rng_device.policy"],
@@ -897,6 +983,55 @@ prebuilt_etc {
}
genrule {
+ name: "cras_snd_device.policy_inline_x86_64",
+ defaults: ["crosvm_inline_seccomp_policy_x86_64"],
+ out: ["cras_snd_device.policy"],
+ srcs: ["x86_64/cras_snd_device.policy"],
+}
+
+prebuilt_usr_share_host {
+ name: "cras_snd_device.policy_x86_64",
+ filename: "cras_snd_device.policy",
+ relative_install_path: "crosvm/x86_64-linux-gnu/seccomp",
+ src: ":cras_snd_device.policy_inline_x86_64",
+}
+
+genrule {
+ name: "cras_snd_device.policy_inline_aarch64",
+ defaults: ["crosvm_inline_seccomp_policy_aarch64"],
+ out: ["cras_snd_device.policy"],
+ srcs: ["aarch64/cras_snd_device.policy"],
+}
+
+prebuilt_usr_share_host {
+ name: "cras_snd_device.policy_aarch64",
+ filename: "cras_snd_device.policy",
+ relative_install_path: "crosvm/aarch64-linux-gnu/seccomp",
+ src: ":cras_snd_device.policy_inline_aarch64",
+}
+
+prebuilt_etc {
+ name: "cras_snd_device.policy",
+ relative_install_path: "seccomp_policy/crosvm",
+ arch: {
+ x86_64: {
+ src: ":cras_snd_device.policy_inline_x86_64",
+ },
+ arm64: {
+ src: ":cras_snd_device.policy_inline_aarch64",
+ },
+ },
+ target: {
+ android_arm: {
+ enabled: false,
+ },
+ android_x86: {
+ enabled: false,
+ },
+ },
+}
+
+genrule {
name: "xhci.policy_inline_x86_64",
defaults: ["crosvm_inline_seccomp_policy_x86_64"],
out: ["xhci.policy"],
@@ -1042,4 +1177,3 @@ prebuilt_etc {
},
},
}
-
diff --git a/seccomp/aarch64/block_device.policy b/seccomp/aarch64/block_device.policy
index 64d5ca5ff..58c61bcab 100644
--- a/seccomp/aarch64/block_device.policy
+++ b/seccomp/aarch64/block_device.policy
@@ -11,6 +11,7 @@ fsync: 1
ftruncate: 1
lseek: 1
openat: return ENOENT
+newfstatat: 1
preadv: 1
pwritev: 1
statx: 1
diff --git a/seccomp/aarch64/coiommu.policy b/seccomp/aarch64/coiommu.policy
new file mode 100644
index 000000000..42f7a30e9
--- /dev/null
+++ b/seccomp/aarch64/coiommu.policy
@@ -0,0 +1,11 @@
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+@include /usr/share/policy/crosvm/common_device.policy
+
+# VFIO_IOMMU_MAP/UNMAP_DMA
+ioctl: arg1 == 0x3B71 || arg1 == 0x3B72
+prctl: arg0 == PR_SET_NAME
+timerfd_create: 1
+timerfd_settime: 1
+timerfd_gettime: 1
diff --git a/seccomp/aarch64/common_device.policy b/seccomp/aarch64/common_device.policy
index 349afe9aa..ca9064e4c 100644
--- a/seccomp/aarch64/common_device.policy
+++ b/seccomp/aarch64/common_device.policy
@@ -3,6 +3,7 @@
# found in the LICENSE file.
brk: 1
+clock_gettime: 1
clone: arg0 & CLONE_THREAD
close: 1
dup3: 1
@@ -14,6 +15,7 @@ eventfd2: 1
exit: 1
exit_group: 1
futex: 1
+getcwd: 1
getpid: 1
gettimeofday: 1
io_uring_setup: 1
@@ -29,6 +31,7 @@ clock_nanosleep: 1
pipe2: 1
ppoll: 1
read: 1
+readlinkat: 1
readv: 1
recvfrom: 1
recvmsg: 1
@@ -37,6 +40,7 @@ rt_sigaction: 1
rt_sigprocmask: 1
rt_sigreturn: 1
sched_getaffinity: 1
+sched_yield: 1
sendmsg: 1
sendto: 1
set_robust_list: 1
@@ -44,3 +48,4 @@ sigaltstack: 1
write: 1
writev: 1
fcntl: 1
+uname: 1
diff --git a/seccomp/aarch64/cras_audio_device.policy b/seccomp/aarch64/cras_audio_device.policy
index 60797f978..943ae1a37 100644
--- a/seccomp/aarch64/cras_audio_device.policy
+++ b/seccomp/aarch64/cras_audio_device.policy
@@ -4,11 +4,14 @@
@include /usr/share/policy/crosvm/common_device.policy
+lseek: 1
madvise: 1
prlimit64: 1
setrlimit: 1
sched_setscheduler: 1
socketpair: arg0 == AF_UNIX
-clock_gettime: 1
openat: return ENOENT
prctl: arg0 == PR_SET_NAME
+timerfd_create: 1
+timerfd_gettime: 1
+timerfd_settime: 1
diff --git a/seccomp/aarch64/cras_snd_device.policy b/seccomp/aarch64/cras_snd_device.policy
new file mode 100644
index 000000000..a4c462391
--- /dev/null
+++ b/seccomp/aarch64/cras_snd_device.policy
@@ -0,0 +1,17 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+@include /usr/share/policy/crosvm/common_device.policy
+
+openat: return ENOENT
+socket: arg0 == AF_UNIX
+socketpair: arg0 == AF_UNIX
+prctl: arg0 == PR_SET_NAME
+connect: 1
+prlimit64: 1
+setrlimit: 1
+sched_setscheduler: 1
+timerfd_create: 1
+timerfd_gettime: 1
+timerfd_settime: 1
diff --git a/seccomp/aarch64/fs_device.policy b/seccomp/aarch64/fs_device.policy
index 828003e0c..fb25fb100 100644
--- a/seccomp/aarch64/fs_device.policy
+++ b/seccomp/aarch64/fs_device.policy
@@ -28,11 +28,16 @@ getdents64: 1
getegid: 1
geteuid: 1
getrandom: 1
+# Use constants for verity ioctls since minijail doesn't understand them yet.
+# 0x40806685 = FS_IOC_ENABLE_VERITY
+# 0xc0046686 = FS_IOC_MEASURE_VERITY
ioctl: arg1 == FS_IOC_FSGETXATTR || \
arg1 == FS_IOC_FSSETXATTR || \
arg1 == FS_IOC_GETFLAGS || \
arg1 == FS_IOC_SETFLAGS || \
- arg1 == FS_IOC_GET_ENCRYPTION_POLICY_EX
+ arg1 == FS_IOC_GET_ENCRYPTION_POLICY_EX || \
+ arg1 == 0x40806685 || \
+ arg1 == 0xc0046686
linkat: 1
lseek: 1
mkdirat: 1
@@ -40,7 +45,6 @@ mknodat: 1
openat: 1
preadv: 1
pwritev: 1
-readlinkat: 1
renameat2: 1
setresgid: 1
setresuid: 1
@@ -51,3 +55,4 @@ utimensat: 1
prctl: arg0 == PR_SET_NAME || arg0 == PR_SET_SECUREBITS || arg0 == PR_GET_SECUREBITS
capget: 1
capset: 1
+unshare: 1
diff --git a/seccomp/aarch64/gpu_common.policy b/seccomp/aarch64/gpu_common.policy
new file mode 100644
index 000000000..3e883bb11
--- /dev/null
+++ b/seccomp/aarch64/gpu_common.policy
@@ -0,0 +1,83 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Rules from common_device.policy with some rules removed because they block certain flags needed
+# for gpu.
+brk: 1
+clock_gettime: 1
+close: 1
+dup3: 1
+dup: 1
+epoll_create1: 1
+epoll_ctl: 1
+epoll_pwait: 1
+eventfd2: 1
+exit: 1
+exit_group: 1
+futex: 1
+getcwd: 1
+getpid: 1
+gettimeofday: 1
+kill: 1
+madvise: arg2 == MADV_DONTNEED || arg2 == MADV_DONTDUMP || arg2 == MADV_REMOVE
+mremap: 1
+munmap: 1
+nanosleep: 1
+clock_nanosleep: 1
+pipe2: 1
+ppoll: 1
+prctl: arg0 == PR_SET_NAME || arg0 == PR_GET_NAME
+read: 1
+readlinkat: 1
+readv: 1
+recvfrom: 1
+recvmsg: 1
+restart_syscall: 1
+rt_sigaction: 1
+rt_sigprocmask: 1
+rt_sigreturn: 1
+sched_getaffinity: 1
+sendmsg: 1
+sendto: 1
+set_robust_list: 1
+sigaltstack: 1
+write: 1
+writev: 1
+uname: 1
+
+# Required for perfetto tracing
+getsockopt: 1
+shutdown: 1
+
+## Rules specific to gpu
+connect: 1
+getrandom: 1
+lseek: 1
+ftruncate: 1
+statx: 1
+fstat: 1
+newfstatat: 1
+getdents64: 1
+sysinfo: 1
+fstatfs: 1
+
+# 0x6400 == DRM_IOCTL_BASE, 0x8000 = KBASE_IOCTL_TYPE (mali), 0x40086200 = DMA_BUF_IOCTL_SYNC, 0x40087543 == UDMABUF_CREATE_LIST
+ioctl: arg1 & 0x6400 || arg1 & 0x8000 || arg1 == 0x40086200 || arg1 == 0x40087543
+
+## mmap/mprotect differ from the common_device.policy
+mmap: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ|PROT_EXEC || arg2 == PROT_WRITE || arg2 == PROT_READ
+mprotect: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ
+openat: 1
+
+## Rules specific to pvr
+geteuid: 1
+getuid: 1
+gettid: 1
+fcntl: 1
+tgkill: 1
+
+# Rules specific to Mesa.
+sched_setscheduler: 1
+sched_setaffinity: 1
+kcmp: 1
diff --git a/seccomp/aarch64/gpu_device.policy b/seccomp/aarch64/gpu_device.policy
index 4ceac5c46..403d86d56 100644
--- a/seccomp/aarch64/gpu_device.policy
+++ b/seccomp/aarch64/gpu_device.policy
@@ -2,82 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-# Rules from common_device.policy with some rules removed because they block certain flags needed
-# for gpu.
-brk: 1
-clone: arg0 & CLONE_THREAD
-close: 1
-dup3: 1
-dup: 1
-epoll_create1: 1
-epoll_ctl: 1
-epoll_pwait: 1
-eventfd2: 1
-exit: 1
-exit_group: 1
-futex: 1
-getpid: 1
-gettimeofday: 1
-kill: 1
-madvise: arg2 == MADV_DONTNEED || arg2 == MADV_DONTDUMP || arg2 == MADV_REMOVE
-mremap: 1
-munmap: 1
-nanosleep: 1
-clock_nanosleep: 1
-pipe2: 1
-ppoll: 1
-prctl: arg0 == PR_SET_NAME || arg0 == PR_GET_NAME
-read: 1
-readv: 1
-recvfrom: 1
-recvmsg: 1
-restart_syscall: 1
-rt_sigaction: 1
-rt_sigprocmask: 1
-rt_sigreturn: 1
-sched_getaffinity: 1
-sendmsg: 1
-sendto: 1
-set_robust_list: 1
-sigaltstack: 1
-write: 1
-writev: 1
-
-# Required for perfetto tracing
-getsockopt: 1
-shutdown: 1
-
-## Rules specific to gpu
-connect: 1
-getrandom: 1
-socket: arg0 == 1 && arg1 == 0x80001 && arg2 == 0
-lseek: 1
-ftruncate: 1
-statx: 1
-fstat: 1
-newfstatat: 1
-getdents64: 1
-sysinfo: 1
-
-# 0x6400 == DRM_IOCTL_BASE, 0x8000 = KBASE_IOCTL_TYPE (mali), 0x40086200 = DMA_BUF_IOCTL_SYNC, 0x40087543 == UDMABUF_CREATE_LIST
-ioctl: arg1 & 0x6400 || arg1 & 0x8000 || arg1 == 0x40086200 || arg1 == 0x40087543
+@include /usr/share/policy/crosvm/gpu_common.policy
-## mmap/mprotect differ from the common_device.policy
-mmap: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ|PROT_EXEC || arg2 == PROT_WRITE || arg2 == PROT_READ
-mprotect: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ
-openat: 1
-
-## Rules specific to pvr
-geteuid: 1
-getuid: 1
-readlinkat: 1
-gettid: 1
-fcntl: 1
-tgkill: 1
-clock_gettime: 1
-
-# Rules specific to Mesa.
-uname: 1
-sched_setscheduler: 1
-sched_setaffinity: 1
-kcmp: 1
+socket: arg0 == AF_UNIX && arg1 == SOCK_STREAM|SOCK_CLOEXEC && arg2 == 0
+clone: arg0 & CLONE_THREAD
diff --git a/seccomp/aarch64/gpu_render_server.policy b/seccomp/aarch64/gpu_render_server.policy
new file mode 100644
index 000000000..536d43dd4
--- /dev/null
+++ b/seccomp/aarch64/gpu_render_server.policy
@@ -0,0 +1,18 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+@include /usr/share/policy/crosvm/gpu_common.policy
+
+# allow fork() and waitid()
+clone: 1
+waitid: 1
+
+# allow SOCK_STREAM and SOCK_DGRAM (syslog)
+socket: arg0 == AF_UNIX && arg2 == 0
+
+# allow socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC)
+socketpair: arg0 == AF_UNIX && arg1 == SOCK_SEQPACKET|SOCK_CLOEXEC && arg2 == 0
+
+# allow signalfd()
+signalfd4: 1
diff --git a/seccomp/aarch64/null_audio_device.policy b/seccomp/aarch64/null_audio_device.policy
index b55aa1e94..33920a604 100644
--- a/seccomp/aarch64/null_audio_device.policy
+++ b/seccomp/aarch64/null_audio_device.policy
@@ -7,6 +7,5 @@
madvise: 1
prlimit64: 1
setrlimit: 1
-clock_gettime: 1
openat: return ENOENT
prctl: arg0 == PR_SET_NAME
diff --git a/seccomp/aarch64/tpm_device.policy b/seccomp/aarch64/tpm_device.policy
index 98d32b6b1..75bd04ba6 100644
--- a/seccomp/aarch64/tpm_device.policy
+++ b/seccomp/aarch64/tpm_device.policy
@@ -2,54 +2,18 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-# common policy
-brk: 1
-clone: arg0 & CLONE_THREAD
-close: 1
-dup3: 1
-dup: 1
-epoll_create1: 1
-epoll_ctl: 1
-epoll_pwait: 1
-eventfd2: 1
-exit: 1
-exit_group: 1
-futex: 1
-getpid: 1
-getrandom: 1
-gettimeofday: 1
-kill: 1
-madvise: arg2 == MADV_DONTNEED || arg2 == MADV_DONTDUMP || arg2 == MADV_REMOVE
-mmap: arg2 in ~PROT_EXEC
-mprotect: arg2 in ~PROT_EXEC
-mremap: 1
-munmap: 1
-nanosleep: 1
-clock_nanosleep: 1
-pipe2: 1
-ppoll: 1
-prctl: arg0 == PR_SET_NAME
-read: 1
-recvfrom: 1
-recvmsg: 1
-restart_syscall: 1
-rt_sigaction: 1
-rt_sigprocmask: 1
-rt_sigreturn: 1
-sched_getaffinity: 1
-sendmsg: 1
-set_robust_list: 1
-sigaltstack: 1
-write: 1
+@include /usr/share/policy/crosvm/common_device.policy
-# tpm-specific policy
chdir: 1
fstat: 1
fsync: 1
ftruncate: 1
+getrandom: 1
getuid: 1
lseek: 1
mkdirat: 1
+newfstatat: 1
openat: 1
+prctl: arg0 == PR_SET_NAME
socket: return EACCES
statx: 1
diff --git a/seccomp/aarch64/vios_audio_device.policy b/seccomp/aarch64/vios_audio_device.policy
index d425ab279..a31996809 100644
--- a/seccomp/aarch64/vios_audio_device.policy
+++ b/seccomp/aarch64/vios_audio_device.policy
@@ -4,7 +4,6 @@
@include /usr/share/policy/crosvm/common_device.policy
-clock_gettime: 1
lseek: 1
openat: return ENOENT
prlimit64: 1
diff --git a/seccomp/aarch64/wl_device.policy b/seccomp/aarch64/wl_device.policy
index cd6804637..7d41af992 100644
--- a/seccomp/aarch64/wl_device.policy
+++ b/seccomp/aarch64/wl_device.policy
@@ -6,8 +6,8 @@
# Used to connect to wayland. arg0 == AF_UNIX && arg1 == SOCK_STREAM|SOCK_CLOEXEC
socket: arg0 == 1 && arg1 == 0x80001 && arg2 == 0
-# arg1 == FIONBIO || arg1 == DMA_BUF_IOCTL_SYNC
-ioctl: arg1 == 0x5421 || arg1 == 0x40086200
+# arg1 == FIONBIO || arg1 == DMA_BUF_IOCTL_SYNC || arg1 == SYNC_IOC_FILE_INFO
+ioctl: arg1 == 0x5421 || arg1 == 0x40086200 || arg1 == 0xc0383e04
connect: 1
openat: return ENOENT
# Used for sharing memory with wayland. arg1 == MFD_CLOEXEC|MFD_ALLOW_SEALING
diff --git a/seccomp/aarch64/xhci.policy b/seccomp/aarch64/xhci.policy
index 684ae0d2f..c61e9b510 100644
--- a/seccomp/aarch64/xhci.policy
+++ b/seccomp/aarch64/xhci.policy
@@ -5,19 +5,18 @@
@include /usr/share/policy/crosvm/common_device.policy
statx: 1
-readlinkat: 1
getdents64: 1
name_to_handle_at: 1
faccessat: 1
+faccessat2: 1
+newfstatat: 1
gettid: 1
-clock_gettime: 1
timerfd_create: 1
getsockname: 1
openat: 1
setsockopt: 1
bind: 1
socket: arg0 == AF_NETLINK
-uname: 1
# The following ioctls are:
# 0x4004550d == USBDEVFS_REAPURBNDELAY32
# 0x550b == USBDEVFS_DISCARDURB
diff --git a/seccomp/arm/9p_device.policy b/seccomp/arm/9p_device.policy
index a7b877b27..c04cc07d1 100644
--- a/seccomp/arm/9p_device.policy
+++ b/seccomp/arm/9p_device.policy
@@ -27,6 +27,7 @@ utimensat_time64: 1
ftruncate64: 1
fchmod: 1
fchown: 1
+fstatfs: 1
fstatfs64: 1
fstatat64: 1
prctl: arg0 == PR_SET_NAME
diff --git a/seccomp/arm/block_device.policy b/seccomp/arm/block_device.policy
index 75b769f43..1d69965d5 100644
--- a/seccomp/arm/block_device.policy
+++ b/seccomp/arm/block_device.policy
@@ -7,6 +7,7 @@
fallocate: 1
fdatasync: 1
fstat64: 1
+fstatat64: 1
fsync: 1
ftruncate64: 1
_llseek: 1
diff --git a/seccomp/arm/coiommu.policy b/seccomp/arm/coiommu.policy
new file mode 100644
index 000000000..42f7a30e9
--- /dev/null
+++ b/seccomp/arm/coiommu.policy
@@ -0,0 +1,11 @@
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+@include /usr/share/policy/crosvm/common_device.policy
+
+# VFIO_IOMMU_MAP/UNMAP_DMA
+ioctl: arg1 == 0x3B71 || arg1 == 0x3B72
+prctl: arg0 == PR_SET_NAME
+timerfd_create: 1
+timerfd_settime: 1
+timerfd_gettime: 1
diff --git a/seccomp/arm/common_device.policy b/seccomp/arm/common_device.policy
index 165bfda6e..11f0a72f0 100644
--- a/seccomp/arm/common_device.policy
+++ b/seccomp/arm/common_device.policy
@@ -17,6 +17,7 @@ exit: 1
exit_group: 1
futex: 1
futex_time64: 1
+getcwd: 1
getpid: 1
gettid: 1
gettimeofday: 1
@@ -36,6 +37,8 @@ poll: 1
ppoll: 1
ppoll_time64: 1
read: 1
+readlink: 1
+readlinkat: 1
readv: 1
recv: 1
recvfrom: 1
@@ -46,6 +49,7 @@ rt_sigaction: 1
rt_sigprocmask: 1
rt_sigreturn: 1
sched_getaffinity: 1
+sched_yield: 1
sendmsg: 1
sendto: 1
set_robust_list: 1
@@ -53,3 +57,4 @@ sigaltstack: 1
write: 1
writev: 1
fcntl64: 1
+uname: 1
diff --git a/seccomp/arm/cras_audio_device.policy b/seccomp/arm/cras_audio_device.policy
index 20bf60e1f..aa10ff86b 100644
--- a/seccomp/arm/cras_audio_device.policy
+++ b/seccomp/arm/cras_audio_device.policy
@@ -4,6 +4,8 @@
@include /usr/share/policy/crosvm/common_device.policy
+_llseek: 1
+lseek: 1
madvise: 1
open: return ENOENT
openat: return ENOENT
@@ -12,3 +14,8 @@ setrlimit: 1
sched_setscheduler: 1
socketpair: arg0 == AF_UNIX
prctl: arg0 == PR_SET_NAME
+timerfd_create: 1
+timerfd_gettime: 1
+timerfd_gettime64: 1
+timerfd_settime: 1
+timerfd_settime64: 1
diff --git a/seccomp/arm/cras_snd_device.policy b/seccomp/arm/cras_snd_device.policy
new file mode 100644
index 000000000..b0a21b2e0
--- /dev/null
+++ b/seccomp/arm/cras_snd_device.policy
@@ -0,0 +1,20 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+@include /usr/share/policy/crosvm/common_device.policy
+
+open: return ENOENT
+openat: return ENOENT
+socket: arg0 == AF_UNIX
+socketpair: arg0 == AF_UNIX
+prctl: arg0 == PR_SET_NAME
+connect: 1
+prlimit64: 1
+setrlimit: 1
+sched_setscheduler: 1
+timerfd_create: 1
+timerfd_gettime: 1
+timerfd_gettime64: 1
+timerfd_settime: 1
+timerfd_settime64: 1
diff --git a/seccomp/arm/fs_device.policy b/seccomp/arm/fs_device.policy
index e84fd08fc..f70178ad6 100644
--- a/seccomp/arm/fs_device.policy
+++ b/seccomp/arm/fs_device.policy
@@ -21,6 +21,7 @@ listxattr: 1
fremovexattr: 1
removexattr: 1
fstatat64: 1
+fstatfs: 1
fstatfs64: 1
fsync: 1
ftruncate64: 1
@@ -28,11 +29,16 @@ getdents64: 1
getegid32: 1
geteuid32: 1
getrandom: 1
+# Use constants for verity ioctls since minijail doesn't understand them yet.
+# 0x40806685 = FS_IOC_ENABLE_VERITY
+# 0xc0046686 = FS_IOC_MEASURE_VERITY
ioctl: arg1 == FS_IOC_FSGETXATTR || \
arg1 == FS_IOC_FSSETXATTR || \
arg1 == FS_IOC_GETFLAGS || \
arg1 == FS_IOC_SETFLAGS || \
- arg1 == FS_IOC_GET_ENCRYPTION_POLICY_EX
+ arg1 == FS_IOC_GET_ENCRYPTION_POLICY_EX || \
+ arg1 == 0x40806685 || \
+ arg1 == 0xc0046686
linkat: 1
_llseek: 1
mkdir: 1
@@ -42,7 +48,6 @@ open: return ENOENT
openat: 1
preadv: 1
pwritev: 1
-readlinkat: 1
renameat2: 1
setresgid32: 1
setresuid32: 1
@@ -55,3 +60,4 @@ utimensat_time64: 1
prctl: arg0 == PR_SET_NAME || arg0 == PR_SET_SECUREBITS || arg0 == PR_GET_SECUREBITS
capget: 1
capset: 1
+unshare: 1
diff --git a/seccomp/arm/gpu_common.policy b/seccomp/arm/gpu_common.policy
new file mode 100644
index 000000000..33c36483a
--- /dev/null
+++ b/seccomp/arm/gpu_common.policy
@@ -0,0 +1,105 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Rules from common_device.policy with some rules removed because they block certain flags needed
+# for gpu.
+brk: 1
+close: 1
+dup2: 1
+dup: 1
+epoll_create1: 1
+epoll_ctl: 1
+epoll_wait: 1
+eventfd2: 1
+exit: 1
+exit_group: 1
+futex: 1
+futex_time64: 1
+getcwd: 1
+getpid: 1
+gettimeofday: 1
+kill: 1
+madvise: arg2 == MADV_DONTNEED || arg2 == MADV_DONTDUMP || arg2 == MADV_REMOVE
+mremap: 1
+munmap: 1
+nanosleep: 1
+clock_nanosleep: 1
+clock_nanosleep_time64: 1
+pipe2: 1
+poll: 1
+ppoll: 1
+ppoll_time64: 1
+prctl: arg0 == PR_SET_NAME || arg0 == PR_GET_NAME
+read: 1
+readlink: 1
+readlinkat: 1
+readv: 1
+recv: 1
+recvfrom: 1
+recvmsg: 1
+recvmmsg_time64: 1
+restart_syscall: 1
+rt_sigaction: 1
+rt_sigprocmask: 1
+rt_sigreturn: 1
+sched_getaffinity: 1
+sched_yield: 1
+sendmsg: 1
+sendto: 1
+set_robust_list: 1
+sigaltstack: 1
+write: 1
+writev: 1
+uname: 1
+
+# Required for perfetto tracing
+getsockopt: 1
+shutdown: 1
+
+## Rules specific to gpu
+connect: 1
+getrandom: 1
+_llseek: 1
+ftruncate64: 1
+stat64: 1
+statx: 1
+fstat64: 1
+fstatat64: 1
+getdents: 1
+getdents64: 1
+sysinfo: 1
+fstatfs: 1
+fstatfs64: 1
+
+# 0x6400 == DRM_IOCTL_BASE, 0x8000 = KBASE_IOCTL_TYPE (mali), 0x40086200 = DMA_BUF_IOCTL_SYNC, 0x40087543 == UDMABUF_CREATE_LIST
+ioctl: arg1 & 0x6400 || arg1 & 0x8000 || arg1 == 0x40086200 || arg1 == 0x40087543
+
+# Used for sharing memory with wayland. arg1 == MFD_CLOEXEC|MFD_ALLOW_SEALING
+memfd_create: arg1 == 3
+
+## mmap/mprotect differ from the common_device.policy
+mmap2: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ|PROT_EXEC || arg2 == PROT_WRITE || arg2 == PROT_READ
+mprotect: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ
+open: return ENOENT
+openat: 1
+
+## Rules specific to pvr
+geteuid32: 1
+getuid32: 1
+lstat64: 1
+gettid: 1
+fcntl64: 1
+tgkill: 1
+clock_gettime: 1
+clock_gettime64: 1
+
+# Rules specific to Mesa.
+sched_setscheduler: 1
+sched_setaffinity: 1
+kcmp: 1
+
+# Rules for Vulkan loader / layers
+access: 1
+getgid32: 1
+getegid32: 1
diff --git a/seccomp/arm/gpu_device.policy b/seccomp/arm/gpu_device.policy
index ec5a5b481..403d86d56 100644
--- a/seccomp/arm/gpu_device.policy
+++ b/seccomp/arm/gpu_device.policy
@@ -2,94 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-# Rules from common_device.policy with some rules removed because they block certain flags needed
-# for gpu.
-brk: 1
-clone: arg0 & CLONE_THREAD
-close: 1
-dup2: 1
-dup: 1
-epoll_create1: 1
-epoll_ctl: 1
-epoll_wait: 1
-eventfd2: 1
-exit: 1
-exit_group: 1
-futex: 1
-futex_time64: 1
-getpid: 1
-gettimeofday: 1
-kill: 1
-madvise: arg2 == MADV_DONTNEED || arg2 == MADV_DONTDUMP || arg2 == MADV_REMOVE
-mremap: 1
-munmap: 1
-nanosleep: 1
-clock_nanosleep: 1
-clock_nanosleep_time64: 1
-pipe2: 1
-poll: 1
-ppoll: 1
-ppoll_time64: 1
-prctl: arg0 == PR_SET_NAME || arg0 == PR_GET_NAME
-read: 1
-readv: 1
-recv: 1
-recvfrom: 1
-recvmsg: 1
-recvmmsg_time64: 1
-restart_syscall: 1
-rt_sigaction: 1
-rt_sigprocmask: 1
-rt_sigreturn: 1
-sched_getaffinity: 1
-sendmsg: 1
-sendto: 1
-set_robust_list: 1
-sigaltstack: 1
-write: 1
-writev: 1
-
-# Required for perfetto tracing
-getsockopt: 1
-shutdown: 1
-
-## Rules specific to gpu
-connect: 1
-getrandom: 1
-socket: arg0 == 1 && arg1 == 0x80001 && arg2 == 0
-_llseek: 1
-ftruncate64: 1
-stat64: 1
-fstat64: 1
-getdents: 1
-getdents64: 1
-sysinfo: 1
-
-# 0x6400 == DRM_IOCTL_BASE, 0x8000 = KBASE_IOCTL_TYPE (mali), 0x40086200 = DMA_BUF_IOCTL_SYNC, 0x40087543 == UDMABUF_CREATE_LIST
-ioctl: arg1 & 0x6400 || arg1 & 0x8000 || arg1 == 0x40086200 || arg1 == 0x40087543
+@include /usr/share/policy/crosvm/gpu_common.policy
-# Used for sharing memory with wayland. arg1 == MFD_CLOEXEC|MFD_ALLOW_SEALING
-memfd_create: arg1 == 3
-
-## mmap/mprotect differ from the common_device.policy
-mmap2: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ|PROT_EXEC || arg2 == PROT_WRITE || arg2 == PROT_READ
-mprotect: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ
-open: return ENOENT
-openat: 1
-
-## Rules specific to pvr
-geteuid32: 1
-getuid32: 1
-lstat64: 1
-readlink: 1
-gettid: 1
-fcntl64: 1
-tgkill: 1
-clock_gettime: 1
-clock_gettime64: 1
-
-# Rules specific to Mesa.
-uname: 1
-sched_setscheduler: 1
-sched_setaffinity: 1
-kcmp: 1
+socket: arg0 == AF_UNIX && arg1 == SOCK_STREAM|SOCK_CLOEXEC && arg2 == 0
+clone: arg0 & CLONE_THREAD
diff --git a/seccomp/arm/gpu_render_server.policy b/seccomp/arm/gpu_render_server.policy
new file mode 100644
index 000000000..c2a602a6f
--- /dev/null
+++ b/seccomp/arm/gpu_render_server.policy
@@ -0,0 +1,20 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+@include /usr/share/policy/crosvm/gpu_common.policy
+
+# allow fork() and waitid()
+clone: 1
+waitid: 1
+
+# allow vsyslog
+send: 1
+# allow SOCK_STREAM and SOCK_DGRAM (syslog)
+socket: arg0 == AF_UNIX && arg2 == 0
+
+# allow socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC)
+socketpair: arg0 == AF_UNIX && arg1 == SOCK_SEQPACKET|SOCK_CLOEXEC && arg2 == 0
+
+# allow signalfd()
+signalfd4: 1
diff --git a/seccomp/arm/tpm_device.policy b/seccomp/arm/tpm_device.policy
index 5653f7794..641b364b4 100644
--- a/seccomp/arm/tpm_device.policy
+++ b/seccomp/arm/tpm_device.policy
@@ -2,64 +2,19 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-# common policy
-brk: 1
-clock_gettime: 1
-clock_gettime64: 1
-clone: arg0 & CLONE_THREAD
-close: 1
-dup2: 1
-dup: 1
-epoll_create1: 1
-epoll_ctl: 1
-epoll_wait: 1
-eventfd2: 1
-exit: 1
-exit_group: 1
-futex: 1
-futex_time64: 1
-getpid: 1
-getrandom: 1
-gettimeofday: 1
-kill: 1
-madvise: arg2 == MADV_DONTNEED || arg2 == MADV_DONTDUMP || arg2 == MADV_REMOVE
-mmap2: arg2 in ~PROT_EXEC
-mprotect: arg2 in ~PROT_EXEC
-mremap: 1
-munmap: 1
-nanosleep: 1
-clock_nanosleep: 1
-clock_nanosleep_time64: 1
-pipe2: 1
-poll: 1
-ppoll: 1
-ppoll_time64: 1
-prctl: arg0 == PR_SET_NAME
-read: 1
-recv: 1
-recvfrom: 1
-recvmsg: 1
-recvmmsg_time64: 1
-restart_syscall: 1
-rt_sigaction: 1
-rt_sigprocmask: 1
-rt_sigreturn: 1
-sched_getaffinity: 1
-sendmsg: 1
-set_robust_list: 1
-sigaltstack: 1
-write: 1
+@include /usr/share/policy/crosvm/common_device.policy
-# tpm-specific policy
chdir: 1
fstat: 1
fsync: 1
ftruncate: 1
+getrandom: 1
getuid: 1
lseek: 1
mkdir: 1
open: 1
openat: 1
+prctl: arg0 == PR_SET_NAME
socket: return EACCES
stat: 1
statx: 1
diff --git a/seccomp/arm/video_device.policy b/seccomp/arm/video_device.policy
index 5c5a4a5a1..9e7d54151 100644
--- a/seccomp/arm/video_device.policy
+++ b/seccomp/arm/video_device.policy
@@ -8,6 +8,8 @@
clock_getres: 1
clock_getres_time64: 1
connect: 1
+fstatfs64: 1
+fstatfs: 1
getegid32: 1
geteuid32: 1
getgid32: 1
@@ -18,10 +20,11 @@ getsockname: 1
getuid32: 1
# ioctl: arg1 == DRM_IOCTL_*
ioctl: arg1 & 0x6400
+memfd_create: 1
openat: 1
-sched_yield: 1
send: 1
setpriority: 1
socket: arg0 == AF_UNIX
+statx: 1
stat64: 1
prctl: arg0 == PR_SET_NAME
diff --git a/seccomp/arm/wl_device.policy b/seccomp/arm/wl_device.policy
index 0a3de4f7f..aa64ec046 100644
--- a/seccomp/arm/wl_device.policy
+++ b/seccomp/arm/wl_device.policy
@@ -6,8 +6,8 @@
# Used to connect to wayland. arg0 == AF_UNIX && arg1 == SOCK_STREAM|SOCK_CLOEXEC
socket: arg0 == 1 && arg1 == 0x80001 && arg2 == 0
-# arg1 == FIONBIO || arg1 == DMA_BUF_IOCTL_SYNC
-ioctl: arg1 == 0x5421 || arg1 == 0x40086200
+# arg1 == FIONBIO || arg1 == DMA_BUF_IOCTL_SYNC || arg1 == SYNC_IOC_FILE_INFO
+ioctl: arg1 == 0x5421 || arg1 == 0x40086200 || arg1 == 0xc0383e04
connect: 1
open: return ENOENT
openat: return ENOENT
diff --git a/seccomp/arm/xhci.policy b/seccomp/arm/xhci.policy
index ca1a73dfc..f4a7d14d4 100644
--- a/seccomp/arm/xhci.policy
+++ b/seccomp/arm/xhci.policy
@@ -6,11 +6,11 @@
stat64: 1
lstat64: 1
-readlink: 1
-readlinkat: 1
getdents64: 1
name_to_handle_at: 1
access: 1
+faccessat: 1
+faccessat2: 1
timerfd_create: 1
getsockname: 1
pipe: 1
@@ -19,7 +19,6 @@ bind: 1
socket: arg0 == AF_NETLINK
stat: 1
statx: 1
-uname: 1
# The following ioctls are:
# 0x4004550d == USBDEVFS_REAPURBNDELAY32
# 0x550b == USBDEVFS_DISCARDURB
diff --git a/seccomp/crosvm_seccomp_policy_product_packages.mk b/seccomp/crosvm_seccomp_policy_product_packages.mk
index 5f7d8c95e..fa32600da 100644
--- a/seccomp/crosvm_seccomp_policy_product_packages.mk
+++ b/seccomp/crosvm_seccomp_policy_product_packages.mk
@@ -20,9 +20,12 @@ PRODUCT_PACKAGES += \
battery.policy \
block_device.policy \
cras_audio_device.policy \
+ cras_snd_device.policy \
fs_device.policy \
gpu_device.policy \
+ gpu_render_server.policy \
input_device.policy \
+ iommu_device.policy \
net_device.policy \
null_audio_device.policy \
pmem_device.policy \
@@ -44,9 +47,12 @@ PRODUCT_ARTIFACT_PATH_REQUIREMENT_ALLOWED_LIST += \
system/etc/seccomp_policy/crosvm/battery.policy \
system/etc/seccomp_policy/crosvm/block_device.policy \
system/etc/seccomp_policy/crosvm/cras_audio_device.policy \
+ system/etc/seccomp_policy/crosvm/cras_snd_device.policy \
system/etc/seccomp_policy/crosvm/fs_device.policy \
system/etc/seccomp_policy/crosvm/gpu_device.policy \
+ system/etc/seccomp_policy/crosvm/gpu_render_server.policy \
system/etc/seccomp_policy/crosvm/input_device.policy \
+ system/etc/seccomp_policy/crosvm/iommu_device.policy \
system/etc/seccomp_policy/crosvm/net_device.policy \
system/etc/seccomp_policy/crosvm/null_audio_device.policy \
system/etc/seccomp_policy/crosvm/pmem_device.policy \
diff --git a/seccomp/gen_android.sh b/seccomp/gen_android.sh
index 4a0ef1d3f..ef3480f48 100755
--- a/seccomp/gen_android.sh
+++ b/seccomp/gen_android.sh
@@ -55,7 +55,8 @@ function scan_policy_name() {
# pushd but no output to stdout/stderr
# the output is taken and used by the caller
pushd $seccomp_dir > /dev/null 2>&1
- ls --hide=common_device.policy --hide=common_device.frequency -1
+ ls --hide=common_device.policy --hide=common_device.frequency \
+ --hide=gpu_common.policy -1
popd > /dev/null 2>&1
)
}
@@ -85,21 +86,33 @@ EOF
function gen_blueprint_boilerplate() {
cat <<EOF
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_crosvm_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ // SPDX-license-identifier-BSD
+ default_applicable_licenses: ["external_crosvm_license"],
+}
+
genrule_defaults {
name: "crosvm_inline_seccomp_policy_x86_64",
- cmd: "\$(location policy-inliner.sh) \$(location x86_64/common_device.policy) < \$(in) > \$(out)",
+ cmd: "\$(location policy-inliner.sh) \$(location x86_64/common_device.policy) \$(location x86_64/gpu_common.policy) < \$(in) > \$(out)",
tool_files: [
"policy-inliner.sh",
"x86_64/common_device.policy",
+ "x86_64/gpu_common.policy",
],
}
genrule_defaults {
name: "crosvm_inline_seccomp_policy_aarch64",
- cmd: "\$(location policy-inliner.sh) \$(location aarch64/common_device.policy) < \$(in) > \$(out)",
+ cmd: "\$(location policy-inliner.sh) \$(location aarch64/common_device.policy) \$(location aarch64/gpu_common.policy) < \$(in) > \$(out)",
tool_files: [
"policy-inliner.sh",
"aarch64/common_device.policy",
+ "aarch64/gpu_common.policy",
],
}
@@ -183,6 +196,20 @@ function gen_crosvm_seccomp_policy_product_packages_mk_fragment() {
done | sort
}
+function print_host_seccomp_policy_lists() {
+ local archs=("$@")
+ echo "Please update the following blocks in device/google/cuttlefish/build/Android.bp:"
+ for arch in ${archs[@]}; do
+ echo
+ echo "cvd_host_seccomp_policy_${arch} = ["
+ for file in $(scan_policy_name ${arch}); do
+ local base_name="$(basename $file)"
+ echo " \"${file}_${arch}\","
+ done | sort
+ echo "]"
+ done
+}
+
# main
check_location
gen_license >Android.bp
@@ -191,3 +218,4 @@ gen_blueprint_boilerplate >>Android.bp
gen_blueprint_arch_policy_files "${seccomp_archs[@]}" >>Android.bp
gen_crosvm_seccomp_policy_product_packages_mk_fragment \
"${seccomp_archs[@]}" >>crosvm_seccomp_policy_product_packages.mk
+print_host_seccomp_policy_lists "${seccomp_archs[@]}"
diff --git a/seccomp/policy-inliner.sh b/seccomp/policy-inliner.sh
index 9933a0eb3..dbd1f25f5 100755
--- a/seccomp/policy-inliner.sh
+++ b/seccomp/policy-inliner.sh
@@ -13,16 +13,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-common="$1"
-if ! [[ -f $common ]]; then
- echo "usage: $0 /path/to/common_device.policy <input.policy >output.policy"
+common_device="$1"
+gpu_common="$2"
+if ! [[ -f $common_device ]] || ! [[ -f $gpu_common ]]; then
+ echo "usage: $0 /path/to/common_device.policy /path/to/gpu_common.policy <input.policy >output.policy"
exit 1
fi
while IFS= read -r line
do
if echo "$line" | egrep "@include[[:space:]]+/usr/share/policy/crosvm/common_device.policy" > /dev/null; then
- cat $common | egrep "^[a-zA-Z0-9_-]+:"
+ cat $common_device
+ continue
+ elif echo "$line" | egrep "@include[[:space:]]+/usr/share/policy/crosvm/gpu_common.policy" > /dev/null; then
+ cat $gpu_common
continue
fi
echo $line
diff --git a/seccomp/x86_64/battery.policy b/seccomp/x86_64/battery.policy
index ce6d41271..f15ed3614 100644
--- a/seccomp/x86_64/battery.policy
+++ b/seccomp/x86_64/battery.policy
@@ -7,7 +7,6 @@
# Syscalls used by power_monitor's powerd implementation.
clock_getres: 1
connect: 1
-getcwd: 1
getegid: 1
geteuid: 1
getrandom: 1
@@ -15,7 +14,6 @@ getresgid: 1
getresuid: 1
getsockname: 1
openat: 1
-readlink: 1
socket: arg0 == AF_UNIX
tgkill: 1
prctl: arg0 == PR_SET_NAME
diff --git a/seccomp/x86_64/block_device.policy b/seccomp/x86_64/block_device.policy
index 8f68c9be5..8a72dc885 100644
--- a/seccomp/x86_64/block_device.policy
+++ b/seccomp/x86_64/block_device.policy
@@ -12,6 +12,7 @@ ftruncate: 1
lseek: 1
open: return ENOENT
openat: return ENOENT
+newfstatat: 1
pread64: 1
preadv: 1
pwrite64: 1
diff --git a/seccomp/x86_64/coiommu.policy b/seccomp/x86_64/coiommu.policy
new file mode 100644
index 000000000..42f7a30e9
--- /dev/null
+++ b/seccomp/x86_64/coiommu.policy
@@ -0,0 +1,11 @@
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+@include /usr/share/policy/crosvm/common_device.policy
+
+# VFIO_IOMMU_MAP/UNMAP_DMA
+ioctl: arg1 == 0x3B71 || arg1 == 0x3B72
+prctl: arg0 == PR_SET_NAME
+timerfd_create: 1
+timerfd_settime: 1
+timerfd_gettime: 1
diff --git a/seccomp/x86_64/common_device.policy b/seccomp/x86_64/common_device.policy
index 49d452051..1f677ea4a 100644
--- a/seccomp/x86_64/common_device.policy
+++ b/seccomp/x86_64/common_device.policy
@@ -4,6 +4,7 @@
@frequency ./common_device.frequency
brk: 1
+clock_gettime: 1
clone: arg0 & CLONE_THREAD
close: 1
dup2: 1
@@ -15,6 +16,7 @@ eventfd2: 1
exit: 1
exit_group: 1
futex: 1
+getcwd: 1
getpid: 1
gettid: 1
gettimeofday: 1
@@ -32,6 +34,8 @@ pipe2: 1
poll: 1
ppoll: 1
read: 1
+readlink: 1
+readlinkat: 1
readv: 1
recvfrom: 1
recvmsg: 1
@@ -40,6 +44,7 @@ rt_sigaction: 1
rt_sigprocmask: 1
rt_sigreturn: 1
sched_getaffinity: 1
+sched_yield: 1
sendmsg: 1
sendto: 1
set_robust_list: 1
@@ -47,3 +52,4 @@ sigaltstack: 1
write: 1
writev: 1
fcntl: 1
+uname: 1
diff --git a/seccomp/x86_64/cras_audio_device.policy b/seccomp/x86_64/cras_audio_device.policy
index bbaffb080..83888bb3e 100644
--- a/seccomp/x86_64/cras_audio_device.policy
+++ b/seccomp/x86_64/cras_audio_device.policy
@@ -4,6 +4,7 @@
@include /usr/share/policy/crosvm/common_device.policy
+lseek: 1
madvise: 1
open: return ENOENT
openat: return ENOENT
@@ -11,5 +12,7 @@ prlimit64: 1
setrlimit: 1
sched_setscheduler: 1
socketpair: arg0 == AF_UNIX
-clock_gettime: 1
prctl: arg0 == PR_SET_NAME
+timerfd_create: 1
+timerfd_gettime: 1
+timerfd_settime: 1
diff --git a/seccomp/x86_64/cras_snd_device.policy b/seccomp/x86_64/cras_snd_device.policy
new file mode 100644
index 000000000..e82003d67
--- /dev/null
+++ b/seccomp/x86_64/cras_snd_device.policy
@@ -0,0 +1,18 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+@include /usr/share/policy/crosvm/common_device.policy
+
+open: return ENOENT
+openat: return ENOENT
+socket: arg0 == AF_UNIX
+socketpair: arg0 == AF_UNIX
+prctl: arg0 == PR_SET_NAME
+connect: 1
+prlimit64: 1
+setrlimit: 1
+sched_setscheduler: 1
+timerfd_create: 1
+timerfd_gettime: 1
+timerfd_settime: 1
diff --git a/seccomp/x86_64/fs_device.policy b/seccomp/x86_64/fs_device.policy
index bd03307a2..61aeaaf21 100644
--- a/seccomp/x86_64/fs_device.policy
+++ b/seccomp/x86_64/fs_device.policy
@@ -27,11 +27,16 @@ getdents64: 1
getegid: 1
geteuid: 1
getrandom: 1
+# Use constants for verity ioctls since minijail doesn't understand them yet.
+# 0x40806685 = FS_IOC_ENABLE_VERITY
+# 0xc0046686 = FS_IOC_MEASURE_VERITY
ioctl: arg1 == FS_IOC_FSGETXATTR || \
arg1 == FS_IOC_FSSETXATTR || \
arg1 == FS_IOC_GETFLAGS || \
arg1 == FS_IOC_SETFLAGS || \
- arg1 == FS_IOC_GET_ENCRYPTION_POLICY_EX
+ arg1 == FS_IOC_GET_ENCRYPTION_POLICY_EX || \
+ arg1 == 0x40806685 || \
+ arg1 == 0xc0046686
linkat: 1
lseek: 1
mkdir: 1
@@ -42,7 +47,6 @@ open: return ENOENT
openat: 1
preadv: 1
pwritev: 1
-readlinkat: 1
renameat2: 1
setresgid: 1
setresuid: 1
@@ -54,3 +58,4 @@ utimensat: 1
prctl: arg0 == PR_SET_NAME || arg0 == PR_SET_SECUREBITS || arg0 == PR_GET_SECUREBITS
capget: 1
capset: 1
+unshare: 1 \ No newline at end of file
diff --git a/seccomp/x86_64/gpu_common.policy b/seccomp/x86_64/gpu_common.policy
new file mode 100644
index 000000000..411874f86
--- /dev/null
+++ b/seccomp/x86_64/gpu_common.policy
@@ -0,0 +1,104 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Rules from common_device.policy with some rules removed because they block certain flags needed
+# for gpu.
+brk: 1
+clock_gettime: 1
+close: 1
+dup2: 1
+dup: 1
+epoll_create1: 1
+epoll_ctl: 1
+epoll_wait: 1
+eventfd2: 1
+exit: 1
+exit_group: 1
+futex: 1
+getcwd: 1
+getpid: 1
+gettid: 1
+gettimeofday: 1
+kill: 1
+madvise: arg2 == MADV_DONTNEED || arg2 == MADV_DONTDUMP || arg2 == MADV_REMOVE
+mremap: 1
+munmap: 1
+nanosleep: 1
+clock_nanosleep: 1
+pipe2: 1
+poll: 1
+ppoll: 1
+prctl: arg0 == PR_SET_NAME || arg0 == PR_GET_NAME
+read: 1
+readlink: 1
+readlinkat: 1
+readv: 1
+recvfrom: 1
+recvmsg: 1
+restart_syscall: 1
+rt_sigaction: 1
+rt_sigprocmask: 1
+rt_sigreturn: 1
+sched_getaffinity: 1
+sched_yield: 1
+sendmsg: 1
+sendto: 1
+set_robust_list: 1
+sigaltstack: 1
+write: 1
+writev: 1
+uname: 1
+
+# Rules specific to gpu
+connect: 1
+# 1033 is F_ADD_SEALS, 1034 is F_GET_SEALS
+fcntl: arg1 == F_DUPFD_CLOEXEC || arg1 == F_SETFD || arg1 == F_GETFL || \
+ arg1 == F_SETFL || arg1 == 1033 || arg1 == 1034
+fstat: 1
+# Used to set of size new memfd.
+ftruncate: 1
+getdents: 1
+getdents64: 1
+geteuid: 1
+getrandom: 1
+getuid: 1
+# 0x40086200 = DMA_BUF_IOCTL_SYNC, 0x6400 == DRM_IOCTL_BASE, 0x40087543 == UDMABUF_CREATE_LIST
+ioctl: arg1 == FIONBIO || arg1 == FIOCLEX || arg1 == 0x40086200 || arg1 & 0x6400 || arg1 == 0x40087543
+lseek: 1
+lstat: 1
+# Used for sharing memory with wayland. Also internally by Intel anv.
+# arg1 == MFD_CLOEXEC|MFD_ALLOW_SEALING or simply MFD_CLOEXEC.
+memfd_create: arg1 == 3 || arg1 == 1
+# mmap/mprotect/open/openat differ from the common_device.policy
+mmap: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ|PROT_EXEC || arg2 == PROT_WRITE || arg2 == PROT_READ
+mprotect: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ
+open: 1
+openat: 1
+stat: 1
+statx: 1
+sysinfo: 1
+fstatfs: 1
+
+# Required for perfetto tracing
+# fcntl: arg1 == F_SETFD || arg1 == F_GETFL || arg1 == F_SETFL (merged above)
+getsockopt: 1
+shutdown: 1
+
+# Rules for Mesa's shader binary cache.
+flock: 1
+mkdir: 1
+newfstatat: 1
+rename: 1
+setpriority: 1
+unlink: 1
+
+# Rules specific to AMD gpus.
+sched_setscheduler: 1
+sched_setaffinity: 1
+kcmp: 1
+
+# Rules for Vulkan loader / layers
+access: 1
+getgid: 1
+getegid: 1
diff --git a/seccomp/x86_64/gpu_device.policy b/seccomp/x86_64/gpu_device.policy
index 2b4f4b2bb..69aea1562 100644
--- a/seccomp/x86_64/gpu_device.policy
+++ b/seccomp/x86_64/gpu_device.policy
@@ -2,103 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-# Rules from common_device.policy with some rules removed because they block certain flags needed
-# for gpu.
-brk: 1
-clock_gettime: 1
-clone: arg0 & CLONE_THREAD
-close: 1
-dup2: 1
-dup: 1
-epoll_create1: 1
-epoll_ctl: 1
-epoll_wait: 1
-eventfd2: 1
-exit: 1
-exit_group: 1
-futex: 1
-getpid: 1
-gettid: 1
-gettimeofday: 1
-kill: 1
-madvise: arg2 == MADV_DONTNEED || arg2 == MADV_DONTDUMP || arg2 == MADV_REMOVE
-mremap: 1
-munmap: 1
-nanosleep: 1
-clock_nanosleep: 1
-pipe2: 1
-poll: 1
-ppoll: 1
-prctl: arg0 == PR_SET_NAME || arg0 == PR_GET_NAME
-read: 1
-readv: 1
-recvfrom: 1
-recvmsg: 1
-restart_syscall: 1
-rt_sigaction: 1
-rt_sigprocmask: 1
-rt_sigreturn: 1
-sched_getaffinity: 1
-sendmsg: 1
-sendto: 1
-set_robust_list: 1
-sigaltstack: 1
-write: 1
-writev: 1
-
-# Rules specific to gpu
-connect: 1
-fcntl: arg1 == F_DUPFD_CLOEXEC || arg1 == F_SETFD || arg1 == F_GETFL || \
- arg1 == F_SETFL
-fstat: 1
-# Used to set of size new memfd.
-ftruncate: 1
-getdents: 1
-getdents64: 1
-geteuid: 1
-getrandom: 1
-getuid: 1
-# 0x40086200 = DMA_BUF_IOCTL_SYNC, 0x6400 == DRM_IOCTL_BASE, 0x40087543 == UDMABUF_CREATE_LIST
-ioctl: arg1 == FIONBIO || arg1 == FIOCLEX || arg1 == 0x40086200 || arg1 & 0x6400 || arg1 == 0x40087543
-lseek: 1
-lstat: 1
-# Used for sharing memory with wayland. Also internally by Intel anv.
-# arg1 == MFD_CLOEXEC|MFD_ALLOW_SEALING or simply MFD_CLOEXEC.
-memfd_create: arg1 == 3 || arg1 == 1
-# mmap/mprotect/open/openat differ from the common_device.policy
-mmap: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ|PROT_EXEC || arg2 == PROT_WRITE || arg2 == PROT_READ
-mprotect: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ
-open: 1
-openat: 1
-readlink: 1
-socket: arg0 == 1 && arg1 == 0x80001 && arg2 == 0
-stat: 1
-statx: 1
-sysinfo: 1
-
-# Required for perfetto tracing
-# fcntl: arg1 == F_SETFD || arg1 == F_GETFL || arg1 == F_SETFL (merged above)
-getsockopt: 1
-shutdown: 1
-
-# Rules for Mesa's shader binary cache.
-flock: 1
-mkdir: 1
-newfstatat: 1
-rename: 1
-setpriority: 1
-unlink: 1
+@include /usr/share/policy/crosvm/gpu_common.policy
-# Rules specific to AMD gpus.
-uname: 1
-sched_setscheduler: 1
-sched_setaffinity: 1
-kcmp: 1
-
-# Rules for Vulkan loader
-access: 1
-getgid: 1
-getegid: 1
-
-# Rules for virglrenderer
-sched_yield: 1
+socket: arg0 == AF_UNIX && arg1 == SOCK_STREAM|SOCK_CLOEXEC && arg2 == 0
+clone: arg0 & CLONE_THREAD
diff --git a/seccomp/x86_64/gpu_render_server.policy b/seccomp/x86_64/gpu_render_server.policy
new file mode 100644
index 000000000..536d43dd4
--- /dev/null
+++ b/seccomp/x86_64/gpu_render_server.policy
@@ -0,0 +1,18 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+@include /usr/share/policy/crosvm/gpu_common.policy
+
+# allow fork() and waitid()
+clone: 1
+waitid: 1
+
+# allow SOCK_STREAM and SOCK_DGRAM (syslog)
+socket: arg0 == AF_UNIX && arg2 == 0
+
+# allow socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC)
+socketpair: arg0 == AF_UNIX && arg1 == SOCK_SEQPACKET|SOCK_CLOEXEC && arg2 == 0
+
+# allow signalfd()
+signalfd4: 1
diff --git a/seccomp/x86_64/iommu_device.policy b/seccomp/x86_64/iommu_device.policy
new file mode 100644
index 000000000..28a2002ed
--- /dev/null
+++ b/seccomp/x86_64/iommu_device.policy
@@ -0,0 +1,13 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+@include /usr/share/policy/crosvm/common_device.policy
+
+prctl: arg0 == PR_SET_NAME
+open: return ENOENT
+openat: return ENOENT
+
+# 0x3B71: VFIO_IOMMU_MAP_DMA
+# 0x3B72: VFIO_IOMMU_UNMAP_DMA
+ioctl: arg1 == 0x3B71 || arg1 == 0x3B72
diff --git a/seccomp/x86_64/tpm_device.policy b/seccomp/x86_64/tpm_device.policy
index bfd64a838..d60631a6b 100644
--- a/seccomp/x86_64/tpm_device.policy
+++ b/seccomp/x86_64/tpm_device.policy
@@ -2,57 +2,20 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-# common policy
-brk: 1
-clone: arg0 & CLONE_THREAD
-close: 1
-dup2: 1
-dup: 1
-epoll_create1: 1
-epoll_ctl: 1
-epoll_wait: 1
-eventfd2: 1
-exit: 1
-exit_group: 1
-futex: 1
-getpid: 1
-getrandom: 1
-gettimeofday: 1
-kill: 1
-madvise: arg2 == MADV_DONTNEED || arg2 == MADV_DONTDUMP || arg2 == MADV_REMOVE
-mmap: arg2 in ~PROT_EXEC
-mprotect: arg2 in ~PROT_EXEC
-mremap: 1
-munmap: 1
-nanosleep: 1
-clock_nanosleep: 1
-pipe2: 1
-poll: 1
-ppoll: 1
-prctl: arg0 == PR_SET_NAME
-read: 1
-recvfrom: 1
-recvmsg: 1
-restart_syscall: 1
-rt_sigaction: 1
-rt_sigprocmask: 1
-rt_sigreturn: 1
-sched_getaffinity: 1
-sendmsg: 1
-set_robust_list: 1
-sigaltstack: 1
-write: 1
+@include /usr/share/policy/crosvm/common_device.policy
-# tpm-specific policy
chdir: 1
fstat: 1
fsync: 1
ftruncate: 1
+getrandom: 1
getuid: 1
lseek: 1
mkdir: 1
+newfstatat: 1
open: 1
openat: 1
+prctl: arg0 == PR_SET_NAME
socket: return EACCES
stat: 1
statx: 1
diff --git a/seccomp/x86_64/vfio_device.policy b/seccomp/x86_64/vfio_device.policy
index bf7a00d12..003f5160a 100644
--- a/seccomp/x86_64/vfio_device.policy
+++ b/seccomp/x86_64/vfio_device.policy
@@ -7,7 +7,6 @@
ioctl: arg1 == 0x3B6E || arg1 == 0x3B71 || arg1 == 0x3B72
open: return ENOENT
openat: return ENOENT
-readlink: 1
pread64: 1
pwrite64: 1
prctl: arg0 == PR_SET_NAME
diff --git a/seccomp/x86_64/video_device.policy b/seccomp/x86_64/video_device.policy
index 4c54d9d17..e147a63e4 100644
--- a/seccomp/x86_64/video_device.policy
+++ b/seccomp/x86_64/video_device.policy
@@ -2,7 +2,56 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-@include /usr/share/policy/crosvm/common_device.policy
+# Rules from common_device.policy with mmap and mprotect removed because the video device needs
+# to allow more arguments for them.
+brk: 1
+clock_gettime: 1
+clone: arg0 & CLONE_THREAD
+close: 1
+dup2: 1
+dup: 1
+epoll_create1: 1
+epoll_ctl: 1
+epoll_wait: 1
+eventfd2: 1
+exit: 1
+exit_group: 1
+futex: 1
+getcwd: 1
+getpid: 1
+gettid: 1
+gettimeofday: 1
+io_uring_setup: 1
+io_uring_enter: 1
+kill: 1
+madvise: arg2 == MADV_DONTNEED || arg2 == MADV_DONTDUMP || arg2 == MADV_REMOVE
+mremap: 1
+munmap: 1
+nanosleep: 1
+clock_nanosleep: 1
+pipe2: 1
+poll: 1
+ppoll: 1
+read: 1
+readlink: 1
+readlinkat: 1
+readv: 1
+recvfrom: 1
+recvmsg: 1
+restart_syscall: 1
+rt_sigaction: 1
+rt_sigprocmask: 1
+rt_sigreturn: 1
+sched_getaffinity: 1
+sched_yield: 1
+sendmsg: 1
+sendto: 1
+set_robust_list: 1
+sigaltstack: 1
+write: 1
+writev: 1
+fcntl: 1
+uname: 1
# Syscalls specific to video devices.
clock_getres: 1
@@ -18,12 +67,15 @@ getsockname: 1
getuid: 1
# ioctl: arg1 == DRM_IOCTL_*
ioctl: arg1 & 0x6400
+memfd_create: 1
+newfstatat: 1
openat: 1
-sched_yield: 1
setpriority: 1
socket: arg0 == AF_UNIX
stat: 1
fstat: 1
+fstatfs: 1
+statx: 1
# Rules needed for minigbm on AMD devices.
getrandom: 1
@@ -31,10 +83,8 @@ lstat: 1
# mmap/mprotect differ from the common_device.policy
mmap: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ|PROT_EXEC || arg2 == PROT_WRITE || arg2 == PROT_READ
mprotect: arg2 == PROT_READ|PROT_WRITE || arg2 == PROT_NONE || arg2 == PROT_READ
-readlink: 1
sched_setaffinity: 1
sched_setscheduler: arg1 == SCHED_IDLE || arg1 == SCHED_BATCH
-uname: 1
# Required by mesa on AMD GPU
sysinfo: 1
diff --git a/seccomp/x86_64/vios_audio_device.policy b/seccomp/x86_64/vios_audio_device.policy
index a3b7f1961..3a1fb0811 100644
--- a/seccomp/x86_64/vios_audio_device.policy
+++ b/seccomp/x86_64/vios_audio_device.policy
@@ -4,7 +4,6 @@
@include /usr/share/policy/crosvm/common_device.policy
-clock_gettime: 1
lseek: 1
open: return ENOENT
openat: return ENOENT
diff --git a/seccomp/x86_64/vvu_proxy_device.policy b/seccomp/x86_64/vvu_proxy_device.policy
new file mode 100644
index 000000000..434193cf7
--- /dev/null
+++ b/seccomp/x86_64/vvu_proxy_device.policy
@@ -0,0 +1,14 @@
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+@include /usr/share/policy/crosvm/common_device.policy
+
+accept4: 1
+bind: 1
+fstat: 1
+getdents64: 1
+ioctl: arg1 == SIOCGIFFLAGS || arg1 == SIOCSIFFLAGS || arg1 == TCGETS
+prctl: arg0 == PR_SET_NAME
+socket: arg0 == AF_UNIX
+socketpair: 1 \ No newline at end of file
diff --git a/seccomp/x86_64/wl_device.policy b/seccomp/x86_64/wl_device.policy
index f2cda7f24..47c7858be 100644
--- a/seccomp/x86_64/wl_device.policy
+++ b/seccomp/x86_64/wl_device.policy
@@ -6,8 +6,8 @@
# Used to connect to wayland. arg0 == AF_UNIX && arg1 == SOCK_STREAM|SOCK_CLOEXEC
socket: arg0 == 1 && arg1 == 0x80001 && arg2 == 0
-# arg1 == FIONBIO || arg1 == DMA_BUF_IOCTL_SYNC
-ioctl: arg1 == 0x5421 || arg1 == 0x40086200
+# arg1 == FIONBIO || arg1 == DMA_BUF_IOCTL_SYNC || arg1 == SYNC_IOC_FILE_INFO
+ioctl: arg1 == 0x5421 || arg1 == 0x40086200 || arg1 == 0xc0383e04
connect: 1
# Used for sharing memory with wayland. arg1 == MFD_CLOEXEC|MFD_ALLOW_SEALING
memfd_create: arg1 == 3
diff --git a/seccomp/x86_64/xhci.policy b/seccomp/x86_64/xhci.policy
index 9ef376698..0f998c0c9 100644
--- a/seccomp/x86_64/xhci.policy
+++ b/seccomp/x86_64/xhci.policy
@@ -5,11 +5,11 @@
@include /usr/share/policy/crosvm/common_device.policy
lstat: 1
-readlink: 1
-readlinkat: 1
timerfd_create: 1
name_to_handle_at: 1
access: 1
+faccessat: 1
+faccessat2: 1
getsockname: 1
pipe: 1
setsockopt: 1
@@ -18,7 +18,7 @@ open: return ENOENT
openat: 1
socket: arg0 == AF_NETLINK
stat: 1
-uname: 1
+statx: 1
# The following ioctls are:
# 0x4008550d == USBDEVFS_REAPURBNDELAY
# 0x41045508 == USBDEVFS_GETDRIVER
@@ -36,6 +36,7 @@ uname: 1
# 0x80185520 == USBDEVFS_CONNINFO_EX
ioctl: arg1 == 0xc0185500 || arg1 == 0x41045508 || arg1 == 0x8004550f || arg1 == 0x4008550d || arg1 == 0x8004551a || arg1 == 0x550b || arg1 == 0x80045510 || arg1 == 0x80045515 || arg1 == 0x8038550a || arg1 == 0x5514 || arg1 == 0x80045505 || arg1 == 0x8108551b || arg1 == 0x40085511 || arg1 == 0x80185520
fstat: 1
+newfstatat: 1
getrandom: 1
getdents: 1
getdents64: 1
diff --git a/rand_ish/Android.bp b/serde_keyvalue/Android.bp
index aa1fd9733..4c9986eaf 100644
--- a/rand_ish/Android.bp
+++ b/serde_keyvalue/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace --no-subdir.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -11,33 +11,38 @@ package {
}
rust_library {
- name: "librand_ish",
+ name: "libserde_keyvalue",
defaults: ["crosvm_defaults"],
host_supported: true,
- crate_name: "rand_ish",
+ crate_name: "serde_keyvalue",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
+ rustlibs: [
+ "libserde",
+ "libthiserror",
+ ],
+ proc_macros: ["libremain"],
}
-rust_defaults {
- name: "rand_ish_defaults",
+rust_test {
+ name: "serde_keyvalue_test_src_lib",
defaults: ["crosvm_defaults"],
- crate_name: "rand_ish",
+ host_supported: true,
+ crate_name: "serde_keyvalue",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
-}
-
-rust_test_host {
- name: "rand_ish_host_test_src_lib",
- defaults: ["rand_ish_defaults"],
test_options: {
unit_test: true,
},
-}
-
-rust_test {
- name: "rand_ish_device_test_src_lib",
- defaults: ["rand_ish_defaults"],
+ edition: "2021",
+ rustlibs: [
+ "libserde",
+ "libthiserror",
+ ],
+ proc_macros: ["libremain"],
}
diff --git a/serde_keyvalue/Cargo.toml b/serde_keyvalue/Cargo.toml
new file mode 100644
index 000000000..88675067d
--- /dev/null
+++ b/serde_keyvalue/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "serde_keyvalue"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2021"
+
+[features]
+argh_derive = ["argh", "serde_keyvalue_derive"]
+
+[dependencies]
+argh = { version = "0.1.7", optional = true }
+serde_keyvalue_derive = { path = "serde_keyvalue_derive", optional = true }
+serde = "1"
+thiserror = { version = "1.0.20" }
+remain = "*"
+
+[dev-dependencies]
+serde = { version = "1", features = ["derive"] }
+
diff --git a/serde_keyvalue/README.md b/serde_keyvalue/README.md
new file mode 100644
index 000000000..30498cf02
--- /dev/null
+++ b/serde_keyvalue/README.md
@@ -0,0 +1,19 @@
+# Serde deserializer from key=value strings
+
+A lightweight serde deserializer for strings containing key-value pairs separated by commas, as
+commonly found in command-line parameters.
+
+Say your program takes a command-line option of the form:
+
+```text
+--foo type=bar,active,nb_threads=8
+```
+
+This crate provides a `from_key_values` function that deserializes these key-values into a
+configuration structure. Since it uses serde, the same configuration structure can also be created
+from any other supported source (such as a TOML or YAML configuration file) that uses the same keys.
+
+Integration with the [argh](https://github.com/google/argh) command-line parser is also provided via
+the `argh_derive` feature.
+
+See the inline documentation for examples and more details.
diff --git a/serde_keyvalue/serde_keyvalue_derive/Android.bp b/serde_keyvalue/serde_keyvalue_derive/Android.bp
new file mode 100644
index 000000000..8cee3f7c0
--- /dev/null
+++ b/serde_keyvalue/serde_keyvalue_derive/Android.bp
@@ -0,0 +1,27 @@
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
+// Do not modify this file as changes will be overridden on upgrade.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_crosvm_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-BSD
+ default_applicable_licenses: ["external_crosvm_license"],
+}
+
+rust_proc_macro {
+ name: "libserde_keyvalue_derive",
+ defaults: ["crosvm_defaults"],
+ crate_name: "serde_keyvalue_derive",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ rustlibs: [
+ "libargh",
+ "libproc_macro2",
+ "libquote",
+ "libsyn",
+ ],
+}
diff --git a/serde_keyvalue/serde_keyvalue_derive/Cargo.toml b/serde_keyvalue/serde_keyvalue_derive/Cargo.toml
new file mode 100644
index 000000000..1ece8456d
--- /dev/null
+++ b/serde_keyvalue/serde_keyvalue_derive/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "serde_keyvalue_derive"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+argh = "0.1.7"
+proc-macro2 = "1.0"
+syn = "1.0"
+quote = "1.0"
diff --git a/serde_keyvalue/serde_keyvalue_derive/src/lib.rs b/serde_keyvalue/serde_keyvalue_derive/src/lib.rs
new file mode 100644
index 000000000..dfb605250
--- /dev/null
+++ b/serde_keyvalue/serde_keyvalue_derive/src/lib.rs
@@ -0,0 +1,24 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use quote::quote;
+use syn::{parse_macro_input, DeriveInput};
+
+/// Implement `argh`'s `FromArgValue` trait for a struct or enum using `from_key_values`.
+#[proc_macro_derive(FromKeyValues)]
+pub fn keyvalues_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let DeriveInput {
+ ident, generics, ..
+ } = parse_macro_input!(input);
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+ quote! {
+ impl #impl_generics ::serde_keyvalue::argh::FromArgValue for #ident #ty_generics #where_clause {
+ fn from_arg_value(value: &str) -> std::result::Result<Self, std::string::String> {
+ ::serde_keyvalue::from_key_values(value).map_err(|e| e.to_string())
+ }
+ }
+ }
+ .into()
+}
diff --git a/serde_keyvalue/src/key_values.rs b/serde_keyvalue/src/key_values.rs
new file mode 100644
index 000000000..415a66be7
--- /dev/null
+++ b/serde_keyvalue/src/key_values.rs
@@ -0,0 +1,1048 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::borrow::Cow;
+use std::fmt::{self, Debug, Display};
+use std::num::{IntErrorKind, ParseIntError};
+use std::str::FromStr;
+
+use remain::sorted;
+use serde::de;
+use serde::Deserialize;
+use thiserror::Error;
+
+#[derive(Debug, Error, PartialEq)]
+#[sorted]
+#[non_exhaustive]
+#[allow(missing_docs)]
+/// Different kinds of errors that can be returned by the parser.
+pub enum ErrorKind {
+ #[error("unexpected end of input")]
+ Eof,
+ #[error("expected a boolean")]
+ ExpectedBoolean,
+ #[error("expected ','")]
+ ExpectedComma,
+ #[error("expected '='")]
+ ExpectedEqual,
+ #[error("expected an identifier")]
+ ExpectedIdentifier,
+ #[error("expected a number")]
+ ExpectedNumber,
+ #[error("expected a string")]
+ ExpectedString,
+ #[error("\" and ' can only be used in quoted strings")]
+ InvalidCharInString,
+ #[error("non-terminated string")]
+ NonTerminatedString,
+ #[error("provided number does not fit in the destination type")]
+ NumberOverflow,
+ #[error("serde error: {0}")]
+ SerdeError(String),
+ #[error("remaining characters in input")]
+ TrailingCharacters,
+}
+
+/// Error that may be thown while parsing a key-values string.
+#[derive(Debug, Error, PartialEq)]
+pub struct ParseError {
+ /// Detailed error that occurred.
+ pub kind: ErrorKind,
+ /// Index of the error in the input string.
+ pub pos: usize,
+}
+
+impl Display for ParseError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match &self.kind {
+ ErrorKind::SerdeError(s) => write!(f, "{}", s),
+ _ => write!(f, "{} at position {}", self.kind, self.pos),
+ }
+ }
+}
+
+impl de::Error for ParseError {
+ fn custom<T>(msg: T) -> Self
+ where
+ T: fmt::Display,
+ {
+ Self {
+ kind: ErrorKind::SerdeError(msg.to_string()),
+ pos: 0,
+ }
+ }
+}
+
+type Result<T> = std::result::Result<T, ParseError>;
+
+/// Serde deserializer for key-values strings.
+struct KeyValueDeserializer<'de> {
+ /// Full input originally received for parsing.
+ original_input: &'de str,
+ /// Input currently remaining to parse.
+ input: &'de str,
+ /// If set, then `deserialize_identifier` will take and return its content the next time it is
+ /// called instead of trying to parse an identifier from the input. This is needed to allow the
+ /// name of the first field of a struct to be omitted, e.g.
+ ///
+ /// --block "/path/to/disk.img,ro=true"
+ ///
+ /// instead of
+ ///
+ /// --block "path=/path/to/disk.img,ro=true"
+ next_identifier: Option<&'de str>,
+}
+
+impl<'de> From<&'de str> for KeyValueDeserializer<'de> {
+ fn from(input: &'de str) -> Self {
+ Self {
+ original_input: input,
+ input,
+ next_identifier: None,
+ }
+ }
+}
+
+impl<'de> KeyValueDeserializer<'de> {
+ /// Return an `kind` error for the current position of the input.
+ fn error_here(&self, kind: ErrorKind) -> ParseError {
+ ParseError {
+ kind,
+ pos: self.original_input.len() - self.input.len(),
+ }
+ }
+
+ /// Returns the next char in the input string without consuming it, or None
+ /// if we reached the end of input.
+ fn peek_char(&self) -> Option<char> {
+ self.input.chars().next()
+ }
+
+ /// Skip the next char in the input string.
+ fn skip_char(&mut self) {
+ let _ = self.next_char();
+ }
+
+ /// Returns the next char in the input string and consume it, or returns
+ /// None if we reached the end of input.
+ fn next_char(&mut self) -> Option<char> {
+ let c = self.peek_char()?;
+ self.input = &self.input[c.len_utf8()..];
+ Some(c)
+ }
+
+ /// Try to peek the next element in the input as an identifier, without consuming it.
+ ///
+ /// Returns the parsed indentifier, an `ExpectedIdentifier` error if the next element is not
+ /// an identifier, or `Eof` if we were at the end of the input string.
+ fn peek_identifier(&self) -> Result<&'de str> {
+ // End of input?
+ if self.input.is_empty() {
+ return Err(self.error_here(ErrorKind::Eof));
+ }
+
+ let res = self.input;
+ let mut len = 0;
+ let mut iter = self.input.chars();
+ loop {
+ match iter.next() {
+ None | Some(',' | '=') => break,
+ Some(c) if c.is_ascii_alphanumeric() || c == '_' || (c == '-' && len > 0) => {
+ len += c.len_utf8();
+ }
+ Some(_) => return Err(self.error_here(ErrorKind::ExpectedIdentifier)),
+ }
+ }
+
+ // An identifier cannot be empty.
+ if len == 0 {
+ Err(self.error_here(ErrorKind::ExpectedIdentifier))
+ } else {
+ Ok(&res[0..len])
+ }
+ }
+
+ /// Peek the next value, i.e. anything until the next comma or the end of the input string.
+ ///
+ /// This can be used to reliably peek any value, except strings which may contain commas in
+ /// quotes.
+ fn peek_value(&self) -> Result<&'de str> {
+ let res = self.input;
+ let mut len = 0;
+ let mut iter = self.input.chars();
+ loop {
+ match iter.next() {
+ None | Some(',') => break,
+ Some(c) => len += c.len_utf8(),
+ }
+ }
+
+ if len > 0 {
+ Ok(&res[0..len])
+ } else {
+ Err(self.error_here(ErrorKind::Eof))
+ }
+ }
+
+ /// Attempts to parse an identifier, either for a key or for the value of an enum type.
+ ///
+ /// Usually identifiers are not allowed to start with a number, but we chose to allow this
+ /// here otherwise options like "mode=2d" won't parse if "2d" is an alias for an enum variant.
+ fn parse_identifier(&mut self) -> Result<&'de str> {
+ let res = self.peek_identifier()?;
+ self.input = &self.input[res.len()..];
+ Ok(res)
+ }
+
+ /// Attempts to parse a string.
+ ///
+ /// A string can be quoted (using single or double quotes) or not. If it is not, we consume
+ /// input until the next ',' separating character. If it is, we consume input until the next
+ /// non-escaped quote.
+ ///
+ /// The returned value is a slice into the current input if no characters to unescape were met,
+ /// or a fully owned string if we had to unescape some characters.
+ fn parse_string(&mut self) -> Result<Cow<'de, str>> {
+ let (s, quote) = match self.peek_char() {
+ // Beginning of quoted string.
+ quote @ Some('"' | '\'') => {
+ // Safe because we just matched against `Some`.
+ let quote = quote.unwrap();
+ // Skip the opening quote.
+ self.skip_char();
+ let mut len = 0;
+ let mut iter = self.input.chars();
+ let mut escaped = false;
+ loop {
+ let c = match iter.next() {
+ Some('\\') if !escaped => {
+ escaped = true;
+ '\\'
+ }
+ // Found end of quoted string if we meet a non-escaped quote.
+ Some(c) if c == quote && !escaped => break,
+ Some(c) => {
+ escaped = false;
+ c
+ }
+ None => return Err(self.error_here(ErrorKind::NonTerminatedString)),
+ };
+ len += c.len_utf8();
+ }
+ let s = &self.input[0..len];
+ self.input = &self.input[len..];
+ // Skip the closing quote
+ self.skip_char();
+ (s, Some(quote))
+ }
+ // Empty strings must use quotes.
+ None | Some(',') => return Err(self.error_here(ErrorKind::ExpectedString)),
+ // Non-quoted string.
+ Some(_) => {
+ let s = self
+ .input
+ .split(&[',', '"', '\''])
+ .next()
+ .unwrap_or(self.input);
+ self.input = &self.input[s.len()..];
+ // If a string was not quoted, it shall not contain a quote.
+ if let Some('"' | '\'') = self.peek_char() {
+ return Err(self.error_here(ErrorKind::InvalidCharInString));
+ }
+ (s, None)
+ }
+ };
+
+ if quote.is_some() {
+ let mut escaped = false;
+ let unescaped_string: String = s
+ .chars()
+ .filter_map(|c| match c {
+ '\\' if !escaped => {
+ escaped = true;
+ None
+ }
+ c => {
+ escaped = false;
+ Some(c)
+ }
+ })
+ .collect();
+ Ok(Cow::Owned(unescaped_string))
+ } else {
+ Ok(Cow::Borrowed(s))
+ }
+ }
+
+ /// A boolean can be 'true', 'false', or nothing (which is equivalent to 'true').
+ fn parse_bool(&mut self) -> Result<bool> {
+ // 'true' and 'false' can be picked by peek_value.
+ let s = match self.peek_value() {
+ Ok(s) => s,
+ // Consider end of input as an empty string, which will be evaluated to `true`.
+ Err(ParseError {
+ kind: ErrorKind::Eof,
+ ..
+ }) => "",
+ Err(_) => return Err(self.error_here(ErrorKind::ExpectedBoolean)),
+ };
+ let res = match s {
+ "" => Ok(true),
+ s => bool::from_str(s).map_err(|_| self.error_here(ErrorKind::ExpectedBoolean)),
+ };
+
+ self.input = &self.input[s.len()..];
+
+ res
+ }
+
+ /// Parse a positive or negative number.
+ // TODO support 0x or 0b notation?
+ fn parse_number<T>(&mut self) -> Result<T>
+ where
+ T: FromStr<Err = ParseIntError>,
+ {
+ let num_str = self.peek_value()?;
+ let val = T::from_str(num_str).map_err(|e| {
+ self.error_here(
+ if let IntErrorKind::PosOverflow | IntErrorKind::NegOverflow = e.kind() {
+ ErrorKind::NumberOverflow
+ } else {
+ ErrorKind::ExpectedNumber
+ },
+ )
+ })?;
+ self.input = &self.input[num_str.len()..];
+ Ok(val)
+ }
+}
+
+impl<'de> de::MapAccess<'de> for KeyValueDeserializer<'de> {
+ type Error = ParseError;
+
+ fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>>
+ where
+ K: de::DeserializeSeed<'de>,
+ {
+ let has_next_identifier = self.next_identifier.is_some();
+
+ if self.peek_char().is_none() {
+ return Ok(None);
+ }
+ let val = seed.deserialize(&mut *self).map(Some)?;
+
+ // We just "deserialized" the content of `next_identifier`, so there should be no equal
+ // character in the input. We can return now.
+ if has_next_identifier {
+ return Ok(val);
+ }
+
+ match self.peek_char() {
+ // We expect an equal after an identifier.
+ Some('=') => {
+ self.skip_char();
+ Ok(val)
+ }
+ // Ok if we are parsing a boolean where an empty value means true.
+ Some(',') | None => Ok(val),
+ Some(_) => Err(self.error_here(ErrorKind::ExpectedEqual)),
+ }
+ }
+
+ fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value>
+ where
+ V: de::DeserializeSeed<'de>,
+ {
+ let val = seed.deserialize(&mut *self)?;
+
+ // We must have a comma or end of input after a value.
+ match self.next_char() {
+ Some(',') | None => Ok(val),
+ Some(_) => Err(self.error_here(ErrorKind::ExpectedComma)),
+ }
+ }
+}
+
+struct Enum<'a, 'de: 'a>(&'a mut KeyValueDeserializer<'de>);
+
+impl<'a, 'de> Enum<'a, 'de> {
+ fn new(de: &'a mut KeyValueDeserializer<'de>) -> Self {
+ Self(de)
+ }
+}
+
+impl<'a, 'de> de::EnumAccess<'de> for Enum<'a, 'de> {
+ type Error = ParseError;
+ type Variant = Self;
+
+ fn variant_seed<V>(self, seed: V) -> Result<(V::Value, Self::Variant)>
+ where
+ V: de::DeserializeSeed<'de>,
+ {
+ let val = seed.deserialize(&mut *self.0)?;
+ Ok((val, self))
+ }
+}
+
+impl<'a, 'de> de::VariantAccess<'de> for Enum<'a, 'de> {
+ type Error = ParseError;
+
+ fn unit_variant(self) -> Result<()> {
+ Ok(())
+ }
+
+ fn newtype_variant_seed<T>(self, _seed: T) -> Result<T::Value>
+ where
+ T: de::DeserializeSeed<'de>,
+ {
+ unimplemented!()
+ }
+
+ fn tuple_variant<V>(self, _len: usize, _visitor: V) -> Result<V::Value>
+ where
+ V: de::Visitor<'de>,
+ {
+ unimplemented!()
+ }
+
+ fn struct_variant<V>(self, _fields: &'static [&'static str], _visitor: V) -> Result<V::Value>
+ where
+ V: de::Visitor<'de>,
+ {
+ unimplemented!()
+ }
+}
+
+impl<'de, 'a> de::Deserializer<'de> for &'a mut KeyValueDeserializer<'de> {
+ type Error = ParseError;
+
+ fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ match self.peek_char() {
+ Some('0'..='9') => self.deserialize_u64(visitor),
+ Some('-') => self.deserialize_i64(visitor),
+ Some('"') => self.deserialize_string(visitor),
+ // Only possible option here is boolean flag.
+ Some(',') | None => self.deserialize_bool(visitor),
+ _ => {
+ // We probably have an unquoted string, but possibly a boolean as well.
+ match self.peek_identifier() {
+ Ok("true") | Ok("false") => self.deserialize_bool(visitor),
+ _ => self.deserialize_str(visitor),
+ }
+ }
+ }
+ }
+
+ fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ visitor.visit_bool(self.parse_bool()?)
+ }
+
+ fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ visitor.visit_i8(self.parse_number()?)
+ }
+
+ fn deserialize_i16<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ visitor.visit_i16(self.parse_number()?)
+ }
+
+ fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ visitor.visit_i32(self.parse_number()?)
+ }
+
+ fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ visitor.visit_i64(self.parse_number()?)
+ }
+
+ fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ visitor.visit_u8(self.parse_number()?)
+ }
+
+ fn deserialize_u16<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ visitor.visit_u16(self.parse_number()?)
+ }
+
+ fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ visitor.visit_u32(self.parse_number()?)
+ }
+
+ fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ visitor.visit_u64(self.parse_number()?)
+ }
+
+ fn deserialize_f32<V>(self, _visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ unimplemented!()
+ }
+
+ fn deserialize_f64<V>(self, _visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ unimplemented!()
+ }
+
+ fn deserialize_char<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ visitor.visit_char(
+ self.next_char()
+ .ok_or_else(|| self.error_here(ErrorKind::Eof))?,
+ )
+ }
+
+ fn deserialize_str<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ match self.parse_string()? {
+ Cow::Borrowed(s) => visitor.visit_borrowed_str(s),
+ Cow::Owned(s) => visitor.visit_string(s),
+ }
+ }
+
+ fn deserialize_string<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ self.deserialize_str(visitor)
+ }
+
+ fn deserialize_bytes<V>(self, _visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ unimplemented!()
+ }
+
+ fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ self.deserialize_bytes(visitor)
+ }
+
+ fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ // The fact that an option is specified implies that is exists, hence we always visit
+ // Some() here.
+ visitor.visit_some(self)
+ }
+
+ fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ visitor.visit_unit()
+ }
+
+ fn deserialize_unit_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ self.deserialize_unit(visitor)
+ }
+
+ fn deserialize_newtype_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ visitor.visit_newtype_struct(self)
+ }
+
+ fn deserialize_seq<V>(self, _visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ unimplemented!()
+ }
+
+ fn deserialize_tuple<V>(self, _len: usize, _visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ unimplemented!()
+ }
+
+ fn deserialize_tuple_struct<V>(
+ self,
+ _name: &'static str,
+ _len: usize,
+ _visitor: V,
+ ) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ unimplemented!()
+ }
+
+ fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ visitor.visit_map(self)
+ }
+
+ fn deserialize_struct<V>(
+ self,
+ _name: &'static str,
+ fields: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ // The name of the first field of a struct can be omitted (see documentation of
+ // `next_identifier` for details).
+ //
+ // To detect this, peek the next identifier, and check if the character following is '='. If
+ // it is not, then we may have a value in first position, unless the value is identical to
+ // one of the field's name - in this case, assume this is a boolean using the flag syntax.
+ self.next_identifier = match self.peek_identifier() {
+ Ok(s) => match self.input.chars().nth(s.chars().count()) {
+ Some('=') => None,
+ _ => {
+ if fields.contains(&s) {
+ None
+ } else {
+ fields.get(0).copied()
+ }
+ }
+ },
+ // Not an identifier, probably means this is a value for the first field then.
+ Err(_) => fields.get(0).copied(),
+ };
+ visitor.visit_map(self)
+ }
+
+ fn deserialize_enum<V>(
+ self,
+ _name: &'static str,
+ _variants: &'static [&'static str],
+ visitor: V,
+ ) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ visitor.visit_enum(Enum::new(self))
+ }
+
+ fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ let identifier = self
+ .next_identifier
+ .take()
+ .map_or_else(|| self.parse_identifier(), Ok)?;
+
+ visitor.visit_borrowed_str(identifier)
+ }
+
+ fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ self.deserialize_any(visitor)
+ }
+}
+
+/// Attempts to deserialize `T` from the key-values string `input`.
+pub fn from_key_values<'a, T>(input: &'a str) -> Result<T>
+where
+ T: Deserialize<'a>,
+{
+ let mut deserializer = KeyValueDeserializer::from(input);
+ let t = T::deserialize(&mut deserializer)?;
+ if deserializer.input.is_empty() {
+ Ok(t)
+ } else {
+ Err(deserializer.error_here(ErrorKind::TrailingCharacters))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::path::PathBuf;
+
+ use super::*;
+
+ #[derive(Deserialize, PartialEq, Debug)]
+ struct SingleStruct<T> {
+ m: T,
+ }
+
+ #[test]
+ fn deserialize_number() {
+ let res = from_key_values::<SingleStruct<usize>>("m=54").unwrap();
+ assert_eq!(res.m, 54);
+
+ let res = from_key_values::<SingleStruct<isize>>("m=-54").unwrap();
+ assert_eq!(res.m, -54);
+
+ // Parsing a signed into an unsigned?
+ let res = from_key_values::<SingleStruct<u32>>("m=-54").unwrap_err();
+ assert_eq!(
+ res,
+ ParseError {
+ kind: ErrorKind::ExpectedNumber,
+ pos: 2
+ }
+ );
+
+ // Value too big for a signed?
+ let val = i32::MAX as u32 + 1;
+ let res = from_key_values::<SingleStruct<i32>>(&format!("m={}", val)).unwrap_err();
+ assert_eq!(
+ res,
+ ParseError {
+ kind: ErrorKind::NumberOverflow,
+ pos: 2
+ }
+ );
+
+ let res = from_key_values::<SingleStruct<usize>>("m=test").unwrap_err();
+ assert_eq!(
+ res,
+ ParseError {
+ kind: ErrorKind::ExpectedNumber,
+ pos: 2,
+ }
+ );
+ }
+
+ #[test]
+ fn deserialize_string() {
+ let kv = "m=John";
+ let res = from_key_values::<SingleStruct<String>>(kv).unwrap();
+ assert_eq!(res.m, "John".to_string());
+
+ // Spaces are valid (but not recommended) in unquoted strings.
+ let kv = "m=John Doe";
+ let res = from_key_values::<SingleStruct<String>>(kv).unwrap();
+ assert_eq!(res.m, "John Doe".to_string());
+
+ // Empty string is not valid if unquoted
+ let kv = "m=";
+ let err = from_key_values::<SingleStruct<String>>(kv).unwrap_err();
+ assert_eq!(
+ err,
+ ParseError {
+ kind: ErrorKind::ExpectedString,
+ pos: 2
+ }
+ );
+
+ // Quoted strings.
+ let kv = r#"m="John Doe""#;
+ let res = from_key_values::<SingleStruct<String>>(kv).unwrap();
+ assert_eq!(res.m, "John Doe".to_string());
+ let kv = r#"m='John Doe'"#;
+ let res = from_key_values::<SingleStruct<String>>(kv).unwrap();
+ assert_eq!(res.m, "John Doe".to_string());
+
+ // Empty quoted strings.
+ let kv = r#"m="""#;
+ let res = from_key_values::<SingleStruct<String>>(kv).unwrap();
+ assert_eq!(res.m, "".to_string());
+ let kv = r#"m=''"#;
+ let res = from_key_values::<SingleStruct<String>>(kv).unwrap();
+ assert_eq!(res.m, "".to_string());
+
+ // "=", "," and "'"" in quote.
+ let kv = r#"m="val = [10, 20, 'a']""#;
+ let res = from_key_values::<SingleStruct<String>>(kv).unwrap();
+ assert_eq!(res.m, r#"val = [10, 20, 'a']"#.to_string());
+
+ // Quotes in unquoted strings are forbidden.
+ let kv = r#"m=val="a""#;
+ let err = from_key_values::<SingleStruct<String>>(kv).unwrap_err();
+ assert_eq!(
+ err,
+ ParseError {
+ kind: ErrorKind::InvalidCharInString,
+ pos: 6
+ }
+ );
+ let kv = r#"m=val='a'"#;
+ let err = from_key_values::<SingleStruct<String>>(kv).unwrap_err();
+ assert_eq!(
+ err,
+ ParseError {
+ kind: ErrorKind::InvalidCharInString,
+ pos: 6
+ }
+ );
+
+ // Numbers and booleans are technically valid strings.
+ let kv = "m=10";
+ let res = from_key_values::<SingleStruct<String>>(kv).unwrap();
+ assert_eq!(res.m, "10".to_string());
+ let kv = "m=false";
+ let res = from_key_values::<SingleStruct<String>>(kv).unwrap();
+ assert_eq!(res.m, "false".to_string());
+
+ // Escaped quote.
+ let kv = r#"m="Escaped \" quote""#;
+ let res = from_key_values::<SingleStruct<String>>(kv).unwrap();
+ assert_eq!(res.m, r#"Escaped " quote"#.to_string());
+
+ // Escaped slash at end of string.
+ let kv = r#"m="Escaped slash\\""#;
+ let res = from_key_values::<SingleStruct<String>>(kv).unwrap();
+ assert_eq!(res.m, r#"Escaped slash\"#.to_string());
+ }
+
+ #[test]
+ fn deserialize_bool() {
+ let res = from_key_values::<SingleStruct<bool>>("m=true").unwrap();
+ assert_eq!(res.m, true);
+
+ let res = from_key_values::<SingleStruct<bool>>("m=false").unwrap();
+ assert_eq!(res.m, false);
+
+ let res = from_key_values::<SingleStruct<bool>>("m").unwrap();
+ assert_eq!(res.m, true);
+
+ let res = from_key_values::<SingleStruct<bool>>("m=10").unwrap_err();
+ assert_eq!(
+ res,
+ ParseError {
+ kind: ErrorKind::ExpectedBoolean,
+ pos: 2,
+ }
+ );
+ }
+
+ #[test]
+ fn deserialize_complex_struct() {
+ #[derive(Deserialize, PartialEq, Debug)]
+ struct TestStruct {
+ num: usize,
+ path: PathBuf,
+ enable: bool,
+ }
+ let kv = "num=54,path=/dev/foomatic,enable=false";
+ let res = from_key_values::<TestStruct>(kv).unwrap();
+ assert_eq!(
+ res,
+ TestStruct {
+ num: 54,
+ path: "/dev/foomatic".into(),
+ enable: false,
+ }
+ );
+
+ let kv = "enable,path=/usr/lib/libossom.so.1,num=12";
+ let res = from_key_values::<TestStruct>(kv).unwrap();
+ assert_eq!(
+ res,
+ TestStruct {
+ num: 12,
+ path: "/usr/lib/libossom.so.1".into(),
+ enable: true,
+ }
+ );
+ }
+
+ #[test]
+ fn deserialize_unknown_field() {
+ #[derive(Deserialize, PartialEq, Debug)]
+ #[serde(deny_unknown_fields)]
+ struct TestStruct {
+ num: usize,
+ path: PathBuf,
+ enable: bool,
+ }
+
+ let kv = "enable,path=/usr/lib/libossom.so.1,num=12,foo=bar";
+ assert!(from_key_values::<TestStruct>(kv).is_err());
+ }
+
+ #[test]
+ fn deserialize_option() {
+ #[derive(Deserialize, PartialEq, Debug)]
+ struct TestStruct {
+ num: u32,
+ opt: Option<u32>,
+ }
+ let kv = "num=16,opt=12";
+ let res: TestStruct = from_key_values(kv).unwrap();
+ assert_eq!(
+ res,
+ TestStruct {
+ num: 16,
+ opt: Some(12),
+ }
+ );
+
+ let kv = "num=16";
+ let res: TestStruct = from_key_values(kv).unwrap();
+ assert_eq!(res, TestStruct { num: 16, opt: None });
+
+ let kv = "";
+ assert!(from_key_values::<TestStruct>(kv).is_err());
+ }
+
+ #[test]
+ fn deserialize_enum() {
+ #[derive(Deserialize, PartialEq, Debug)]
+ enum TestEnum {
+ #[serde(rename = "first")]
+ FirstVariant,
+ #[serde(rename = "second")]
+ SecondVariant,
+ }
+ let res: TestEnum = from_key_values("first").unwrap();
+ assert_eq!(res, TestEnum::FirstVariant,);
+
+ let res: TestEnum = from_key_values("second").unwrap();
+ assert_eq!(res, TestEnum::SecondVariant,);
+
+ from_key_values::<TestEnum>("third").unwrap_err();
+ }
+
+ #[test]
+ fn deserialize_embedded_enum() {
+ #[derive(Deserialize, PartialEq, Debug)]
+ enum TestEnum {
+ #[serde(rename = "first")]
+ FirstVariant,
+ #[serde(rename = "second")]
+ SecondVariant,
+ }
+ #[derive(Deserialize, PartialEq, Debug)]
+ struct TestStruct {
+ variant: TestEnum,
+ #[serde(default)]
+ active: bool,
+ }
+ let res: TestStruct = from_key_values("variant=first").unwrap();
+ assert_eq!(
+ res,
+ TestStruct {
+ variant: TestEnum::FirstVariant,
+ active: false,
+ }
+ );
+ let res: TestStruct = from_key_values("variant=second,active=true").unwrap();
+ assert_eq!(
+ res,
+ TestStruct {
+ variant: TestEnum::SecondVariant,
+ active: true,
+ }
+ );
+ let res: TestStruct = from_key_values("active=true,variant=second").unwrap();
+ assert_eq!(
+ res,
+ TestStruct {
+ variant: TestEnum::SecondVariant,
+ active: true,
+ }
+ );
+ let res: TestStruct = from_key_values("active,variant=second").unwrap();
+ assert_eq!(
+ res,
+ TestStruct {
+ variant: TestEnum::SecondVariant,
+ active: true,
+ }
+ );
+ let res: TestStruct = from_key_values("active=false,variant=second").unwrap();
+ assert_eq!(
+ res,
+ TestStruct {
+ variant: TestEnum::SecondVariant,
+ active: false,
+ }
+ );
+ }
+
+ #[test]
+ fn deserialize_first_arg_string() {
+ #[derive(Deserialize, PartialEq, Debug)]
+ struct TestStruct {
+ name: String,
+ num: u8,
+ }
+ let res: TestStruct = from_key_values("name=foo,num=12").unwrap();
+ assert_eq!(
+ res,
+ TestStruct {
+ name: "foo".into(),
+ num: 12,
+ }
+ );
+
+ let res: TestStruct = from_key_values("foo,num=12").unwrap();
+ assert_eq!(
+ res,
+ TestStruct {
+ name: "foo".into(),
+ num: 12,
+ }
+ );
+ }
+
+ #[test]
+ fn deserialize_first_arg_int() {
+ #[derive(Deserialize, PartialEq, Debug)]
+ struct TestStruct {
+ num: u8,
+ name: String,
+ }
+ let res: TestStruct = from_key_values("name=foo,num=12").unwrap();
+ assert_eq!(
+ res,
+ TestStruct {
+ num: 12,
+ name: "foo".into(),
+ }
+ );
+
+ let res: TestStruct = from_key_values("12,name=foo").unwrap();
+ assert_eq!(
+ res,
+ TestStruct {
+ num: 12,
+ name: "foo".into(),
+ }
+ );
+ }
+}
diff --git a/serde_keyvalue/src/lib.rs b/serde_keyvalue/src/lib.rs
new file mode 100644
index 000000000..51d9d37f2
--- /dev/null
+++ b/serde_keyvalue/src/lib.rs
@@ -0,0 +1,295 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! A lightweight serde deserializer for strings containing key-value pairs separated by commas, as
+//! commonly found in command-line parameters.
+//!
+//! Say your program takes a command-line option of the form:
+//!
+//! ```text
+//! --foo type=bar,active,nb_threads=8
+//! ```
+//!
+//! This crate provides a [from_key_values] function that deserializes these key-values into a
+//! configuration structure. Since it uses serde, the same configuration structure can also be
+//! created from any other supported source (such as a TOML or YAML configuration file) that uses
+//! the same keys.
+//!
+//! Integration with the [argh](https://github.com/google/argh) command-line parser is also
+//! provided via the `argh_derive` feature.
+//!
+//! The deserializer supports parsing signed and unsigned integers, booleans, strings (quoted or
+//! not), paths, and enums inside a top-level struct. The order in which the fields appear in the
+//! string is not important.
+//!
+//! Simple example:
+//!
+//! ```
+//! use serde_keyvalue::from_key_values;
+//! use serde::Deserialize;
+//!
+//! #[derive(Debug, PartialEq, Deserialize)]
+//! struct Config {
+//! path: String,
+//! threads: u8,
+//! active: bool,
+//! }
+//!
+//! let config: Config = from_key_values("path=/some/path,threads=16,active=true").unwrap();
+//! assert_eq!(config, Config { path: "/some/path".into(), threads: 16, active: true });
+//!
+//! let config: Config = from_key_values("threads=16,active=true,path=/some/path").unwrap();
+//! assert_eq!(config, Config { path: "/some/path".into(), threads: 16, active: true });
+//! ```
+//!
+//! As a convenience the name of the first field of a struct can be omitted:
+//!
+//! ```
+//! # use serde_keyvalue::from_key_values;
+//! # use serde::Deserialize;
+//! #[derive(Debug, PartialEq, Deserialize)]
+//! struct Config {
+//! path: String,
+//! threads: u8,
+//! active: bool,
+//! }
+//!
+//! let config: Config = from_key_values("/some/path,threads=16,active=true").unwrap();
+//! assert_eq!(config, Config { path: "/some/path".into(), threads: 16, active: true });
+//! ```
+//!
+//! Fields that are behind an `Option` can be omitted, in which case they will be `None`.
+//!
+//! ```
+//! # use serde_keyvalue::from_key_values;
+//! # use serde::Deserialize;
+//! #[derive(Debug, PartialEq, Deserialize)]
+//! struct Config {
+//! path: Option<String>,
+//! threads: u8,
+//! active: bool,
+//! }
+//!
+//! let config: Config = from_key_values("path=/some/path,threads=16,active=true").unwrap();
+//! assert_eq!(config, Config { path: Some("/some/path".into()), threads: 16, active: true });
+//!
+//! let config: Config = from_key_values("threads=16,active=true").unwrap();
+//! assert_eq!(config, Config { path: None, threads: 16, active: true });
+//! ```
+//!
+//! Alternatively, the serde `default` attribute can be used on select fields or on the whole
+//! struct to make unspecified fields be assigned their default value. In the following example only
+//! the `path` parameter must be specified.
+//!
+//! ```
+//! # use serde_keyvalue::from_key_values;
+//! # use serde::Deserialize;
+//! #[derive(Debug, PartialEq, Deserialize)]
+//! struct Config {
+//! path: String,
+//! #[serde(default)]
+//! threads: u8,
+//! #[serde(default)]
+//! active: bool,
+//! }
+//!
+//! let config: Config = from_key_values("path=/some/path").unwrap();
+//! assert_eq!(config, Config { path: "/some/path".into(), threads: 0, active: false });
+//! ```
+//!
+//! A function providing a default value can also be specified, see the [serde documentation for
+//! field attributes](https://serde.rs/field-attrs.html) for details.
+//!
+//! Booleans can be `true` or `false`, or take no value at all, in which case they will be `true`.
+//! Combined with default values this allows to implement flags very easily:
+//!
+//! ```
+//! # use serde_keyvalue::from_key_values;
+//! # use serde::Deserialize;
+//! #[derive(Debug, Default, PartialEq, Deserialize)]
+//! #[serde(default)]
+//! struct Config {
+//! active: bool,
+//! delayed: bool,
+//! pooled: bool,
+//! }
+//!
+//! let config: Config = from_key_values("active=true,delayed=false,pooled=true").unwrap();
+//! assert_eq!(config, Config { active: true, delayed: false, pooled: true });
+//!
+//! let config: Config = from_key_values("active,pooled").unwrap();
+//! assert_eq!(config, Config { active: true, delayed: false, pooled: true });
+//! ```
+//!
+//! Strings can be quoted, which is useful if they e.g. need to include a comma. Quoted strings can
+//! also contain escaped characters, where any character after a `\` is repeated as-is:
+//!
+//! ```
+//! # use serde_keyvalue::from_key_values;
+//! # use serde::Deserialize;
+//! #[derive(Debug, PartialEq, Deserialize)]
+//! struct Config {
+//! path: String,
+//! }
+//!
+//! let config: Config = from_key_values(r#"path="/some/\"strange\"/pa,th""#).unwrap();
+//! assert_eq!(config, Config { path: r#"/some/"strange"/pa,th"#.into() });
+//! ```
+//!
+//! Enums can be directly specified by name. It is recommended to use the `rename_all` serde
+//! container attribute to make them parseable using snake or kebab case representation. Serde's
+//! `rename` and `alias` field attributes can also be used to provide shorter values:
+//!
+//! ```
+//! # use serde_keyvalue::from_key_values;
+//! # use serde::Deserialize;
+//! #[derive(Debug, PartialEq, Deserialize)]
+//! #[serde(rename_all="kebab-case")]
+//! enum Mode {
+//! Slow,
+//! Fast,
+//! #[serde(rename="ludicrous")]
+//! LudicrousSpeed,
+//! }
+//!
+//! #[derive(Deserialize, PartialEq, Debug)]
+//! struct Config {
+//! mode: Mode,
+//! }
+//!
+//! let config: Config = from_key_values("mode=slow").unwrap();
+//! assert_eq!(config, Config { mode: Mode::Slow });
+//!
+//! let config: Config = from_key_values("mode=ludicrous").unwrap();
+//! assert_eq!(config, Config { mode: Mode::LudicrousSpeed });
+//! ```
+//!
+//! Enums taking a single value should use the `flatten` field attribute in order to be inferred
+//! from their variant key directly:
+//!
+//! ```
+//! # use serde_keyvalue::from_key_values;
+//! # use serde::Deserialize;
+//! #[derive(Debug, PartialEq, Deserialize)]
+//! #[serde(rename_all="kebab-case")]
+//! enum Mode {
+//! // Work with a local file.
+//! File(String),
+//! // Work with a remote URL.
+//! Url(String),
+//! }
+//!
+//! #[derive(Deserialize, PartialEq, Debug)]
+//! struct Config {
+//! #[serde(flatten)]
+//! mode: Mode,
+//! }
+//!
+//! let config: Config = from_key_values("file=/some/path").unwrap();
+//! assert_eq!(config, Config { mode: Mode::File("/some/path".into()) });
+//!
+//! let config: Config = from_key_values("url=https://www.google.com").unwrap();
+//! assert_eq!(config, Config { mode: Mode::Url("https://www.google.com".into()) });
+//! ```
+//!
+//! The `flatten` attribute can also be used to embed one struct within another one and parse both
+//! from the same string:
+//!
+//! ```
+//! # use serde_keyvalue::from_key_values;
+//! # use serde::Deserialize;
+//! #[derive(Debug, PartialEq, Deserialize)]
+//! struct BaseConfig {
+//! enabled: bool,
+//! num_threads: u8,
+//! }
+//!
+//! #[derive(Debug, PartialEq, Deserialize)]
+//! struct Config {
+//! #[serde(flatten)]
+//! base: BaseConfig,
+//! path: String,
+//! }
+//!
+//! let config: Config = from_key_values("path=/some/path,enabled,num_threads=16").unwrap();
+//! assert_eq!(
+//! config,
+//! Config {
+//! path: "/some/path".into(),
+//! base: BaseConfig {
+//! num_threads: 16,
+//! enabled: true,
+//! }
+//! }
+//! );
+//! ```
+//!
+//! If an enum's variants are made of structs, it should take the `untagged` container attribute so
+//! it can be inferred directly from the fields of the embedded structs:
+//!
+//! ```
+//! # use serde_keyvalue::from_key_values;
+//! # use serde::Deserialize;
+//! #[derive(Debug, PartialEq, Deserialize)]
+//! #[serde(untagged)]
+//! enum Mode {
+//! // Work with a local file.
+//! File {
+//! path: String,
+//! #[serde(default)]
+//! read_only: bool,
+//! },
+//! // Work with a remote URL.
+//! Remote {
+//! server: String,
+//! port: u16,
+//! }
+//! }
+//!
+//! #[derive(Debug, PartialEq, Deserialize)]
+//! struct Config {
+//! #[serde(flatten)]
+//! mode: Mode,
+//! }
+//!
+//! let config: Config = from_key_values("path=/some/path").unwrap();
+//! assert_eq!(config, Config { mode: Mode::File { path: "/some/path".into(), read_only: false } });
+//!
+//! let config: Config = from_key_values("server=google.com,port=80").unwrap();
+//! assert_eq!(config, Config { mode: Mode::Remote { server: "google.com".into(), port: 80 } });
+//! ```
+//!
+//! Using this crate, parsing errors and invalid or missing fields are precisely reported:
+//!
+//! ```
+//! # use serde_keyvalue::from_key_values;
+//! # use serde::Deserialize;
+//! #[derive(Debug, PartialEq, Deserialize)]
+//! struct Config {
+//! path: String,
+//! threads: u8,
+//! active: bool,
+//! }
+//!
+//! let config = from_key_values::<Config>("path=/some/path,active=true").unwrap_err();
+//! assert_eq!(format!("{}", config), "missing field `threads`");
+//! ```
+//!
+//! Most of the serde [container](https://serde.rs/container-attrs.html) and
+//! [field](https://serde.rs/field-attrs.html) attributes can be applied to your configuration
+//! struct. Most useful ones include
+//! [`deny_unknown_fields`](https://serde.rs/container-attrs.html#deny_unknown_fields) to report an
+//! error if an unknown field is met in the input, and
+//! [`deserialize_with`](https://serde.rs/field-attrs.html#deserialize_with) to use a custom
+//! deserialization function for a specific field.
+#![deny(missing_docs)]
+
+mod key_values;
+
+pub use key_values::{from_key_values, ErrorKind, ParseError};
+
+#[cfg(feature = "argh_derive")]
+pub use argh;
+#[cfg(feature = "argh_derive")]
+pub use serde_keyvalue_derive::FromKeyValues;
diff --git a/ci/vm_tools/wait_for_vm_with_timeout b/setup_cros_cargo.sh
index 7502177d1..589fcee4a 100755
--- a/ci/vm_tools/wait_for_vm_with_timeout
+++ b/setup_cros_cargo.sh
@@ -2,9 +2,5 @@
# Copyright 2021 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-
-if ! timeout --foreground 180s ${0%/*}/wait_for_vm; then
- echo ""
- echo "Timeout while waiting for VM (See 'screen -r vm' or vm.log in sponge)"
- exit 1
-fi
+echo "./setup_cros_cargo.sh is deprecated. " \
+ "Please use: ./tools/chromeos/setup_cargo"
diff --git a/src/argument.rs b/src/argument.rs
index 64be18227..a825822dc 100644
--- a/src/argument.rs
+++ b/src/argument.rs
@@ -43,47 +43,44 @@
//! }
//! ```
-use std::fmt::{self, Display};
+use std::convert::TryFrom;
use std::result;
+use std::str::FromStr;
+
+use remain::sorted;
+use thiserror::Error;
/// An error with argument parsing.
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- /// There was a syntax error with the argument.
- Syntax(String),
- /// The argument's name is unused.
- UnknownArgument(String),
+ /// Free error for use with the `serde_keyvalue` crate parser.
+ #[error("failed to parse key-value arguments: {0}")]
+ ConfigParserError(String),
/// The argument was required.
+ #[error("expected argument: {0}")]
ExpectedArgument(String),
+ /// The argument expects a value.
+ #[error("expected parameter value: {0}")]
+ ExpectedValue(String),
/// The argument's given value is invalid.
+ #[error("invalid value {value:?}: {expected}")]
InvalidValue { value: String, expected: String },
+ /// The help information was requested
+ #[error("help was requested")]
+ PrintHelp,
+ /// There was a syntax error with the argument.
+ #[error("syntax error: {0}")]
+ Syntax(String),
/// The argument was already given and none more are expected.
+ #[error("too many arguments: {0}")]
TooManyArguments(String),
- /// The argument expects a value.
- ExpectedValue(String),
/// The argument does not expect a value.
+ #[error("unexpected parameter value: {0}")]
UnexpectedValue(String),
- /// The help information was requested
- PrintHelp,
-}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- Syntax(s) => write!(f, "syntax error: {}", s),
- UnknownArgument(s) => write!(f, "unknown argument: {}", s),
- ExpectedArgument(s) => write!(f, "expected argument: {}", s),
- InvalidValue { value, expected } => {
- write!(f, "invalid value {:?}: {}", value, expected)
- }
- TooManyArguments(s) => write!(f, "too many arguments: {}", s),
- ExpectedValue(s) => write!(f, "expected parameter value: {}", s),
- UnexpectedValue(s) => write!(f, "unexpected parameter value: {}", s),
- PrintHelp => write!(f, "help was requested"),
- }
- }
+ /// The argument's name is unused.
+ #[error("unknown argument: {0}")]
+ UnknownArgument(String),
}
/// Result of a argument parsing.
@@ -283,19 +280,19 @@ where
State::Top
}
} else {
- f("", Some(&arg))?;
+ f("", Some(arg))?;
State::Positional
}
}
State::Positional => {
- f("", Some(&arg))?;
+ f("", Some(arg))?;
State::Positional
}
State::Value { name } => {
if arg.starts_with('-') {
arg_consumed = false;
f(&name, None)?;
- } else if let Err(e) = f(&name, Some(&arg)) {
+ } else if let Err(e) = f(&name, Some(arg)) {
arg_consumed = false;
f(&name, None).map_err(|_| e)?;
}
@@ -401,6 +398,166 @@ pub fn print_help(program_name: &str, required_arg: &str, args: &[Argument]) {
}
}
+pub fn parse_hex_or_decimal(maybe_hex_string: &str) -> Result<u64> {
+ // Parse string starting with 0x as hex and others as numbers.
+ if let Some(hex_string) = maybe_hex_string.strip_prefix("0x") {
+ u64::from_str_radix(hex_string, 16)
+ } else if let Some(hex_string) = maybe_hex_string.strip_prefix("0X") {
+ u64::from_str_radix(hex_string, 16)
+ } else {
+ u64::from_str(maybe_hex_string)
+ }
+ .map_err(|e| Error::InvalidValue {
+ value: maybe_hex_string.to_string(),
+ expected: e.to_string(),
+ })
+}
+
+pub struct KeyValuePair<'a> {
+ context: &'a str,
+ key: &'a str,
+ value: Option<&'a str>,
+}
+
+impl<'a> KeyValuePair<'a> {
+ fn handle_parse_err<T, E: std::error::Error>(
+ &self,
+ result: std::result::Result<T, E>,
+ ) -> Result<T> {
+ result.map_err(|e| {
+ self.invalid_value_err(format!(
+ "Failed to parse parameter `{}` as {}: {}",
+ self.key,
+ std::any::type_name::<T>(),
+ e
+ ))
+ })
+ }
+
+ pub fn key(&self) -> &'a str {
+ self.key
+ }
+
+ pub fn value(&self) -> Result<&'a str> {
+ self.value.ok_or(Error::ExpectedValue(format!(
+ "{}: parameter `{}` requires a value",
+ self.context, self.key
+ )))
+ }
+
+ fn get_numeric<T>(&self, val: &str) -> Result<T>
+ where
+ T: TryFrom<u64>,
+ <T as TryFrom<u64>>::Error: std::error::Error,
+ {
+ let num = parse_hex_or_decimal(val)?;
+ self.handle_parse_err(T::try_from(num))
+ }
+
+ pub fn parse_numeric<T>(&self) -> Result<T>
+ where
+ T: TryFrom<u64>,
+ <T as TryFrom<u64>>::Error: std::error::Error,
+ {
+ let val = self.value()?;
+ self.get_numeric(val)
+ }
+
+ pub fn key_numeric<T>(&self) -> Result<T>
+ where
+ T: TryFrom<u64>,
+ <T as TryFrom<u64>>::Error: std::error::Error,
+ {
+ self.get_numeric(self.key())
+ }
+
+ pub fn parse<T>(&self) -> Result<T>
+ where
+ T: FromStr,
+ <T as FromStr>::Err: std::error::Error,
+ {
+ self.handle_parse_err(T::from_str(self.value()?))
+ }
+
+ pub fn parse_or<T>(&self, default: T) -> Result<T>
+ where
+ T: FromStr,
+ <T as FromStr>::Err: std::error::Error,
+ {
+ match self.value {
+ Some(v) => self.handle_parse_err(T::from_str(v)),
+ None => Ok(default),
+ }
+ }
+
+ pub fn invalid_key_err(&self) -> Error {
+ Error::UnknownArgument(format!(
+ "{}: Unknown parameter `{}`",
+ self.context, self.key
+ ))
+ }
+
+ pub fn invalid_value_err(&self, description: String) -> Error {
+ Error::InvalidValue {
+ value: self
+ .value
+ .expect("invalid value error without value")
+ .to_string(),
+ expected: format!("{}: {}", self.context, description),
+ }
+ }
+}
+
+/// Parse a string of delimiter-separated key-value options. This is intended to simplify parsing
+/// of command-line options that take a bunch of parameters encoded into the argument, e.g. for
+/// setting up an emulated hardware device. Returns an Iterator of KeyValuePair, which provides
+/// convenience functions to parse numeric values and performs appropriate error handling.
+///
+/// `flagname` - name of the command line parameter, used as context in error messages
+/// `s` - the string to parse
+/// `delimiter` - the character that separates individual pairs
+///
+/// Usage example:
+/// ```
+/// # use crosvm::argument::{Result, parse_key_value_options};
+///
+/// fn parse_turbo_button_parameters(s: &str) -> Result<(String, u32, bool)> {
+/// let mut color = String::new();
+/// let mut speed = 0u32;
+/// let mut turbo = false;
+///
+/// for opt in parse_key_value_options("turbo-button", s, ',') {
+/// match opt.key() {
+/// "color" => color = opt.value()?.to_string(),
+/// "speed" => speed = opt.parse_numeric::<u32>()?,
+/// "turbo" => turbo = opt.parse_or::<bool>(true)?,
+/// _ => return Err(opt.invalid_key_err()),
+/// }
+/// }
+///
+/// Ok((color, speed, turbo))
+/// }
+///
+/// assert_eq!(parse_turbo_button_parameters("color=red,speed=0xff,turbo").unwrap(),
+/// ("red".to_string(), 0xff, true))
+/// ```
+///
+/// TODO: upgrade `delimiter` to generic Pattern support once that has been stabilized
+/// at <https://github.com/rust-lang/rust/issues/27721>.
+pub fn parse_key_value_options<'a>(
+ flagname: &'a str,
+ s: &'a str,
+ delimiter: char,
+) -> impl Iterator<Item = KeyValuePair<'a>> {
+ s.split(delimiter)
+ .map(|frag| frag.splitn(2, '='))
+ .map(move |mut kv| KeyValuePair {
+ context: flagname,
+ key: kv.next().unwrap_or(""),
+ value: kv.next(),
+ })
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -409,11 +566,9 @@ mod tests {
fn request_help() {
let arguments = [Argument::short_flag('h', "help", "Print help message.")];
- let match_res = set_arguments(["-h"].iter(), &arguments[..], |name, _| {
- match name {
- "help" => return Err(Error::PrintHelp),
- _ => unreachable!(),
- };
+ let match_res = set_arguments(["-h"].iter(), &arguments[..], |name, _| match name {
+ "help" => Err(Error::PrintHelp),
+ _ => unreachable!(),
});
match match_res {
Err(Error::PrintHelp) => {}
@@ -546,4 +701,60 @@ mod tests {
"2D"
);
}
+
+ #[test]
+ fn parse_key_value_options_simple() {
+ let mut opts = parse_key_value_options("test", "fruit=apple,number=13,flag,hex=0x123", ',');
+
+ let kv1 = opts.next().unwrap();
+ assert_eq!(kv1.key(), "fruit");
+ assert_eq!(kv1.value().unwrap(), "apple");
+
+ let kv2 = opts.next().unwrap();
+ assert_eq!(kv2.key(), "number");
+ assert_eq!(kv2.parse::<u32>().unwrap(), 13);
+
+ let kv3 = opts.next().unwrap();
+ assert_eq!(kv3.key(), "flag");
+ assert!(kv3.value().is_err());
+ assert!(kv3.parse_or::<bool>(true).unwrap());
+
+ let kv4 = opts.next().unwrap();
+ assert_eq!(kv4.key(), "hex");
+ assert_eq!(kv4.parse_numeric::<u32>().unwrap(), 0x123);
+
+ assert!(opts.next().is_none());
+ }
+
+ #[test]
+ fn parse_key_value_options_overflow() {
+ let mut opts = parse_key_value_options("test", "key=1000000000000000", ',');
+ let kv = opts.next().unwrap();
+ assert!(kv.parse::<u32>().is_err());
+ assert!(kv.parse_numeric::<u32>().is_err());
+ }
+
+ #[test]
+ fn parse_hex_or_decimal_simple() {
+ assert_eq!(parse_hex_or_decimal("15").unwrap(), 15);
+ assert_eq!(parse_hex_or_decimal("0x15").unwrap(), 0x15);
+ assert_eq!(parse_hex_or_decimal("0X15").unwrap(), 0x15);
+ assert!(parse_hex_or_decimal("0xz").is_err());
+ assert!(parse_hex_or_decimal("hello world").is_err());
+ }
+
+ #[test]
+ fn parse_key_value_options_numeric_key() {
+ let mut opts = parse_key_value_options("test", "0x30,0x100=value,nonnumeric=value", ',');
+ let kv = opts.next().unwrap();
+ assert_eq!(kv.key_numeric::<u32>().unwrap(), 0x30);
+
+ let kv = opts.next().unwrap();
+ assert_eq!(kv.key_numeric::<u32>().unwrap(), 0x100);
+ assert_eq!(kv.value().unwrap(), "value");
+
+ let kv = opts.next().unwrap();
+ assert!(kv.key_numeric::<u32>().is_err());
+ assert_eq!(kv.key(), "nonnumeric");
+ }
}
diff --git a/src/crosvm.rs b/src/crosvm.rs
index 7a4318ab3..c79ef0115 100644
--- a/src/crosvm.rs
+++ b/src/crosvm.rs
@@ -8,29 +8,42 @@
pub mod argument;
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
pub mod gdb;
-#[path = "linux.rs"]
+#[path = "linux/mod.rs"]
pub mod platform;
#[cfg(feature = "plugin")]
pub mod plugin;
-use std::collections::BTreeMap;
+use std::collections::{BTreeMap, BTreeSet};
use std::net;
+use std::ops::RangeInclusive;
use std::os::unix::io::RawFd;
use std::path::{Path, PathBuf};
use std::str::FromStr;
-use arch::{Pstore, SerialHardware, SerialParameters, VcpuAffinity};
+use arch::{Pstore, VcpuAffinity};
+use devices::serial_device::{SerialHardware, SerialParameters};
+use devices::virtio::block::block::DiskOption;
+#[cfg(feature = "audio_cras")]
+use devices::virtio::cras_backend::Parameters as CrasSndParameters;
use devices::virtio::fs::passthrough;
#[cfg(feature = "gpu")]
use devices::virtio::gpu::GpuParameters;
+use devices::virtio::vhost::vsock::VhostVsockDeviceParameter;
+#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
+use devices::virtio::VideoBackendType;
#[cfg(feature = "audio")]
use devices::Ac97Parameters;
-use devices::ProtectionType;
+#[cfg(feature = "direct")]
+use devices::BusRange;
+use devices::{IommuDevType, PciAddress, StubPciParameters};
+use hypervisor::ProtectionType;
use libc::{getegid, geteuid};
+#[cfg(feature = "gpu")]
+use platform::GpuRenderServerParameters;
+use uuid::Uuid;
use vm_control::BatteryType;
static KVM_PATH: &str = "/dev/kvm";
-static VHOST_VSOCK_PATH: &str = "/dev/vhost-vsock";
static VHOST_NET_PATH: &str = "/dev/vhost-net";
static SECCOMP_POLICY_DIR: &str = "/usr/share/policy/crosvm";
@@ -45,19 +58,6 @@ pub enum Executable {
Plugin(PathBuf),
}
-/// Maximum length of a `DiskOption` identifier.
-///
-/// This is based on the virtio-block ID length limit.
-pub const DISK_ID_LEN: usize = 20;
-
-pub struct DiskOption {
- pub path: PathBuf,
- pub read_only: bool,
- pub sparse: bool,
- pub block_size: u32,
- pub id: Option<[u8; DISK_ID_LEN]>,
-}
-
pub struct VhostUserOption {
pub socket: PathBuf,
}
@@ -67,6 +67,18 @@ pub struct VhostUserFsOption {
pub tag: String,
}
+pub struct VhostUserWlOption {
+ pub socket: PathBuf,
+ pub vm_tube: PathBuf,
+}
+
+/// Options for virtio-vhost-user proxy device.
+pub struct VvuOption {
+ pub socket: PathBuf,
+ pub addr: Option<PciAddress>,
+ pub uuid: Option<Uuid>,
+}
+
/// A bind mount for directories in the plugin process.
pub struct BindMount {
pub src: PathBuf,
@@ -83,9 +95,10 @@ pub struct GidMap {
/// Direct IO forwarding options
#[cfg(feature = "direct")]
+#[derive(Debug)]
pub struct DirectIoOption {
pub path: PathBuf,
- pub ranges: Vec<(u64, u64)>,
+ pub ranges: Vec<BusRange>,
}
pub const DEFAULT_TOUCH_DEVICE_HEIGHT: u32 = 1024;
@@ -191,23 +204,182 @@ impl Default for SharedDir {
}
}
+/// Vfio device type, recognized based on command line option.
+#[derive(Eq, PartialEq, Clone, Copy)]
+pub enum VfioType {
+ Pci,
+ Platform,
+}
+
+impl FromStr for VfioType {
+ type Err = &'static str;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ use VfioType::*;
+ match s {
+ "vfio" => Ok(Pci),
+ "vfio-platform" => Ok(Platform),
+ _ => Err("invalid vfio device type, must be 'vfio|vfio-platform'"),
+ }
+ }
+}
+
+/// VFIO device structure for creating a new instance based on command line options.
+pub struct VfioCommand {
+ vfio_path: PathBuf,
+ dev_type: VfioType,
+ params: BTreeMap<String, String>,
+}
+
+impl VfioCommand {
+ pub fn new(dev_type: VfioType, path: &str) -> argument::Result<VfioCommand> {
+ let mut param = path.split(',');
+ let vfio_path =
+ PathBuf::from(param.next().ok_or_else(|| argument::Error::InvalidValue {
+ value: path.to_owned(),
+ expected: String::from("missing vfio path"),
+ })?);
+
+ if !vfio_path.exists() {
+ return Err(argument::Error::InvalidValue {
+ value: path.to_owned(),
+ expected: String::from("the vfio path does not exist"),
+ });
+ }
+ if !vfio_path.is_dir() {
+ return Err(argument::Error::InvalidValue {
+ value: path.to_owned(),
+ expected: String::from("the vfio path should be directory"),
+ });
+ }
+
+ let mut params = BTreeMap::new();
+ for p in param {
+ let mut kv = p.splitn(2, '=');
+ if let (Some(kind), Some(value)) = (kv.next(), kv.next()) {
+ Self::validate_params(kind, value)?;
+ params.insert(kind.to_owned(), value.to_owned());
+ };
+ }
+ Ok(VfioCommand {
+ vfio_path,
+ params,
+ dev_type,
+ })
+ }
+
+ fn validate_params(kind: &str, value: &str) -> Result<(), argument::Error> {
+ match kind {
+ "guest-address" => {
+ if value.eq_ignore_ascii_case("auto") || PciAddress::from_str(value).is_ok() {
+ Ok(())
+ } else {
+ Err(argument::Error::InvalidValue {
+ value: format!("{}={}", kind.to_owned(), value.to_owned()),
+ expected: String::from(
+ "option must be `guest-address=auto|<BUS:DEVICE.FUNCTION>`",
+ ),
+ })
+ }
+ }
+ "iommu" => {
+ if IommuDevType::from_str(value).is_ok() {
+ Ok(())
+ } else {
+ Err(argument::Error::InvalidValue {
+ value: format!("{}={}", kind.to_owned(), value.to_owned()),
+ expected: String::from("option must be `iommu=viommu|coiommu|off`"),
+ })
+ }
+ }
+ _ => Err(argument::Error::InvalidValue {
+ value: format!("{}={}", kind.to_owned(), value.to_owned()),
+ expected: String::from("option must be `guest-address=<val>` and/or `iommu=<val>`"),
+ }),
+ }
+ }
+
+ pub fn get_type(&self) -> VfioType {
+ self.dev_type
+ }
+
+ pub fn guest_address(&self) -> Option<PciAddress> {
+ self.params
+ .get("guest-address")
+ .and_then(|addr| PciAddress::from_str(addr).ok())
+ }
+
+ pub fn iommu_dev_type(&self) -> IommuDevType {
+ if let Some(iommu) = self.params.get("iommu") {
+ if let Ok(v) = IommuDevType::from_str(iommu) {
+ return v;
+ }
+ }
+ IommuDevType::NoIommu
+ }
+}
+
+#[derive(Debug)]
+pub struct FileBackedMappingParameters {
+ pub address: u64,
+ pub size: u64,
+ pub path: PathBuf,
+ pub offset: u64,
+ pub writable: bool,
+ pub sync: bool,
+}
+
+#[derive(Clone)]
+pub struct HostPcieRootPortParameters {
+ pub host_path: PathBuf,
+ pub hp_gpe: Option<u32>,
+}
+
+#[derive(Debug)]
+pub struct JailConfig {
+ pub pivot_root: PathBuf,
+ pub seccomp_policy_dir: PathBuf,
+ pub seccomp_log_failures: bool,
+}
+
+impl Default for JailConfig {
+ fn default() -> Self {
+ JailConfig {
+ pivot_root: PathBuf::from(option_env!("DEFAULT_PIVOT_ROOT").unwrap_or("/var/empty")),
+ seccomp_policy_dir: PathBuf::from(SECCOMP_POLICY_DIR),
+ seccomp_log_failures: false,
+ }
+ }
+}
+
/// Aggregate of all configurable options for a running VM.
pub struct Config {
pub kvm_device_path: PathBuf,
- pub vhost_vsock_device_path: PathBuf,
+ pub vhost_vsock_device: Option<VhostVsockDeviceParameter>,
pub vhost_net_device_path: PathBuf,
pub vcpu_count: Option<usize>,
+ pub vcpu_cgroup_path: Option<PathBuf>,
pub rt_cpus: Vec<usize>,
pub vcpu_affinity: Option<VcpuAffinity>,
+ pub cpu_clusters: Vec<Vec<usize>>,
+ pub cpu_capacity: BTreeMap<usize, u32>, // CPU index -> capacity
+ pub per_vm_core_scheduling: bool,
+ #[cfg(feature = "audio_cras")]
+ pub cras_snds: Vec<CrasSndParameters>,
+ pub delay_rt: bool,
pub no_smt: bool,
pub memory: Option<u64>,
+ pub swiotlb: Option<u64>,
pub hugepages: bool,
pub memory_file: Option<PathBuf>,
pub executable_path: Option<Executable>,
pub android_fstab: Option<PathBuf>,
pub initrd_path: Option<PathBuf>,
+ pub jail_config: Option<JailConfig>,
+ pub jail_enabled: bool,
pub params: Vec<String>,
pub socket_path: Option<PathBuf>,
+ pub balloon_control: Option<PathBuf>,
pub plugin_root: Option<PathBuf>,
pub plugin_mounts: Vec<BindMount>,
pub plugin_gid_maps: Vec<GidMap>,
@@ -220,70 +392,121 @@ pub struct Config {
pub net_vq_pairs: Option<u16>,
pub vhost_net: bool,
pub tap_fd: Vec<RawFd>,
+ pub tap_name: Vec<String>,
pub cid: Option<u64>,
pub wayland_socket_paths: BTreeMap<String, PathBuf>,
- pub wayland_dmabuf: bool,
pub x_display: Option<String>,
pub shared_dirs: Vec<SharedDir>,
- pub sandbox: bool,
- pub seccomp_policy_dir: PathBuf,
- pub seccomp_log_failures: bool,
#[cfg(feature = "gpu")]
pub gpu_parameters: Option<GpuParameters>,
+ #[cfg(feature = "gpu")]
+ pub gpu_render_server_parameters: Option<GpuRenderServerParameters>,
pub software_tpm: bool,
pub display_window_keyboard: bool,
pub display_window_mouse: bool,
#[cfg(feature = "audio")]
pub ac97_parameters: Vec<Ac97Parameters>,
+ #[cfg(feature = "audio")]
+ pub sound: Option<PathBuf>,
pub serial_parameters: BTreeMap<(SerialHardware, u8), SerialParameters>,
pub syslog_tag: Option<String>,
- pub virtio_single_touch: Option<TouchDeviceOption>,
- pub virtio_multi_touch: Option<TouchDeviceOption>,
- pub virtio_trackpad: Option<TouchDeviceOption>,
- pub virtio_mouse: Option<PathBuf>,
- pub virtio_keyboard: Option<PathBuf>,
- pub virtio_switches: Option<PathBuf>,
+ pub usb: bool,
+ pub virtio_single_touch: Vec<TouchDeviceOption>,
+ pub virtio_multi_touch: Vec<TouchDeviceOption>,
+ pub virtio_trackpad: Vec<TouchDeviceOption>,
+ pub virtio_mice: Vec<PathBuf>,
+ pub virtio_keyboard: Vec<PathBuf>,
+ pub virtio_switches: Vec<PathBuf>,
pub virtio_input_evdevs: Vec<PathBuf>,
+ pub virtio_iommu: bool,
pub split_irqchip: bool,
- pub vfio: Vec<PathBuf>,
- pub video_dec: bool,
- pub video_enc: bool,
+ pub vfio: Vec<VfioCommand>,
+ #[cfg(feature = "video-decoder")]
+ pub video_dec: Option<VideoBackendType>,
+ #[cfg(feature = "video-encoder")]
+ pub video_enc: Option<VideoBackendType>,
pub acpi_tables: Vec<PathBuf>,
pub protected_vm: ProtectionType,
pub battery_type: Option<BatteryType>,
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
pub gdb: Option<u32>,
+ pub balloon: bool,
pub balloon_bias: i64,
pub vhost_user_blk: Vec<VhostUserOption>,
+ pub vhost_user_console: Vec<VhostUserOption>,
pub vhost_user_fs: Vec<VhostUserFsOption>,
+ pub vhost_user_gpu: Vec<VhostUserOption>,
+ pub vhost_user_mac80211_hwsim: Option<VhostUserOption>,
pub vhost_user_net: Vec<VhostUserOption>,
+ #[cfg(feature = "audio")]
+ pub vhost_user_snd: Vec<VhostUserOption>,
+ pub vhost_user_vsock: Vec<VhostUserOption>,
+ pub vhost_user_wl: Vec<VhostUserWlOption>,
#[cfg(feature = "direct")]
pub direct_pmio: Option<DirectIoOption>,
#[cfg(feature = "direct")]
+ pub direct_mmio: Option<DirectIoOption>,
+ #[cfg(feature = "direct")]
pub direct_level_irq: Vec<u32>,
#[cfg(feature = "direct")]
pub direct_edge_irq: Vec<u32>,
+ #[cfg(feature = "direct")]
+ pub direct_wake_irq: Vec<u32>,
+ #[cfg(feature = "direct")]
+ pub direct_gpe: Vec<u32>,
pub dmi_path: Option<PathBuf>,
+ pub no_legacy: bool,
+ pub host_cpu_topology: bool,
+ pub privileged_vm: bool,
+ pub stub_pci_devices: Vec<StubPciParameters>,
+ pub vvu_proxy: Vec<VvuOption>,
+ pub coiommu_param: Option<devices::CoIommuParameters>,
+ pub file_backed_mappings: Vec<FileBackedMappingParameters>,
+ pub init_memory: Option<u64>,
+ #[cfg(feature = "direct")]
+ pub pcie_rp: Vec<HostPcieRootPortParameters>,
+ pub rng: bool,
+ pub force_s2idle: bool,
+ pub strict_balloon: bool,
+ pub mmio_address_ranges: Vec<RangeInclusive<u64>>,
+ pub userspace_msr: BTreeSet<u32>,
+ #[cfg(target_os = "android")]
+ pub task_profiles: Vec<String>,
}
impl Default for Config {
fn default() -> Config {
Config {
kvm_device_path: PathBuf::from(KVM_PATH),
- vhost_vsock_device_path: PathBuf::from(VHOST_VSOCK_PATH),
+ vhost_vsock_device: None,
vhost_net_device_path: PathBuf::from(VHOST_NET_PATH),
vcpu_count: None,
+ vcpu_cgroup_path: None,
rt_cpus: Vec::new(),
vcpu_affinity: None,
+ cpu_clusters: Vec::new(),
+ cpu_capacity: BTreeMap::new(),
+ per_vm_core_scheduling: false,
+ #[cfg(feature = "audio_cras")]
+ cras_snds: Vec::new(),
+ delay_rt: false,
no_smt: false,
memory: None,
+ swiotlb: None,
hugepages: false,
memory_file: None,
executable_path: None,
android_fstab: None,
initrd_path: None,
+ // We initialize the jail configuration with a default value so jail-related options can
+ // apply irrespective of whether jail is enabled or not. `jail_config` will then be
+ // assigned `None` if it turns out that `jail_enabled` is `false` after we parse all the
+ // arguments.
+ jail_config: Some(Default::default()),
+ jail_enabled: !cfg!(feature = "default-no-sandbox"),
params: Vec::new(),
socket_path: None,
+ balloon_control: None,
plugin_root: None,
plugin_mounts: Vec::new(),
plugin_gid_maps: Vec::new(),
@@ -296,50 +519,86 @@ impl Default for Config {
net_vq_pairs: None,
vhost_net: false,
tap_fd: Vec::new(),
+ tap_name: Vec::new(),
cid: None,
#[cfg(feature = "gpu")]
gpu_parameters: None,
+ #[cfg(feature = "gpu")]
+ gpu_render_server_parameters: None,
software_tpm: false,
wayland_socket_paths: BTreeMap::new(),
- wayland_dmabuf: false,
x_display: None,
display_window_keyboard: false,
display_window_mouse: false,
shared_dirs: Vec::new(),
- sandbox: !cfg!(feature = "default-no-sandbox"),
- seccomp_policy_dir: PathBuf::from(SECCOMP_POLICY_DIR),
- seccomp_log_failures: false,
#[cfg(feature = "audio")]
ac97_parameters: Vec::new(),
+ #[cfg(feature = "audio")]
+ sound: None,
serial_parameters: BTreeMap::new(),
syslog_tag: None,
- virtio_single_touch: None,
- virtio_multi_touch: None,
- virtio_trackpad: None,
- virtio_mouse: None,
- virtio_keyboard: None,
- virtio_switches: None,
+ usb: true,
+ virtio_single_touch: Vec::new(),
+ virtio_multi_touch: Vec::new(),
+ virtio_trackpad: Vec::new(),
+ virtio_mice: Vec::new(),
+ virtio_keyboard: Vec::new(),
+ virtio_switches: Vec::new(),
virtio_input_evdevs: Vec::new(),
+ virtio_iommu: false,
split_irqchip: false,
vfio: Vec::new(),
- video_dec: false,
- video_enc: false,
+ #[cfg(feature = "video-decoder")]
+ video_dec: None,
+ #[cfg(feature = "video-encoder")]
+ video_enc: None,
acpi_tables: Vec::new(),
protected_vm: ProtectionType::Unprotected,
battery_type: None,
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
gdb: None,
+ balloon: true,
balloon_bias: 0,
vhost_user_blk: Vec::new(),
+ vhost_user_console: Vec::new(),
+ vhost_user_gpu: Vec::new(),
vhost_user_fs: Vec::new(),
+ vhost_user_mac80211_hwsim: None,
vhost_user_net: Vec::new(),
+ #[cfg(feature = "audio")]
+ vhost_user_snd: Vec::new(),
+ vhost_user_vsock: Vec::new(),
+ vhost_user_wl: Vec::new(),
+ vvu_proxy: Vec::new(),
#[cfg(feature = "direct")]
direct_pmio: None,
#[cfg(feature = "direct")]
+ direct_mmio: None,
+ #[cfg(feature = "direct")]
direct_level_irq: Vec::new(),
#[cfg(feature = "direct")]
direct_edge_irq: Vec::new(),
+ #[cfg(feature = "direct")]
+ direct_wake_irq: Vec::new(),
+ #[cfg(feature = "direct")]
+ direct_gpe: Vec::new(),
dmi_path: None,
+ no_legacy: false,
+ host_cpu_topology: false,
+ privileged_vm: false,
+ stub_pci_devices: Vec::new(),
+ coiommu_param: None,
+ file_backed_mappings: Vec::new(),
+ init_memory: None,
+ #[cfg(feature = "direct")]
+ pcie_rp: Vec::new(),
+ rng: true,
+ force_s2idle: false,
+ strict_balloon: false,
+ mmio_address_ranges: Vec::new(),
+ userspace_msr: BTreeSet::new(),
+ #[cfg(target_os = "android")]
+ task_profiles: Vec::new(),
}
}
}
diff --git a/src/gdb.rs b/src/gdb.rs
index 6a18f5564..9972512d6 100644
--- a/src/gdb.rs
+++ b/src/gdb.rs
@@ -14,15 +14,17 @@ use vm_control::{
};
use vm_memory::GuestAddress;
-#[cfg(target_arch = "x86_64")]
-use gdbstub::arch::x86::X86_64_SSE as GdbArch;
use gdbstub::arch::Arch;
use gdbstub::target::ext::base::singlethread::{ResumeAction, SingleThreadOps, StopReason};
-use gdbstub::target::ext::base::BaseOps;
-use gdbstub::target::ext::breakpoints::{HwBreakpoint, HwBreakpointOps};
+use gdbstub::target::ext::base::{BaseOps, GdbInterrupt};
+use gdbstub::target::ext::breakpoints::{
+ Breakpoints, BreakpointsOps, HwBreakpoint, HwBreakpointOps,
+};
use gdbstub::target::TargetError::NonFatal;
use gdbstub::target::{Target, TargetResult};
use gdbstub::Connection;
+#[cfg(target_arch = "x86_64")]
+use gdbstub_arch::x86::X86_64_SSE as GdbArch;
use remain::sorted;
use thiserror::Error as ThisError;
@@ -141,7 +143,7 @@ impl Target for GdbStub {
}
// TODO(keiichiw): sw_breakpoint, hw_watchpoint, extended_mode, monitor_cmd, section_offsets
- fn hw_breakpoint(&mut self) -> Option<HwBreakpointOps<Self>> {
+ fn breakpoints(&mut self) -> Option<BreakpointsOps<Self>> {
Some(self)
}
}
@@ -150,7 +152,7 @@ impl SingleThreadOps for GdbStub {
fn resume(
&mut self,
action: ResumeAction,
- check_gdb_interrupt: &mut dyn FnMut() -> bool,
+ check_gdb_interrupt: GdbInterrupt,
) -> Result<StopReason<ArchUsize>, Self::Error> {
let single_step = ResumeAction::Step == action;
@@ -173,13 +175,15 @@ impl SingleThreadOps for GdbStub {
"Failed to resume the target"
})?;
+ let mut check_gdb_interrupt = check_gdb_interrupt.no_async();
// Polling
loop {
- match self
+ // TODO(keiichiw): handle error?
+ if let Ok(msg) = self
.from_vcpu
.recv_timeout(std::time::Duration::from_millis(100))
{
- Ok(msg) => match msg.msg {
+ match msg.msg {
VcpuDebugStatus::HitBreakPoint => {
if single_step {
return Ok(StopReason::DoneStep);
@@ -190,11 +194,10 @@ impl SingleThreadOps for GdbStub {
status => {
error!("Unexpected VcpuDebugStatus: {:?}", status);
}
- },
- Err(_) => {} // TODO(keiichiw): handle error?
- };
+ }
+ }
- if check_gdb_interrupt() {
+ if check_gdb_interrupt.pending() {
self.vm_request(VmRequest::Suspend).map_err(|e| {
error!("Failed to suspend the target: {}", e);
"Failed to suspend the target"
@@ -291,10 +294,20 @@ impl SingleThreadOps for GdbStub {
}
}
+impl Breakpoints for GdbStub {
+ fn hw_breakpoint(&mut self) -> Option<HwBreakpointOps<Self>> {
+ Some(self)
+ }
+}
+
impl HwBreakpoint for GdbStub {
/// Add a new hardware breakpoint.
/// Return `Ok(false)` if the operation could not be completed.
- fn add_hw_breakpoint(&mut self, addr: <Self::Arch as Arch>::Usize) -> TargetResult<bool, Self> {
+ fn add_hw_breakpoint(
+ &mut self,
+ addr: <Self::Arch as Arch>::Usize,
+ _kind: <Self::Arch as Arch>::BreakpointKind,
+ ) -> TargetResult<bool, Self> {
// If we already have 4 breakpoints, we cannot set a new one.
if self.hw_breakpoints.len() >= 4 {
error!("Not allowed to set more than 4 HW breakpoints");
@@ -322,6 +335,7 @@ impl HwBreakpoint for GdbStub {
fn remove_hw_breakpoint(
&mut self,
addr: <Self::Arch as Arch>::Usize,
+ _kind: <Self::Arch as Arch>::BreakpointKind,
) -> TargetResult<bool, Self> {
self.hw_breakpoints.retain(|&b| b.0 != addr);
diff --git a/src/linux.rs b/src/linux.rs
deleted file mode 100644
index 5efa2bb1f..000000000
--- a/src/linux.rs
+++ /dev/null
@@ -1,3250 +0,0 @@
-// Copyright 2017 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use std::cmp::{max, min, Reverse};
-use std::convert::TryFrom;
-#[cfg(feature = "gpu")]
-use std::env;
-use std::error::Error as StdError;
-use std::ffi::CStr;
-use std::fmt::{self, Display};
-use std::fs::{File, OpenOptions};
-use std::io::{self, stdin, Read};
-use std::iter;
-use std::mem;
-use std::net::Ipv4Addr;
-#[cfg(feature = "gpu")]
-use std::num::NonZeroU8;
-use std::num::ParseIntError;
-use std::os::unix::io::FromRawFd;
-use std::os::unix::net::UnixStream;
-use std::path::{Path, PathBuf};
-use std::ptr;
-use std::str;
-use std::sync::{mpsc, Arc, Barrier};
-
-use std::thread;
-use std::thread::JoinHandle;
-use std::time::Duration;
-
-use libc::{self, c_int, gid_t, uid_t};
-
-use acpi_tables::sdt::SDT;
-
-use base::net::{UnixSeqpacketListener, UnlinkUnixSeqpacketListener};
-use base::*;
-use devices::virtio::vhost::user::{
- Block as VhostUserBlock, Error as VhostUserError, Fs as VhostUserFs, Net as VhostUserNet,
-};
-#[cfg(feature = "gpu")]
-use devices::virtio::EventDevice;
-use devices::virtio::{self, Console, VirtioDevice};
-#[cfg(feature = "audio")]
-use devices::Ac97Dev;
-use devices::{
- self, HostBackendDeviceProvider, IrqChip, IrqEventIndex, KvmKernelIrqChip, PciDevice,
- VcpuRunState, VfioContainer, VfioDevice, VfioPciDevice, VirtioPciDevice, XhciController,
-};
-use hypervisor::kvm::{Kvm, KvmVcpu, KvmVm};
-use hypervisor::{HypervisorCap, Vcpu, VcpuExit, VcpuRunHandle, Vm, VmCap};
-use minijail::{self, Minijail};
-use net_util::{Error as NetError, MacAddress, Tap};
-use remain::sorted;
-use resources::{Alloc, MmioType, SystemAllocator};
-use rutabaga_gfx::RutabagaGralloc;
-use sync::Mutex;
-use vm_control::*;
-use vm_memory::{GuestAddress, GuestMemory, MemoryPolicy};
-
-#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
-use crate::gdb::{gdb_thread, GdbStub};
-use crate::{
- Config, DiskOption, Executable, SharedDir, SharedDirKind, TouchDeviceOption, VhostUserFsOption,
- VhostUserOption,
-};
-use arch::{
- self, LinuxArch, RunnableLinuxVm, SerialHardware, SerialParameters, VcpuAffinity,
- VirtioDeviceStub, VmComponents, VmImage,
-};
-
-#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
-use {
- aarch64::AArch64 as Arch,
- devices::IrqChipAArch64 as IrqChipArch,
- hypervisor::{VcpuAArch64 as VcpuArch, VmAArch64 as VmArch},
-};
-#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
-use {
- devices::{IrqChipX86_64 as IrqChipArch, KvmSplitIrqChip},
- hypervisor::{VcpuX86_64 as VcpuArch, VmX86_64 as VmArch},
- x86_64::X8664arch as Arch,
-};
-
-#[sorted]
-#[derive(Debug)]
-pub enum Error {
- AddGpuDeviceMemory(base::Error),
- AddIrqChipVcpu(base::Error),
- AddPmemDeviceMemory(base::Error),
- AllocateGpuDeviceAddress,
- AllocatePmemDeviceAddress(resources::Error),
- BalloonActualTooLarge,
- BalloonDeviceNew(virtio::BalloonError),
- BlockDeviceNew(base::Error),
- BlockSignal(base::signal::Error),
- BuildVm(<Arch as LinuxArch>::Error),
- ChownTpmStorage(base::Error),
- CloneEvent(base::Error),
- CloneVcpu(base::Error),
- ConfigureVcpu(<Arch as LinuxArch>::Error),
- #[cfg(feature = "audio")]
- CreateAc97(devices::PciDeviceError),
- CreateConsole(arch::serial::Error),
- CreateControlServer(io::Error),
- CreateDiskError(disk::Error),
- CreateEvent(base::Error),
- CreateGrallocError(rutabaga_gfx::RutabagaError),
- CreateKvm(base::Error),
- CreateSignalFd(base::SignalFdError),
- CreateSocket(io::Error),
- CreateTapDevice(NetError),
- CreateTimer(base::Error),
- CreateTpmStorage(PathBuf, io::Error),
- CreateTube(TubeError),
- CreateUsbProvider(devices::usb::host_backend::error::Error),
- CreateVcpu(base::Error),
- CreateVfioDevice(devices::vfio::VfioError),
- CreateVm(base::Error),
- CreateWaitContext(base::Error),
- DeviceJail(minijail::Error),
- DevicePivotRoot(minijail::Error),
- #[cfg(feature = "direct")]
- DirectIo(io::Error),
- #[cfg(feature = "direct")]
- DirectIrq(devices::DirectIrqError),
- Disk(PathBuf, io::Error),
- DiskImageLock(base::Error),
- DropCapabilities(base::Error),
- FsDeviceNew(virtio::fs::Error),
- GetMaxOpenFiles(io::Error),
- GetSignalMask(signal::Error),
- GuestCachedMissing(),
- GuestCachedTooLarge(std::num::TryFromIntError),
- GuestFreeMissing(),
- GuestFreeTooLarge(std::num::TryFromIntError),
- GuestMemoryLayout(<Arch as LinuxArch>::Error),
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- HandleDebugCommand(<Arch as LinuxArch>::Error),
- InputDeviceNew(virtio::InputError),
- InputEventsOpen(std::io::Error),
- InvalidFdPath,
- InvalidWaylandPath,
- IoJail(minijail::Error),
- LoadKernel(Box<dyn StdError>),
- MemoryTooLarge,
- NetDeviceNew(virtio::NetError),
- OpenAcpiTable(PathBuf, io::Error),
- OpenAndroidFstab(PathBuf, io::Error),
- OpenBios(PathBuf, io::Error),
- OpenInitrd(PathBuf, io::Error),
- OpenKernel(PathBuf, io::Error),
- OpenVinput(PathBuf, io::Error),
- P9DeviceNew(virtio::P9Error),
- ParseMaxOpenFiles(ParseIntError),
- PivotRootDoesntExist(&'static str),
- PmemDeviceImageTooBig,
- PmemDeviceNew(base::Error),
- ReadMemAvailable(io::Error),
- ReadStatm(io::Error),
- RegisterBalloon(arch::DeviceRegistrationError),
- RegisterBlock(arch::DeviceRegistrationError),
- RegisterGpu(arch::DeviceRegistrationError),
- RegisterNet(arch::DeviceRegistrationError),
- RegisterP9(arch::DeviceRegistrationError),
- RegisterRng(arch::DeviceRegistrationError),
- RegisterSignalHandler(base::Error),
- RegisterWayland(arch::DeviceRegistrationError),
- ReserveGpuMemory(base::MmapError),
- ReserveMemory(base::Error),
- ReservePmemMemory(base::MmapError),
- ResetTimer(base::Error),
- RngDeviceNew(virtio::RngError),
- RunnableVcpu(base::Error),
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- SendDebugStatus(Box<mpsc::SendError<VcpuDebugStatusMessage>>),
- SettingGidMap(minijail::Error),
- SettingMaxOpenFiles(minijail::Error),
- SettingSignalMask(base::Error),
- SettingUidMap(minijail::Error),
- SignalFd(base::SignalFdError),
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- SpawnGdbServer(io::Error),
- SpawnVcpu(io::Error),
- Timer(base::Error),
- ValidateRawDescriptor(base::Error),
- VhostNetDeviceNew(virtio::vhost::Error),
- VhostUserBlockDeviceNew(VhostUserError),
- VhostUserFsDeviceNew(VhostUserError),
- VhostUserNetDeviceNew(VhostUserError),
- VhostUserNetWithNetArgs,
- VhostVsockDeviceNew(virtio::vhost::Error),
- VirtioPciDev(base::Error),
- WaitContextAdd(base::Error),
- WaitContextDelete(base::Error),
- WaylandDeviceNew(base::Error),
-}
-
-impl Display for Error {
- #[remain::check]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- #[sorted]
- match self {
- AddGpuDeviceMemory(e) => write!(f, "failed to add gpu device memory: {}", e),
- AddIrqChipVcpu(e) => write!(f, "failed to add vcpu to irq chip: {}", e),
- AddPmemDeviceMemory(e) => write!(f, "failed to add pmem device memory: {}", e),
- AllocateGpuDeviceAddress => write!(f, "failed to allocate gpu device guest address"),
- AllocatePmemDeviceAddress(e) => {
- write!(f, "failed to allocate memory for pmem device: {}", e)
- }
- BalloonActualTooLarge => write!(f, "balloon actual size is too large"),
- BalloonDeviceNew(e) => write!(f, "failed to create balloon: {}", e),
- BlockDeviceNew(e) => write!(f, "failed to create block device: {}", e),
- BlockSignal(e) => write!(f, "failed to block signal: {}", e),
- BuildVm(e) => write!(f, "The architecture failed to build the vm: {}", e),
- ChownTpmStorage(e) => write!(f, "failed to chown tpm storage: {}", e),
- CloneEvent(e) => write!(f, "failed to clone event: {}", e),
- CloneVcpu(e) => write!(f, "failed to clone vcpu: {}", e),
- ConfigureVcpu(e) => write!(f, "failed to configure vcpu: {}", e),
- #[cfg(feature = "audio")]
- CreateAc97(e) => write!(f, "failed to create ac97 device: {}", e),
- CreateConsole(e) => write!(f, "failed to create console device: {}", e),
- CreateControlServer(e) => write!(f, "failed to create control server: {}", e),
- CreateDiskError(e) => write!(f, "failed to create virtual disk: {}", e),
- CreateEvent(e) => write!(f, "failed to create event: {}", e),
- CreateGrallocError(e) => write!(f, "failed to create gralloc: {}", e),
- CreateKvm(e) => write!(f, "failed to create kvm: {}", e),
- CreateSignalFd(e) => write!(f, "failed to create signalfd: {}", e),
- CreateSocket(e) => write!(f, "failed to create socket: {}", e),
- CreateTapDevice(e) => write!(f, "failed to create tap device: {}", e),
- CreateTimer(e) => write!(f, "failed to create Timer: {}", e),
- CreateTpmStorage(p, e) => {
- write!(f, "failed to create tpm storage dir {}: {}", p.display(), e)
- }
- CreateTube(e) => write!(f, "failed to create tube: {}", e),
- CreateUsbProvider(e) => write!(f, "failed to create usb provider: {}", e),
- CreateVcpu(e) => write!(f, "failed to create vcpu: {}", e),
- CreateVfioDevice(e) => write!(f, "Failed to create vfio device {}", e),
- CreateVm(e) => write!(f, "failed to create vm: {}", e),
- CreateWaitContext(e) => write!(f, "failed to create wait context: {}", e),
- DeviceJail(e) => write!(f, "failed to jail device: {}", e),
- DevicePivotRoot(e) => write!(f, "failed to pivot root device: {}", e),
- #[cfg(feature = "direct")]
- DirectIo(e) => write!(f, "failed to open direct io device: {}", e),
- #[cfg(feature = "direct")]
- DirectIrq(e) => write!(f, "failed to enable interrupt forwarding: {}", e),
- Disk(p, e) => write!(f, "failed to load disk image {}: {}", p.display(), e),
- DiskImageLock(e) => write!(f, "failed to lock disk image: {}", e),
- DropCapabilities(e) => write!(f, "failed to drop process capabilities: {}", e),
- FsDeviceNew(e) => write!(f, "failed to create fs device: {}", e),
- GetMaxOpenFiles(e) => write!(f, "failed to get max number of open files: {}", e),
- GetSignalMask(e) => write!(f, "failed to retrieve signal mask for vcpu: {}", e),
- GuestCachedMissing() => write!(f, "guest cached is missing from balloon stats"),
- GuestCachedTooLarge(e) => write!(f, "guest cached is too large: {}", e),
- GuestFreeMissing() => write!(f, "guest free is missing from balloon stats"),
- GuestFreeTooLarge(e) => write!(f, "guest free is too large: {}", e),
- GuestMemoryLayout(e) => write!(f, "failed to create guest memory layout: {}", e),
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- HandleDebugCommand(e) => write!(f, "failed to handle a gdb command: {}", e),
- InputDeviceNew(e) => write!(f, "failed to set up input device: {}", e),
- InputEventsOpen(e) => write!(f, "failed to open event device: {}", e),
- InvalidFdPath => write!(f, "failed parsing a /proc/self/fd/*"),
- InvalidWaylandPath => write!(f, "wayland socket path has no parent or file name"),
- IoJail(e) => write!(f, "{}", e),
- LoadKernel(e) => write!(f, "failed to load kernel: {}", e),
- MemoryTooLarge => write!(f, "requested memory size too large"),
- NetDeviceNew(e) => write!(f, "failed to set up virtio networking: {}", e),
- OpenAcpiTable(p, e) => write!(f, "failed to open ACPI file {}: {}", p.display(), e),
- OpenAndroidFstab(p, e) => write!(
- f,
- "failed to open android fstab file {}: {}",
- p.display(),
- e
- ),
- OpenBios(p, e) => write!(f, "failed to open bios {}: {}", p.display(), e),
- OpenInitrd(p, e) => write!(f, "failed to open initrd {}: {}", p.display(), e),
- OpenKernel(p, e) => write!(f, "failed to open kernel image {}: {}", p.display(), e),
- OpenVinput(p, e) => write!(f, "failed to open vinput device {}: {}", p.display(), e),
- P9DeviceNew(e) => write!(f, "failed to create 9p device: {}", e),
- ParseMaxOpenFiles(e) => write!(f, "failed to parse max number of open files: {}", e),
- PivotRootDoesntExist(p) => write!(f, "{} doesn't exist, can't jail devices.", p),
- PmemDeviceImageTooBig => {
- write!(f, "failed to create pmem device: pmem device image too big")
- }
- PmemDeviceNew(e) => write!(f, "failed to create pmem device: {}", e),
- ReadMemAvailable(e) => write!(
- f,
- "failed to read /sys/kernel/mm/chromeos-low_mem/available: {}",
- e
- ),
- ReadStatm(e) => write!(f, "failed to read /proc/self/statm: {}", e),
- RegisterBalloon(e) => write!(f, "error registering balloon device: {}", e),
- RegisterBlock(e) => write!(f, "error registering block device: {}", e),
- RegisterGpu(e) => write!(f, "error registering gpu device: {}", e),
- RegisterNet(e) => write!(f, "error registering net device: {}", e),
- RegisterP9(e) => write!(f, "error registering 9p device: {}", e),
- RegisterRng(e) => write!(f, "error registering rng device: {}", e),
- RegisterSignalHandler(e) => write!(f, "error registering signal handler: {}", e),
- RegisterWayland(e) => write!(f, "error registering wayland device: {}", e),
- ReserveGpuMemory(e) => write!(f, "failed to reserve gpu memory: {}", e),
- ReserveMemory(e) => write!(f, "failed to reserve memory: {}", e),
- ReservePmemMemory(e) => write!(f, "failed to reserve pmem memory: {}", e),
- ResetTimer(e) => write!(f, "failed to reset Timer: {}", e),
- RngDeviceNew(e) => write!(f, "failed to set up rng: {}", e),
- RunnableVcpu(e) => write!(f, "failed to set thread id for vcpu: {}", e),
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- SendDebugStatus(e) => write!(f, "failed to send a debug status to GDB thread: {}", e),
- SettingGidMap(e) => write!(f, "error setting GID map: {}", e),
- SettingMaxOpenFiles(e) => write!(f, "error setting max open files: {}", e),
- SettingSignalMask(e) => write!(f, "failed to set the signal mask for vcpu: {}", e),
- SettingUidMap(e) => write!(f, "error setting UID map: {}", e),
- SignalFd(e) => write!(f, "failed to read signal fd: {}", e),
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- SpawnGdbServer(e) => write!(f, "failed to spawn GDB thread: {}", e),
- SpawnVcpu(e) => write!(f, "failed to spawn VCPU thread: {}", e),
- Timer(e) => write!(f, "failed to read timer fd: {}", e),
- ValidateRawDescriptor(e) => write!(f, "failed to validate raw descriptor: {}", e),
- VhostNetDeviceNew(e) => write!(f, "failed to set up vhost networking: {}", e),
- VhostUserBlockDeviceNew(e) => {
- write!(f, "failed to set up vhost-user block device: {}", e)
- }
- VhostUserFsDeviceNew(e) => write!(f, "failed to set up vhost-user fs device: {}", e),
- VhostUserNetDeviceNew(e) => write!(f, "failed to set up vhost-user net device: {}", e),
- VhostUserNetWithNetArgs => write!(
- f,
- "vhost-user-net cannot be used with any of --host_ip, --netmask or --mac"
- ),
- VhostVsockDeviceNew(e) => write!(f, "failed to set up virtual socket device: {}", e),
- VirtioPciDev(e) => write!(f, "failed to create virtio pci dev: {}", e),
- WaitContextAdd(e) => write!(f, "failed to add descriptor to wait context: {}", e),
- WaitContextDelete(e) => {
- write!(f, "failed to remove descriptor from wait context: {}", e)
- }
- WaylandDeviceNew(e) => write!(f, "failed to create wayland device: {}", e),
- }
- }
-}
-
-impl From<minijail::Error> for Error {
- fn from(err: minijail::Error) -> Self {
- Error::IoJail(err)
- }
-}
-
-impl std::error::Error for Error {}
-
-type Result<T> = std::result::Result<T, Error>;
-
-enum TaggedControlTube {
- Fs(Tube),
- Vm(Tube),
- VmMemory(Tube),
- VmIrq(Tube),
- VmMsync(Tube),
-}
-
-impl AsRef<Tube> for TaggedControlTube {
- fn as_ref(&self) -> &Tube {
- use self::TaggedControlTube::*;
- match &self {
- Fs(tube) | Vm(tube) | VmMemory(tube) | VmIrq(tube) | VmMsync(tube) => tube,
- }
- }
-}
-
-impl AsRawDescriptor for TaggedControlTube {
- fn as_raw_descriptor(&self) -> RawDescriptor {
- self.as_ref().as_raw_descriptor()
- }
-}
-
-fn get_max_open_files() -> Result<u64> {
- let mut buf = mem::MaybeUninit::<libc::rlimit64>::zeroed();
-
- // Safe because this will only modify `buf` and we check the return value.
- let res = unsafe { libc::prlimit64(0, libc::RLIMIT_NOFILE, ptr::null(), buf.as_mut_ptr()) };
- if res == 0 {
- // Safe because the kernel guarantees that the struct is fully initialized.
- let limit = unsafe { buf.assume_init() };
- Ok(limit.rlim_max)
- } else {
- Err(Error::GetMaxOpenFiles(io::Error::last_os_error()))
- }
-}
-
-struct SandboxConfig<'a> {
- limit_caps: bool,
- log_failures: bool,
- seccomp_policy: &'a Path,
- uid_map: Option<&'a str>,
- gid_map: Option<&'a str>,
-}
-
-fn create_base_minijail(
- root: &Path,
- r_limit: Option<u64>,
- config: Option<&SandboxConfig>,
-) -> Result<Minijail> {
- // All child jails run in a new user namespace without any users mapped,
- // they run as nobody unless otherwise configured.
- let mut j = Minijail::new().map_err(Error::DeviceJail)?;
-
- if let Some(config) = config {
- j.namespace_pids();
- j.namespace_user();
- j.namespace_user_disable_setgroups();
- if config.limit_caps {
- // Don't need any capabilities.
- j.use_caps(0);
- }
- if let Some(uid_map) = config.uid_map {
- j.uidmap(uid_map).map_err(Error::SettingUidMap)?;
- }
- if let Some(gid_map) = config.gid_map {
- j.gidmap(gid_map).map_err(Error::SettingGidMap)?;
- }
- // Run in a new mount namespace.
- j.namespace_vfs();
-
- // Run in an empty network namespace.
- j.namespace_net();
-
- // Don't allow the device to gain new privileges.
- j.no_new_privs();
-
- // By default we'll prioritize using the pre-compiled .bpf over the .policy
- // file (the .bpf is expected to be compiled using "trap" as the failure
- // behavior instead of the default "kill" behavior).
- // Refer to the code comment for the "seccomp-log-failures"
- // command-line parameter for an explanation about why the |log_failures|
- // flag forces the use of .policy files (and the build-time alternative to
- // this run-time flag).
- let bpf_policy_file = config.seccomp_policy.with_extension("bpf");
- if bpf_policy_file.exists() && !config.log_failures {
- j.parse_seccomp_program(&bpf_policy_file)
- .map_err(Error::DeviceJail)?;
- } else {
- // Use TSYNC only for the side effect of it using SECCOMP_RET_TRAP,
- // which will correctly kill the entire device process if a worker
- // thread commits a seccomp violation.
- j.set_seccomp_filter_tsync();
- if config.log_failures {
- j.log_seccomp_filter_failures();
- }
- j.parse_seccomp_filters(&config.seccomp_policy.with_extension("policy"))
- .map_err(Error::DeviceJail)?;
- }
- j.use_seccomp_filter();
- // Don't do init setup.
- j.run_as_init();
- }
-
- // Only pivot_root if we are not re-using the current root directory.
- if root != Path::new("/") {
- // It's safe to call `namespace_vfs` multiple times.
- j.namespace_vfs();
- j.enter_pivot_root(root).map_err(Error::DevicePivotRoot)?;
- }
-
- // Most devices don't need to open many fds.
- let limit = if let Some(r) = r_limit { r } else { 1024u64 };
- j.set_rlimit(libc::RLIMIT_NOFILE as i32, limit, limit)
- .map_err(Error::SettingMaxOpenFiles)?;
-
- Ok(j)
-}
-
-fn simple_jail(cfg: &Config, policy: &str) -> Result<Option<Minijail>> {
- if cfg.sandbox {
- let pivot_root: &str = option_env!("DEFAULT_PIVOT_ROOT").unwrap_or("/var/empty");
- // A directory for a jailed device's pivot root.
- let root_path = Path::new(pivot_root);
- if !root_path.exists() {
- return Err(Error::PivotRootDoesntExist(pivot_root));
- }
- let policy_path: PathBuf = cfg.seccomp_policy_dir.join(policy);
- let config = SandboxConfig {
- limit_caps: true,
- log_failures: cfg.seccomp_log_failures,
- seccomp_policy: &policy_path,
- uid_map: None,
- gid_map: None,
- };
- Ok(Some(create_base_minijail(root_path, None, Some(&config))?))
- } else {
- Ok(None)
- }
-}
-
-type DeviceResult<T = VirtioDeviceStub> = std::result::Result<T, Error>;
-
-fn create_block_device(cfg: &Config, disk: &DiskOption, disk_device_tube: Tube) -> DeviceResult {
- // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
- let raw_image: File = if disk.path.parent() == Some(Path::new("/proc/self/fd")) {
- // Safe because we will validate |raw_fd|.
- unsafe { File::from_raw_descriptor(raw_descriptor_from_path(&disk.path)?) }
- } else {
- OpenOptions::new()
- .read(true)
- .write(!disk.read_only)
- .open(&disk.path)
- .map_err(|e| Error::Disk(disk.path.to_path_buf(), e))?
- };
- // Lock the disk image to prevent other crosvm instances from using it.
- let lock_op = if disk.read_only {
- FlockOperation::LockShared
- } else {
- FlockOperation::LockExclusive
- };
- flock(&raw_image, lock_op, true).map_err(Error::DiskImageLock)?;
-
- let dev = if disk::async_ok(&raw_image).map_err(Error::CreateDiskError)? {
- let async_file = disk::create_async_disk_file(raw_image).map_err(Error::CreateDiskError)?;
- Box::new(
- virtio::BlockAsync::new(
- virtio::base_features(cfg.protected_vm),
- async_file,
- disk.read_only,
- disk.sparse,
- disk.block_size,
- disk.id,
- Some(disk_device_tube),
- )
- .map_err(Error::BlockDeviceNew)?,
- ) as Box<dyn VirtioDevice>
- } else {
- let disk_file = disk::create_disk_file(raw_image).map_err(Error::CreateDiskError)?;
- Box::new(
- virtio::Block::new(
- virtio::base_features(cfg.protected_vm),
- disk_file,
- disk.read_only,
- disk.sparse,
- disk.block_size,
- disk.id,
- Some(disk_device_tube),
- )
- .map_err(Error::BlockDeviceNew)?,
- ) as Box<dyn VirtioDevice>
- };
-
- Ok(VirtioDeviceStub {
- dev,
- jail: simple_jail(&cfg, "block_device")?,
- })
-}
-
-fn create_vhost_user_block_device(cfg: &Config, opt: &VhostUserOption) -> DeviceResult {
- let dev = VhostUserBlock::new(virtio::base_features(cfg.protected_vm), &opt.socket)
- .map_err(Error::VhostUserBlockDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- // no sandbox here because virtqueue handling is exported to a different process.
- jail: None,
- })
-}
-
-fn create_vhost_user_fs_device(cfg: &Config, option: &VhostUserFsOption) -> DeviceResult {
- let dev = VhostUserFs::new(
- virtio::base_features(cfg.protected_vm),
- &option.socket,
- &option.tag,
- )
- .map_err(Error::VhostUserFsDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- // no sandbox here because virtqueue handling is exported to a different process.
- jail: None,
- })
-}
-
-fn create_rng_device(cfg: &Config) -> DeviceResult {
- let dev =
- virtio::Rng::new(virtio::base_features(cfg.protected_vm)).map_err(Error::RngDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: simple_jail(&cfg, "rng_device")?,
- })
-}
-
-#[cfg(feature = "tpm")]
-fn create_tpm_device(cfg: &Config) -> DeviceResult {
- use std::ffi::CString;
- use std::fs;
- use std::process;
-
- let tpm_storage: PathBuf;
- let mut tpm_jail = simple_jail(&cfg, "tpm_device")?;
-
- match &mut tpm_jail {
- Some(jail) => {
- // Create a tmpfs in the device's root directory for tpm
- // simulator storage. The size is 20*1024, or 20 KB.
- jail.mount_with_data(
- Path::new("none"),
- Path::new("/"),
- "tmpfs",
- (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
- "size=20480",
- )?;
-
- let crosvm_ids = add_crosvm_user_to_jail(jail, "tpm")?;
-
- let pid = process::id();
- let tpm_pid_dir = format!("/run/vm/tpm.{}", pid);
- tpm_storage = Path::new(&tpm_pid_dir).to_owned();
- fs::create_dir_all(&tpm_storage)
- .map_err(|e| Error::CreateTpmStorage(tpm_storage.to_owned(), e))?;
- let tpm_pid_dir_c = CString::new(tpm_pid_dir).expect("no nul bytes");
- chown(&tpm_pid_dir_c, crosvm_ids.uid, crosvm_ids.gid)
- .map_err(Error::ChownTpmStorage)?;
-
- jail.mount_bind(&tpm_storage, &tpm_storage, true)?;
- }
- None => {
- // Path used inside cros_sdk which does not have /run/vm.
- tpm_storage = Path::new("/tmp/tpm-simulator").to_owned();
- }
- }
-
- let dev = virtio::Tpm::new(tpm_storage);
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: tpm_jail,
- })
-}
-
-fn create_single_touch_device(cfg: &Config, single_touch_spec: &TouchDeviceOption) -> DeviceResult {
- let socket = single_touch_spec
- .get_path()
- .into_unix_stream()
- .map_err(|e| {
- error!("failed configuring virtio single touch: {:?}", e);
- e
- })?;
-
- let (width, height) = single_touch_spec.get_size();
- let dev = virtio::new_single_touch(
- socket,
- width,
- height,
- virtio::base_features(cfg.protected_vm),
- )
- .map_err(Error::InputDeviceNew)?;
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: simple_jail(&cfg, "input_device")?,
- })
-}
-
-fn create_multi_touch_device(cfg: &Config, multi_touch_spec: &TouchDeviceOption) -> DeviceResult {
- let socket = multi_touch_spec
- .get_path()
- .into_unix_stream()
- .map_err(|e| {
- error!("failed configuring virtio multi touch: {:?}", e);
- e
- })?;
-
- let (width, height) = multi_touch_spec.get_size();
- let dev = virtio::new_multi_touch(
- socket,
- width,
- height,
- virtio::base_features(cfg.protected_vm),
- )
- .map_err(Error::InputDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: simple_jail(&cfg, "input_device")?,
- })
-}
-
-fn create_trackpad_device(cfg: &Config, trackpad_spec: &TouchDeviceOption) -> DeviceResult {
- let socket = trackpad_spec.get_path().into_unix_stream().map_err(|e| {
- error!("failed configuring virtio trackpad: {}", e);
- e
- })?;
-
- let (width, height) = trackpad_spec.get_size();
- let dev = virtio::new_trackpad(
- socket,
- width,
- height,
- virtio::base_features(cfg.protected_vm),
- )
- .map_err(Error::InputDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: simple_jail(&cfg, "input_device")?,
- })
-}
-
-fn create_mouse_device<T: IntoUnixStream>(cfg: &Config, mouse_socket: T) -> DeviceResult {
- let socket = mouse_socket.into_unix_stream().map_err(|e| {
- error!("failed configuring virtio mouse: {}", e);
- e
- })?;
-
- let dev = virtio::new_mouse(socket, virtio::base_features(cfg.protected_vm))
- .map_err(Error::InputDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: simple_jail(&cfg, "input_device")?,
- })
-}
-
-fn create_keyboard_device<T: IntoUnixStream>(cfg: &Config, keyboard_socket: T) -> DeviceResult {
- let socket = keyboard_socket.into_unix_stream().map_err(|e| {
- error!("failed configuring virtio keyboard: {}", e);
- e
- })?;
-
- let dev = virtio::new_keyboard(socket, virtio::base_features(cfg.protected_vm))
- .map_err(Error::InputDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: simple_jail(&cfg, "input_device")?,
- })
-}
-
-fn create_switches_device<T: IntoUnixStream>(cfg: &Config, switches_socket: T) -> DeviceResult {
- let socket = switches_socket.into_unix_stream().map_err(|e| {
- error!("failed configuring virtio switches: {}", e);
- e
- })?;
-
- let dev = virtio::new_switches(socket, virtio::base_features(cfg.protected_vm))
- .map_err(Error::InputDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: simple_jail(&cfg, "input_device")?,
- })
-}
-
-fn create_vinput_device(cfg: &Config, dev_path: &Path) -> DeviceResult {
- let dev_file = OpenOptions::new()
- .read(true)
- .write(true)
- .open(dev_path)
- .map_err(|e| Error::OpenVinput(dev_path.to_owned(), e))?;
-
- let dev = virtio::new_evdev(dev_file, virtio::base_features(cfg.protected_vm))
- .map_err(Error::InputDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: simple_jail(&cfg, "input_device")?,
- })
-}
-
-fn create_balloon_device(cfg: &Config, tube: Tube) -> DeviceResult {
- let dev = virtio::Balloon::new(virtio::base_features(cfg.protected_vm), tube)
- .map_err(Error::BalloonDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: simple_jail(&cfg, "balloon_device")?,
- })
-}
-
-fn create_tap_net_device(cfg: &Config, tap_fd: RawDescriptor) -> DeviceResult {
- // Safe because we ensure that we get a unique handle to the fd.
- let tap = unsafe {
- Tap::from_raw_descriptor(
- validate_raw_descriptor(tap_fd).map_err(Error::ValidateRawDescriptor)?,
- )
- .map_err(Error::CreateTapDevice)?
- };
-
- let mut vq_pairs = cfg.net_vq_pairs.unwrap_or(1);
- let vcpu_count = cfg.vcpu_count.unwrap_or(1);
- if vcpu_count < vq_pairs as usize {
- error!("net vq pairs must be smaller than vcpu count, fall back to single queue mode");
- vq_pairs = 1;
- }
- let features = virtio::base_features(cfg.protected_vm);
- let dev = virtio::Net::from(features, tap, vq_pairs).map_err(Error::NetDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: simple_jail(&cfg, "net_device")?,
- })
-}
-
-fn create_net_device(
- cfg: &Config,
- host_ip: Ipv4Addr,
- netmask: Ipv4Addr,
- mac_address: MacAddress,
- mem: &GuestMemory,
-) -> DeviceResult {
- let mut vq_pairs = cfg.net_vq_pairs.unwrap_or(1);
- let vcpu_count = cfg.vcpu_count.unwrap_or(1);
- if vcpu_count < vq_pairs as usize {
- error!("net vq pairs must be smaller than vcpu count, fall back to single queue mode");
- vq_pairs = 1;
- }
-
- let features = virtio::base_features(cfg.protected_vm);
- let dev = if cfg.vhost_net {
- let dev = virtio::vhost::Net::<Tap, vhost::Net<Tap>>::new(
- &cfg.vhost_net_device_path,
- features,
- host_ip,
- netmask,
- mac_address,
- mem,
- )
- .map_err(Error::VhostNetDeviceNew)?;
- Box::new(dev) as Box<dyn VirtioDevice>
- } else {
- let dev = virtio::Net::<Tap>::new(features, host_ip, netmask, mac_address, vq_pairs)
- .map_err(Error::NetDeviceNew)?;
- Box::new(dev) as Box<dyn VirtioDevice>
- };
-
- let policy = if cfg.vhost_net {
- "vhost_net_device"
- } else {
- "net_device"
- };
-
- Ok(VirtioDeviceStub {
- dev,
- jail: simple_jail(&cfg, policy)?,
- })
-}
-
-fn create_vhost_user_net_device(cfg: &Config, opt: &VhostUserOption) -> DeviceResult {
- let dev = VhostUserNet::new(virtio::base_features(cfg.protected_vm), &opt.socket)
- .map_err(Error::VhostUserNetDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- // no sandbox here because virtqueue handling is exported to a different process.
- jail: None,
- })
-}
-
-#[cfg(feature = "gpu")]
-fn create_gpu_device(
- cfg: &Config,
- exit_evt: &Event,
- gpu_device_tube: Tube,
- resource_bridges: Vec<Tube>,
- wayland_socket_path: Option<&PathBuf>,
- x_display: Option<String>,
- event_devices: Vec<EventDevice>,
- map_request: Arc<Mutex<Option<ExternalMapping>>>,
- mem: &GuestMemory,
-) -> DeviceResult {
- let jailed_wayland_path = Path::new("/wayland-0");
-
- let mut display_backends = vec![
- virtio::DisplayBackend::X(x_display),
- virtio::DisplayBackend::Stub,
- ];
-
- if let Some(socket_path) = wayland_socket_path {
- display_backends.insert(
- 0,
- virtio::DisplayBackend::Wayland(if cfg.sandbox {
- Some(jailed_wayland_path.to_owned())
- } else {
- Some(socket_path.to_owned())
- }),
- );
- }
-
- let dev = virtio::Gpu::new(
- exit_evt.try_clone().map_err(Error::CloneEvent)?,
- Some(gpu_device_tube),
- NonZeroU8::new(1).unwrap(), // number of scanouts
- resource_bridges,
- display_backends,
- cfg.gpu_parameters.as_ref().unwrap(),
- event_devices,
- map_request,
- cfg.sandbox,
- virtio::base_features(cfg.protected_vm),
- cfg.wayland_socket_paths.clone(),
- mem.clone(),
- );
-
- let jail = match simple_jail(&cfg, "gpu_device")? {
- Some(mut jail) => {
- // Create a tmpfs in the device's root directory so that we can bind mount the
- // dri directory into it. The size=67108864 is size=64*1024*1024 or size=64MB.
- jail.mount_with_data(
- Path::new("none"),
- Path::new("/"),
- "tmpfs",
- (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
- "size=67108864",
- )?;
-
- // Device nodes required for DRM.
- let sys_dev_char_path = Path::new("/sys/dev/char");
- jail.mount_bind(sys_dev_char_path, sys_dev_char_path, false)?;
- let sys_devices_path = Path::new("/sys/devices");
- jail.mount_bind(sys_devices_path, sys_devices_path, false)?;
-
- let drm_dri_path = Path::new("/dev/dri");
- if drm_dri_path.exists() {
- jail.mount_bind(drm_dri_path, drm_dri_path, false)?;
- }
-
- // Prepare GPU shader disk cache directory.
- if let Some(cache_dir) = cfg
- .gpu_parameters
- .as_ref()
- .and_then(|params| params.cache_path.as_ref())
- {
- if cfg!(any(target_arch = "arm", target_arch = "aarch64")) && cfg.sandbox {
- warn!("shader caching not yet supported on ARM with sandbox enabled");
- env::set_var("MESA_GLSL_CACHE_DISABLE", "true");
- } else {
- env::set_var("MESA_GLSL_CACHE_DISABLE", "false");
- env::set_var("MESA_GLSL_CACHE_DIR", cache_dir);
- if let Some(cache_size) = cfg
- .gpu_parameters
- .as_ref()
- .and_then(|params| params.cache_size.as_ref())
- {
- env::set_var("MESA_GLSL_CACHE_MAX_SIZE", cache_size);
- }
- let shadercache_path = Path::new(cache_dir);
- jail.mount_bind(shadercache_path, shadercache_path, true)?;
- }
- }
-
- // If the ARM specific devices exist on the host, bind mount them in.
- let mali0_path = Path::new("/dev/mali0");
- if mali0_path.exists() {
- jail.mount_bind(mali0_path, mali0_path, true)?;
- }
-
- let pvr_sync_path = Path::new("/dev/pvr_sync");
- if pvr_sync_path.exists() {
- jail.mount_bind(pvr_sync_path, pvr_sync_path, true)?;
- }
-
- // If the udmabuf driver exists on the host, bind mount it in.
- let udmabuf_path = Path::new("/dev/udmabuf");
- if udmabuf_path.exists() {
- jail.mount_bind(udmabuf_path, udmabuf_path, true)?;
- }
-
- // Libraries that are required when mesa drivers are dynamically loaded.
- let lib_dirs = &[
- "/usr/lib",
- "/usr/lib64",
- "/lib",
- "/lib64",
- "/usr/share/vulkan",
- ];
- for dir in lib_dirs {
- let dir_path = Path::new(dir);
- if dir_path.exists() {
- jail.mount_bind(dir_path, dir_path, false)?;
- }
- }
-
- // Bind mount the wayland socket into jail's root. This is necessary since each
- // new wayland context must open() the socket. Don't bind mount the camera socket
- // since it seems to cause problems on ARCVM (b/180126126) + Mali. It's unclear if
- // camera team will opt for virtio-camera or continue using virtio-wl, so this should
- // be fine for now.
- if let Some(path) = wayland_socket_path {
- jail.mount_bind(path, jailed_wayland_path, true)?;
- }
-
- add_crosvm_user_to_jail(&mut jail, "gpu")?;
-
- // pvr driver requires read access to /proc/self/task/*/comm.
- let proc_path = Path::new("/proc");
- jail.mount(
- proc_path,
- proc_path,
- "proc",
- (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC | libc::MS_RDONLY) as usize,
- )?;
-
- // To enable perfetto tracing, we need to give access to the perfetto service IPC
- // endpoints.
- let perfetto_path = Path::new("/run/perfetto");
- if perfetto_path.exists() {
- jail.mount_bind(perfetto_path, perfetto_path, true)?;
- }
-
- Some(jail)
- }
- None => None,
- };
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail,
- })
-}
-
-fn create_wayland_device(
- cfg: &Config,
- control_tube: Tube,
- resource_bridge: Option<Tube>,
-) -> DeviceResult {
- let wayland_socket_dirs = cfg
- .wayland_socket_paths
- .iter()
- .map(|(_name, path)| path.parent())
- .collect::<Option<Vec<_>>>()
- .ok_or(Error::InvalidWaylandPath)?;
-
- let features = virtio::base_features(cfg.protected_vm);
- let dev = virtio::Wl::new(
- features,
- cfg.wayland_socket_paths.clone(),
- control_tube,
- resource_bridge,
- )
- .map_err(Error::WaylandDeviceNew)?;
-
- let jail = match simple_jail(&cfg, "wl_device")? {
- Some(mut jail) => {
- // Create a tmpfs in the device's root directory so that we can bind mount the wayland
- // socket directory into it. The size=67108864 is size=64*1024*1024 or size=64MB.
- jail.mount_with_data(
- Path::new("none"),
- Path::new("/"),
- "tmpfs",
- (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
- "size=67108864",
- )?;
-
- // Bind mount the wayland socket's directory into jail's root. This is necessary since
- // each new wayland context must open() the socket. If the wayland socket is ever
- // destroyed and remade in the same host directory, new connections will be possible
- // without restarting the wayland device.
- for dir in &wayland_socket_dirs {
- jail.mount_bind(dir, dir, true)?;
- }
- add_crosvm_user_to_jail(&mut jail, "Wayland")?;
-
- Some(jail)
- }
- None => None,
- };
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail,
- })
-}
-
-#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
-fn create_video_device(
- cfg: &Config,
- typ: devices::virtio::VideoDeviceType,
- resource_bridge: Tube,
-) -> DeviceResult {
- let jail = match simple_jail(&cfg, "video_device")? {
- Some(mut jail) => {
- match typ {
- devices::virtio::VideoDeviceType::Decoder => {
- add_crosvm_user_to_jail(&mut jail, "video-decoder")?
- }
- devices::virtio::VideoDeviceType::Encoder => {
- add_crosvm_user_to_jail(&mut jail, "video-encoder")?
- }
- };
-
- // Create a tmpfs in the device's root directory so that we can bind mount files.
- jail.mount_with_data(
- Path::new("none"),
- Path::new("/"),
- "tmpfs",
- (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
- "size=67108864",
- )?;
-
- // Render node for libvda.
- let dev_dri_path = Path::new("/dev/dri/renderD128");
- jail.mount_bind(dev_dri_path, dev_dri_path, false)?;
-
- #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
- {
- // Device nodes used by libdrm through minigbm in libvda on AMD devices.
- let sys_dev_char_path = Path::new("/sys/dev/char");
- jail.mount_bind(sys_dev_char_path, sys_dev_char_path, false)?;
- let sys_devices_path = Path::new("/sys/devices");
- jail.mount_bind(sys_devices_path, sys_devices_path, false)?;
-
- // Required for loading dri libraries loaded by minigbm on AMD devices.
- let lib_dir = Path::new("/usr/lib64");
- jail.mount_bind(lib_dir, lib_dir, false)?;
- }
-
- // Device nodes required by libchrome which establishes Mojo connection in libvda.
- let dev_urandom_path = Path::new("/dev/urandom");
- jail.mount_bind(dev_urandom_path, dev_urandom_path, false)?;
- let system_bus_socket_path = Path::new("/run/dbus/system_bus_socket");
- jail.mount_bind(system_bus_socket_path, system_bus_socket_path, true)?;
-
- Some(jail)
- }
- None => None,
- };
-
- Ok(VirtioDeviceStub {
- dev: Box::new(devices::virtio::VideoDevice::new(
- virtio::base_features(cfg.protected_vm),
- typ,
- Some(resource_bridge),
- )),
- jail,
- })
-}
-
-#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
-fn register_video_device(
- devs: &mut Vec<VirtioDeviceStub>,
- video_tube: Tube,
- cfg: &Config,
- typ: devices::virtio::VideoDeviceType,
-) -> std::result::Result<(), Error> {
- devs.push(create_video_device(cfg, typ, video_tube)?);
- Ok(())
-}
-
-fn create_vhost_vsock_device(cfg: &Config, cid: u64, mem: &GuestMemory) -> DeviceResult {
- let features = virtio::base_features(cfg.protected_vm);
- let dev = virtio::vhost::Vsock::new(&cfg.vhost_vsock_device_path, features, cid, mem)
- .map_err(Error::VhostVsockDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: simple_jail(&cfg, "vhost_vsock_device")?,
- })
-}
-
-fn create_fs_device(
- cfg: &Config,
- uid_map: &str,
- gid_map: &str,
- src: &Path,
- tag: &str,
- fs_cfg: virtio::fs::passthrough::Config,
- device_tube: Tube,
-) -> DeviceResult {
- let max_open_files = get_max_open_files()?;
- let j = if cfg.sandbox {
- let seccomp_policy = cfg.seccomp_policy_dir.join("fs_device");
- let config = SandboxConfig {
- limit_caps: false,
- uid_map: Some(uid_map),
- gid_map: Some(gid_map),
- log_failures: cfg.seccomp_log_failures,
- seccomp_policy: &seccomp_policy,
- };
- let mut jail = create_base_minijail(src, Some(max_open_files), Some(&config))?;
- // We want bind mounts from the parent namespaces to propagate into the fs device's
- // namespace.
- jail.set_remount_mode(libc::MS_SLAVE);
-
- jail
- } else {
- create_base_minijail(src, Some(max_open_files), None)?
- };
-
- let features = virtio::base_features(cfg.protected_vm);
- // TODO(chirantan): Use more than one worker once the kernel driver has been fixed to not panic
- // when num_queues > 1.
- let dev =
- virtio::fs::Fs::new(features, tag, 1, fs_cfg, device_tube).map_err(Error::FsDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: Some(j),
- })
-}
-
-fn create_9p_device(
- cfg: &Config,
- uid_map: &str,
- gid_map: &str,
- src: &Path,
- tag: &str,
- mut p9_cfg: p9::Config,
-) -> DeviceResult {
- let max_open_files = get_max_open_files()?;
- let (jail, root) = if cfg.sandbox {
- let seccomp_policy = cfg.seccomp_policy_dir.join("9p_device");
- let config = SandboxConfig {
- limit_caps: false,
- uid_map: Some(uid_map),
- gid_map: Some(gid_map),
- log_failures: cfg.seccomp_log_failures,
- seccomp_policy: &seccomp_policy,
- };
-
- let mut jail = create_base_minijail(src, Some(max_open_files), Some(&config))?;
- // We want bind mounts from the parent namespaces to propagate into the 9p server's
- // namespace.
- jail.set_remount_mode(libc::MS_SLAVE);
-
- // The shared directory becomes the root of the device's file system.
- let root = Path::new("/");
- (Some(jail), root)
- } else {
- // There's no mount namespace so we tell the server to treat the source directory as the
- // root.
- (None, src)
- };
-
- let features = virtio::base_features(cfg.protected_vm);
- p9_cfg.root = root.into();
- let dev = virtio::P9::new(features, tag, p9_cfg).map_err(Error::P9DeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail,
- })
-}
-
-fn create_pmem_device(
- cfg: &Config,
- vm: &mut impl Vm,
- resources: &mut SystemAllocator,
- disk: &DiskOption,
- index: usize,
- pmem_device_tube: Tube,
-) -> DeviceResult {
- // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
- let fd: File = if disk.path.parent() == Some(Path::new("/proc/self/fd")) {
- // Safe because we will validate |raw_fd|.
- unsafe { File::from_raw_descriptor(raw_descriptor_from_path(&disk.path)?) }
- } else {
- OpenOptions::new()
- .read(true)
- .write(!disk.read_only)
- .open(&disk.path)
- .map_err(|e| Error::Disk(disk.path.to_path_buf(), e))?
- };
-
- let arena_size = {
- let metadata =
- std::fs::metadata(&disk.path).map_err(|e| Error::Disk(disk.path.to_path_buf(), e))?;
- let disk_len = metadata.len();
- // Linux requires pmem region sizes to be 2 MiB aligned. Linux will fill any partial page
- // at the end of an mmap'd file and won't write back beyond the actual file length, but if
- // we just align the size of the file to 2 MiB then access beyond the last page of the
- // mapped file will generate SIGBUS. So use a memory mapping arena that will provide
- // padding up to 2 MiB.
- let alignment = 2 * 1024 * 1024;
- let align_adjust = if disk_len % alignment != 0 {
- alignment - (disk_len % alignment)
- } else {
- 0
- };
- disk_len
- .checked_add(align_adjust)
- .ok_or(Error::PmemDeviceImageTooBig)?
- };
-
- let protection = {
- if disk.read_only {
- Protection::read()
- } else {
- Protection::read_write()
- }
- };
-
- let arena = {
- // Conversion from u64 to usize may fail on 32bit system.
- let arena_size = usize::try_from(arena_size).map_err(|_| Error::PmemDeviceImageTooBig)?;
-
- let mut arena = MemoryMappingArena::new(arena_size).map_err(Error::ReservePmemMemory)?;
- arena
- .add_fd_offset_protection(0, arena_size, &fd, 0, protection)
- .map_err(Error::ReservePmemMemory)?;
- arena
- };
-
- let mapping_address = resources
- .mmio_allocator(MmioType::High)
- .allocate_with_align(
- arena_size,
- Alloc::PmemDevice(index),
- format!("pmem_disk_image_{}", index),
- // Linux kernel requires pmem namespaces to be 128 MiB aligned.
- 128 * 1024 * 1024, /* 128 MiB */
- )
- .map_err(Error::AllocatePmemDeviceAddress)?;
-
- let slot = vm
- .add_memory_region(
- GuestAddress(mapping_address),
- Box::new(arena),
- /* read_only = */ disk.read_only,
- /* log_dirty_pages = */ false,
- )
- .map_err(Error::AddPmemDeviceMemory)?;
-
- let dev = virtio::Pmem::new(
- virtio::base_features(cfg.protected_vm),
- fd,
- GuestAddress(mapping_address),
- slot,
- arena_size,
- Some(pmem_device_tube),
- )
- .map_err(Error::PmemDeviceNew)?;
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev) as Box<dyn VirtioDevice>,
- jail: simple_jail(&cfg, "pmem_device")?,
- })
-}
-
-fn create_console_device(cfg: &Config, param: &SerialParameters) -> DeviceResult {
- let mut keep_rds = Vec::new();
- let evt = Event::new().map_err(Error::CreateEvent)?;
- let dev = param
- .create_serial_device::<Console>(cfg.protected_vm, &evt, &mut keep_rds)
- .map_err(Error::CreateConsole)?;
-
- let jail = match simple_jail(&cfg, "serial")? {
- Some(mut jail) => {
- // Create a tmpfs in the device's root directory so that we can bind mount the
- // log socket directory into it.
- // The size=67108864 is size=64*1024*1024 or size=64MB.
- jail.mount_with_data(
- Path::new("none"),
- Path::new("/"),
- "tmpfs",
- (libc::MS_NODEV | libc::MS_NOEXEC | libc::MS_NOSUID) as usize,
- "size=67108864",
- )?;
- add_crosvm_user_to_jail(&mut jail, "serial")?;
- let res = param.add_bind_mounts(&mut jail);
- if res.is_err() {
- error!("failed to add bind mounts for console device");
- }
- Some(jail)
- }
- None => None,
- };
-
- Ok(VirtioDeviceStub {
- dev: Box::new(dev),
- jail, // TODO(dverkamp): use a separate policy for console?
- })
-}
-
-// gpu_device_tube is not used when GPU support is disabled.
-#[cfg_attr(not(feature = "gpu"), allow(unused_variables))]
-fn create_virtio_devices(
- cfg: &Config,
- mem: &GuestMemory,
- vm: &mut impl Vm,
- resources: &mut SystemAllocator,
- _exit_evt: &Event,
- wayland_device_tube: Tube,
- gpu_device_tube: Tube,
- balloon_device_tube: Tube,
- disk_device_tubes: &mut Vec<Tube>,
- pmem_device_tubes: &mut Vec<Tube>,
- map_request: Arc<Mutex<Option<ExternalMapping>>>,
- fs_device_tubes: &mut Vec<Tube>,
-) -> DeviceResult<Vec<VirtioDeviceStub>> {
- let mut devs = Vec::new();
-
- for (_, param) in cfg
- .serial_parameters
- .iter()
- .filter(|(_k, v)| v.hardware == SerialHardware::VirtioConsole)
- {
- let dev = create_console_device(cfg, param)?;
- devs.push(dev);
- }
-
- for disk in &cfg.disks {
- let disk_device_tube = disk_device_tubes.remove(0);
- devs.push(create_block_device(cfg, disk, disk_device_tube)?);
- }
-
- for blk in &cfg.vhost_user_blk {
- devs.push(create_vhost_user_block_device(cfg, blk)?);
- }
-
- for (index, pmem_disk) in cfg.pmem_devices.iter().enumerate() {
- let pmem_device_tube = pmem_device_tubes.remove(0);
- devs.push(create_pmem_device(
- cfg,
- vm,
- resources,
- pmem_disk,
- index,
- pmem_device_tube,
- )?);
- }
-
- devs.push(create_rng_device(cfg)?);
-
- #[cfg(feature = "tpm")]
- {
- if cfg.software_tpm {
- devs.push(create_tpm_device(cfg)?);
- }
- }
-
- if let Some(single_touch_spec) = &cfg.virtio_single_touch {
- devs.push(create_single_touch_device(cfg, single_touch_spec)?);
- }
-
- if let Some(multi_touch_spec) = &cfg.virtio_multi_touch {
- devs.push(create_multi_touch_device(cfg, multi_touch_spec)?);
- }
-
- if let Some(trackpad_spec) = &cfg.virtio_trackpad {
- devs.push(create_trackpad_device(cfg, trackpad_spec)?);
- }
-
- if let Some(mouse_socket) = &cfg.virtio_mouse {
- devs.push(create_mouse_device(cfg, mouse_socket)?);
- }
-
- if let Some(keyboard_socket) = &cfg.virtio_keyboard {
- devs.push(create_keyboard_device(cfg, keyboard_socket)?);
- }
-
- if let Some(switches_socket) = &cfg.virtio_switches {
- devs.push(create_switches_device(cfg, switches_socket)?);
- }
-
- for dev_path in &cfg.virtio_input_evdevs {
- devs.push(create_vinput_device(cfg, dev_path)?);
- }
-
- devs.push(create_balloon_device(cfg, balloon_device_tube)?);
-
- // We checked above that if the IP is defined, then the netmask is, too.
- for tap_fd in &cfg.tap_fd {
- devs.push(create_tap_net_device(cfg, *tap_fd)?);
- }
-
- if let (Some(host_ip), Some(netmask), Some(mac_address)) =
- (cfg.host_ip, cfg.netmask, cfg.mac_address)
- {
- if !cfg.vhost_user_net.is_empty() {
- return Err(Error::VhostUserNetWithNetArgs);
- }
- devs.push(create_net_device(cfg, host_ip, netmask, mac_address, mem)?);
- }
-
- for net in &cfg.vhost_user_net {
- devs.push(create_vhost_user_net_device(cfg, net)?);
- }
-
- #[cfg_attr(not(feature = "gpu"), allow(unused_mut))]
- let mut resource_bridges = Vec::<Tube>::new();
-
- if !cfg.wayland_socket_paths.is_empty() {
- #[cfg_attr(not(feature = "gpu"), allow(unused_mut))]
- let mut wl_resource_bridge = None::<Tube>;
-
- #[cfg(feature = "gpu")]
- {
- if cfg.gpu_parameters.is_some() {
- let (wl_socket, gpu_socket) = Tube::pair().map_err(Error::CreateTube)?;
- resource_bridges.push(gpu_socket);
- wl_resource_bridge = Some(wl_socket);
- }
- }
-
- devs.push(create_wayland_device(
- cfg,
- wayland_device_tube,
- wl_resource_bridge,
- )?);
- }
-
- #[cfg(feature = "video-decoder")]
- let video_dec_tube = if cfg.video_dec {
- let (video_tube, gpu_tube) = Tube::pair().map_err(Error::CreateTube)?;
- resource_bridges.push(gpu_tube);
- Some(video_tube)
- } else {
- None
- };
-
- #[cfg(feature = "video-encoder")]
- let video_enc_tube = if cfg.video_enc {
- let (video_tube, gpu_tube) = Tube::pair().map_err(Error::CreateTube)?;
- resource_bridges.push(gpu_tube);
- Some(video_tube)
- } else {
- None
- };
-
- #[cfg(feature = "gpu")]
- {
- if let Some(gpu_parameters) = &cfg.gpu_parameters {
- let mut event_devices = Vec::new();
- if cfg.display_window_mouse {
- let (event_device_socket, virtio_dev_socket) =
- UnixStream::pair().map_err(Error::CreateSocket)?;
- let (multi_touch_width, multi_touch_height) = cfg
- .virtio_multi_touch
- .as_ref()
- .map(|multi_touch_spec| multi_touch_spec.get_size())
- .unwrap_or((gpu_parameters.display_width, gpu_parameters.display_height));
- let dev = virtio::new_multi_touch(
- virtio_dev_socket,
- multi_touch_width,
- multi_touch_height,
- virtio::base_features(cfg.protected_vm),
- )
- .map_err(Error::InputDeviceNew)?;
- devs.push(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: simple_jail(&cfg, "input_device")?,
- });
- event_devices.push(EventDevice::touchscreen(event_device_socket));
- }
- if cfg.display_window_keyboard {
- let (event_device_socket, virtio_dev_socket) =
- UnixStream::pair().map_err(Error::CreateSocket)?;
- let dev = virtio::new_keyboard(
- virtio_dev_socket,
- virtio::base_features(cfg.protected_vm),
- )
- .map_err(Error::InputDeviceNew)?;
- devs.push(VirtioDeviceStub {
- dev: Box::new(dev),
- jail: simple_jail(&cfg, "input_device")?,
- });
- event_devices.push(EventDevice::keyboard(event_device_socket));
- }
- devs.push(create_gpu_device(
- cfg,
- _exit_evt,
- gpu_device_tube,
- resource_bridges,
- // Use the unnamed socket for GPU display screens.
- cfg.wayland_socket_paths.get(""),
- cfg.x_display.clone(),
- event_devices,
- map_request,
- mem,
- )?);
- }
- }
-
- #[cfg(feature = "video-decoder")]
- {
- if let Some(video_dec_tube) = video_dec_tube {
- register_video_device(
- &mut devs,
- video_dec_tube,
- cfg,
- devices::virtio::VideoDeviceType::Decoder,
- )?;
- }
- }
-
- #[cfg(feature = "video-encoder")]
- {
- if let Some(video_enc_tube) = video_enc_tube {
- register_video_device(
- &mut devs,
- video_enc_tube,
- cfg,
- devices::virtio::VideoDeviceType::Encoder,
- )?;
- }
- }
-
- if let Some(cid) = cfg.cid {
- devs.push(create_vhost_vsock_device(cfg, cid, mem)?);
- }
-
- for vhost_user_fs in &cfg.vhost_user_fs {
- devs.push(create_vhost_user_fs_device(cfg, &vhost_user_fs)?);
- }
-
- for shared_dir in &cfg.shared_dirs {
- let SharedDir {
- src,
- tag,
- kind,
- uid_map,
- gid_map,
- fs_cfg,
- p9_cfg,
- } = shared_dir;
-
- let dev = match kind {
- SharedDirKind::FS => {
- let device_tube = fs_device_tubes.remove(0);
- create_fs_device(cfg, uid_map, gid_map, src, tag, fs_cfg.clone(), device_tube)?
- }
- SharedDirKind::P9 => create_9p_device(cfg, uid_map, gid_map, src, tag, p9_cfg.clone())?,
- };
- devs.push(dev);
- }
-
- Ok(devs)
-}
-
-fn create_devices(
- cfg: &Config,
- mem: &GuestMemory,
- vm: &mut impl Vm,
- resources: &mut SystemAllocator,
- exit_evt: &Event,
- control_tubes: &mut Vec<TaggedControlTube>,
- wayland_device_tube: Tube,
- gpu_device_tube: Tube,
- balloon_device_tube: Tube,
- disk_device_tubes: &mut Vec<Tube>,
- pmem_device_tubes: &mut Vec<Tube>,
- fs_device_tubes: &mut Vec<Tube>,
- usb_provider: HostBackendDeviceProvider,
- map_request: Arc<Mutex<Option<ExternalMapping>>>,
-) -> DeviceResult<Vec<(Box<dyn PciDevice>, Option<Minijail>)>> {
- let stubs = create_virtio_devices(
- &cfg,
- mem,
- vm,
- resources,
- exit_evt,
- wayland_device_tube,
- gpu_device_tube,
- balloon_device_tube,
- disk_device_tubes,
- pmem_device_tubes,
- map_request,
- fs_device_tubes,
- )?;
-
- let mut pci_devices = Vec::new();
-
- for stub in stubs {
- let (msi_host_tube, msi_device_tube) = Tube::pair().map_err(Error::CreateTube)?;
- control_tubes.push(TaggedControlTube::VmIrq(msi_host_tube));
- let dev = VirtioPciDevice::new(mem.clone(), stub.dev, msi_device_tube)
- .map_err(Error::VirtioPciDev)?;
- let dev = Box::new(dev) as Box<dyn PciDevice>;
- pci_devices.push((dev, stub.jail));
- }
-
- #[cfg(feature = "audio")]
- for ac97_param in &cfg.ac97_parameters {
- let dev = Ac97Dev::try_new(mem.clone(), ac97_param.clone()).map_err(Error::CreateAc97)?;
- let jail = simple_jail(&cfg, dev.minijail_policy())?;
- pci_devices.push((Box::new(dev), jail));
- }
-
- // Create xhci controller.
- let usb_controller = Box::new(XhciController::new(mem.clone(), usb_provider));
- pci_devices.push((usb_controller, simple_jail(&cfg, "xhci")?));
-
- if !cfg.vfio.is_empty() {
- let vfio_container = Arc::new(Mutex::new(
- VfioContainer::new().map_err(Error::CreateVfioDevice)?,
- ));
-
- for vfio_path in &cfg.vfio {
- // create MSI, MSI-X, and Mem request sockets for each vfio device
- let (vfio_host_tube_msi, vfio_device_tube_msi) =
- Tube::pair().map_err(Error::CreateTube)?;
- control_tubes.push(TaggedControlTube::VmIrq(vfio_host_tube_msi));
-
- let (vfio_host_tube_msix, vfio_device_tube_msix) =
- Tube::pair().map_err(Error::CreateTube)?;
- control_tubes.push(TaggedControlTube::VmIrq(vfio_host_tube_msix));
-
- let (vfio_host_tube_mem, vfio_device_tube_mem) =
- Tube::pair().map_err(Error::CreateTube)?;
- control_tubes.push(TaggedControlTube::VmMemory(vfio_host_tube_mem));
-
- let vfiodevice = VfioDevice::new(vfio_path.as_path(), vm, mem, vfio_container.clone())
- .map_err(Error::CreateVfioDevice)?;
- let mut vfiopcidevice = Box::new(VfioPciDevice::new(
- vfiodevice,
- vfio_device_tube_msi,
- vfio_device_tube_msix,
- vfio_device_tube_mem,
- ));
- // early reservation for pass-through PCI devices.
- if vfiopcidevice.allocate_address(resources).is_err() {
- warn!(
- "address reservation failed for vfio {}",
- vfiopcidevice.debug_label()
- );
- }
- pci_devices.push((vfiopcidevice, simple_jail(&cfg, "vfio_device")?));
- }
- }
-
- Ok(pci_devices)
-}
-
-#[derive(Copy, Clone)]
-#[cfg_attr(not(feature = "tpm"), allow(dead_code))]
-struct Ids {
- uid: uid_t,
- gid: gid_t,
-}
-
-// Set the uid/gid for the jailed process and give a basic id map. This is
-// required for bind mounts to work.
-fn add_crosvm_user_to_jail(jail: &mut Minijail, feature: &str) -> Result<Ids> {
- let crosvm_user_group = CStr::from_bytes_with_nul(b"crosvm\0").unwrap();
-
- let crosvm_uid = match get_user_id(&crosvm_user_group) {
- Ok(u) => u,
- Err(e) => {
- warn!("falling back to current user id for {}: {}", feature, e);
- geteuid()
- }
- };
-
- let crosvm_gid = match get_group_id(&crosvm_user_group) {
- Ok(u) => u,
- Err(e) => {
- warn!("falling back to current group id for {}: {}", feature, e);
- getegid()
- }
- };
-
- jail.change_uid(crosvm_uid);
- jail.change_gid(crosvm_gid);
- jail.uidmap(&format!("{0} {0} 1", crosvm_uid))
- .map_err(Error::SettingUidMap)?;
- jail.gidmap(&format!("{0} {0} 1", crosvm_gid))
- .map_err(Error::SettingGidMap)?;
-
- Ok(Ids {
- uid: crosvm_uid,
- gid: crosvm_gid,
- })
-}
-
-fn raw_descriptor_from_path(path: &Path) -> Result<RawDescriptor> {
- if !path.is_file() {
- return Err(Error::InvalidFdPath);
- }
- let raw_descriptor = path
- .file_name()
- .and_then(|fd_osstr| fd_osstr.to_str())
- .and_then(|fd_str| fd_str.parse::<c_int>().ok())
- .ok_or(Error::InvalidFdPath)?;
- validate_raw_descriptor(raw_descriptor).map_err(Error::ValidateRawDescriptor)
-}
-
-trait IntoUnixStream {
- fn into_unix_stream(self) -> Result<UnixStream>;
-}
-
-impl<'a> IntoUnixStream for &'a Path {
- fn into_unix_stream(self) -> Result<UnixStream> {
- if self.parent() == Some(Path::new("/proc/self/fd")) {
- // Safe because we will validate |raw_fd|.
- unsafe { Ok(UnixStream::from_raw_fd(raw_descriptor_from_path(self)?)) }
- } else {
- UnixStream::connect(self).map_err(Error::InputEventsOpen)
- }
- }
-}
-impl<'a> IntoUnixStream for &'a PathBuf {
- fn into_unix_stream(self) -> Result<UnixStream> {
- self.as_path().into_unix_stream()
- }
-}
-
-impl IntoUnixStream for UnixStream {
- fn into_unix_stream(self) -> Result<UnixStream> {
- Ok(self)
- }
-}
-
-fn setup_vcpu_signal_handler<T: Vcpu>(use_hypervisor_signals: bool) -> Result<()> {
- if use_hypervisor_signals {
- unsafe {
- extern "C" fn handle_signal(_: c_int) {}
- // Our signal handler does nothing and is trivially async signal safe.
- register_rt_signal_handler(SIGRTMIN() + 0, handle_signal)
- .map_err(Error::RegisterSignalHandler)?;
- }
- block_signal(SIGRTMIN() + 0).map_err(Error::BlockSignal)?;
- } else {
- unsafe {
- extern "C" fn handle_signal<T: Vcpu>(_: c_int) {
- T::set_local_immediate_exit(true);
- }
- register_rt_signal_handler(SIGRTMIN() + 0, handle_signal::<T>)
- .map_err(Error::RegisterSignalHandler)?;
- }
- }
- Ok(())
-}
-
-// Sets up a vcpu and converts it into a runnable vcpu.
-fn runnable_vcpu<V>(
- cpu_id: usize,
- vcpu: Option<V>,
- vm: impl VmArch,
- irq_chip: &mut impl IrqChipArch,
- vcpu_count: usize,
- run_rt: bool,
- vcpu_affinity: Vec<usize>,
- no_smt: bool,
- has_bios: bool,
- use_hypervisor_signals: bool,
-) -> Result<(V, VcpuRunHandle)>
-where
- V: VcpuArch,
-{
- let mut vcpu = match vcpu {
- Some(v) => v,
- None => {
- // If vcpu is None, it means this arch/hypervisor requires create_vcpu to be called from
- // the vcpu thread.
- match vm
- .create_vcpu(cpu_id)
- .map_err(Error::CreateVcpu)?
- .downcast::<V>()
- {
- Ok(v) => *v,
- Err(_) => panic!("VM created wrong type of VCPU"),
- }
- }
- };
-
- irq_chip
- .add_vcpu(cpu_id, &vcpu)
- .map_err(Error::AddIrqChipVcpu)?;
-
- if !vcpu_affinity.is_empty() {
- if let Err(e) = set_cpu_affinity(vcpu_affinity) {
- error!("Failed to set CPU affinity: {}", e);
- }
- }
-
- Arch::configure_vcpu(
- vm.get_memory(),
- vm.get_hypervisor(),
- irq_chip,
- &mut vcpu,
- cpu_id,
- vcpu_count,
- has_bios,
- no_smt,
- )
- .map_err(Error::ConfigureVcpu)?;
-
- #[cfg(feature = "chromeos")]
- if let Err(e) = base::sched::enable_core_scheduling() {
- error!("Failed to enable core scheduling: {}", e);
- }
-
- if run_rt {
- const DEFAULT_VCPU_RT_LEVEL: u16 = 6;
- if let Err(e) = set_rt_prio_limit(u64::from(DEFAULT_VCPU_RT_LEVEL))
- .and_then(|_| set_rt_round_robin(i32::from(DEFAULT_VCPU_RT_LEVEL)))
- {
- warn!("Failed to set vcpu to real time: {}", e);
- }
- }
-
- if use_hypervisor_signals {
- let mut v = get_blocked_signals().map_err(Error::GetSignalMask)?;
- v.retain(|&x| x != SIGRTMIN() + 0);
- vcpu.set_signal_mask(&v).map_err(Error::SettingSignalMask)?;
- }
-
- let vcpu_run_handle = vcpu
- .take_run_handle(Some(SIGRTMIN() + 0))
- .map_err(Error::RunnableVcpu)?;
-
- Ok((vcpu, vcpu_run_handle))
-}
-
-#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
-fn handle_debug_msg<V>(
- cpu_id: usize,
- vcpu: &V,
- guest_mem: &GuestMemory,
- d: VcpuDebug,
- reply_tube: &mpsc::Sender<VcpuDebugStatusMessage>,
-) -> Result<()>
-where
- V: VcpuArch + 'static,
-{
- match d {
- VcpuDebug::ReadRegs => {
- let msg = VcpuDebugStatusMessage {
- cpu: cpu_id as usize,
- msg: VcpuDebugStatus::RegValues(
- Arch::debug_read_registers(vcpu as &V).map_err(Error::HandleDebugCommand)?,
- ),
- };
- reply_tube
- .send(msg)
- .map_err(|e| Error::SendDebugStatus(Box::new(e)))
- }
- VcpuDebug::WriteRegs(regs) => {
- Arch::debug_write_registers(vcpu as &V, &regs).map_err(Error::HandleDebugCommand)?;
- reply_tube
- .send(VcpuDebugStatusMessage {
- cpu: cpu_id as usize,
- msg: VcpuDebugStatus::CommandComplete,
- })
- .map_err(|e| Error::SendDebugStatus(Box::new(e)))
- }
- VcpuDebug::ReadMem(vaddr, len) => {
- let msg = VcpuDebugStatusMessage {
- cpu: cpu_id as usize,
- msg: VcpuDebugStatus::MemoryRegion(
- Arch::debug_read_memory(vcpu as &V, guest_mem, vaddr, len)
- .unwrap_or(Vec::new()),
- ),
- };
- reply_tube
- .send(msg)
- .map_err(|e| Error::SendDebugStatus(Box::new(e)))
- }
- VcpuDebug::WriteMem(vaddr, buf) => {
- Arch::debug_write_memory(vcpu as &V, guest_mem, vaddr, &buf)
- .map_err(Error::HandleDebugCommand)?;
- reply_tube
- .send(VcpuDebugStatusMessage {
- cpu: cpu_id as usize,
- msg: VcpuDebugStatus::CommandComplete,
- })
- .map_err(|e| Error::SendDebugStatus(Box::new(e)))
- }
- VcpuDebug::EnableSinglestep => {
- Arch::debug_enable_singlestep(vcpu as &V).map_err(Error::HandleDebugCommand)?;
- reply_tube
- .send(VcpuDebugStatusMessage {
- cpu: cpu_id as usize,
- msg: VcpuDebugStatus::CommandComplete,
- })
- .map_err(|e| Error::SendDebugStatus(Box::new(e)))
- }
- VcpuDebug::SetHwBreakPoint(addrs) => {
- Arch::debug_set_hw_breakpoints(vcpu as &V, &addrs)
- .map_err(Error::HandleDebugCommand)?;
- reply_tube
- .send(VcpuDebugStatusMessage {
- cpu: cpu_id as usize,
- msg: VcpuDebugStatus::CommandComplete,
- })
- .map_err(|e| Error::SendDebugStatus(Box::new(e)))
- }
- }
-}
-
-fn run_vcpu<V>(
- cpu_id: usize,
- vcpu: Option<V>,
- vm: impl VmArch + 'static,
- mut irq_chip: impl IrqChipArch + 'static,
- vcpu_count: usize,
- run_rt: bool,
- vcpu_affinity: Vec<usize>,
- no_smt: bool,
- start_barrier: Arc<Barrier>,
- has_bios: bool,
- io_bus: devices::Bus,
- mmio_bus: devices::Bus,
- exit_evt: Event,
- requires_pvclock_ctrl: bool,
- from_main_tube: mpsc::Receiver<VcpuControl>,
- use_hypervisor_signals: bool,
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))] to_gdb_tube: Option<
- mpsc::Sender<VcpuDebugStatusMessage>,
- >,
-) -> Result<JoinHandle<()>>
-where
- V: VcpuArch + 'static,
-{
- thread::Builder::new()
- .name(format!("crosvm_vcpu{}", cpu_id))
- .spawn(move || {
- // The VCPU thread must trigger the `exit_evt` in all paths, and a `ScopedEvent`'s Drop
- // implementation accomplishes that.
- let _scoped_exit_evt = ScopedEvent::from(exit_evt);
-
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- let guest_mem = vm.get_memory().clone();
- let runnable_vcpu = runnable_vcpu(
- cpu_id,
- vcpu,
- vm,
- &mut irq_chip,
- vcpu_count,
- run_rt,
- vcpu_affinity,
- no_smt,
- has_bios,
- use_hypervisor_signals,
- );
-
- start_barrier.wait();
-
- let (vcpu, vcpu_run_handle) = match runnable_vcpu {
- Ok(v) => v,
- Err(e) => {
- error!("failed to start vcpu {}: {}", cpu_id, e);
- return;
- }
- };
-
- let mut run_mode = VmRunMode::Running;
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- if to_gdb_tube.is_some() {
- // Wait until a GDB client attaches
- run_mode = VmRunMode::Breakpoint;
- }
-
- let mut interrupted_by_signal = false;
-
- 'vcpu_loop: loop {
- // Start by checking for messages to process and the run state of the CPU.
- // An extra check here for Running so there isn't a need to call recv unless a
- // message is likely to be ready because a signal was sent.
- if interrupted_by_signal || run_mode != VmRunMode::Running {
- 'state_loop: loop {
- // Tries to get a pending message without blocking first.
- let msg = match from_main_tube.try_recv() {
- Ok(m) => m,
- Err(mpsc::TryRecvError::Empty) if run_mode == VmRunMode::Running => {
- // If the VM is running and no message is pending, the state won't
- // change.
- break 'state_loop;
- }
- Err(mpsc::TryRecvError::Empty) => {
- // If the VM is not running, wait until a message is ready.
- match from_main_tube.recv() {
- Ok(m) => m,
- Err(mpsc::RecvError) => {
- error!("Failed to read from main tube in vcpu");
- break 'vcpu_loop;
- }
- }
- }
- Err(mpsc::TryRecvError::Disconnected) => {
- error!("Failed to read from main tube in vcpu");
- break 'vcpu_loop;
- }
- };
-
- // Collect all pending messages.
- let mut messages = vec![msg];
- messages.append(&mut from_main_tube.try_iter().collect());
-
- for msg in messages {
- match msg {
- VcpuControl::RunState(new_mode) => {
- run_mode = new_mode;
- match run_mode {
- VmRunMode::Running => break 'state_loop,
- VmRunMode::Suspending => {
- // On KVM implementations that use a paravirtualized
- // clock (e.g. x86), a flag must be set to indicate to
- // the guest kernel that a vCPU was suspended. The guest
- // kernel will use this flag to prevent the soft lockup
- // detection from triggering when this vCPU resumes,
- // which could happen days later in realtime.
- if requires_pvclock_ctrl {
- if let Err(e) = vcpu.pvclock_ctrl() {
- error!(
- "failed to tell hypervisor vcpu {} is suspending: {}",
- cpu_id, e
- );
- }
- }
- }
- VmRunMode::Breakpoint => {}
- VmRunMode::Exiting => break 'vcpu_loop,
- }
- }
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- VcpuControl::Debug(d) => {
- match &to_gdb_tube {
- Some(ref ch) => {
- if let Err(e) = handle_debug_msg(
- cpu_id, &vcpu, &guest_mem, d, &ch,
- ) {
- error!("Failed to handle gdb message: {}", e);
- }
- },
- None => {
- error!("VcpuControl::Debug received while GDB feature is disabled: {:?}", d);
- }
- }
- }
- }
- }
- }
- }
-
- interrupted_by_signal = false;
-
- // Vcpus may have run a HLT instruction, which puts them into a state other than
- // VcpuRunState::Runnable. In that case, this call to wait_until_runnable blocks
- // until either the irqchip receives an interrupt for this vcpu, or until the main
- // thread kicks this vcpu as a result of some VmControl operation. In most IrqChip
- // implementations HLT instructions do not make it to crosvm, and thus this is a
- // no-op that always returns VcpuRunState::Runnable.
- match irq_chip.wait_until_runnable(&vcpu) {
- Ok(VcpuRunState::Runnable) => {}
- Ok(VcpuRunState::Interrupted) => interrupted_by_signal = true,
- Err(e) => error!(
- "error waiting for vcpu {} to become runnable: {}",
- cpu_id, e
- ),
- }
-
- if !interrupted_by_signal {
- match vcpu.run(&vcpu_run_handle) {
- Ok(VcpuExit::IoIn { port, mut size }) => {
- let mut data = [0; 8];
- if size > data.len() {
- error!("unsupported IoIn size of {} bytes", size);
- size = data.len();
- }
- io_bus.read(port as u64, &mut data[..size]);
- if let Err(e) = vcpu.set_data(&data[..size]) {
- error!("failed to set return data for IoIn: {}", e);
- }
- }
- Ok(VcpuExit::IoOut {
- port,
- mut size,
- data,
- }) => {
- if size > data.len() {
- error!("unsupported IoOut size of {} bytes", size);
- size = data.len();
- }
- io_bus.write(port as u64, &data[..size]);
- }
- Ok(VcpuExit::MmioRead { address, size }) => {
- let mut data = [0; 8];
- mmio_bus.read(address, &mut data[..size]);
- // Setting data for mmio can not fail.
- let _ = vcpu.set_data(&data[..size]);
- }
- Ok(VcpuExit::MmioWrite {
- address,
- size,
- data,
- }) => {
- mmio_bus.write(address, &data[..size]);
- }
- Ok(VcpuExit::IoapicEoi { vector }) => {
- if let Err(e) = irq_chip.broadcast_eoi(vector) {
- error!(
- "failed to broadcast eoi {} on vcpu {}: {}",
- vector, cpu_id, e
- );
- }
- }
- Ok(VcpuExit::IrqWindowOpen) => {}
- Ok(VcpuExit::Hlt) => irq_chip.halted(cpu_id),
- Ok(VcpuExit::Shutdown) => break,
- Ok(VcpuExit::FailEntry {
- hardware_entry_failure_reason,
- }) => {
- error!("vcpu hw run failure: {:#x}", hardware_entry_failure_reason);
- break;
- }
- Ok(VcpuExit::SystemEvent(_, _)) => break,
- Ok(VcpuExit::Debug { .. }) => {
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- {
- let msg = VcpuDebugStatusMessage {
- cpu: cpu_id as usize,
- msg: VcpuDebugStatus::HitBreakPoint,
- };
- if let Some(ref ch) = to_gdb_tube {
- if let Err(e) = ch.send(msg) {
- error!("failed to notify breakpoint to GDB thread: {}", e);
- break;
- }
- }
- run_mode = VmRunMode::Breakpoint;
- }
- }
- Ok(r) => warn!("unexpected vcpu exit: {:?}", r),
- Err(e) => match e.errno() {
- libc::EINTR => interrupted_by_signal = true,
- libc::EAGAIN => {}
- _ => {
- error!("vcpu hit unknown error: {}", e);
- break;
- }
- },
- }
- }
-
- if interrupted_by_signal {
- if use_hypervisor_signals {
- // Try to clear the signal that we use to kick VCPU if it is pending before
- // attempting to handle pause requests.
- if let Err(e) = clear_signal(SIGRTMIN() + 0) {
- error!("failed to clear pending signal: {}", e);
- break;
- }
- } else {
- vcpu.set_immediate_exit(false);
- }
- }
-
- if let Err(e) = irq_chip.inject_interrupts(&vcpu) {
- error!("failed to inject interrupts for vcpu {}: {}", cpu_id, e);
- }
- }
- })
- .map_err(Error::SpawnVcpu)
-}
-
-// Reads the contents of a file and converts the space-separated fields into a Vec of i64s.
-// Returns an error if any of the fields fail to parse.
-fn file_fields_to_i64<P: AsRef<Path>>(path: P) -> io::Result<Vec<i64>> {
- let mut file = File::open(path)?;
-
- let mut buf = [0u8; 32];
- let count = file.read(&mut buf)?;
-
- let content =
- str::from_utf8(&buf[..count]).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
- content
- .trim()
- .split_whitespace()
- .map(|x| {
- x.parse::<i64>()
- .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
- })
- .collect()
-}
-
-// Reads the contents of a file and converts them into a u64, and if there
-// are multiple fields it only returns the first one.
-fn file_to_i64<P: AsRef<Path>>(path: P, nth: usize) -> io::Result<i64> {
- file_fields_to_i64(path)?
- .into_iter()
- .nth(nth)
- .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "empty file"))
-}
-
-fn create_kvm_kernel_irq_chip(
- vm: &KvmVm,
- vcpu_count: usize,
- _ioapic_device_tube: Tube,
-) -> base::Result<impl IrqChipArch> {
- let irq_chip = KvmKernelIrqChip::new(vm.try_clone()?, vcpu_count)?;
- Ok(irq_chip)
-}
-
-#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
-fn create_kvm_split_irq_chip(
- vm: &KvmVm,
- vcpu_count: usize,
- ioapic_device_tube: Tube,
-) -> base::Result<impl IrqChipArch> {
- let irq_chip =
- KvmSplitIrqChip::new(vm.try_clone()?, vcpu_count, ioapic_device_tube, Some(120))?;
- Ok(irq_chip)
-}
-
-pub fn run_config(cfg: Config) -> Result<()> {
- let components = setup_vm_components(&cfg)?;
-
- let guest_mem_layout =
- Arch::guest_memory_layout(&components).map_err(Error::GuestMemoryLayout)?;
- let guest_mem = GuestMemory::new(&guest_mem_layout).unwrap();
- let mut mem_policy = MemoryPolicy::empty();
- if components.hugepages {
- mem_policy |= MemoryPolicy::USE_HUGEPAGES;
- }
- guest_mem.set_memory_policy(mem_policy);
- let kvm = Kvm::new_with_path(&cfg.kvm_device_path).map_err(Error::CreateKvm)?;
- let vm = KvmVm::new(&kvm, guest_mem).map_err(Error::CreateVm)?;
-
- if cfg.split_irqchip {
- #[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
- {
- unimplemented!("KVM split irqchip mode only supported on x86 processors")
- }
-
- #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
- {
- run_vm::<KvmVcpu, _, _, _>(cfg, components, vm, create_kvm_split_irq_chip)
- }
- } else {
- run_vm::<KvmVcpu, _, _, _>(cfg, components, vm, create_kvm_kernel_irq_chip)
- }
-}
-
-fn setup_vm_components(cfg: &Config) -> Result<VmComponents> {
- let initrd_image = if let Some(initrd_path) = &cfg.initrd_path {
- Some(File::open(initrd_path).map_err(|e| Error::OpenInitrd(initrd_path.clone(), e))?)
- } else {
- None
- };
-
- let vm_image = match cfg.executable_path {
- Some(Executable::Kernel(ref kernel_path)) => VmImage::Kernel(
- File::open(kernel_path).map_err(|e| Error::OpenKernel(kernel_path.to_path_buf(), e))?,
- ),
- Some(Executable::Bios(ref bios_path)) => VmImage::Bios(
- File::open(bios_path).map_err(|e| Error::OpenBios(bios_path.to_path_buf(), e))?,
- ),
- _ => panic!("Did not receive a bios or kernel, should be impossible."),
- };
-
- Ok(VmComponents {
- memory_size: cfg
- .memory
- .unwrap_or(256)
- .checked_mul(1024 * 1024)
- .ok_or(Error::MemoryTooLarge)?,
- vcpu_count: cfg.vcpu_count.unwrap_or(1),
- vcpu_affinity: cfg.vcpu_affinity.clone(),
- no_smt: cfg.no_smt,
- hugepages: cfg.hugepages,
- vm_image,
- android_fstab: cfg
- .android_fstab
- .as_ref()
- .map(|x| File::open(x).map_err(|e| Error::OpenAndroidFstab(x.to_path_buf(), e)))
- .map_or(Ok(None), |v| v.map(Some))?,
- pstore: cfg.pstore.clone(),
- initrd_image,
- extra_kernel_params: cfg.params.clone(),
- wayland_dmabuf: cfg.wayland_dmabuf,
- acpi_sdts: cfg
- .acpi_tables
- .iter()
- .map(|path| SDT::from_file(path).map_err(|e| Error::OpenAcpiTable(path.clone(), e)))
- .collect::<Result<Vec<SDT>>>()?,
- rt_cpus: cfg.rt_cpus.clone(),
- protected_vm: cfg.protected_vm,
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- gdb: None,
- dmi_path: cfg.dmi_path.clone(),
- })
-}
-
-fn run_vm<Vcpu, V, I, FI>(
- cfg: Config,
- #[allow(unused_mut)] mut components: VmComponents,
- vm: V,
- create_irq_chip: FI,
-) -> Result<()>
-where
- Vcpu: VcpuArch + 'static,
- V: VmArch + 'static,
- I: IrqChipArch + 'static,
- FI: FnOnce(
- &V,
- usize, // vcpu_count
- Tube, // ioapic_device_tube
- ) -> base::Result<I>,
-{
- if cfg.sandbox {
- // Printing something to the syslog before entering minijail so that libc's syslogger has a
- // chance to open files necessary for its operation, like `/etc/localtime`. After jailing,
- // access to those files will not be possible.
- info!("crosvm entering multiprocess mode");
- }
-
- let (usb_control_tube, usb_provider) =
- HostBackendDeviceProvider::new().map_err(Error::CreateUsbProvider)?;
- // Masking signals is inherently dangerous, since this can persist across clones/execs. Do this
- // before any jailed devices have been spawned, so that we can catch any of them that fail very
- // quickly.
- let sigchld_fd = SignalFd::new(libc::SIGCHLD).map_err(Error::CreateSignalFd)?;
-
- let control_server_socket = match &cfg.socket_path {
- Some(path) => Some(UnlinkUnixSeqpacketListener(
- UnixSeqpacketListener::bind(path).map_err(Error::CreateControlServer)?,
- )),
- None => None,
- };
-
- let mut control_tubes = Vec::new();
-
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- if let Some(port) = cfg.gdb {
- // GDB needs a control socket to interrupt vcpus.
- let (gdb_host_tube, gdb_control_tube) = Tube::pair().map_err(Error::CreateTube)?;
- control_tubes.push(TaggedControlTube::Vm(gdb_host_tube));
- components.gdb = Some((port, gdb_control_tube));
- }
-
- let (wayland_host_tube, wayland_device_tube) = Tube::pair().map_err(Error::CreateTube)?;
- control_tubes.push(TaggedControlTube::VmMemory(wayland_host_tube));
- // Balloon gets a special socket so balloon requests can be forwarded from the main process.
- let (balloon_host_tube, balloon_device_tube) = Tube::pair().map_err(Error::CreateTube)?;
-
- // Create one control socket per disk.
- let mut disk_device_tubes = Vec::new();
- let mut disk_host_tubes = Vec::new();
- let disk_count = cfg.disks.len();
- for _ in 0..disk_count {
- let (disk_host_tub, disk_device_tube) = Tube::pair().map_err(Error::CreateTube)?;
- disk_host_tubes.push(disk_host_tub);
- disk_device_tubes.push(disk_device_tube);
- }
-
- let mut pmem_device_tubes = Vec::new();
- let pmem_count = cfg.pmem_devices.len();
- for _ in 0..pmem_count {
- let (pmem_host_tube, pmem_device_tube) = Tube::pair().map_err(Error::CreateTube)?;
- pmem_device_tubes.push(pmem_device_tube);
- control_tubes.push(TaggedControlTube::VmMsync(pmem_host_tube));
- }
-
- let (gpu_host_tube, gpu_device_tube) = Tube::pair().map_err(Error::CreateTube)?;
- control_tubes.push(TaggedControlTube::VmMemory(gpu_host_tube));
-
- let (ioapic_host_tube, ioapic_device_tube) = Tube::pair().map_err(Error::CreateTube)?;
- control_tubes.push(TaggedControlTube::VmIrq(ioapic_host_tube));
-
- let battery = if cfg.battery_type.is_some() {
- let jail = match simple_jail(&cfg, "battery")? {
- #[cfg_attr(not(feature = "powerd-monitor-powerd"), allow(unused_mut))]
- Some(mut jail) => {
- // Setup a bind mount to the system D-Bus socket if the powerd monitor is used.
- #[cfg(feature = "power-monitor-powerd")]
- {
- add_crosvm_user_to_jail(&mut jail, "battery")?;
-
- // Create a tmpfs in the device's root directory so that we can bind mount files.
- jail.mount_with_data(
- Path::new("none"),
- Path::new("/"),
- "tmpfs",
- (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
- "size=67108864",
- )?;
-
- let system_bus_socket_path = Path::new("/run/dbus/system_bus_socket");
- jail.mount_bind(system_bus_socket_path, system_bus_socket_path, true)?;
- }
- Some(jail)
- }
- None => None,
- };
- (&cfg.battery_type, jail)
- } else {
- (&cfg.battery_type, None)
- };
-
- let gralloc = RutabagaGralloc::new().map_err(Error::CreateGrallocError)?;
- let map_request: Arc<Mutex<Option<ExternalMapping>>> = Arc::new(Mutex::new(None));
-
- let fs_count = cfg
- .shared_dirs
- .iter()
- .filter(|sd| sd.kind == SharedDirKind::FS)
- .count();
- let mut fs_device_tubes = Vec::with_capacity(fs_count);
- for _ in 0..fs_count {
- let (fs_host_tube, fs_device_tube) = Tube::pair().map_err(Error::CreateTube)?;
- control_tubes.push(TaggedControlTube::Fs(fs_host_tube));
- fs_device_tubes.push(fs_device_tube);
- }
-
- #[cfg_attr(not(feature = "direct"), allow(unused_mut))]
- let mut linux: RunnableLinuxVm<_, Vcpu, _> = Arch::build_vm(
- components,
- &cfg.serial_parameters,
- simple_jail(&cfg, "serial")?,
- battery,
- vm,
- |mem, vm, sys_allocator, exit_evt| {
- create_devices(
- &cfg,
- mem,
- vm,
- sys_allocator,
- exit_evt,
- &mut control_tubes,
- wayland_device_tube,
- gpu_device_tube,
- balloon_device_tube,
- &mut disk_device_tubes,
- &mut pmem_device_tubes,
- &mut fs_device_tubes,
- usb_provider,
- Arc::clone(&map_request),
- )
- },
- |vm, vcpu_count| create_irq_chip(vm, vcpu_count, ioapic_device_tube),
- )
- .map_err(Error::BuildVm)?;
-
- #[cfg(feature = "direct")]
- if let Some(pmio) = &cfg.direct_pmio {
- let direct_io =
- Arc::new(devices::DirectIo::new(&pmio.path, false).map_err(Error::DirectIo)?);
- for range in pmio.ranges.iter() {
- linux
- .io_bus
- .insert_sync(direct_io.clone(), range.0, range.1)
- .unwrap();
- }
- };
-
- #[cfg(feature = "direct")]
- let mut irqs = Vec::new();
-
- #[cfg(feature = "direct")]
- for irq in &cfg.direct_level_irq {
- if !linux.resources.reserve_irq(*irq) {
- warn!("irq {} already reserved.", irq);
- }
- let trigger = Event::new().map_err(Error::CreateEvent)?;
- let resample = Event::new().map_err(Error::CreateEvent)?;
- linux
- .irq_chip
- .register_irq_event(*irq, &trigger, Some(&resample))
- .unwrap();
- let direct_irq =
- devices::DirectIrq::new(trigger, Some(resample)).map_err(Error::DirectIrq)?;
- direct_irq.irq_enable(*irq).map_err(Error::DirectIrq)?;
- irqs.push(direct_irq);
- }
-
- #[cfg(feature = "direct")]
- for irq in &cfg.direct_edge_irq {
- if !linux.resources.reserve_irq(*irq) {
- warn!("irq {} already reserved.", irq);
- }
- let trigger = Event::new().map_err(Error::CreateEvent)?;
- linux
- .irq_chip
- .register_irq_event(*irq, &trigger, None)
- .unwrap();
- let direct_irq = devices::DirectIrq::new(trigger, None).map_err(Error::DirectIrq)?;
- direct_irq.irq_enable(*irq).map_err(Error::DirectIrq)?;
- irqs.push(direct_irq);
- }
-
- run_control(
- linux,
- control_server_socket,
- control_tubes,
- balloon_host_tube,
- &disk_host_tubes,
- usb_control_tube,
- sigchld_fd,
- cfg.sandbox,
- Arc::clone(&map_request),
- cfg.balloon_bias,
- gralloc,
- )
-}
-
-/// Signals all running VCPUs to vmexit, sends VmRunMode message to each VCPU tube, and tells
-/// `irq_chip` to stop blocking halted VCPUs. The tube message is set first because both the
-/// signal and the irq_chip kick could cause the VCPU thread to continue through the VCPU run
-/// loop.
-fn kick_all_vcpus(
- vcpu_handles: &[(JoinHandle<()>, mpsc::Sender<vm_control::VcpuControl>)],
- irq_chip: &impl IrqChip,
- run_mode: &VmRunMode,
-) {
- for (handle, tube) in vcpu_handles {
- if let Err(e) = tube.send(VcpuControl::RunState(run_mode.clone())) {
- error!("failed to send VmRunMode: {}", e);
- }
- let _ = handle.kill(SIGRTMIN() + 0);
- }
- irq_chip.kick_halted_vcpus();
-}
-
-// BalloonPolicy determines the size to set the balloon.
-struct BalloonPolicy {
- // Estimate for when the guest starts aggressivly freeing memory.
- critical_guest_available: i64,
- critical_host_available: i64, // ChromeOS critical margin.
- guest_available_bias: i64,
- max_balloon_actual: i64, // The largest the balloon has ever been observed.
- prev_balloon_full_percent: i64, // How full was the balloon at the previous timestep.
- prev_guest_available: i64, // Available memory in the guest at the previous timestep.
-}
-
-const ONE_KB: i64 = 1024;
-const ONE_MB: i64 = 1024 * ONE_KB;
-
-const LOWMEM_AVAILABLE: &str = "/sys/kernel/mm/chromeos-low_mem/available";
-const LOWMEM_MARGIN: &str = "/sys/kernel/mm/chromeos-low_mem/margin";
-
-// BalloonPolicy implements the virtio balloon sizing logic.
-// The balloon is sized with the following heuristics:
-// Balance Available
-// The balloon is sized to balance the amount of available memory above a
-// critical margin. The critical margin is the level at which memory is
-// freed. In the host, this is the ChromeOS available critical margin, which
-// is the trigger to kill tabs. In the guest, we estimate this level by
-// tracking the minimum amount of available memory, discounting sharp
-// 'valleys'. If the guest manages to keep available memory above a given
-// level even with some pressure, then we determine that this is the
-// 'critical' level for the guest. We don't update this critical value if
-// the balloon is fully inflated because in that case, the guest may be out
-// of memory to free.
-// guest_available_bias
-// Even if available memory is perfectly balanced between host and guest,
-// The size of the balloon will still drift randomly depending on whether
-// those host or guest reclaims memory first/faster every time memory is
-// low. To encourage large balloons to shrink and small balloons to grow,
-// the following bias is added to the guest critical margin:
-// (guest_available_bias * balloon_full_percent) / 100
-// This give the guest more memory when the balloon is full.
-impl BalloonPolicy {
- fn new(
- memory_size: i64,
- critical_host_available: i64,
- guest_available_bias: i64,
- ) -> BalloonPolicy {
- // Estimate some reasonable initial maximum for balloon size.
- let max_balloon_actual = (memory_size * 3) / 4;
- // 400MB is above the zone min margin even for Crostini VMs on 16GB
- // devices (~85MB), and is above when Android Low Memory Killer kills
- // apps (~250MB).
- let critical_guest_available = 400 * ONE_MB;
-
- BalloonPolicy {
- critical_guest_available,
- critical_host_available,
- guest_available_bias,
- max_balloon_actual,
- prev_balloon_full_percent: 0,
- prev_guest_available: 0,
- }
- }
- fn delta(&mut self, stats: BalloonStats, balloon_actual_u: u64) -> Result<i64> {
- let guest_free = stats
- .free_memory
- .map(i64::try_from)
- .ok_or(Error::GuestFreeMissing())?
- .map_err(Error::GuestFreeTooLarge)?;
- let guest_cached = stats
- .disk_caches
- .map(i64::try_from)
- .ok_or(Error::GuestFreeMissing())?
- .map_err(Error::GuestFreeTooLarge)?;
- let balloon_actual = match balloon_actual_u {
- size if size < i64::max_value() as u64 => size as i64,
- _ => return Err(Error::BalloonActualTooLarge),
- };
- let guest_available = guest_free + guest_cached;
- // Available memory is reported in MB, and we need bytes.
- let host_available =
- file_to_i64(LOWMEM_AVAILABLE, 0).map_err(Error::ReadMemAvailable)? * ONE_MB;
- if self.max_balloon_actual < balloon_actual {
- self.max_balloon_actual = balloon_actual;
- info!(
- "balloon updated max_balloon_actual to {} MiB",
- self.max_balloon_actual / ONE_MB,
- );
- }
- let balloon_full_percent = balloon_actual * 100 / self.max_balloon_actual;
- // Update critical_guest_available if we see a lower available with the
- // balloon not fully inflated. If the balloon is completely inflated
- // there is a risk that the low available level we see comes at the cost
- // of stability. The Linux OOM Killer might have been forced to kill
- // something important, or page reclaim was so aggressive that there are
- // long UI hangs.
- if guest_available < self.critical_guest_available && balloon_full_percent < 95 {
- // To ignore temporary low memory states, we require that two guest
- // available measurements in a row are low.
- if self.prev_guest_available < self.critical_guest_available
- && self.prev_balloon_full_percent < 95
- {
- self.critical_guest_available = self.prev_guest_available;
- info!(
- "balloon updated critical_guest_available to {} MiB",
- self.critical_guest_available / ONE_MB,
- );
- }
- }
-
- // Compute the difference in available memory above the host and guest
- // critical thresholds.
- let bias = (self.guest_available_bias * balloon_full_percent) / 100;
- let guest_above_critical = guest_available - self.critical_guest_available - bias;
- let host_above_critical = host_available - self.critical_host_available;
- let balloon_delta = guest_above_critical - host_above_critical;
- // Only let the balloon take up MAX_CRITICAL_DELTA of available memory
- // below the critical level in host or guest.
- const MAX_CRITICAL_DELTA: i64 = 10 * ONE_MB;
- let balloon_delta_capped = if balloon_delta < 0 {
- // The balloon is deflating, taking memory from the host. Don't let
- // it take more than the amount of available memory above the
- // critical margin, plus MAX_CRITICAL_DELTA.
- max(
- balloon_delta,
- -(host_available - self.critical_host_available + MAX_CRITICAL_DELTA),
- )
- } else {
- // The balloon is inflating, taking memory from the guest. Don't let
- // it take more than the amount of available memory above the
- // critical margin, plus MAX_CRITICAL_DELTA.
- min(
- balloon_delta,
- guest_available - self.critical_guest_available + MAX_CRITICAL_DELTA,
- )
- };
-
- self.prev_balloon_full_percent = balloon_full_percent;
- self.prev_guest_available = guest_available;
-
- // Only return a value if target would change available above critical
- // by more than 1%, or we are within 1 MB of critical in host or guest.
- if guest_above_critical < ONE_MB
- || host_above_critical < ONE_MB
- || (balloon_delta.abs() * 100) / guest_above_critical > 1
- || (balloon_delta.abs() * 100) / host_above_critical > 1
- {
- // Finally, make sure the balloon delta won't cause a negative size.
- let result = max(balloon_delta_capped, -balloon_actual);
- if result != 0 {
- info!(
- "balloon delta={:<6} ha={:<6} hc={:<6} ga={:<6} gc={:<6} bias={:<6} full={:>3}%",
- result / ONE_MB,
- host_available / ONE_MB,
- self.critical_host_available / ONE_MB,
- guest_available / ONE_MB,
- self.critical_guest_available / ONE_MB,
- bias / ONE_MB,
- balloon_full_percent,
- );
- }
- return Ok(result);
- }
- Ok(0)
- }
-}
-
-fn run_control<V: VmArch + 'static, Vcpu: VcpuArch + 'static, I: IrqChipArch + 'static>(
- mut linux: RunnableLinuxVm<V, Vcpu, I>,
- control_server_socket: Option<UnlinkUnixSeqpacketListener>,
- mut control_tubes: Vec<TaggedControlTube>,
- balloon_host_tube: Tube,
- disk_host_tubes: &[Tube],
- usb_control_tube: Tube,
- sigchld_fd: SignalFd,
- sandbox: bool,
- map_request: Arc<Mutex<Option<ExternalMapping>>>,
- balloon_bias: i64,
- mut gralloc: RutabagaGralloc,
-) -> Result<()> {
- #[derive(PollToken)]
- enum Token {
- Exit,
- Suspend,
- ChildSignal,
- IrqFd { index: IrqEventIndex },
- BalanceMemory,
- BalloonResult,
- VmControlServer,
- VmControl { index: usize },
- }
-
- stdin()
- .set_raw_mode()
- .expect("failed to set terminal raw mode");
-
- let wait_ctx = WaitContext::build_with(&[
- (&linux.exit_evt, Token::Exit),
- (&linux.suspend_evt, Token::Suspend),
- (&sigchld_fd, Token::ChildSignal),
- ])
- .map_err(Error::WaitContextAdd)?;
-
- if let Some(socket_server) = &control_server_socket {
- wait_ctx
- .add(socket_server, Token::VmControlServer)
- .map_err(Error::WaitContextAdd)?;
- }
- for (index, socket) in control_tubes.iter().enumerate() {
- wait_ctx
- .add(socket.as_ref(), Token::VmControl { index })
- .map_err(Error::WaitContextAdd)?;
- }
-
- let events = linux
- .irq_chip
- .irq_event_tokens()
- .map_err(Error::WaitContextAdd)?;
-
- for (index, _gsi, evt) in events {
- wait_ctx
- .add(&evt, Token::IrqFd { index })
- .map_err(Error::WaitContextAdd)?;
- }
-
- // Balance available memory between guest and host every second.
- let mut balancemem_timer = Timer::new().map_err(Error::CreateTimer)?;
- let mut balloon_policy = if let Ok(critical_margin) = file_to_i64(LOWMEM_MARGIN, 0) {
- // Create timer request balloon stats every 1s.
- wait_ctx
- .add(&balancemem_timer, Token::BalanceMemory)
- .map_err(Error::WaitContextAdd)?;
- let balancemem_dur = Duration::from_secs(1);
- let balancemem_int = Duration::from_secs(1);
- balancemem_timer
- .reset(balancemem_dur, Some(balancemem_int))
- .map_err(Error::ResetTimer)?;
-
- // Listen for balloon statistics from the guest so we can balance.
- wait_ctx
- .add(&balloon_host_tube, Token::BalloonResult)
- .map_err(Error::WaitContextAdd)?;
- Some(BalloonPolicy::new(
- linux.vm.get_memory().memory_size() as i64,
- critical_margin * ONE_MB,
- balloon_bias,
- ))
- } else {
- warn!("Unable to open low mem margin, maybe not a chrome os kernel");
- None
- };
-
- if sandbox {
- // Before starting VCPUs, in case we started with some capabilities, drop them all.
- drop_capabilities().map_err(Error::DropCapabilities)?;
- }
-
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- // Create a channel for GDB thread.
- let (to_gdb_channel, from_vcpu_channel) = if linux.gdb.is_some() {
- let (s, r) = mpsc::channel();
- (Some(s), Some(r))
- } else {
- (None, None)
- };
-
- let mut vcpu_handles = Vec::with_capacity(linux.vcpu_count);
- let vcpu_thread_barrier = Arc::new(Barrier::new(linux.vcpu_count + 1));
- let use_hypervisor_signals = !linux
- .vm
- .get_hypervisor()
- .check_capability(&HypervisorCap::ImmediateExit);
- setup_vcpu_signal_handler::<Vcpu>(use_hypervisor_signals)?;
-
- let vcpus: Vec<Option<_>> = match linux.vcpus.take() {
- Some(vec) => vec.into_iter().map(Some).collect(),
- None => iter::repeat_with(|| None).take(linux.vcpu_count).collect(),
- };
- for (cpu_id, vcpu) in vcpus.into_iter().enumerate() {
- let (to_vcpu_channel, from_main_channel) = mpsc::channel();
- let vcpu_affinity = match linux.vcpu_affinity.clone() {
- Some(VcpuAffinity::Global(v)) => v,
- Some(VcpuAffinity::PerVcpu(mut m)) => m.remove(&cpu_id).unwrap_or_default(),
- None => Default::default(),
- };
- let handle = run_vcpu(
- cpu_id,
- vcpu,
- linux.vm.try_clone().map_err(Error::CloneEvent)?,
- linux.irq_chip.try_clone().map_err(Error::CloneEvent)?,
- linux.vcpu_count,
- linux.rt_cpus.contains(&cpu_id),
- vcpu_affinity,
- linux.no_smt,
- vcpu_thread_barrier.clone(),
- linux.has_bios,
- linux.io_bus.clone(),
- linux.mmio_bus.clone(),
- linux.exit_evt.try_clone().map_err(Error::CloneEvent)?,
- linux.vm.check_capability(VmCap::PvClockSuspend),
- from_main_channel,
- use_hypervisor_signals,
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- to_gdb_channel.clone(),
- )?;
- vcpu_handles.push((handle, to_vcpu_channel));
- }
-
- #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- // Spawn GDB thread.
- if let Some((gdb_port_num, gdb_control_tube)) = linux.gdb.take() {
- let to_vcpu_channels = vcpu_handles
- .iter()
- .map(|(_handle, channel)| channel.clone())
- .collect();
- let target = GdbStub::new(
- gdb_control_tube,
- to_vcpu_channels,
- from_vcpu_channel.unwrap(), // Must succeed to unwrap()
- );
- thread::Builder::new()
- .name("gdb".to_owned())
- .spawn(move || gdb_thread(target, gdb_port_num))
- .map_err(Error::SpawnGdbServer)?;
- };
-
- vcpu_thread_barrier.wait();
-
- 'wait: loop {
- let events = {
- match wait_ctx.wait() {
- Ok(v) => v,
- Err(e) => {
- error!("failed to poll: {}", e);
- break;
- }
- }
- };
-
- if let Err(e) = linux.irq_chip.process_delayed_irq_events() {
- warn!("can't deliver delayed irqs: {}", e);
- }
-
- let mut vm_control_indices_to_remove = Vec::new();
- for event in events.iter().filter(|e| e.is_readable) {
- match event.token {
- Token::Exit => {
- info!("vcpu requested shutdown");
- break 'wait;
- }
- Token::Suspend => {
- info!("VM requested suspend");
- linux.suspend_evt.read().unwrap();
- kick_all_vcpus(&vcpu_handles, &linux.irq_chip, &VmRunMode::Suspending);
- }
- Token::ChildSignal => {
- // Print all available siginfo structs, then exit the loop.
- while let Some(siginfo) = sigchld_fd.read().map_err(Error::SignalFd)? {
- let pid = siginfo.ssi_pid;
- let pid_label = match linux.pid_debug_label_map.get(&pid) {
- Some(label) => format!("{} (pid {})", label, pid),
- None => format!("pid {}", pid),
- };
- error!(
- "child {} died: signo {}, status {}, code {}",
- pid_label, siginfo.ssi_signo, siginfo.ssi_status, siginfo.ssi_code
- );
- }
- break 'wait;
- }
- Token::IrqFd { index } => {
- if let Err(e) = linux.irq_chip.service_irq_event(index) {
- error!("failed to signal irq {}: {}", index, e);
- }
- }
- Token::BalanceMemory => {
- balancemem_timer.wait().map_err(Error::Timer)?;
- let command = BalloonControlCommand::Stats {};
- if let Err(e) = balloon_host_tube.send(&command) {
- warn!("failed to send stats request to balloon device: {}", e);
- }
- }
- Token::BalloonResult => {
- match balloon_host_tube.recv() {
- Ok(BalloonControlResult::Stats {
- stats,
- balloon_actual: balloon_actual_u,
- }) => {
- match balloon_policy
- .as_mut()
- .map(|p| p.delta(stats, balloon_actual_u))
- {
- None => {
- error!(
- "got result from balloon stats, but no policy is running"
- );
- }
- Some(Err(e)) => {
- warn!("failed to run balloon policy {}", e);
- }
- Some(Ok(delta)) if delta != 0 => {
- let target = max((balloon_actual_u as i64) + delta, 0) as u64;
- let command =
- BalloonControlCommand::Adjust { num_bytes: target };
- if let Err(e) = balloon_host_tube.send(&command) {
- warn!(
- "failed to send memory value to balloon device: {}",
- e
- );
- }
- }
- Some(Ok(_)) => {}
- }
- }
- Err(e) => {
- error!("failed to recv BalloonControlResult: {}", e);
- }
- };
- }
- Token::VmControlServer => {
- if let Some(socket_server) = &control_server_socket {
- match socket_server.accept() {
- Ok(socket) => {
- wait_ctx
- .add(
- &socket,
- Token::VmControl {
- index: control_tubes.len(),
- },
- )
- .map_err(Error::WaitContextAdd)?;
- control_tubes.push(TaggedControlTube::Vm(Tube::new(socket)));
- }
- Err(e) => error!("failed to accept socket: {}", e),
- }
- }
- }
- Token::VmControl { index } => {
- if let Some(socket) = control_tubes.get(index) {
- match socket {
- TaggedControlTube::Vm(tube) => match tube.recv::<VmRequest>() {
- Ok(request) => {
- let mut run_mode_opt = None;
- let response = request.execute(
- &mut run_mode_opt,
- &balloon_host_tube,
- disk_host_tubes,
- &usb_control_tube,
- &mut linux.bat_control,
- );
- if let Err(e) = tube.send(&response) {
- error!("failed to send VmResponse: {}", e);
- }
- if let Some(run_mode) = run_mode_opt {
- info!("control socket changed run mode to {}", run_mode);
- match run_mode {
- VmRunMode::Exiting => {
- break 'wait;
- }
- other => {
- if other == VmRunMode::Running {
- linux.io_bus.notify_resume();
- }
- kick_all_vcpus(
- &vcpu_handles,
- &linux.irq_chip,
- &other,
- );
- }
- }
- }
- }
- Err(e) => {
- if let TubeError::Disconnected = e {
- vm_control_indices_to_remove.push(index);
- } else {
- error!("failed to recv VmRequest: {}", e);
- }
- }
- },
- TaggedControlTube::VmMemory(tube) => {
- match tube.recv::<VmMemoryRequest>() {
- Ok(request) => {
- let response = request.execute(
- &mut linux.vm,
- &mut linux.resources,
- Arc::clone(&map_request),
- &mut gralloc,
- );
- if let Err(e) = tube.send(&response) {
- error!("failed to send VmMemoryControlResponse: {}", e);
- }
- }
- Err(e) => {
- if let TubeError::Disconnected = e {
- vm_control_indices_to_remove.push(index);
- } else {
- error!("failed to recv VmMemoryControlRequest: {}", e);
- }
- }
- }
- }
- TaggedControlTube::VmIrq(tube) => match tube.recv::<VmIrqRequest>() {
- Ok(request) => {
- let response = {
- let irq_chip = &mut linux.irq_chip;
- request.execute(
- |setup| match setup {
- IrqSetup::Event(irq, ev) => {
- if let Some(event_index) = irq_chip
- .register_irq_event(irq, ev, None)?
- {
- match wait_ctx.add(
- ev,
- Token::IrqFd {
- index: event_index
- },
- ) {
- Err(e) => {
- warn!("failed to add IrqFd to poll context: {}", e);
- Err(e)
- },
- Ok(_) => {
- Ok(())
- }
- }
- } else {
- Ok(())
- }
- }
- IrqSetup::Route(route) => irq_chip.route_irq(route),
- },
- &mut linux.resources,
- )
- };
- if let Err(e) = tube.send(&response) {
- error!("failed to send VmIrqResponse: {}", e);
- }
- }
- Err(e) => {
- if let TubeError::Disconnected = e {
- vm_control_indices_to_remove.push(index);
- } else {
- error!("failed to recv VmIrqRequest: {}", e);
- }
- }
- },
- TaggedControlTube::VmMsync(tube) => {
- match tube.recv::<VmMsyncRequest>() {
- Ok(request) => {
- let response = request.execute(&mut linux.vm);
- if let Err(e) = tube.send(&response) {
- error!("failed to send VmMsyncResponse: {}", e);
- }
- }
- Err(e) => {
- if let TubeError::Disconnected = e {
- vm_control_indices_to_remove.push(index);
- } else {
- error!("failed to recv VmMsyncRequest: {}", e);
- }
- }
- }
- }
- TaggedControlTube::Fs(tube) => match tube.recv::<FsMappingRequest>() {
- Ok(request) => {
- let response =
- request.execute(&mut linux.vm, &mut linux.resources);
- if let Err(e) = tube.send(&response) {
- error!("failed to send VmResponse: {}", e);
- }
- }
- Err(e) => {
- if let TubeError::Disconnected = e {
- vm_control_indices_to_remove.push(index);
- } else {
- error!("failed to recv VmResponse: {}", e);
- }
- }
- },
- }
- }
- }
- }
- }
-
- for event in events.iter().filter(|e| e.is_hungup) {
- match event.token {
- Token::Exit => {}
- Token::Suspend => {}
- Token::ChildSignal => {}
- Token::IrqFd { index: _ } => {}
- Token::BalanceMemory => {}
- Token::BalloonResult => {}
- Token::VmControlServer => {}
- Token::VmControl { index } => {
- // It's possible more data is readable and buffered while the socket is hungup,
- // so don't delete the tube from the poll context until we're sure all the
- // data is read.
- if control_tubes
- .get(index)
- .map(|s| !s.as_ref().is_packet_ready())
- .unwrap_or(false)
- {
- vm_control_indices_to_remove.push(index);
- }
- }
- }
- }
-
- // Sort in reverse so the highest indexes are removed first. This removal algorithm
- // preserves correct indexes as each element is removed.
- vm_control_indices_to_remove.sort_unstable_by_key(|&k| Reverse(k));
- vm_control_indices_to_remove.dedup();
- for index in vm_control_indices_to_remove {
- // Delete the socket from the `wait_ctx` synchronously. Otherwise, the kernel will do
- // this automatically when the FD inserted into the `wait_ctx` is closed after this
- // if-block, but this removal can be deferred unpredictably. In some instances where the
- // system is under heavy load, we can even get events returned by `wait_ctx` for an FD
- // that has already been closed. Because the token associated with that spurious event
- // now belongs to a different socket, the control loop will start to interact with
- // sockets that might not be ready to use. This can cause incorrect hangup detection or
- // blocking on a socket that will never be ready. See also: crbug.com/1019986
- if let Some(socket) = control_tubes.get(index) {
- wait_ctx.delete(socket).map_err(Error::WaitContextDelete)?;
- }
-
- // This line implicitly drops the socket at `index` when it gets returned by
- // `swap_remove`. After this line, the socket at `index` is not the one from
- // `vm_control_indices_to_remove`. Because of this socket's change in index, we need to
- // use `wait_ctx.modify` to change the associated index in its `Token::VmControl`.
- control_tubes.swap_remove(index);
- if let Some(tube) = control_tubes.get(index) {
- wait_ctx
- .modify(tube, EventType::Read, Token::VmControl { index })
- .map_err(Error::WaitContextAdd)?;
- }
- }
- }
-
- kick_all_vcpus(&vcpu_handles, &linux.irq_chip, &VmRunMode::Exiting);
- for (handle, _) in vcpu_handles {
- if let Err(e) = handle.join() {
- error!("failed to join vcpu thread: {:?}", e);
- }
- }
-
- // Explicitly drop the VM structure here to allow the devices to clean up before the
- // control sockets are closed when this function exits.
- mem::drop(linux);
-
- stdin()
- .set_canon_mode()
- .expect("failed to restore canonical mode for terminal");
-
- Ok(())
-}
diff --git a/src/linux/android.rs b/src/linux/android.rs
new file mode 100644
index 000000000..c18e760b4
--- /dev/null
+++ b/src/linux/android.rs
@@ -0,0 +1,41 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#![cfg(target_os = "android")]
+
+use anyhow::{anyhow, Result};
+use libc;
+use std::ffi::{CStr, CString};
+
+#[link(name = "processgroup")]
+extern "C" {
+ fn android_set_process_profiles(
+ uid: libc::uid_t,
+ pid: libc::pid_t,
+ num_profiles: libc::size_t,
+ profiles: *const *const libc::c_char,
+ ) -> bool;
+}
+
+// Apply the listed task profiles to all tasks (current and future) in this process.
+pub fn set_process_profiles(profiles: &Vec<String>) -> Result<()> {
+ if (profiles.is_empty()) {
+ return Ok(());
+ }
+ let owned: Vec<CString> = profiles
+ .iter()
+ .map(|s| CString::new(s.clone()).unwrap())
+ .collect();
+ let ptrs: Vec<*const libc::c_char> = owned.iter().map(|s| s.as_ptr()).collect();
+ // SAFETY: the ownership of the array of string is not passed. The function copies it
+ // internally.
+ unsafe {
+ if (android_set_process_profiles(libc::getuid(), libc::getpid(), ptrs.len(), ptrs.as_ptr()))
+ {
+ Ok(())
+ } else {
+ Err(anyhow!("failed to set task profiles"))
+ }
+ }
+}
diff --git a/src/linux/device_helpers.rs b/src/linux/device_helpers.rs
new file mode 100644
index 000000000..cb402d31c
--- /dev/null
+++ b/src/linux/device_helpers.rs
@@ -0,0 +1,1250 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::collections::BTreeMap;
+use std::convert::TryFrom;
+use std::fs::{File, OpenOptions};
+use std::net::Ipv4Addr;
+use std::ops::RangeInclusive;
+use std::os::unix::net::UnixListener;
+use std::os::unix::{net::UnixStream, prelude::OpenOptionsExt};
+use std::path::{Path, PathBuf};
+use std::str;
+use std::sync::Arc;
+
+use crate::{
+ Config, DiskOption, TouchDeviceOption, VhostUserFsOption, VhostUserOption, VhostUserWlOption,
+ VvuOption,
+};
+use anyhow::{anyhow, bail, Context, Result};
+use arch::{self, VirtioDeviceStub};
+use base::*;
+use devices::serial_device::{SerialParameters, SerialType};
+use devices::vfio::{VfioCommonSetup, VfioCommonTrait};
+use devices::virtio::ipc_memory_mapper::{create_ipc_mapper, CreateIpcMapperRet};
+use devices::virtio::memory_mapper::{BasicMemoryMapper, MemoryMapperTrait};
+#[cfg(feature = "audio_cras")]
+use devices::virtio::snd::cras_backend::Parameters as CrasSndParameters;
+use devices::virtio::vfio_wrapper::VfioWrapper;
+use devices::virtio::vhost::user::proxy::VirtioVhostUser;
+#[cfg(feature = "audio")]
+use devices::virtio::vhost::user::vmm::Snd as VhostUserSnd;
+use devices::virtio::vhost::user::vmm::{
+ Block as VhostUserBlock, Console as VhostUserConsole, Fs as VhostUserFs,
+ Mac80211Hwsim as VhostUserMac80211Hwsim, Net as VhostUserNet, Vsock as VhostUserVsock,
+ Wl as VhostUserWl,
+};
+use devices::virtio::vhost::vsock::VhostVsockConfig;
+#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
+use devices::virtio::VideoBackendType;
+use devices::virtio::{self, BalloonMode, Console, VirtioDevice};
+use devices::IommuDevType;
+#[cfg(feature = "tpm")]
+use devices::SoftwareTpm;
+use devices::{
+ self, BusDeviceObj, PciAddress, PciDevice, VfioDevice, VfioPciDevice, VfioPlatformDevice,
+};
+use hypervisor::Vm;
+use minijail::{self, Minijail};
+use net_util::{MacAddress, Tap, TapT};
+use resources::{Alloc, MmioType, SystemAllocator};
+use sync::Mutex;
+use vm_memory::GuestAddress;
+
+use super::jail_helpers::*;
+
+pub enum TaggedControlTube {
+ Fs(Tube),
+ Vm(Tube),
+ VmMemory(Tube),
+ VmIrq(Tube),
+ VmMsync(Tube),
+}
+
+impl AsRef<Tube> for TaggedControlTube {
+ fn as_ref(&self) -> &Tube {
+ use self::TaggedControlTube::*;
+ match &self {
+ Fs(tube) | Vm(tube) | VmMemory(tube) | VmIrq(tube) | VmMsync(tube) => tube,
+ }
+ }
+}
+
+impl AsRawDescriptor for TaggedControlTube {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.as_ref().as_raw_descriptor()
+ }
+}
+
+pub trait IntoUnixStream {
+ fn into_unix_stream(self) -> Result<UnixStream>;
+}
+
+impl<'a> IntoUnixStream for &'a Path {
+ fn into_unix_stream(self) -> Result<UnixStream> {
+ if let Some(fd) = safe_descriptor_from_path(self).context("failed to open event device")? {
+ Ok(fd.into())
+ } else {
+ UnixStream::connect(self).context("failed to open event device")
+ }
+ }
+}
+
+impl<'a> IntoUnixStream for &'a PathBuf {
+ fn into_unix_stream(self) -> Result<UnixStream> {
+ self.as_path().into_unix_stream()
+ }
+}
+
+impl IntoUnixStream for UnixStream {
+ fn into_unix_stream(self) -> Result<UnixStream> {
+ Ok(self)
+ }
+}
+
+pub type DeviceResult<T = VirtioDeviceStub> = Result<T>;
+
+pub fn create_block_device(
+ cfg: &Config,
+ disk: &DiskOption,
+ disk_device_tube: Tube,
+) -> DeviceResult {
+ let mut options = OpenOptions::new();
+ options.read(true).write(!disk.read_only);
+
+ #[cfg(unix)]
+ if disk.o_direct {
+ options.custom_flags(libc::O_DIRECT);
+ }
+
+ let raw_image: File = open_file(&disk.path, &options)
+ .with_context(|| format!("failed to load disk image {}", disk.path.display()))?;
+ // Lock the disk image to prevent other crosvm instances from using it.
+ let lock_op = if disk.read_only {
+ FlockOperation::LockShared
+ } else {
+ FlockOperation::LockExclusive
+ };
+ flock(&raw_image, lock_op, true).context("failed to lock disk image")?;
+
+ info!("Trying to attach block device: {}", disk.path.display());
+ let dev = if disk::async_ok(&raw_image).context("failed to check disk async_ok")? {
+ let async_file = disk::create_async_disk_file(raw_image)
+ .context("failed to create async virtual disk")?;
+ Box::new(
+ virtio::BlockAsync::new(
+ virtio::base_features(cfg.protected_vm),
+ async_file,
+ disk.read_only,
+ disk.sparse,
+ disk.block_size,
+ disk.id,
+ Some(disk_device_tube),
+ )
+ .context("failed to create block device")?,
+ ) as Box<dyn VirtioDevice>
+ } else {
+ let disk_file = disk::create_disk_file(raw_image, disk::MAX_NESTING_DEPTH, &disk.path)
+ .context("failed to create virtual disk")?;
+ Box::new(
+ virtio::Block::new(
+ virtio::base_features(cfg.protected_vm),
+ disk_file,
+ disk.read_only,
+ disk.sparse,
+ disk.block_size,
+ disk.id,
+ Some(disk_device_tube),
+ )
+ .context("failed to create block device")?,
+ ) as Box<dyn VirtioDevice>
+ };
+
+ Ok(VirtioDeviceStub {
+ dev,
+ jail: simple_jail(&cfg.jail_config, "block_device")?,
+ })
+}
+
+pub fn create_vhost_user_block_device(cfg: &Config, opt: &VhostUserOption) -> DeviceResult {
+ let dev = VhostUserBlock::new(virtio::base_features(cfg.protected_vm), &opt.socket)
+ .context("failed to set up vhost-user block device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ // no sandbox here because virtqueue handling is exported to a different process.
+ jail: None,
+ })
+}
+
+pub fn create_vhost_user_console_device(cfg: &Config, opt: &VhostUserOption) -> DeviceResult {
+ let dev = VhostUserConsole::new(virtio::base_features(cfg.protected_vm), &opt.socket)
+ .context("failed to set up vhost-user console device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ // no sandbox here because virtqueue handling is exported to a different process.
+ jail: None,
+ })
+}
+
+pub fn create_vhost_user_fs_device(cfg: &Config, option: &VhostUserFsOption) -> DeviceResult {
+ let dev = VhostUserFs::new(
+ virtio::base_features(cfg.protected_vm),
+ &option.socket,
+ &option.tag,
+ )
+ .context("failed to set up vhost-user fs device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ // no sandbox here because virtqueue handling is exported to a different process.
+ jail: None,
+ })
+}
+
+pub fn create_vhost_user_mac80211_hwsim_device(
+ cfg: &Config,
+ opt: &VhostUserOption,
+) -> DeviceResult {
+ let dev = VhostUserMac80211Hwsim::new(virtio::base_features(cfg.protected_vm), &opt.socket)
+ .context("failed to set up vhost-user mac80211_hwsim device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ // no sandbox here because virtqueue handling is exported to a different process.
+ jail: None,
+ })
+}
+
+#[cfg(feature = "audio")]
+pub fn create_vhost_user_snd_device(cfg: &Config, option: &VhostUserOption) -> DeviceResult {
+ let dev = VhostUserSnd::new(virtio::base_features(cfg.protected_vm), &option.socket)
+ .context("failed to set up vhost-user snd device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ // no sandbox here because virtqueue handling is exported to a different process.
+ jail: None,
+ })
+}
+
+pub fn create_vvu_proxy_device(
+ cfg: &Config,
+ opt: &VvuOption,
+ tube: Tube,
+ max_sibling_mem_size: u64,
+) -> DeviceResult {
+ let listener = UnixListener::bind(&opt.socket).map_err(|e| {
+ error!("failed to bind listener for vvu proxy device: {}", e);
+ e
+ })?;
+
+ let dev = VirtioVhostUser::new(
+ virtio::base_features(cfg.protected_vm),
+ listener,
+ tube,
+ opt.addr,
+ opt.uuid,
+ max_sibling_mem_size,
+ )
+ .context("failed to create VVU proxy device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "vvu_proxy_device")?,
+ })
+}
+
+pub fn create_rng_device(cfg: &Config) -> DeviceResult {
+ let dev = virtio::Rng::new(virtio::base_features(cfg.protected_vm))
+ .context("failed to set up rng")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "rng_device")?,
+ })
+}
+
+#[cfg(feature = "audio_cras")]
+pub fn create_cras_snd_device(cfg: &Config, cras_snd: CrasSndParameters) -> DeviceResult {
+ let dev = virtio::snd::cras_backend::VirtioSndCras::new(
+ virtio::base_features(cfg.protected_vm),
+ cras_snd,
+ )
+ .context("failed to create cras sound device")?;
+
+ let jail = match simple_jail(&cfg.jail_config, "cras_snd_device")? {
+ Some(mut jail) => {
+ // Create a tmpfs in the device's root directory for cras_snd_device.
+ // The size is 20*1024, or 20 KB.
+ jail.mount_with_data(
+ Path::new("none"),
+ Path::new("/"),
+ "tmpfs",
+ (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
+ "size=20480",
+ )?;
+
+ let run_cras_path = Path::new("/run/cras");
+ jail.mount_bind(run_cras_path, run_cras_path, true)?;
+
+ add_current_user_to_jail(&mut jail)?;
+
+ Some(jail)
+ }
+ None => None,
+ };
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail,
+ })
+}
+
+#[cfg(feature = "tpm")]
+pub fn create_software_tpm_device(cfg: &Config) -> DeviceResult {
+ use std::ffi::CString;
+ use std::fs;
+ use std::process;
+
+ let tpm_storage: PathBuf;
+ let mut tpm_jail = simple_jail(&cfg.jail_config, "tpm_device")?;
+
+ match &mut tpm_jail {
+ Some(jail) => {
+ // Create a tmpfs in the device's root directory for tpm
+ // simulator storage. The size is 20*1024, or 20 KB.
+ jail.mount_with_data(
+ Path::new("none"),
+ Path::new("/"),
+ "tmpfs",
+ (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
+ "size=20480",
+ )?;
+
+ let crosvm_ids = add_current_user_to_jail(jail)?;
+
+ let pid = process::id();
+ let tpm_pid_dir = format!("/run/vm/tpm.{}", pid);
+ tpm_storage = Path::new(&tpm_pid_dir).to_owned();
+ fs::create_dir_all(&tpm_storage).with_context(|| {
+ format!("failed to create tpm storage dir {}", tpm_storage.display())
+ })?;
+ let tpm_pid_dir_c = CString::new(tpm_pid_dir).expect("no nul bytes");
+ chown(&tpm_pid_dir_c, crosvm_ids.uid, crosvm_ids.gid)
+ .context("failed to chown tpm storage")?;
+
+ jail.mount_bind(&tpm_storage, &tpm_storage, true)?;
+ }
+ None => {
+ // Path used inside cros_sdk which does not have /run/vm.
+ tpm_storage = Path::new("/tmp/tpm-simulator").to_owned();
+ }
+ }
+
+ let backend = SoftwareTpm::new(tpm_storage).context("failed to create SoftwareTpm")?;
+ let dev = virtio::Tpm::new(Arc::new(Mutex::new(backend)));
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: tpm_jail,
+ })
+}
+
+pub fn create_single_touch_device(
+ cfg: &Config,
+ single_touch_spec: &TouchDeviceOption,
+ idx: u32,
+) -> DeviceResult {
+ let socket = single_touch_spec
+ .get_path()
+ .into_unix_stream()
+ .map_err(|e| {
+ error!("failed configuring virtio single touch: {:?}", e);
+ e
+ })?;
+
+ let (width, height) = single_touch_spec.get_size();
+ let dev = virtio::new_single_touch(
+ idx,
+ socket,
+ width,
+ height,
+ virtio::base_features(cfg.protected_vm),
+ )
+ .context("failed to set up input device")?;
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "input_device")?,
+ })
+}
+
+pub fn create_multi_touch_device(
+ cfg: &Config,
+ multi_touch_spec: &TouchDeviceOption,
+ idx: u32,
+) -> DeviceResult {
+ let socket = multi_touch_spec
+ .get_path()
+ .into_unix_stream()
+ .map_err(|e| {
+ error!("failed configuring virtio multi touch: {:?}", e);
+ e
+ })?;
+
+ let (width, height) = multi_touch_spec.get_size();
+ let dev = virtio::new_multi_touch(
+ idx,
+ socket,
+ width,
+ height,
+ virtio::base_features(cfg.protected_vm),
+ )
+ .context("failed to set up input device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "input_device")?,
+ })
+}
+
+pub fn create_trackpad_device(
+ cfg: &Config,
+ trackpad_spec: &TouchDeviceOption,
+ idx: u32,
+) -> DeviceResult {
+ let socket = trackpad_spec.get_path().into_unix_stream().map_err(|e| {
+ error!("failed configuring virtio trackpad: {:#}", e);
+ e
+ })?;
+
+ let (width, height) = trackpad_spec.get_size();
+ let dev = virtio::new_trackpad(
+ idx,
+ socket,
+ width,
+ height,
+ virtio::base_features(cfg.protected_vm),
+ )
+ .context("failed to set up input device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "input_device")?,
+ })
+}
+
+pub fn create_mouse_device<T: IntoUnixStream>(
+ cfg: &Config,
+ mouse_socket: T,
+ idx: u32,
+) -> DeviceResult {
+ let socket = mouse_socket.into_unix_stream().map_err(|e| {
+ error!("failed configuring virtio mouse: {:#}", e);
+ e
+ })?;
+
+ let dev = virtio::new_mouse(idx, socket, virtio::base_features(cfg.protected_vm))
+ .context("failed to set up input device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "input_device")?,
+ })
+}
+
+pub fn create_keyboard_device<T: IntoUnixStream>(
+ cfg: &Config,
+ keyboard_socket: T,
+ idx: u32,
+) -> DeviceResult {
+ let socket = keyboard_socket.into_unix_stream().map_err(|e| {
+ error!("failed configuring virtio keyboard: {:#}", e);
+ e
+ })?;
+
+ let dev = virtio::new_keyboard(idx, socket, virtio::base_features(cfg.protected_vm))
+ .context("failed to set up input device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "input_device")?,
+ })
+}
+
+pub fn create_switches_device<T: IntoUnixStream>(
+ cfg: &Config,
+ switches_socket: T,
+ idx: u32,
+) -> DeviceResult {
+ let socket = switches_socket.into_unix_stream().map_err(|e| {
+ error!("failed configuring virtio switches: {:#}", e);
+ e
+ })?;
+
+ let dev = virtio::new_switches(idx, socket, virtio::base_features(cfg.protected_vm))
+ .context("failed to set up input device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "input_device")?,
+ })
+}
+
+pub fn create_vinput_device(cfg: &Config, dev_path: &Path) -> DeviceResult {
+ let dev_file = OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(dev_path)
+ .with_context(|| format!("failed to open vinput device {}", dev_path.display()))?;
+
+ let dev = virtio::new_evdev(dev_file, virtio::base_features(cfg.protected_vm))
+ .context("failed to set up input device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "input_device")?,
+ })
+}
+
+pub fn create_balloon_device(
+ cfg: &Config,
+ tube: Tube,
+ inflate_tube: Option<Tube>,
+ init_balloon_size: u64,
+) -> DeviceResult {
+ let dev = virtio::Balloon::new(
+ virtio::base_features(cfg.protected_vm),
+ tube,
+ inflate_tube,
+ init_balloon_size,
+ if cfg.strict_balloon {
+ BalloonMode::Strict
+ } else {
+ BalloonMode::Relaxed
+ },
+ )
+ .context("failed to create balloon")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "balloon_device")?,
+ })
+}
+
+/// Generic method for creating a network device. `create_device` is a closure that takes the virtio
+/// features and number of queue pairs as parameters, and is responsible for creating the device
+/// itself.
+pub fn create_net_device<F, T>(cfg: &Config, policy: &str, create_device: F) -> DeviceResult
+where
+ F: Fn(u64, u16) -> Result<T>,
+ T: VirtioDevice + 'static,
+{
+ let mut vq_pairs = cfg.net_vq_pairs.unwrap_or(1);
+ let vcpu_count = cfg.vcpu_count.unwrap_or(1);
+ if vcpu_count < vq_pairs as usize {
+ warn!("the number of net vq pairs must not exceed the vcpu count, falling back to single queue mode");
+ vq_pairs = 1;
+ }
+ let features = virtio::base_features(cfg.protected_vm);
+
+ let dev = create_device(features, vq_pairs)?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev) as Box<dyn VirtioDevice>,
+ jail: simple_jail(&cfg.jail_config, policy)?,
+ })
+}
+
+/// Returns a network device created from a new TAP interface configured with `host_ip`, `netmask`,
+/// and `mac_address`.
+pub fn create_net_device_from_config(
+ cfg: &Config,
+ host_ip: Ipv4Addr,
+ netmask: Ipv4Addr,
+ mac_address: MacAddress,
+) -> DeviceResult {
+ let policy = if cfg.vhost_net {
+ "vhost_net_device"
+ } else {
+ "net_device"
+ };
+
+ if cfg.vhost_net {
+ create_net_device(cfg, policy, |features, _vq_pairs| {
+ virtio::vhost::Net::<Tap, vhost::Net<Tap>>::new(
+ &cfg.vhost_net_device_path,
+ features,
+ host_ip,
+ netmask,
+ mac_address,
+ )
+ .context("failed to set up vhost networking")
+ })
+ } else {
+ create_net_device(cfg, policy, |features, vq_pairs| {
+ virtio::Net::<Tap>::new(features, host_ip, netmask, mac_address, vq_pairs)
+ .context("failed to create virtio network device")
+ })
+ }
+}
+
+/// Returns a network device from a file descriptor to a configured TAP interface.
+pub fn create_tap_net_device_from_fd(cfg: &Config, tap_fd: RawDescriptor) -> DeviceResult {
+ create_net_device(cfg, "net_device", |features, vq_pairs| {
+ // Safe because we ensure that we get a unique handle to the fd.
+ let tap = unsafe {
+ Tap::from_raw_descriptor(
+ validate_raw_descriptor(tap_fd).context("failed to validate tap descriptor")?,
+ )
+ .context("failed to create tap device")?
+ };
+
+ virtio::Net::from(features, tap, vq_pairs).context("failed to create tap net device")
+ })
+}
+
+/// Returns a network device created by opening the persistent, configured TAP interface `tap_name`.
+pub fn create_tap_net_device_from_name(cfg: &Config, tap_name: &[u8]) -> DeviceResult {
+ create_net_device(cfg, "net_device", |features, vq_pairs| {
+ virtio::Net::<Tap>::new_from_name(features, tap_name, vq_pairs)
+ .context("failed to create configured virtio network device")
+ })
+}
+
+pub fn create_vhost_user_net_device(cfg: &Config, opt: &VhostUserOption) -> DeviceResult {
+ let dev = VhostUserNet::new(virtio::base_features(cfg.protected_vm), &opt.socket)
+ .context("failed to set up vhost-user net device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ // no sandbox here because virtqueue handling is exported to a different process.
+ jail: None,
+ })
+}
+
+pub fn create_vhost_user_vsock_device(cfg: &Config, opt: &VhostUserOption) -> DeviceResult {
+ let dev = VhostUserVsock::new(virtio::base_features(cfg.protected_vm), &opt.socket)
+ .context("failed to set up vhost-user vsock device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ // no sandbox here because virtqueue handling is exported to a different process.
+ jail: None,
+ })
+}
+
+pub fn create_vhost_user_wl_device(cfg: &Config, opt: &VhostUserWlOption) -> DeviceResult {
+ // The crosvm wl device expects us to connect the tube before it will accept a vhost-user
+ // connection.
+ let dev = VhostUserWl::new(virtio::base_features(cfg.protected_vm), &opt.socket)
+ .context("failed to set up vhost-user wl device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ // no sandbox here because virtqueue handling is exported to a different process.
+ jail: None,
+ })
+}
+
+pub fn create_wayland_device(
+ cfg: &Config,
+ control_tube: Tube,
+ resource_bridge: Option<Tube>,
+) -> DeviceResult {
+ let wayland_socket_dirs = cfg
+ .wayland_socket_paths
+ .iter()
+ .map(|(_name, path)| path.parent())
+ .collect::<Option<Vec<_>>>()
+ .ok_or_else(|| anyhow!("wayland socket path has no parent or file name"))?;
+
+ let features = virtio::base_features(cfg.protected_vm);
+ let dev = virtio::Wl::new(
+ features,
+ cfg.wayland_socket_paths.clone(),
+ control_tube,
+ resource_bridge,
+ )
+ .context("failed to create wayland device")?;
+
+ let jail = match simple_jail(&cfg.jail_config, "wl_device")? {
+ Some(mut jail) => {
+ // Create a tmpfs in the device's root directory so that we can bind mount the wayland
+ // socket directory into it. The size=67108864 is size=64*1024*1024 or size=64MB.
+ jail.mount_with_data(
+ Path::new("none"),
+ Path::new("/"),
+ "tmpfs",
+ (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
+ "size=67108864",
+ )?;
+
+ // Bind mount the wayland socket's directory into jail's root. This is necessary since
+ // each new wayland context must open() the socket. If the wayland socket is ever
+ // destroyed and remade in the same host directory, new connections will be possible
+ // without restarting the wayland device.
+ for dir in &wayland_socket_dirs {
+ jail.mount_bind(dir, dir, true)?;
+ }
+ add_current_user_to_jail(&mut jail)?;
+
+ Some(jail)
+ }
+ None => None,
+ };
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail,
+ })
+}
+
+#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
+pub fn create_video_device(
+ backend: VideoBackendType,
+ cfg: &Config,
+ typ: devices::virtio::VideoDeviceType,
+ resource_bridge: Tube,
+) -> DeviceResult {
+ let jail = match simple_jail(&cfg.jail_config, "video_device")? {
+ Some(mut jail) => {
+ match typ {
+ #[cfg(feature = "video-decoder")]
+ devices::virtio::VideoDeviceType::Decoder => add_current_user_to_jail(&mut jail)?,
+ #[cfg(feature = "video-encoder")]
+ devices::virtio::VideoDeviceType::Encoder => add_current_user_to_jail(&mut jail)?,
+ };
+
+ // Create a tmpfs in the device's root directory so that we can bind mount files.
+ jail.mount_with_data(
+ Path::new("none"),
+ Path::new("/"),
+ "tmpfs",
+ (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
+ "size=67108864",
+ )?;
+
+ #[cfg(feature = "libvda")]
+ // Render node for libvda.
+ if backend == VideoBackendType::Libvda || backend == VideoBackendType::LibvdaVd {
+ // follow the implementation at:
+ // https://chromium.googlesource.com/chromiumos/platform/minigbm/+/c06cc9cccb3cf3c7f9d2aec706c27c34cd6162a0/cros_gralloc/cros_gralloc_driver.cc#90
+ const DRM_NUM_NODES: u32 = 63;
+ const DRM_RENDER_NODE_START: u32 = 128;
+ for offset in 0..DRM_NUM_NODES {
+ let path_str = format!("/dev/dri/renderD{}", DRM_RENDER_NODE_START + offset);
+ let dev_dri_path = Path::new(&path_str);
+ if !dev_dri_path.exists() {
+ break;
+ }
+ jail.mount_bind(dev_dri_path, dev_dri_path, false)?;
+ }
+ }
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ // Device nodes used by libdrm through minigbm in libvda on AMD devices.
+ let sys_dev_char_path = Path::new("/sys/dev/char");
+ jail.mount_bind(sys_dev_char_path, sys_dev_char_path, false)?;
+ let sys_devices_path = Path::new("/sys/devices");
+ jail.mount_bind(sys_devices_path, sys_devices_path, false)?;
+
+ // Required for loading dri libraries loaded by minigbm on AMD devices.
+ jail_mount_bind_if_exists(&mut jail, &["/usr/lib64"])?;
+ }
+
+ // Device nodes required by libchrome which establishes Mojo connection in libvda.
+ let dev_urandom_path = Path::new("/dev/urandom");
+ jail.mount_bind(dev_urandom_path, dev_urandom_path, false)?;
+ let system_bus_socket_path = Path::new("/run/dbus/system_bus_socket");
+ jail.mount_bind(system_bus_socket_path, system_bus_socket_path, true)?;
+
+ Some(jail)
+ }
+ None => None,
+ };
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(devices::virtio::VideoDevice::new(
+ virtio::base_features(cfg.protected_vm),
+ typ,
+ backend,
+ Some(resource_bridge),
+ )),
+ jail,
+ })
+}
+
+#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
+pub fn register_video_device(
+ backend: VideoBackendType,
+ devs: &mut Vec<VirtioDeviceStub>,
+ video_tube: Tube,
+ cfg: &Config,
+ typ: devices::virtio::VideoDeviceType,
+) -> Result<()> {
+ devs.push(create_video_device(backend, cfg, typ, video_tube)?);
+ Ok(())
+}
+
+pub fn create_vhost_vsock_device(cfg: &Config, vhost_config: &VhostVsockConfig) -> DeviceResult {
+ let features = virtio::base_features(cfg.protected_vm);
+
+ let dev = virtio::vhost::Vsock::new(features, vhost_config)
+ .context("failed to set up virtual socket device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "vhost_vsock_device")?,
+ })
+}
+
+pub fn create_fs_device(
+ cfg: &Config,
+ uid_map: &str,
+ gid_map: &str,
+ src: &Path,
+ tag: &str,
+ fs_cfg: virtio::fs::passthrough::Config,
+ device_tube: Tube,
+) -> DeviceResult {
+ let max_open_files =
+ base::get_max_open_files().context("failed to get max number of open files")?;
+ let j = if let Some(jail_config) = &cfg.jail_config {
+ let seccomp_policy = jail_config.seccomp_policy_dir.join("fs_device");
+ let config = SandboxConfig {
+ limit_caps: false,
+ uid_map: Some(uid_map),
+ gid_map: Some(gid_map),
+ log_failures: jail_config.seccomp_log_failures,
+ seccomp_policy: &seccomp_policy,
+ // We want bind mounts from the parent namespaces to propagate into the fs device's
+ // namespace.
+ remount_mode: Some(libc::MS_SLAVE),
+ };
+ create_base_minijail(src, Some(max_open_files), Some(&config))?
+ } else {
+ create_base_minijail(src, Some(max_open_files), None)?
+ };
+
+ let features = virtio::base_features(cfg.protected_vm);
+ // TODO(chirantan): Use more than one worker once the kernel driver has been fixed to not panic
+ // when num_queues > 1.
+ let dev = virtio::fs::Fs::new(features, tag, 1, fs_cfg, device_tube)
+ .context("failed to create fs device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: Some(j),
+ })
+}
+
+pub fn create_9p_device(
+ cfg: &Config,
+ uid_map: &str,
+ gid_map: &str,
+ src: &Path,
+ tag: &str,
+ mut p9_cfg: p9::Config,
+) -> DeviceResult {
+ let max_open_files =
+ base::get_max_open_files().context("failed to get max number of open files")?;
+ let (jail, root) = if let Some(jail_config) = &cfg.jail_config {
+ let seccomp_policy = jail_config.seccomp_policy_dir.join("9p_device");
+ let config = SandboxConfig {
+ limit_caps: false,
+ uid_map: Some(uid_map),
+ gid_map: Some(gid_map),
+ log_failures: jail_config.seccomp_log_failures,
+ seccomp_policy: &seccomp_policy,
+ // We want bind mounts from the parent namespaces to propagate into the 9p server's
+ // namespace.
+ remount_mode: Some(libc::MS_SLAVE),
+ };
+
+ let jail = create_base_minijail(src, Some(max_open_files), Some(&config))?;
+
+ // The shared directory becomes the root of the device's file system.
+ let root = Path::new("/");
+ (Some(jail), root)
+ } else {
+ // There's no mount namespace so we tell the server to treat the source directory as the
+ // root.
+ (None, src)
+ };
+
+ let features = virtio::base_features(cfg.protected_vm);
+ p9_cfg.root = root.into();
+ let dev = virtio::P9::new(features, tag, p9_cfg).context("failed to create 9p device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail,
+ })
+}
+
+pub fn create_pmem_device(
+ cfg: &Config,
+ vm: &mut impl Vm,
+ resources: &mut SystemAllocator,
+ disk: &DiskOption,
+ index: usize,
+ pmem_device_tube: Tube,
+) -> DeviceResult {
+ let fd = open_file(
+ &disk.path,
+ OpenOptions::new().read(true).write(!disk.read_only),
+ )
+ .with_context(|| format!("failed to load disk image {}", disk.path.display()))?;
+
+ let (disk_size, arena_size) = {
+ let metadata = std::fs::metadata(&disk.path).with_context(|| {
+ format!("failed to get disk image {} metadata", disk.path.display())
+ })?;
+ let disk_len = metadata.len();
+ // Linux requires pmem region sizes to be 2 MiB aligned. Linux will fill any partial page
+ // at the end of an mmap'd file and won't write back beyond the actual file length, but if
+ // we just align the size of the file to 2 MiB then access beyond the last page of the
+ // mapped file will generate SIGBUS. So use a memory mapping arena that will provide
+ // padding up to 2 MiB.
+ let alignment = 2 * 1024 * 1024;
+ let align_adjust = if disk_len % alignment != 0 {
+ alignment - (disk_len % alignment)
+ } else {
+ 0
+ };
+ (
+ disk_len,
+ disk_len
+ .checked_add(align_adjust)
+ .ok_or_else(|| anyhow!("pmem device image too big"))?,
+ )
+ };
+
+ let protection = {
+ if disk.read_only {
+ Protection::read()
+ } else {
+ Protection::read_write()
+ }
+ };
+
+ let arena = {
+ // Conversion from u64 to usize may fail on 32bit system.
+ let arena_size = usize::try_from(arena_size).context("pmem device image too big")?;
+ let disk_size = usize::try_from(disk_size).context("pmem device image too big")?;
+
+ let mut arena =
+ MemoryMappingArena::new(arena_size).context("failed to reserve pmem memory")?;
+ arena
+ .add_fd_offset_protection(0, disk_size, &fd, 0, protection)
+ .context("failed to reserve pmem memory")?;
+
+ // If the disk is not a multiple of the page size, the OS will fill the remaining part
+ // of the page with zeroes. However, the anonymous mapping added below must start on a
+ // page boundary, so round up the size before calculating the offset of the anon region.
+ let disk_size = round_up_to_page_size(disk_size);
+
+ if arena_size > disk_size {
+ // Add an anonymous region with the same protection as the disk mapping if the arena
+ // size was aligned.
+ arena
+ .add_anon_protection(disk_size, arena_size - disk_size, protection)
+ .context("failed to reserve pmem padding")?;
+ }
+ arena
+ };
+
+ let mapping_address = resources
+ .mmio_allocator(MmioType::High)
+ .reverse_allocate_with_align(
+ arena_size,
+ Alloc::PmemDevice(index),
+ format!("pmem_disk_image_{}", index),
+ // Linux kernel requires pmem namespaces to be 128 MiB aligned.
+ 128 * 1024 * 1024, /* 128 MiB */
+ )
+ .context("failed to allocate memory for pmem device")?;
+
+ let slot = vm
+ .add_memory_region(
+ GuestAddress(mapping_address),
+ Box::new(arena),
+ /* read_only = */ disk.read_only,
+ /* log_dirty_pages = */ false,
+ )
+ .context("failed to add pmem device memory")?;
+
+ let dev = virtio::Pmem::new(
+ virtio::base_features(cfg.protected_vm),
+ fd,
+ GuestAddress(mapping_address),
+ slot,
+ arena_size,
+ Some(pmem_device_tube),
+ )
+ .context("failed to create pmem device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev) as Box<dyn VirtioDevice>,
+ jail: simple_jail(&cfg.jail_config, "pmem_device")?,
+ })
+}
+
+pub fn create_iommu_device(
+ cfg: &Config,
+ phys_max_addr: u64,
+ endpoints: BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>,
+ hp_endpoints_ranges: Vec<RangeInclusive<u32>>,
+ translate_response_senders: Option<BTreeMap<u32, Tube>>,
+ translate_request_rx: Option<Tube>,
+ iommu_device_tube: Tube,
+) -> DeviceResult {
+ let dev = virtio::Iommu::new(
+ virtio::base_features(cfg.protected_vm),
+ endpoints,
+ phys_max_addr,
+ hp_endpoints_ranges,
+ translate_response_senders,
+ translate_request_rx,
+ Some(iommu_device_tube),
+ )
+ .context("failed to create IOMMU device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "iommu_device")?,
+ })
+}
+
+fn add_bind_mounts(param: &SerialParameters, jail: &mut Minijail) -> Result<(), minijail::Error> {
+ if let Some(path) = &param.path {
+ if let SerialType::SystemSerialType = param.type_ {
+ if let Some(parent) = path.as_path().parent() {
+ if parent.exists() {
+ info!("Bind mounting dir {}", parent.display());
+ jail.mount_bind(parent, parent, true)?;
+ }
+ }
+ }
+ }
+ Ok(())
+}
+
+pub fn create_console_device(cfg: &Config, param: &SerialParameters) -> DeviceResult {
+ let mut keep_rds = Vec::new();
+ let evt = Event::new().context("failed to create event")?;
+ let dev = param
+ .create_serial_device::<Console>(cfg.protected_vm, &evt, &mut keep_rds)
+ .context("failed to create console device")?;
+
+ let jail = match simple_jail(&cfg.jail_config, "serial")? {
+ Some(mut jail) => {
+ // Create a tmpfs in the device's root directory so that we can bind mount the
+ // log socket directory into it.
+ // The size=67108864 is size=64*1024*1024 or size=64MB.
+ jail.mount_with_data(
+ Path::new("none"),
+ Path::new("/"),
+ "tmpfs",
+ (libc::MS_NODEV | libc::MS_NOEXEC | libc::MS_NOSUID) as usize,
+ "size=67108864",
+ )?;
+ add_current_user_to_jail(&mut jail)?;
+ let res = add_bind_mounts(param, &mut jail);
+ if res.is_err() {
+ error!("failed to add bind mounts for console device");
+ }
+ Some(jail)
+ }
+ None => None,
+ };
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail, // TODO(dverkamp): use a separate policy for console?
+ })
+}
+
+#[cfg(feature = "audio")]
+pub fn create_sound_device(path: &Path, cfg: &Config) -> DeviceResult {
+ let dev = virtio::new_sound(path, virtio::base_features(cfg.protected_vm))
+ .context("failed to create sound device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "vios_audio_device")?,
+ })
+}
+
+pub fn create_vfio_device(
+ cfg: &Config,
+ vm: &impl Vm,
+ resources: &mut SystemAllocator,
+ control_tubes: &mut Vec<TaggedControlTube>,
+ vfio_path: &Path,
+ bus_num: Option<u8>,
+ guest_address: Option<PciAddress>,
+ iommu_endpoints: &mut BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>,
+ coiommu_endpoints: Option<&mut Vec<u16>>,
+ iommu_dev: IommuDevType,
+) -> DeviceResult<(Box<VfioPciDevice>, Option<Minijail>)> {
+ let vfio_container = VfioCommonSetup::vfio_get_container(iommu_dev, Some(vfio_path))
+ .context("failed to get vfio container")?;
+
+ // create MSI, MSI-X, and Mem request sockets for each vfio device
+ let (vfio_host_tube_msi, vfio_device_tube_msi) =
+ Tube::pair().context("failed to create tube")?;
+ control_tubes.push(TaggedControlTube::VmIrq(vfio_host_tube_msi));
+
+ let (vfio_host_tube_msix, vfio_device_tube_msix) =
+ Tube::pair().context("failed to create tube")?;
+ control_tubes.push(TaggedControlTube::VmIrq(vfio_host_tube_msix));
+
+ let (vfio_host_tube_mem, vfio_device_tube_mem) =
+ Tube::pair().context("failed to create tube")?;
+ control_tubes.push(TaggedControlTube::VmMemory(vfio_host_tube_mem));
+
+ let hotplug = bus_num.is_some();
+ let vfio_device_tube_vm = if hotplug {
+ let (vfio_host_tube_vm, device_tube_vm) = Tube::pair().context("failed to create tube")?;
+ control_tubes.push(TaggedControlTube::Vm(vfio_host_tube_vm));
+ Some(device_tube_vm)
+ } else {
+ None
+ };
+
+ let vfio_device = VfioDevice::new_passthrough(
+ &vfio_path,
+ vm,
+ vfio_container.clone(),
+ iommu_dev != IommuDevType::NoIommu,
+ )
+ .context("failed to create vfio device")?;
+ let mut vfio_pci_device = Box::new(VfioPciDevice::new(
+ #[cfg(feature = "direct")]
+ vfio_path,
+ vfio_device,
+ bus_num,
+ guest_address,
+ vfio_device_tube_msi,
+ vfio_device_tube_msix,
+ vfio_device_tube_mem,
+ vfio_device_tube_vm,
+ ));
+ // early reservation for pass-through PCI devices.
+ let endpoint_addr = vfio_pci_device
+ .allocate_address(resources)
+ .context("failed to allocate resources early for vfio pci dev")?;
+
+ match iommu_dev {
+ IommuDevType::NoIommu => {}
+ IommuDevType::VirtioIommu => {
+ iommu_endpoints.insert(
+ endpoint_addr.to_u32(),
+ Arc::new(Mutex::new(Box::new(VfioWrapper::new(
+ vfio_container,
+ vm.get_memory().clone(),
+ )))),
+ );
+ }
+ IommuDevType::CoIommu => {
+ if let Some(endpoints) = coiommu_endpoints {
+ endpoints.push(endpoint_addr.to_u32() as u16);
+ } else {
+ bail!("Missed coiommu_endpoints vector to store the endpoint addr");
+ }
+ }
+ }
+
+ if hotplug {
+ Ok((vfio_pci_device, None))
+ } else {
+ Ok((
+ vfio_pci_device,
+ simple_jail(&cfg.jail_config, "vfio_device")?,
+ ))
+ }
+}
+
+pub fn create_vfio_platform_device(
+ cfg: &Config,
+ vm: &impl Vm,
+ _resources: &mut SystemAllocator,
+ control_tubes: &mut Vec<TaggedControlTube>,
+ vfio_path: &Path,
+ _endpoints: &mut BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>,
+ iommu_dev: IommuDevType,
+) -> DeviceResult<(VfioPlatformDevice, Option<Minijail>)> {
+ let vfio_container = VfioCommonSetup::vfio_get_container(iommu_dev, Some(vfio_path))
+ .context("Failed to create vfio device")?;
+
+ let (vfio_host_tube_mem, vfio_device_tube_mem) =
+ Tube::pair().context("failed to create tube")?;
+ control_tubes.push(TaggedControlTube::VmMemory(vfio_host_tube_mem));
+
+ let vfio_device = VfioDevice::new_passthrough(
+ &vfio_path,
+ vm,
+ vfio_container,
+ iommu_dev != IommuDevType::NoIommu,
+ )
+ .context("Failed to create vfio device")?;
+ let vfio_plat_dev = VfioPlatformDevice::new(vfio_device, vfio_device_tube_mem);
+
+ Ok((
+ vfio_plat_dev,
+ simple_jail(&cfg.jail_config, "vfio_platform_device")?,
+ ))
+}
+
+/// Setup for devices with VIRTIO_F_ACCESS_PLATFORM
+pub fn setup_virtio_access_platform(
+ resources: &mut SystemAllocator,
+ iommu_attached_endpoints: &mut BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>,
+ devices: &mut [(Box<dyn BusDeviceObj>, Option<Minijail>)],
+) -> DeviceResult<(Option<BTreeMap<u32, Tube>>, Option<Tube>)> {
+ let mut translate_response_senders: Option<
+ BTreeMap<
+ u32, // endpoint id
+ Tube,
+ >,
+ > = None;
+ let mut tube_pair: Option<(Tube, Tube)> = None;
+
+ for dev in devices.iter_mut() {
+ if let Some(pci_dev) = dev.0.as_pci_device_mut() {
+ if pci_dev.supports_iommu() {
+ let endpoint_id = pci_dev
+ .allocate_address(resources)
+ .context("failed to allocate resources for pci dev")?
+ .to_u32();
+ let mapper: Arc<Mutex<Box<dyn MemoryMapperTrait>>> =
+ Arc::new(Mutex::new(Box::new(BasicMemoryMapper::new(u64::MAX))));
+ let (request_tx, _request_rx) =
+ tube_pair.get_or_insert_with(|| Tube::pair().unwrap());
+ let CreateIpcMapperRet {
+ mapper: ipc_mapper,
+ response_tx,
+ } = create_ipc_mapper(
+ endpoint_id,
+ #[allow(deprecated)]
+ request_tx.try_clone()?,
+ );
+ translate_response_senders
+ .get_or_insert_with(BTreeMap::new)
+ .insert(endpoint_id, response_tx);
+ iommu_attached_endpoints.insert(endpoint_id, mapper);
+ pci_dev.set_iommu(ipc_mapper)?;
+ }
+ }
+ }
+
+ Ok((
+ translate_response_senders,
+ tube_pair.map(|(_request_tx, request_rx)| request_rx),
+ ))
+}
diff --git a/src/linux/gpu.rs b/src/linux/gpu.rs
new file mode 100644
index 000000000..7fe860a9f
--- /dev/null
+++ b/src/linux/gpu.rs
@@ -0,0 +1,332 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! GPU related things
+//! depends on "gpu" feature
+use std::collections::HashSet;
+use std::env;
+use std::path::PathBuf;
+
+use devices::virtio::vhost::user::vmm::Gpu as VhostUserGpu;
+
+use crate::{JailConfig, VhostUserOption};
+
+use super::*;
+
+pub fn create_vhost_user_gpu_device(
+ cfg: &Config,
+ opt: &VhostUserOption,
+ gpu_tubes: (Tube, Tube),
+ device_control_tube: Tube,
+) -> DeviceResult {
+ // The crosvm gpu device expects us to connect the tube before it will accept a vhost-user
+ // connection.
+ let dev = VhostUserGpu::new(
+ virtio::base_features(cfg.protected_vm),
+ &opt.socket,
+ gpu_tubes,
+ device_control_tube,
+ )
+ .context("failed to set up vhost-user gpu device")?;
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ // no sandbox here because virtqueue handling is exported to a different process.
+ jail: None,
+ })
+}
+
+pub fn gpu_jail(jail_config: &Option<JailConfig>, policy: &str) -> Result<Option<Minijail>> {
+ match simple_jail(jail_config, policy)? {
+ Some(mut jail) => {
+ // Create a tmpfs in the device's root directory so that we can bind mount the
+ // dri directory into it. The size=67108864 is size=64*1024*1024 or size=64MB.
+ jail.mount_with_data(
+ Path::new("none"),
+ Path::new("/"),
+ "tmpfs",
+ (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
+ "size=67108864",
+ )?;
+
+ // Device nodes required for DRM.
+ let sys_dev_char_path = Path::new("/sys/dev/char");
+ jail.mount_bind(sys_dev_char_path, sys_dev_char_path, false)?;
+ let sys_devices_path = Path::new("/sys/devices");
+ jail.mount_bind(sys_devices_path, sys_devices_path, false)?;
+
+ let drm_dri_path = Path::new("/dev/dri");
+ if drm_dri_path.exists() {
+ jail.mount_bind(drm_dri_path, drm_dri_path, false)?;
+ }
+
+ // If the ARM specific devices exist on the host, bind mount them in.
+ let mali0_path = Path::new("/dev/mali0");
+ if mali0_path.exists() {
+ jail.mount_bind(mali0_path, mali0_path, true)?;
+ }
+
+ let pvr_sync_path = Path::new("/dev/pvr_sync");
+ if pvr_sync_path.exists() {
+ jail.mount_bind(pvr_sync_path, pvr_sync_path, true)?;
+ }
+
+ // If the udmabuf driver exists on the host, bind mount it in.
+ let udmabuf_path = Path::new("/dev/udmabuf");
+ if udmabuf_path.exists() {
+ jail.mount_bind(udmabuf_path, udmabuf_path, true)?;
+ }
+
+ // Libraries that are required when mesa drivers are dynamically loaded.
+ jail_mount_bind_if_exists(
+ &mut jail,
+ &[
+ "/usr/lib",
+ "/usr/lib64",
+ "/lib",
+ "/lib64",
+ "/usr/share/drirc.d",
+ "/usr/share/glvnd",
+ "/usr/share/vulkan",
+ ],
+ )?;
+
+ // pvr driver requires read access to /proc/self/task/*/comm.
+ let proc_path = Path::new("/proc");
+ jail.mount(
+ proc_path,
+ proc_path,
+ "proc",
+ (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC | libc::MS_RDONLY) as usize,
+ )?;
+
+ // To enable perfetto tracing, we need to give access to the perfetto service IPC
+ // endpoints.
+ let perfetto_path = Path::new("/run/perfetto");
+ if perfetto_path.exists() {
+ jail.mount_bind(perfetto_path, perfetto_path, true)?;
+ }
+
+ Ok(Some(jail))
+ }
+ None => Ok(None),
+ }
+}
+
+pub struct GpuCacheInfo<'a> {
+ directory: Option<&'a str>,
+ environment: Vec<(&'a str, &'a str)>,
+}
+
+pub fn get_gpu_cache_info<'a>(
+ cache_dir: Option<&'a String>,
+ cache_size: Option<&'a String>,
+ sandbox: bool,
+) -> GpuCacheInfo<'a> {
+ let mut dir = None;
+ let mut env = Vec::new();
+
+ if let Some(cache_dir) = cache_dir {
+ if !Path::new(cache_dir).exists() {
+ warn!("shader caching dir {} does not exist", cache_dir);
+ env.push(("MESA_GLSL_CACHE_DISABLE", "true"));
+ } else if cfg!(any(target_arch = "arm", target_arch = "aarch64")) && sandbox {
+ warn!("shader caching not yet supported on ARM with sandbox enabled");
+ env.push(("MESA_GLSL_CACHE_DISABLE", "true"));
+ } else {
+ dir = Some(cache_dir.as_str());
+
+ env.push(("MESA_GLSL_CACHE_DISABLE", "false"));
+ env.push(("MESA_GLSL_CACHE_DIR", cache_dir.as_str()));
+ if let Some(cache_size) = cache_size {
+ env.push(("MESA_GLSL_CACHE_MAX_SIZE", cache_size.as_str()));
+ }
+ }
+ }
+
+ GpuCacheInfo {
+ directory: dir,
+ environment: env,
+ }
+}
+
+pub fn create_gpu_device(
+ cfg: &Config,
+ exit_evt: &Event,
+ gpu_device_tube: Tube,
+ resource_bridges: Vec<Tube>,
+ wayland_socket_path: Option<&PathBuf>,
+ x_display: Option<String>,
+ render_server_fd: Option<SafeDescriptor>,
+ event_devices: Vec<EventDevice>,
+ map_request: Arc<Mutex<Option<ExternalMapping>>>,
+) -> DeviceResult {
+ let mut display_backends = vec![
+ virtio::DisplayBackend::X(x_display),
+ virtio::DisplayBackend::Stub,
+ ];
+
+ let wayland_socket_dirs = cfg
+ .wayland_socket_paths
+ .iter()
+ .map(|(_name, path)| path.parent())
+ .collect::<Option<Vec<_>>>()
+ .ok_or_else(|| anyhow!("wayland socket path has no parent or file name"))?;
+
+ if let Some(socket_path) = wayland_socket_path {
+ display_backends.insert(
+ 0,
+ virtio::DisplayBackend::Wayland(Some(socket_path.to_owned())),
+ );
+ }
+
+ let dev = virtio::Gpu::new(
+ exit_evt.try_clone().context("failed to clone event")?,
+ Some(gpu_device_tube),
+ resource_bridges,
+ display_backends,
+ cfg.gpu_parameters.as_ref().unwrap(),
+ render_server_fd,
+ event_devices,
+ map_request,
+ cfg.jail_config.is_some(),
+ virtio::base_features(cfg.protected_vm),
+ cfg.wayland_socket_paths.clone(),
+ );
+
+ let jail = match gpu_jail(&cfg.jail_config, "gpu_device")? {
+ Some(mut jail) => {
+ // Prepare GPU shader disk cache directory.
+ let (cache_dir, cache_size) = cfg
+ .gpu_parameters
+ .as_ref()
+ .map(|params| (params.cache_path.as_ref(), params.cache_size.as_ref()))
+ .unwrap();
+ let cache_info = get_gpu_cache_info(cache_dir, cache_size, cfg.jail_config.is_some());
+
+ if let Some(dir) = cache_info.directory {
+ jail.mount_bind(dir, dir, true)?;
+ }
+ for (key, val) in cache_info.environment {
+ env::set_var(key, val);
+ }
+
+ // Bind mount the wayland socket's directory into jail's root. This is necessary since
+ // each new wayland context must open() the socket. If the wayland socket is ever
+ // destroyed and remade in the same host directory, new connections will be possible
+ // without restarting the wayland device.
+ for dir in &wayland_socket_dirs {
+ jail.mount_bind(dir, dir, true)?;
+ }
+
+ add_current_user_to_jail(&mut jail)?;
+
+ Some(jail)
+ }
+ None => None,
+ };
+
+ Ok(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail,
+ })
+}
+
+#[derive(Debug)]
+pub struct GpuRenderServerParameters {
+ pub path: PathBuf,
+ pub cache_path: Option<String>,
+ pub cache_size: Option<String>,
+}
+
+fn get_gpu_render_server_environment(cache_info: &GpuCacheInfo) -> Result<Vec<String>> {
+ let mut env = Vec::new();
+
+ let mut cache_env_keys = HashSet::with_capacity(cache_info.environment.len());
+ for (key, val) in cache_info.environment.iter() {
+ env.push(format!("{}={}", key, val));
+ cache_env_keys.insert(*key);
+ }
+
+ for (key_os, val_os) in env::vars_os() {
+ // minijail should accept OsStr rather than str...
+ let into_string_err = |_| anyhow!("invalid environment key/val");
+ let key = key_os.into_string().map_err(into_string_err)?;
+ let val = val_os.into_string().map_err(into_string_err)?;
+
+ if !cache_env_keys.contains(key.as_str()) {
+ env.push(format!("{}={}", key, val));
+ }
+ }
+
+ Ok(env)
+}
+
+pub fn start_gpu_render_server(
+ cfg: &Config,
+ render_server_parameters: &GpuRenderServerParameters,
+) -> Result<(Minijail, SafeDescriptor)> {
+ let (server_socket, client_socket) =
+ UnixSeqpacket::pair().context("failed to create render server socket")?;
+
+ let mut env = None;
+ let jail = match gpu_jail(&cfg.jail_config, "gpu_render_server")? {
+ Some(mut jail) => {
+ let cache_info = get_gpu_cache_info(
+ render_server_parameters.cache_path.as_ref(),
+ render_server_parameters.cache_size.as_ref(),
+ true,
+ );
+
+ if let Some(dir) = cache_info.directory {
+ jail.mount_bind(dir, dir, true)?;
+ }
+
+ if !cache_info.environment.is_empty() {
+ env = Some(get_gpu_render_server_environment(&cache_info)?);
+ }
+
+ // bind mount /dev/log for syslog
+ let log_path = Path::new("/dev/log");
+ if log_path.exists() {
+ jail.mount_bind(log_path, log_path, true)?;
+ }
+
+ // Run as root in the jail to keep capabilities after execve, which is needed for
+ // mounting to work. All capabilities will be dropped afterwards.
+ add_current_user_as_root_to_jail(&mut jail)?;
+
+ jail
+ }
+ None => Minijail::new().context("failed to create jail")?,
+ };
+
+ let inheritable_fds = [
+ server_socket.as_raw_descriptor(),
+ libc::STDOUT_FILENO,
+ libc::STDERR_FILENO,
+ ];
+
+ let cmd = &render_server_parameters.path;
+ let cmd_str = cmd
+ .to_str()
+ .ok_or_else(|| anyhow!("invalid render server path"))?;
+ let fd_str = server_socket.as_raw_descriptor().to_string();
+ let args = [cmd_str, "--socket-fd", &fd_str];
+
+ let mut envp: Option<Vec<&str>> = None;
+ if let Some(ref env) = env {
+ envp = Some(env.iter().map(AsRef::as_ref).collect());
+ }
+
+ jail.run_command(minijail::Command::new_for_path(
+ cmd,
+ &inheritable_fds,
+ &args,
+ envp.as_deref(),
+ )?)
+ .context("failed to start gpu render server")?;
+
+ Ok((jail, SafeDescriptor::from(client_socket)))
+}
diff --git a/src/linux/jail_helpers.rs b/src/linux/jail_helpers.rs
new file mode 100644
index 000000000..60dd5df37
--- /dev/null
+++ b/src/linux/jail_helpers.rs
@@ -0,0 +1,205 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::path::{Path, PathBuf};
+use std::str;
+
+use libc::{self, c_ulong, gid_t, uid_t};
+
+use anyhow::{bail, Context, Result};
+use base::*;
+use minijail::{self, Minijail};
+
+use crate::JailConfig;
+
+pub(super) struct SandboxConfig<'a> {
+ pub(super) limit_caps: bool,
+ pub(super) log_failures: bool,
+ pub(super) seccomp_policy: &'a Path,
+ pub(super) uid_map: Option<&'a str>,
+ pub(super) gid_map: Option<&'a str>,
+ pub(super) remount_mode: Option<c_ulong>,
+}
+
+pub(crate) struct ScopedMinijail(pub Minijail);
+
+impl Drop for ScopedMinijail {
+ fn drop(&mut self) {
+ let _ = self.0.kill();
+ }
+}
+
+pub(super) fn create_base_minijail(
+ root: &Path,
+ r_limit: Option<u64>,
+ config: Option<&SandboxConfig>,
+) -> Result<Minijail> {
+ // All child jails run in a new user namespace without any users mapped,
+ // they run as nobody unless otherwise configured.
+ let mut j = Minijail::new().context("failed to jail device")?;
+
+ if let Some(config) = config {
+ j.namespace_pids();
+ j.namespace_user();
+ j.namespace_user_disable_setgroups();
+ if config.limit_caps {
+ // Don't need any capabilities.
+ j.use_caps(0);
+ }
+ if let Some(uid_map) = config.uid_map {
+ j.uidmap(uid_map).context("error setting UID map")?;
+ }
+ if let Some(gid_map) = config.gid_map {
+ j.gidmap(gid_map).context("error setting GID map")?;
+ }
+ // Run in a new mount namespace.
+ j.namespace_vfs();
+
+ // Run in an empty network namespace.
+ j.namespace_net();
+
+ // Don't allow the device to gain new privileges.
+ j.no_new_privs();
+
+ // By default we'll prioritize using the pre-compiled .bpf over the .policy
+ // file (the .bpf is expected to be compiled using "trap" as the failure
+ // behavior instead of the default "kill" behavior).
+ // Refer to the code comment for the "seccomp-log-failures"
+ // command-line parameter for an explanation about why the |log_failures|
+ // flag forces the use of .policy files (and the build-time alternative to
+ // this run-time flag).
+ let bpf_policy_file = config.seccomp_policy.with_extension("bpf");
+ if bpf_policy_file.exists() && !config.log_failures {
+ j.parse_seccomp_program(&bpf_policy_file)
+ .context("failed to parse precompiled seccomp policy")?;
+ } else {
+ // Use TSYNC only for the side effect of it using SECCOMP_RET_TRAP,
+ // which will correctly kill the entire device process if a worker
+ // thread commits a seccomp violation.
+ j.set_seccomp_filter_tsync();
+ if config.log_failures {
+ j.log_seccomp_filter_failures();
+ }
+ j.parse_seccomp_filters(&config.seccomp_policy.with_extension("policy"))
+ .context("failed to parse seccomp policy")?;
+ }
+ j.use_seccomp_filter();
+ // Don't do init setup.
+ j.run_as_init();
+ // Set up requested remount mode instead of default MS_PRIVATE.
+ if let Some(mode) = config.remount_mode {
+ j.set_remount_mode(mode);
+ }
+ }
+
+ // Only pivot_root if we are not re-using the current root directory.
+ if root != Path::new("/") {
+ // It's safe to call `namespace_vfs` multiple times.
+ j.namespace_vfs();
+ j.enter_pivot_root(root)
+ .context("failed to pivot root device")?;
+ }
+
+ // Most devices don't need to open many fds.
+ let limit = if let Some(r) = r_limit { r } else { 1024u64 };
+ j.set_rlimit(libc::RLIMIT_NOFILE as i32, limit, limit)
+ .context("error setting max open files")?;
+
+ Ok(j)
+}
+
+pub(super) fn simple_jail(
+ jail_config: &Option<JailConfig>,
+ policy: &str,
+) -> Result<Option<Minijail>> {
+ if let Some(jail_config) = jail_config {
+ // A directory for a jailed device's pivot root.
+ if !jail_config.pivot_root.exists() {
+ bail!(
+ "{:?} doesn't exist, can't jail devices",
+ jail_config.pivot_root
+ );
+ }
+ let policy_path: PathBuf = jail_config.seccomp_policy_dir.join(policy);
+ let config = SandboxConfig {
+ limit_caps: true,
+ log_failures: jail_config.seccomp_log_failures,
+ seccomp_policy: &policy_path,
+ uid_map: None,
+ gid_map: None,
+ remount_mode: None,
+ };
+ Ok(Some(create_base_minijail(
+ &jail_config.pivot_root,
+ None,
+ Some(&config),
+ )?))
+ } else {
+ Ok(None)
+ }
+}
+
+/// Mirror-mount all the directories in `dirs` into `jail` on a best-effort basis.
+///
+/// This function will not return an error if any of the directories in `dirs` is missing.
+#[cfg(any(feature = "gpu", feature = "video-decoder", feature = "video-encoder"))]
+pub(super) fn jail_mount_bind_if_exists<P: AsRef<std::ffi::OsStr>>(
+ jail: &mut Minijail,
+ dirs: &[P],
+) -> Result<()> {
+ for dir in dirs {
+ let dir_path = Path::new(dir);
+ if dir_path.exists() {
+ jail.mount_bind(dir_path, dir_path, false)?;
+ }
+ }
+
+ Ok(())
+}
+
+#[derive(Copy, Clone)]
+#[cfg_attr(not(feature = "tpm"), allow(dead_code))]
+pub(super) struct Ids {
+ pub(super) uid: uid_t,
+ pub(super) gid: gid_t,
+}
+
+#[cfg(feature = "gpu")]
+pub(super) fn add_current_user_as_root_to_jail(jail: &mut Minijail) -> Result<Ids> {
+ let crosvm_uid = geteuid();
+ let crosvm_gid = getegid();
+ jail.uidmap(&format!("0 {0} 1", crosvm_uid))
+ .context("error setting UID map")?;
+ jail.gidmap(&format!("0 {0} 1", crosvm_gid))
+ .context("error setting GID map")?;
+
+ Ok(Ids {
+ uid: crosvm_uid,
+ gid: crosvm_gid,
+ })
+}
+
+/// Set the uid/gid for the jailed process and give a basic id map. This is
+/// required for bind mounts to work.
+pub(super) fn add_current_user_to_jail(jail: &mut Minijail) -> Result<Ids> {
+ let crosvm_uid = geteuid();
+ let crosvm_gid = getegid();
+
+ jail.uidmap(&format!("{0} {0} 1", crosvm_uid))
+ .context("error setting UID map")?;
+ jail.gidmap(&format!("{0} {0} 1", crosvm_gid))
+ .context("error setting GID map")?;
+
+ if crosvm_uid != 0 {
+ jail.change_uid(crosvm_uid);
+ }
+ if crosvm_gid != 0 {
+ jail.change_gid(crosvm_gid);
+ }
+
+ Ok(Ids {
+ uid: crosvm_uid,
+ gid: crosvm_gid,
+ })
+}
diff --git a/src/linux/mod.rs b/src/linux/mod.rs
new file mode 100644
index 000000000..444d8c9b4
--- /dev/null
+++ b/src/linux/mod.rs
@@ -0,0 +1,2330 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::cmp::{max, Reverse};
+use std::collections::{BTreeMap, BTreeSet};
+use std::convert::TryInto;
+use std::fs::{File, OpenOptions};
+use std::io::prelude::*;
+use std::io::stdin;
+use std::iter;
+use std::mem;
+use std::ops::RangeInclusive;
+#[cfg(feature = "gpu")]
+use std::os::unix::net::UnixStream;
+use std::os::unix::prelude::OpenOptionsExt;
+use std::path::Path;
+use std::str::FromStr;
+use std::sync::{mpsc, Arc, Barrier};
+use std::time::Duration;
+
+use std::process;
+#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+use std::thread;
+
+use devices::virtio::vhost::vsock::{VhostVsockConfig, VhostVsockDeviceParameter};
+use libc;
+
+use acpi_tables::sdt::SDT;
+
+use anyhow::{anyhow, bail, Context, Result};
+use base::*;
+use base::{UnixSeqpacket, UnixSeqpacketListener, UnlinkUnixSeqpacketListener};
+use devices::serial_device::SerialHardware;
+use devices::vfio::{VfioCommonSetup, VfioCommonTrait};
+use devices::virtio::memory_mapper::MemoryMapperTrait;
+#[cfg(feature = "gpu")]
+use devices::virtio::{self, EventDevice};
+#[cfg(feature = "audio")]
+use devices::Ac97Dev;
+use devices::{
+ self, BusDeviceObj, HostHotPlugKey, HotPlugBus, IrqEventIndex, KvmKernelIrqChip, PciAddress,
+ PciDevice, PvPanicCode, PvPanicPciDevice, StubPciDevice, VirtioPciDevice,
+};
+use devices::{CoIommuDev, IommuDevType};
+#[cfg(feature = "usb")]
+use devices::{HostBackendDeviceProvider, XhciController};
+use hypervisor::kvm::{Kvm, KvmVcpu, KvmVm};
+use hypervisor::{HypervisorCap, ProtectionType, Vm, VmCap};
+use minijail::{self, Minijail};
+use resources::{Alloc, SystemAllocator};
+use rutabaga_gfx::RutabagaGralloc;
+use sync::Mutex;
+use vm_control::*;
+use vm_memory::{GuestAddress, GuestMemory, MemoryPolicy};
+
+#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+use crate::gdb::{gdb_thread, GdbStub};
+use crate::{Config, Executable, FileBackedMappingParameters, SharedDir, SharedDirKind, VfioType};
+use arch::{
+ self, LinuxArch, RunnableLinuxVm, VcpuAffinity, VirtioDeviceStub, VmComponents, VmImage,
+};
+
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use {
+ crate::HostPcieRootPortParameters,
+ devices::{
+ IrqChipX86_64 as IrqChipArch, KvmSplitIrqChip, PciBridge, PcieHostRootPort, PcieRootPort,
+ },
+ hypervisor::{VcpuX86_64 as VcpuArch, VmX86_64 as VmArch},
+ x86_64::X8664arch as Arch,
+};
+#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
+use {
+ aarch64::AArch64 as Arch,
+ devices::IrqChipAArch64 as IrqChipArch,
+ hypervisor::{VcpuAArch64 as VcpuArch, VmAArch64 as VmArch},
+};
+
+mod device_helpers;
+use device_helpers::*;
+pub(crate) mod jail_helpers;
+use jail_helpers::*;
+mod vcpu;
+
+#[cfg(feature = "gpu")]
+pub(crate) mod gpu;
+#[cfg(feature = "gpu")]
+pub use gpu::GpuRenderServerParameters;
+#[cfg(feature = "gpu")]
+use gpu::*;
+
+#[cfg(target_os = "android")]
+mod android;
+
+// gpu_device_tube is not used when GPU support is disabled.
+#[cfg_attr(not(feature = "gpu"), allow(unused_variables))]
+fn create_virtio_devices(
+ cfg: &Config,
+ vm: &mut impl Vm,
+ resources: &mut SystemAllocator,
+ _exit_evt: &Event,
+ wayland_device_tube: Tube,
+ gpu_device_tube: Tube,
+ vhost_user_gpu_tubes: Vec<(Tube, Tube, Tube)>,
+ balloon_device_tube: Option<Tube>,
+ balloon_inflate_tube: Option<Tube>,
+ init_balloon_size: u64,
+ disk_device_tubes: &mut Vec<Tube>,
+ pmem_device_tubes: &mut Vec<Tube>,
+ map_request: Arc<Mutex<Option<ExternalMapping>>>,
+ fs_device_tubes: &mut Vec<Tube>,
+ #[cfg(feature = "gpu")] render_server_fd: Option<SafeDescriptor>,
+ vvu_proxy_device_tubes: &mut Vec<Tube>,
+ vvu_proxy_max_sibling_mem_size: u64,
+) -> DeviceResult<Vec<VirtioDeviceStub>> {
+ let mut devs = Vec::new();
+
+ #[cfg(feature = "gpu")]
+ for (opt, (host_gpu_tube, device_gpu_tube, device_control_tube)) in
+ cfg.vhost_user_gpu.iter().zip(vhost_user_gpu_tubes)
+ {
+ devs.push(create_vhost_user_gpu_device(
+ cfg,
+ opt,
+ (host_gpu_tube, device_gpu_tube),
+ device_control_tube,
+ )?);
+ }
+
+ for opt in &cfg.vvu_proxy {
+ devs.push(create_vvu_proxy_device(
+ cfg,
+ opt,
+ vvu_proxy_device_tubes.remove(0),
+ vvu_proxy_max_sibling_mem_size,
+ )?);
+ }
+
+ #[cfg_attr(not(feature = "gpu"), allow(unused_mut))]
+ let mut resource_bridges = Vec::<Tube>::new();
+
+ if !cfg.wayland_socket_paths.is_empty() {
+ #[cfg_attr(not(feature = "gpu"), allow(unused_mut))]
+ let mut wl_resource_bridge = None::<Tube>;
+
+ #[cfg(feature = "gpu")]
+ {
+ if cfg.gpu_parameters.is_some() {
+ let (wl_socket, gpu_socket) = Tube::pair().context("failed to create tube")?;
+ resource_bridges.push(gpu_socket);
+ wl_resource_bridge = Some(wl_socket);
+ }
+ }
+
+ devs.push(create_wayland_device(
+ cfg,
+ wayland_device_tube,
+ wl_resource_bridge,
+ )?);
+ }
+
+ #[cfg(feature = "video-decoder")]
+ let video_dec_cfg = if let Some(backend) = cfg.video_dec {
+ let (video_tube, gpu_tube) = Tube::pair().context("failed to create tube")?;
+ resource_bridges.push(gpu_tube);
+ Some((video_tube, backend))
+ } else {
+ None
+ };
+
+ #[cfg(feature = "video-encoder")]
+ let video_enc_cfg = if let Some(backend) = cfg.video_enc {
+ let (video_tube, gpu_tube) = Tube::pair().context("failed to create tube")?;
+ resource_bridges.push(gpu_tube);
+ Some((video_tube, backend))
+ } else {
+ None
+ };
+
+ #[cfg(feature = "gpu")]
+ {
+ if let Some(gpu_parameters) = &cfg.gpu_parameters {
+ let mut gpu_display_w = virtio::DEFAULT_DISPLAY_WIDTH;
+ let mut gpu_display_h = virtio::DEFAULT_DISPLAY_HEIGHT;
+ if !gpu_parameters.displays.is_empty() {
+ gpu_display_w = gpu_parameters.displays[0].width;
+ gpu_display_h = gpu_parameters.displays[0].height;
+ }
+
+ let mut event_devices = Vec::new();
+ if cfg.display_window_mouse {
+ let (event_device_socket, virtio_dev_socket) =
+ UnixStream::pair().context("failed to create socket")?;
+ let (multi_touch_width, multi_touch_height) = cfg
+ .virtio_multi_touch
+ .first()
+ .as_ref()
+ .map(|multi_touch_spec| multi_touch_spec.get_size())
+ .unwrap_or((gpu_display_w, gpu_display_h));
+ let dev = virtio::new_multi_touch(
+ // u32::MAX is the least likely to collide with the indices generated above for
+ // the multi_touch options, which begin at 0.
+ u32::MAX,
+ virtio_dev_socket,
+ multi_touch_width,
+ multi_touch_height,
+ virtio::base_features(cfg.protected_vm),
+ )
+ .context("failed to set up mouse device")?;
+ devs.push(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "input_device")?,
+ });
+ event_devices.push(EventDevice::touchscreen(event_device_socket));
+ }
+ if cfg.display_window_keyboard {
+ let (event_device_socket, virtio_dev_socket) =
+ UnixStream::pair().context("failed to create socket")?;
+ let dev = virtio::new_keyboard(
+ // u32::MAX is the least likely to collide with the indices generated above for
+ // the multi_touch options, which begin at 0.
+ u32::MAX,
+ virtio_dev_socket,
+ virtio::base_features(cfg.protected_vm),
+ )
+ .context("failed to set up keyboard device")?;
+ devs.push(VirtioDeviceStub {
+ dev: Box::new(dev),
+ jail: simple_jail(&cfg.jail_config, "input_device")?,
+ });
+ event_devices.push(EventDevice::keyboard(event_device_socket));
+ }
+
+ devs.push(create_gpu_device(
+ cfg,
+ _exit_evt,
+ gpu_device_tube,
+ resource_bridges,
+ // Use the unnamed socket for GPU display screens.
+ cfg.wayland_socket_paths.get(""),
+ cfg.x_display.clone(),
+ render_server_fd,
+ event_devices,
+ map_request,
+ )?);
+ }
+ }
+
+ for (_, param) in cfg
+ .serial_parameters
+ .iter()
+ .filter(|(_k, v)| v.hardware == SerialHardware::VirtioConsole)
+ {
+ let dev = create_console_device(cfg, param)?;
+ devs.push(dev);
+ }
+
+ for disk in &cfg.disks {
+ let disk_device_tube = disk_device_tubes.remove(0);
+ devs.push(create_block_device(cfg, disk, disk_device_tube)?);
+ }
+
+ for blk in &cfg.vhost_user_blk {
+ devs.push(create_vhost_user_block_device(cfg, blk)?);
+ }
+
+ for console in &cfg.vhost_user_console {
+ devs.push(create_vhost_user_console_device(cfg, console)?);
+ }
+
+ for (index, pmem_disk) in cfg.pmem_devices.iter().enumerate() {
+ let pmem_device_tube = pmem_device_tubes.remove(0);
+ devs.push(create_pmem_device(
+ cfg,
+ vm,
+ resources,
+ pmem_disk,
+ index,
+ pmem_device_tube,
+ )?);
+ }
+
+ if cfg.rng {
+ devs.push(create_rng_device(cfg)?);
+ }
+
+ #[cfg(feature = "tpm")]
+ {
+ if cfg.software_tpm {
+ devs.push(create_software_tpm_device(cfg)?);
+ }
+ }
+
+ for (idx, single_touch_spec) in cfg.virtio_single_touch.iter().enumerate() {
+ devs.push(create_single_touch_device(
+ cfg,
+ single_touch_spec,
+ idx as u32,
+ )?);
+ }
+
+ for (idx, multi_touch_spec) in cfg.virtio_multi_touch.iter().enumerate() {
+ devs.push(create_multi_touch_device(
+ cfg,
+ multi_touch_spec,
+ idx as u32,
+ )?);
+ }
+
+ for (idx, trackpad_spec) in cfg.virtio_trackpad.iter().enumerate() {
+ devs.push(create_trackpad_device(cfg, trackpad_spec, idx as u32)?);
+ }
+
+ for (idx, mouse_socket) in cfg.virtio_mice.iter().enumerate() {
+ devs.push(create_mouse_device(cfg, mouse_socket, idx as u32)?);
+ }
+
+ for (idx, keyboard_socket) in cfg.virtio_keyboard.iter().enumerate() {
+ devs.push(create_keyboard_device(cfg, keyboard_socket, idx as u32)?);
+ }
+
+ for (idx, switches_socket) in cfg.virtio_switches.iter().enumerate() {
+ devs.push(create_switches_device(cfg, switches_socket, idx as u32)?);
+ }
+
+ for dev_path in &cfg.virtio_input_evdevs {
+ devs.push(create_vinput_device(cfg, dev_path)?);
+ }
+
+ if let Some(balloon_device_tube) = balloon_device_tube {
+ devs.push(create_balloon_device(
+ cfg,
+ balloon_device_tube,
+ balloon_inflate_tube,
+ init_balloon_size,
+ )?);
+ }
+
+ // We checked above that if the IP is defined, then the netmask is, too.
+ for tap_fd in &cfg.tap_fd {
+ devs.push(create_tap_net_device_from_fd(cfg, *tap_fd)?);
+ }
+
+ if let (Some(host_ip), Some(netmask), Some(mac_address)) =
+ (cfg.host_ip, cfg.netmask, cfg.mac_address)
+ {
+ if !cfg.vhost_user_net.is_empty() {
+ bail!("vhost-user-net cannot be used with any of --host_ip, --netmask or --mac");
+ }
+ devs.push(create_net_device_from_config(
+ cfg,
+ host_ip,
+ netmask,
+ mac_address,
+ )?);
+ }
+
+ for tap_name in &cfg.tap_name {
+ devs.push(create_tap_net_device_from_name(cfg, tap_name.as_bytes())?);
+ }
+
+ for net in &cfg.vhost_user_net {
+ devs.push(create_vhost_user_net_device(cfg, net)?);
+ }
+
+ for vsock in &cfg.vhost_user_vsock {
+ devs.push(create_vhost_user_vsock_device(cfg, vsock)?);
+ }
+
+ for opt in &cfg.vhost_user_wl {
+ devs.push(create_vhost_user_wl_device(cfg, opt)?);
+ }
+
+ #[cfg(feature = "audio_cras")]
+ {
+ for cras_snd in &cfg.cras_snds {
+ devs.push(create_cras_snd_device(cfg, cras_snd.clone())?);
+ }
+ }
+
+ #[cfg(feature = "video-decoder")]
+ {
+ if let Some((video_dec_tube, video_dec_backend)) = video_dec_cfg {
+ register_video_device(
+ video_dec_backend,
+ &mut devs,
+ video_dec_tube,
+ cfg,
+ devices::virtio::VideoDeviceType::Decoder,
+ )?;
+ }
+ }
+
+ #[cfg(feature = "video-encoder")]
+ {
+ if let Some((video_enc_tube, video_enc_backend)) = video_enc_cfg {
+ register_video_device(
+ video_enc_backend,
+ &mut devs,
+ video_enc_tube,
+ cfg,
+ devices::virtio::VideoDeviceType::Encoder,
+ )?;
+ }
+ }
+
+ if let Some(cid) = cfg.cid {
+ let vhost_config = VhostVsockConfig {
+ device: cfg
+ .vhost_vsock_device
+ .clone()
+ .unwrap_or(VhostVsockDeviceParameter::default()),
+ cid,
+ };
+ devs.push(create_vhost_vsock_device(cfg, &vhost_config)?);
+ }
+
+ for vhost_user_fs in &cfg.vhost_user_fs {
+ devs.push(create_vhost_user_fs_device(cfg, vhost_user_fs)?);
+ }
+
+ #[cfg(feature = "audio")]
+ for vhost_user_snd in &cfg.vhost_user_snd {
+ devs.push(create_vhost_user_snd_device(cfg, vhost_user_snd)?);
+ }
+
+ for shared_dir in &cfg.shared_dirs {
+ let SharedDir {
+ src,
+ tag,
+ kind,
+ uid_map,
+ gid_map,
+ fs_cfg,
+ p9_cfg,
+ } = shared_dir;
+
+ let dev = match kind {
+ SharedDirKind::FS => {
+ let device_tube = fs_device_tubes.remove(0);
+ create_fs_device(cfg, uid_map, gid_map, src, tag, fs_cfg.clone(), device_tube)?
+ }
+ SharedDirKind::P9 => create_9p_device(cfg, uid_map, gid_map, src, tag, p9_cfg.clone())?,
+ };
+ devs.push(dev);
+ }
+
+ if let Some(vhost_user_mac80211_hwsim) = &cfg.vhost_user_mac80211_hwsim {
+ devs.push(create_vhost_user_mac80211_hwsim_device(
+ cfg,
+ vhost_user_mac80211_hwsim,
+ )?);
+ }
+
+ #[cfg(feature = "audio")]
+ if let Some(path) = &cfg.sound {
+ devs.push(create_sound_device(path, cfg)?);
+ }
+
+ Ok(devs)
+}
+
+fn create_devices(
+ cfg: &Config,
+ vm: &mut impl Vm,
+ resources: &mut SystemAllocator,
+ exit_evt: &Event,
+ panic_wrtube: Tube,
+ iommu_attached_endpoints: &mut BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>>,
+ control_tubes: &mut Vec<TaggedControlTube>,
+ wayland_device_tube: Tube,
+ gpu_device_tube: Tube,
+ // Tuple content: (host-side GPU tube, device-side GPU tube, device-side control tube).
+ vhost_user_gpu_tubes: Vec<(Tube, Tube, Tube)>,
+ balloon_device_tube: Option<Tube>,
+ init_balloon_size: u64,
+ disk_device_tubes: &mut Vec<Tube>,
+ pmem_device_tubes: &mut Vec<Tube>,
+ fs_device_tubes: &mut Vec<Tube>,
+ #[cfg(feature = "usb")] usb_provider: HostBackendDeviceProvider,
+ map_request: Arc<Mutex<Option<ExternalMapping>>>,
+ #[cfg(feature = "gpu")] render_server_fd: Option<SafeDescriptor>,
+ vvu_proxy_device_tubes: &mut Vec<Tube>,
+ vvu_proxy_max_sibling_mem_size: u64,
+) -> DeviceResult<Vec<(Box<dyn BusDeviceObj>, Option<Minijail>)>> {
+ let mut devices: Vec<(Box<dyn BusDeviceObj>, Option<Minijail>)> = Vec::new();
+ let mut balloon_inflate_tube: Option<Tube> = None;
+ if !cfg.vfio.is_empty() {
+ let mut coiommu_attached_endpoints = Vec::new();
+
+ for vfio_dev in cfg
+ .vfio
+ .iter()
+ .filter(|dev| dev.get_type() == VfioType::Pci)
+ {
+ let vfio_path = &vfio_dev.vfio_path;
+ let (vfio_pci_device, jail) = create_vfio_device(
+ cfg,
+ vm,
+ resources,
+ control_tubes,
+ vfio_path.as_path(),
+ None,
+ vfio_dev.guest_address(),
+ iommu_attached_endpoints,
+ Some(&mut coiommu_attached_endpoints),
+ vfio_dev.iommu_dev_type(),
+ )?;
+
+ devices.push((vfio_pci_device, jail));
+ }
+
+ for vfio_dev in cfg
+ .vfio
+ .iter()
+ .filter(|dev| dev.get_type() == VfioType::Platform)
+ {
+ let vfio_path = &vfio_dev.vfio_path;
+ let (vfio_plat_dev, jail) = create_vfio_platform_device(
+ cfg,
+ vm,
+ resources,
+ control_tubes,
+ vfio_path.as_path(),
+ iommu_attached_endpoints,
+ IommuDevType::NoIommu, // Virtio IOMMU is not supported yet
+ )?;
+
+ devices.push((Box::new(vfio_plat_dev), jail));
+ }
+
+ if !coiommu_attached_endpoints.is_empty() || !iommu_attached_endpoints.is_empty() {
+ let mut buf = mem::MaybeUninit::<libc::rlimit>::zeroed();
+ let res = unsafe { libc::getrlimit(libc::RLIMIT_MEMLOCK, buf.as_mut_ptr()) };
+ if res == 0 {
+ let limit = unsafe { buf.assume_init() };
+ let rlim_new = limit
+ .rlim_cur
+ .saturating_add(vm.get_memory().memory_size() as libc::rlim_t);
+ let rlim_max = max(limit.rlim_max, rlim_new);
+ if limit.rlim_cur < rlim_new {
+ let limit_arg = libc::rlimit {
+ rlim_cur: rlim_new as libc::rlim_t,
+ rlim_max: rlim_max as libc::rlim_t,
+ };
+ let res = unsafe { libc::setrlimit(libc::RLIMIT_MEMLOCK, &limit_arg) };
+ if res != 0 {
+ bail!("Set rlimit failed");
+ }
+ }
+ } else {
+ bail!("Get rlimit failed");
+ }
+ }
+
+ if !coiommu_attached_endpoints.is_empty() {
+ let vfio_container =
+ VfioCommonSetup::vfio_get_container(IommuDevType::CoIommu, None as Option<&Path>)
+ .context("failed to get vfio container")?;
+ let (coiommu_host_tube, coiommu_device_tube) =
+ Tube::pair().context("failed to create coiommu tube")?;
+ control_tubes.push(TaggedControlTube::VmMemory(coiommu_host_tube));
+ let vcpu_count = cfg.vcpu_count.unwrap_or(1) as u64;
+ let (coiommu_tube, balloon_tube) =
+ Tube::pair().context("failed to create coiommu tube")?;
+ balloon_inflate_tube = Some(balloon_tube);
+ let dev = CoIommuDev::new(
+ vm.get_memory().clone(),
+ vfio_container,
+ coiommu_device_tube,
+ coiommu_tube,
+ coiommu_attached_endpoints,
+ vcpu_count,
+ cfg.coiommu_param.unwrap_or_default(),
+ )
+ .context("failed to create coiommu device")?;
+
+ devices.push((Box::new(dev), simple_jail(&cfg.jail_config, "coiommu")?));
+ }
+ }
+
+ let stubs = create_virtio_devices(
+ cfg,
+ vm,
+ resources,
+ exit_evt,
+ wayland_device_tube,
+ gpu_device_tube,
+ vhost_user_gpu_tubes,
+ balloon_device_tube,
+ balloon_inflate_tube,
+ init_balloon_size,
+ disk_device_tubes,
+ pmem_device_tubes,
+ map_request,
+ fs_device_tubes,
+ #[cfg(feature = "gpu")]
+ render_server_fd,
+ vvu_proxy_device_tubes,
+ vvu_proxy_max_sibling_mem_size,
+ )?;
+
+ for stub in stubs {
+ let (msi_host_tube, msi_device_tube) = Tube::pair().context("failed to create tube")?;
+ control_tubes.push(TaggedControlTube::VmIrq(msi_host_tube));
+ let dev = VirtioPciDevice::new(vm.get_memory().clone(), stub.dev, msi_device_tube)
+ .context("failed to create virtio pci dev")?;
+ let dev = Box::new(dev) as Box<dyn BusDeviceObj>;
+ devices.push((dev, stub.jail));
+ }
+
+ #[cfg(feature = "audio")]
+ for ac97_param in &cfg.ac97_parameters {
+ let dev = Ac97Dev::try_new(vm.get_memory().clone(), ac97_param.clone())
+ .context("failed to create ac97 device")?;
+ let jail = simple_jail(&cfg.jail_config, dev.minijail_policy())?;
+ devices.push((Box::new(dev), jail));
+ }
+
+ #[cfg(feature = "usb")]
+ if cfg.usb {
+ // Create xhci controller.
+ let usb_controller = Box::new(XhciController::new(vm.get_memory().clone(), usb_provider));
+ devices.push((usb_controller, simple_jail(&cfg.jail_config, "xhci")?));
+ }
+
+ for params in &cfg.stub_pci_devices {
+ // Stub devices don't need jailing since they don't do anything.
+ devices.push((Box::new(StubPciDevice::new(params)), None));
+ }
+
+ devices.push((Box::new(PvPanicPciDevice::new(panic_wrtube)), None));
+ Ok(devices)
+}
+
+fn create_file_backed_mappings(
+ cfg: &Config,
+ vm: &mut impl Vm,
+ resources: &mut SystemAllocator,
+) -> Result<()> {
+ for mapping in &cfg.file_backed_mappings {
+ let file = OpenOptions::new()
+ .read(true)
+ .write(mapping.writable)
+ .custom_flags(if mapping.sync { libc::O_SYNC } else { 0 })
+ .open(&mapping.path)
+ .context("failed to open file for file-backed mapping")?;
+ let prot = if mapping.writable {
+ Protection::read_write()
+ } else {
+ Protection::read()
+ };
+ let size = mapping
+ .size
+ .try_into()
+ .context("Invalid size for file-backed mapping")?;
+ let memory_mapping = MemoryMappingBuilder::new(size)
+ .from_file(&file)
+ .offset(mapping.offset)
+ .protection(prot)
+ .build()
+ .context("failed to map backing file for file-backed mapping")?;
+
+ match resources.mmio_allocator_any().allocate_at(
+ mapping.address,
+ mapping.size,
+ Alloc::FileBacked(mapping.address),
+ "file-backed mapping".to_owned(),
+ ) {
+ // OutOfSpace just means that this mapping is not in the MMIO regions at all, so don't
+ // consider it an error.
+ // TODO(b/222769529): Reserve this region in a global memory address space allocator once
+ // we have that so nothing else can accidentally overlap with it.
+ Ok(()) | Err(resources::Error::OutOfSpace) => {}
+ e => e.context("failed to allocate guest address for file-backed mapping")?,
+ }
+
+ vm.add_memory_region(
+ GuestAddress(mapping.address),
+ Box::new(memory_mapping),
+ !mapping.writable,
+ /* log_dirty_pages = */ false,
+ )
+ .context("failed to configure file-backed mapping")?;
+ }
+
+ Ok(())
+}
+
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+fn create_pcie_root_port(
+ host_pcie_rp: Vec<HostPcieRootPortParameters>,
+ sys_allocator: &mut SystemAllocator,
+ control_tubes: &mut Vec<TaggedControlTube>,
+ devices: &mut Vec<(Box<dyn BusDeviceObj>, Option<Minijail>)>,
+ hp_vec: &mut Vec<Arc<Mutex<dyn HotPlugBus>>>,
+ hp_endpoints_ranges: &mut Vec<RangeInclusive<u32>>,
+ // TODO(b/228627457): clippy is incorrectly warning about this Vec, which needs to be a Vec so
+ // we can push into it
+ #[allow(clippy::ptr_arg)] gpe_notify_devs: &mut Vec<(u32, Arc<Mutex<dyn GpeNotify>>)>,
+) -> Result<()> {
+ if host_pcie_rp.is_empty() {
+ // user doesn't specify host pcie root port which link to this virtual pcie rp,
+ // find the empty bus and create a total virtual pcie rp
+ let mut hp_sec_bus = 0u8;
+ // Create Pcie Root Port for non-root buses, each non-root bus device will be
+ // connected behind a virtual pcie root port.
+ for i in 1..255 {
+ if sys_allocator.pci_bus_empty(i) {
+ if hp_sec_bus == 0 {
+ hp_sec_bus = i;
+ }
+ continue;
+ }
+ let pcie_root_port = Arc::new(Mutex::new(PcieRootPort::new(i, false)));
+ let (msi_host_tube, msi_device_tube) = Tube::pair().context("failed to create tube")?;
+ control_tubes.push(TaggedControlTube::VmIrq(msi_host_tube));
+ let pci_bridge = Box::new(PciBridge::new(pcie_root_port.clone(), msi_device_tube));
+ // no ipc is used if the root port disables hotplug
+ devices.push((pci_bridge, None));
+ }
+
+ // Create Pcie Root Port for hot-plug
+ if hp_sec_bus == 0 {
+ return Err(anyhow!("no more addresses are available"));
+ }
+ let pcie_root_port = Arc::new(Mutex::new(PcieRootPort::new(hp_sec_bus, true)));
+ let (msi_host_tube, msi_device_tube) = Tube::pair().context("failed to create tube")?;
+ control_tubes.push(TaggedControlTube::VmIrq(msi_host_tube));
+ let pci_bridge = Box::new(PciBridge::new(pcie_root_port.clone(), msi_device_tube));
+
+ hp_endpoints_ranges.push(RangeInclusive::new(
+ PciAddress {
+ bus: pci_bridge.get_secondary_num(),
+ dev: 0,
+ func: 0,
+ }
+ .to_u32(),
+ PciAddress {
+ bus: pci_bridge.get_subordinate_num(),
+ dev: 32,
+ func: 8,
+ }
+ .to_u32(),
+ ));
+
+ devices.push((pci_bridge, None));
+ hp_vec.push(pcie_root_port as Arc<Mutex<dyn HotPlugBus>>);
+ } else {
+ // user specify host pcie root port which link to this virtual pcie rp,
+ // reserve the host pci BDF and create a virtual pcie RP with some attrs same as host
+ for host_pcie in host_pcie_rp.iter() {
+ let (vm_host_tube, vm_device_tube) = Tube::pair().context("failed to create tube")?;
+ let pcie_host = PcieHostRootPort::new(host_pcie.host_path.as_path(), vm_device_tube)?;
+ let bus_range = pcie_host.get_bus_range();
+ let mut slot_implemented = true;
+ for i in bus_range.secondary..=bus_range.subordinate {
+ // if this bus is occupied by one vfio-pci device, this vfio-pci device is
+ // connected to a pci bridge on host statically, then it should be connected
+ // to a virtual pci bridge in guest statically, this bridge won't have
+ // hotplug capability and won't use slot.
+ if !sys_allocator.pci_bus_empty(i) {
+ slot_implemented = false;
+ break;
+ }
+ }
+
+ let pcie_root_port = Arc::new(Mutex::new(PcieRootPort::new_from_host(
+ pcie_host,
+ slot_implemented,
+ )?));
+ control_tubes.push(TaggedControlTube::Vm(vm_host_tube));
+
+ let (msi_host_tube, msi_device_tube) = Tube::pair().context("failed to create tube")?;
+ control_tubes.push(TaggedControlTube::VmIrq(msi_host_tube));
+ let mut pci_bridge = Box::new(PciBridge::new(pcie_root_port.clone(), msi_device_tube));
+ // early reservation for host pcie root port devices.
+ let rootport_addr = pci_bridge.allocate_address(sys_allocator);
+ if rootport_addr.is_err() {
+ warn!(
+ "address reservation failed for hot pcie root port {}",
+ pci_bridge.debug_label()
+ );
+ }
+
+ // Only append the sub pci range of a hot-pluggable root port to virtio-iommu
+ if slot_implemented {
+ hp_endpoints_ranges.push(RangeInclusive::new(
+ PciAddress {
+ bus: pci_bridge.get_secondary_num(),
+ dev: 0,
+ func: 0,
+ }
+ .to_u32(),
+ PciAddress {
+ bus: pci_bridge.get_subordinate_num(),
+ dev: 32,
+ func: 8,
+ }
+ .to_u32(),
+ ));
+ }
+
+ devices.push((pci_bridge, None));
+ if slot_implemented {
+ if let Some(gpe) = host_pcie.hp_gpe {
+ gpe_notify_devs
+ .push((gpe, pcie_root_port.clone() as Arc<Mutex<dyn GpeNotify>>));
+ }
+ hp_vec.push(pcie_root_port as Arc<Mutex<dyn HotPlugBus>>);
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn setup_vm_components(cfg: &Config) -> Result<VmComponents> {
+ let initrd_image = if let Some(initrd_path) = &cfg.initrd_path {
+ Some(
+ open_file(initrd_path, OpenOptions::new().read(true))
+ .with_context(|| format!("failed to open initrd {}", initrd_path.display()))?,
+ )
+ } else {
+ None
+ };
+
+ let vm_image = match cfg.executable_path {
+ Some(Executable::Kernel(ref kernel_path)) => VmImage::Kernel(
+ open_file(kernel_path, OpenOptions::new().read(true)).with_context(|| {
+ format!("failed to open kernel image {}", kernel_path.display())
+ })?,
+ ),
+ Some(Executable::Bios(ref bios_path)) => VmImage::Bios(
+ open_file(bios_path, OpenOptions::new().read(true))
+ .with_context(|| format!("failed to open bios {}", bios_path.display()))?,
+ ),
+ _ => panic!("Did not receive a bios or kernel, should be impossible."),
+ };
+
+ let swiotlb = if let Some(size) = cfg.swiotlb {
+ Some(
+ size.checked_mul(1024 * 1024)
+ .ok_or_else(|| anyhow!("requested swiotlb size too large"))?,
+ )
+ } else {
+ match cfg.protected_vm {
+ ProtectionType::Protected | ProtectionType::ProtectedWithoutFirmware => {
+ Some(64 * 1024 * 1024)
+ }
+ ProtectionType::Unprotected => None,
+ }
+ };
+
+ Ok(VmComponents {
+ memory_size: cfg
+ .memory
+ .unwrap_or(256)
+ .checked_mul(1024 * 1024)
+ .ok_or_else(|| anyhow!("requested memory size too large"))?,
+ swiotlb,
+ vcpu_count: cfg.vcpu_count.unwrap_or(1),
+ vcpu_affinity: cfg.vcpu_affinity.clone(),
+ cpu_clusters: cfg.cpu_clusters.clone(),
+ cpu_capacity: cfg.cpu_capacity.clone(),
+ #[cfg(feature = "direct")]
+ direct_gpe: cfg.direct_gpe.clone(),
+ no_smt: cfg.no_smt,
+ hugepages: cfg.hugepages,
+ vm_image,
+ android_fstab: cfg
+ .android_fstab
+ .as_ref()
+ .map(|x| {
+ File::open(x)
+ .with_context(|| format!("failed to open android fstab file {}", x.display()))
+ })
+ .map_or(Ok(None), |v| v.map(Some))?,
+ pstore: cfg.pstore.clone(),
+ initrd_image,
+ extra_kernel_params: cfg.params.clone(),
+ acpi_sdts: cfg
+ .acpi_tables
+ .iter()
+ .map(|path| {
+ SDT::from_file(path)
+ .with_context(|| format!("failed to open ACPI file {}", path.display()))
+ })
+ .collect::<Result<Vec<SDT>>>()?,
+ rt_cpus: cfg.rt_cpus.clone(),
+ delay_rt: cfg.delay_rt,
+ protected_vm: cfg.protected_vm,
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+ gdb: None,
+ dmi_path: cfg.dmi_path.clone(),
+ no_legacy: cfg.no_legacy,
+ host_cpu_topology: cfg.host_cpu_topology,
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ force_s2idle: cfg.force_s2idle,
+ })
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub enum ExitState {
+ Reset,
+ Stop,
+ Crash,
+ GuestPanic,
+}
+
+// Remove ranges in `guest_mem_layout` that overlap with ranges in `file_backed_mappings`.
+// Returns the updated guest memory layout.
+fn punch_holes_in_guest_mem_layout_for_mappings(
+ guest_mem_layout: Vec<(GuestAddress, u64)>,
+ file_backed_mappings: &[FileBackedMappingParameters],
+) -> Vec<(GuestAddress, u64)> {
+ // Create a set containing (start, end) pairs with exclusive end (end = start + size; the byte
+ // at end is not included in the range).
+ let mut layout_set = BTreeSet::new();
+ for (addr, size) in &guest_mem_layout {
+ layout_set.insert((addr.offset(), addr.offset() + size));
+ }
+
+ for mapping in file_backed_mappings {
+ let mapping_start = mapping.address;
+ let mapping_end = mapping_start + mapping.size;
+
+ // Repeatedly split overlapping guest memory regions until no overlaps remain.
+ while let Some((range_start, range_end)) = layout_set
+ .iter()
+ .find(|&&(range_start, range_end)| {
+ mapping_start < range_end && mapping_end > range_start
+ })
+ .cloned()
+ {
+ layout_set.remove(&(range_start, range_end));
+
+ if range_start < mapping_start {
+ layout_set.insert((range_start, mapping_start));
+ }
+ if range_end > mapping_end {
+ layout_set.insert((mapping_end, range_end));
+ }
+ }
+ }
+
+ // Build the final guest memory layout from the modified layout_set.
+ layout_set
+ .iter()
+ .map(|(start, end)| (GuestAddress(*start), end - start))
+ .collect()
+}
+
+pub fn run_config(cfg: Config) -> Result<ExitState> {
+ let components = setup_vm_components(&cfg)?;
+
+ let guest_mem_layout =
+ Arch::guest_memory_layout(&components).context("failed to create guest memory layout")?;
+
+ let guest_mem_layout =
+ punch_holes_in_guest_mem_layout_for_mappings(guest_mem_layout, &cfg.file_backed_mappings);
+
+ let guest_mem = GuestMemory::new(&guest_mem_layout).context("failed to create guest memory")?;
+ let mut mem_policy = MemoryPolicy::empty();
+ if components.hugepages {
+ mem_policy |= MemoryPolicy::USE_HUGEPAGES;
+ }
+ guest_mem.set_memory_policy(mem_policy);
+ let kvm = Kvm::new_with_path(&cfg.kvm_device_path).context("failed to create kvm")?;
+ let vm = KvmVm::new(&kvm, guest_mem, components.protected_vm).context("failed to create vm")?;
+
+ if !cfg.userspace_msr.is_empty() {
+ vm.enable_userspace_msr()
+ .context("failed to enable userspace MSR handling, do you have kernel 5.10 or later")?;
+ }
+
+ // Check that the VM was actually created in protected mode as expected.
+ if cfg.protected_vm != ProtectionType::Unprotected && !vm.check_capability(VmCap::Protected) {
+ bail!("Failed to create protected VM");
+ }
+ let vm_clone = vm.try_clone().context("failed to clone vm")?;
+
+ enum KvmIrqChip {
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ Split(KvmSplitIrqChip),
+ Kernel(KvmKernelIrqChip),
+ }
+
+ impl KvmIrqChip {
+ fn as_mut(&mut self) -> &mut dyn IrqChipArch {
+ match self {
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ KvmIrqChip::Split(i) => i,
+ KvmIrqChip::Kernel(i) => i,
+ }
+ }
+ }
+
+ let ioapic_host_tube;
+ let mut irq_chip = if cfg.split_irqchip {
+ #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+ unimplemented!("KVM split irqchip mode only supported on x86 processors");
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ let (host_tube, ioapic_device_tube) = Tube::pair().context("failed to create tube")?;
+ ioapic_host_tube = Some(host_tube);
+ KvmIrqChip::Split(
+ KvmSplitIrqChip::new(
+ vm_clone,
+ components.vcpu_count,
+ ioapic_device_tube,
+ Some(120),
+ )
+ .context("failed to create IRQ chip")?,
+ )
+ }
+ } else {
+ ioapic_host_tube = None;
+ KvmIrqChip::Kernel(
+ KvmKernelIrqChip::new(vm_clone, components.vcpu_count)
+ .context("failed to create IRQ chip")?,
+ )
+ };
+
+ run_vm::<KvmVcpu, KvmVm>(cfg, components, vm, irq_chip.as_mut(), ioapic_host_tube)
+}
+
+fn run_vm<Vcpu, V>(
+ cfg: Config,
+ #[allow(unused_mut)] mut components: VmComponents,
+ mut vm: V,
+ irq_chip: &mut dyn IrqChipArch,
+ ioapic_host_tube: Option<Tube>,
+) -> Result<ExitState>
+where
+ Vcpu: VcpuArch + 'static,
+ V: VmArch + 'static,
+{
+ if cfg.jail_config.is_some() {
+ // Printing something to the syslog before entering minijail so that libc's syslogger has a
+ // chance to open files necessary for its operation, like `/etc/localtime`. After jailing,
+ // access to those files will not be possible.
+ info!("crosvm entering multiprocess mode");
+ }
+
+ #[cfg(feature = "usb")]
+ let (usb_control_tube, usb_provider) =
+ HostBackendDeviceProvider::new().context("failed to create usb provider")?;
+
+ // Masking signals is inherently dangerous, since this can persist across clones/execs. Do this
+ // before any jailed devices have been spawned, so that we can catch any of them that fail very
+ // quickly.
+ let sigchld_fd = SignalFd::new(libc::SIGCHLD).context("failed to create signalfd")?;
+
+ let control_server_socket = match &cfg.socket_path {
+ Some(path) => Some(UnlinkUnixSeqpacketListener(
+ UnixSeqpacketListener::bind(path).context("failed to create control server")?,
+ )),
+ None => None,
+ };
+
+ let mut control_tubes = Vec::new();
+
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+ if let Some(port) = cfg.gdb {
+ // GDB needs a control socket to interrupt vcpus.
+ let (gdb_host_tube, gdb_control_tube) = Tube::pair().context("failed to create tube")?;
+ control_tubes.push(TaggedControlTube::Vm(gdb_host_tube));
+ components.gdb = Some((port, gdb_control_tube));
+ }
+
+ for wl_cfg in &cfg.vhost_user_wl {
+ let wayland_host_tube = UnixSeqpacket::connect(&wl_cfg.vm_tube)
+ .map(Tube::new)
+ .context("failed to connect to wayland tube")?;
+ control_tubes.push(TaggedControlTube::VmMemory(wayland_host_tube));
+ }
+
+ let mut vhost_user_gpu_tubes = Vec::with_capacity(cfg.vhost_user_gpu.len());
+ for _ in 0..cfg.vhost_user_gpu.len() {
+ let (host_control_tube, device_control_tube) =
+ Tube::pair().context("failed to create tube")?;
+ let (host_gpu_tube, device_gpu_tube) = Tube::pair().context("failed to create tube")?;
+ vhost_user_gpu_tubes.push((host_gpu_tube, device_gpu_tube, device_control_tube));
+ control_tubes.push(TaggedControlTube::VmMemory(host_control_tube));
+ }
+
+ let (wayland_host_tube, wayland_device_tube) = Tube::pair().context("failed to create tube")?;
+ control_tubes.push(TaggedControlTube::VmMemory(wayland_host_tube));
+
+ let (balloon_host_tube, balloon_device_tube) = if cfg.balloon {
+ if let Some(ref path) = cfg.balloon_control {
+ (
+ None,
+ Some(Tube::new(
+ UnixSeqpacket::connect(path).context("failed to create balloon control")?,
+ )),
+ )
+ } else {
+ // Balloon gets a special socket so balloon requests can be forwarded
+ // from the main process.
+ let (host, device) = Tube::pair().context("failed to create tube")?;
+ // Set recv timeout to avoid deadlock on sending BalloonControlCommand
+ // before the guest is ready.
+ host.set_recv_timeout(Some(Duration::from_millis(100)))
+ .context("failed to set timeout")?;
+ (Some(host), Some(device))
+ }
+ } else {
+ (None, None)
+ };
+
+ // Create one control socket per disk.
+ let mut disk_device_tubes = Vec::new();
+ let mut disk_host_tubes = Vec::new();
+ let disk_count = cfg.disks.len();
+ for _ in 0..disk_count {
+ let (disk_host_tub, disk_device_tube) = Tube::pair().context("failed to create tube")?;
+ disk_host_tubes.push(disk_host_tub);
+ disk_device_tubes.push(disk_device_tube);
+ }
+
+ let mut pmem_device_tubes = Vec::new();
+ let pmem_count = cfg.pmem_devices.len();
+ for _ in 0..pmem_count {
+ let (pmem_host_tube, pmem_device_tube) = Tube::pair().context("failed to create tube")?;
+ pmem_device_tubes.push(pmem_device_tube);
+ control_tubes.push(TaggedControlTube::VmMsync(pmem_host_tube));
+ }
+
+ let (gpu_host_tube, gpu_device_tube) = Tube::pair().context("failed to create tube")?;
+ control_tubes.push(TaggedControlTube::VmMemory(gpu_host_tube));
+
+ if let Some(ioapic_host_tube) = ioapic_host_tube {
+ control_tubes.push(TaggedControlTube::VmIrq(ioapic_host_tube));
+ }
+
+ let battery = if cfg.battery_type.is_some() {
+ #[cfg_attr(not(feature = "power-monitor-powerd"), allow(clippy::manual_map))]
+ let jail = match simple_jail(&cfg.jail_config, "battery")? {
+ #[cfg_attr(not(feature = "power-monitor-powerd"), allow(unused_mut))]
+ Some(mut jail) => {
+ // Setup a bind mount to the system D-Bus socket if the powerd monitor is used.
+ #[cfg(feature = "power-monitor-powerd")]
+ {
+ add_current_user_to_jail(&mut jail)?;
+
+ // Create a tmpfs in the device's root directory so that we can bind mount files.
+ jail.mount_with_data(
+ Path::new("none"),
+ Path::new("/"),
+ "tmpfs",
+ (libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC) as usize,
+ "size=67108864",
+ )?;
+
+ let system_bus_socket_path = Path::new("/run/dbus/system_bus_socket");
+ jail.mount_bind(system_bus_socket_path, system_bus_socket_path, true)?;
+ }
+ Some(jail)
+ }
+ None => None,
+ };
+ (&cfg.battery_type, jail)
+ } else {
+ (&cfg.battery_type, None)
+ };
+
+ let map_request: Arc<Mutex<Option<ExternalMapping>>> = Arc::new(Mutex::new(None));
+
+ let fs_count = cfg
+ .shared_dirs
+ .iter()
+ .filter(|sd| sd.kind == SharedDirKind::FS)
+ .count();
+ let mut fs_device_tubes = Vec::with_capacity(fs_count);
+ for _ in 0..fs_count {
+ let (fs_host_tube, fs_device_tube) = Tube::pair().context("failed to create tube")?;
+ control_tubes.push(TaggedControlTube::Fs(fs_host_tube));
+ fs_device_tubes.push(fs_device_tube);
+ }
+
+ let mut vvu_proxy_device_tubes = Vec::new();
+ for _ in 0..cfg.vvu_proxy.len() {
+ let (vvu_proxy_host_tube, vvu_proxy_device_tube) =
+ Tube::pair().context("failed to create VVU proxy tube")?;
+ control_tubes.push(TaggedControlTube::VmMemory(vvu_proxy_host_tube));
+ vvu_proxy_device_tubes.push(vvu_proxy_device_tube);
+ }
+
+ let exit_evt = Event::new().context("failed to create event")?;
+ let reset_evt = Event::new().context("failed to create event")?;
+ let crash_evt = Event::new().context("failed to create event")?;
+ let (panic_rdtube, panic_wrtube) = Tube::pair().context("failed to create tube")?;
+
+ let pstore_size = components.pstore.as_ref().map(|pstore| pstore.size as u64);
+ let mut sys_allocator = SystemAllocator::new(
+ Arch::get_system_allocator_config(&vm),
+ pstore_size,
+ &cfg.mmio_address_ranges,
+ )
+ .context("failed to create system allocator")?;
+
+ let ramoops_region = match &components.pstore {
+ Some(pstore) => Some(
+ arch::pstore::create_memory_region(
+ &mut vm,
+ sys_allocator.reserved_region().unwrap(),
+ pstore,
+ )
+ .context("failed to allocate pstore region")?,
+ ),
+ None => None,
+ };
+
+ create_file_backed_mappings(&cfg, &mut vm, &mut sys_allocator)?;
+
+ #[cfg(feature = "gpu")]
+ // Hold on to the render server jail so it keeps running until we exit run_vm()
+ let (_render_server_jail, render_server_fd) =
+ if let Some(parameters) = &cfg.gpu_render_server_parameters {
+ let (jail, fd) = start_gpu_render_server(&cfg, parameters)?;
+ (Some(ScopedMinijail(jail)), Some(fd))
+ } else {
+ (None, None)
+ };
+
+ let init_balloon_size = components
+ .memory_size
+ .checked_sub(cfg.init_memory.map_or(components.memory_size, |m| {
+ m.checked_mul(1024 * 1024).unwrap_or(u64::MAX)
+ }))
+ .context("failed to calculate init balloon size")?;
+
+ #[cfg(feature = "direct")]
+ let mut irqs = Vec::new();
+
+ #[cfg(feature = "direct")]
+ for irq in &cfg.direct_level_irq {
+ if !sys_allocator.reserve_irq(*irq) {
+ warn!("irq {} already reserved.", irq);
+ }
+ let irq_evt = devices::IrqLevelEvent::new().context("failed to create event")?;
+ irq_chip.register_level_irq_event(*irq, &irq_evt).unwrap();
+ let direct_irq = devices::DirectIrq::new_level(&irq_evt)
+ .context("failed to enable interrupt forwarding")?;
+ direct_irq
+ .irq_enable(*irq)
+ .context("failed to enable interrupt forwarding")?;
+
+ if cfg.direct_wake_irq.contains(&irq) {
+ direct_irq
+ .irq_wake_enable(*irq)
+ .context("failed to enable interrupt wake")?;
+ }
+
+ irqs.push(direct_irq);
+ }
+
+ #[cfg(feature = "direct")]
+ for irq in &cfg.direct_edge_irq {
+ if !sys_allocator.reserve_irq(*irq) {
+ warn!("irq {} already reserved.", irq);
+ }
+ let irq_evt = devices::IrqEdgeEvent::new().context("failed to create event")?;
+ irq_chip.register_edge_irq_event(*irq, &irq_evt).unwrap();
+ let direct_irq = devices::DirectIrq::new_edge(&irq_evt)
+ .context("failed to enable interrupt forwarding")?;
+ direct_irq
+ .irq_enable(*irq)
+ .context("failed to enable interrupt forwarding")?;
+
+ if cfg.direct_wake_irq.contains(&irq) {
+ direct_irq
+ .irq_wake_enable(*irq)
+ .context("failed to enable interrupt wake")?;
+ }
+
+ irqs.push(direct_irq);
+ }
+
+ let mut iommu_attached_endpoints: BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>> =
+ BTreeMap::new();
+ let mut devices = create_devices(
+ &cfg,
+ &mut vm,
+ &mut sys_allocator,
+ &exit_evt,
+ panic_wrtube,
+ &mut iommu_attached_endpoints,
+ &mut control_tubes,
+ wayland_device_tube,
+ gpu_device_tube,
+ vhost_user_gpu_tubes,
+ balloon_device_tube,
+ init_balloon_size,
+ &mut disk_device_tubes,
+ &mut pmem_device_tubes,
+ &mut fs_device_tubes,
+ #[cfg(feature = "usb")]
+ usb_provider,
+ Arc::clone(&map_request),
+ #[cfg(feature = "gpu")]
+ render_server_fd,
+ &mut vvu_proxy_device_tubes,
+ components.memory_size,
+ )?;
+
+ let mut hp_endpoints_ranges: Vec<RangeInclusive<u32>> = Vec::new();
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ let mut hotplug_buses: Vec<Arc<Mutex<dyn HotPlugBus>>> = Vec::new();
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ let mut gpe_notify_devs: Vec<(u32, Arc<Mutex<dyn GpeNotify>>)> = Vec::new();
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ #[cfg(feature = "direct")]
+ let rp_host = cfg.pcie_rp.clone();
+ #[cfg(not(feature = "direct"))]
+ let rp_host: Vec<HostPcieRootPortParameters> = Vec::new();
+
+ // Create Pcie Root Port
+ create_pcie_root_port(
+ rp_host,
+ &mut sys_allocator,
+ &mut control_tubes,
+ &mut devices,
+ &mut hotplug_buses,
+ &mut hp_endpoints_ranges,
+ &mut gpe_notify_devs,
+ )?;
+ }
+
+ let (translate_response_senders, request_rx) = setup_virtio_access_platform(
+ &mut sys_allocator,
+ &mut iommu_attached_endpoints,
+ &mut devices,
+ )?;
+
+ let iommu_host_tube = if !iommu_attached_endpoints.is_empty() || cfg.virtio_iommu {
+ let (iommu_host_tube, iommu_device_tube) = Tube::pair().context("failed to create tube")?;
+ let iommu_dev = create_iommu_device(
+ &cfg,
+ (1u64 << vm.get_guest_phys_addr_bits()) - 1,
+ iommu_attached_endpoints,
+ hp_endpoints_ranges,
+ translate_response_senders,
+ request_rx,
+ iommu_device_tube,
+ )?;
+
+ let (msi_host_tube, msi_device_tube) = Tube::pair().context("failed to create tube")?;
+ control_tubes.push(TaggedControlTube::VmIrq(msi_host_tube));
+ let mut dev = VirtioPciDevice::new(vm.get_memory().clone(), iommu_dev.dev, msi_device_tube)
+ .context("failed to create virtio pci dev")?;
+ // early reservation for viommu.
+ dev.allocate_address(&mut sys_allocator)
+ .context("failed to allocate resources early for virtio pci dev")?;
+ let dev = Box::new(dev);
+ devices.push((dev, iommu_dev.jail));
+ Some(iommu_host_tube)
+ } else {
+ None
+ };
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ for device in devices
+ .iter_mut()
+ .filter_map(|(dev, _)| dev.as_pci_device_mut())
+ {
+ let sdts = device
+ .generate_acpi(components.acpi_sdts)
+ .or_else(|| {
+ error!("ACPI table generation error");
+ None
+ })
+ .ok_or_else(|| anyhow!("failed to generate ACPI table"))?;
+ components.acpi_sdts = sdts;
+ }
+
+ // KVM_CREATE_VCPU uses apic id for x86 and uses cpu id for others.
+ let mut kvm_vcpu_ids = Vec::new();
+
+ #[cfg_attr(not(feature = "direct"), allow(unused_mut))]
+ let mut linux = Arch::build_vm::<V, Vcpu>(
+ components,
+ &exit_evt,
+ &reset_evt,
+ &mut sys_allocator,
+ &cfg.serial_parameters,
+ simple_jail(&cfg.jail_config, "serial")?,
+ battery,
+ vm,
+ ramoops_region,
+ devices,
+ irq_chip,
+ &mut kvm_vcpu_ids,
+ )
+ .context("the architecture failed to build the vm")?;
+
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ {
+ for hotplug_bus in hotplug_buses.iter() {
+ linux.hotplug_bus.push(hotplug_bus.clone());
+ }
+
+ if let Some(pm) = &linux.pm {
+ while let Some((gpe, notify_dev)) = gpe_notify_devs.pop() {
+ pm.lock().register_gpe_notify_dev(gpe, notify_dev);
+ }
+ }
+ }
+
+ #[cfg(feature = "direct")]
+ if let Some(pmio) = &cfg.direct_pmio {
+ let direct_io = Arc::new(
+ devices::DirectIo::new(&pmio.path, false).context("failed to open direct io device")?,
+ );
+ for range in pmio.ranges.iter() {
+ linux
+ .io_bus
+ .insert_sync(direct_io.clone(), range.base, range.len)
+ .unwrap();
+ }
+ };
+
+ #[cfg(feature = "direct")]
+ if let Some(mmio) = &cfg.direct_mmio {
+ let direct_mmio = Arc::new(
+ devices::DirectMmio::new(&mmio.path, false, &mmio.ranges)
+ .context("failed to open direct mmio device")?,
+ );
+
+ for range in mmio.ranges.iter() {
+ linux
+ .mmio_bus
+ .insert_sync(direct_mmio.clone(), range.base, range.len)
+ .unwrap();
+ }
+ };
+
+ let gralloc = RutabagaGralloc::new().context("failed to create gralloc")?;
+ run_control(
+ linux,
+ sys_allocator,
+ cfg,
+ control_server_socket,
+ control_tubes,
+ balloon_host_tube,
+ &disk_host_tubes,
+ #[cfg(feature = "usb")]
+ usb_control_tube,
+ exit_evt,
+ reset_evt,
+ crash_evt,
+ panic_rdtube,
+ sigchld_fd,
+ Arc::clone(&map_request),
+ gralloc,
+ kvm_vcpu_ids,
+ iommu_host_tube,
+ )
+}
+
+fn get_hp_bus<V: VmArch, Vcpu: VcpuArch>(
+ linux: &RunnableLinuxVm<V, Vcpu>,
+ host_addr: PciAddress,
+) -> Result<(Arc<Mutex<dyn HotPlugBus>>, u8)> {
+ for hp_bus in linux.hotplug_bus.iter() {
+ if let Some(number) = hp_bus.lock().is_match(host_addr) {
+ return Ok((hp_bus.clone(), number));
+ }
+ }
+ Err(anyhow!("Failed to find a suitable hotplug bus"))
+}
+
+fn add_vfio_device<V: VmArch, Vcpu: VcpuArch>(
+ linux: &mut RunnableLinuxVm<V, Vcpu>,
+ sys_allocator: &mut SystemAllocator,
+ cfg: &Config,
+ control_tubes: &mut Vec<TaggedControlTube>,
+ iommu_host_tube: &Option<Tube>,
+ vfio_path: &Path,
+) -> Result<()> {
+ let host_os_str = vfio_path
+ .file_name()
+ .ok_or_else(|| anyhow!("failed to parse or find vfio path"))?;
+ let host_str = host_os_str
+ .to_str()
+ .ok_or_else(|| anyhow!("failed to parse or find vfio path"))?;
+ let host_addr = PciAddress::from_str(host_str).context("failed to parse vfio pci address")?;
+
+ let (hp_bus, bus_num) = get_hp_bus(linux, host_addr)?;
+
+ let mut endpoints: BTreeMap<u32, Arc<Mutex<Box<dyn MemoryMapperTrait>>>> = BTreeMap::new();
+ let (vfio_pci_device, jail) = create_vfio_device(
+ cfg,
+ &linux.vm,
+ sys_allocator,
+ control_tubes,
+ vfio_path,
+ Some(bus_num),
+ None,
+ &mut endpoints,
+ None,
+ if iommu_host_tube.is_some() {
+ IommuDevType::VirtioIommu
+ } else {
+ IommuDevType::NoIommu
+ },
+ )?;
+
+ let pci_address = Arch::register_pci_device(linux, vfio_pci_device, jail, sys_allocator)
+ .context("Failed to configure pci hotplug device")?;
+
+ if let Some(iommu_host_tube) = iommu_host_tube {
+ let &endpoint_addr = endpoints.iter().next().unwrap().0;
+ let mapper = endpoints.remove(&endpoint_addr).unwrap();
+ if let Some(vfio_wrapper) = mapper.lock().as_vfio_wrapper() {
+ let vfio_container = vfio_wrapper.as_vfio_container();
+ let descriptor = vfio_container.lock().into_raw_descriptor()?;
+ let request = VirtioIOMMURequest::VfioCommand(VirtioIOMMUVfioCommand::VfioDeviceAdd {
+ endpoint_addr,
+ container: {
+ // Safe because the descriptor is uniquely owned by `descriptor`.
+ unsafe { File::from_raw_descriptor(descriptor) }
+ },
+ });
+
+ match virtio_iommu_request(iommu_host_tube, &request)
+ .map_err(|_| VirtioIOMMUVfioError::SocketFailed)?
+ {
+ VirtioIOMMUResponse::VfioResponse(VirtioIOMMUVfioResult::Ok) => (),
+ resp => bail!("Unexpected message response: {:?}", resp),
+ }
+ };
+ }
+
+ let host_key = HostHotPlugKey::Vfio { host_addr };
+ let mut hp_bus = hp_bus.lock();
+ hp_bus.add_hotplug_device(host_key, pci_address);
+ hp_bus.hot_plug(pci_address);
+ Ok(())
+}
+
+fn remove_vfio_device<V: VmArch, Vcpu: VcpuArch>(
+ linux: &RunnableLinuxVm<V, Vcpu>,
+ sys_allocator: &mut SystemAllocator,
+ iommu_host_tube: &Option<Tube>,
+ vfio_path: &Path,
+) -> Result<()> {
+ let host_os_str = vfio_path
+ .file_name()
+ .ok_or_else(|| anyhow!("failed to parse or find vfio path"))?;
+ let host_str = host_os_str
+ .to_str()
+ .ok_or_else(|| anyhow!("failed to parse or find vfio path"))?;
+ let host_addr = PciAddress::from_str(host_str).context("failed to parse vfio pci address")?;
+ let host_key = HostHotPlugKey::Vfio { host_addr };
+ for hp_bus in linux.hotplug_bus.iter() {
+ let mut hp_bus_lock = hp_bus.lock();
+ if let Some(pci_addr) = hp_bus_lock.get_hotplug_device(host_key) {
+ if let Some(iommu_host_tube) = iommu_host_tube {
+ let request =
+ VirtioIOMMURequest::VfioCommand(VirtioIOMMUVfioCommand::VfioDeviceDel {
+ endpoint_addr: pci_addr.to_u32(),
+ });
+ match virtio_iommu_request(iommu_host_tube, &request)
+ .map_err(|_| VirtioIOMMUVfioError::SocketFailed)?
+ {
+ VirtioIOMMUResponse::VfioResponse(VirtioIOMMUVfioResult::Ok) => (),
+ resp => bail!("Unexpected message response: {:?}", resp),
+ }
+ }
+
+ hp_bus_lock.hot_unplug(pci_addr);
+ sys_allocator.release_pci(pci_addr.bus, pci_addr.dev, pci_addr.func);
+ return Ok(());
+ }
+ }
+
+ Err(anyhow!("HotPlugBus hasn't been implemented"))
+}
+
+fn handle_vfio_command<V: VmArch, Vcpu: VcpuArch>(
+ linux: &mut RunnableLinuxVm<V, Vcpu>,
+ sys_allocator: &mut SystemAllocator,
+ cfg: &Config,
+ add_tubes: &mut Vec<TaggedControlTube>,
+ iommu_host_tube: &Option<Tube>,
+ vfio_path: &Path,
+ add: bool,
+) -> VmResponse {
+ let ret = if add {
+ add_vfio_device(
+ linux,
+ sys_allocator,
+ cfg,
+ add_tubes,
+ iommu_host_tube,
+ vfio_path,
+ )
+ } else {
+ remove_vfio_device(linux, sys_allocator, iommu_host_tube, vfio_path)
+ };
+
+ match ret {
+ Ok(()) => VmResponse::Ok,
+ Err(e) => {
+ error!("hanlde_vfio_command failure: {}", e);
+ add_tubes.clear();
+ VmResponse::Err(base::Error::new(libc::EINVAL))
+ }
+ }
+}
+
+fn run_control<V: VmArch + 'static, Vcpu: VcpuArch + 'static>(
+ mut linux: RunnableLinuxVm<V, Vcpu>,
+ mut sys_allocator: SystemAllocator,
+ cfg: Config,
+ control_server_socket: Option<UnlinkUnixSeqpacketListener>,
+ mut control_tubes: Vec<TaggedControlTube>,
+ balloon_host_tube: Option<Tube>,
+ disk_host_tubes: &[Tube],
+ #[cfg(feature = "usb")] usb_control_tube: Tube,
+ exit_evt: Event,
+ reset_evt: Event,
+ crash_evt: Event,
+ panic_rdtube: Tube,
+ sigchld_fd: SignalFd,
+ map_request: Arc<Mutex<Option<ExternalMapping>>>,
+ mut gralloc: RutabagaGralloc,
+ kvm_vcpu_ids: Vec<usize>,
+ iommu_host_tube: Option<Tube>,
+) -> Result<ExitState> {
+ #[derive(PollToken)]
+ enum Token {
+ Exit,
+ Reset,
+ Crash,
+ Panic,
+ Suspend,
+ ChildSignal,
+ IrqFd { index: IrqEventIndex },
+ VmControlServer,
+ VmControl { index: usize },
+ DelayedIrqFd,
+ }
+
+ stdin()
+ .set_raw_mode()
+ .expect("failed to set terminal raw mode");
+
+ let wait_ctx = WaitContext::build_with(&[
+ (&exit_evt, Token::Exit),
+ (&reset_evt, Token::Reset),
+ (&crash_evt, Token::Crash),
+ (&panic_rdtube, Token::Panic),
+ (&linux.suspend_evt, Token::Suspend),
+ (&sigchld_fd, Token::ChildSignal),
+ ])
+ .context("failed to add descriptor to wait context")?;
+
+ if let Some(socket_server) = &control_server_socket {
+ wait_ctx
+ .add(socket_server, Token::VmControlServer)
+ .context("failed to add descriptor to wait context")?;
+ }
+ for (index, socket) in control_tubes.iter().enumerate() {
+ wait_ctx
+ .add(socket.as_ref(), Token::VmControl { index })
+ .context("failed to add descriptor to wait context")?;
+ }
+
+ let events = linux
+ .irq_chip
+ .irq_event_tokens()
+ .context("failed to add descriptor to wait context")?;
+
+ for (index, _gsi, evt) in events {
+ wait_ctx
+ .add(&evt, Token::IrqFd { index })
+ .context("failed to add descriptor to wait context")?;
+ }
+
+ if let Some(delayed_ioapic_irq_trigger) = linux.irq_chip.irq_delayed_event_token()? {
+ wait_ctx
+ .add(&delayed_ioapic_irq_trigger, Token::DelayedIrqFd)
+ .context("failed to add descriptor to wait context")?;
+ }
+
+ if cfg.jail_config.is_some() {
+ // Before starting VCPUs, in case we started with some capabilities, drop them all.
+ drop_capabilities().context("failed to drop process capabilities")?;
+ }
+
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+ // Create a channel for GDB thread.
+ let (to_gdb_channel, from_vcpu_channel) = if linux.gdb.is_some() {
+ let (s, r) = mpsc::channel();
+ (Some(s), Some(r))
+ } else {
+ (None, None)
+ };
+
+ let mut vcpu_handles = Vec::with_capacity(linux.vcpu_count);
+ let vcpu_thread_barrier = Arc::new(Barrier::new(linux.vcpu_count + 1));
+ let use_hypervisor_signals = !linux
+ .vm
+ .get_hypervisor()
+ .check_capability(HypervisorCap::ImmediateExit);
+ vcpu::setup_vcpu_signal_handler::<Vcpu>(use_hypervisor_signals)?;
+
+ let vcpus: Vec<Option<_>> = match linux.vcpus.take() {
+ Some(vec) => vec.into_iter().map(Some).collect(),
+ None => iter::repeat_with(|| None).take(linux.vcpu_count).collect(),
+ };
+ // Enable core scheduling before creating vCPUs so that the cookie will be
+ // shared by all vCPU threads.
+ // TODO(b/199312402): Avoid enabling core scheduling for the crosvm process
+ // itself for even better performance. Only vCPUs need the feature.
+ if cfg.per_vm_core_scheduling {
+ if let Err(e) = enable_core_scheduling() {
+ error!("Failed to enable core scheduling: {}", e);
+ }
+ }
+ let vcpu_cgroup_tasks_file = match &cfg.vcpu_cgroup_path {
+ None => None,
+ Some(cgroup_path) => {
+ // Move main process to cgroup_path
+ let mut f = File::create(&cgroup_path.join("tasks"))?;
+ f.write_all(process::id().to_string().as_bytes())?;
+ Some(f)
+ }
+ };
+
+ #[cfg(target_os = "android")]
+ android::set_process_profiles(&cfg.task_profiles)?;
+
+ for (cpu_id, vcpu) in vcpus.into_iter().enumerate() {
+ let (to_vcpu_channel, from_main_channel) = mpsc::channel();
+ let vcpu_affinity = match linux.vcpu_affinity.clone() {
+ Some(VcpuAffinity::Global(v)) => v,
+ Some(VcpuAffinity::PerVcpu(mut m)) => m.remove(&cpu_id).unwrap_or_default(),
+ None => Default::default(),
+ };
+ let handle = vcpu::run_vcpu(
+ cpu_id,
+ kvm_vcpu_ids[cpu_id],
+ vcpu,
+ linux.vm.try_clone().context("failed to clone vm")?,
+ linux
+ .irq_chip
+ .try_box_clone()
+ .context("failed to clone irqchip")?,
+ linux.vcpu_count,
+ linux.rt_cpus.contains(&cpu_id),
+ vcpu_affinity,
+ linux.delay_rt,
+ linux.no_smt,
+ vcpu_thread_barrier.clone(),
+ linux.has_bios,
+ (*linux.io_bus).clone(),
+ (*linux.mmio_bus).clone(),
+ exit_evt.try_clone().context("failed to clone event")?,
+ reset_evt.try_clone().context("failed to clone event")?,
+ crash_evt.try_clone().context("failed to clone event")?,
+ linux.vm.check_capability(VmCap::PvClockSuspend),
+ from_main_channel,
+ use_hypervisor_signals,
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+ to_gdb_channel.clone(),
+ cfg.per_vm_core_scheduling,
+ cfg.host_cpu_topology,
+ cfg.privileged_vm,
+ match vcpu_cgroup_tasks_file {
+ None => None,
+ Some(ref f) => Some(
+ f.try_clone()
+ .context("failed to clone vcpu cgroup tasks file")?,
+ ),
+ },
+ cfg.userspace_msr.clone(),
+ )?;
+ vcpu_handles.push((handle, to_vcpu_channel));
+ }
+
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+ // Spawn GDB thread.
+ if let Some((gdb_port_num, gdb_control_tube)) = linux.gdb.take() {
+ let to_vcpu_channels = vcpu_handles
+ .iter()
+ .map(|(_handle, channel)| channel.clone())
+ .collect();
+ let target = GdbStub::new(
+ gdb_control_tube,
+ to_vcpu_channels,
+ from_vcpu_channel.unwrap(), // Must succeed to unwrap()
+ );
+ thread::Builder::new()
+ .name("gdb".to_owned())
+ .spawn(move || gdb_thread(target, gdb_port_num))
+ .context("failed to spawn GDB thread")?;
+ };
+
+ vcpu_thread_barrier.wait();
+
+ let mut exit_state = ExitState::Stop;
+ let mut balloon_stats_id: u64 = 0;
+
+ 'wait: loop {
+ let events = {
+ match wait_ctx.wait() {
+ Ok(v) => v,
+ Err(e) => {
+ error!("failed to poll: {}", e);
+ break;
+ }
+ }
+ };
+
+ let mut vm_control_indices_to_remove = Vec::new();
+ for event in events.iter().filter(|e| e.is_readable) {
+ match event.token {
+ Token::Exit => {
+ info!("vcpu requested shutdown");
+ break 'wait;
+ }
+ Token::Reset => {
+ info!("vcpu requested reset");
+ exit_state = ExitState::Reset;
+ break 'wait;
+ }
+ Token::Crash => {
+ info!("vcpu crashed");
+ exit_state = ExitState::Crash;
+ break 'wait;
+ }
+ Token::Panic => {
+ let mut break_to_wait: bool = true;
+ match panic_rdtube.recv::<u8>() {
+ Ok(panic_code) => {
+ let panic_code = PvPanicCode::from_u8(panic_code);
+ info!("Guest reported panic [Code: {}]", panic_code);
+ if panic_code == PvPanicCode::CrashLoaded {
+ // VM is booting to crash kernel.
+ break_to_wait = false;
+ }
+ }
+ Err(e) => {
+ warn!("failed to recv panic event: {} ", e);
+ }
+ }
+ if break_to_wait {
+ exit_state = ExitState::GuestPanic;
+ break 'wait;
+ }
+ }
+ Token::Suspend => {
+ info!("VM requested suspend");
+ linux.suspend_evt.read().unwrap();
+ vcpu::kick_all_vcpus(
+ &vcpu_handles,
+ linux.irq_chip.as_irq_chip(),
+ VcpuControl::RunState(VmRunMode::Suspending),
+ );
+ }
+ Token::ChildSignal => {
+ // Print all available siginfo structs, then exit the loop.
+ while let Some(siginfo) =
+ sigchld_fd.read().context("failed to create signalfd")?
+ {
+ let pid = siginfo.ssi_pid;
+ let pid_label = match linux.pid_debug_label_map.get(&pid) {
+ Some(label) => format!("{} (pid {})", label, pid),
+ None => format!("pid {}", pid),
+ };
+ error!(
+ "child {} died: signo {}, status {}, code {}",
+ pid_label, siginfo.ssi_signo, siginfo.ssi_status, siginfo.ssi_code
+ );
+ }
+ break 'wait;
+ }
+ Token::IrqFd { index } => {
+ if let Err(e) = linux.irq_chip.service_irq_event(index) {
+ error!("failed to signal irq {}: {}", index, e);
+ }
+ }
+ Token::DelayedIrqFd => {
+ if let Err(e) = linux.irq_chip.process_delayed_irq_events() {
+ warn!("can't deliver delayed irqs: {}", e);
+ }
+ }
+ Token::VmControlServer => {
+ if let Some(socket_server) = &control_server_socket {
+ match socket_server.accept() {
+ Ok(socket) => {
+ wait_ctx
+ .add(
+ &socket,
+ Token::VmControl {
+ index: control_tubes.len(),
+ },
+ )
+ .context("failed to add descriptor to wait context")?;
+ control_tubes.push(TaggedControlTube::Vm(Tube::new(socket)));
+ }
+ Err(e) => error!("failed to accept socket: {}", e),
+ }
+ }
+ }
+ Token::VmControl { index } => {
+ let mut add_tubes = Vec::new();
+ if let Some(socket) = control_tubes.get(index) {
+ match socket {
+ TaggedControlTube::Vm(tube) => match tube.recv::<VmRequest>() {
+ Ok(request) => {
+ let mut run_mode_opt = None;
+ let response = match request {
+ VmRequest::VfioCommand { vfio_path, add } => {
+ handle_vfio_command(
+ &mut linux,
+ &mut sys_allocator,
+ &cfg,
+ &mut add_tubes,
+ &iommu_host_tube,
+ &vfio_path,
+ add,
+ )
+ }
+ _ => request.execute(
+ &mut run_mode_opt,
+ balloon_host_tube.as_ref(),
+ &mut balloon_stats_id,
+ disk_host_tubes,
+ &mut linux.pm,
+ #[cfg(feature = "usb")]
+ Some(&usb_control_tube),
+ #[cfg(not(feature = "usb"))]
+ None,
+ &mut linux.bat_control,
+ &vcpu_handles,
+ ),
+ };
+
+ if let Err(e) = tube.send(&response) {
+ error!("failed to send VmResponse: {}", e);
+ }
+ if let Some(run_mode) = run_mode_opt {
+ info!("control socket changed run mode to {}", run_mode);
+ match run_mode {
+ VmRunMode::Exiting => {
+ break 'wait;
+ }
+ other => {
+ if other == VmRunMode::Running {
+ for dev in &linux.resume_notify_devices {
+ dev.lock().resume_imminent();
+ }
+ }
+ vcpu::kick_all_vcpus(
+ &vcpu_handles,
+ linux.irq_chip.as_irq_chip(),
+ VcpuControl::RunState(other),
+ );
+ }
+ }
+ }
+ }
+ Err(e) => {
+ if let TubeError::Disconnected = e {
+ vm_control_indices_to_remove.push(index);
+ } else {
+ error!("failed to recv VmRequest: {}", e);
+ }
+ }
+ },
+ TaggedControlTube::VmMemory(tube) => {
+ match tube.recv::<VmMemoryRequest>() {
+ Ok(request) => {
+ let response = request.execute(
+ &mut linux.vm,
+ &mut sys_allocator,
+ Arc::clone(&map_request),
+ &mut gralloc,
+ );
+ if let Err(e) = tube.send(&response) {
+ error!("failed to send VmMemoryControlResponse: {}", e);
+ }
+ }
+ Err(e) => {
+ if let TubeError::Disconnected = e {
+ vm_control_indices_to_remove.push(index);
+ } else {
+ error!("failed to recv VmMemoryControlRequest: {}", e);
+ }
+ }
+ }
+ }
+ TaggedControlTube::VmIrq(tube) => match tube.recv::<VmIrqRequest>() {
+ Ok(request) => {
+ let response = {
+ let irq_chip = &mut linux.irq_chip;
+ request.execute(
+ |setup| match setup {
+ IrqSetup::Event(irq, ev, _, _, _) => {
+ let irq_evt = devices::IrqEdgeEvent::from_event(ev.try_clone()?);
+ if let Some(event_index) = irq_chip
+ .register_edge_irq_event(irq, &irq_evt)?
+ {
+ match wait_ctx.add(
+ ev,
+ Token::IrqFd {
+ index: event_index
+ },
+ ) {
+ Err(e) => {
+ warn!("failed to add IrqFd to poll context: {}", e);
+ Err(e)
+ },
+ Ok(_) => {
+ Ok(())
+ }
+ }
+ } else {
+ Ok(())
+ }
+ }
+ IrqSetup::Route(route) => irq_chip.route_irq(route),
+ IrqSetup::UnRegister(irq, ev) => {
+ let irq_evt = devices::IrqEdgeEvent::from_event(ev.try_clone()?);
+ irq_chip.unregister_edge_irq_event(irq, &irq_evt)
+ }
+ },
+ &mut sys_allocator,
+ )
+ };
+ if let Err(e) = tube.send(&response) {
+ error!("failed to send VmIrqResponse: {}", e);
+ }
+ }
+ Err(e) => {
+ if let TubeError::Disconnected = e {
+ vm_control_indices_to_remove.push(index);
+ } else {
+ error!("failed to recv VmIrqRequest: {}", e);
+ }
+ }
+ },
+ TaggedControlTube::VmMsync(tube) => {
+ match tube.recv::<VmMsyncRequest>() {
+ Ok(request) => {
+ let response = request.execute(&mut linux.vm);
+ if let Err(e) = tube.send(&response) {
+ error!("failed to send VmMsyncResponse: {}", e);
+ }
+ }
+ Err(e) => {
+ if let TubeError::Disconnected = e {
+ vm_control_indices_to_remove.push(index);
+ } else {
+ error!("failed to recv VmMsyncRequest: {}", e);
+ }
+ }
+ }
+ }
+ TaggedControlTube::Fs(tube) => match tube.recv::<FsMappingRequest>() {
+ Ok(request) => {
+ let response =
+ request.execute(&mut linux.vm, &mut sys_allocator);
+ if let Err(e) = tube.send(&response) {
+ error!("failed to send VmResponse: {}", e);
+ }
+ }
+ Err(e) => {
+ if let TubeError::Disconnected = e {
+ vm_control_indices_to_remove.push(index);
+ } else {
+ error!("failed to recv VmResponse: {}", e);
+ }
+ }
+ },
+ }
+ }
+ if !add_tubes.is_empty() {
+ for (idx, socket) in add_tubes.iter().enumerate() {
+ wait_ctx
+ .add(
+ socket.as_ref(),
+ Token::VmControl {
+ index: idx + control_tubes.len(),
+ },
+ )
+ .context(
+ "failed to add hotplug vfio-pci descriptor ot wait context",
+ )?;
+ }
+ control_tubes.append(&mut add_tubes);
+ }
+ }
+ }
+ }
+
+ // It's possible more data is readable and buffered while the socket is hungup,
+ // so don't delete the tube from the poll context until we're sure all the
+ // data is read.
+ // Below case covers a condition where we have received a hungup event and the tube is not
+ // readable.
+ // In case of readable tube, once all data is read, any attempt to read more data on hungup
+ // tube should fail. On such failure, we get Disconnected error and index gets added to
+ // vm_control_indices_to_remove by the time we reach here.
+ for event in events.iter().filter(|e| e.is_hungup && !e.is_readable) {
+ if let Token::VmControl { index } = event.token {
+ vm_control_indices_to_remove.push(index);
+ }
+ }
+
+ // Sort in reverse so the highest indexes are removed first. This removal algorithm
+ // preserves correct indexes as each element is removed.
+ vm_control_indices_to_remove.sort_unstable_by_key(|&k| Reverse(k));
+ vm_control_indices_to_remove.dedup();
+ for index in vm_control_indices_to_remove {
+ // Delete the socket from the `wait_ctx` synchronously. Otherwise, the kernel will do
+ // this automatically when the FD inserted into the `wait_ctx` is closed after this
+ // if-block, but this removal can be deferred unpredictably. In some instances where the
+ // system is under heavy load, we can even get events returned by `wait_ctx` for an FD
+ // that has already been closed. Because the token associated with that spurious event
+ // now belongs to a different socket, the control loop will start to interact with
+ // sockets that might not be ready to use. This can cause incorrect hangup detection or
+ // blocking on a socket that will never be ready. See also: crbug.com/1019986
+ if let Some(socket) = control_tubes.get(index) {
+ wait_ctx
+ .delete(socket)
+ .context("failed to remove descriptor from wait context")?;
+ }
+
+ // This line implicitly drops the socket at `index` when it gets returned by
+ // `swap_remove`. After this line, the socket at `index` is not the one from
+ // `vm_control_indices_to_remove`. Because of this socket's change in index, we need to
+ // use `wait_ctx.modify` to change the associated index in its `Token::VmControl`.
+ control_tubes.swap_remove(index);
+ if let Some(tube) = control_tubes.get(index) {
+ wait_ctx
+ .modify(tube, EventType::Read, Token::VmControl { index })
+ .context("failed to add descriptor to wait context")?;
+ }
+ }
+ }
+
+ vcpu::kick_all_vcpus(
+ &vcpu_handles,
+ linux.irq_chip.as_irq_chip(),
+ VcpuControl::RunState(VmRunMode::Exiting),
+ );
+ for (handle, _) in vcpu_handles {
+ if let Err(e) = handle.join() {
+ error!("failed to join vcpu thread: {:?}", e);
+ }
+ }
+
+ // Explicitly drop the VM structure here to allow the devices to clean up before the
+ // control sockets are closed when this function exits.
+ mem::drop(linux);
+
+ stdin()
+ .set_canon_mode()
+ .expect("failed to restore canonical mode for terminal");
+
+ Ok(exit_state)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::path::PathBuf;
+
+ // Create a file-backed mapping parameters struct with the given `address` and `size` and other
+ // parameters set to default values.
+ fn test_file_backed_mapping(address: u64, size: u64) -> FileBackedMappingParameters {
+ FileBackedMappingParameters {
+ address,
+ size,
+ path: PathBuf::new(),
+ offset: 0,
+ writable: false,
+ sync: false,
+ }
+ }
+
+ #[test]
+ fn guest_mem_file_backed_mappings_overlap() {
+ // Base case: no file mappings; output layout should be identical.
+ assert_eq!(
+ punch_holes_in_guest_mem_layout_for_mappings(
+ vec![
+ (GuestAddress(0), 0xD000_0000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000),
+ ],
+ &[]
+ ),
+ vec![
+ (GuestAddress(0), 0xD000_0000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000),
+ ]
+ );
+
+ // File mapping that does not overlap guest memory.
+ assert_eq!(
+ punch_holes_in_guest_mem_layout_for_mappings(
+ vec![
+ (GuestAddress(0), 0xD000_0000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000),
+ ],
+ &[test_file_backed_mapping(0xD000_0000, 0x1000)]
+ ),
+ vec![
+ (GuestAddress(0), 0xD000_0000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000),
+ ]
+ );
+
+ // File mapping at the start of the low address space region.
+ assert_eq!(
+ punch_holes_in_guest_mem_layout_for_mappings(
+ vec![
+ (GuestAddress(0), 0xD000_0000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000),
+ ],
+ &[test_file_backed_mapping(0, 0x2000)]
+ ),
+ vec![
+ (GuestAddress(0x2000), 0xD000_0000 - 0x2000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000),
+ ]
+ );
+
+ // File mapping at the end of the low address space region.
+ assert_eq!(
+ punch_holes_in_guest_mem_layout_for_mappings(
+ vec![
+ (GuestAddress(0), 0xD000_0000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000),
+ ],
+ &[test_file_backed_mapping(0xD000_0000 - 0x2000, 0x2000)]
+ ),
+ vec![
+ (GuestAddress(0), 0xD000_0000 - 0x2000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000),
+ ]
+ );
+
+ // File mapping fully contained within the middle of the low address space region.
+ assert_eq!(
+ punch_holes_in_guest_mem_layout_for_mappings(
+ vec![
+ (GuestAddress(0), 0xD000_0000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000),
+ ],
+ &[test_file_backed_mapping(0x1000, 0x2000)]
+ ),
+ vec![
+ (GuestAddress(0), 0x1000),
+ (GuestAddress(0x3000), 0xD000_0000 - 0x3000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000),
+ ]
+ );
+
+ // File mapping at the start of the high address space region.
+ assert_eq!(
+ punch_holes_in_guest_mem_layout_for_mappings(
+ vec![
+ (GuestAddress(0), 0xD000_0000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000),
+ ],
+ &[test_file_backed_mapping(0x1_0000_0000, 0x2000)]
+ ),
+ vec![
+ (GuestAddress(0), 0xD000_0000),
+ (GuestAddress(0x1_0000_2000), 0x8_0000 - 0x2000),
+ ]
+ );
+
+ // File mapping at the end of the high address space region.
+ assert_eq!(
+ punch_holes_in_guest_mem_layout_for_mappings(
+ vec![
+ (GuestAddress(0), 0xD000_0000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000),
+ ],
+ &[test_file_backed_mapping(0x1_0008_0000 - 0x2000, 0x2000)]
+ ),
+ vec![
+ (GuestAddress(0), 0xD000_0000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000 - 0x2000),
+ ]
+ );
+
+ // File mapping fully contained within the middle of the high address space region.
+ assert_eq!(
+ punch_holes_in_guest_mem_layout_for_mappings(
+ vec![
+ (GuestAddress(0), 0xD000_0000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000),
+ ],
+ &[test_file_backed_mapping(0x1_0000_1000, 0x2000)]
+ ),
+ vec![
+ (GuestAddress(0), 0xD000_0000),
+ (GuestAddress(0x1_0000_0000), 0x1000),
+ (GuestAddress(0x1_0000_3000), 0x8_0000 - 0x3000),
+ ]
+ );
+
+ // File mapping overlapping two guest memory regions.
+ assert_eq!(
+ punch_holes_in_guest_mem_layout_for_mappings(
+ vec![
+ (GuestAddress(0), 0xD000_0000),
+ (GuestAddress(0x1_0000_0000), 0x8_0000),
+ ],
+ &[test_file_backed_mapping(0xA000_0000, 0x60002000)]
+ ),
+ vec![
+ (GuestAddress(0), 0xA000_0000),
+ (GuestAddress(0x1_0000_2000), 0x8_0000 - 0x2000),
+ ]
+ );
+ }
+}
diff --git a/src/linux/vcpu.rs b/src/linux/vcpu.rs
new file mode 100644
index 000000000..562b528ee
--- /dev/null
+++ b/src/linux/vcpu.rs
@@ -0,0 +1,744 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::collections::{BTreeMap, BTreeSet};
+use std::fs::{File, OpenOptions};
+use std::io::prelude::*;
+use std::os::unix::fs::FileExt;
+use std::rc::Rc;
+use std::sync::{mpsc, Arc, Barrier};
+
+use std::thread;
+use std::thread::JoinHandle;
+
+use libc::{self, c_int};
+
+use anyhow::{Context, Result};
+use base::*;
+use devices::{self, IrqChip, VcpuRunState};
+use hypervisor::{Vcpu, VcpuExit, VcpuRunHandle};
+use vm_control::*;
+#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+use vm_memory::GuestMemory;
+
+use arch::{self, LinuxArch};
+
+#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
+use {
+ aarch64::AArch64 as Arch,
+ devices::IrqChipAArch64 as IrqChipArch,
+ hypervisor::{VcpuAArch64 as VcpuArch, VmAArch64 as VmArch},
+};
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+use {
+ devices::IrqChipX86_64 as IrqChipArch,
+ hypervisor::{VcpuX86_64 as VcpuArch, VmX86_64 as VmArch},
+ x86_64::X8664arch as Arch,
+};
+
+use super::ExitState;
+
+pub fn setup_vcpu_signal_handler<T: Vcpu>(use_hypervisor_signals: bool) -> Result<()> {
+ if use_hypervisor_signals {
+ unsafe {
+ extern "C" fn handle_signal(_: c_int) {}
+ // Our signal handler does nothing and is trivially async signal safe.
+ register_rt_signal_handler(SIGRTMIN() + 0, handle_signal)
+ .context("error registering signal handler")?;
+ }
+ block_signal(SIGRTMIN() + 0).context("failed to block signal")?;
+ } else {
+ unsafe {
+ extern "C" fn handle_signal<T: Vcpu>(_: c_int) {
+ T::set_local_immediate_exit(true);
+ }
+ register_rt_signal_handler(SIGRTMIN() + 0, handle_signal::<T>)
+ .context("error registering signal handler")?;
+ }
+ }
+ Ok(())
+}
+
+// Sets up a vcpu and converts it into a runnable vcpu.
+pub fn runnable_vcpu<V>(
+ cpu_id: usize,
+ kvm_vcpu_id: usize,
+ vcpu: Option<V>,
+ vm: impl VmArch,
+ irq_chip: &mut dyn IrqChipArch,
+ vcpu_count: usize,
+ run_rt: bool,
+ vcpu_affinity: Vec<usize>,
+ no_smt: bool,
+ has_bios: bool,
+ use_hypervisor_signals: bool,
+ enable_per_vm_core_scheduling: bool,
+ host_cpu_topology: bool,
+ vcpu_cgroup_tasks_file: Option<File>,
+) -> Result<(V, VcpuRunHandle)>
+where
+ V: VcpuArch,
+{
+ let mut vcpu = match vcpu {
+ Some(v) => v,
+ None => {
+ // If vcpu is None, it means this arch/hypervisor requires create_vcpu to be called from
+ // the vcpu thread.
+ match vm
+ .create_vcpu(kvm_vcpu_id)
+ .context("failed to create vcpu")?
+ .downcast::<V>()
+ {
+ Ok(v) => *v,
+ Err(_) => panic!("VM created wrong type of VCPU"),
+ }
+ }
+ };
+
+ irq_chip
+ .add_vcpu(cpu_id, &vcpu)
+ .context("failed to add vcpu to irq chip")?;
+
+ if !vcpu_affinity.is_empty() {
+ if let Err(e) = set_cpu_affinity(vcpu_affinity) {
+ error!("Failed to set CPU affinity: {}", e);
+ }
+ }
+
+ Arch::configure_vcpu(
+ &vm,
+ vm.get_hypervisor(),
+ irq_chip,
+ &mut vcpu,
+ cpu_id,
+ vcpu_count,
+ has_bios,
+ no_smt,
+ host_cpu_topology,
+ )
+ .context("failed to configure vcpu")?;
+
+ if !enable_per_vm_core_scheduling {
+ // Do per-vCPU core scheduling by setting a unique cookie to each vCPU.
+ if let Err(e) = enable_core_scheduling() {
+ error!("Failed to enable core scheduling: {}", e);
+ }
+ }
+
+ // Move vcpu thread to cgroup
+ if let Some(mut f) = vcpu_cgroup_tasks_file {
+ f.write_all(base::gettid().to_string().as_bytes())
+ .context("failed to write vcpu tid to cgroup tasks")?;
+ }
+
+ if run_rt {
+ const DEFAULT_VCPU_RT_LEVEL: u16 = 6;
+ if let Err(e) = set_rt_prio_limit(u64::from(DEFAULT_VCPU_RT_LEVEL))
+ .and_then(|_| set_rt_round_robin(i32::from(DEFAULT_VCPU_RT_LEVEL)))
+ {
+ warn!("Failed to set vcpu to real time: {}", e);
+ }
+ }
+
+ if use_hypervisor_signals {
+ let mut v = get_blocked_signals().context("failed to retrieve signal mask for vcpu")?;
+ v.retain(|&x| x != SIGRTMIN() + 0);
+ vcpu.set_signal_mask(&v)
+ .context("failed to set the signal mask for vcpu")?;
+ }
+
+ let vcpu_run_handle = vcpu
+ .take_run_handle(Some(SIGRTMIN() + 0))
+ .context("failed to set thread id for vcpu")?;
+
+ Ok((vcpu, vcpu_run_handle))
+}
+
+#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+fn handle_debug_msg<V>(
+ cpu_id: usize,
+ vcpu: &V,
+ guest_mem: &GuestMemory,
+ d: VcpuDebug,
+ reply_tube: &mpsc::Sender<VcpuDebugStatusMessage>,
+) -> Result<()>
+where
+ V: VcpuArch + 'static,
+{
+ match d {
+ VcpuDebug::ReadRegs => {
+ let msg = VcpuDebugStatusMessage {
+ cpu: cpu_id as usize,
+ msg: VcpuDebugStatus::RegValues(
+ Arch::debug_read_registers(vcpu as &V)
+ .context("failed to handle a gdb ReadRegs command")?,
+ ),
+ };
+ reply_tube
+ .send(msg)
+ .context("failed to send a debug status to GDB thread")
+ }
+ VcpuDebug::WriteRegs(regs) => {
+ Arch::debug_write_registers(vcpu as &V, &regs)
+ .context("failed to handle a gdb WriteRegs command")?;
+ reply_tube
+ .send(VcpuDebugStatusMessage {
+ cpu: cpu_id as usize,
+ msg: VcpuDebugStatus::CommandComplete,
+ })
+ .context("failed to send a debug status to GDB thread")
+ }
+ VcpuDebug::ReadMem(vaddr, len) => {
+ let msg = VcpuDebugStatusMessage {
+ cpu: cpu_id as usize,
+ msg: VcpuDebugStatus::MemoryRegion(
+ Arch::debug_read_memory(vcpu as &V, guest_mem, vaddr, len)
+ .unwrap_or(Vec::new()),
+ ),
+ };
+ reply_tube
+ .send(msg)
+ .context("failed to send a debug status to GDB thread")
+ }
+ VcpuDebug::WriteMem(vaddr, buf) => {
+ Arch::debug_write_memory(vcpu as &V, guest_mem, vaddr, &buf)
+ .context("failed to handle a gdb WriteMem command")?;
+ reply_tube
+ .send(VcpuDebugStatusMessage {
+ cpu: cpu_id as usize,
+ msg: VcpuDebugStatus::CommandComplete,
+ })
+ .context("failed to send a debug status to GDB thread")
+ }
+ VcpuDebug::EnableSinglestep => {
+ Arch::debug_enable_singlestep(vcpu as &V)
+ .context("failed to handle a gdb EnableSingleStep command")?;
+ reply_tube
+ .send(VcpuDebugStatusMessage {
+ cpu: cpu_id as usize,
+ msg: VcpuDebugStatus::CommandComplete,
+ })
+ .context("failed to send a debug status to GDB thread")
+ }
+ VcpuDebug::SetHwBreakPoint(addrs) => {
+ Arch::debug_set_hw_breakpoints(vcpu as &V, &addrs)
+ .context("failed to handle a gdb SetHwBreakPoint command")?;
+ reply_tube
+ .send(VcpuDebugStatusMessage {
+ cpu: cpu_id as usize,
+ msg: VcpuDebugStatus::CommandComplete,
+ })
+ .context("failed to send a debug status to GDB thread")
+ }
+ }
+}
+
+#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
+fn handle_s2idle_request(_privileged_vm: bool) {}
+
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+fn handle_s2idle_request(privileged_vm: bool) {
+ const POWER_STATE_FREEZE: &[u8] = b"freeze";
+
+ // For non privileged guests, we silently ignore the suspend request
+ if !privileged_vm {
+ return;
+ }
+
+ let mut power_state = match OpenOptions::new().write(true).open("/sys/power/state") {
+ Ok(s) => s,
+ Err(err) => {
+ error!("Failed on open /sys/power/state: {}", err);
+ return;
+ }
+ };
+
+ if let Err(err) = power_state.write(POWER_STATE_FREEZE) {
+ error!("Failed on writing to /sys/power/state: {}", err);
+ return;
+ }
+}
+
+fn vcpu_loop<V>(
+ mut run_mode: VmRunMode,
+ cpu_id: usize,
+ vcpu: V,
+ vcpu_run_handle: VcpuRunHandle,
+ irq_chip: Box<dyn IrqChipArch + 'static>,
+ run_rt: bool,
+ delay_rt: bool,
+ io_bus: devices::Bus,
+ mmio_bus: devices::Bus,
+ requires_pvclock_ctrl: bool,
+ from_main_tube: mpsc::Receiver<VcpuControl>,
+ use_hypervisor_signals: bool,
+ privileged_vm: bool,
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))] to_gdb_tube: Option<
+ mpsc::Sender<VcpuDebugStatusMessage>,
+ >,
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))] guest_mem: GuestMemory,
+ msr_handlers: MsrHandlers,
+) -> ExitState
+where
+ V: VcpuArch + 'static,
+{
+ let mut interrupted_by_signal = false;
+
+ loop {
+ // Start by checking for messages to process and the run state of the CPU.
+ // An extra check here for Running so there isn't a need to call recv unless a
+ // message is likely to be ready because a signal was sent.
+ if interrupted_by_signal || run_mode != VmRunMode::Running {
+ 'state_loop: loop {
+ // Tries to get a pending message without blocking first.
+ let msg = match from_main_tube.try_recv() {
+ Ok(m) => m,
+ Err(mpsc::TryRecvError::Empty) if run_mode == VmRunMode::Running => {
+ // If the VM is running and no message is pending, the state won't
+ // change.
+ break 'state_loop;
+ }
+ Err(mpsc::TryRecvError::Empty) => {
+ // If the VM is not running, wait until a message is ready.
+ match from_main_tube.recv() {
+ Ok(m) => m,
+ Err(mpsc::RecvError) => {
+ error!("Failed to read from main tube in vcpu");
+ return ExitState::Crash;
+ }
+ }
+ }
+ Err(mpsc::TryRecvError::Disconnected) => {
+ error!("Failed to read from main tube in vcpu");
+ return ExitState::Crash;
+ }
+ };
+
+ // Collect all pending messages.
+ let mut messages = vec![msg];
+ messages.append(&mut from_main_tube.try_iter().collect());
+
+ for msg in messages {
+ match msg {
+ VcpuControl::RunState(new_mode) => {
+ run_mode = new_mode;
+ match run_mode {
+ VmRunMode::Running => break 'state_loop,
+ VmRunMode::Suspending => {
+ // On KVM implementations that use a paravirtualized
+ // clock (e.g. x86), a flag must be set to indicate to
+ // the guest kernel that a vCPU was suspended. The guest
+ // kernel will use this flag to prevent the soft lockup
+ // detection from triggering when this vCPU resumes,
+ // which could happen days later in realtime.
+ if requires_pvclock_ctrl {
+ if let Err(e) = vcpu.pvclock_ctrl() {
+ error!(
+ "failed to tell hypervisor vcpu {} is suspending: {}",
+ cpu_id, e
+ );
+ }
+ }
+ }
+ VmRunMode::Breakpoint => {}
+ VmRunMode::Exiting => return ExitState::Stop,
+ }
+ }
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+ VcpuControl::Debug(d) => match &to_gdb_tube {
+ Some(ref ch) => {
+ if let Err(e) = handle_debug_msg(cpu_id, &vcpu, &guest_mem, d, ch) {
+ error!("Failed to handle gdb message: {}", e);
+ }
+ }
+ None => {
+ error!("VcpuControl::Debug received while GDB feature is disabled: {:?}", d);
+ }
+ },
+ VcpuControl::MakeRT => {
+ if run_rt && delay_rt {
+ info!("Making vcpu {} RT\n", cpu_id);
+ const DEFAULT_VCPU_RT_LEVEL: u16 = 6;
+ if let Err(e) = set_rt_prio_limit(u64::from(DEFAULT_VCPU_RT_LEVEL))
+ .and_then(|_| {
+ set_rt_round_robin(i32::from(DEFAULT_VCPU_RT_LEVEL))
+ })
+ {
+ warn!("Failed to set vcpu to real time: {}", e);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ interrupted_by_signal = false;
+
+ // Vcpus may have run a HLT instruction, which puts them into a state other than
+ // VcpuRunState::Runnable. In that case, this call to wait_until_runnable blocks
+ // until either the irqchip receives an interrupt for this vcpu, or until the main
+ // thread kicks this vcpu as a result of some VmControl operation. In most IrqChip
+ // implementations HLT instructions do not make it to crosvm, and thus this is a
+ // no-op that always returns VcpuRunState::Runnable.
+ match irq_chip.wait_until_runnable(&vcpu) {
+ Ok(VcpuRunState::Runnable) => {}
+ Ok(VcpuRunState::Interrupted) => interrupted_by_signal = true,
+ Err(e) => error!(
+ "error waiting for vcpu {} to become runnable: {}",
+ cpu_id, e
+ ),
+ }
+
+ if !interrupted_by_signal {
+ match vcpu.run(&vcpu_run_handle) {
+ Ok(VcpuExit::IoIn { port, mut size }) => {
+ let mut data = [0; 8];
+ if size > data.len() {
+ error!(
+ "unsupported IoIn size of {} bytes at port {:#x}",
+ size, port
+ );
+ size = data.len();
+ }
+ io_bus.read(port as u64, &mut data[..size]);
+ if let Err(e) = vcpu.set_data(&data[..size]) {
+ error!(
+ "failed to set return data for IoIn at port {:#x}: {}",
+ port, e
+ );
+ }
+ }
+ Ok(VcpuExit::IoOut {
+ port,
+ mut size,
+ data,
+ }) => {
+ if size > data.len() {
+ error!(
+ "unsupported IoOut size of {} bytes at port {:#x}",
+ size, port
+ );
+ size = data.len();
+ }
+ io_bus.write(port as u64, &data[..size]);
+ }
+ Ok(VcpuExit::MmioRead { address, size }) => {
+ let mut data = [0; 8];
+ mmio_bus.read(address, &mut data[..size]);
+ // Setting data for mmio can not fail.
+ let _ = vcpu.set_data(&data[..size]);
+ }
+ Ok(VcpuExit::MmioWrite {
+ address,
+ size,
+ data,
+ }) => {
+ mmio_bus.write(address, &data[..size]);
+ }
+ Ok(VcpuExit::RdMsr { index }) => {
+ if let Some(data) = msr_handlers.read(index) {
+ let _ = vcpu.set_data(&data.to_ne_bytes());
+ }
+ }
+ Ok(VcpuExit::WrMsr { .. }) => {
+ // TODO(b/215297064): implement MSR write
+ }
+ Ok(VcpuExit::IoapicEoi { vector }) => {
+ if let Err(e) = irq_chip.broadcast_eoi(vector) {
+ error!(
+ "failed to broadcast eoi {} on vcpu {}: {}",
+ vector, cpu_id, e
+ );
+ }
+ }
+ Ok(VcpuExit::IrqWindowOpen) => {}
+ Ok(VcpuExit::Hlt) => irq_chip.halted(cpu_id),
+ Ok(VcpuExit::Shutdown) => return ExitState::Stop,
+ Ok(VcpuExit::FailEntry {
+ hardware_entry_failure_reason,
+ }) => {
+ error!("vcpu hw run failure: {:#x}", hardware_entry_failure_reason);
+ return ExitState::Crash;
+ }
+ Ok(VcpuExit::SystemEventShutdown) => {
+ info!("system shutdown event on vcpu {}", cpu_id);
+ return ExitState::Stop;
+ }
+ Ok(VcpuExit::SystemEventReset) => {
+ info!("system reset event");
+ return ExitState::Reset;
+ }
+ Ok(VcpuExit::SystemEventCrash) => {
+ info!("system crash event on vcpu {}", cpu_id);
+ return ExitState::Stop;
+ }
+ Ok(VcpuExit::SystemEventS2Idle) => {
+ handle_s2idle_request(privileged_vm);
+ }
+ #[rustfmt::skip] Ok(VcpuExit::Debug { .. }) => {
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+ {
+ let msg = VcpuDebugStatusMessage {
+ cpu: cpu_id as usize,
+ msg: VcpuDebugStatus::HitBreakPoint,
+ };
+ if let Some(ref ch) = to_gdb_tube {
+ if let Err(e) = ch.send(msg) {
+ error!("failed to notify breakpoint to GDB thread: {}", e);
+ return ExitState::Crash;
+ }
+ }
+ run_mode = VmRunMode::Breakpoint;
+ }
+ }
+ Ok(r) => warn!("unexpected vcpu exit: {:?}", r),
+ Err(e) => match e.errno() {
+ libc::EINTR => interrupted_by_signal = true,
+ libc::EAGAIN => {}
+ _ => {
+ error!("vcpu hit unknown error: {}", e);
+ return ExitState::Crash;
+ }
+ },
+ }
+ }
+
+ if interrupted_by_signal {
+ if use_hypervisor_signals {
+ // Try to clear the signal that we use to kick VCPU if it is pending before
+ // attempting to handle pause requests.
+ if let Err(e) = clear_signal(SIGRTMIN() + 0) {
+ error!("failed to clear pending signal: {}", e);
+ return ExitState::Crash;
+ }
+ } else {
+ vcpu.set_immediate_exit(false);
+ }
+ }
+
+ if let Err(e) = irq_chip.inject_interrupts(&vcpu) {
+ error!("failed to inject interrupts for vcpu {}: {}", cpu_id, e);
+ }
+ }
+}
+
+trait MsrHandling {
+ fn read(&self, index: u32) -> Result<u64>;
+ fn write(&self, index: u32, data: u64) -> Result<()>;
+}
+
+struct ReadPassthrough {
+ dev_msr: std::fs::File,
+}
+
+impl MsrHandling for ReadPassthrough {
+ fn read(&self, index: u32) -> Result<u64> {
+ let mut data = [0; 8];
+ self.dev_msr.read_exact_at(&mut data, index.into())?;
+ Ok(u64::from_ne_bytes(data))
+ }
+
+ fn write(&self, _index: u32, _data: u64) -> Result<()> {
+ // TODO(b/215297064): implement MSR write
+ unimplemented!();
+ }
+}
+
+impl ReadPassthrough {
+ fn new() -> Result<Self> {
+ // TODO(b/215297064): Support reading from other CPUs than 0, should match running CPU.
+ let filename = "/dev/cpu/0/msr";
+ let dev_msr = OpenOptions::new()
+ .read(true)
+ .open(&filename)
+ .context("Cannot open /dev/cpu/0/msr, are you root?")?;
+ Ok(ReadPassthrough { dev_msr })
+ }
+}
+
+/// MSR handler configuration. Per-cpu.
+struct MsrHandlers {
+ handler: BTreeMap<u32, Rc<Box<dyn MsrHandling>>>,
+}
+
+impl MsrHandlers {
+ fn new() -> Self {
+ MsrHandlers {
+ handler: BTreeMap::new(),
+ }
+ }
+
+ fn read(&self, index: u32) -> Option<u64> {
+ if let Some(handler) = self.handler.get(&index) {
+ match handler.read(index) {
+ Ok(data) => Some(data),
+ Err(e) => {
+ error!("MSR host read failed {:#x} {:?}", index, e);
+ None
+ }
+ }
+ } else {
+ None
+ }
+ }
+}
+
+pub fn run_vcpu<V>(
+ cpu_id: usize,
+ kvm_vcpu_id: usize,
+ vcpu: Option<V>,
+ vm: impl VmArch + 'static,
+ mut irq_chip: Box<dyn IrqChipArch + 'static>,
+ vcpu_count: usize,
+ run_rt: bool,
+ vcpu_affinity: Vec<usize>,
+ delay_rt: bool,
+ no_smt: bool,
+ start_barrier: Arc<Barrier>,
+ has_bios: bool,
+ mut io_bus: devices::Bus,
+ mut mmio_bus: devices::Bus,
+ exit_evt: Event,
+ reset_evt: Event,
+ crash_evt: Event,
+ requires_pvclock_ctrl: bool,
+ from_main_tube: mpsc::Receiver<VcpuControl>,
+ use_hypervisor_signals: bool,
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))] to_gdb_tube: Option<
+ mpsc::Sender<VcpuDebugStatusMessage>,
+ >,
+ enable_per_vm_core_scheduling: bool,
+ host_cpu_topology: bool,
+ privileged_vm: bool,
+ vcpu_cgroup_tasks_file: Option<File>,
+ userspace_msr: BTreeSet<u32>,
+) -> Result<JoinHandle<()>>
+where
+ V: VcpuArch + 'static,
+{
+ thread::Builder::new()
+ .name(format!("crosvm_vcpu{}", cpu_id))
+ .spawn(move || {
+ // The VCPU thread must trigger either `exit_evt` or `reset_event` in all paths. A
+ // `ScopedEvent`'s Drop implementation ensures that the `exit_evt` will be sent if
+ // anything happens before we get to writing the final event.
+ let scoped_exit_evt = ScopedEvent::from(exit_evt);
+
+ let mut msr_handlers = MsrHandlers::new();
+ if !userspace_msr.is_empty() {
+ let read_passthrough: Rc<Box<dyn MsrHandling>> = match ReadPassthrough::new() {
+ Ok(r) => Rc::new(Box::new(r)),
+ Err(e) => {
+ error!(
+ "failed to create MSR read passthrough handler for vcpu {}: {:#}",
+ cpu_id, e
+ );
+ return;
+ }
+ };
+
+ userspace_msr.iter().for_each(|&index| {
+ msr_handlers.handler.insert(index, read_passthrough.clone());
+ });
+ }
+
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+ let guest_mem = vm.get_memory().clone();
+ let runnable_vcpu = runnable_vcpu(
+ cpu_id,
+ kvm_vcpu_id,
+ vcpu,
+ vm,
+ irq_chip.as_mut(),
+ vcpu_count,
+ run_rt && !delay_rt,
+ vcpu_affinity,
+ no_smt,
+ has_bios,
+ use_hypervisor_signals,
+ enable_per_vm_core_scheduling,
+ host_cpu_topology,
+ vcpu_cgroup_tasks_file,
+ );
+
+ start_barrier.wait();
+
+ let (vcpu, vcpu_run_handle) = match runnable_vcpu {
+ Ok(v) => v,
+ Err(e) => {
+ error!("failed to start vcpu {}: {:#}", cpu_id, e);
+ return;
+ }
+ };
+
+ #[allow(unused_mut)]
+ let mut run_mode = VmRunMode::Running;
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+ if to_gdb_tube.is_some() {
+ // Wait until a GDB client attaches
+ run_mode = VmRunMode::Breakpoint;
+ }
+
+ mmio_bus.set_access_id(cpu_id);
+ io_bus.set_access_id(cpu_id);
+
+ let exit_reason = vcpu_loop(
+ run_mode,
+ cpu_id,
+ vcpu,
+ vcpu_run_handle,
+ irq_chip,
+ run_rt,
+ delay_rt,
+ io_bus,
+ mmio_bus,
+ requires_pvclock_ctrl,
+ from_main_tube,
+ use_hypervisor_signals,
+ privileged_vm,
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+ to_gdb_tube,
+ #[cfg(all(target_arch = "x86_64", feature = "gdb"))]
+ guest_mem,
+ msr_handlers,
+ );
+
+ let exit_evt = scoped_exit_evt.into();
+ let final_event = match exit_reason {
+ ExitState::Stop => Some(exit_evt),
+ ExitState::Reset => Some(reset_evt),
+ ExitState::Crash => Some(crash_evt),
+ // vcpu_loop doesn't exit with GuestPanic.
+ ExitState::GuestPanic => None,
+ };
+ if let Some(final_event) = final_event {
+ if let Err(e) = final_event.write(1) {
+ error!(
+ "failed to send final event {:?} on vcpu {}: {}",
+ final_event, cpu_id, e
+ )
+ }
+ }
+ })
+ .context("failed to spawn VCPU thread")
+}
+
+/// Signals all running VCPUs to vmexit, sends VcpuControl message to each VCPU tube, and tells
+/// `irq_chip` to stop blocking halted VCPUs. The channel message is set first because both the
+/// signal and the irq_chip kick could cause the VCPU thread to continue through the VCPU run
+/// loop.
+pub fn kick_all_vcpus(
+ vcpu_handles: &[(JoinHandle<()>, mpsc::Sender<vm_control::VcpuControl>)],
+ irq_chip: &dyn IrqChip,
+ message: VcpuControl,
+) {
+ for (handle, tube) in vcpu_handles {
+ if let Err(e) = tube.send(message.clone()) {
+ error!("failed to send VcpuControl: {}", e);
+ }
+ let _ = handle.kill(SIGRTMIN() + 0);
+ }
+ irq_chip.kick_halted_vcpus();
+}
diff --git a/src/main.rs b/src/main.rs
index e86845a65..c910065f6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,41 +7,77 @@
pub mod panic_hook;
use std::collections::BTreeMap;
+use std::convert::TryFrom;
use std::default::Default;
use std::fs::{File, OpenOptions};
use std::io::{BufRead, BufReader};
+use std::ops::Deref;
+#[cfg(feature = "direct")]
+use std::ops::RangeInclusive;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::string::String;
use std::thread::sleep;
use std::time::Duration;
-use arch::{
- set_default_serial_parameters, Pstore, SerialHardware, SerialParameters, SerialType,
- VcpuAffinity,
-};
-use base::{debug, error, getpid, info, kill_process_group, reap_child, syslog, warn};
+use arch::{set_default_serial_parameters, Pstore, VcpuAffinity};
+use base::{debug, error, getpid, info, kill_process_group, pagesize, reap_child, syslog, warn};
+#[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
+use crosvm::platform::GpuRenderServerParameters;
#[cfg(feature = "direct")]
-use crosvm::DirectIoOption;
+use crosvm::{argument::parse_hex_or_decimal, DirectIoOption, HostPcieRootPortParameters};
use crosvm::{
argument::{self, print_help, set_arguments, Argument},
- platform, BindMount, Config, DiskOption, Executable, GidMap, SharedDir, TouchDeviceOption,
- VhostUserFsOption, VhostUserOption, DISK_ID_LEN,
+ platform, BindMount, Config, Executable, FileBackedMappingParameters, GidMap, SharedDir,
+ TouchDeviceOption, VfioCommand, VhostUserFsOption, VhostUserOption, VhostUserWlOption,
+ VvuOption,
};
+use devices::serial_device::{SerialHardware, SerialParameters};
+use devices::virtio::block::block::DiskOption;
+#[cfg(feature = "audio_cras")]
+use devices::virtio::snd::cras_backend::Error as CrasSndError;
+#[cfg(feature = "audio_cras")]
+use devices::virtio::vhost::user::device::run_cras_snd_device;
+use devices::virtio::vhost::user::device::{
+ run_block_device, run_console_device, run_fs_device, run_net_device, run_vsock_device,
+ run_wl_device,
+};
+use devices::virtio::vhost::vsock::VhostVsockDeviceParameter;
+#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
+use devices::virtio::VideoBackendType;
#[cfg(feature = "gpu")]
-use devices::virtio::gpu::{GpuMode, GpuParameters};
-use devices::ProtectionType;
+use devices::virtio::{
+ gpu::{
+ GpuDisplayParameters, GpuMode, GpuParameters, DEFAULT_DISPLAY_HEIGHT, DEFAULT_DISPLAY_WIDTH,
+ },
+ vhost::user::device::run_gpu_device,
+};
+#[cfg(feature = "direct")]
+use devices::BusRange;
#[cfg(feature = "audio")]
use devices::{Ac97Backend, Ac97Parameters};
-use disk::QcowFile;
+use devices::{PciAddress, PciClassCode, StubPciParameters};
+use disk::{self, QcowFile};
+#[cfg(feature = "composite-disk")]
+use disk::{
+ create_composite_disk, create_disk_file, create_zero_filler, ImagePartitionType, PartitionInfo,
+};
+use hypervisor::ProtectionType;
+use serde_keyvalue::from_key_values;
+use uuid::Uuid;
use vm_control::{
client::{
do_modify_battery, do_usb_attach, do_usb_detach, do_usb_list, handle_request, vms_request,
ModifyUsbError, ModifyUsbResult,
},
BalloonControlCommand, BatteryType, DiskControlCommand, UsbControlResult, VmRequest,
+ VmResponse,
};
+#[cfg(feature = "scudo")]
+#[global_allocator]
+static ALLOCATOR: scudo::GlobalScudoAllocator = scudo::GlobalScudoAllocator;
+
fn executable_is_plugin(executable: &Option<Executable>) -> bool {
matches!(executable, Some(Executable::Plugin(_)))
}
@@ -153,9 +189,39 @@ fn parse_cpu_affinity(s: &str) -> argument::Result<VcpuAffinity> {
}
}
+fn parse_cpu_capacity(s: &str, cpu_capacity: &mut BTreeMap<usize, u32>) -> argument::Result<()> {
+ for cpu_pair in s.split(',') {
+ let assignment: Vec<&str> = cpu_pair.split('=').collect();
+ if assignment.len() != 2 {
+ return Err(argument::Error::InvalidValue {
+ value: cpu_pair.to_owned(),
+ expected: String::from("invalid CPU capacity syntax"),
+ });
+ }
+ let cpu = assignment[0]
+ .parse()
+ .map_err(|_| argument::Error::InvalidValue {
+ value: assignment[0].to_owned(),
+ expected: String::from("CPU index must be a non-negative integer"),
+ })?;
+ let capacity = assignment[1]
+ .parse()
+ .map_err(|_| argument::Error::InvalidValue {
+ value: assignment[1].to_owned(),
+ expected: String::from("CPU capacity must be a non-negative integer"),
+ })?;
+ if cpu_capacity.insert(cpu, capacity).is_some() {
+ return Err(argument::Error::InvalidValue {
+ value: cpu_pair.to_owned(),
+ expected: String::from("CPU index must be unique"),
+ });
+ }
+ }
+ Ok(())
+}
+
#[cfg(feature = "gpu")]
-fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> {
- let mut gpu_params: GpuParameters = Default::default();
+fn parse_gpu_options(s: Option<&str>, gpu_params: &mut GpuParameters) -> argument::Result<()> {
#[cfg(feature = "gfxstream")]
let mut vulkan_specified = false;
#[cfg(feature = "gfxstream")]
@@ -163,6 +229,9 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> {
#[cfg(feature = "gfxstream")]
let mut angle_specified = false;
+ let mut display_w: Option<u32> = None;
+ let mut display_h: Option<u32> = None;
+
if let Some(s) = s {
let opts = s
.split(',')
@@ -323,24 +392,24 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> {
}
}
"width" => {
- gpu_params.display_width =
- v.parse::<u32>()
- .map_err(|_| argument::Error::InvalidValue {
- value: v.to_string(),
- expected: String::from(
- "gpu parameter 'width' must be a valid integer",
- ),
- })?;
+ let width = v
+ .parse::<u32>()
+ .map_err(|_| argument::Error::InvalidValue {
+ value: v.to_string(),
+ expected: String::from("gpu parameter 'width' must be a valid integer"),
+ })?;
+ display_w = Some(width);
}
"height" => {
- gpu_params.display_height =
- v.parse::<u32>()
- .map_err(|_| argument::Error::InvalidValue {
- value: v.to_string(),
- expected: String::from(
- "gpu parameter 'height' must be a valid integer",
- ),
- })?;
+ let height = v
+ .parse::<u32>()
+ .map_err(|_| argument::Error::InvalidValue {
+ value: v.to_string(),
+ expected: String::from(
+ "gpu parameter 'height' must be a valid integer",
+ ),
+ })?;
+ display_h = Some(height);
}
"cache-path" => gpu_params.cache_path = Some(v.to_string()),
"cache-size" => gpu_params.cache_size = Some(v.to_string()),
@@ -369,6 +438,22 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> {
}
}
+ if display_w.is_some() || display_h.is_some() {
+ if display_w.is_none() || display_h.is_none() {
+ return Err(argument::Error::InvalidValue {
+ value: s.unwrap_or("").to_string(),
+ expected: String::from(
+ "gpu must include both 'width' and 'height' if either is supplied",
+ ),
+ });
+ }
+
+ gpu_params.displays.push(GpuDisplayParameters {
+ width: display_w.unwrap(),
+ height: display_h.unwrap(),
+ });
+ }
+
#[cfg(feature = "gfxstream")]
{
if !vulkan_specified && gpu_params.mode == GpuMode::ModeGfxstream {
@@ -388,7 +473,145 @@ fn parse_gpu_options(s: Option<&str>) -> argument::Result<GpuParameters> {
}
}
- Ok(gpu_params)
+ Ok(())
+}
+
+#[cfg(any(feature = "video-decoder", feature = "video-encoder"))]
+fn parse_video_options(s: Option<&str>) -> argument::Result<VideoBackendType> {
+ const VALID_VIDEO_BACKENDS: &[&str] = &[
+ #[cfg(feature = "libvda")]
+ "libvda",
+ ];
+
+ match s {
+ None => {
+ cfg_if::cfg_if! {
+ if #[cfg(feature = "libvda")] {
+ Ok(VideoBackendType::Libvda)
+ }
+ }
+ }
+ #[cfg(feature = "libvda")]
+ Some("libvda") => Ok(VideoBackendType::Libvda),
+ #[cfg(feature = "libvda")]
+ Some("libvda-vd") => Ok(VideoBackendType::LibvdaVd),
+ Some(s) => Err(argument::Error::InvalidValue {
+ value: s.to_owned(),
+ expected: format!("should be one of ({})", VALID_VIDEO_BACKENDS.join("|")),
+ }),
+ }
+}
+
+#[cfg(feature = "gpu")]
+fn parse_gpu_display_options(
+ s: Option<&str>,
+ gpu_params: &mut GpuParameters,
+) -> argument::Result<()> {
+ let mut display_w: Option<u32> = None;
+ let mut display_h: Option<u32> = None;
+
+ if let Some(s) = s {
+ let opts = s
+ .split(',')
+ .map(|frag| frag.split('='))
+ .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
+
+ for (k, v) in opts {
+ match k {
+ "width" => {
+ let width = v
+ .parse::<u32>()
+ .map_err(|_| argument::Error::InvalidValue {
+ value: v.to_string(),
+ expected: String::from("gpu parameter 'width' must be a valid integer"),
+ })?;
+ display_w = Some(width);
+ }
+ "height" => {
+ let height = v
+ .parse::<u32>()
+ .map_err(|_| argument::Error::InvalidValue {
+ value: v.to_string(),
+ expected: String::from(
+ "gpu parameter 'height' must be a valid integer",
+ ),
+ })?;
+ display_h = Some(height);
+ }
+ "" => {}
+ _ => {
+ return Err(argument::Error::UnknownArgument(format!(
+ "gpu-display parameter {}",
+ k
+ )));
+ }
+ }
+ }
+ }
+
+ if display_w.is_none() || display_h.is_none() {
+ return Err(argument::Error::InvalidValue {
+ value: s.unwrap_or("").to_string(),
+ expected: String::from("gpu-display must include both 'width' and 'height'"),
+ });
+ }
+
+ gpu_params.displays.push(GpuDisplayParameters {
+ width: display_w.unwrap(),
+ height: display_h.unwrap(),
+ });
+
+ Ok(())
+}
+
+#[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
+fn parse_gpu_render_server_options(s: Option<&str>) -> argument::Result<GpuRenderServerParameters> {
+ let mut path: Option<PathBuf> = None;
+ let mut cache_path = None;
+ let mut cache_size = None;
+
+ if let Some(s) = s {
+ let opts = s
+ .split(',')
+ .map(|frag| frag.split('='))
+ .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
+
+ for (k, v) in opts {
+ match k {
+ "path" => {
+ path =
+ Some(
+ PathBuf::from_str(v).map_err(|e| argument::Error::InvalidValue {
+ value: v.to_string(),
+ expected: e.to_string(),
+ })?,
+ )
+ }
+ "cache-path" => cache_path = Some(v.to_string()),
+ "cache-size" => cache_size = Some(v.to_string()),
+ "" => {}
+ _ => {
+ return Err(argument::Error::UnknownArgument(format!(
+ "gpu-render-server parameter {}",
+ k
+ )));
+ }
+ }
+ }
+ }
+
+ if let Some(p) = path {
+ Ok(GpuRenderServerParameters {
+ path: p,
+ cache_path,
+ cache_size,
+ })
+ } else {
+ Err(argument::Error::InvalidValue {
+ value: s.unwrap_or("").to_string(),
+ expected: String::from("gpu-render-server must include 'path'"),
+ })
+ }
}
#[cfg(feature = "audio")]
@@ -415,6 +638,7 @@ fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> {
argument::Error::Syntax(format!("invalid capture option: {}", e))
})?;
}
+ #[cfg(feature = "audio_cras")]
"client_type" => {
ac97_params
.set_client_type(v)
@@ -423,6 +647,15 @@ fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> {
expected: e.to_string(),
})?;
}
+ #[cfg(feature = "audio_cras")]
+ "socket_type" => {
+ ac97_params
+ .set_socket_type(v)
+ .map_err(|e| argument::Error::InvalidValue {
+ value: v.to_string(),
+ expected: e.to_string(),
+ })?;
+ }
#[cfg(any(target_os = "linux", target_os = "android"))]
"server" => {
ac97_params.vios_server_path =
@@ -464,91 +697,57 @@ fn parse_ac97_options(s: &str) -> argument::Result<Ac97Parameters> {
Ok(ac97_params)
}
-fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> {
- let mut serial_setting = SerialParameters {
- type_: SerialType::Sink,
- hardware: SerialHardware::Serial,
- path: None,
- input: None,
- num: 1,
- console: false,
- earlycon: false,
- stdin: false,
- };
-
- let opts = s
- .split(',')
- .map(|frag| frag.splitn(2, '='))
- .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
+enum MsrAction {
+ Invalid,
+ /// Read MSR value from host CPU0 regardless of current vcpu.
+ ReadFromCPU0,
+}
- for (k, v) in opts {
- match k {
- "hardware" => {
- serial_setting.hardware = v
- .parse::<SerialHardware>()
- .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
- }
- "type" => {
- serial_setting.type_ = v
- .parse::<SerialType>()
- .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
- }
- "num" => {
- let num = v.parse::<u8>().map_err(|e| {
- argument::Error::Syntax(format!("serial device number is not parsable: {}", e))
- })?;
- if num < 1 {
- return Err(argument::Error::InvalidValue {
- value: num.to_string(),
- expected: String::from("Serial port num must be at least 1"),
- });
- }
- serial_setting.num = num;
- }
- "console" => {
- serial_setting.console = v.parse::<bool>().map_err(|e| {
- argument::Error::Syntax(format!(
- "serial device console is not parseable: {}",
- e
- ))
- })?
- }
- "earlycon" => {
- serial_setting.earlycon = v.parse::<bool>().map_err(|e| {
- argument::Error::Syntax(format!(
- "serial device earlycon is not parseable: {}",
- e,
- ))
- })?
- }
- "stdin" => {
- serial_setting.stdin = v.parse::<bool>().map_err(|e| {
- argument::Error::Syntax(format!("serial device stdin is not parseable: {}", e))
- })?;
- if serial_setting.stdin && serial_setting.input.is_some() {
- return Err(argument::Error::TooManyArguments(
- "Cannot specify both stdin and input options".to_string(),
- ));
- }
- }
- "path" => serial_setting.path = Some(PathBuf::from(v)),
- "input" => {
- if serial_setting.stdin {
- return Err(argument::Error::TooManyArguments(
- "Cannot specify both stdin and input options".to_string(),
- ));
- }
- serial_setting.input = Some(PathBuf::from(v));
- }
- _ => {
- return Err(argument::Error::UnknownArgument(format!(
- "serial parameter {}",
- k
- )));
- }
+fn parse_userspace_msr_options(value: &str) -> argument::Result<u32> {
+ // TODO(b/215297064): Implement different type of operations, such
+ // as write or reading from the correct CPU.
+ let mut options = argument::parse_key_value_options("userspace-msr", value, ',');
+ let index: u32 = options
+ .next()
+ .ok_or(argument::Error::ExpectedValue(String::from(
+ "userspace-msr: expected index",
+ )))?
+ .key_numeric()?;
+ let mut msr_config = MsrAction::Invalid;
+ for opt in options {
+ match opt.key() {
+ "action" => match opt.value()? {
+ "r0" => msr_config = MsrAction::ReadFromCPU0,
+ _ => return Err(opt.invalid_value_err(String::from("bad action"))),
+ },
+ _ => return Err(opt.invalid_key_err()),
}
}
+ match msr_config {
+ MsrAction::ReadFromCPU0 => Ok(index),
+ _ => Err(argument::Error::UnknownArgument(
+ "userspace-msr action not specified".to_string(),
+ )),
+ }
+}
+
+fn parse_serial_options(s: &str) -> argument::Result<SerialParameters> {
+ let serial_setting: SerialParameters =
+ from_key_values(s).map_err(|e| argument::Error::ConfigParserError(e.to_string()))?;
+
+ if serial_setting.stdin && serial_setting.input.is_some() {
+ return Err(argument::Error::TooManyArguments(
+ "Cannot specify both stdin and input options".to_string(),
+ ));
+ }
+ if serial_setting.num < 1 {
+ return Err(argument::Error::InvalidValue {
+ value: serial_setting.num.to_string(),
+ expected: String::from("Serial port num must be at least 1"),
+ });
+ }
+
if serial_setting.hardware == SerialHardware::Serial && serial_setting.num > 4 {
return Err(argument::Error::InvalidValue {
value: serial_setting.num.to_string(),
@@ -702,31 +901,32 @@ fn parse_direct_io_options(s: Option<&str>) -> argument::Result<DirectIoOption>
expected: String::from("the path does not exist"),
});
};
- let ranges: argument::Result<Vec<(u64, u64)>> = parts[1]
+ let ranges: argument::Result<Vec<BusRange>> = parts[1]
.split(',')
.map(|frag| frag.split('-'))
.map(|mut range| {
let base = range
.next()
- .map(|v| v.parse::<u64>())
+ .map(|v| parse_hex_or_decimal(v))
.map_or(Ok(None), |r| r.map(Some));
let last = range
.next()
- .map(|v| v.parse::<u64>())
+ .map(|v| parse_hex_or_decimal(v))
.map_or(Ok(None), |r| r.map(Some));
(base, last)
})
.map(|range| match range {
- (Ok(Some(base)), Ok(None)) => Ok((base, 1)),
- (Ok(Some(base)), Ok(Some(last))) => {
- Ok((base, last.saturating_sub(base).saturating_add(1)))
- }
- (Err(e), _) => Err(argument::Error::InvalidValue {
- value: e.to_string(),
+ (Ok(Some(base)), Ok(None)) => Ok(BusRange { base, len: 1 }),
+ (Ok(Some(base)), Ok(Some(last))) => Ok(BusRange {
+ base,
+ len: last.saturating_sub(base).saturating_add(1),
+ }),
+ (Err(_), _) => Err(argument::Error::InvalidValue {
+ value: s.to_owned(),
expected: String::from("invalid base range value"),
}),
- (_, Err(e)) => Err(argument::Error::InvalidValue {
- value: e.to_string(),
+ (_, Err(_)) => Err(argument::Error::InvalidValue {
+ value: s.to_owned(),
expected: String::from("invalid last range value"),
}),
_ => Err(argument::Error::InvalidValue {
@@ -741,6 +941,110 @@ fn parse_direct_io_options(s: Option<&str>) -> argument::Result<DirectIoOption>
})
}
+fn parse_stub_pci_parameters(s: Option<&str>) -> argument::Result<StubPciParameters> {
+ let s = s.ok_or(argument::Error::ExpectedValue(String::from(
+ "stub-pci-device configuration expected",
+ )))?;
+
+ let mut options = argument::parse_key_value_options("stub-pci-device", s, ',');
+ let addr = options
+ .next()
+ .ok_or(argument::Error::ExpectedValue(String::from(
+ "stub-pci-device: expected device address",
+ )))?
+ .key();
+ let mut params = StubPciParameters {
+ address: PciAddress::from_str(addr).map_err(|e| argument::Error::InvalidValue {
+ value: addr.to_owned(),
+ expected: format!("stub-pci-device: expected PCI address: {}", e),
+ })?,
+ vendor_id: 0,
+ device_id: 0,
+ class: PciClassCode::Other,
+ subclass: 0,
+ programming_interface: 0,
+ subsystem_device_id: 0,
+ subsystem_vendor_id: 0,
+ revision_id: 0,
+ };
+ for opt in options {
+ match opt.key() {
+ "vendor" => params.vendor_id = opt.parse_numeric::<u16>()?,
+ "device" => params.device_id = opt.parse_numeric::<u16>()?,
+ "class" => {
+ let class = opt.parse_numeric::<u32>()?;
+ params.class = PciClassCode::try_from((class >> 16) as u8)
+ .map_err(|_| opt.invalid_value_err(String::from("Unknown class code")))?;
+ params.subclass = (class >> 8) as u8;
+ params.programming_interface = class as u8;
+ }
+ "multifunction" => {} // Ignore but allow the multifunction option for compatibility.
+ "subsystem_vendor" => params.subsystem_vendor_id = opt.parse_numeric::<u16>()?,
+ "subsystem_device" => params.subsystem_device_id = opt.parse_numeric::<u16>()?,
+ "revision" => params.revision_id = opt.parse_numeric::<u8>()?,
+ _ => return Err(opt.invalid_key_err()),
+ }
+ }
+
+ Ok(params)
+}
+
+fn parse_file_backed_mapping(s: Option<&str>) -> argument::Result<FileBackedMappingParameters> {
+ let s = s.ok_or(argument::Error::ExpectedValue(String::from(
+ "file-backed-mapping: memory mapping option value required",
+ )))?;
+
+ let mut address = None;
+ let mut size = None;
+ let mut path = None;
+ let mut offset = None;
+ let mut writable = false;
+ let mut sync = false;
+ let mut align = false;
+ for opt in argument::parse_key_value_options("file-backed-mapping", s, ',') {
+ match opt.key() {
+ "addr" => address = Some(opt.parse_numeric::<u64>()?),
+ "size" => size = Some(opt.parse_numeric::<u64>()?),
+ "path" => path = Some(PathBuf::from(opt.value()?)),
+ "offset" => offset = Some(opt.parse_numeric::<u64>()?),
+ "ro" => writable = !opt.parse_or::<bool>(true)?,
+ "rw" => writable = opt.parse_or::<bool>(true)?,
+ "sync" => sync = opt.parse_or::<bool>(true)?,
+ "align" => align = opt.parse_or::<bool>(true)?,
+ _ => return Err(opt.invalid_key_err()),
+ }
+ }
+
+ let (address, path, size) = match (address, path, size) {
+ (Some(a), Some(p), Some(s)) => (a, p, s),
+ _ => {
+ return Err(argument::Error::ExpectedValue(String::from(
+ "file-backed-mapping: address, size, and path parameters are required",
+ )))
+ }
+ };
+
+ let pagesize_mask = pagesize() as u64 - 1;
+ let aligned_address = address & !pagesize_mask;
+ let aligned_size = ((address + size + pagesize_mask) & !pagesize_mask) - aligned_address;
+
+ if !align && (aligned_address != address || aligned_size != size) {
+ return Err(argument::Error::InvalidValue {
+ value: s.to_owned(),
+ expected: String::from("addr and size parameters must be page size aligned"),
+ });
+ }
+
+ Ok(FileBackedMappingParameters {
+ address: aligned_address,
+ size: aligned_size,
+ path,
+ offset: offset.unwrap_or(0),
+ writable,
+ sync,
+ })
+}
+
fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::Result<()> {
match name {
"" => {
@@ -770,7 +1074,32 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
cfg.kvm_device_path = kvm_device_path;
}
+ "vhost-vsock-fd" => {
+ if cfg.vhost_vsock_device.is_some() {
+ return Err(argument::Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from("A vhost-vsock device was already specified"),
+ });
+ }
+ cfg.vhost_vsock_device = Some(VhostVsockDeviceParameter::Fd(
+ value
+ .unwrap()
+ .parse()
+ .map_err(|_| argument::Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from(
+ "this value for `vhost-vsock-fd` needs to be integer",
+ ),
+ })?,
+ ));
+ }
"vhost-vsock-device" => {
+ if cfg.vhost_vsock_device.is_some() {
+ return Err(argument::Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from("A vhost-vsock device was already specified"),
+ });
+ }
let vhost_vsock_device_path = PathBuf::from(value.unwrap());
if !vhost_vsock_device_path.exists() {
return Err(argument::Error::InvalidValue {
@@ -779,7 +1108,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
});
}
- cfg.vhost_vsock_device_path = vhost_vsock_device_path;
+ cfg.vhost_vsock_device = Some(VhostVsockDeviceParameter::Path(vhost_vsock_device_path));
}
"vhost-net-device" => {
let vhost_net_device_path = PathBuf::from(value.unwrap());
@@ -838,6 +1167,35 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
}
cfg.vcpu_affinity = Some(parse_cpu_affinity(value.unwrap())?);
}
+ "cpu-cluster" => {
+ cfg.cpu_clusters.push(parse_cpu_set(value.unwrap())?);
+ }
+ "cpu-capacity" => {
+ parse_cpu_capacity(value.unwrap(), &mut cfg.cpu_capacity)?;
+ }
+ "per-vm-core-scheduling" => {
+ cfg.per_vm_core_scheduling = true;
+ }
+ "vcpu-cgroup-path" => {
+ let vcpu_cgroup_path = PathBuf::from(value.unwrap());
+ if !vcpu_cgroup_path.exists() {
+ return Err(argument::Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from("This vcpu_cgroup_path path does not exist"),
+ });
+ }
+
+ cfg.vcpu_cgroup_path = Some(vcpu_cgroup_path);
+ }
+ #[cfg(feature = "audio_cras")]
+ "cras-snd" => {
+ cfg.cras_snds.push(
+ value
+ .unwrap()
+ .parse()
+ .map_err(|e: CrasSndError| argument::Error::Syntax(e.to_string()))?,
+ );
+ }
"no-smt" => {
cfg.no_smt = true;
}
@@ -849,6 +1207,9 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
}
cfg.rt_cpus = parse_cpu_set(value.unwrap())?;
}
+ "delay-rt" => {
+ cfg.delay_rt = true;
+ }
"mem" => {
if cfg.memory.is_some() {
return Err(argument::Error::TooManyArguments(
@@ -866,6 +1227,24 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
})?,
)
}
+ #[cfg(target_arch = "aarch64")]
+ "swiotlb" => {
+ if cfg.swiotlb.is_some() {
+ return Err(argument::Error::TooManyArguments(
+ "`swiotlb` already given".to_owned(),
+ ));
+ }
+ cfg.swiotlb =
+ Some(
+ value
+ .unwrap()
+ .parse()
+ .map_err(|_| argument::Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from("this value for `swiotlb` needs to be integer"),
+ })?,
+ )
+ }
"hugepages" => {
cfg.hugepages = true;
}
@@ -881,6 +1260,11 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
}
cfg.ac97_parameters.push(ac97_params);
}
+ #[cfg(feature = "audio")]
+ "sound" => {
+ let client_path = PathBuf::from(value.unwrap());
+ cfg.sound = Some(client_path);
+ }
"serial" => {
let serial_params = parse_serial_options(value.unwrap())?;
let num = serial_params.num;
@@ -945,21 +1329,21 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
cfg.syslog_tag = Some(value.unwrap().to_owned());
}
"root" | "rwroot" | "disk" | "rwdisk" => {
- let param = value.unwrap();
- let mut components = param.split(',');
- let read_only = !name.starts_with("rw");
- let disk_path =
- PathBuf::from(
- components
- .next()
- .ok_or_else(|| argument::Error::InvalidValue {
- value: param.to_owned(),
- expected: String::from("missing disk path"),
- })?,
- );
+ let value = value.ok_or(argument::Error::ExpectedArgument(
+ "path to the disk image is missing".to_owned(),
+ ))?;
+ let mut params: DiskOption = from_key_values(value).map_err(|e| {
+ argument::Error::Syntax(format!("while parsing \"{}\" parameter: {}", name, e))
+ })?;
+
+ if !name.starts_with("rw") {
+ params.read_only = true;
+ }
+
+ let disk_path = &params.path;
if !disk_path.exists() {
return Err(argument::Error::InvalidValue {
- value: param.to_owned(),
+ value: disk_path.to_string_lossy().into_owned(),
expected: String::from("this disk path does not exist"),
});
}
@@ -972,71 +1356,11 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
cfg.params.push(format!(
"root=/dev/vd{} {}",
char::from(b'a' + cfg.disks.len() as u8),
- if read_only { "ro" } else { "rw" }
+ if params.read_only { "ro" } else { "rw" }
));
}
- let mut disk = DiskOption {
- path: disk_path,
- read_only,
- sparse: true,
- block_size: 512,
- id: None,
- };
-
- for opt in components {
- let mut o = opt.splitn(2, '=');
- let kind = o.next().ok_or_else(|| argument::Error::InvalidValue {
- value: opt.to_owned(),
- expected: String::from("disk options must not be empty"),
- })?;
- let value = o.next().ok_or_else(|| argument::Error::InvalidValue {
- value: opt.to_owned(),
- expected: String::from("disk options must be of the form `kind=value`"),
- })?;
-
- match kind {
- "sparse" => {
- let sparse = value.parse().map_err(|_| argument::Error::InvalidValue {
- value: value.to_owned(),
- expected: String::from("`sparse` must be a boolean"),
- })?;
- disk.sparse = sparse;
- }
- "block_size" => {
- let block_size =
- value.parse().map_err(|_| argument::Error::InvalidValue {
- value: value.to_owned(),
- expected: String::from("`block_size` must be an integer"),
- })?;
- disk.block_size = block_size;
- }
- "id" => {
- if value.len() > DISK_ID_LEN {
- return Err(argument::Error::InvalidValue {
- value: value.to_owned(),
- expected: format!(
- "`id` must be {} or fewer characters",
- DISK_ID_LEN
- ),
- });
- }
- let mut id = [0u8; DISK_ID_LEN];
- // Slicing id to value's length will never panic
- // because we checked that value will fit into id above.
- id[..value.len()].copy_from_slice(value.as_bytes());
- disk.id = Some(id);
- }
- _ => {
- return Err(argument::Error::InvalidValue {
- value: kind.to_owned(),
- expected: String::from("unrecognized disk option"),
- });
- }
- }
- }
-
- cfg.disks.push(disk);
+ cfg.disks.push(params);
}
"pmem-device" | "rw-pmem-device" => {
let disk_path = PathBuf::from(value.unwrap());
@@ -1051,6 +1375,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
path: disk_path,
read_only: !name.starts_with("rw"),
sparse: false,
+ o_direct: false,
block_size: base::pagesize() as u32,
id: None,
});
@@ -1213,7 +1538,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
cfg.wayland_socket_paths.insert(name.to_string(), path);
}
#[cfg(feature = "wl-dmabuf")]
- "wayland-dmabuf" => cfg.wayland_dmabuf = true,
+ "wayland-dmabuf" => {}
"x-display" => {
if cfg.x_display.is_some() {
return Err(argument::Error::TooManyArguments(
@@ -1246,8 +1571,23 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
}
cfg.socket_path = Some(socket_path);
}
+ "balloon-control" => {
+ if cfg.balloon_control.is_some() {
+ return Err(argument::Error::TooManyArguments(
+ "`balloon-control` already given".to_owned(),
+ ));
+ }
+ let path = PathBuf::from(value.unwrap());
+ if path.is_dir() || !path.exists() {
+ return Err(argument::Error::InvalidValue {
+ value: path.to_string_lossy().into_owned(),
+ expected: String::from("path is directory or missing"),
+ });
+ }
+ cfg.balloon_control = Some(path);
+ }
"disable-sandbox" => {
- cfg.sandbox = false;
+ cfg.jail_config = None;
}
"cid" => {
if cfg.cid.is_some() {
@@ -1274,6 +1614,9 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
// (default: "0 <current euid> 1")
// * gidmap=GIDMAP - a gid map in the same format as uidmap
// (default: "0 <current egid> 1")
+ // * privileged_quota_uids=UIDS - Space-separated list of privileged uid values. When
+ // performing quota-related operations, these UIDs are treated as if they have
+ // CAP_FOWNER.
// * timeout=TIMEOUT - a timeout value in seconds, which indicates how long attributes
// and directory contents should be considered valid (default: 5)
// * cache=CACHE - one of "never", "always", or "auto" (default: auto)
@@ -1330,6 +1673,11 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
}
"uidmap" => shared_dir.uid_map = value.into(),
"gidmap" => shared_dir.gid_map = value.into(),
+ #[cfg(feature = "chromeos")]
+ "privileged_quota_uids" => {
+ shared_dir.fs_cfg.privileged_quota_uids =
+ value.split(' ').map(|s| s.parse().unwrap()).collect();
+ }
"timeout" => {
let seconds = value.parse().map_err(|_| argument::Error::InvalidValue {
value: value.to_owned(),
@@ -1376,6 +1724,21 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
shared_dir.fs_cfg.ascii_casefold = ascii_casefold;
shared_dir.p9_cfg.ascii_casefold = ascii_casefold;
}
+ "dax" => {
+ let use_dax = value.parse().map_err(|_| argument::Error::InvalidValue {
+ value: value.to_owned(),
+ expected: String::from("`dax` must be a boolean"),
+ })?;
+ shared_dir.fs_cfg.use_dax = use_dax;
+ }
+ "posix_acl" => {
+ let posix_acl =
+ value.parse().map_err(|_| argument::Error::InvalidValue {
+ value: value.to_owned(),
+ expected: String::from("`posix_acl` must be a boolean"),
+ })?;
+ shared_dir.fs_cfg.posix_acl = posix_acl;
+ }
_ => {
return Err(argument::Error::InvalidValue {
value: kind.to_owned(),
@@ -1387,8 +1750,10 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
cfg.shared_dirs.push(shared_dir);
}
"seccomp-policy-dir" => {
- // `value` is Some because we are in this match so it's safe to unwrap.
- cfg.seccomp_policy_dir = PathBuf::from(value.unwrap());
+ if let Some(jail_config) = &mut cfg.jail_config {
+ // `value` is Some because we are in this match so it's safe to unwrap.
+ jail_config.seccomp_policy_dir = PathBuf::from(value.unwrap());
+ }
}
"seccomp-log-failures" => {
// A side-effect of this flag is to force the use of .policy files
@@ -1408,7 +1773,9 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
// or 2) do not use this command-line parameter and instead
// temporarily change the build by passing "log" rather than
// "trap" as the "--default-action" to compile_seccomp_policy.py.
- cfg.seccomp_log_failures = true;
+ if let Some(jail_config) = &mut cfg.jail_config {
+ jail_config.seccomp_log_failures = true;
+ }
}
"plugin" => {
if cfg.executable_path.is_some() {
@@ -1441,7 +1808,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
let reader = BufReader::new(file);
for l in reader.lines() {
let line = l.unwrap();
- let trimmed_line = line.splitn(2, '#').next().unwrap().trim();
+ let trimmed_line = line.split_once('#').map_or(&*line, |x| x.0).trim();
if !trimmed_line.is_empty() {
let mount = parse_plugin_mount_option(trimmed_line)?;
cfg.plugin_mounts.push(mount);
@@ -1460,7 +1827,7 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
let reader = BufReader::new(file);
for l in reader.lines() {
let line = l.unwrap();
- let trimmed_line = line.splitn(2, '#').next().unwrap().trim();
+ let trimmed_line = line.split_once('#').map_or(&*line, |x| x.0).trim();
if !trimmed_line.is_empty() {
let map = parse_plugin_gid_map_option(trimmed_line)?;
cfg.plugin_gid_maps.push(map);
@@ -1481,20 +1848,27 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
})?,
);
}
+ "tap-name" => {
+ cfg.tap_name.push(value.unwrap().to_owned());
+ }
#[cfg(feature = "gpu")]
"gpu" => {
- let params = parse_gpu_options(value)?;
- cfg.gpu_parameters = Some(params);
+ let gpu_parameters = cfg.gpu_parameters.get_or_insert_with(Default::default);
+ parse_gpu_options(value, gpu_parameters)?;
+ }
+ #[cfg(feature = "gpu")]
+ "gpu-display" => {
+ let gpu_parameters = cfg.gpu_parameters.get_or_insert_with(Default::default);
+ parse_gpu_display_options(value, gpu_parameters)?;
+ }
+ #[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
+ "gpu-render-server" => {
+ cfg.gpu_render_server_parameters = Some(parse_gpu_render_server_options(value)?);
}
"software-tpm" => {
cfg.software_tpm = true;
}
"single-touch" => {
- if cfg.virtio_single_touch.is_some() {
- return Err(argument::Error::TooManyArguments(
- "`single-touch` already given".to_owned(),
- ));
- }
let mut it = value.unwrap().split(':');
let mut single_touch_spec =
@@ -1505,14 +1879,9 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
if let Some(height) = it.next() {
single_touch_spec.set_height(height.trim().parse().unwrap());
}
- cfg.virtio_single_touch = Some(single_touch_spec);
+ cfg.virtio_single_touch.push(single_touch_spec);
}
"multi-touch" => {
- if cfg.virtio_multi_touch.is_some() {
- return Err(argument::Error::TooManyArguments(
- "`multi-touch` already given".to_owned(),
- ));
- }
let mut it = value.unwrap().split(':');
let mut multi_touch_spec =
@@ -1523,14 +1892,9 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
if let Some(height) = it.next() {
multi_touch_spec.set_height(height.trim().parse().unwrap());
}
- cfg.virtio_multi_touch = Some(multi_touch_spec);
+ cfg.virtio_multi_touch.push(multi_touch_spec);
}
"trackpad" => {
- if cfg.virtio_trackpad.is_some() {
- return Err(argument::Error::TooManyArguments(
- "`trackpad` already given".to_owned(),
- ));
- }
let mut it = value.unwrap().split(':');
let mut trackpad_spec =
@@ -1541,31 +1905,19 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
if let Some(height) = it.next() {
trackpad_spec.set_height(height.trim().parse().unwrap());
}
- cfg.virtio_trackpad = Some(trackpad_spec);
+ cfg.virtio_trackpad.push(trackpad_spec);
}
"mouse" => {
- if cfg.virtio_mouse.is_some() {
- return Err(argument::Error::TooManyArguments(
- "`mouse` already given".to_owned(),
- ));
- }
- cfg.virtio_mouse = Some(PathBuf::from(value.unwrap().to_owned()));
+ cfg.virtio_mice
+ .push(PathBuf::from(value.unwrap().to_owned()));
}
"keyboard" => {
- if cfg.virtio_keyboard.is_some() {
- return Err(argument::Error::TooManyArguments(
- "`keyboard` already given".to_owned(),
- ));
- }
- cfg.virtio_keyboard = Some(PathBuf::from(value.unwrap().to_owned()));
+ cfg.virtio_keyboard
+ .push(PathBuf::from(value.unwrap().to_owned()));
}
"switches" => {
- if cfg.virtio_switches.is_some() {
- return Err(argument::Error::TooManyArguments(
- "`switches` already given".to_owned(),
- ));
- }
- cfg.virtio_switches = Some(PathBuf::from(value.unwrap().to_owned()));
+ cfg.virtio_switches
+ .push(PathBuf::from(value.unwrap().to_owned()));
}
"evdev" => {
let dev_path = PathBuf::from(value.unwrap());
@@ -1592,28 +1944,21 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
}
cfg.executable_path = Some(Executable::Bios(PathBuf::from(value.unwrap().to_owned())));
}
- "vfio" => {
- let vfio_path = PathBuf::from(value.unwrap());
- if !vfio_path.exists() {
- return Err(argument::Error::InvalidValue {
- value: value.unwrap().to_owned(),
- expected: String::from("the vfio path does not exist"),
- });
- }
- if !vfio_path.is_dir() {
- return Err(argument::Error::InvalidValue {
- value: value.unwrap().to_owned(),
- expected: String::from("the vfio path should be directory"),
- });
- }
-
- cfg.vfio.push(vfio_path);
+ "vfio" | "vfio-platform" => {
+ let vfio_type = name.parse().unwrap();
+ let vfio_dev = VfioCommand::new(vfio_type, value.unwrap())?;
+ cfg.vfio.push(vfio_dev);
+ }
+ "virtio-iommu" => {
+ cfg.virtio_iommu = true;
}
+ #[cfg(feature = "video-decoder")]
"video-decoder" => {
- cfg.video_dec = true;
+ cfg.video_dec = Some(parse_video_options(value)?);
}
+ #[cfg(feature = "video-encoder")]
"video-encoder" => {
- cfg.video_enc = true;
+ cfg.video_enc = Some(parse_video_options(value)?);
}
"acpi-table" => {
let acpi_table = PathBuf::from(value.unwrap());
@@ -1634,7 +1979,19 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
}
"protected-vm" => {
cfg.protected_vm = ProtectionType::Protected;
- cfg.params.push("swiotlb=force".to_string());
+ // Balloon and USB devices only work for unprotected VMs.
+ cfg.balloon = false;
+ cfg.usb = false;
+ // Protected VMs can't trust the RNG device, so don't provide it.
+ cfg.rng = false;
+ }
+ "protected-vm-without-firmware" => {
+ cfg.protected_vm = ProtectionType::ProtectedWithoutFirmware;
+ // Balloon and USB devices only work for unprotected VMs.
+ cfg.balloon = false;
+ cfg.usb = false;
+ // Protected VMs can't trust the RNG device, so don't provide it.
+ cfg.rng = false;
}
"battery" => {
let params = parse_battery_options(value)?;
@@ -1651,6 +2008,15 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
})?;
cfg.gdb = Some(port);
}
+ "no-balloon" => {
+ cfg.balloon = false;
+ }
+ "no-rng" => {
+ cfg.rng = false;
+ }
+ "no-usb" => {
+ cfg.usb = false;
+ }
"balloon_bias_mib" => {
cfg.balloon_bias =
value
@@ -1666,9 +2032,44 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
"vhost-user-blk" => cfg.vhost_user_blk.push(VhostUserOption {
socket: PathBuf::from(value.unwrap()),
}),
+ "vhost-user-console" => cfg.vhost_user_console.push(VhostUserOption {
+ socket: PathBuf::from(value.unwrap()),
+ }),
+ "vhost-user-gpu" => cfg.vhost_user_gpu.push(VhostUserOption {
+ socket: PathBuf::from(value.unwrap()),
+ }),
+ "vhost-user-mac80211-hwsim" => {
+ cfg.vhost_user_mac80211_hwsim = Some(VhostUserOption {
+ socket: PathBuf::from(value.unwrap()),
+ });
+ }
"vhost-user-net" => cfg.vhost_user_net.push(VhostUserOption {
socket: PathBuf::from(value.unwrap()),
}),
+ #[cfg(feature = "audio")]
+ "vhost-user-snd" => cfg.vhost_user_snd.push(VhostUserOption {
+ socket: PathBuf::from(value.unwrap()),
+ }),
+ "vhost-user-vsock" => cfg.vhost_user_vsock.push(VhostUserOption {
+ socket: PathBuf::from(value.unwrap()),
+ }),
+ "vhost-user-wl" => {
+ let mut components = value.unwrap().splitn(2, ":");
+ let socket = components.next().map(PathBuf::from).ok_or_else(|| {
+ argument::Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from("missing socket path"),
+ }
+ })?;
+ let vm_tube = components.next().map(PathBuf::from).ok_or_else(|| {
+ argument::Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from("missing vm tube path"),
+ }
+ })?;
+ cfg.vhost_user_wl
+ .push(VhostUserWlOption { socket, vm_tube });
+ }
"vhost-user-fs" => {
// (socket:tag)
let param = value.unwrap();
@@ -1701,6 +2102,15 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
cfg.direct_pmio = Some(parse_direct_io_options(value)?);
}
#[cfg(feature = "direct")]
+ "direct-mmio" => {
+ if cfg.direct_mmio.is_some() {
+ return Err(argument::Error::TooManyArguments(
+ "`direct_mmio` already given".to_owned(),
+ ));
+ }
+ cfg.direct_mmio = Some(parse_direct_io_options(value)?);
+ }
+ #[cfg(feature = "direct")]
"direct-level-irq" => {
cfg.direct_level_irq
.push(
@@ -1730,6 +2140,32 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
})?,
);
}
+ #[cfg(feature = "direct")]
+ "direct-wake-irq" => {
+ cfg.direct_wake_irq
+ .push(
+ value
+ .unwrap()
+ .parse()
+ .map_err(|_| argument::Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from(
+ "this value for `direct-wake-irq` must be an unsigned integer",
+ ),
+ })?,
+ );
+ }
+ #[cfg(feature = "direct")]
+ "direct-gpe" => {
+ cfg.direct_gpe.push(value.unwrap().parse().map_err(|_| {
+ argument::Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from(
+ "this value for `direct-gpe` must be an unsigned integer",
+ ),
+ }
+ })?);
+ }
"dmi" => {
if cfg.dmi_path.is_some() {
return Err(argument::Error::TooManyArguments(
@@ -1751,6 +2187,248 @@ fn set_argument(cfg: &mut Config, name: &str, value: Option<&str>) -> argument::
}
cfg.dmi_path = Some(dmi_path);
}
+ "no-legacy" => {
+ cfg.no_legacy = true;
+ }
+ "userspace-msr" => {
+ let index = parse_userspace_msr_options(value.unwrap())?;
+ cfg.userspace_msr.insert(index);
+ }
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ "host-cpu-topology" => {
+ cfg.host_cpu_topology = true;
+ }
+ "privileged-vm" => {
+ cfg.privileged_vm = true;
+ }
+ "stub-pci-device" => {
+ cfg.stub_pci_devices.push(parse_stub_pci_parameters(value)?);
+ }
+ "vvu-proxy" => {
+ let opts: Vec<_> = value.unwrap().splitn(2, ',').collect();
+ let socket = PathBuf::from(opts[0]);
+ let mut vvu_opt = VvuOption {
+ socket,
+ addr: None,
+ uuid: Default::default(),
+ };
+
+ if let Some(kvs) = opts.get(1) {
+ for kv in argument::parse_key_value_options("vvu-proxy", kvs, ',') {
+ match kv.key() {
+ "addr" => {
+ let pci_address = kv.value()?;
+ if vvu_opt.addr.is_some() {
+ return Err(argument::Error::TooManyArguments(
+ "`addr` already given".to_owned(),
+ ));
+ }
+
+ vvu_opt.addr =
+ Some(PciAddress::from_str(pci_address).map_err(|e| {
+ argument::Error::InvalidValue {
+ value: pci_address.to_string(),
+ expected: format!("vvu-proxy PCI address: {}", e),
+ }
+ })?);
+ }
+ "uuid" => {
+ let value = kv.value()?;
+ if vvu_opt.uuid.is_some() {
+ return Err(argument::Error::TooManyArguments(
+ "`uuid` already given".to_owned(),
+ ));
+ }
+ let uuid = Uuid::parse_str(value).map_err(|e| {
+ argument::Error::InvalidValue {
+ value: value.to_string(),
+ expected: format!("invalid UUID is given for vvu-proxy: {}", e),
+ }
+ })?;
+ vvu_opt.uuid = Some(uuid);
+ }
+ _ => {
+ kv.invalid_key_err();
+ }
+ }
+ }
+ }
+
+ cfg.vvu_proxy.push(vvu_opt);
+ }
+ "coiommu" => {
+ let mut params: devices::CoIommuParameters = Default::default();
+ if let Some(v) = value {
+ let opts = v
+ .split(',')
+ .map(|frag| frag.splitn(2, '='))
+ .map(|mut kv| (kv.next().unwrap_or(""), kv.next().unwrap_or("")));
+
+ for (k, v) in opts {
+ match k {
+ "unpin_policy" => {
+ params.unpin_policy = v
+ .parse::<devices::CoIommuUnpinPolicy>()
+ .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
+ }
+ "unpin_interval" => {
+ params.unpin_interval =
+ Duration::from_secs(v.parse::<u64>().map_err(|e| {
+ argument::Error::UnknownArgument(format!("{}", e))
+ })?)
+ }
+ "unpin_limit" => {
+ let limit = v
+ .parse::<u64>()
+ .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?;
+
+ if limit == 0 {
+ return Err(argument::Error::InvalidValue {
+ value: v.to_owned(),
+ expected: String::from("Please use non-zero unpin_limit value"),
+ });
+ }
+
+ params.unpin_limit = Some(limit)
+ }
+ "unpin_gen_threshold" => {
+ params.unpin_gen_threshold = v
+ .parse::<u64>()
+ .map_err(|e| argument::Error::UnknownArgument(format!("{}", e)))?
+ }
+ _ => {
+ return Err(argument::Error::UnknownArgument(format!(
+ "coiommu parameter {}",
+ k
+ )));
+ }
+ }
+ }
+ }
+
+ if cfg.coiommu_param.is_some() {
+ return Err(argument::Error::TooManyArguments(
+ "coiommu param already given".to_owned(),
+ ));
+ }
+ cfg.coiommu_param = Some(params);
+ }
+ "file-backed-mapping" => {
+ cfg.file_backed_mappings
+ .push(parse_file_backed_mapping(value)?);
+ }
+ "init-mem" => {
+ if cfg.init_memory.is_some() {
+ return Err(argument::Error::TooManyArguments(
+ "`init-mem` already given".to_owned(),
+ ));
+ }
+ cfg.init_memory =
+ Some(
+ value
+ .unwrap()
+ .parse()
+ .map_err(|_| argument::Error::InvalidValue {
+ value: value.unwrap().to_owned(),
+ expected: String::from("this value for `init-mem` needs to be integer"),
+ })?,
+ )
+ }
+ #[cfg(feature = "direct")]
+ "pcie-root-port" => {
+ let opts: Vec<_> = value.unwrap().split(',').collect();
+ if opts.len() > 2 {
+ return Err(argument::Error::TooManyArguments(
+ "pcie-root-port has maxmimum two arguments".to_owned(),
+ ));
+ }
+ let pcie_path = PathBuf::from(opts[0]);
+ if !pcie_path.exists() {
+ return Err(argument::Error::InvalidValue {
+ value: opts[0].to_owned(),
+ expected: String::from("the pcie root port path does not exist"),
+ });
+ }
+ if !pcie_path.is_dir() {
+ return Err(argument::Error::InvalidValue {
+ value: opts[0].to_owned(),
+ expected: String::from("the pcie root port path should be directory"),
+ });
+ }
+
+ let hp_gpe = if opts.len() == 2 {
+ let gpes: Vec<&str> = opts[1].split('=').collect();
+ if gpes.len() != 2 || gpes[0] != "hp_gpe" {
+ return Err(argument::Error::InvalidValue {
+ value: opts[1].to_owned(),
+ expected: String::from("it should be hp_gpe=Num"),
+ });
+ }
+ match gpes[1].parse::<u32>() {
+ Ok(gpe) => Some(gpe),
+ Err(_) => {
+ return Err(argument::Error::InvalidValue {
+ value: gpes[1].to_owned(),
+ expected: String::from("host hp gpe must be a non-negative integer"),
+ });
+ }
+ }
+ } else {
+ None
+ };
+
+ cfg.pcie_rp.push(HostPcieRootPortParameters {
+ host_path: pcie_path,
+ hp_gpe,
+ });
+ }
+ "pivot-root" => {
+ if let Some(jail_config) = &mut cfg.jail_config {
+ jail_config.pivot_root = PathBuf::from(value.unwrap());
+ }
+ }
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ "s2idle" => {
+ cfg.force_s2idle = true;
+ }
+ "strict-balloon" => {
+ cfg.strict_balloon = true;
+ }
+ #[cfg(feature = "direct")]
+ "mmio-address-range" => {
+ let ranges: argument::Result<Vec<RangeInclusive<u64>>> = value
+ .unwrap()
+ .split(",")
+ .map(|s| {
+ let r: Vec<&str> = s.split("-").collect();
+ if r.len() != 2 {
+ return Err(argument::Error::InvalidValue {
+ value: s.to_string(),
+ expected: String::from("invalid range"),
+ });
+ }
+ let parse = |s: &str| -> argument::Result<u64> {
+ match parse_hex_or_decimal(s) {
+ Ok(v) => Ok(v),
+ Err(_) => {
+ return Err(argument::Error::InvalidValue {
+ value: s.to_owned(),
+ expected: String::from("expected u64 value"),
+ });
+ }
+ }
+ };
+ Ok(RangeInclusive::new(parse(r[0])?, parse(r[1])?))
+ })
+ .collect();
+ cfg.mmio_address_ranges = ranges?;
+ }
+ #[cfg(target_os = "android")]
+ "task-profiles" => {
+ for name in value.unwrap().split(',') {
+ cfg.task_profiles.push(name.to_owned());
+ }
+ }
"help" => return Err(argument::Error::PrintHelp),
_ => unreachable!(),
}
@@ -1785,32 +2463,104 @@ fn validate_arguments(cfg: &mut Config) -> std::result::Result<(), argument::Err
}
#[cfg(feature = "gpu")]
{
- if let Some(gpu_parameters) = cfg.gpu_parameters.as_ref() {
- let (width, height) = (gpu_parameters.display_width, gpu_parameters.display_height);
- if let Some(virtio_multi_touch) = cfg.virtio_multi_touch.as_mut() {
+ if let Some(gpu_parameters) = cfg.gpu_parameters.as_mut() {
+ if gpu_parameters.displays.is_empty() {
+ gpu_parameters.displays.push(GpuDisplayParameters {
+ width: DEFAULT_DISPLAY_WIDTH,
+ height: DEFAULT_DISPLAY_HEIGHT,
+ });
+ }
+
+ let width = gpu_parameters.displays[0].width;
+ let height = gpu_parameters.displays[0].height;
+
+ if let Some(virtio_multi_touch) = cfg.virtio_multi_touch.first_mut() {
virtio_multi_touch.set_default_size(width, height);
}
- if let Some(virtio_single_touch) = cfg.virtio_single_touch.as_mut() {
+ if let Some(virtio_single_touch) = cfg.virtio_single_touch.first_mut() {
virtio_single_touch.set_default_size(width, height);
}
}
}
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
- if cfg.gdb.is_some() {
- if cfg.vcpu_count.unwrap_or(1) != 1 {
+ if cfg.gdb.is_some() && cfg.vcpu_count.unwrap_or(1) != 1 {
+ return Err(argument::Error::ExpectedArgument(
+ "`gdb` requires the number of vCPU to be 1".to_owned(),
+ ));
+ }
+ if cfg.host_cpu_topology {
+ if cfg.no_smt {
return Err(argument::Error::ExpectedArgument(
- "`gdb` requires the number of vCPU to be 1".to_owned(),
+ "`host-cpu-topology` cannot be set at the same time as `no_smt`, since \
+ the smt of the Guest is the same as that of the Host when \
+ `host-cpu-topology` is set."
+ .to_owned(),
));
}
+
+ // Safe because we pass a flag for this call and the host supports this system call
+ let pcpu_count = unsafe { libc::sysconf(libc::_SC_NPROCESSORS_CONF) } as usize;
+ if cfg.vcpu_count.is_some() {
+ if pcpu_count != cfg.vcpu_count.unwrap() {
+ return Err(argument::Error::ExpectedArgument(format!(
+ "`host-cpu-topology` requires the count of vCPUs({}) to equal the \
+ count of CPUs({}) on host.",
+ cfg.vcpu_count.unwrap(),
+ pcpu_count
+ )));
+ }
+ } else {
+ cfg.vcpu_count = Some(pcpu_count);
+ }
+
+ match &cfg.vcpu_affinity {
+ None => {
+ let mut affinity_map = BTreeMap::new();
+ for cpu_id in 0..cfg.vcpu_count.unwrap() {
+ affinity_map.insert(cpu_id, vec![cpu_id]);
+ }
+ cfg.vcpu_affinity = Some(VcpuAffinity::PerVcpu(affinity_map));
+ }
+ _ => {
+ return Err(argument::Error::ExpectedArgument(
+ "`host-cpu-topology` requires not to set `cpu-affinity` at the same time"
+ .to_owned(),
+ ));
+ }
+ }
}
- set_default_serial_parameters(&mut cfg.serial_parameters);
+ if !cfg.balloon && cfg.balloon_control.is_some() {
+ return Err(argument::Error::ExpectedArgument(
+ "'balloon-control' requires enabled balloon".to_owned(),
+ ));
+ }
+
+ set_default_serial_parameters(
+ &mut cfg.serial_parameters,
+ !cfg.vhost_user_console.is_empty(),
+ );
+
+ // Remove jail configuration if it has not been enabled.
+ if !cfg.jail_enabled {
+ cfg.jail_config = None;
+ }
+
Ok(())
}
-fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
+enum CommandStatus {
+ Success,
+ VmReset,
+ VmStop,
+ VmCrash,
+ GuestPanic,
+}
+
+fn run_vm(args: std::env::Args) -> std::result::Result<CommandStatus, ()> {
let arguments =
&[Argument::positional("KERNEL", "bzImage of kernel to run"),
Argument::value("kvm-device", "PATH", "Path to the KVM device. (default /dev/kvm)"),
+ Argument::value("vhost-vsock-fd", "FD", "Open FD to the vhost-vsock device, mutually exclusive with vhost-vsock-device."),
Argument::value("vhost-vsock-device", "PATH", "Path to the vhost-vsock device. (default /dev/vhost-vsock)"),
Argument::value("vhost-net-device", "PATH", "Path to the vhost-net device. (default /dev/vhost-net)"),
Argument::value("android-fstab", "PATH", "Path to Android fstab"),
@@ -1822,12 +2572,31 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
Argument::short_value('c', "cpus", "N", "Number of VCPUs. (default: 1)"),
Argument::value("cpu-affinity", "CPUSET", "Comma-separated list of CPUs or CPU ranges to run VCPUs on (e.g. 0,1-3,5)
or colon-separated list of assignments of guest to host CPU assignments (e.g. 0=0:1=1:2=2) (default: no mask)"),
+ Argument::value("cpu-cluster", "CPUSET", "Group the given CPUs into a cluster (default: no clusters)"),
+ Argument::value("cpu-capacity", "CPU=CAP[,CPU=CAP[,...]]", "Set the relative capacity of the given CPU (default: no capacity)"),
+ Argument::flag("per-vm-core-scheduling", "Enable per-VM core scheduling intead of the default one (per-vCPU core scheduing) by
+ making all vCPU threads share same cookie for core scheduling.
+ This option is no-op on devices that have neither MDS nor L1TF vulnerability."),
+ Argument::value("vcpu-cgroup-path", "PATH", "Move all vCPU threads to this CGroup (default: nothing moves)."),
+#[cfg(feature = "audio_cras")]
+ Argument::value("cras-snd",
+ "[capture=true,client=crosvm,socket=unified,num_output_streams=1,num_input_streams=1]",
+ "Comma separated key=value pairs for setting up cras snd devices.
+ Possible key values:
+ capture - Enable audio capture. Default to false.
+ client_type - Set specific client type for cras backend.
+ num_output_streams - Set number of output PCM streams
+ num_input_streams - Set number of input PCM streams"),
Argument::flag("no-smt", "Don't use SMT in the guest"),
Argument::value("rt-cpus", "CPUSET", "Comma-separated list of CPUs or CPU ranges to run VCPUs on. (e.g. 0,1-3,5) (default: none)"),
+ Argument::flag("delay-rt", "Don't set VCPUs real-time until make-rt command is run"),
Argument::short_value('m',
"mem",
"N",
"Amount of guest memory in MiB. (default: 256)"),
+ Argument::value("init-mem",
+ "N",
+ "Amount of guest memory outside the balloon at boot in MiB. (default: --mem)"),
Argument::flag("hugepages", "Advise the kernel to use Huge Pages for guest memory mappings."),
Argument::short_value('r',
"root",
@@ -1841,12 +2610,13 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
Valid keys:
sparse=BOOL - Indicates whether the disk should support the discard operation (default: true)
block_size=BYTES - Set the reported block size of the disk (default: 512)
- id=STRING - Set the block device identifier to an ASCII string, up to 20 characters (default: no ID)"),
+ id=STRING - Set the block device identifier to an ASCII string, up to 20 characters (default: no ID)
+ o_direct=BOOL - Use O_DIRECT mode to bypass page cache"),
Argument::value("rwdisk", "PATH[,key=value[,key=value[,...]]", "Path to a writable disk image followed by optional comma-separated options.
See --disk for valid options."),
Argument::value("rw-pmem-device", "PATH", "Path to a writable disk image."),
Argument::value("pmem-device", "PATH", "Path to a disk image."),
- Argument::value("pstore", "path=PATH,size=SIZE", "Path to pstore buffer backend file follewed by size."),
+ Argument::value("pstore", "path=PATH,size=SIZE", "Path to pstore buffer backend file followed by size."),
Argument::value("host_ip",
"IP",
"IP address to assign to host tap interface."),
@@ -1857,49 +2627,55 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
Argument::value("ac97",
"[backend=BACKEND,capture=true,capture_effect=EFFECT,client_type=TYPE,shm-fd=FD,client-fd=FD,server-fd=FD]",
"Comma separated key=value pairs for setting up Ac97 devices. Can be given more than once .
- Possible key values:
- backend=(null, cras, vios) - Where to route the audio device. If not provided, backend will default to null.
- `null` for /dev/null, cras for CRAS server and vios for VioS server.
- capture - Enable audio capture
- capture_effects - | separated effects to be enabled for recording. The only supported effect value now is EchoCancellation or aec.
- client_type - Set specific client type for cras backend.
- server - The to the VIOS server (unix socket)."),
+ Possible key values:
+ backend=(null, cras, vios) - Where to route the audio device. If not provided, backend will default to null.
+ `null` for /dev/null, cras for CRAS server and vios for VioS server.
+ capture - Enable audio capture
+ capture_effects - | separated effects to be enabled for recording. The only supported effect value now is EchoCancellation or aec.
+ client_type - Set specific client type for cras backend.
+ socket_type - Set specific socket type for cras backend.
+ server - The to the VIOS server (unix socket)."),
+ #[cfg(feature = "audio")]
+ Argument::value("sound", "[PATH]", "Path to the VioS server socket for setting up virtio-snd devices."),
Argument::value("serial",
"type=TYPE,[hardware=HW,num=NUM,path=PATH,input=PATH,console,earlycon,stdin]",
"Comma separated key=value pairs for setting up serial devices. Can be given more than once.
- Possible key values:
- type=(stdout,syslog,sink,file) - Where to route the serial device
- hardware=(serial,virtio-console) - Which type of serial hardware to emulate. Defaults to 8250 UART (serial).
- num=(1,2,3,4) - Serial Device Number. If not provided, num will default to 1.
- path=PATH - The path to the file to write to when type=file
- input=PATH - The path to the file to read from when not stdin
- console - Use this serial device as the guest console. Can only be given once. Will default to first serial port if not provided.
- earlycon - Use this serial device as the early console. Can only be given once.
- stdin - Direct standard input to this serial device. Can only be given once. Will default to first serial port if not provided.
- "),
+ Possible key values:
+ type=(stdout,syslog,sink,file) - Where to route the serial device
+ hardware=(serial,virtio-console) - Which type of serial hardware to emulate. Defaults to 8250 UART (serial).
+ num=(1,2,3,4) - Serial Device Number. If not provided, num will default to 1.
+ path=PATH - The path to the file to write to when type=file
+ input=PATH - The path to the file to read from when not stdin
+ console - Use this serial device as the guest console. Can only be given once. Will default to first serial port if not provided.
+ earlycon - Use this serial device as the early console. Can only be given once.
+ stdin - Direct standard input to this serial device. Can only be given once. Will default to first serial port if not provided.
+ "),
Argument::value("syslog-tag", "TAG", "When logging to syslog, use the provided tag."),
Argument::value("x-display", "DISPLAY", "X11 display name to use."),
Argument::flag("display-window-keyboard", "Capture keyboard input from the display window."),
Argument::flag("display-window-mouse", "Capture keyboard input from the display window."),
Argument::value("wayland-sock", "PATH[,name=NAME]", "Path to the Wayland socket to use. The unnamed one is used for displaying virtual screens. Named ones are only for IPC."),
#[cfg(feature = "wl-dmabuf")]
- Argument::flag("wayland-dmabuf", "Enable support for DMABufs in Wayland device."),
+ Argument::flag("wayland-dmabuf", "DEPRECATED: Enable support for DMABufs in Wayland device."),
Argument::short_value('s',
"socket",
"PATH",
"Path to put the control socket. If PATH is a directory, a name will be generated."),
+ Argument::value("balloon-control", "PATH", "Path for balloon controller socket."),
Argument::flag("disable-sandbox", "Run all devices in one, non-sandboxed process."),
Argument::value("cid", "CID", "Context ID for virtual sockets."),
- Argument::value("shared-dir", "PATH:TAG[:type=TYPE:writeback=BOOL:timeout=SECONDS:uidmap=UIDMAP:gidmap=GIDMAP:cache=CACHE]",
+ Argument::value("shared-dir", "PATH:TAG[:type=TYPE:writeback=BOOL:timeout=SECONDS:uidmap=UIDMAP:gidmap=GIDMAP:cache=CACHE:dax=BOOL,posix_acl=BOOL]",
"Colon-separated options for configuring a directory to be shared with the VM.
-The first field is the directory to be shared and the second field is the tag that the VM can use to identify the device.
-The remaining fields are key=value pairs that may appear in any order. Valid keys are:
-type=(p9, fs) - Indicates whether the directory should be shared via virtio-9p or virtio-fs (default: p9).
-uidmap=UIDMAP - The uid map to use for the device's jail in the format \"inner outer count[,inner outer count]\" (default: 0 <current euid> 1).
-gidmap=GIDMAP - The gid map to use for the device's jail in the format \"inner outer count[,inner outer count]\" (default: 0 <current egid> 1).
-cache=(never, auto, always) - Indicates whether the VM can cache the contents of the shared directory (default: auto). When set to \"auto\" and the type is \"fs\", the VM will use close-to-open consistency for file contents.
-timeout=SECONDS - How long the VM should consider file attributes and directory entries to be valid (default: 5). If the VM has exclusive access to the directory, then this should be a large value. If the directory can be modified by other processes, then this should be 0.
-writeback=BOOL - Indicates whether the VM can use writeback caching (default: false). This is only safe to do when the VM has exclusive access to the files in a directory. Additionally, the server should have read permission for all files as the VM may issue read requests even for files that are opened write-only.
+ The first field is the directory to be shared and the second field is the tag that the VM can use to identify the device.
+ The remaining fields are key=value pairs that may appear in any order. Valid keys are:
+ type=(p9, fs) - Indicates whether the directory should be shared via virtio-9p or virtio-fs (default: p9).
+ uidmap=UIDMAP - The uid map to use for the device's jail in the format \"inner outer count[,inner outer count]\" (default: 0 <current euid> 1).
+ gidmap=GIDMAP - The gid map to use for the device's jail in the format \"inner outer count[,inner outer count]\" (default: 0 <current egid> 1).
+ cache=(never, auto, always) - Indicates whether the VM can cache the contents of the shared directory (default: auto). When set to \"auto\" and the type is \"fs\", the VM will use close-to-open consistency for file contents.
+ timeout=SECONDS - How long the VM should consider file attributes and directory entries to be valid (default: 5). If the VM has exclusive access to the directory, then this should be a large value. If the directory can be modified by other processes, then this should be 0.
+ writeback=BOOL - Enables writeback caching (default: false). This is only safe to do when the VM has exclusive access to the files in a directory. Additionally, the server should have read permission for all files as the VM may issue read requests even for files that are opened write-only.
+ dax=BOOL - Enables DAX support. Enabling DAX can improve performance for frequently accessed files by mapping regions of the file directly into the VM's memory. There is a cost of slightly increased latency the first time the file is accessed. Since the mapping is shared directly from the host kernel's file cache, enabling DAX can improve performance even when the guest cache policy is \"Never\". The default value for this option is \"false\".
+ posix_acl=BOOL - Indicates whether the shared directory supports POSIX ACLs. This should only be enabled when the underlying file system supports POSIX ACLs. The default value for this option is \"true\".
"),
Argument::value("seccomp-policy-dir", "PATH", "Path to seccomp .policy files."),
Argument::flag("seccomp-log-failures", "Instead of seccomp filter failures being fatal, they will be logged instead."),
@@ -1916,6 +2692,9 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa
#[cfg(feature = "plugin")]
Argument::value("plugin-gid-map-file", "PATH", "Path to the file listing supplemental GIDs that should be mapped in plugin jail. Can be given more than once."),
Argument::flag("vhost-net", "Use vhost for networking."),
+ Argument::value("tap-name",
+ "NAME",
+ "Name of a configured persistent TAP interface to use for networking. A different virtual network card will be added each time this argument is given."),
Argument::value("tap-fd",
"fd",
"File descriptor for configured tap device. A different virtual network card will be added each time this argument is given."),
@@ -1923,17 +2702,33 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa
Argument::flag_or_value("gpu",
"[width=INT,height=INT]",
"(EXPERIMENTAL) Comma separated key=value pairs for setting up a virtio-gpu device
- Possible key values:
- backend=(2d|virglrenderer|gfxstream) - Which backend to use for virtio-gpu (determining rendering protocol)
- width=INT - The width of the virtual display connected to the virtio-gpu.
- height=INT - The height of the virtual display connected to the virtio-gpu.
- egl[=true|=false] - If the backend should use a EGL context for rendering.
- glx[=true|=false] - If the backend should use a GLX context for rendering.
- surfaceless[=true|=false] - If the backend should use a surfaceless context for rendering.
- angle[=true|=false] - If the gfxstream backend should use ANGLE (OpenGL on Vulkan) as its native OpenGL driver.
- syncfd[=true|=false] - If the gfxstream backend should support EGL_ANDROID_native_fence_sync
- vulkan[=true|=false] - If the backend should support vulkan
- "),
+ Possible key values:
+ backend=(2d|virglrenderer|gfxstream) - Which backend to use for virtio-gpu (determining rendering protocol)
+ width=INT - The width of the virtual display connected to the virtio-gpu.
+ height=INT - The height of the virtual display connected to the virtio-gpu.
+ egl[=true|=false] - If the backend should use a EGL context for rendering.
+ glx[=true|=false] - If the backend should use a GLX context for rendering.
+ surfaceless[=true|=false] - If the backend should use a surfaceless context for rendering.
+ angle[=true|=false] - If the gfxstream backend should use ANGLE (OpenGL on Vulkan) as its native OpenGL driver.
+ syncfd[=true|=false] - If the gfxstream backend should support EGL_ANDROID_native_fence_sync
+ vulkan[=true|=false] - If the backend should support vulkan
+ cache-path=PATH - The path to the virtio-gpu device shader cache.
+ cache-size=SIZE - The maximum size of the shader cache."),
+ #[cfg(feature = "gpu")]
+ Argument::flag_or_value("gpu-display",
+ "[width=INT,height=INT]",
+ "(EXPERIMENTAL) Comma separated key=value pairs for setting up a display on the virtio-gpu device
+ Possible key values:
+ width=INT - The width of the virtual display connected to the virtio-gpu.
+ height=INT - The height of the virtual display connected to the virtio-gpu."),
+ #[cfg(all(feature = "gpu", feature = "virgl_renderer_next"))]
+ Argument::flag_or_value("gpu-render-server",
+ "[path=PATH]",
+ "(EXPERIMENTAL) Comma separated key=value pairs for setting up a render server for the virtio-gpu device
+ Possible key values:
+ path=PATH - The path to the render server executable.
+ cache-path=PATH - The path to the render server shader cache.
+ cache-size=SIZE - The maximum size of the shader cache."),
#[cfg(feature = "tpm")]
Argument::flag("software-tpm", "enable a software emulated trusted platform module device"),
Argument::value("evdev", "PATH", "Path to an event device node. The device will be grabbed (unusable from the host) and made available to the guest with the same configuration it shows on the host"),
@@ -1946,32 +2741,106 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
Argument::flag("split-irqchip", "(EXPERIMENTAL) enable split-irqchip support"),
Argument::value("bios", "PATH", "Path to BIOS/firmware ROM"),
- Argument::value("vfio", "PATH", "Path to sysfs of pass through or mdev device"),
+ Argument::value("vfio", "PATH[,guest-address=auto|<BUS:DEVICE.FUNCTION>][,iommu=on|off]", "Path to sysfs of PCI pass through or mdev device.
+guest-address=auto|<BUS:DEVICE.FUNCTION> - PCI address that the device will be assigned in the guest (default: auto). When set to \"auto\", the device will be assigned an address that mirrors its address in the host.
+iommu=on|off - indicates whether to enable virtio IOMMU for this device"),
+ Argument::value("vfio-platform", "PATH", "Path to sysfs of platform pass through"),
+ Argument::flag("virtio-iommu", "Add a virtio-iommu device"),
#[cfg(feature = "video-decoder")]
- Argument::flag("video-decoder", "(EXPERIMENTAL) enable virtio-video decoder device"),
+ Argument::flag_or_value("video-decoder", "[backend]", "(EXPERIMENTAL) enable virtio-video decoder device
+ Possible backend values: libvda"),
#[cfg(feature = "video-encoder")]
- Argument::flag("video-encoder", "(EXPERIMENTAL) enable virtio-video encoder device"),
+ Argument::flag_or_value("video-encoder", "[backend]", "(EXPERIMENTAL) enable virtio-video encoder device
+ Possible backend values: libvda"),
Argument::value("acpi-table", "PATH", "Path to user provided ACPI table"),
Argument::flag("protected-vm", "(EXPERIMENTAL) prevent host access to guest memory"),
+ Argument::flag("protected-vm-without-firmware", "(EXPERIMENTAL) prevent host access to guest memory, but don't use protected VM firmware"),
+ #[cfg(target_arch = "aarch64")]
+ Argument::value("swiotlb", "N", "(EXPERIMENTAL) Size of virtio swiotlb buffer in MiB (default: 64 if `--protected-vm` or `--protected-vm-without-firmware` is present)."),
Argument::flag_or_value("battery",
"[type=TYPE]",
"Comma separated key=value pairs for setting up battery device
- Possible key values:
- type=goldfish - type of battery emulation, defaults to goldfish
- "),
+ Possible key values:
+ type=goldfish - type of battery emulation, defaults to goldfish"),
Argument::value("gdb", "PORT", "(EXPERIMENTAL) gdb on the given port"),
+ Argument::flag("no-balloon", "Don't use virtio-balloon device in the guest"),
+ #[cfg(feature = "usb")]
+ Argument::flag("no-usb", "Don't use usb devices in the guest"),
+ Argument::flag("no-rng", "Don't create RNG device in the guest"),
Argument::value("balloon_bias_mib", "N", "Amount to bias balance of memory between host and guest as the balloon inflates, in MiB."),
Argument::value("vhost-user-blk", "SOCKET_PATH", "Path to a socket for vhost-user block"),
+ Argument::value("vhost-user-console", "SOCKET_PATH", "Path to a socket for vhost-user console"),
+ Argument::value("vhost-user-gpu", "SOCKET_PATH", "Paths to a vhost-user socket for gpu"),
+ Argument::value("vhost-user-mac80211-hwsim", "SOCKET_PATH", "Path to a socket for vhost-user mac80211_hwsim"),
Argument::value("vhost-user-net", "SOCKET_PATH", "Path to a socket for vhost-user net"),
+ #[cfg(feature = "audio")]
+ Argument::value("vhost-user-snd", "SOCKET_PATH", "Path to a socket for vhost-user snd"),
+ Argument::value("vhost-user-vsock", "SOCKET_PATH", "Path to a socket for vhost-user vsock"),
+ Argument::value("vhost-user-wl", "SOCKET_PATH:TUBE_PATH", "Paths to a vhost-user socket for wayland and a Tube socket for additional wayland-specific messages"),
Argument::value("vhost-user-fs", "SOCKET_PATH:TAG",
"Path to a socket path for vhost-user fs, and tag for the shared dir"),
+ Argument::value("vvu-proxy", "SOCKET_PATH[,addr=DOMAIN:BUS:DEVICE.FUNCTION,uuid=UUID]", "Socket path for the Virtio Vhost User proxy device.
+ Parameters
+ addr=BUS:DEVICE.FUNCTION - PCI address that the proxy device will be allocated (default: automatically allocated)
+ uuid=UUID - UUID which will be stored in VVU PCI config space that is readable from guest userspace"),
#[cfg(feature = "direct")]
- Argument::value("direct-pmio", "PATH@RANGE[,RANGE[,...]]", "Path and ranges for direct port I/O access"),
+ Argument::value("direct-pmio", "PATH@RANGE[,RANGE[,...]]", "Path and ranges for direct port mapped I/O access. RANGE may be decimal or hex (starting with 0x)."),
#[cfg(feature = "direct")]
+ Argument::value("direct-mmio", "PATH@RANGE[,RANGE[,...]]", "Path and ranges for direct memory mapped I/O access. RANGE may be decimal or hex (starting with 0x)."),
+#[cfg(feature = "direct")]
Argument::value("direct-level-irq", "irq", "Enable interrupt passthrough"),
- #[cfg(feature = "direct")]
+#[cfg(feature = "direct")]
Argument::value("direct-edge-irq", "irq", "Enable interrupt passthrough"),
+#[cfg(feature = "direct")]
+ Argument::value("direct-wake-irq", "irq", "Enable wakeup interrupt for host"),
+#[cfg(feature = "direct")]
+ Argument::value("direct-gpe", "gpe", "Enable GPE interrupt and register access passthrough"),
Argument::value("dmi", "DIR", "Directory with smbios_entry_point/DMI files"),
+ Argument::flag("no-legacy", "Don't use legacy KBD/RTC devices emulation"),
+ Argument::value("userspace-msr", "INDEX,action=r0", "Userspace MSR handling. Takes INDEX of the MSR and how they are handled.
+ action=r0 - forward RDMSR to host kernel cpu0.
+"),
+#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ Argument::flag("host-cpu-topology", "Use mirror cpu topology of Host for Guest VM"),
+ Argument::flag("privileged-vm", "Grant this Guest VM certian privileges to manage Host resources, such as power management."),
+ Argument::value("stub-pci-device", "DOMAIN:BUS:DEVICE.FUNCTION[,vendor=NUM][,device=NUM][,class=NUM][,subsystem_vendor=NUM][,subsystem_device=NUM][,revision=NUM]", "Comma-separated key=value pairs for setting up a stub PCI device that just enumerates. The first option in the list must specify a PCI address to claim.
+ Optional further parameters
+ vendor=NUM - PCI vendor ID
+ device=NUM - PCI device ID
+ class=NUM - PCI class (including class code, subclass, and programming interface)
+ subsystem_vendor=NUM - PCI subsystem vendor ID
+ subsystem_device=NUM - PCI subsystem device ID
+ revision=NUM - revision"),
+ Argument::flag_or_value("coiommu",
+ "unpin_policy=POLICY,unpin_interval=NUM,unpin_limit=NUM,unpin_gen_threshold=NUM ",
+ "Comma separated key=value pairs for setting up coiommu devices.
+ Possible key values:
+ unpin_policy=lru - LRU unpin policy.
+ unpin_interval=NUM - Unpin interval time in seconds.
+ unpin_limit=NUM - Unpin limit for each unpin cycle, in unit of page count. 0 is invalid.
+ unpin_gen_threshold=NUM - Number of unpin intervals a pinned page must be busy for to be aged into the older which is less frequently checked generation."),
+ Argument::value("file-backed-mapping", "addr=NUM,size=SIZE,path=PATH[,offset=NUM][,ro][,rw][,sync]", "Map the given file into guest memory at the specified address.
+ Parameters (addr, size, path are required):
+ addr=NUM - guest physical address to map at
+ size=NUM - amount of memory to map
+ path=PATH - path to backing file/device to map
+ offset=NUM - offset in backing file (default 0)
+ ro - make the mapping readonly (default)
+ rw - make the mapping writable
+ sync - open backing file with O_SYNC
+ align - whether to adjust addr and size to page boundaries implicitly"),
+ #[cfg(feature = "direct")]
+ Argument::value("pcie-root-port", "PATH[,hp_gpe=NUM]", "Path to sysfs of host pcie root port and host pcie root port hotplug gpe number"),
+ Argument::value("pivot-root", "PATH", "Path to empty directory to use for sandbox pivot root."),
+ #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+ Argument::flag("s2idle", "Set Low Power S0 Idle Capable Flag for guest Fixed ACPI Description Table"),
+ Argument::flag("strict-balloon", "Don't allow guest to use pages from the balloon"),
+ Argument::value("mmio-address-range", "STARTADDR-ENDADDR[,STARTADDR-ENDADDR]*",
+ "Ranges (inclusive) into which to limit guest mmio addresses. Note that
+ this this may cause mmio allocations to fail if the specified ranges are
+ incompatible with the default ranges calculated by crosvm."),
+ #[cfg(target_os = "android")]
+ Argument::value("task-profiles", "NAME[,...]", "Comma-separated names of the task profiles to apply to all threads in crosvm including the vCPU threads."),
Argument::short_flag('h', "help", "Print help message.")];
let mut cfg = Config::default();
@@ -1986,27 +2855,39 @@ writeback=BOOL - Indicates whether the VM can use writeback caching (default: fa
match crosvm::plugin::run_config(cfg) {
Ok(_) => {
info!("crosvm and plugin have exited normally");
- Ok(())
+ Ok(CommandStatus::VmStop)
}
Err(e) => {
- error!("{}", e);
+ error!("{:#}", e);
Err(())
}
}
}
Ok(()) => match platform::run_config(cfg) {
- Ok(_) => {
+ Ok(platform::ExitState::Stop) => {
info!("crosvm has exited normally");
- Ok(())
+ Ok(CommandStatus::VmStop)
+ }
+ Ok(platform::ExitState::Reset) => {
+ info!("crosvm has exited normally due to reset request");
+ Ok(CommandStatus::VmReset)
+ }
+ Ok(platform::ExitState::Crash) => {
+ info!("crosvm has exited due to a VM crash");
+ Ok(CommandStatus::VmCrash)
+ }
+ Ok(platform::ExitState::GuestPanic) => {
+ info!("crosvm has exited due to a kernel panic in guest");
+ Ok(CommandStatus::GuestPanic)
}
Err(e) => {
- error!("crosvm has exited with error: {}", e);
+ error!("crosvm has exited with error: {:#}", e);
Err(())
}
},
Err(argument::Error::PrintHelp) => {
print_help("crosvm run", "KERNEL", &arguments[..]);
- Ok(())
+ Ok(CommandStatus::Success)
}
Err(e) => {
error!("{}", e);
@@ -2048,6 +2929,36 @@ fn resume_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
vms_request(&VmRequest::Resume, socket_path)
}
+fn powerbtn_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
+ if args.len() == 0 {
+ print_help("crosvm powerbtn", "VM_SOCKET...", &[]);
+ println!("Triggers a power button event in the crosvm instance listening on each `VM_SOCKET` given.");
+ return Err(());
+ }
+ let socket_path = &args.next().unwrap();
+ let socket_path = Path::new(&socket_path);
+ vms_request(&VmRequest::Powerbtn, socket_path)
+}
+
+fn inject_gpe(mut args: std::env::Args) -> std::result::Result<(), ()> {
+ if args.len() < 2 {
+ print_help("crosvm gpe", "GPE# VM_SOCKET...", &[]);
+ println!("Injects a general-purpose event (GPE#) into the crosvm instance listening on each `VM_SOCKET` given.");
+ return Err(());
+ }
+ let gpe = match args.next().unwrap().parse::<u32>() {
+ Ok(n) => n,
+ Err(_) => {
+ error!("Failed to parse GPE#");
+ return Err(());
+ }
+ };
+
+ let socket_path = &args.next().unwrap();
+ let socket_path = Path::new(&socket_path);
+ vms_request(&VmRequest::Gpe(gpe), socket_path)
+}
+
fn balloon_vms(mut args: std::env::Args) -> std::result::Result<(), ()> {
if args.len() < 2 {
print_help("crosvm balloon", "SIZE VM_SOCKET...", &[]);
@@ -2079,7 +2990,181 @@ fn balloon_stats(mut args: std::env::Args) -> std::result::Result<(), ()> {
let socket_path = &args.next().unwrap();
let socket_path = Path::new(&socket_path);
let response = handle_request(request, socket_path)?;
- println!("{}", response);
+ match serde_json::to_string_pretty(&response) {
+ Ok(response_json) => println!("{}", response_json),
+ Err(e) => {
+ error!("Failed to serialize into JSON: {}", e);
+ return Err(());
+ }
+ }
+ match response {
+ VmResponse::BalloonStats { .. } => Ok(()),
+ _ => Err(()),
+ }
+}
+
+fn modify_battery(mut args: std::env::Args) -> std::result::Result<(), ()> {
+ if args.len() < 4 {
+ print_help(
+ "crosvm battery BATTERY_TYPE ",
+ "[status STATUS | \
+ present PRESENT | \
+ health HEALTH | \
+ capacity CAPACITY | \
+ aconline ACONLINE ] \
+ VM_SOCKET...",
+ &[],
+ );
+ return Err(());
+ }
+
+ // This unwrap will not panic because of the above length check.
+ let battery_type = args.next().unwrap();
+ let property = args.next().unwrap();
+ let target = args.next().unwrap();
+
+ let socket_path = args.next().unwrap();
+ let socket_path = Path::new(&socket_path);
+
+ do_modify_battery(socket_path, &*battery_type, &*property, &*target)
+}
+
+fn modify_vfio(mut args: std::env::Args) -> std::result::Result<(), ()> {
+ if args.len() < 3 {
+ print_help(
+ "crosvm vfio",
+ "[add | remove host_vfio_sysfs] VM_SOCKET...",
+ &[],
+ );
+ return Err(());
+ }
+
+ // This unwrap will not panic because of the above length check.
+ let command = args.next().unwrap();
+ let path_str = args.next().unwrap();
+ let vfio_path = PathBuf::from(&path_str);
+ if !vfio_path.exists() || !vfio_path.is_dir() {
+ error!("Invalid host sysfs path: {}", path_str);
+ return Err(());
+ }
+
+ let socket_path = args.next().unwrap();
+ let socket_path = Path::new(&socket_path);
+
+ let add = match command.as_ref() {
+ "add" => true,
+ "remove" => false,
+ other => {
+ error!("Invalid vfio command {}", other);
+ return Err(());
+ }
+ };
+
+ let request = VmRequest::VfioCommand { vfio_path, add };
+ handle_request(&request, socket_path)?;
+ Ok(())
+}
+
+#[cfg(feature = "composite-disk")]
+fn create_composite(mut args: std::env::Args) -> std::result::Result<(), ()> {
+ if args.len() < 1 {
+ print_help("crosvm create_composite", "PATH [LABEL:PARTITION]..", &[]);
+ println!("Creates a new composite disk image containing the given partition images");
+ return Err(());
+ }
+
+ let composite_image_path = args.next().unwrap();
+ let zero_filler_path = format!("{}.filler", composite_image_path);
+ let header_path = format!("{}.header", composite_image_path);
+ let footer_path = format!("{}.footer", composite_image_path);
+
+ let mut composite_image_file = OpenOptions::new()
+ .create(true)
+ .read(true)
+ .write(true)
+ .truncate(true)
+ .open(&composite_image_path)
+ .map_err(|e| {
+ error!(
+ "Failed opening composite disk image file at '{}': {}",
+ composite_image_path, e
+ );
+ })?;
+ create_zero_filler(&zero_filler_path).map_err(|e| {
+ error!(
+ "Failed to create zero filler file at '{}': {}",
+ &zero_filler_path, e
+ );
+ })?;
+ let mut header_file = OpenOptions::new()
+ .create(true)
+ .read(true)
+ .write(true)
+ .truncate(true)
+ .open(&header_path)
+ .map_err(|e| {
+ error!(
+ "Failed opening header image file at '{}': {}",
+ header_path, e
+ );
+ })?;
+ let mut footer_file = OpenOptions::new()
+ .create(true)
+ .read(true)
+ .write(true)
+ .truncate(true)
+ .open(&footer_path)
+ .map_err(|e| {
+ error!(
+ "Failed opening footer image file at '{}': {}",
+ footer_path, e
+ );
+ })?;
+
+ let partitions = args
+ .into_iter()
+ .map(|partition_arg| {
+ if let [label, path] = partition_arg.split(":").collect::<Vec<_>>()[..] {
+ let partition_file = File::open(path)
+ .map_err(|e| error!("Failed to open partition image: {}", e))?;
+ let size =
+ create_disk_file(partition_file, disk::MAX_NESTING_DEPTH, Path::new(path))
+ .map_err(|e| error!("Failed to create DiskFile instance: {}", e))?
+ .get_len()
+ .map_err(|e| error!("Failed to get length of partition image: {}", e))?;
+ Ok(PartitionInfo {
+ label: label.to_owned(),
+ path: Path::new(path).to_owned(),
+ partition_type: ImagePartitionType::LinuxFilesystem,
+ writable: false,
+ size,
+ })
+ } else {
+ error!(
+ "Must specify label and path for partition '{}', like LABEL:PATH",
+ partition_arg
+ );
+ Err(())
+ }
+ })
+ .collect::<Result<Vec<_>, _>>()?;
+
+ create_composite_disk(
+ &partitions,
+ &PathBuf::from(zero_filler_path),
+ &PathBuf::from(header_path),
+ &mut header_file,
+ &PathBuf::from(footer_path),
+ &mut footer_file,
+ &mut composite_image_file,
+ )
+ .map_err(|e| {
+ error!(
+ "Failed to create composite disk image at '{}': {}",
+ composite_image_path, e
+ );
+ })?;
+
Ok(())
}
@@ -2153,15 +3238,62 @@ with a '--backing_file'."
error!("Failed to create qcow file at '{}': {}", file_path, e);
})?,
(None, Some(backing_file)) => {
- QcowFile::new_from_backing(file, &backing_file).map_err(|e| {
- error!("Failed to create qcow file at '{}': {}", file_path, e);
- })?
+ QcowFile::new_from_backing(file, &backing_file, disk::MAX_NESTING_DEPTH).map_err(
+ |e| {
+ error!("Failed to create qcow file at '{}': {}", file_path, e);
+ },
+ )?
}
_ => unreachable!(),
};
Ok(())
}
+fn start_device(mut args: std::env::Args) -> std::result::Result<(), ()> {
+ let print_usage = || {
+ print_help(
+ "crosvm device",
+ " (block|console|cras-snd|fs|gpu|net|wl) <device-specific arguments>",
+ &[],
+ );
+ };
+
+ if args.len() == 0 {
+ print_usage();
+ return Err(());
+ }
+
+ let device = args.next().unwrap();
+
+ let program_name = format!("crosvm device {}", device);
+
+ let args = args.collect::<Vec<_>>();
+ let args = args.iter().map(Deref::deref).collect::<Vec<_>>();
+ let args = args.as_slice();
+
+ let result = match device.as_str() {
+ "block" => run_block_device(&program_name, args),
+ "console" => run_console_device(&program_name, args),
+ #[cfg(feature = "audio_cras")]
+ "cras-snd" => run_cras_snd_device(&program_name, args),
+ "fs" => run_fs_device(&program_name, args),
+ #[cfg(feature = "gpu")]
+ "gpu" => run_gpu_device(&program_name, args),
+ "net" => run_net_device(&program_name, args),
+ "vsock" => run_vsock_device(&program_name, args),
+ "wl" => run_wl_device(&program_name, args),
+ _ => {
+ println!("Unknown device name: {}", device);
+ print_usage();
+ return Err(());
+ }
+ };
+
+ result.map_err(|e| {
+ error!("Failed to run {} device: {:#}", device, e);
+ })
+}
+
fn disk_cmd(mut args: std::env::Args) -> std::result::Result<(), ()> {
if args.len() < 2 {
print_help("crosvm disk", "SUBCOMMAND VM_SOCKET...", &[]);
@@ -2206,6 +3338,17 @@ fn disk_cmd(mut args: std::env::Args) -> std::result::Result<(), ()> {
vms_request(&request, socket_path)
}
+fn make_rt(mut args: std::env::Args) -> std::result::Result<(), ()> {
+ if args.len() == 0 {
+ print_help("crosvm make_rt", "VM_SOCKET...", &[]);
+ println!("Makes the crosvm instance listening on each `VM_SOCKET` given RT.");
+ return Err(());
+ }
+ let socket_path = &args.next().unwrap();
+ let socket_path = Path::new(&socket_path);
+ vms_request(&VmRequest::MakeRT, socket_path)
+}
+
fn parse_bus_id_addr(v: &str) -> ModifyUsbResult<(u8, u8, u16, u16)> {
debug!("parse_bus_id_addr: {}", v);
let mut ids = v.split(':');
@@ -2217,9 +3360,9 @@ fn parse_bus_id_addr(v: &str) -> ModifyUsbResult<(u8, u8, u16, u16)> {
let addr = addr
.parse::<u8>()
.map_err(|e| ModifyUsbError::ArgParseInt("addr", addr.to_owned(), e))?;
- let vid = u16::from_str_radix(&vid, 16)
+ let vid = u16::from_str_radix(vid, 16)
.map_err(|e| ModifyUsbError::ArgParseInt("vid", vid.to_owned(), e))?;
- let pid = u16::from_str_radix(&pid, 16)
+ let pid = u16::from_str_radix(pid, 16)
.map_err(|e| ModifyUsbError::ArgParseInt("pid", pid.to_owned(), e))?;
Ok((bus_id, addr, vid, pid))
}
@@ -2245,7 +3388,7 @@ fn usb_attach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
.ok_or(ModifyUsbError::ArgMissing("control socket path"))?;
let socket_path = Path::new(&socket_path);
- do_usb_attach(&socket_path, bus, addr, vid, pid, &dev_path)
+ do_usb_attach(socket_path, bus, addr, vid, pid, &dev_path)
}
fn usb_detach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
@@ -2259,7 +3402,7 @@ fn usb_detach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
.next()
.ok_or(ModifyUsbError::ArgMissing("control socket path"))?;
let socket_path = Path::new(&socket_path);
- do_usb_detach(&socket_path, port)
+ do_usb_detach(socket_path, port)
}
fn usb_list(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
@@ -2267,7 +3410,7 @@ fn usb_list(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
.next()
.ok_or(ModifyUsbError::ArgMissing("control socket path"))?;
let socket_path = Path::new(&socket_path);
- do_usb_list(&socket_path)
+ do_usb_list(socket_path)
}
fn modify_usb(mut args: std::env::Args) -> std::result::Result<(), ()> {
@@ -2297,22 +3440,6 @@ fn modify_usb(mut args: std::env::Args) -> std::result::Result<(), ()> {
}
}
-fn print_usage() {
- print_help("crosvm", "[command]", &[]);
- println!("Commands:");
- println!(" balloon - Set balloon size of the crosvm instance.");
- println!(" balloon_stats - Prints virtio balloon statistics.");
- println!(" battery - Modify battery.");
- println!(" create_qcow2 - Create a new qcow2 disk image file.");
- println!(" disk - Manage attached virtual disk devices.");
- println!(" resume - Resumes the crosvm instance.");
- println!(" run - Start a new crosvm instance.");
- println!(" stop - Stops crosvm instances via their control sockets.");
- println!(" suspend - Suspends the crosvm instance.");
- println!(" usb - Manage attached virtual USB devices.");
- println!(" version - Show package version.");
-}
-
#[allow(clippy::unnecessary_wraps)]
fn pkg_version() -> std::result::Result<(), ()> {
const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
@@ -2326,25 +3453,33 @@ fn pkg_version() -> std::result::Result<(), ()> {
Ok(())
}
-fn modify_battery(mut args: std::env::Args) -> std::result::Result<(), ()> {
- if args.len() < 4 {
- print_help("crosvm battery BATTERY_TYPE ",
- "[status STATUS | present PRESENT | health HEALTH | capacity CAPACITY | aconline ACONLINE ] VM_SOCKET...", &[]);
- return Err(());
- }
-
- // This unwrap will not panic because of the above length check.
- let battery_type = args.next().unwrap();
- let property = args.next().unwrap();
- let target = args.next().unwrap();
-
- let socket_path = args.next().unwrap();
- let socket_path = Path::new(&socket_path);
-
- do_modify_battery(&socket_path, &*battery_type, &*property, &*target)
+fn print_usage() {
+ print_help("crosvm", "[--extended-status] [command]", &[]);
+ println!("Commands:");
+ println!(" balloon - Set balloon size of the crosvm instance.");
+ println!(" balloon_stats - Prints virtio balloon statistics.");
+ println!(" battery - Modify battery.");
+ #[cfg(feature = "composite-disk")]
+ println!(" create_composite - Create a new composite disk image file.");
+ println!(" create_qcow2 - Create a new qcow2 disk image file.");
+ println!(" device - Start a device process.");
+ println!(" disk - Manage attached virtual disk devices.");
+ println!(
+ " make_rt - Enables real-time vcpu priority for crosvm instances started with \
+ `--delay-rt`."
+ );
+ println!(" resume - Resumes the crosvm instance.");
+ println!(" run - Start a new crosvm instance.");
+ println!(" stop - Stops crosvm instances via their control sockets.");
+ println!(" suspend - Suspends the crosvm instance.");
+ println!(" powerbtn - Triggers a power button event in the crosvm instance.");
+ println!(" gpe - Injects a general-purpose event into the crosvm instance.");
+ println!(" usb - Manage attached virtual USB devices.");
+ println!(" version - Show package version.");
+ println!(" vfio - add/remove host vfio pci device into guest.");
}
-fn crosvm_main() -> std::result::Result<(), ()> {
+fn crosvm_main() -> std::result::Result<CommandStatus, ()> {
if let Err(e) = syslog::init() {
println!("failed to initialize syslog: {}", e);
return Err(());
@@ -2358,28 +3493,54 @@ fn crosvm_main() -> std::result::Result<(), ()> {
return Err(());
}
- // Past this point, usage of exit is in danger of leaking zombie processes.
- let ret = match args.next().as_ref().map(|a| a.as_ref()) {
+ let mut cmd_arg = args.next();
+ let extended_status = match cmd_arg.as_ref().map(|s| s.as_ref()) {
+ Some("--extended-status") => {
+ cmd_arg = args.next();
+ true
+ }
+ _ => false,
+ };
+
+ let command = match cmd_arg {
+ Some(c) => c,
None => {
print_usage();
- Ok(())
+ return Ok(CommandStatus::Success);
}
- Some("stop") => stop_vms(args),
- Some("suspend") => suspend_vms(args),
- Some("resume") => resume_vms(args),
- Some("run") => run_vm(args),
- Some("balloon") => balloon_vms(args),
- Some("balloon_stats") => balloon_stats(args),
- Some("create_qcow2") => create_qcow2(args),
- Some("disk") => disk_cmd(args),
- Some("usb") => modify_usb(args),
- Some("version") => pkg_version(),
- Some("battery") => modify_battery(args),
- Some(c) => {
- println!("invalid subcommand: {:?}", c);
- print_usage();
- Err(())
+ };
+
+ // Past this point, usage of exit is in danger of leaking zombie processes.
+ let ret = if command == "run" {
+ // We handle run_vm separately because it does not simply signal success/error
+ // but also indicates whether the guest requested reset or stop.
+ run_vm(args)
+ } else {
+ match &command[..] {
+ "balloon" => balloon_vms(args),
+ "balloon_stats" => balloon_stats(args),
+ "battery" => modify_battery(args),
+ #[cfg(feature = "composite-disk")]
+ "create_composite" => create_composite(args),
+ "create_qcow2" => create_qcow2(args),
+ "device" => start_device(args),
+ "disk" => disk_cmd(args),
+ "make_rt" => make_rt(args),
+ "resume" => resume_vms(args),
+ "stop" => stop_vms(args),
+ "suspend" => suspend_vms(args),
+ "powerbtn" => powerbtn_vms(args),
+ "gpe" => inject_gpe(args),
+ "usb" => modify_usb(args),
+ "version" => pkg_version(),
+ "vfio" => modify_vfio(args),
+ c => {
+ println!("invalid subcommand: {:?}", c);
+ print_usage();
+ Err(())
+ }
}
+ .map(|_| CommandStatus::Success)
};
// Reap exit status from any child device processes. At this point, all devices should have been
@@ -2396,11 +3557,24 @@ fn crosvm_main() -> std::result::Result<(), ()> {
// WARNING: Any code added after this point is not guaranteed to run
// since we may forcibly kill this process (and its children) above.
- ret
+ ret.map(|s| {
+ if extended_status {
+ s
+ } else {
+ CommandStatus::Success
+ }
+ })
}
fn main() {
- std::process::exit(if crosvm_main().is_ok() { 0 } else { 1 });
+ let exit_code = match crosvm_main() {
+ Ok(CommandStatus::Success | CommandStatus::VmStop) => 0,
+ Ok(CommandStatus::VmReset) => 32,
+ Ok(CommandStatus::VmCrash) => 33,
+ Ok(CommandStatus::GuestPanic) => 34,
+ Err(_) => 1,
+ };
+ std::process::exit(exit_code);
}
#[cfg(test)]
@@ -2498,7 +3672,7 @@ mod tests {
);
}
- #[cfg(feature = "audio")]
+ #[cfg(feature = "audio_cras")]
#[test]
fn parse_ac97_vaild() {
parse_ac97_options("backend=cras").expect("parse should have succeded");
@@ -2510,13 +3684,13 @@ mod tests {
parse_ac97_options("backend=null").expect("parse should have succeded");
}
- #[cfg(feature = "audio")]
+ #[cfg(feature = "audio_cras")]
#[test]
fn parse_ac97_capture_vaild() {
parse_ac97_options("backend=cras,capture=true").expect("parse should have succeded");
}
- #[cfg(feature = "audio")]
+ #[cfg(feature = "audio_cras")]
#[test]
fn parse_ac97_client_type() {
parse_ac97_options("backend=cras,capture=true,client_type=crosvm")
@@ -2527,6 +3701,13 @@ mod tests {
.expect_err("parse should have failed");
}
+ #[cfg(feature = "audio_cras")]
+ #[test]
+ fn parse_ac97_socket_type() {
+ parse_ac97_options("socket_type=unified").expect("parse should have succeded");
+ parse_ac97_options("socket_type=legacy").expect("parse should have succeded");
+ }
+
#[cfg(feature = "audio")]
#[test]
fn parse_ac97_vios_valid() {
@@ -2609,7 +3790,7 @@ mod tests {
.expect("parse should succeed");
assert_eq!(config.plugin_mounts[0].src, PathBuf::from("/dev/null"));
assert_eq!(config.plugin_mounts[0].dst, PathBuf::from("/dev/zero"));
- assert_eq!(config.plugin_mounts[0].writable, true);
+ assert!(config.plugin_mounts[0].writable);
}
#[test]
@@ -2617,15 +3798,15 @@ mod tests {
let mut config = Config::default();
set_argument(&mut config, "plugin-mount", Some("/dev/null")).expect("parse should succeed");
assert_eq!(config.plugin_mounts[0].dst, PathBuf::from("/dev/null"));
- assert_eq!(config.plugin_mounts[0].writable, false);
+ assert!(!config.plugin_mounts[0].writable);
set_argument(&mut config, "plugin-mount", Some("/dev/null:/dev/zero"))
.expect("parse should succeed");
assert_eq!(config.plugin_mounts[1].dst, PathBuf::from("/dev/zero"));
- assert_eq!(config.plugin_mounts[1].writable, false);
+ assert!(!config.plugin_mounts[1].writable);
set_argument(&mut config, "plugin-mount", Some("/dev/null::true"))
.expect("parse should succeed");
assert_eq!(config.plugin_mounts[2].dst, PathBuf::from("/dev/null"));
- assert_eq!(config.plugin_mounts[2].writable, true);
+ assert!(config.plugin_mounts[2].writable);
}
#[test]
@@ -2696,11 +3877,11 @@ mod tests {
set_argument(&mut config, "trackpad", Some("/dev/single-touch-test")).unwrap();
validate_arguments(&mut config).unwrap();
assert_eq!(
- config.virtio_single_touch.unwrap().get_size(),
+ config.virtio_single_touch.first().unwrap().get_size(),
(DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
);
assert_eq!(
- config.virtio_trackpad.unwrap().get_size(),
+ config.virtio_trackpad.first().unwrap().get_size(),
(DEFAULT_TOUCH_DEVICE_WIDTH, DEFAULT_TOUCH_DEVICE_HEIGHT)
);
}
@@ -2723,7 +3904,7 @@ mod tests {
.unwrap();
validate_arguments(&mut config).unwrap();
assert_eq!(
- config.virtio_single_touch.unwrap().get_size(),
+ config.virtio_single_touch.first().unwrap().get_size(),
(width, height)
);
}
@@ -2750,10 +3931,13 @@ mod tests {
.unwrap();
validate_arguments(&mut config).unwrap();
assert_eq!(
- config.virtio_single_touch.unwrap().get_size(),
+ config.virtio_single_touch.first().unwrap().get_size(),
+ (width, height)
+ );
+ assert_eq!(
+ config.virtio_trackpad.first().unwrap().get_size(),
(width, height)
);
- assert_eq!(config.virtio_trackpad.unwrap().get_size(), (width, height));
}
#[cfg(feature = "gpu")]
@@ -2787,7 +3971,7 @@ mod tests {
.unwrap();
validate_arguments(&mut config).unwrap();
assert_eq!(
- config.virtio_single_touch.unwrap().get_size(),
+ config.virtio_single_touch.first().unwrap().get_size(),
(touch_width, touch_height)
);
}
@@ -2801,7 +3985,7 @@ mod tests {
set_argument(&mut config, "switches", Some("/dev/switches-test")).unwrap();
validate_arguments(&mut config).unwrap();
assert_eq!(
- config.virtio_switches.unwrap(),
+ config.virtio_switches.pop().unwrap(),
PathBuf::from("/dev/switches-test")
);
}
@@ -2809,81 +3993,211 @@ mod tests {
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_default_vulkan_support() {
- assert!(
- !parse_gpu_options(Some("backend=virglrenderer"))
- .unwrap()
- .use_vulkan
- );
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(parse_gpu_options(Some("backend=virglrenderer"), &mut gpu_params).is_ok());
+ assert!(!gpu_params.use_vulkan);
+ }
#[cfg(feature = "gfxstream")]
- assert!(
- parse_gpu_options(Some("backend=gfxstream"))
- .unwrap()
- .use_vulkan
- );
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(parse_gpu_options(Some("backend=gfxstream"), &mut gpu_params).is_ok());
+ assert!(gpu_params.use_vulkan);
+ }
}
#[cfg(feature = "gpu")]
#[test]
fn parse_gpu_options_with_vulkan_specified() {
- assert!(parse_gpu_options(Some("vulkan=true")).unwrap().use_vulkan);
- assert!(
- parse_gpu_options(Some("backend=virglrenderer,vulkan=true"))
- .unwrap()
- .use_vulkan
- );
- assert!(
- parse_gpu_options(Some("vulkan=true,backend=virglrenderer"))
- .unwrap()
- .use_vulkan
- );
- assert!(!parse_gpu_options(Some("vulkan=false")).unwrap().use_vulkan);
- assert!(
- !parse_gpu_options(Some("backend=virglrenderer,vulkan=false"))
- .unwrap()
- .use_vulkan
- );
- assert!(
- !parse_gpu_options(Some("vulkan=false,backend=virglrenderer"))
- .unwrap()
- .use_vulkan
- );
- assert!(parse_gpu_options(Some("backend=virglrenderer,vulkan=invalid_value")).is_err());
- assert!(parse_gpu_options(Some("vulkan=invalid_value,backend=virglrenderer")).is_err());
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(parse_gpu_options(Some("vulkan=true"), &mut gpu_params).is_ok());
+ assert!(gpu_params.use_vulkan);
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(
+ parse_gpu_options(Some("backend=virglrenderer,vulkan=true"), &mut gpu_params)
+ .is_ok()
+ );
+ assert!(gpu_params.use_vulkan);
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(
+ parse_gpu_options(Some("vulkan=true,backend=virglrenderer"), &mut gpu_params)
+ .is_ok()
+ );
+ assert!(gpu_params.use_vulkan);
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(parse_gpu_options(Some("vulkan=false"), &mut gpu_params).is_ok());
+ assert!(!gpu_params.use_vulkan);
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(
+ parse_gpu_options(Some("backend=virglrenderer,vulkan=false"), &mut gpu_params)
+ .is_ok()
+ );
+ assert!(!gpu_params.use_vulkan);
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(
+ parse_gpu_options(Some("vulkan=false,backend=virglrenderer"), &mut gpu_params)
+ .is_ok()
+ );
+ assert!(!gpu_params.use_vulkan);
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(parse_gpu_options(
+ Some("backend=virglrenderer,vulkan=invalid_value"),
+ &mut gpu_params
+ )
+ .is_err());
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(parse_gpu_options(
+ Some("vulkan=invalid_value,backend=virglrenderer"),
+ &mut gpu_params
+ )
+ .is_err());
+ }
}
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
#[test]
fn parse_gpu_options_gfxstream_with_syncfd_specified() {
- assert!(
- parse_gpu_options(Some("backend=gfxstream,syncfd=true"))
- .unwrap()
- .gfxstream_use_syncfd
- );
- assert!(
- parse_gpu_options(Some("syncfd=true,backend=gfxstream"))
- .unwrap()
- .gfxstream_use_syncfd
- );
- assert!(
- !parse_gpu_options(Some("backend=gfxstream,syncfd=false"))
- .unwrap()
- .gfxstream_use_syncfd
- );
- assert!(
- !parse_gpu_options(Some("syncfd=false,backend=gfxstream"))
- .unwrap()
- .gfxstream_use_syncfd
- );
- assert!(parse_gpu_options(Some("backend=gfxstream,syncfd=invalid_value")).is_err());
- assert!(parse_gpu_options(Some("syncfd=invalid_value,backend=gfxstream")).is_err());
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(
+ parse_gpu_options(Some("backend=gfxstream,syncfd=true"), &mut gpu_params).is_ok()
+ );
+ assert!(gpu_params.gfxstream_use_syncfd);
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(
+ parse_gpu_options(Some("syncfd=true,backend=gfxstream"), &mut gpu_params).is_ok()
+ );
+ assert!(gpu_params.gfxstream_use_syncfd);
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(
+ parse_gpu_options(Some("backend=gfxstream,syncfd=false"), &mut gpu_params).is_ok()
+ );
+ assert!(!gpu_params.gfxstream_use_syncfd);
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(
+ parse_gpu_options(Some("syncfd=false,backend=gfxstream"), &mut gpu_params).is_ok()
+ );
+ assert!(!gpu_params.gfxstream_use_syncfd);
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(parse_gpu_options(
+ Some("backend=gfxstream,syncfd=invalid_value"),
+ &mut gpu_params
+ )
+ .is_err());
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(parse_gpu_options(
+ Some("syncfd=invalid_value,backend=gfxstream"),
+ &mut gpu_params
+ )
+ .is_err());
+ }
}
#[cfg(all(feature = "gpu", feature = "gfxstream"))]
#[test]
fn parse_gpu_options_not_gfxstream_with_syncfd_specified() {
- assert!(parse_gpu_options(Some("backend=virglrenderer,syncfd=true")).is_err());
- assert!(parse_gpu_options(Some("syncfd=true,backend=virglrenderer")).is_err());
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(
+ parse_gpu_options(Some("backend=virglrenderer,syncfd=true"), &mut gpu_params)
+ .is_err()
+ );
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(
+ parse_gpu_options(Some("syncfd=true,backend=virglrenderer"), &mut gpu_params)
+ .is_err()
+ );
+ }
+ }
+
+ #[cfg(feature = "gpu")]
+ #[test]
+ fn parse_gpu_display_options_valid() {
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(
+ parse_gpu_display_options(Some("width=500,height=600"), &mut gpu_params).is_ok()
+ );
+ assert_eq!(gpu_params.displays.len(), 1);
+ assert_eq!(gpu_params.displays[0].width, 500);
+ assert_eq!(gpu_params.displays[0].height, 600);
+ }
+ }
+
+ #[cfg(feature = "gpu")]
+ #[test]
+ fn parse_gpu_display_options_invalid() {
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(parse_gpu_display_options(Some("width=500"), &mut gpu_params).is_err());
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(parse_gpu_display_options(Some("height=500"), &mut gpu_params).is_err());
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(parse_gpu_display_options(Some("width"), &mut gpu_params).is_err());
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(parse_gpu_display_options(Some("blah"), &mut gpu_params).is_err());
+ }
+ }
+
+ #[cfg(feature = "gpu")]
+ #[test]
+ fn parse_gpu_options_and_gpu_display_options_valid() {
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(parse_gpu_options(Some("2D,width=500,height=600"), &mut gpu_params).is_ok());
+ assert!(
+ parse_gpu_display_options(Some("width=700,height=800"), &mut gpu_params).is_ok()
+ );
+ assert_eq!(gpu_params.displays.len(), 2);
+ assert_eq!(gpu_params.displays[0].width, 500);
+ assert_eq!(gpu_params.displays[0].height, 600);
+ assert_eq!(gpu_params.displays[1].width, 700);
+ assert_eq!(gpu_params.displays[1].height, 800);
+ }
+ {
+ let mut gpu_params: GpuParameters = Default::default();
+ assert!(parse_gpu_options(Some("2D"), &mut gpu_params).is_ok());
+ assert!(
+ parse_gpu_display_options(Some("width=700,height=800"), &mut gpu_params).is_ok()
+ );
+ assert_eq!(gpu_params.displays.len(), 1);
+ assert_eq!(gpu_params.displays[0].width, 700);
+ assert_eq!(gpu_params.displays[0].height, 800);
+ }
}
#[test]
@@ -2905,4 +4219,123 @@ mod tests {
fn parse_battery_invaild_type_value() {
parse_battery_options(Some("type=xxx")).expect_err("parse should have failed");
}
+
+ #[test]
+ fn parse_stub_pci() {
+ let params = parse_stub_pci_parameters(Some("0000:01:02.3,vendor=0xfffe,device=0xfffd,class=0xffc1c2,subsystem_vendor=0xfffc,subsystem_device=0xfffb,revision=0xa")).unwrap();
+ assert_eq!(params.address.bus, 1);
+ assert_eq!(params.address.dev, 2);
+ assert_eq!(params.address.func, 3);
+ assert_eq!(params.vendor_id, 0xfffe);
+ assert_eq!(params.device_id, 0xfffd);
+ assert_eq!(params.class as u8, PciClassCode::Other as u8);
+ assert_eq!(params.subclass, 0xc1);
+ assert_eq!(params.programming_interface, 0xc2);
+ assert_eq!(params.subsystem_vendor_id, 0xfffc);
+ assert_eq!(params.subsystem_device_id, 0xfffb);
+ assert_eq!(params.revision_id, 0xa);
+ }
+
+ #[cfg(feature = "direct")]
+ #[test]
+ fn parse_direct_io_options_valid() {
+ let params = parse_direct_io_options(Some("/dev/mem@1,100-110")).unwrap();
+ assert_eq!(params.path.to_str(), Some("/dev/mem"));
+ assert_eq!(params.ranges[0], BusRange { base: 1, len: 1 });
+ assert_eq!(params.ranges[1], BusRange { base: 100, len: 11 });
+ }
+
+ #[cfg(feature = "direct")]
+ #[test]
+ fn parse_direct_io_options_hex() {
+ let params = parse_direct_io_options(Some("/dev/mem@1,0x10,100-110,0x10-0x20")).unwrap();
+ assert_eq!(params.path.to_str(), Some("/dev/mem"));
+ assert_eq!(params.ranges[0], BusRange { base: 1, len: 1 });
+ assert_eq!(params.ranges[1], BusRange { base: 0x10, len: 1 });
+ assert_eq!(params.ranges[2], BusRange { base: 100, len: 11 });
+ assert_eq!(
+ params.ranges[3],
+ BusRange {
+ base: 0x10,
+ len: 0x11
+ }
+ );
+ }
+
+ #[cfg(feature = "direct")]
+ #[test]
+ fn parse_direct_io_options_invalid() {
+ assert!(parse_direct_io_options(Some("/dev/mem@0y10"))
+ .unwrap_err()
+ .to_string()
+ .contains("invalid base range value"));
+
+ assert!(parse_direct_io_options(Some("/dev/mem@"))
+ .unwrap_err()
+ .to_string()
+ .contains("invalid base range value"));
+ }
+
+ #[test]
+ fn parse_file_backed_mapping_valid() {
+ let params = parse_file_backed_mapping(Some(
+ "addr=0x1000,size=0x2000,path=/dev/mem,offset=0x3000,ro,rw,sync",
+ ))
+ .unwrap();
+ assert_eq!(params.address, 0x1000);
+ assert_eq!(params.size, 0x2000);
+ assert_eq!(params.path, PathBuf::from("/dev/mem"));
+ assert_eq!(params.offset, 0x3000);
+ assert!(params.writable);
+ assert!(params.sync);
+ }
+
+ #[test]
+ fn parse_file_backed_mapping_incomplete() {
+ assert!(parse_file_backed_mapping(Some("addr=0x1000,size=0x2000"))
+ .unwrap_err()
+ .to_string()
+ .contains("required"));
+ assert!(parse_file_backed_mapping(Some("size=0x2000,path=/dev/mem"))
+ .unwrap_err()
+ .to_string()
+ .contains("required"));
+ assert!(parse_file_backed_mapping(Some("addr=0x1000,path=/dev/mem"))
+ .unwrap_err()
+ .to_string()
+ .contains("required"));
+ }
+
+ #[test]
+ fn parse_file_backed_mapping_unaligned() {
+ assert!(
+ parse_file_backed_mapping(Some("addr=0x1001,size=0x2000,path=/dev/mem"))
+ .unwrap_err()
+ .to_string()
+ .contains("aligned")
+ );
+ assert!(
+ parse_file_backed_mapping(Some("addr=0x1000,size=0x2001,path=/dev/mem"))
+ .unwrap_err()
+ .to_string()
+ .contains("aligned")
+ );
+ }
+
+ #[test]
+ fn parse_file_backed_mapping_align() {
+ let params =
+ parse_file_backed_mapping(Some("addr=0x3042,size=0xff0,path=/dev/mem,align")).unwrap();
+ assert_eq!(params.address, 0x3000);
+ assert_eq!(params.size, 0x2000);
+ }
+
+ #[test]
+ fn parse_userspace_msr_options_test() {
+ let index = parse_userspace_msr_options("0x10,action=r0").unwrap();
+ assert_eq!(index, 0x10);
+ assert!(parse_userspace_msr_options("0x10,action=none").is_err());
+ assert!(parse_userspace_msr_options("0x10").is_err());
+ assert!(parse_userspace_msr_options("hoge").is_err());
+ }
}
diff --git a/src/plugin/mod.rs b/src/plugin/mod.rs
index 8236425c0..32425b0b3 100644
--- a/src/plugin/mod.rs
+++ b/src/plugin/mod.rs
@@ -5,12 +5,11 @@
mod process;
mod vcpu;
-use std::fmt::{self, Display};
use std::fs::File;
use std::io;
+use std::io::Read;
use std::os::unix::net::UnixDatagram;
use std::path::Path;
-use std::result;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Barrier};
use std::thread;
@@ -19,21 +18,23 @@ use std::time::{Duration, Instant};
use libc::{
c_int, c_ulong, fcntl, ioctl, socketpair, AF_UNIX, EAGAIN, EBADF, EDEADLK, EEXIST, EINTR,
EINVAL, ENOENT, EOVERFLOW, EPERM, FIOCLEX, F_SETPIPE_SZ, MS_NODEV, MS_NOEXEC, MS_NOSUID,
- MS_RDONLY, SIGCHLD, SOCK_SEQPACKET,
+ MS_RDONLY, O_NONBLOCK, SIGCHLD, SOCK_SEQPACKET,
};
+use anyhow::{anyhow, bail, Context, Result};
use protobuf::ProtobufError;
use remain::sorted;
+use thiserror::Error;
use base::{
- block_signal, clear_signal, drop_capabilities, error, getegid, geteuid, info, pipe,
- register_rt_signal_handler, validate_raw_descriptor, warn, AsRawDescriptor, Error as SysError,
- Event, FromRawDescriptor, Killable, MmapError, PollToken, Result as SysResult, SignalFd,
- SignalFdError, WaitContext, SIGRTMIN,
+ add_fd_flags, block_signal, clear_signal, drop_capabilities, enable_core_scheduling, error,
+ getegid, geteuid, info, pipe, register_rt_signal_handler, validate_raw_descriptor, warn,
+ AsRawDescriptor, Descriptor, Error as SysError, Event, FromRawDescriptor, Killable, MmapError,
+ PollToken, RawDescriptor, Result as SysResult, SignalFd, WaitContext, SIGRTMIN,
};
use kvm::{Cap, Datamatch, IoeventAddress, Kvm, Vcpu, VcpuExit, Vm};
use minijail::{self, Minijail};
-use net_util::{Error as TapError, Tap, TapT};
+use net_util::{Tap, TapT};
use vm_memory::{GuestMemory, MemoryPolicy};
use self::process::*;
@@ -42,145 +43,24 @@ use crate::{Config, Executable};
const MAX_DATAGRAM_SIZE: usize = 4096;
const MAX_VCPU_DATAGRAM_SIZE: usize = 0x40000;
+const CROSVM_GPU_SERVER_FD_ENV: &str = "CROSVM_GPU_SERVER_FD";
-/// An error that occurs during the lifetime of a plugin process.
+/// An error that occurs when communicating with the plugin process.
#[sorted]
-pub enum Error {
- CloneEvent(SysError),
- CloneVcpuPipe(io::Error),
- CreateEvent(SysError),
- CreateIrqChip(SysError),
- CreateJail(minijail::Error),
- CreateKvm(SysError),
- CreateMainSocket(SysError),
- CreatePIT(SysError),
- CreateSignalFd(SignalFdError),
- CreateSocketPair(io::Error),
- CreateTapFd(TapError),
- CreateVcpu(SysError),
- CreateVcpuSocket(SysError),
- CreateVm(SysError),
- CreateWaitContext(SysError),
+#[derive(Error, Debug)]
+pub enum CommError {
+ #[error("failed to decode plugin request: {0}")]
DecodeRequest(ProtobufError),
- DropCapabilities(SysError),
+ #[error("failed to encode plugin response: {0}")]
EncodeResponse(ProtobufError),
- Mount(minijail::Error),
- MountDev(minijail::Error),
- MountLib(minijail::Error),
- MountLib64(minijail::Error),
- MountPlugin(minijail::Error),
- MountPluginLib(minijail::Error),
- MountProc(minijail::Error),
- MountRoot(minijail::Error),
- NoRootDir,
- ParsePivotRoot(minijail::Error),
- ParseSeccomp(minijail::Error),
- PluginFailed(i32),
- PluginKill(SysError),
- PluginKilled(i32),
- PluginRunJail(minijail::Error),
+ #[error("plugin request socket has been hung up")]
PluginSocketHup,
- PluginSocketPoll(SysError),
+ #[error("failed to recv from plugin request socket: {0}")]
PluginSocketRecv(SysError),
+ #[error("failed to send to plugin request socket: {0}")]
PluginSocketSend(SysError),
- PluginSpawn(io::Error),
- PluginTimeout,
- PluginWait(SysError),
- Poll(SysError),
- RootNotAbsolute,
- RootNotDir,
- SetGidMap(minijail::Error),
- SetUidMap(minijail::Error),
- SigChild {
- pid: u32,
- signo: u32,
- status: i32,
- code: i32,
- },
- SignalFd(SignalFdError),
- SpawnVcpu(io::Error),
- TapEnable(TapError),
- TapOpen(TapError),
- TapSetIp(TapError),
- TapSetMacAddress(TapError),
- TapSetNetmask(TapError),
- ValidateTapFd(SysError),
- WaitContextAdd(SysError),
}
-impl Display for Error {
- #[remain::check]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- #[sorted]
- match self {
- CloneEvent(e) => write!(f, "failed to clone event: {}", e),
- CloneVcpuPipe(e) => write!(f, "failed to clone vcpu pipe: {}", e),
- CreateEvent(e) => write!(f, "failed to create event: {}", e),
- CreateIrqChip(e) => write!(f, "failed to create kvm irqchip: {}", e),
- CreateJail(e) => write!(f, "failed to create jail: {}", e),
- CreateKvm(e) => write!(f, "error creating Kvm: {}", e),
- CreateMainSocket(e) => write!(f, "error creating main request socket: {}", e),
- CreatePIT(e) => write!(f, "failed to create kvm PIT: {}", e),
- CreateSignalFd(e) => write!(f, "failed to create signalfd: {}", e),
- CreateSocketPair(e) => write!(f, "failed to create socket pair: {}", e),
- CreateTapFd(e) => write!(f, "failed to create tap device from raw fd: {}", e),
- CreateVcpu(e) => write!(f, "error creating vcpu: {}", e),
- CreateVcpuSocket(e) => write!(f, "error creating vcpu request socket: {}", e),
- CreateVm(e) => write!(f, "error creating vm: {}", e),
- CreateWaitContext(e) => write!(f, "failed to create wait context: {}", e),
- DecodeRequest(e) => write!(f, "failed to decode plugin request: {}", e),
- DropCapabilities(e) => write!(f, "failed to drop process capabilities: {}", e),
- EncodeResponse(e) => write!(f, "failed to encode plugin response: {}", e),
- Mount(e) | MountDev(e) | MountLib(e) | MountLib64(e) | MountPlugin(e)
- | MountPluginLib(e) | MountProc(e) | MountRoot(e) => {
- write!(f, "failed to mount: {}", e)
- }
- NoRootDir => write!(f, "no root directory for jailed process to pivot root into"),
- ParsePivotRoot(e) => write!(f, "failed to set jail pivot root: {}", e),
- ParseSeccomp(e) => write!(f, "failed to parse jail seccomp filter: {}", e),
- PluginFailed(e) => write!(f, "plugin exited with error: {}", e),
- PluginKill(e) => write!(f, "error sending kill signal to plugin: {}", e),
- PluginKilled(e) => write!(f, "plugin exited with signal {}", e),
- PluginRunJail(e) => write!(f, "failed to run jail: {}", e),
- PluginSocketHup => write!(f, "plugin request socket has been hung up"),
- PluginSocketPoll(e) => write!(f, "failed to poll plugin request sockets: {}", e),
- PluginSocketRecv(e) => write!(f, "failed to recv from plugin request socket: {}", e),
- PluginSocketSend(e) => write!(f, "failed to send to plugin request socket: {}", e),
- PluginSpawn(e) => write!(f, "failed to spawn plugin: {}", e),
- PluginTimeout => write!(f, "plugin did not exit within timeout"),
- PluginWait(e) => write!(f, "error waiting for plugin to exit: {}", e),
- Poll(e) => write!(f, "failed to poll all FDs: {}", e),
- RootNotAbsolute => write!(f, "path to the root directory must be absolute"),
- RootNotDir => write!(f, "specified root directory is not a directory"),
- SetGidMap(e) => write!(f, "failed to set gidmap for jail: {}", e),
- SetUidMap(e) => write!(f, "failed to set uidmap for jail: {}", e),
- SigChild {
- pid,
- signo,
- status,
- code,
- } => write!(
- f,
- "process {} died with signal {}, status {}, and code {}",
- pid, signo, status, code
- ),
- SignalFd(e) => write!(f, "failed to read signal fd: {}", e),
- SpawnVcpu(e) => write!(f, "error spawning vcpu thread: {}", e),
- TapEnable(e) => write!(f, "error enabling tap device: {}", e),
- TapOpen(e) => write!(f, "error opening tap device: {}", e),
- TapSetIp(e) => write!(f, "error setting tap ip: {}", e),
- TapSetMacAddress(e) => write!(f, "error setting tap mac address: {}", e),
- TapSetNetmask(e) => write!(f, "error setting tap netmask: {}", e),
- ValidateTapFd(e) => write!(f, "failed to validate raw tap fd: {}", e),
- WaitContextAdd(e) => write!(f, "failed to add descriptor to wait context: {}", e),
- }
- }
-}
-
-type Result<T> = result::Result<T, Error>;
-
fn new_seqpacket_pair() -> SysResult<(UnixDatagram, UnixDatagram)> {
let mut fds = [0, 0];
unsafe {
@@ -267,19 +147,20 @@ fn mmap_to_sys_err(e: MmapError) -> SysError {
fn create_plugin_jail(root: &Path, log_failures: bool, seccomp_policy: &Path) -> Result<Minijail> {
// All child jails run in a new user namespace without any users mapped,
// they run as nobody unless otherwise configured.
- let mut j = Minijail::new().map_err(Error::CreateJail)?;
+ let mut j = Minijail::new().context("failed to create jail")?;
j.namespace_pids();
j.namespace_user();
j.uidmap(&format!("0 {0} 1", geteuid()))
- .map_err(Error::SetUidMap)?;
+ .context("failed to set uidmap for jail")?;
j.gidmap(&format!("0 {0} 1", getegid()))
- .map_err(Error::SetGidMap)?;
+ .context("failed to set gidmap for jail")?;
j.namespace_user_disable_setgroups();
// Don't need any capabilities.
j.use_caps(0);
// Create a new mount namespace with an empty root FS.
j.namespace_vfs();
- j.enter_pivot_root(root).map_err(Error::ParsePivotRoot)?;
+ j.enter_pivot_root(root)
+ .context("failed to set jail pivot root")?;
// Run in an empty network namespace.
j.namespace_net();
j.no_new_privs();
@@ -293,7 +174,7 @@ fn create_plugin_jail(root: &Path, log_failures: bool, seccomp_policy: &Path) ->
let bpf_policy_file = seccomp_policy.with_extension("bpf");
if bpf_policy_file.exists() && !log_failures {
j.parse_seccomp_program(&bpf_policy_file)
- .map_err(Error::ParseSeccomp)?;
+ .context("failed to parse jail seccomp BPF program")?;
} else {
// Use TSYNC only for the side effect of it using SECCOMP_RET_TRAP,
// which will correctly kill the entire device process if a worker
@@ -303,7 +184,7 @@ fn create_plugin_jail(root: &Path, log_failures: bool, seccomp_policy: &Path) ->
j.log_seccomp_filter_failures();
}
j.parse_seccomp_filters(&seccomp_policy.with_extension("policy"))
- .map_err(Error::ParseSeccomp)?;
+ .context("failed to parse jail seccomp filter")?;
}
j.use_seccomp_filter();
// Don't do init setup.
@@ -318,7 +199,7 @@ fn create_plugin_jail(root: &Path, log_failures: bool, seccomp_policy: &Path) ->
(MS_NOSUID | MS_NODEV | MS_NOEXEC) as usize,
"size=67108864",
)
- .map_err(Error::MountRoot)?;
+ .context("failed to mount root")?;
// Because we requested to "run as init", minijail will not mount /proc for us even though
// plugin will be running in its own PID namespace, so we have to mount it ourselves.
@@ -328,7 +209,7 @@ fn create_plugin_jail(root: &Path, log_failures: bool, seccomp_policy: &Path) ->
"proc",
(MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_RDONLY) as usize,
)
- .map_err(Error::MountProc)?;
+ .context("failed to mount proc")?;
Ok(j)
}
@@ -441,9 +322,9 @@ pub fn run_vcpus(
for cpu_id in 0..vcpu_count {
let kill_signaled = kill_signaled.clone();
let vcpu_thread_barrier = vcpu_thread_barrier.clone();
- let vcpu_exit_evt = exit_evt.try_clone().map_err(Error::CloneEvent)?;
+ let vcpu_exit_evt = exit_evt.try_clone().context("failed to clone event")?;
let vcpu_plugin = plugin.create_vcpu(cpu_id)?;
- let vcpu = Vcpu::new(cpu_id as c_ulong, kvm, vm).map_err(Error::CreateVcpu)?;
+ let vcpu = Vcpu::new(cpu_id as c_ulong, kvm, vm).context("error creating vcpu")?;
vcpu_handles.push(
thread::Builder::new()
@@ -456,8 +337,7 @@ pub fn run_vcpus(
.expect("failed to set up KVM VCPU signal mask");
}
- #[cfg(feature = "chromeos")]
- if let Err(e) = base::sched::enable_core_scheduling() {
+ if let Err(e) = enable_core_scheduling() {
error!("Failed to enable core scheduling: {}", e);
}
@@ -478,12 +358,18 @@ pub fn run_vcpus(
VcpuExit::IoIn { port, mut size } => {
let mut data = [0; 256];
if size > data.len() {
- error!("unsupported IoIn size of {} bytes", size);
+ error!(
+ "unsupported IoIn size of {} bytes at port {:#x}",
+ size, port
+ );
size = data.len();
}
vcpu_plugin.io_read(port as u64, &mut data[..size], &vcpu);
if let Err(e) = vcpu.set_data(&data[..size]) {
- error!("failed to set return data for IoIn: {}", e);
+ error!(
+ "failed to set return data for IoIn at port {:#x}: {}",
+ port, e
+ );
}
}
VcpuExit::IoOut {
@@ -492,7 +378,7 @@ pub fn run_vcpus(
data,
} => {
if size > data.len() {
- error!("unsupported IoOut size of {} bytes", size);
+ error!("unsupported IoOut size of {} bytes at port {:#x}", size, port);
size = data.len();
}
vcpu_plugin.io_write(port as u64, &data[..size], &vcpu);
@@ -582,7 +468,7 @@ pub fn run_vcpus(
.write(1)
.expect("failed to signal vcpu exit event");
})
- .map_err(Error::SpawnVcpu)?,
+ .context("error spawning vcpu thread")?,
);
}
Ok(())
@@ -592,6 +478,7 @@ pub fn run_vcpus(
enum Token {
Exit,
ChildSignal,
+ Stderr,
Plugin { index: usize },
}
@@ -605,9 +492,51 @@ pub fn run_config(cfg: Config) -> Result<()> {
// Masking signals is inherently dangerous, since this can persist across clones/execs. Do this
// before any jailed devices have been spawned, so that we can catch any of them that fail very
// quickly.
- let sigchld_fd = SignalFd::new(SIGCHLD).map_err(Error::CreateSignalFd)?;
+ let sigchld_fd = SignalFd::new(SIGCHLD).context("failed to create signalfd")?;
- let jail = if cfg.sandbox {
+ // Create a pipe to capture error messages from plugin and minijail.
+ let (mut stderr_rd, stderr_wr) = pipe(true).context("failed to create stderr pipe")?;
+ add_fd_flags(stderr_rd.as_raw_descriptor(), O_NONBLOCK)
+ .context("error marking stderr nonblocking")?;
+
+ #[allow(unused_mut)]
+ let mut env_fds: Vec<(String, Descriptor)> = Vec::default();
+
+ let _default_render_server_params = crate::platform::GpuRenderServerParameters {
+ path: std::path::PathBuf::from("/usr/libexec/virgl_render_server"),
+ cache_path: None,
+ cache_size: None,
+ };
+
+ #[cfg(feature = "gpu")]
+ let gpu_render_server_parameters = if let Some(parameters) = &cfg.gpu_render_server_parameters {
+ Some(parameters)
+ } else {
+ if cfg!(feature = "plugin-render-server") {
+ Some(&_default_render_server_params)
+ } else {
+ None
+ }
+ };
+
+ #[cfg(feature = "gpu")]
+ // Hold on to the render server jail so it keeps running until we exit run_config()
+ let (_render_server_jail, _render_server_fd) =
+ if let Some(parameters) = &gpu_render_server_parameters {
+ let (jail, fd) = crate::platform::gpu::start_gpu_render_server(&cfg, parameters)?;
+ env_fds.push((
+ CROSVM_GPU_SERVER_FD_ENV.to_string(),
+ Descriptor(fd.as_raw_descriptor()),
+ ));
+ (
+ Some(crate::platform::jail_helpers::ScopedMinijail(jail)),
+ Some(fd),
+ )
+ } else {
+ (None, None)
+ };
+
+ let jail = if let Some(jail_config) = &cfg.jail_config {
// An empty directory for jailed plugin pivot root.
let root_path = match &cfg.plugin_root {
Some(dir) => dir,
@@ -615,19 +544,20 @@ pub fn run_config(cfg: Config) -> Result<()> {
};
if root_path.is_relative() {
- return Err(Error::RootNotAbsolute);
+ bail!("path to the root directory must be absolute");
}
if !root_path.exists() {
- return Err(Error::NoRootDir);
+ bail!("no root directory for jailed process to pivot root into");
}
if !root_path.is_dir() {
- return Err(Error::RootNotDir);
+ bail!("specified root directory is not a directory");
}
- let policy_path = cfg.seccomp_policy_dir.join("plugin");
- let mut jail = create_plugin_jail(root_path, cfg.seccomp_log_failures, &policy_path)?;
+ let policy_path = jail_config.seccomp_policy_dir.join("plugin");
+ let mut jail =
+ create_plugin_jail(root_path, jail_config.seccomp_log_failures, &policy_path)?;
// Update gid map of the jail if caller provided supplemental groups.
if !cfg.plugin_gid_maps.is_empty() {
@@ -637,7 +567,7 @@ pub fn run_config(cfg: Config) -> Result<()> {
.into_iter()
.map(|m| format!(",{} {} {}", m.inner, m.outer, m.count))
.collect::<String>();
- jail.gidmap(&map).map_err(Error::SetGidMap)?;
+ jail.gidmap(&map).context("failed to set gidmap for jail")?;
}
// Mount minimal set of devices (full, zero, urandom, etc). We can not use
@@ -646,12 +576,23 @@ pub fn run_config(cfg: Config) -> Result<()> {
for name in &device_names {
let device = Path::new("/dev").join(&name);
jail.mount_bind(&device, &device, true)
- .map_err(Error::MountDev)?;
+ .context("failed to mount dev")?;
}
for bind_mount in &cfg.plugin_mounts {
jail.mount_bind(&bind_mount.src, &bind_mount.dst, bind_mount.writable)
- .map_err(Error::Mount)?;
+ .with_context(|| {
+ format!(
+ "failed to bind mount {} -> {} as {} ",
+ bind_mount.src.display(),
+ bind_mount.dst.display(),
+ if bind_mount.writable {
+ "writable"
+ } else {
+ "read only"
+ }
+ )
+ })?;
}
Some(jail)
@@ -663,13 +604,14 @@ pub fn run_config(cfg: Config) -> Result<()> {
if let Some(host_ip) = cfg.host_ip {
if let Some(netmask) = cfg.netmask {
if let Some(mac_address) = cfg.mac_address {
- let tap = Tap::new(false, false).map_err(Error::TapOpen)?;
- tap.set_ip_addr(host_ip).map_err(Error::TapSetIp)?;
- tap.set_netmask(netmask).map_err(Error::TapSetNetmask)?;
+ let tap = Tap::new(false, false).context("error opening tap device")?;
+ tap.set_ip_addr(host_ip).context("error setting tap ip")?;
+ tap.set_netmask(netmask)
+ .context("error setting tap netmask")?;
tap.set_mac_address(mac_address)
- .map_err(Error::TapSetMacAddress)?;
+ .context("error setting tap mac address")?;
- tap.enable().map_err(Error::TapEnable)?;
+ tap.enable().context("error enabling tap device")?;
tap_interfaces.push(tap);
}
}
@@ -677,8 +619,10 @@ pub fn run_config(cfg: Config) -> Result<()> {
for tap_fd in cfg.tap_fd {
// Safe because we ensure that we get a unique handle to the fd.
let tap = unsafe {
- Tap::from_raw_descriptor(validate_raw_descriptor(tap_fd).map_err(Error::ValidateTapFd)?)
- .map_err(Error::CreateTapFd)?
+ Tap::from_raw_descriptor(
+ validate_raw_descriptor(tap_fd).context("failed to validate raw tap fd")?,
+ )
+ .context("failed to create tap device from raw fd")?
};
tap_interfaces.push(tap);
}
@@ -696,28 +640,39 @@ pub fn run_config(cfg: Config) -> Result<()> {
mem_policy |= MemoryPolicy::USE_HUGEPAGES;
}
mem.set_memory_policy(mem_policy);
- let kvm = Kvm::new_with_path(&cfg.kvm_device_path).map_err(Error::CreateKvm)?;
- let mut vm = Vm::new(&kvm, mem).map_err(Error::CreateVm)?;
- vm.create_irq_chip().map_err(Error::CreateIrqChip)?;
- vm.create_pit().map_err(Error::CreatePIT)?;
-
- let mut plugin = Process::new(vcpu_count, plugin_path, &plugin_args, jail)?;
+ let kvm = Kvm::new_with_path(&cfg.kvm_device_path).context("error creating Kvm")?;
+ let mut vm = Vm::new(&kvm, mem).context("error creating vm")?;
+ vm.create_irq_chip()
+ .context("failed to create kvm irqchip")?;
+ vm.create_pit().context("failed to create kvm PIT")?;
+
+ let mut plugin = Process::new(
+ vcpu_count,
+ plugin_path,
+ &plugin_args,
+ jail,
+ stderr_wr,
+ env_fds,
+ )?;
// Now that the jail for the plugin has been created and we had a chance to adjust gids there,
// we can drop all our capabilities in case we had any.
- drop_capabilities().map_err(Error::DropCapabilities)?;
+ drop_capabilities().context("failed to drop process capabilities")?;
let mut res = Ok(());
// If Some, we will exit after enough time is passed to shutdown cleanly.
let mut dying_instant: Option<Instant> = None;
let duration_to_die = Duration::from_millis(1000);
- let exit_evt = Event::new().map_err(Error::CreateEvent)?;
+ let exit_evt = Event::new().context("failed to create event")?;
let kill_signaled = Arc::new(AtomicBool::new(false));
let mut vcpu_handles = Vec::with_capacity(vcpu_count as usize);
- let wait_ctx =
- WaitContext::build_with(&[(&exit_evt, Token::Exit), (&sigchld_fd, Token::ChildSignal)])
- .map_err(Error::WaitContextAdd)?;
+ let wait_ctx = WaitContext::build_with(&[
+ (&exit_evt, Token::Exit),
+ (&sigchld_fd, Token::ChildSignal),
+ (&stderr_rd, Token::Stderr),
+ ])
+ .context("failed to add control descriptors to wait context")?;
let mut sockets_to_drop = Vec::new();
let mut redo_wait_ctx_sockets = true;
@@ -738,7 +693,7 @@ pub fn run_config(cfg: Config) -> Result<()> {
for (index, socket) in plugin.sockets().iter().enumerate() {
wait_ctx
.add(socket, Token::Plugin { index })
- .map_err(Error::WaitContextAdd)?;
+ .context("failed to add plugin sockets to wait context")?;
}
}
@@ -753,12 +708,19 @@ pub fn run_config(cfg: Config) -> Result<()> {
Err(e) => {
// Polling no longer works, time to break and cleanup,
if res.is_ok() {
- res = Err(Error::Poll(e));
+ res = Err(e).context("failed to poll all FDs");
}
break;
}
}
};
+
+ for event in events.iter().filter(|e| e.is_hungup) {
+ if let Token::Stderr = event.token {
+ let _ = wait_ctx.delete(&stderr_rd);
+ }
+ }
+
for event in events.iter().filter(|e| e.is_readable) {
match event.token {
Token::Exit => {
@@ -767,7 +729,7 @@ pub fn run_config(cfg: Config) -> Result<()> {
dying_instant.get_or_insert(Instant::now());
let sig_res = plugin.signal_kill();
if res.is_ok() && sig_res.is_err() {
- res = sig_res.map_err(Error::PluginKill);
+ res = sig_res.context("error sending kill signal to plugin on exit event");
}
}
Token::ChildSignal => {
@@ -783,12 +745,13 @@ pub fn run_config(cfg: Config) -> Result<()> {
// Because SIGCHLD is not expected from anything other than the
// plugin process, report it as an error.
if res.is_ok() {
- res = Err(Error::SigChild {
- pid: siginfo.ssi_pid,
- signo: siginfo.ssi_signo,
- status: siginfo.ssi_status,
- code: siginfo.ssi_code,
- })
+ res = Err(anyhow!(
+ "process {} died with signal {}, status {}, and code {}",
+ siginfo.ssi_pid,
+ siginfo.ssi_signo,
+ siginfo.ssi_status,
+ siginfo.ssi_code,
+ ));
}
}
Ok(None) => break, // No more signals to read.
@@ -796,7 +759,7 @@ pub fn run_config(cfg: Config) -> Result<()> {
// Something really must be messed up for this to happen, continue
// processing connections for a limited time.
if res.is_ok() {
- res = Err(Error::SignalFd(e));
+ res = Err(e).context("failed to read signal fd");
}
break;
}
@@ -807,16 +770,33 @@ pub fn run_config(cfg: Config) -> Result<()> {
dying_instant.get_or_insert(Instant::now());
let sig_res = plugin.signal_kill();
if res.is_ok() && sig_res.is_err() {
- res = sig_res.map_err(Error::PluginKill);
+ res = sig_res.context("error sending kill signal to plugin on SIGCHLD");
}
}
+ Token::Stderr => loop {
+ let mut buf = [0u8; 4096];
+ match stderr_rd.read(&mut buf) {
+ Ok(len) => {
+ for l in String::from_utf8_lossy(&buf[0..len]).lines() {
+ error!("minijail/plugin: {}", l);
+ }
+ }
+ Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
+ break;
+ }
+ Err(e) => {
+ error!("failed reading from stderr: {}", e);
+ break;
+ }
+ }
+ },
Token::Plugin { index } => {
match plugin.handle_socket(index, &kvm, &mut vm, &vcpu_handles, &tap_interfaces)
{
Ok(_) => {}
// A HUP is an expected event for a socket, so don't bother warning about
// it.
- Err(Error::PluginSocketHup) => sockets_to_drop.push(index),
+ Err(CommError::PluginSocketHup) => sockets_to_drop.push(index),
// Only one connection out of potentially many is broken. Drop it, but don't
// start cleaning up. Because the error isn't returned, we will warn about
// it here.
@@ -865,7 +845,9 @@ pub fn run_config(cfg: Config) -> Result<()> {
// Depending on how we ended up here, the plugin process, or a VCPU thread waiting for requests
// might be stuck. The `signal_kill` call will unstick all the VCPU threads by closing their
// blocked connections.
- plugin.signal_kill().map_err(Error::PluginKill)?;
+ plugin
+ .signal_kill()
+ .context("error sending kill signal to plugin on cleanup")?;
for handle in vcpu_handles {
match handle.kill(SIGRTMIN() + 0) {
Ok(_) => {
@@ -879,11 +861,11 @@ pub fn run_config(cfg: Config) -> Result<()> {
match plugin.try_wait() {
// The plugin has run out of time by now
- Ok(ProcessStatus::Running) => Err(Error::PluginTimeout),
+ Ok(ProcessStatus::Running) => Err(anyhow!("plugin did not exit within timeout")),
// Return an error discovered earlier in this function.
- Ok(ProcessStatus::Success) => res,
- Ok(ProcessStatus::Fail(code)) => Err(Error::PluginFailed(code)),
- Ok(ProcessStatus::Signal(code)) => Err(Error::PluginKilled(code)),
- Err(e) => Err(Error::PluginWait(e)),
+ Ok(ProcessStatus::Success) => res.map_err(anyhow::Error::msg),
+ Ok(ProcessStatus::Fail(code)) => Err(anyhow!("plugin exited with error: {}", code)),
+ Ok(ProcessStatus::Signal(code)) => Err(anyhow!("plugin exited with signal {}", code)),
+ Err(e) => Err(anyhow!("error waiting for plugin to exit: {}", e)),
}
}
diff --git a/src/plugin/process.rs b/src/plugin/process.rs
index c34b708bb..88d078ff6 100644
--- a/src/plugin/process.rs
+++ b/src/plugin/process.rs
@@ -5,24 +5,27 @@
use std::collections::hash_map::{Entry, HashMap, VacantEntry};
use std::env::set_var;
use std::fs::File;
-use std::io::{IoSlice, Write};
+use std::io::{IoSlice, IoSliceMut, Write};
use std::mem::transmute;
use std::os::unix::net::UnixDatagram;
use std::path::Path;
use std::process::Command;
+use std::result;
use std::sync::{Arc, RwLock};
use std::thread::JoinHandle;
use net_util::Error as NetError;
-use libc::{pid_t, waitpid, EINVAL, ENODATA, ENOTTY, WEXITSTATUS, WIFEXITED, WNOHANG, WTERMSIG};
+use libc::{
+ pid_t, waitpid, EINVAL, ENODATA, ENOTTY, STDERR_FILENO, WEXITSTATUS, WIFEXITED, WNOHANG,
+ WTERMSIG,
+};
use protobuf::Message;
use base::{
- error, AsRawDescriptor, Error as SysError, Event, IntoRawDescriptor, Killable,
- MemoryMappingBuilder, RawDescriptor, Result as SysResult, ScmSocket, SharedMemory,
- SharedMemoryUnix, SIGRTMIN,
+ error, AsRawDescriptor, Descriptor, Error as SysError, Event, IntoRawDescriptor, Killable,
+ MemoryMappingBuilder, Result as SysResult, ScmSocket, SharedMemory, SharedMemoryUnix, SIGRTMIN,
};
use kvm::{dirty_log_bitmap_size, Datamatch, IoeventAddress, IrqRoute, IrqSource, PicId, Vm};
use kvm_sys::{kvm_clock_data, kvm_ioapic_state, kvm_pic_state, kvm_pit_state2};
@@ -48,6 +51,8 @@ unsafe impl DataInit for VmPitState {}
struct VmClockState(kvm_clock_data);
unsafe impl DataInit for VmClockState {}
+const CROSVM_SOCKET_ENV: &str = "CROSVM_SOCKET";
+
fn get_vm_state(vm: &Vm, state_set: MainRequest_StateSet) -> SysResult<Vec<u8>> {
Ok(match state_set {
MainRequest_StateSet::PIC0 => VmPicState(vm.get_pic_state(PicId::Primary)?)
@@ -137,47 +142,84 @@ impl Process {
/// Set the `jail` argument to spawn the plugin process within the preconfigured jail.
/// Due to an API limitation in libminijail necessitating that this function set an environment
/// variable, this function is not thread-safe.
+ ///
+ /// Arguments:
+ ///
+ /// * `cpu_count`: number of vcpus
+ /// * `cmd`: path to plugin executable
+ /// * `args`: arguments to plugin executable
+ /// * `jail`: jail to launch plugin in. If None plugin will just be spawned as a child
+ /// * `stderr`: File to redirect stderr of plugin process to
+ /// * `env_fds`: collection of (Name, FD) where FD will be inherited by spawned process
+ /// and added to child's environment as a variable Name
pub fn new(
cpu_count: u32,
cmd: &Path,
args: &[&str],
jail: Option<Minijail>,
+ stderr: File,
+ env_fds: Vec<(String, Descriptor)>,
) -> Result<Process> {
let (request_socket, child_socket) =
- new_seqpacket_pair().map_err(Error::CreateMainSocket)?;
+ new_seqpacket_pair().context("error creating main request socket")?;
let mut vcpu_pipes: Vec<VcpuPipe> = Vec::with_capacity(cpu_count as usize);
for _ in 0..cpu_count {
- vcpu_pipes.push(new_pipe_pair().map_err(Error::CreateVcpuSocket)?);
- }
- let mut per_vcpu_states: Vec<Arc<Mutex<PerVcpuState>>> =
- Vec::with_capacity(cpu_count as usize);
- // TODO(zachr): replace with `resize_default` when that stabilizes. Using a plain `resize`
- // is incorrect because each element in the `Vec` will contain a shared reference to the
- // same `PerVcpuState` instance. This happens because `resize` fills new slots using clones
- // of the instance given to `resize`.
- for _ in 0..cpu_count {
- per_vcpu_states.push(Default::default());
+ vcpu_pipes.push(new_pipe_pair().context("error creating vcpu request socket")?);
}
+ let mut per_vcpu_states: Vec<Arc<Mutex<PerVcpuState>>> = Vec::new();
+ per_vcpu_states.resize_with(cpu_count as usize, Default::default);
+
let plugin_pid = match jail {
Some(jail) => {
set_var(
- "CROSVM_SOCKET",
+ CROSVM_SOCKET_ENV,
child_socket.as_raw_descriptor().to_string(),
);
- jail.run(cmd, &[0, 1, 2, child_socket.as_raw_descriptor()], args)
- .map_err(Error::PluginRunJail)?
- }
- None => Command::new(cmd)
- .args(args)
- .env(
- "CROSVM_SOCKET",
- child_socket.as_raw_descriptor().to_string(),
+ env_fds
+ .iter()
+ .for_each(|(k, fd)| set_var(k, fd.as_raw_descriptor().to_string()));
+ jail.run_remap(
+ cmd,
+ &env_fds
+ .into_iter()
+ .map(|(_, fd)| (fd.as_raw_descriptor(), fd.as_raw_descriptor()))
+ .chain(
+ [
+ (stderr.as_raw_descriptor(), STDERR_FILENO),
+ (
+ child_socket.as_raw_descriptor(),
+ child_socket.as_raw_descriptor(),
+ ),
+ ]
+ .into_iter(),
+ )
+ .collect::<Vec<_>>(),
+ args,
)
- .spawn()
- .map_err(Error::PluginSpawn)?
- .id() as pid_t,
+ .context("failed to run plugin jail")?
+ }
+ None => {
+ for (_, fd) in env_fds.iter() {
+ base::clear_descriptor_cloexec(fd)?;
+ }
+ Command::new(cmd)
+ .args(args)
+ .envs(
+ env_fds
+ .into_iter()
+ .map(|(k, fd)| (k, { fd.as_raw_descriptor().to_string() })),
+ )
+ .env(
+ CROSVM_SOCKET_ENV,
+ child_socket.as_raw_descriptor().to_string(),
+ )
+ .stderr(stderr)
+ .spawn()
+ .context("failed to spawn plugin")?
+ .id() as pid_t
+ }
};
Ok(Process {
@@ -187,7 +229,7 @@ impl Process {
objects: Default::default(),
shared_vcpu_state: Default::default(),
per_vcpu_states,
- kill_evt: Event::new().map_err(Error::CreateEvent)?,
+ kill_evt: Event::new().context("failed to create plugin kill event")?,
vcpu_pipes,
request_buffer: vec![0; MAX_DATAGRAM_SIZE],
response_buffer: Vec::new(),
@@ -204,11 +246,11 @@ impl Process {
let vcpu_pipe_read = self.vcpu_pipes[cpu_id as usize]
.crosvm_read
.try_clone()
- .map_err(Error::CloneVcpuPipe)?;
+ .context("failed to clone vcpu read pipe")?;
let vcpu_pipe_write = self.vcpu_pipes[cpu_id as usize]
.crosvm_write
.try_clone()
- .map_err(Error::CloneVcpuPipe)?;
+ .context("failed to clone vcpu write pipe")?;
Ok(PluginVcpu::new(
self.shared_vcpu_state.clone(),
self.per_vcpu_states[cpu_id as usize].clone(),
@@ -512,17 +554,17 @@ impl Process {
vm: &mut Vm,
vcpu_handles: &[JoinHandle<()>],
taps: &[Tap],
- ) -> Result<()> {
+ ) -> result::Result<(), CommError> {
let (msg_size, request_file) = self.request_sockets[index]
- .recv_with_fd(&mut self.request_buffer)
- .map_err(Error::PluginSocketRecv)?;
+ .recv_with_fd(IoSliceMut::new(&mut self.request_buffer))
+ .map_err(CommError::PluginSocketRecv)?;
if msg_size == 0 {
- return Err(Error::PluginSocketHup);
+ return Err(CommError::PluginSocketHup);
}
- let request = protobuf::parse_from_bytes::<MainRequest>(&self.request_buffer[..msg_size])
- .map_err(Error::DecodeRequest)?;
+ let request: MainRequest = Message::parse_from_bytes(&self.request_buffer[..msg_size])
+ .map_err(CommError::DecodeRequest)?;
/// Use this to make it easier to stuff various kinds of File-like objects into the
/// `boxed_fds` list.
@@ -736,11 +778,11 @@ impl Process {
self.response_buffer.clear();
response
.write_to_vec(&mut self.response_buffer)
- .map_err(Error::EncodeResponse)?;
+ .map_err(CommError::EncodeResponse)?;
assert_ne!(self.response_buffer.len(), 0);
self.request_sockets[index]
.send_with_fds(&[IoSlice::new(&self.response_buffer[..])], &response_fds)
- .map_err(Error::PluginSocketSend)?;
+ .map_err(CommError::PluginSocketSend)?;
Ok(())
}
diff --git a/src/plugin/vcpu.rs b/src/plugin/vcpu.rs
index 535ee1c39..ef5eea5fa 100644
--- a/src/plugin/vcpu.rs
+++ b/src/plugin/vcpu.rs
@@ -13,7 +13,7 @@ use std::sync::{Arc, RwLock};
use libc::{EINVAL, ENOENT, ENOTTY, EPERM, EPIPE, EPROTO};
-use protobuf::Message;
+use protobuf::{CodedOutputStream, Message};
use assertions::const_assert;
use base::{error, LayoutAllocation};
@@ -23,7 +23,6 @@ use kvm_sys::{
kvm_debugregs, kvm_enable_cap, kvm_fpu, kvm_lapic_state, kvm_mp_state, kvm_msr_entry, kvm_msrs,
kvm_regs, kvm_sregs, kvm_vcpu_events, kvm_xcrs, KVM_CPUID_FLAG_SIGNIFCANT_INDEX,
};
-use protobuf::stream::CodedOutputStream;
use protos::plugin::*;
use sync::Mutex;
@@ -592,9 +591,8 @@ impl PluginVcpu {
let mut read_pipe = &self.read_pipe;
let msg_size = read_pipe.read(&mut request_buffer).map_err(io_to_sys_err)?;
- let mut request =
- protobuf::parse_from_bytes::<VcpuRequest>(&request_buffer[..msg_size])
- .map_err(proto_to_sys_err)?;
+ let mut request: VcpuRequest =
+ Message::parse_from_bytes(&request_buffer[..msg_size]).map_err(proto_to_sys_err)?;
let res = if request.has_wait() {
match wait_reason {
@@ -701,7 +699,7 @@ impl PluginVcpu {
}
}
kvm_msrs.nmsrs = request_entries.len() as u32;
- vcpu.set_msrs(&kvm_msrs)
+ vcpu.set_msrs(kvm_msrs)
} else if request.has_set_cpuid() {
response.mut_set_cpuid();
let request_entries = &request.get_set_cpuid().entries;
@@ -727,8 +725,10 @@ impl PluginVcpu {
{
Err(SysError::new(EINVAL))
} else {
- let mut cap: kvm_enable_cap = Default::default();
- cap.cap = capability;
+ let cap = kvm_enable_cap {
+ cap: capability,
+ ..Default::default()
+ };
// Safe because the allowed capabilities don't take pointer arguments.
unsafe { vcpu.kvm_enable_cap(&cap) }
}
diff --git a/sys_util/.build_test_serial b/sys_util/.build_test_serial
deleted file mode 100644
index e69de29bb..000000000
--- a/sys_util/.build_test_serial
+++ /dev/null
diff --git a/sys_util/Android.bp b/sys_util/Android.bp
deleted file mode 100644
index 0572fd2cd..000000000
--- a/sys_util/Android.bp
+++ /dev/null
@@ -1,105 +0,0 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace --no-subdir.
-// Do not modify this file as changes will be overridden on upgrade.
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "external_crosvm_license"
- // to get the below license kinds:
- // SPDX-license-identifier-BSD
- default_applicable_licenses: ["external_crosvm_license"],
-}
-
-rust_library {
- name: "libsys_util",
- defaults: ["crosvm_defaults"],
- host_supported: true,
- crate_name: "sys_util",
- srcs: ["src/lib.rs"],
- edition: "2018",
- rustlibs: [
- "libdata_model",
- "liblibc",
- "libserde",
- "libserde_json",
- "libsync_rust",
- "libtempfile",
- ],
- proc_macros: ["libpoll_token_derive"],
- shared_libs: ["libcap"], // specified in src/capabilities.rs
- target: {
- android: {
- rustlibs: [
- "libandroid_log_sys",
- ],
- },
- linux_bionic_arm64: {
- // For ARM architecture, we use aarch64-linux-android for BOTH
- // device and host targets. As a result, host targets are also
- // built with target_os = "android". Therefore, sys_util/src/android
- // is used and thus this android module is required.
- // This seems incorrect, but is inevitable because rustc doesn't
- // yet support a Linux-based target using Bionic as libc. We can't
- // use aarch64-unknown-linux-gnu because it's using glibc which
- // we don't support for cross-host builds.
- rustlibs: [
- "libandroid_log_sys",
- ],
- },
- },
-}
-
-rust_defaults {
- name: "sys_util_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "sys_util",
- srcs: ["src/lib.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libdata_model",
- "liblibc",
- "libserde",
- "libserde_json",
- "libsync_rust",
- "libtempfile",
- ],
- proc_macros: ["libpoll_token_derive"],
- shared_libs: ["libcap"], // specified in src/capabilities.rs
-}
-
-// TODO: This doesn't link because of missing getrandom in Bionic (host only, it's
-// available in the guest). Fix it upstream.
-// rust_test_host {
-// name: "sys_util_host_test_src_lib",
-// defaults: ["sys_util_defaults"],
-// test_options: {
-// unit_test: true,
-// },
-// }
-
-// TODO: This doesn't build due to missing shm_open &c. in Bionic. Fix it upstream.
-//rust_test {
-// name: "sys_util_device_test_src_lib",
-// defaults: ["sys_util_defaults"],
-// rustlibs: [
-// "libandroid_log_sys",
-// ],
-//}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../data_model/src/lib.rs
-// ../sync/src/lib.rs
-// ../tempfile/src/lib.rs
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// syn-1.0.70 "clone-impls,default,derive,parsing,printing,proc-macro,quote"
-// unicode-xid-0.2.1 "default"
diff --git a/sys_util/src/fork.rs b/sys_util/src/fork.rs
deleted file mode 100644
index 7f4a25640..000000000
--- a/sys_util/src/fork.rs
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2017 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use std::fs;
-use std::io;
-use std::path::Path;
-use std::process;
-use std::result;
-
-use libc::{c_long, pid_t, syscall, SYS_clone, CLONE_NEWPID, CLONE_NEWUSER, SIGCHLD};
-
-use crate::errno_result;
-
-/// Controls what namespace `clone_process` will have. See NAMESPACES(7).
-#[repr(u32)]
-pub enum CloneNamespace {
- /// The new process will inherit the namespace from the old process.
- Inherit = 0,
- /// The new process with be in a new user and PID namespace.
- NewUserPid = CLONE_NEWUSER as u32 | CLONE_NEWPID as u32,
-}
-
-#[derive(Debug)]
-pub enum CloneError {
- /// There was an error trying to iterate this process's threads.
- IterateTasks(io::Error),
- /// There are multiple threads running. The `usize` indicates how many threads.
- Multithreaded(usize),
- /// There was an error while cloning.
- Sys(crate::Error),
-}
-
-unsafe fn do_clone(flags: i32) -> crate::Result<pid_t> {
- // Forking is unsafe, this function must be unsafe as there is no way to guarantee safety
- // without more context about the state of the program.
- let pid = syscall(SYS_clone as c_long, flags | SIGCHLD as i32, 0);
- if pid < 0 {
- errno_result()
- } else {
- Ok(pid as pid_t)
- }
-}
-
-fn count_dir_entries<P: AsRef<Path>>(path: P) -> io::Result<usize> {
- Ok(fs::read_dir(path)?.count())
-}
-
-/// Clones this process and calls a closure in the new process.
-///
-/// After `post_clone_cb` returns or panics, the new process exits. Similar to how a `fork` syscall
-/// works, the new process is the same as the current process with the exception of the namespace
-/// controlled with the `ns` argument.
-///
-/// # Arguments
-/// * `ns` - What namespace the new process will have (see NAMESPACES(7)).
-/// * `post_clone_cb` - Callback to run in the new process
-pub fn clone_process<F>(ns: CloneNamespace, post_clone_cb: F) -> result::Result<pid_t, CloneError>
-where
- F: FnOnce(),
-{
- match count_dir_entries("/proc/self/task") {
- Ok(1) => {}
- Ok(thread_count) => {
- // Test cfg gets a free pass on this because tests generally have multiple independent
- // test threads going.
- let _ = thread_count;
- #[cfg(not(test))]
- return Err(CloneError::Multithreaded(thread_count));
- }
- Err(e) => return Err(CloneError::IterateTasks(e)),
- }
- // Forking is considered unsafe in mutlithreaded programs, but we just checked for other threads
- // in this process. We also only allow valid flags from CloneNamespace and check the return
- // result for errors. We also never let the cloned process return from this function.
- let ret = unsafe { do_clone(ns as i32) }.map_err(CloneError::Sys)?;
- if ret == 0 {
- struct ExitGuard;
- impl Drop for ExitGuard {
- fn drop(&mut self) {
- process::exit(101);
- }
- }
- // Prevents a panic in post_clone_cb from bypassing the process::exit.
- #[allow(unused_variables)]
- let exit_guard = ExitGuard {};
- post_clone_cb();
- // ! Never returns
- process::exit(0);
- }
-
- Ok(ret)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::{getpid, EventFd};
-
- fn wait_process(pid: libc::pid_t) -> crate::Result<libc::c_int> {
- let mut status: libc::c_int = 0;
- unsafe {
- if libc::waitpid(pid, &mut status as *mut libc::c_int, 0) < 0 {
- errno_result()
- } else {
- Ok(libc::WEXITSTATUS(status))
- }
- }
- }
-
- #[test]
- fn pid_diff() {
- let evt_fd = EventFd::new().expect("failed to create EventFd");
- let evt_fd_fork = evt_fd.try_clone().expect("failed to clone EventFd");
- let pid = getpid();
- clone_process(CloneNamespace::Inherit, || {
- // checks that this is a genuine fork with a new PID
- if pid != getpid() {
- evt_fd_fork.write(1).unwrap()
- } else {
- evt_fd_fork.write(2).unwrap()
- }
- })
- .expect("failed to clone");
- assert_eq!(evt_fd.read(), Ok(1));
- }
-
- // This test can deadlock occasionally when running in the builders VM. It
- // is disabled for now.
- // TODO(b/179924844): Investigate the issue and re-enable
- #[test]
- #[ignore]
- fn panic_safe() {
- let pid = getpid();
- assert_ne!(pid, 0);
-
- let clone_pid = clone_process(CloneNamespace::Inherit, || {
- panic!();
- })
- .expect("failed to clone");
-
- // This should never happen;
- if pid != getpid() {
- process::exit(2);
- }
-
- let status = wait_process(clone_pid).expect("wait_process failed");
- assert!(status == 101 || status == 0);
- }
-}
diff --git a/sys_util/src/passwd.rs b/sys_util/src/passwd.rs
deleted file mode 100644
index c8d35df25..000000000
--- a/sys_util/src/passwd.rs
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2017 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-//! Wrappers for passwd and group file access.
-
-use std::ffi::CStr;
-use std::mem;
-use std::ptr;
-
-use libc::{self, getgrnam_r, getpwnam_r, gid_t, uid_t};
-
-use crate::{errno_result, Result};
-
-/// Safe wrapper for getting a uid from a user name with `getpwnam_r(3)`.
-#[inline(always)]
-pub fn get_user_id(user_name: &CStr) -> Result<uid_t> {
- // libc::passwd is a C struct and can be safely initialized with zeroed memory.
- let mut passwd: libc::passwd = unsafe { mem::zeroed() };
- let mut passwd_result: *mut libc::passwd = ptr::null_mut();
- let mut buf = [0; 256];
-
- // For thread-safety, use the reentrant version of this function. This allows us to give it a
- // buffer on the stack (instead of a global buffer). Unlike most libc functions, the return
- // value of this doesn't really need to be checked, since the extra result pointer that is
- // passed in indicates whether or not the function succeeded.
- //
- // This call is safe as long as it behaves as described in the man page. We pass in valid
- // pointers to stack-allocated buffers, and the length check for the scratch buffer is correct.
- unsafe {
- handle_eintr_rc!(getpwnam_r(
- user_name.as_ptr(),
- &mut passwd,
- buf.as_mut_ptr(),
- buf.len(),
- &mut passwd_result
- ))
- };
-
- if passwd_result.is_null() {
- errno_result()
- } else {
- Ok(passwd.pw_uid)
- }
-}
-
-/// Safe wrapper for getting a gid from a group name with `getgrnam_r(3)`.
-#[inline(always)]
-pub fn get_group_id(group_name: &CStr) -> Result<gid_t> {
- // libc::group is a C struct and can be safely initialized with zeroed memory.
- let mut group: libc::group = unsafe { mem::zeroed() };
- let mut group_result: *mut libc::group = ptr::null_mut();
- let mut buf = [0; 256];
-
- // For thread-safety, use the reentrant version of this function. This allows us to give it a
- // buffer on the stack (instead of a global buffer). Unlike most libc functions, the return
- // value of this doesn't really need to be checked, since the extra result pointer that is
- // passed in indicates whether or not the function succeeded.
- //
- // This call is safe as long as it behaves as described in the man page. We pass in valid
- // pointers to stack-allocated buffers, and the length check for the scratch buffer is correct.
- unsafe {
- handle_eintr_rc!(getgrnam_r(
- group_name.as_ptr(),
- &mut group,
- buf.as_mut_ptr(),
- buf.len(),
- &mut group_result
- ))
- };
-
- if group_result.is_null() {
- errno_result()
- } else {
- Ok(group.gr_gid)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn get_good_uid() {
- let root_name = CStr::from_bytes_with_nul(b"root\0").unwrap();
-
- // root's uid should always exist, and should be 0.
- let root_uid = get_user_id(root_name).unwrap();
- assert_eq!(root_uid, 0);
- }
-
- #[test]
- fn get_bad_uid() {
- let bad_name = CStr::from_bytes_with_nul(b"this better not be a user\0").unwrap();
-
- // This user should give us an error. As a cruel joke, the getpwnam(3) man page allows
- // ENOENT, ESRCH, EBADF, EPERM, or even 0 to be set in errno if a user isn't found. So
- // instead of checking which error we got, just see that we did get one.
- let bad_uid_result = get_user_id(bad_name);
- assert!(bad_uid_result.is_err());
- }
-
- #[test]
- fn get_good_gid() {
- let root_name = CStr::from_bytes_with_nul(b"root\0").unwrap();
-
- // root's gid should always exist, and should be 0.
- let root_gid = get_group_id(root_name).unwrap();
- assert_eq!(root_gid, 0);
- }
-
- #[test]
- fn get_bad_gid() {
- let bad_name = CStr::from_bytes_with_nul(b"this better not be a group\0").unwrap();
-
- // This group should give us an error. As a cruel joke, the getgrnam(3) man page allows
- // ENOENT, ESRCH, EBADF, EPERM, or even 0 to be set in errno if a group isn't found. So
- // instead of checking which error we got, just see that we did get one.
- let bad_gid_result = get_group_id(bad_name);
- assert!(bad_gid_result.is_err());
- }
-}
diff --git a/sys_util/src/sched.rs b/sys_util/src/sched.rs
deleted file mode 100644
index b68a84549..000000000
--- a/sys_util/src/sched.rs
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-//! Wrappers for CPU affinity functions.
-
-use std::iter::FromIterator;
-use std::mem;
-
-use libc::{cpu_set_t, sched_setaffinity, CPU_SET, CPU_SETSIZE, CPU_ZERO, EINVAL};
-
-use crate::{errno_result, Error, Result};
-
-// This is needed because otherwise the compiler will complain that the
-// impl doesn't reference any types from inside this crate.
-struct CpuSet(cpu_set_t);
-
-impl FromIterator<usize> for CpuSet {
- fn from_iter<I: IntoIterator<Item = usize>>(cpus: I) -> Self {
- // cpu_set_t is a C struct and can be safely initialized with zeroed memory.
- let mut cpuset: cpu_set_t = unsafe { mem::zeroed() };
- // Safe because we pass a valid cpuset pointer.
- unsafe { CPU_ZERO(&mut cpuset) };
- for cpu in cpus {
- // Safe because we pass a valid cpuset pointer and cpu index.
- unsafe { CPU_SET(cpu, &mut cpuset) };
- }
- CpuSet(cpuset)
- }
-}
-
-/// Set the CPU affinity of the current thread to a given set of CPUs.
-///
-/// # Examples
-///
-/// Set the calling thread's CPU affinity so it will run on only CPUs
-/// 0, 1, 5, and 6.
-///
-/// ```
-/// # use sys_util::set_cpu_affinity;
-/// set_cpu_affinity(vec![0, 1, 5, 6]).unwrap();
-/// ```
-pub fn set_cpu_affinity<I: IntoIterator<Item = usize>>(cpus: I) -> Result<()> {
- let CpuSet(cpuset) = cpus
- .into_iter()
- .map(|cpu| {
- if cpu < CPU_SETSIZE as usize {
- Ok(cpu)
- } else {
- Err(Error::new(EINVAL))
- }
- })
- .collect::<Result<CpuSet>>()?;
-
- // Safe because we pass 0 for the current thread, and cpuset is a valid pointer and only
- // used for the duration of this call.
- let res = unsafe { sched_setaffinity(0, mem::size_of_val(&cpuset), &cpuset) };
-
- if res != 0 {
- errno_result()
- } else {
- Ok(())
- }
-}
-
-/// Enable experimental core scheduling for the current thread.
-///
-/// If succesful, the kernel should not schedule this thread with any other thread within the same
-/// SMT core.
-#[cfg(feature = "chromeos")]
-pub fn enable_core_scheduling() -> Result<()> {
- use libc::prctl;
- const PR_SET_CORE_SCHED: i32 = 0x200;
- let ret = unsafe { prctl(PR_SET_CORE_SCHED, 1) };
- if ret == -1 {
- errno_result()
- } else {
- Ok(())
- }
-}
diff --git a/sys_util/src/seek_hole.rs b/sys_util/src/seek_hole.rs
deleted file mode 100644
index b2dd6692f..000000000
--- a/sys_util/src/seek_hole.rs
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2018 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use std::fs::File;
-use std::io::{Error, Result};
-use std::os::unix::io::AsRawFd;
-
-use libc::{lseek64, ENXIO, SEEK_DATA, SEEK_HOLE};
-
-/// A trait for seeking to the next hole or non-hole position in a file.
-pub trait SeekHole {
- /// Seek to the first hole in a file at a position greater than or equal to `offset`.
- /// If no holes exist after `offset`, the seek position will be set to the end of the file.
- /// If `offset` is at or after the end of the file, the seek position is unchanged, and None is returned.
- /// Returns the current seek position after the seek or an error.
- fn seek_hole(&mut self, offset: u64) -> Result<Option<u64>>;
-
- /// Seek to the first data in a file at a position greater than or equal to `offset`.
- /// If no data exists after `offset`, the seek position is unchanged, and None is returned.
- /// Returns the current offset after the seek or an error.
- fn seek_data(&mut self, offset: u64) -> Result<Option<u64>>;
-}
-
-/// Safe wrapper for `libc::lseek64()`
-fn lseek(file: &mut File, offset: i64, whence: i32) -> Result<Option<u64>> {
- // This is safe because we pass a known-good file descriptor.
- let res = unsafe { lseek64(file.as_raw_fd(), offset, whence) };
-
- if res < 0 {
- // Convert ENXIO into None; pass any other error as-is.
- let err = Error::last_os_error();
- if let Some(errno) = Error::raw_os_error(&err) {
- if errno == ENXIO {
- return Ok(None);
- }
- }
- Err(err)
- } else {
- Ok(Some(res as u64))
- }
-}
-
-impl SeekHole for File {
- fn seek_hole(&mut self, offset: u64) -> Result<Option<u64>> {
- lseek(self, offset as i64, SEEK_HOLE)
- }
-
- fn seek_data(&mut self, offset: u64) -> Result<Option<u64>> {
- lseek(self, offset as i64, SEEK_DATA)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use std::fs::File;
- use std::io::{Seek, SeekFrom, Write};
- use tempfile::tempfile;
-
- fn seek_cur(file: &mut File) -> u64 {
- file.seek(SeekFrom::Current(0)).unwrap()
- }
-
- #[test]
- fn seek_data() {
- let mut file = tempfile().unwrap();
-
- // Empty file
- assert_eq!(file.seek_data(0).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
-
- // File with non-zero length consisting entirely of a hole
- file.set_len(0x10000).unwrap();
- assert_eq!(file.seek_data(0).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
-
- // seek_data at or after the end of the file should return None
- assert_eq!(file.seek_data(0x10000).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
- assert_eq!(file.seek_data(0x10001).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
-
- // Write some data to [0x10000, 0x20000)
- let b = [0x55u8; 0x10000];
- file.seek(SeekFrom::Start(0x10000)).unwrap();
- file.write_all(&b).unwrap();
- assert_eq!(file.seek_data(0).unwrap(), Some(0x10000));
- assert_eq!(seek_cur(&mut file), 0x10000);
-
- // seek_data within data should return the same offset
- assert_eq!(file.seek_data(0x10000).unwrap(), Some(0x10000));
- assert_eq!(seek_cur(&mut file), 0x10000);
- assert_eq!(file.seek_data(0x10001).unwrap(), Some(0x10001));
- assert_eq!(seek_cur(&mut file), 0x10001);
- assert_eq!(file.seek_data(0x1FFFF).unwrap(), Some(0x1FFFF));
- assert_eq!(seek_cur(&mut file), 0x1FFFF);
-
- // Extend the file to add another hole after the data
- file.set_len(0x30000).unwrap();
- assert_eq!(file.seek_data(0).unwrap(), Some(0x10000));
- assert_eq!(seek_cur(&mut file), 0x10000);
- assert_eq!(file.seek_data(0x1FFFF).unwrap(), Some(0x1FFFF));
- assert_eq!(seek_cur(&mut file), 0x1FFFF);
- assert_eq!(file.seek_data(0x20000).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0x1FFFF);
- }
-
- #[test]
- fn seek_hole() {
- let mut file = tempfile().unwrap();
-
- // Empty file
- assert_eq!(file.seek_hole(0).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
-
- // File with non-zero length consisting entirely of a hole
- file.set_len(0x10000).unwrap();
- assert_eq!(file.seek_hole(0).unwrap(), Some(0));
- assert_eq!(seek_cur(&mut file), 0);
- assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
- assert_eq!(seek_cur(&mut file), 0xFFFF);
-
- // seek_hole at or after the end of the file should return None
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x10000).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
- assert_eq!(file.seek_hole(0x10001).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
-
- // Write some data to [0x10000, 0x20000)
- let b = [0x55u8; 0x10000];
- file.seek(SeekFrom::Start(0x10000)).unwrap();
- file.write_all(&b).unwrap();
-
- // seek_hole within a hole should return the same offset
- assert_eq!(file.seek_hole(0).unwrap(), Some(0));
- assert_eq!(seek_cur(&mut file), 0);
- assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
- assert_eq!(seek_cur(&mut file), 0xFFFF);
-
- // seek_hole within data should return the next hole (EOF)
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
- assert_eq!(seek_cur(&mut file), 0x20000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x10001).unwrap(), Some(0x20000));
- assert_eq!(seek_cur(&mut file), 0x20000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
- assert_eq!(seek_cur(&mut file), 0x20000);
-
- // seek_hole at EOF after data should return None
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x20000).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
-
- // Extend the file to add another hole after the data
- file.set_len(0x30000).unwrap();
- assert_eq!(file.seek_hole(0).unwrap(), Some(0));
- assert_eq!(seek_cur(&mut file), 0);
- assert_eq!(file.seek_hole(0xFFFF).unwrap(), Some(0xFFFF));
- assert_eq!(seek_cur(&mut file), 0xFFFF);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x10000).unwrap(), Some(0x20000));
- assert_eq!(seek_cur(&mut file), 0x20000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x1FFFF).unwrap(), Some(0x20000));
- assert_eq!(seek_cur(&mut file), 0x20000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x20000));
- assert_eq!(seek_cur(&mut file), 0x20000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x20001));
- assert_eq!(seek_cur(&mut file), 0x20001);
-
- // seek_hole at EOF after a hole should return None
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x30000).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
-
- // Write some data to [0x20000, 0x30000)
- file.seek(SeekFrom::Start(0x20000)).unwrap();
- file.write_all(&b).unwrap();
-
- // seek_hole within [0x20000, 0x30000) should now find the hole at EOF
- assert_eq!(file.seek_hole(0x20000).unwrap(), Some(0x30000));
- assert_eq!(seek_cur(&mut file), 0x30000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x20001).unwrap(), Some(0x30000));
- assert_eq!(seek_cur(&mut file), 0x30000);
- file.seek(SeekFrom::Start(0)).unwrap();
- assert_eq!(file.seek_hole(0x30000).unwrap(), None);
- assert_eq!(seek_cur(&mut file), 0);
- }
-}
diff --git a/sys_util/src/struct_util.rs b/sys_util/src/struct_util.rs
deleted file mode 100644
index 964b3113a..000000000
--- a/sys_util/src/struct_util.rs
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright 2017 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-use std::io::Read;
-use std::mem::size_of;
-
-#[derive(Debug)]
-pub enum Error {
- ReadStruct,
-}
-pub type Result<T> = std::result::Result<T, Error>;
-
-/// Reads a struct from an input buffer.
-///
-/// # Arguments
-///
-/// * `f` - The input to read from. Often this is a file.
-/// * `out` - The struct to fill with data read from `f`.
-///
-/// # Safety
-///
-/// This is unsafe because the struct is initialized to unverified data read from the input.
-/// `read_struct` should only be called to fill plain old data structs. It is not endian safe.
-pub unsafe fn read_struct<T: Copy, F: Read>(f: &mut F, out: &mut T) -> Result<()> {
- let out_slice = std::slice::from_raw_parts_mut(out as *mut T as *mut u8, size_of::<T>());
- f.read_exact(out_slice).map_err(|_| Error::ReadStruct)?;
- Ok(())
-}
-
-/// Reads an array of structs from an input buffer. Returns a Vec of structs initialized with data
-/// from the specified input.
-///
-/// # Arguments
-///
-/// * `f` - The input to read from. Often this is a file.
-/// * `len` - The number of structs to fill with data read from `f`.
-///
-/// # Safety
-///
-/// This is unsafe because the structs are initialized to unverified data read from the input.
-/// `read_struct_slice` should only be called for plain old data structs. It is not endian safe.
-pub unsafe fn read_struct_slice<T: Copy, F: Read>(f: &mut F, len: usize) -> Result<Vec<T>> {
- let mut out: Vec<T> = Vec::with_capacity(len);
- out.set_len(len);
- let out_slice =
- std::slice::from_raw_parts_mut(out.as_ptr() as *mut T as *mut u8, size_of::<T>() * len);
- f.read_exact(out_slice).map_err(|_| Error::ReadStruct)?;
- Ok(out)
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
- use std::io::Cursor;
- use std::mem;
-
- #[derive(Clone, Copy, Debug, Default, PartialEq)]
- struct TestRead {
- a: u64,
- b: u8,
- c: u8,
- d: u8,
- e: u8,
- }
-
- #[test]
- fn struct_basic_read() {
- let orig = TestRead {
- a: 0x7766554433221100,
- b: 0x88,
- c: 0x99,
- d: 0xaa,
- e: 0xbb,
- };
- let source = unsafe {
- // Don't worry it's a test
- std::slice::from_raw_parts(
- &orig as *const _ as *const u8,
- std::mem::size_of::<TestRead>(),
- )
- };
- assert_eq!(mem::size_of::<TestRead>(), mem::size_of_val(&source));
- let mut tr: TestRead = Default::default();
- unsafe {
- read_struct(&mut Cursor::new(source), &mut tr).unwrap();
- }
- assert_eq!(orig, tr);
- }
-
- #[test]
- fn struct_read_past_end() {
- let orig = TestRead {
- a: 0x7766554433221100,
- b: 0x88,
- c: 0x99,
- d: 0xaa,
- e: 0xbb,
- };
- let source = unsafe {
- // Don't worry it's a test
- std::slice::from_raw_parts(
- &orig as *const _ as *const u8,
- std::mem::size_of::<TestRead>() - 1,
- )
- };
- let mut tr: TestRead = Default::default();
- unsafe {
- assert!(read_struct(&mut Cursor::new(source), &mut tr).is_err());
- }
- }
-
- #[test]
- fn struct_slice_read() {
- let orig = vec![
- TestRead {
- a: 0x7766554433221100,
- b: 0x88,
- c: 0x99,
- d: 0xaa,
- e: 0xbb,
- },
- TestRead {
- a: 0x7867564534231201,
- b: 0x02,
- c: 0x13,
- d: 0x24,
- e: 0x35,
- },
- TestRead {
- a: 0x7a69584736251403,
- b: 0x04,
- c: 0x15,
- d: 0x26,
- e: 0x37,
- },
- ];
- let source = unsafe {
- // Don't worry it's a test
- std::slice::from_raw_parts(
- orig.as_ptr() as *const u8,
- std::mem::size_of::<TestRead>() * 3,
- )
- };
-
- let tr: Vec<TestRead> = unsafe { read_struct_slice(&mut Cursor::new(source), 3).unwrap() };
- assert_eq!(orig, tr);
- }
-}
diff --git a/syscall_defines/src/lib.rs b/syscall_defines/src/lib.rs
deleted file mode 100644
index cf016ab35..000000000
--- a/syscall_defines/src/lib.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2017 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#[cfg(target_arch = "x86_64")]
-#[path = "linux-x86_64/mod.rs"]
-pub mod linux;
-
-#[cfg(target_arch = "x86")]
-#[path = "linux-x86/mod.rs"]
-pub mod linux;
-
-#[cfg(target_arch = "aarch64")]
-#[path = "linux-aarch64/mod.rs"]
-pub mod linux;
-
-#[cfg(target_arch = "arm")]
-#[path = "linux-arm/mod.rs"]
-pub mod linux;
diff --git a/syscall_defines/src/linux-aarch64/mod.rs b/syscall_defines/src/linux-aarch64/mod.rs
deleted file mode 100644
index 775b0367c..000000000
--- a/syscall_defines/src/linux-aarch64/mod.rs
+++ /dev/null
@@ -1,333 +0,0 @@
-// Copyright 2017 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Generated with: cat include/uapi/asm-generic/unistd.h |
-// awk ' { print "SYS_" $2 " = " $2"," } '
-#[allow(dead_code)]
-#[allow(non_camel_case_types)]
-pub enum LinuxSyscall {
- SYS_io_setup = 0,
- SYS_io_destroy = 1,
- SYS_io_submit = 2,
- SYS_io_cancel = 3,
- SYS_io_getevents = 4,
- SYS_setxattr = 5,
- SYS_lsetxattr = 6,
- SYS_fsetxattr = 7,
- SYS_getxattr = 8,
- SYS_lgetxattr = 9,
- SYS_fgetxattr = 10,
- SYS_listxattr = 11,
- SYS_llistxattr = 12,
- SYS_flistxattr = 13,
- SYS_removexattr = 14,
- SYS_lremovexattr = 15,
- SYS_fremovexattr = 16,
- SYS_getcwd = 17,
- SYS_lookup_dcookie = 18,
- SYS_eventfd2 = 19,
- SYS_epoll_create1 = 20,
- SYS_epoll_ctl = 21,
- SYS_epoll_pwait = 22,
- SYS_dup = 23,
- SYS_dup3 = 24,
- SYS_inotify_init1 = 26,
- SYS_inotify_add_watch = 27,
- SYS_inotify_rm_watch = 28,
- SYS_ioctl = 29,
- SYS_ioprio_set = 30,
- SYS_ioprio_get = 31,
- SYS_flock = 32,
- SYS_mknodat = 33,
- SYS_mkdirat = 34,
- SYS_unlinkat = 35,
- SYS_symlinkat = 36,
- SYS_linkat = 37,
- SYS_renameat = 38,
- SYS_umount2 = 39,
- SYS_mount = 40,
- SYS_pivot_root = 41,
- SYS_nfsservctl = 42,
- SYS_fallocate = 47,
- SYS_faccessat = 48,
- SYS_chdir = 49,
- SYS_fchdir = 50,
- SYS_chroot = 51,
- SYS_fchmod = 52,
- SYS_fchmodat = 53,
- SYS_fchownat = 54,
- SYS_fchown = 55,
- SYS_openat = 56,
- SYS_close = 57,
- SYS_vhangup = 58,
- SYS_pipe2 = 59,
- SYS_quotactl = 60,
- SYS_getdents64 = 61,
- SYS_read = 63,
- SYS_write = 64,
- SYS_readv = 65,
- SYS_writev = 66,
- SYS_pread64 = 67,
- SYS_pwrite64 = 68,
- SYS_preadv = 69,
- SYS_pwritev = 70,
- SYS_pselect6 = 72,
- SYS_ppoll = 73,
- SYS_signalfd4 = 74,
- SYS_vmsplice = 75,
- SYS_splice = 76,
- SYS_tee = 77,
- SYS_readlinkat = 78,
- SYS_sync = 81,
- SYS_fsync = 82,
- SYS_fdatasync = 83,
- SYS_sync_file_range = 84,
- SYS_timerfd_create = 85,
- SYS_timerfd_settime = 86,
- SYS_timerfd_gettime = 87,
- SYS_utimensat = 88,
- SYS_acct = 89,
- SYS_capget = 90,
- SYS_capset = 91,
- SYS_personality = 92,
- SYS_exit = 93,
- SYS_exit_group = 94,
- SYS_waitid = 95,
- SYS_set_tid_address = 96,
- SYS_unshare = 97,
- SYS_futex = 98,
- SYS_set_robust_list = 99,
- SYS_get_robust_list = 100,
- SYS_nanosleep = 101,
- SYS_getitimer = 102,
- SYS_setitimer = 103,
- SYS_kexec_load = 104,
- SYS_init_module = 105,
- SYS_delete_module = 106,
- SYS_timer_create = 107,
- SYS_timer_gettime = 108,
- SYS_timer_getoverrun = 109,
- SYS_timer_settime = 110,
- SYS_timer_delete = 111,
- SYS_clock_settime = 112,
- SYS_clock_gettime = 113,
- SYS_clock_getres = 114,
- SYS_clock_nanosleep = 115,
- SYS_syslog = 116,
- SYS_ptrace = 117,
- SYS_sched_setparam = 118,
- SYS_sched_setscheduler = 119,
- SYS_sched_getscheduler = 120,
- SYS_sched_getparam = 121,
- SYS_sched_setaffinity = 122,
- SYS_sched_getaffinity = 123,
- SYS_sched_yield = 124,
- SYS_sched_get_priority_max = 125,
- SYS_sched_get_priority_min = 126,
- SYS_sched_rr_get_interval = 127,
- SYS_restart_syscall = 128,
- SYS_kill = 129,
- SYS_tkill = 130,
- SYS_tgkill = 131,
- SYS_sigaltstack = 132,
- SYS_rt_sigsuspend = 133,
- SYS_rt_sigaction = 134,
- SYS_rt_sigprocmask = 135,
- SYS_rt_sigpending = 136,
- SYS_rt_sigtimedwait = 137,
- SYS_rt_sigqueueinfo = 138,
- SYS_rt_sigreturn = 139,
- SYS_setpriority = 140,
- SYS_getpriority = 141,
- SYS_reboot = 142,
- SYS_setregid = 143,
- SYS_setgid = 144,
- SYS_setreuid = 145,
- SYS_setuid = 146,
- SYS_setresuid = 147,
- SYS_getresuid = 148,
- SYS_setresgid = 149,
- SYS_getresgid = 150,
- SYS_setfsuid = 151,
- SYS_setfsgid = 152,
- SYS_times = 153,
- SYS_setpgid = 154,
- SYS_getpgid = 155,
- SYS_getsid = 156,
- SYS_setsid = 157,
- SYS_getgroups = 158,
- SYS_setgroups = 159,
- SYS_uname = 160,
- SYS_sethostname = 161,
- SYS_setdomainname = 162,
- SYS_getrlimit = 163,
- SYS_setrlimit = 164,
- SYS_getrusage = 165,
- SYS_umask = 166,
- SYS_prctl = 167,
- SYS_getcpu = 168,
- SYS_gettimeofday = 169,
- SYS_settimeofday = 170,
- SYS_adjtimex = 171,
- SYS_getpid = 172,
- SYS_getppid = 173,
- SYS_getuid = 174,
- SYS_geteuid = 175,
- SYS_getgid = 176,
- SYS_getegid = 177,
- SYS_gettid = 178,
- SYS_sysinfo = 179,
- SYS_mq_open = 180,
- SYS_mq_unlink = 181,
- SYS_mq_timedsend = 182,
- SYS_mq_timedreceive = 183,
- SYS_mq_notify = 184,
- SYS_mq_getsetattr = 185,
- SYS_msgget = 186,
- SYS_msgctl = 187,
- SYS_msgrcv = 188,
- SYS_msgsnd = 189,
- SYS_semget = 190,
- SYS_semctl = 191,
- SYS_semtimedop = 192,
- SYS_semop = 193,
- SYS_shmget = 194,
- SYS_shmctl = 195,
- SYS_shmat = 196,
- SYS_shmdt = 197,
- SYS_socket = 198,
- SYS_socketpair = 199,
- SYS_bind = 200,
- SYS_listen = 201,
- SYS_accept = 202,
- SYS_connect = 203,
- SYS_getsockname = 204,
- SYS_getpeername = 205,
- SYS_sendto = 206,
- SYS_recvfrom = 207,
- SYS_setsockopt = 208,
- SYS_getsockopt = 209,
- SYS_shutdown = 210,
- SYS_sendmsg = 211,
- SYS_recvmsg = 212,
- SYS_readahead = 213,
- SYS_brk = 214,
- SYS_munmap = 215,
- SYS_mremap = 216,
- SYS_add_key = 217,
- SYS_request_key = 218,
- SYS_keyctl = 219,
- SYS_clone = 220,
- SYS_execve = 221,
- SYS_swapon = 224,
- SYS_swapoff = 225,
- SYS_mprotect = 226,
- SYS_msync = 227,
- SYS_mlock = 228,
- SYS_munlock = 229,
- SYS_mlockall = 230,
- SYS_munlockall = 231,
- SYS_mincore = 232,
- SYS_madvise = 233,
- SYS_remap_file_pages = 234,
- SYS_mbind = 235,
- SYS_get_mempolicy = 236,
- SYS_set_mempolicy = 237,
- SYS_migrate_pages = 238,
- SYS_move_pages = 239,
- SYS_rt_tgsigqueueinfo = 240,
- SYS_perf_event_open = 241,
- SYS_accept4 = 242,
- SYS_recvmmsg = 243,
- SYS_arch_specific_syscall = 244,
- SYS_wait4 = 260,
- SYS_prlimit64 = 261,
- SYS_fanotify_init = 262,
- SYS_fanotify_mark = 263,
- SYS_name_to_handle_at = 264,
- SYS_open_by_handle_at = 265,
- SYS_clock_adjtime = 266,
- SYS_syncfs = 267,
- SYS_setns = 268,
- SYS_sendmmsg = 269,
- SYS_process_vm_readv = 270,
- SYS_process_vm_writev = 271,
- SYS_kcmp = 272,
- SYS_finit_module = 273,
- SYS_sched_setattr = 274,
- SYS_sched_getattr = 275,
- SYS_renameat2 = 276,
- SYS_seccomp = 277,
- SYS_getrandom = 278,
- SYS_memfd_create = 279,
- SYS_bpf = 280,
- SYS_execveat = 281,
- SYS_userfaultfd = 282,
- SYS_membarrier = 283,
- SYS_mlock2 = 284,
- SYS_copy_file_range = 285,
- SYS_preadv2 = 286,
- SYS_pwritev2 = 287,
- SYS_pkey_mprotect = 288,
- SYS_pkey_alloc = 289,
- SYS_pkey_free = 290,
- SYS_syscalls = 291,
- SYS_io_uring_setup = 425,
- SYS_io_uring_enter = 426,
- SYS_io_uring_register = 427,
- SYS_open = 1024,
- SYS_link = 1025,
- SYS_unlink = 1026,
- SYS_mknod = 1027,
- SYS_chmod = 1028,
- SYS_chown = 1029,
- SYS_mkdir = 1030,
- SYS_rmdir = 1031,
- SYS_lchown = 1032,
- SYS_access = 1033,
- SYS_rename = 1034,
- SYS_readlink = 1035,
- SYS_symlink = 1036,
- SYS_utimes = 1037,
- SYS_pipe = 1040,
- SYS_dup2 = 1041,
- SYS_epoll_create = 1042,
- SYS_inotify_init = 1043,
- SYS_eventfd = 1044,
- SYS_signalfd = 1045,
- SYS_sendfile = 1046,
- SYS_ftruncate = 1047,
- SYS_truncate = 1048,
- SYS_stat = 1049,
- SYS_lstat = 1050,
- SYS_fstat = 1051,
- SYS_fcntl = 1052,
- SYS_fadvise64 = 1053,
- SYS_newfstatat = 1054,
- SYS_fstatfs = 1055,
- SYS_statfs = 1056,
- SYS_lseek = 1057,
- SYS_mmap = 1058,
- SYS_alarm = 1059,
- SYS_getpgrp = 1060,
- SYS_pause = 1061,
- SYS_time = 1062,
- SYS_utime = 1063,
- SYS_creat = 1064,
- SYS_getdents = 1065,
- SYS_futimesat = 1066,
- SYS_select = 1067,
- SYS_poll = 1068,
- SYS_epoll_wait = 1069,
- SYS_ustat = 1070,
- SYS_vfork = 1071,
- SYS_oldwait4 = 1072,
- SYS_recv = 1073,
- SYS_send = 1074,
- SYS_bdflush = 1075,
- SYS_umount = 1076,
- SYS_uselib = 1077,
- SYS__sysctl = 1078,
- SYS_fork = 1079,
-}
diff --git a/syscall_defines/src/linux-arm/mod.rs b/syscall_defines/src/linux-arm/mod.rs
deleted file mode 100644
index 1047f7abb..000000000
--- a/syscall_defines/src/linux-arm/mod.rs
+++ /dev/null
@@ -1,410 +0,0 @@
-// Copyright 2017 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Based on /usr/include/asm/unistd.h from the sysroot of an arm32 board.
-#[allow(dead_code)]
-#[allow(non_camel_case_types)]
-pub enum LinuxSyscall {
- SYS_restart_syscall = 0,
- SYS_exit = 1,
- SYS_fork = 2,
- SYS_read = 3,
- SYS_write = 4,
- SYS_open = 5,
- SYS_close = 6,
- SYS_creat = 8,
- SYS_link = 9,
- SYS_unlink = 10,
- SYS_execve = 11,
- SYS_chdir = 12,
- SYS_time32 = 13,
- SYS_mknod = 14,
- SYS_chmod = 15,
- SYS_lchown16 = 16,
- SYS_lseek = 19,
- SYS_getpid = 20,
- SYS_mount = 21,
- SYS_oldumount = 22,
- SYS_setuid16 = 23,
- SYS_getuid16 = 24,
- SYS_stime32 = 25,
- SYS_ptrace = 26,
- SYS_alarm = 27,
- SYS_pause = 29,
- SYS_utime32 = 30,
- SYS_access = 33,
- SYS_nice = 34,
- SYS_sync = 36,
- SYS_kill = 37,
- SYS_rename = 38,
- SYS_mkdir = 39,
- SYS_rmdir = 40,
- SYS_dup = 41,
- SYS_pipe = 42,
- SYS_times = 43,
- SYS_brk = 45,
- SYS_setgid16 = 46,
- SYS_getgid16 = 47,
- SYS_geteuid16 = 49,
- SYS_getegid16 = 50,
- SYS_acct = 51,
- SYS_umount = 52,
- SYS_ioctl = 54,
- SYS_fcntl = 55,
- SYS_setpgid = 57,
- SYS_umask = 60,
- SYS_chroot = 61,
- SYS_ustat = 62,
- SYS_dup2 = 63,
- SYS_getppid = 64,
- SYS_getpgrp = 65,
- SYS_setsid = 66,
- SYS_sigaction = 67,
- SYS_setreuid16 = 70,
- SYS_setregid16 = 71,
- SYS_sigsuspend = 72,
- SYS_sigpending = 73,
- SYS_sethostname = 74,
- SYS_setrlimit = 75,
- SYS_old_getrlimit = 76,
- SYS_getrusage = 77,
- SYS_gettimeofday = 78,
- SYS_settimeofday = 79,
- SYS_getgroups16 = 80,
- SYS_setgroups16 = 81,
- SYS_old_select = 82,
- SYS_symlink = 83,
- SYS_readlink = 85,
- SYS_uselib = 86,
- SYS_swapon = 87,
- SYS_reboot = 88,
- SYS_old_readdir = 89,
- SYS_old_mmap = 90,
- SYS_munmap = 91,
- SYS_truncate = 92,
- SYS_ftruncate = 93,
- SYS_fchmod = 94,
- SYS_fchown16 = 95,
- SYS_getpriority = 96,
- SYS_setpriority = 97,
- SYS_statfs = 99,
- SYS_fstatfs = 100,
- SYS_socketcall = 102,
- SYS_syslog = 103,
- SYS_setitimer = 104,
- SYS_getitimer = 105,
- SYS_newstat = 106,
- SYS_newlstat = 107,
- SYS_newfstat = 108,
- SYS_vhangup = 111,
- SYS_syscall = 113,
- SYS_wait4 = 114,
- SYS_swapoff = 115,
- SYS_sysinfo = 116,
- SYS_ipc = 117,
- SYS_fsync = 118,
- SYS_sigreturn_wrapper = 119,
- SYS_clone = 120,
- SYS_setdomainname = 121,
- SYS_newuname = 122,
- SYS_adjtimex_time32 = 124,
- SYS_mprotect = 125,
- SYS_sigprocmask = 126,
- SYS_init_module = 128,
- SYS_delete_module = 129,
- SYS_quotactl = 131,
- SYS_getpgid = 132,
- SYS_fchdir = 133,
- SYS_bdflush = 134,
- SYS_sysfs = 135,
- SYS_personality = 136,
- SYS_setfsuid16 = 138,
- SYS_setfsgid16 = 139,
- SYS_llseek = 140,
- SYS_getdents = 141,
- SYS_select = 142,
- SYS_flock = 143,
- SYS_msync = 144,
- SYS_readv = 145,
- SYS_writev = 146,
- SYS_getsid = 147,
- SYS_fdatasync = 148,
- SYS_sysctl = 149,
- SYS_mlock = 150,
- SYS_munlock = 151,
- SYS_mlockall = 152,
- SYS_munlockall = 153,
- SYS_sched_setparam = 154,
- SYS_sched_getparam = 155,
- SYS_sched_setscheduler = 156,
- SYS_sched_getscheduler = 157,
- SYS_sched_yield = 158,
- SYS_sched_get_priority_max = 159,
- SYS_sched_get_priority_min = 160,
- SYS_sched_rr_get_interval_time32 = 161,
- SYS_nanosleep_time32 = 162,
- SYS_mremap = 163,
- SYS_setresuid16 = 164,
- SYS_getresuid16 = 165,
- SYS_poll = 168,
- SYS_nfsservctl = 169,
- SYS_setresgid16 = 170,
- SYS_getresgid16 = 171,
- SYS_prctl = 172,
- SYS_rt_sigreturn_wrapper = 173,
- SYS_rt_sigaction = 174,
- SYS_rt_sigprocmask = 175,
- SYS_rt_sigpending = 176,
- SYS_rt_sigtimedwait_time32 = 177,
- SYS_rt_sigqueueinfo = 178,
- SYS_rt_sigsuspend = 179,
- SYS_pread64 = 180,
- SYS_pwrite64 = 181,
- SYS_chown16 = 182,
- SYS_getcwd = 183,
- SYS_capget = 184,
- SYS_capset = 185,
- SYS_sigaltstack = 186,
- SYS_sendfile = 187,
- SYS_vfork = 190,
- SYS_getrlimit = 191,
- SYS_mmap2 = 192,
- SYS_truncate64 = 193,
- SYS_ftruncate64 = 194,
- SYS_stat64 = 195,
- SYS_lstat64 = 196,
- SYS_fstat64 = 197,
- SYS_lchown = 198,
- SYS_getuid = 199,
- SYS_getgid = 200,
- SYS_geteuid = 201,
- SYS_getegid = 202,
- SYS_setreuid = 203,
- SYS_setregid = 204,
- SYS_getgroups = 205,
- SYS_setgroups = 206,
- SYS_fchown = 207,
- SYS_setresuid = 208,
- SYS_getresuid = 209,
- SYS_setresgid = 210,
- SYS_getresgid = 211,
- SYS_chown = 212,
- SYS_setuid = 213,
- SYS_setgid = 214,
- SYS_setfsuid = 215,
- SYS_setfsgid = 216,
- SYS_getdents64 = 217,
- SYS_pivot_root = 218,
- SYS_mincore = 219,
- SYS_madvise = 220,
- SYS_fcntl64 = 221,
- SYS_gettid = 224,
- SYS_readahead = 225,
- SYS_setxattr = 226,
- SYS_lsetxattr = 227,
- SYS_fsetxattr = 228,
- SYS_getxattr = 229,
- SYS_lgetxattr = 230,
- SYS_fgetxattr = 231,
- SYS_listxattr = 232,
- SYS_llistxattr = 233,
- SYS_flistxattr = 234,
- SYS_removexattr = 235,
- SYS_lremovexattr = 236,
- SYS_fremovexattr = 237,
- SYS_tkill = 238,
- SYS_sendfile64 = 239,
- SYS_futex_time32 = 240,
- SYS_sched_setaffinity = 241,
- SYS_sched_getaffinity = 242,
- SYS_io_setup = 243,
- SYS_io_destroy = 244,
- SYS_io_getevents_time32 = 245,
- SYS_io_submit = 246,
- SYS_io_cancel = 247,
- SYS_exit_group = 248,
- SYS_lookup_dcookie = 249,
- SYS_epoll_create = 250,
- SYS_epoll_ctl = 251,
- SYS_epoll_wait = 252,
- SYS_remap_file_pages = 253,
- SYS_set_tid_address = 256,
- SYS_timer_create = 257,
- SYS_timer_settime32 = 258,
- SYS_timer_gettime32 = 259,
- SYS_timer_getoverrun = 260,
- SYS_timer_delete = 261,
- SYS_clock_settime32 = 262,
- SYS_clock_gettime32 = 263,
- SYS_clock_getres_time32 = 264,
- SYS_clock_nanosleep_time32 = 265,
- SYS_statfs64_wrapper = 266,
- SYS_fstatfs64_wrapper = 267,
- SYS_tgkill = 268,
- SYS_utimes_time32 = 269,
- SYS_arm_fadvise64_64 = 270,
- SYS_pciconfig_iobase = 271,
- SYS_pciconfig_read = 272,
- SYS_pciconfig_write = 273,
- SYS_mq_open = 274,
- SYS_mq_unlink = 275,
- SYS_mq_timedsend_time32 = 276,
- SYS_mq_timedreceive_time32 = 277,
- SYS_mq_notify = 278,
- SYS_mq_getsetattr = 279,
- SYS_waitid = 280,
- SYS_socket = 281,
- SYS_bind = 282,
- SYS_connect = 283,
- SYS_listen = 284,
- SYS_accept = 285,
- SYS_getsockname = 286,
- SYS_getpeername = 287,
- SYS_socketpair = 288,
- SYS_send = 289,
- SYS_sendto = 290,
- SYS_recv = 291,
- SYS_recvfrom = 292,
- SYS_shutdown = 293,
- SYS_setsockopt = 294,
- SYS_getsockopt = 295,
- SYS_sendmsg = 296,
- SYS_recvmsg = 297,
- SYS_semop = 298,
- SYS_semget = 299,
- SYS_old_semctl = 300,
- SYS_msgsnd = 301,
- SYS_msgrcv = 302,
- SYS_msgget = 303,
- SYS_old_msgctl = 304,
- SYS_shmat = 305,
- SYS_shmdt = 306,
- SYS_shmget = 307,
- SYS_old_shmctl = 308,
- SYS_add_key = 309,
- SYS_request_key = 310,
- SYS_keyctl = 311,
- SYS_semtimedop_time32 = 312,
- SYS_vserver = 313,
- SYS_ioprio_set = 314,
- SYS_ioprio_get = 315,
- SYS_inotify_init = 316,
- SYS_inotify_add_watch = 317,
- SYS_inotify_rm_watch = 318,
- SYS_mbind = 319,
- SYS_get_mempolicy = 320,
- SYS_set_mempolicy = 321,
- SYS_openat = 322,
- SYS_mkdirat = 323,
- SYS_mknodat = 324,
- SYS_fchownat = 325,
- SYS_futimesat_time32 = 326,
- SYS_fstatat64 = 327,
- SYS_unlinkat = 328,
- SYS_renameat = 329,
- SYS_linkat = 330,
- SYS_symlinkat = 331,
- SYS_readlinkat = 332,
- SYS_fchmodat = 333,
- SYS_faccessat = 334,
- SYS_pselect6_time32 = 335,
- SYS_ppoll_time32 = 336,
- SYS_unshare = 337,
- SYS_set_robust_list = 338,
- SYS_get_robust_list = 339,
- SYS_splice = 340,
- SYS_sync_file_range2 = 341,
- SYS_tee = 342,
- SYS_vmsplice = 343,
- SYS_move_pages = 344,
- SYS_getcpu = 345,
- SYS_epoll_pwait = 346,
- SYS_kexec_load = 347,
- SYS_utimensat_time32 = 348,
- SYS_signalfd = 349,
- SYS_timerfd_create = 350,
- SYS_eventfd = 351,
- SYS_fallocate = 352,
- SYS_timerfd_settime32 = 353,
- SYS_timerfd_gettime32 = 354,
- SYS_signalfd4 = 355,
- SYS_eventfd2 = 356,
- SYS_epoll_create1 = 357,
- SYS_dup3 = 358,
- SYS_pipe2 = 359,
- SYS_inotify_init1 = 360,
- SYS_preadv = 361,
- SYS_pwritev = 362,
- SYS_rt_tgsigqueueinfo = 363,
- SYS_perf_event_open = 364,
- SYS_recvmmsg_time32 = 365,
- SYS_accept4 = 366,
- SYS_fanotify_init = 367,
- SYS_fanotify_mark = 368,
- SYS_prlimit64 = 369,
- SYS_name_to_handle_at = 370,
- SYS_open_by_handle_at = 371,
- SYS_clock_adjtime32 = 372,
- SYS_syncfs = 373,
- SYS_sendmmsg = 374,
- SYS_setns = 375,
- SYS_process_vm_readv = 376,
- SYS_process_vm_writev = 377,
- SYS_kcmp = 378,
- SYS_finit_module = 379,
- SYS_sched_setattr = 380,
- SYS_sched_getattr = 381,
- SYS_renameat2 = 382,
- SYS_seccomp = 383,
- SYS_getrandom = 384,
- SYS_memfd_create = 385,
- SYS_bpf = 386,
- SYS_execveat = 387,
- SYS_userfaultfd = 388,
- SYS_membarrier = 389,
- SYS_mlock2 = 390,
- SYS_copy_file_range = 391,
- SYS_preadv2 = 392,
- SYS_pwritev2 = 393,
- SYS_pkey_mprotect = 394,
- SYS_pkey_alloc = 395,
- SYS_pkey_free = 396,
- SYS_statx = 397,
- SYS_rseq = 398,
- SYS_io_pgetevents_time32 = 399,
- SYS_migrate_pages = 400,
- SYS_kexec_file_load = 401,
- SYS_clock_gettime = 403,
- SYS_clock_settime = 404,
- SYS_clock_adjtime = 405,
- SYS_clock_getres = 406,
- SYS_clock_nanosleep = 407,
- SYS_timer_gettime = 408,
- SYS_timer_settime = 409,
- SYS_timerfd_gettime = 410,
- SYS_timerfd_settime = 411,
- SYS_utimensat = 412,
- SYS_pselect6 = 413,
- SYS_ppoll = 414,
- SYS_io_pgetevents = 416,
- SYS_recvmmsg = 417,
- SYS_mq_timedsend = 418,
- SYS_mq_timedreceive = 419,
- SYS_semtimedop = 420,
- SYS_rt_sigtimedwait = 421,
- SYS_futex = 422,
- SYS_sched_rr_get_interval = 423,
- SYS_pidfd_send_signal = 424,
- SYS_io_uring_setup = 425,
- SYS_io_uring_enter = 426,
- SYS_io_uring_register = 427,
- SYS_open_tree = 428,
- SYS_move_mount = 429,
- SYS_fsopen = 430,
- SYS_fsconfig = 431,
- SYS_fsmount = 432,
- SYS_fspick = 433,
- SYS_pidfd_open = 434,
- SYS_clone3 = 435,
-}
diff --git a/syscall_defines/src/linux-x86/mod.rs b/syscall_defines/src/linux-x86/mod.rs
deleted file mode 100644
index 4a02899b5..000000000
--- a/syscall_defines/src/linux-x86/mod.rs
+++ /dev/null
@@ -1,437 +0,0 @@
-// Copyright 2017 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Generated with: cat arch/x86/entry/syscalls/syscall_32.tbl |
-// awk ' { if ($1 != "#" && $1 != "") print " SYS_" $3 " = " $1"," } '
-#[allow(dead_code)]
-#[allow(non_camel_case_types)]
-pub enum LinuxSyscall {
- SYS_restart_syscall = 0,
- SYS_exit = 1,
- SYS_fork = 2,
- SYS_read = 3,
- SYS_write = 4,
- SYS_open = 5,
- SYS_close = 6,
- SYS_waitpid = 7,
- SYS_creat = 8,
- SYS_link = 9,
- SYS_unlink = 10,
- SYS_execve = 11,
- SYS_chdir = 12,
- SYS_time = 13,
- SYS_mknod = 14,
- SYS_chmod = 15,
- SYS_lchown = 16,
- SYS_break = 17,
- SYS_oldstat = 18,
- SYS_lseek = 19,
- SYS_getpid = 20,
- SYS_mount = 21,
- SYS_umount = 22,
- SYS_setuid = 23,
- SYS_getuid = 24,
- SYS_stime = 25,
- SYS_ptrace = 26,
- SYS_alarm = 27,
- SYS_oldfstat = 28,
- SYS_pause = 29,
- SYS_utime = 30,
- SYS_stty = 31,
- SYS_gtty = 32,
- SYS_access = 33,
- SYS_nice = 34,
- SYS_ftime = 35,
- SYS_sync = 36,
- SYS_kill = 37,
- SYS_rename = 38,
- SYS_mkdir = 39,
- SYS_rmdir = 40,
- SYS_dup = 41,
- SYS_pipe = 42,
- SYS_times = 43,
- SYS_prof = 44,
- SYS_brk = 45,
- SYS_setgid = 46,
- SYS_getgid = 47,
- SYS_signal = 48,
- SYS_geteuid = 49,
- SYS_getegid = 50,
- SYS_acct = 51,
- SYS_umount2 = 52,
- SYS_lock = 53,
- SYS_ioctl = 54,
- SYS_fcntl = 55,
- SYS_mpx = 56,
- SYS_setpgid = 57,
- SYS_ulimit = 58,
- SYS_oldolduname = 59,
- SYS_umask = 60,
- SYS_chroot = 61,
- SYS_ustat = 62,
- SYS_dup2 = 63,
- SYS_getppid = 64,
- SYS_getpgrp = 65,
- SYS_setsid = 66,
- SYS_sigaction = 67,
- SYS_sgetmask = 68,
- SYS_ssetmask = 69,
- SYS_setreuid = 70,
- SYS_setregid = 71,
- SYS_sigsuspend = 72,
- SYS_sigpending = 73,
- SYS_sethostname = 74,
- SYS_setrlimit = 75,
- SYS_getrlimit = 76,
- SYS_getrusage = 77,
- SYS_gettimeofday = 78,
- SYS_settimeofday = 79,
- SYS_getgroups = 80,
- SYS_setgroups = 81,
- SYS_select = 82,
- SYS_symlink = 83,
- SYS_oldlstat = 84,
- SYS_readlink = 85,
- SYS_uselib = 86,
- SYS_swapon = 87,
- SYS_reboot = 88,
- SYS_readdir = 89,
- SYS_mmap = 90,
- SYS_munmap = 91,
- SYS_truncate = 92,
- SYS_ftruncate = 93,
- SYS_fchmod = 94,
- SYS_fchown = 95,
- SYS_getpriority = 96,
- SYS_setpriority = 97,
- SYS_profil = 98,
- SYS_statfs = 99,
- SYS_fstatfs = 100,
- SYS_ioperm = 101,
- SYS_socketcall = 102,
- SYS_syslog = 103,
- SYS_setitimer = 104,
- SYS_getitimer = 105,
- SYS_stat = 106,
- SYS_lstat = 107,
- SYS_fstat = 108,
- SYS_olduname = 109,
- SYS_iopl = 110,
- SYS_vhangup = 111,
- SYS_idle = 112,
- SYS_vm86old = 113,
- SYS_wait4 = 114,
- SYS_swapoff = 115,
- SYS_sysinfo = 116,
- SYS_ipc = 117,
- SYS_fsync = 118,
- SYS_sigreturn = 119,
- SYS_clone = 120,
- SYS_setdomainname = 121,
- SYS_uname = 122,
- SYS_modify_ldt = 123,
- SYS_adjtimex = 124,
- SYS_mprotect = 125,
- SYS_sigprocmask = 126,
- SYS_create_module = 127,
- SYS_init_module = 128,
- SYS_delete_module = 129,
- SYS_get_kernel_syms = 130,
- SYS_quotactl = 131,
- SYS_getpgid = 132,
- SYS_fchdir = 133,
- SYS_bdflush = 134,
- SYS_sysfs = 135,
- SYS_personality = 136,
- SYS_afs_syscall = 137,
- SYS_setfsuid = 138,
- SYS_setfsgid = 139,
- SYS__llseek = 140,
- SYS_getdents = 141,
- SYS__newselect = 142,
- SYS_flock = 143,
- SYS_msync = 144,
- SYS_readv = 145,
- SYS_writev = 146,
- SYS_getsid = 147,
- SYS_fdatasync = 148,
- SYS__sysctl = 149,
- SYS_mlock = 150,
- SYS_munlock = 151,
- SYS_mlockall = 152,
- SYS_munlockall = 153,
- SYS_sched_setparam = 154,
- SYS_sched_getparam = 155,
- SYS_sched_setscheduler = 156,
- SYS_sched_getscheduler = 157,
- SYS_sched_yield = 158,
- SYS_sched_get_priority_max = 159,
- SYS_sched_get_priority_min = 160,
- SYS_sched_rr_get_interval = 161,
- SYS_nanosleep = 162,
- SYS_mremap = 163,
- SYS_setresuid = 164,
- SYS_getresuid = 165,
- SYS_vm86 = 166,
- SYS_query_module = 167,
- SYS_poll = 168,
- SYS_nfsservctl = 169,
- SYS_setresgid = 170,
- SYS_getresgid = 171,
- SYS_prctl = 172,
- SYS_rt_sigreturn = 173,
- SYS_rt_sigaction = 174,
- SYS_rt_sigprocmask = 175,
- SYS_rt_sigpending = 176,
- SYS_rt_sigtimedwait = 177,
- SYS_rt_sigqueueinfo = 178,
- SYS_rt_sigsuspend = 179,
- SYS_pread64 = 180,
- SYS_pwrite64 = 181,
- SYS_chown = 182,
- SYS_getcwd = 183,
- SYS_capget = 184,
- SYS_capset = 185,
- SYS_sigaltstack = 186,
- SYS_sendfile = 187,
- SYS_getpmsg = 188,
- SYS_putpmsg = 189,
- SYS_vfork = 190,
- SYS_ugetrlimit = 191,
- SYS_mmap2 = 192,
- SYS_truncate64 = 193,
- SYS_ftruncate64 = 194,
- SYS_stat64 = 195,
- SYS_lstat64 = 196,
- SYS_fstat64 = 197,
- SYS_lchown32 = 198,
- SYS_getuid32 = 199,
- SYS_getgid32 = 200,
- SYS_geteuid32 = 201,
- SYS_getegid32 = 202,
- SYS_setreuid32 = 203,
- SYS_setregid32 = 204,
- SYS_getgroups32 = 205,
- SYS_setgroups32 = 206,
- SYS_fchown32 = 207,
- SYS_setresuid32 = 208,
- SYS_getresuid32 = 209,
- SYS_setresgid32 = 210,
- SYS_getresgid32 = 211,
- SYS_chown32 = 212,
- SYS_setuid32 = 213,
- SYS_setgid32 = 214,
- SYS_setfsuid32 = 215,
- SYS_setfsgid32 = 216,
- SYS_pivot_root = 217,
- SYS_mincore = 218,
- SYS_madvise = 219,
- SYS_getdents64 = 220,
- SYS_fcntl64 = 221,
- SYS_gettid = 224,
- SYS_readahead = 225,
- SYS_setxattr = 226,
- SYS_lsetxattr = 227,
- SYS_fsetxattr = 228,
- SYS_getxattr = 229,
- SYS_lgetxattr = 230,
- SYS_fgetxattr = 231,
- SYS_listxattr = 232,
- SYS_llistxattr = 233,
- SYS_flistxattr = 234,
- SYS_removexattr = 235,
- SYS_lremovexattr = 236,
- SYS_fremovexattr = 237,
- SYS_tkill = 238,
- SYS_sendfile64 = 239,
- SYS_futex = 240,
- SYS_sched_setaffinity = 241,
- SYS_sched_getaffinity = 242,
- SYS_set_thread_area = 243,
- SYS_get_thread_area = 244,
- SYS_io_setup = 245,
- SYS_io_destroy = 246,
- SYS_io_getevents = 247,
- SYS_io_submit = 248,
- SYS_io_cancel = 249,
- SYS_fadvise64 = 250,
- SYS_exit_group = 252,
- SYS_lookup_dcookie = 253,
- SYS_epoll_create = 254,
- SYS_epoll_ctl = 255,
- SYS_epoll_wait = 256,
- SYS_remap_file_pages = 257,
- SYS_set_tid_address = 258,
- SYS_timer_create = 259,
- SYS_timer_settime = 260,
- SYS_timer_gettime = 261,
- SYS_timer_getoverrun = 262,
- SYS_timer_delete = 263,
- SYS_clock_settime = 264,
- SYS_clock_gettime = 265,
- SYS_clock_getres = 266,
- SYS_clock_nanosleep = 267,
- SYS_statfs64 = 268,
- SYS_fstatfs64 = 269,
- SYS_tgkill = 270,
- SYS_utimes = 271,
- SYS_fadvise64_64 = 272,
- SYS_vserver = 273,
- SYS_mbind = 274,
- SYS_get_mempolicy = 275,
- SYS_set_mempolicy = 276,
- SYS_mq_open = 277,
- SYS_mq_unlink = 278,
- SYS_mq_timedsend = 279,
- SYS_mq_timedreceive = 280,
- SYS_mq_notify = 281,
- SYS_mq_getsetattr = 282,
- SYS_kexec_load = 283,
- SYS_waitid = 284,
- SYS_add_key = 286,
- SYS_request_key = 287,
- SYS_keyctl = 288,
- SYS_ioprio_set = 289,
- SYS_ioprio_get = 290,
- SYS_inotify_init = 291,
- SYS_inotify_add_watch = 292,
- SYS_inotify_rm_watch = 293,
- SYS_migrate_pages = 294,
- SYS_openat = 295,
- SYS_mkdirat = 296,
- SYS_mknodat = 297,
- SYS_fchownat = 298,
- SYS_futimesat = 299,
- SYS_fstatat64 = 300,
- SYS_unlinkat = 301,
- SYS_renameat = 302,
- SYS_linkat = 303,
- SYS_symlinkat = 304,
- SYS_readlinkat = 305,
- SYS_fchmodat = 306,
- SYS_faccessat = 307,
- SYS_pselect6 = 308,
- SYS_ppoll = 309,
- SYS_unshare = 310,
- SYS_set_robust_list = 311,
- SYS_get_robust_list = 312,
- SYS_splice = 313,
- SYS_sync_file_range = 314,
- SYS_tee = 315,
- SYS_vmsplice = 316,
- SYS_move_pages = 317,
- SYS_getcpu = 318,
- SYS_epoll_pwait = 319,
- SYS_utimensat = 320,
- SYS_signalfd = 321,
- SYS_timerfd_create = 322,
- SYS_eventfd = 323,
- SYS_fallocate = 324,
- SYS_timerfd_settime = 325,
- SYS_timerfd_gettime = 326,
- SYS_signalfd4 = 327,
- SYS_eventfd2 = 328,
- SYS_epoll_create1 = 329,
- SYS_dup3 = 330,
- SYS_pipe2 = 331,
- SYS_inotify_init1 = 332,
- SYS_preadv = 333,
- SYS_pwritev = 334,
- SYS_rt_tgsigqueueinfo = 335,
- SYS_perf_event_open = 336,
- SYS_recvmmsg = 337,
- SYS_fanotify_init = 338,
- SYS_fanotify_mark = 339,
- SYS_prlimit64 = 340,
- SYS_name_to_handle_at = 341,
- SYS_open_by_handle_at = 342,
- SYS_clock_adjtime = 343,
- SYS_syncfs = 344,
- SYS_sendmmsg = 345,
- SYS_setns = 346,
- SYS_process_vm_readv = 347,
- SYS_process_vm_writev = 348,
- SYS_kcmp = 349,
- SYS_finit_module = 350,
- SYS_sched_setattr = 351,
- SYS_sched_getattr = 352,
- SYS_renameat2 = 353,
- SYS_seccomp = 354,
- SYS_getrandom = 355,
- SYS_memfd_create = 356,
- SYS_bpf = 357,
- SYS_execveat = 358,
- SYS_socket = 359,
- SYS_socketpair = 360,
- SYS_bind = 361,
- SYS_connect = 362,
- SYS_listen = 363,
- SYS_accept4 = 364,
- SYS_getsockopt = 365,
- SYS_setsockopt = 366,
- SYS_getsockname = 367,
- SYS_getpeername = 368,
- SYS_sendto = 369,
- SYS_sendmsg = 370,
- SYS_recvfrom = 371,
- SYS_recvmsg = 372,
- SYS_shutdown = 373,
- SYS_userfaultfd = 374,
- SYS_membarrier = 375,
- SYS_mlock2 = 376,
- SYS_copy_file_range = 377,
- SYS_preadv2 = 378,
- SYS_pwritev2 = 379,
- SYS_pkey_mprotect = 380,
- SYS_pkey_alloc = 381,
- SYS_pkey_free = 382,
- SYS_statx = 383,
- SYS_arch_prctl = 384,
- SYS_io_pgetevents = 385,
- SYS_rseq = 386,
- SYS_semget = 393,
- SYS_semctl = 394,
- SYS_shmget = 395,
- SYS_shmctl = 396,
- SYS_shmat = 397,
- SYS_shmdt = 398,
- SYS_msgget = 399,
- SYS_msgsnd = 400,
- SYS_msgrcv = 401,
- SYS_msgctl = 402,
- SYS_clock_gettime64 = 403,
- SYS_clock_settime64 = 404,
- SYS_clock_adjtime64 = 405,
- SYS_clock_getres_time64 = 406,
- SYS_clock_nanosleep_time64 = 407,
- SYS_timer_gettime64 = 408,
- SYS_timer_settime64 = 409,
- SYS_timerfd_gettime64 = 410,
- SYS_timerfd_settime64 = 411,
- SYS_utimensat_time64 = 412,
- SYS_pselect6_time64 = 413,
- SYS_ppoll_time64 = 414,
- SYS_io_pgetevents_time64 = 416,
- SYS_recvmmsg_time64 = 417,
- SYS_mq_timedsend_time64 = 418,
- SYS_mq_timedreceive_time64 = 419,
- SYS_semtimedop_time64 = 420,
- SYS_rt_sigtimedwait_time64 = 421,
- SYS_futex_time64 = 422,
- SYS_sched_rr_get_interval_time64 = 423,
- SYS_pidfd_send_signal = 424,
- SYS_io_uring_setup = 425,
- SYS_io_uring_enter = 426,
- SYS_io_uring_register = 427,
- SYS_open_tree = 428,
- SYS_move_mount = 429,
- SYS_fsopen = 430,
- SYS_fsconfig = 431,
- SYS_fsmount = 432,
- SYS_fspick = 433,
- SYS_pidfd_open = 434,
- SYS_clone3 = 435,
- SYS_openat2 = 437,
- SYS_pidfd_getfd = 438,
-}
diff --git a/syscall_defines/src/linux-x86_64/mod.rs b/syscall_defines/src/linux-x86_64/mod.rs
deleted file mode 100644
index 11d73540a..000000000
--- a/syscall_defines/src/linux-x86_64/mod.rs
+++ /dev/null
@@ -1,395 +0,0 @@
-// Copyright 2017 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Generated with: cat arch/x86/entry/syscalls/syscall_64.tbl |
-// awk ' { if ($1 != "#" && $1 != "") print " " ($2 == "x32" ? "compat_" : "") "SYS_" $3 " = " $1"," } '
-#[allow(dead_code)]
-#[allow(non_camel_case_types)]
-pub enum LinuxSyscall {
- SYS_read = 0,
- SYS_write = 1,
- SYS_open = 2,
- SYS_close = 3,
- SYS_stat = 4,
- SYS_fstat = 5,
- SYS_lstat = 6,
- SYS_poll = 7,
- SYS_lseek = 8,
- SYS_mmap = 9,
- SYS_mprotect = 10,
- SYS_munmap = 11,
- SYS_brk = 12,
- SYS_rt_sigaction = 13,
- SYS_rt_sigprocmask = 14,
- SYS_rt_sigreturn = 15,
- SYS_ioctl = 16,
- SYS_pread64 = 17,
- SYS_pwrite64 = 18,
- SYS_readv = 19,
- SYS_writev = 20,
- SYS_access = 21,
- SYS_pipe = 22,
- SYS_select = 23,
- SYS_sched_yield = 24,
- SYS_mremap = 25,
- SYS_msync = 26,
- SYS_mincore = 27,
- SYS_madvise = 28,
- SYS_shmget = 29,
- SYS_shmat = 30,
- SYS_shmctl = 31,
- SYS_dup = 32,
- SYS_dup2 = 33,
- SYS_pause = 34,
- SYS_nanosleep = 35,
- SYS_getitimer = 36,
- SYS_alarm = 37,
- SYS_setitimer = 38,
- SYS_getpid = 39,
- SYS_sendfile = 40,
- SYS_socket = 41,
- SYS_connect = 42,
- SYS_accept = 43,
- SYS_sendto = 44,
- SYS_recvfrom = 45,
- SYS_sendmsg = 46,
- SYS_recvmsg = 47,
- SYS_shutdown = 48,
- SYS_bind = 49,
- SYS_listen = 50,
- SYS_getsockname = 51,
- SYS_getpeername = 52,
- SYS_socketpair = 53,
- SYS_setsockopt = 54,
- SYS_getsockopt = 55,
- SYS_clone = 56,
- SYS_fork = 57,
- SYS_vfork = 58,
- SYS_execve = 59,
- SYS_exit = 60,
- SYS_wait4 = 61,
- SYS_kill = 62,
- SYS_uname = 63,
- SYS_semget = 64,
- SYS_semop = 65,
- SYS_semctl = 66,
- SYS_shmdt = 67,
- SYS_msgget = 68,
- SYS_msgsnd = 69,
- SYS_msgrcv = 70,
- SYS_msgctl = 71,
- SYS_fcntl = 72,
- SYS_flock = 73,
- SYS_fsync = 74,
- SYS_fdatasync = 75,
- SYS_truncate = 76,
- SYS_ftruncate = 77,
- SYS_getdents = 78,
- SYS_getcwd = 79,
- SYS_chdir = 80,
- SYS_fchdir = 81,
- SYS_rename = 82,
- SYS_mkdir = 83,
- SYS_rmdir = 84,
- SYS_creat = 85,
- SYS_link = 86,
- SYS_unlink = 87,
- SYS_symlink = 88,
- SYS_readlink = 89,
- SYS_chmod = 90,
- SYS_fchmod = 91,
- SYS_chown = 92,
- SYS_fchown = 93,
- SYS_lchown = 94,
- SYS_umask = 95,
- SYS_gettimeofday = 96,
- SYS_getrlimit = 97,
- SYS_getrusage = 98,
- SYS_sysinfo = 99,
- SYS_times = 100,
- SYS_ptrace = 101,
- SYS_getuid = 102,
- SYS_syslog = 103,
- SYS_getgid = 104,
- SYS_setuid = 105,
- SYS_setgid = 106,
- SYS_geteuid = 107,
- SYS_getegid = 108,
- SYS_setpgid = 109,
- SYS_getppid = 110,
- SYS_getpgrp = 111,
- SYS_setsid = 112,
- SYS_setreuid = 113,
- SYS_setregid = 114,
- SYS_getgroups = 115,
- SYS_setgroups = 116,
- SYS_setresuid = 117,
- SYS_getresuid = 118,
- SYS_setresgid = 119,
- SYS_getresgid = 120,
- SYS_getpgid = 121,
- SYS_setfsuid = 122,
- SYS_setfsgid = 123,
- SYS_getsid = 124,
- SYS_capget = 125,
- SYS_capset = 126,
- SYS_rt_sigpending = 127,
- SYS_rt_sigtimedwait = 128,
- SYS_rt_sigqueueinfo = 129,
- SYS_rt_sigsuspend = 130,
- SYS_sigaltstack = 131,
- SYS_utime = 132,
- SYS_mknod = 133,
- SYS_uselib = 134,
- SYS_personality = 135,
- SYS_ustat = 136,
- SYS_statfs = 137,
- SYS_fstatfs = 138,
- SYS_sysfs = 139,
- SYS_getpriority = 140,
- SYS_setpriority = 141,
- SYS_sched_setparam = 142,
- SYS_sched_getparam = 143,
- SYS_sched_setscheduler = 144,
- SYS_sched_getscheduler = 145,
- SYS_sched_get_priority_max = 146,
- SYS_sched_get_priority_min = 147,
- SYS_sched_rr_get_interval = 148,
- SYS_mlock = 149,
- SYS_munlock = 150,
- SYS_mlockall = 151,
- SYS_munlockall = 152,
- SYS_vhangup = 153,
- SYS_modify_ldt = 154,
- SYS_pivot_root = 155,
- SYS__sysctl = 156,
- SYS_prctl = 157,
- SYS_arch_prctl = 158,
- SYS_adjtimex = 159,
- SYS_setrlimit = 160,
- SYS_chroot = 161,
- SYS_sync = 162,
- SYS_acct = 163,
- SYS_settimeofday = 164,
- SYS_mount = 165,
- SYS_umount2 = 166,
- SYS_swapon = 167,
- SYS_swapoff = 168,
- SYS_reboot = 169,
- SYS_sethostname = 170,
- SYS_setdomainname = 171,
- SYS_iopl = 172,
- SYS_ioperm = 173,
- SYS_create_module = 174,
- SYS_init_module = 175,
- SYS_delete_module = 176,
- SYS_get_kernel_syms = 177,
- SYS_query_module = 178,
- SYS_quotactl = 179,
- SYS_nfsservctl = 180,
- SYS_getpmsg = 181,
- SYS_putpmsg = 182,
- SYS_afs_syscall = 183,
- SYS_tuxcall = 184,
- SYS_security = 185,
- SYS_gettid = 186,
- SYS_readahead = 187,
- SYS_setxattr = 188,
- SYS_lsetxattr = 189,
- SYS_fsetxattr = 190,
- SYS_getxattr = 191,
- SYS_lgetxattr = 192,
- SYS_fgetxattr = 193,
- SYS_listxattr = 194,
- SYS_llistxattr = 195,
- SYS_flistxattr = 196,
- SYS_removexattr = 197,
- SYS_lremovexattr = 198,
- SYS_fremovexattr = 199,
- SYS_tkill = 200,
- SYS_time = 201,
- SYS_futex = 202,
- SYS_sched_setaffinity = 203,
- SYS_sched_getaffinity = 204,
- SYS_set_thread_area = 205,
- SYS_io_setup = 206,
- SYS_io_destroy = 207,
- SYS_io_getevents = 208,
- SYS_io_submit = 209,
- SYS_io_cancel = 210,
- SYS_get_thread_area = 211,
- SYS_lookup_dcookie = 212,
- SYS_epoll_create = 213,
- SYS_epoll_ctl_old = 214,
- SYS_epoll_wait_old = 215,
- SYS_remap_file_pages = 216,
- SYS_getdents64 = 217,
- SYS_set_tid_address = 218,
- SYS_restart_syscall = 219,
- SYS_semtimedop = 220,
- SYS_fadvise64 = 221,
- SYS_timer_create = 222,
- SYS_timer_settime = 223,
- SYS_timer_gettime = 224,
- SYS_timer_getoverrun = 225,
- SYS_timer_delete = 226,
- SYS_clock_settime = 227,
- SYS_clock_gettime = 228,
- SYS_clock_getres = 229,
- SYS_clock_nanosleep = 230,
- SYS_exit_group = 231,
- SYS_epoll_wait = 232,
- SYS_epoll_ctl = 233,
- SYS_tgkill = 234,
- SYS_utimes = 235,
- SYS_vserver = 236,
- SYS_mbind = 237,
- SYS_set_mempolicy = 238,
- SYS_get_mempolicy = 239,
- SYS_mq_open = 240,
- SYS_mq_unlink = 241,
- SYS_mq_timedsend = 242,
- SYS_mq_timedreceive = 243,
- SYS_mq_notify = 244,
- SYS_mq_getsetattr = 245,
- SYS_kexec_load = 246,
- SYS_waitid = 247,
- SYS_add_key = 248,
- SYS_request_key = 249,
- SYS_keyctl = 250,
- SYS_ioprio_set = 251,
- SYS_ioprio_get = 252,
- SYS_inotify_init = 253,
- SYS_inotify_add_watch = 254,
- SYS_inotify_rm_watch = 255,
- SYS_migrate_pages = 256,
- SYS_openat = 257,
- SYS_mkdirat = 258,
- SYS_mknodat = 259,
- SYS_fchownat = 260,
- SYS_futimesat = 261,
- SYS_newfstatat = 262,
- SYS_unlinkat = 263,
- SYS_renameat = 264,
- SYS_linkat = 265,
- SYS_symlinkat = 266,
- SYS_readlinkat = 267,
- SYS_fchmodat = 268,
- SYS_faccessat = 269,
- SYS_pselect6 = 270,
- SYS_ppoll = 271,
- SYS_unshare = 272,
- SYS_set_robust_list = 273,
- SYS_get_robust_list = 274,
- SYS_splice = 275,
- SYS_tee = 276,
- SYS_sync_file_range = 277,
- SYS_vmsplice = 278,
- SYS_move_pages = 279,
- SYS_utimensat = 280,
- SYS_epoll_pwait = 281,
- SYS_signalfd = 282,
- SYS_timerfd_create = 283,
- SYS_eventfd = 284,
- SYS_fallocate = 285,
- SYS_timerfd_settime = 286,
- SYS_timerfd_gettime = 287,
- SYS_accept4 = 288,
- SYS_signalfd4 = 289,
- SYS_eventfd2 = 290,
- SYS_epoll_create1 = 291,
- SYS_dup3 = 292,
- SYS_pipe2 = 293,
- SYS_inotify_init1 = 294,
- SYS_preadv = 295,
- SYS_pwritev = 296,
- SYS_rt_tgsigqueueinfo = 297,
- SYS_perf_event_open = 298,
- SYS_recvmmsg = 299,
- SYS_fanotify_init = 300,
- SYS_fanotify_mark = 301,
- SYS_prlimit64 = 302,
- SYS_name_to_handle_at = 303,
- SYS_open_by_handle_at = 304,
- SYS_clock_adjtime = 305,
- SYS_syncfs = 306,
- SYS_sendmmsg = 307,
- SYS_setns = 308,
- SYS_getcpu = 309,
- SYS_process_vm_readv = 310,
- SYS_process_vm_writev = 311,
- SYS_kcmp = 312,
- SYS_finit_module = 313,
- SYS_sched_setattr = 314,
- SYS_sched_getattr = 315,
- SYS_renameat2 = 316,
- SYS_seccomp = 317,
- SYS_getrandom = 318,
- SYS_memfd_create = 319,
- SYS_kexec_file_load = 320,
- SYS_bpf = 321,
- SYS_execveat = 322,
- SYS_userfaultfd = 323,
- SYS_membarrier = 324,
- SYS_mlock2 = 325,
- SYS_copy_file_range = 326,
- SYS_preadv2 = 327,
- SYS_pwritev2 = 328,
- SYS_pkey_mprotect = 329,
- SYS_pkey_alloc = 330,
- SYS_pkey_free = 331,
- SYS_statx = 332,
- SYS_io_pgetevents = 333,
- SYS_rseq = 334,
- SYS_pidfd_send_signal = 424,
- SYS_io_uring_setup = 425,
- SYS_io_uring_enter = 426,
- SYS_io_uring_register = 427,
- SYS_open_tree = 428,
- SYS_move_mount = 429,
- SYS_fsopen = 430,
- SYS_fsconfig = 431,
- SYS_fsmount = 432,
- SYS_fspick = 433,
- SYS_pidfd_open = 434,
- SYS_clone3 = 435,
- SYS_openat2 = 437,
- SYS_pidfd_getfd = 438,
- compat_SYS_rt_sigaction = 512,
- compat_SYS_rt_sigreturn = 513,
- compat_SYS_ioctl = 514,
- compat_SYS_readv = 515,
- compat_SYS_writev = 516,
- compat_SYS_recvfrom = 517,
- compat_SYS_sendmsg = 518,
- compat_SYS_recvmsg = 519,
- compat_SYS_execve = 520,
- compat_SYS_ptrace = 521,
- compat_SYS_rt_sigpending = 522,
- compat_SYS_rt_sigtimedwait = 523,
- compat_SYS_rt_sigqueueinfo = 524,
- compat_SYS_sigaltstack = 525,
- compat_SYS_timer_create = 526,
- compat_SYS_mq_notify = 527,
- compat_SYS_kexec_load = 528,
- compat_SYS_waitid = 529,
- compat_SYS_set_robust_list = 530,
- compat_SYS_get_robust_list = 531,
- compat_SYS_vmsplice = 532,
- compat_SYS_move_pages = 533,
- compat_SYS_preadv = 534,
- compat_SYS_pwritev = 535,
- compat_SYS_rt_tgsigqueueinfo = 536,
- compat_SYS_recvmmsg = 537,
- compat_SYS_sendmmsg = 538,
- compat_SYS_process_vm_readv = 539,
- compat_SYS_process_vm_writev = 540,
- compat_SYS_setsockopt = 541,
- compat_SYS_getsockopt = 542,
- compat_SYS_io_setup = 543,
- compat_SYS_io_submit = 544,
- compat_SYS_execveat = 545,
- compat_SYS_preadv2 = 546,
- compat_SYS_pwritev2 = 547,
-}
diff --git a/system_api_stub/Android.bp b/system_api_stub/Android.bp
new file mode 100644
index 000000000..4855351da
--- /dev/null
+++ b/system_api_stub/Android.bp
@@ -0,0 +1,22 @@
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
+// Do not modify this file as changes will be overridden on upgrade.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_crosvm_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-BSD
+ default_applicable_licenses: ["external_crosvm_license"],
+}
+
+rust_library {
+ name: "libsystem_api",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "system_api",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
+ srcs: ["src/system_api.rs"],
+ edition: "2021",
+}
diff --git a/system_api_stub/Cargo.toml b/system_api_stub/Cargo.toml
new file mode 100644
index 000000000..764a68c7b
--- /dev/null
+++ b/system_api_stub/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "system_api"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2021"
+
+[lib]
+path = "src/system_api.rs"
diff --git a/system_api_stub/README.md b/system_api_stub/README.md
new file mode 100644
index 000000000..4a51914bc
--- /dev/null
+++ b/system_api_stub/README.md
@@ -0,0 +1,11 @@
+# Stub crate for system_api
+
+system_api is used by ChromeOS to interact with other system services.
+
+In ChromeOS builds, the `chromeos` cargo feature is enabled and this crate is replaced with the
+actual [system_api] implementation.
+
+On other platforms, the feature flag will remain disabled and this crate is used to satisfy cargo
+dependencies on system_api.
+
+[system_api]: https://source.chromium.org/chromiumos/chromiumos/codesearch/+/main:src/platform2/system_api/
diff --git a/system_api_stub/src/system_api.rs b/system_api_stub/src/system_api.rs
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/system_api_stub/src/system_api.rs
@@ -0,0 +1 @@
+
diff --git a/tempfile/Cargo.toml b/tempfile/Cargo.toml
deleted file mode 100644
index b7ac1c17a..000000000
--- a/tempfile/Cargo.toml
+++ /dev/null
@@ -1,8 +0,0 @@
-[package]
-name = "tempfile"
-version = "3.0.7"
-authors = ["The Chromium OS Authors"]
-edition = "2018"
-
-[dependencies]
-libc = "*"
diff --git a/tempfile/src/lib.rs b/tempfile/src/lib.rs
deleted file mode 100644
index 3bb9952f9..000000000
--- a/tempfile/src/lib.rs
+++ /dev/null
@@ -1,265 +0,0 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-//! Simplified tempfile which doesn't depend on the `rand` crate.
-//!
-//! # Example
-//!
-//! ```
-//! use std::io::Result;
-//! use std::path::{Path, PathBuf};
-//! use tempfile::TempDir;
-//!
-//! fn main() -> Result<()> {
-//! let t = TempDir::new()?;
-//! assert!(t.path().exists());
-//!
-//! Ok(())
-//! }
-//! ```
-
-use libc::{mkdtemp, mkstemp};
-use std::env;
-use std::ffi::CString;
-use std::fs::{self, File};
-use std::io::{Error, ErrorKind, Result};
-use std::mem::ManuallyDrop;
-use std::os::unix::io::FromRawFd;
-use std::path::{Path, PathBuf};
-use std::ptr;
-
-fn temp_path_template(prefix: &str) -> Result<CString> {
- // mkdtemp()/mkstemp() require the template to end in 6 X chars, which will be replaced
- // with random characters to make the path unique.
- let path_template = env::temp_dir().join(format!("{}.XXXXXX", prefix));
- match path_template.to_str() {
- Some(s) => Ok(CString::new(s)?),
- None => Err(Error::new(
- ErrorKind::InvalidData,
- "Path to string conversion failed",
- )),
- }
-}
-
-pub struct Builder {
- prefix: String,
-}
-
-// Note: we implement a builder because the protoc-rust crate uses this API from
-// crates.io's tempfile. Our code mostly uses TempDir::new directly.
-impl Builder {
- pub fn new() -> Self {
- Builder {
- prefix: ".tmp".to_owned(),
- }
- }
-
- /// Set a custom filename prefix.
- ///
- /// Default: `.tmp`
- pub fn prefix(&mut self, prefix: &str) -> &mut Self {
- self.prefix = prefix.to_owned();
- self
- }
-
- /// Creates a new temporary directory under libc's preferred system
- /// temporary directory. The new directory will be removed when the returned
- /// handle of type `TempDir` is dropped.
- pub fn tempdir(&self) -> Result<TempDir> {
- let template = temp_path_template(&self.prefix)?;
- let ptr = template.into_raw();
- // Safe because ownership of the buffer is handed off to mkdtemp() only
- // until it returns, and ownership is reclaimed by calling CString::from_raw()
- // on the same pointer returned by into_raw().
- let path = unsafe {
- let ret = mkdtemp(ptr);
- let path = CString::from_raw(ptr);
- if ret.is_null() {
- return Err(Error::last_os_error());
- }
- path
- };
- Ok(TempDir {
- path: PathBuf::from(path.to_str().map_err(|_| {
- Error::new(ErrorKind::InvalidData, "Path to string conversion failed")
- })?),
- })
- }
-
- /// Creates a new temporary file under libc's preferred system
- /// temporary directory. The new file will be removed when the returned
- /// handle of type `NamedTempFile` is dropped.
- pub fn tempfile(&self) -> Result<NamedTempFile> {
- let template = temp_path_template(&self.prefix)?;
- let ptr = template.into_raw();
- // Safe because ownership of the buffer is handed off to mkstemp() only
- // until it returns, and ownership is reclaimed by calling CString::from_raw()
- // on the same pointer returned by into_raw().
- let (file, path) = unsafe {
- let ret = mkstemp(ptr);
- let path = CString::from_raw(ptr);
- if ret < 0 {
- return Err(Error::last_os_error());
- }
- (File::from_raw_fd(ret), path)
- };
-
- Ok(NamedTempFile {
- path: TempPath {
- path: PathBuf::from(path.to_str().map_err(|_| {
- Error::new(ErrorKind::InvalidData, "Path to string conversion failed")
- })?),
- },
- file,
- })
- }
-}
-
-impl Default for Builder {
- fn default() -> Self {
- Self::new()
- }
-}
-
-/// Temporary directory. The directory will be removed when this object is
-/// dropped.
-pub struct TempDir {
- path: PathBuf,
- // When adding new fields to TempDir: note that anything with a Drop impl
- // will need to be dropped explicitly via ptr::read inside TempDir::remove
- // or else it gets leaked (memory safe but not ideal).
-}
-
-impl TempDir {
- pub fn new() -> Result<Self> {
- Builder::new().tempdir()
- }
-
- /// Accesses the tempdir's [`Path`].
- ///
- /// [`Path`]: http://doc.rust-lang.org/std/path/struct.Path.html
- pub fn path(&self) -> &Path {
- self.path.as_ref()
- }
-
- /// Removes the temporary directory.
- ///
- /// Calling this is optional as dropping a TempDir object will also remove
- /// the directory. Calling remove explicitly allows for any resulting error
- /// to be handled.
- pub fn remove(self) -> Result<()> {
- // Place self inside ManuallyDrop so its Drop impl doesn't run, but nor
- // does the path inside get dropped. Then use ptr::read to take out the
- // PathBuf so that it *does* get dropped correctly at the bottom of this
- // function.
- let dont_drop = ManuallyDrop::new(self);
- let path: PathBuf = unsafe { ptr::read(&dont_drop.path) };
-
- fs::remove_dir_all(path)
- }
-}
-
-impl Drop for TempDir {
- fn drop(&mut self) {
- let _ = fs::remove_dir_all(&self.path);
- }
-}
-
-/// Temporary file with a known name. The file will be removed when this object is dropped.
-pub struct NamedTempFile {
- path: TempPath,
- file: File,
-}
-
-impl NamedTempFile {
- pub fn new() -> Result<Self> {
- Builder::new().tempfile()
- }
-
- /// Accesses the temporary file's `Path`.
- pub fn path(&self) -> &Path {
- self.path.path.as_ref()
- }
-
- /// Accesses the temporary file's `File` object.
- pub fn as_file(&self) -> &File {
- &self.file
- }
-
- /// Accesses the temporary file's `File` object mutably.
- pub fn as_file_mut(&mut self) -> &mut File {
- &mut self.file
- }
-
- /// Convert this `TempFile` into an open `File` and unlink it from the filesystem.
- pub fn into_file(self) -> File {
- self.file
- }
-}
-
-// Container for NamedTempFile's path so that it can be dropped separately from the File.
-struct TempPath {
- path: PathBuf,
-}
-
-impl Drop for TempPath {
- fn drop(&mut self) {
- let _ = fs::remove_file(&self.path);
- }
-}
-
-/// Create a new anonymous temporary file under the preferred system
-/// temporary directory. The new file will be removed when the returned
-/// `File` is dropped.
-pub fn tempfile() -> Result<File> {
- Ok(NamedTempFile::new()?.into_file())
-}
-
-#[cfg(test)]
-mod tests {
- use std::io::{Read, Seek, SeekFrom, Write};
-
- use crate::{tempfile, NamedTempFile, TempDir};
-
- #[test]
- fn create_dir() {
- let t = TempDir::new().unwrap();
- let path = t.path();
- assert!(path.exists());
- assert!(path.is_dir());
- }
-
- #[test]
- fn remove_dir() {
- let t = TempDir::new().unwrap();
- let path = t.path().to_owned();
- assert!(t.remove().is_ok());
- assert!(!path.exists());
- }
-
- #[test]
- fn create_file() {
- let mut f = tempfile().expect("tempfile() failed");
- f.write_all(&[0, 1, 2, 3]).unwrap();
- f.seek(SeekFrom::Start(0)).unwrap();
- let mut data = vec![0u8; 4];
- f.read_exact(&mut data).unwrap();
- assert_eq!(data, [0, 1, 2, 3]);
- }
-
- #[test]
- fn create_named_file() {
- let named_temp = NamedTempFile::new().unwrap();
- let path = named_temp.path().to_owned();
- assert!(path.exists());
-
- // as_file() should not delete the file.
- let _f = named_temp.as_file();
- assert!(path.exists());
-
- // Dropping the NamedTempFile should delete the file.
- drop(named_temp);
- assert!(!path.exists());
- }
-}
diff --git a/test_all b/test_all
index fd09937a5..85aa3da6c 100755
--- a/test_all
+++ b/test_all
@@ -2,8 +2,4 @@
# Copyright 2021 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
-#
-# Runs tests for both x86 and aarch64.
-
-time ./ci/builder --vm ./run_tests &&
- time ./ci/aarch64_builder --vm ./run_tests
+echo "./test_all is deprecated. Please run ./tools/presubmit --quick"
diff --git a/tests/plugin_net_config.c b/tests/plugin_net_config.c
index c064c537a..a31fe77f9 100644
--- a/tests/plugin_net_config.c
+++ b/tests/plugin_net_config.c
@@ -49,7 +49,8 @@ int main(int argc, char** argv) {
unsigned int features;
if (ioctl(net_config.tap_fd, TUNGETFEATURES, &features) < 0) {
fprintf(stderr,
- "failed to read tap features: %s\n",
+ "failed to read tap(fd: %d) features: %s\n",
+ net_config.tap_fd,
strerror(errno));
return 1;
}
diff --git a/tests/plugins.rs b/tests/plugins.rs
index e7c872e45..cb7afad51 100644
--- a/tests/plugins.rs
+++ b/tests/plugins.rs
@@ -10,13 +10,20 @@ use std::fs::remove_file;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
+use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread::sleep;
use std::time::Duration;
use base::{ioctl, AsRawDescriptor};
-use rand_ish::urandom_str;
use tempfile::tempfile;
+lazy_static::lazy_static! {
+ static ref TAP_AVAILABLE: bool = {
+ use net_util::TapT;
+ net_util::Tap::new(true, false).is_ok()
+ };
+}
+
struct RemovePath(PathBuf);
impl Drop for RemovePath {
fn drop(&mut self) {
@@ -50,20 +57,23 @@ fn get_crosvm_path() -> PathBuf {
}
fn build_plugin(src: &str) -> RemovePath {
+ static PLUGIN_NUM: AtomicUsize = AtomicUsize::new(0);
let libcrosvm_plugin_dir = get_target_path();
let mut out_bin = libcrosvm_plugin_dir.clone();
- let randbin = urandom_str(10).expect("failed to generate random bin name");
- out_bin.push(randbin);
+ out_bin.push(format!(
+ "plugin-test{}",
+ PLUGIN_NUM.fetch_add(1, Ordering::Relaxed)
+ ));
let mut child = Command::new(var_os("CC").unwrap_or(OsString::from("cc")))
.args(&["-Icrosvm_plugin", "-pthread", "-o"]) // crosvm.h location and set output path.
.arg(&out_bin)
.arg("-L") // Path of shared object to link to.
.arg(&libcrosvm_plugin_dir)
- .arg("-lcrosvm_plugin")
.arg("-Wl,-rpath") // Search for shared object in the same path when exec'd.
.arg(&libcrosvm_plugin_dir)
.args(&["-Wl,-rpath", "."]) // Also check current directory in case of sandboxing.
.args(&["-xc", "-"]) // Read source code from piped stdin.
+ .arg("-lcrosvm_plugin")
.stdin(Stdio::piped())
.spawn()
.expect("failed to spawn compiler");
@@ -86,12 +96,6 @@ fn run_plugin(bin_path: &Path, with_sandbox: bool) {
"run",
"-c",
"1",
- "--host_ip",
- "100.115.92.5",
- "--netmask",
- "255.255.255.252",
- "--mac",
- "de:21:e8:47:6b:6a",
"--seccomp-policy-dir",
"tests",
"--plugin",
@@ -101,6 +105,17 @@ fn run_plugin(bin_path: &Path, with_sandbox: bool) {
.canonicalize()
.expect("failed to canonicalize plugin path"),
);
+
+ if *TAP_AVAILABLE {
+ cmd.args(&[
+ "--host_ip",
+ "100.115.92.5",
+ "--netmask",
+ "255.255.255.252",
+ "--mac",
+ "de:21:e8:47:6b:6a",
+ ]);
+ }
if !with_sandbox {
cmd.arg("--disable-sandbox");
}
@@ -259,6 +274,8 @@ fn test_supported_cpuid() {
test_plugin(include_str!("plugin_supported_cpuid.c"));
}
+// b:223675792
+#[ignore]
#[test]
fn test_enable_cap() {
test_plugin(include_str!("plugin_enable_cap.c"));
@@ -281,7 +298,9 @@ fn test_vcpu_pause() {
#[test]
fn test_net_config() {
- test_plugin(include_str!("plugin_net_config.c"));
+ if *TAP_AVAILABLE {
+ test_plugin(include_str!("plugin_net_config.c"));
+ }
}
#[test]
diff --git a/third_party/vmm_vhost/.buildkite/pipeline.yml b/third_party/vmm_vhost/.buildkite/pipeline.yml
new file mode 100644
index 000000000..0e77e1f1d
--- /dev/null
+++ b/third_party/vmm_vhost/.buildkite/pipeline.yml
@@ -0,0 +1,17 @@
+# Copyright 2021 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-Google file.
+
+steps:
+ - label: "clippy-x86-custom"
+ commands:
+ - cargo clippy --all-features --all-targets --workspace -- -D warnings
+ retry:
+ automatic: false
+ agents:
+ platform: x86_64.metal
+ os: linux
+ plugins:
+ - docker#v3.0.1:
+ image: "rustvmm/dev:v12"
+ always-pull: true
diff --git a/third_party/vmm_vhost/.cargo/config b/third_party/vmm_vhost/.cargo/config
new file mode 100644
index 000000000..bf8523e02
--- /dev/null
+++ b/third_party/vmm_vhost/.cargo/config
@@ -0,0 +1,5 @@
+# This workaround is needed because the linker is unable to find __addtf3,
+# __multf3 and __subtf3.
+# Related issue: https://github.com/rust-lang/compiler-builtins/issues/201
+[target.aarch64-unknown-linux-musl]
+rustflags = [ "-C", "target-feature=+crt-static", "-C", "link-arg=-lgcc"]
diff --git a/third_party/vmm_vhost/.github/dependabot.yml b/third_party/vmm_vhost/.github/dependabot.yml
new file mode 100644
index 000000000..4fcd556e7
--- /dev/null
+++ b/third_party/vmm_vhost/.github/dependabot.yml
@@ -0,0 +1,7 @@
+version: 2
+updates:
+- package-ecosystem: gitsubmodule
+ directory: "/"
+ schedule:
+ interval: daily
+ open-pull-requests-limit: 10
diff --git a/third_party/vmm_vhost/.gitignore b/third_party/vmm_vhost/.gitignore
new file mode 100644
index 000000000..f738aa84a
--- /dev/null
+++ b/third_party/vmm_vhost/.gitignore
@@ -0,0 +1,6 @@
+/build
+/kcov_build
+/target
+.idea
+**/*.rs.bk
+Cargo.lock
diff --git a/third_party/vmm_vhost/.gitmodules b/third_party/vmm_vhost/.gitmodules
new file mode 100644
index 000000000..bda97eb35
--- /dev/null
+++ b/third_party/vmm_vhost/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "rust-vmm-ci"]
+ path = rust-vmm-ci
+ url = https://github.com/rust-vmm/rust-vmm-ci.git
diff --git a/third_party/vmm_vhost/Android.bp b/third_party/vmm_vhost/Android.bp
new file mode 100644
index 000000000..d9f9e0ea7
--- /dev/null
+++ b/third_party/vmm_vhost/Android.bp
@@ -0,0 +1,100 @@
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
+
+package {
+ default_applicable_licenses: [
+ "external_crosvm_third_party_vmm_vhost_license",
+ ],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+//
+// large-scale-change included anything that looked like it might be a license
+// text as a license_text. e.g. LICENSE, NOTICE, COPYING etc.
+//
+// Please consider removing redundant or irrelevant files from 'license_text:'.
+// See: http://go/android-license-faq
+license {
+ name: "external_crosvm_third_party_vmm_vhost_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ "SPDX-license-identifier-BSD",
+ ],
+ license_text: [
+ "LICENSE",
+ "LICENSE-BSD-3-Clause",
+ "LICENSE-BSD-Chromium",
+ ],
+}
+
+rust_library {
+ name: "libvmm_vhost",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "vmm_vhost",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
+ srcs: ["src/lib.rs"],
+ edition: "2021",
+ features: [
+ "device",
+ "vfio-device",
+ "vmm",
+ ],
+ rustlibs: [
+ "libanyhow",
+ "libbase_rust",
+ "libbitflags",
+ "libcfg_if",
+ "libdata_model",
+ "liblibc",
+ "libtempfile",
+ "libthiserror",
+ ],
+ proc_macros: ["libremain"],
+}
+
+rust_test {
+ name: "vmm_vhost_test_src_lib",
+ defaults: ["crosvm_defaults"],
+ host_supported: true,
+ crate_name: "vmm_vhost",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
+ srcs: ["src/lib.rs"],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2021",
+ features: [
+ "device",
+ "vfio-device",
+ "vmm",
+ ],
+ rustlibs: [
+ "libanyhow",
+ "libbase_rust",
+ "libbitflags",
+ "libcfg_if",
+ "libdata_model",
+ "liblibc",
+ "libtempfile",
+ "libthiserror",
+ ],
+ proc_macros: ["libremain"],
+}
diff --git a/third_party/vmm_vhost/CODEOWNERS b/third_party/vmm_vhost/CODEOWNERS
new file mode 100644
index 000000000..7174a1b45
--- /dev/null
+++ b/third_party/vmm_vhost/CODEOWNERS
@@ -0,0 +1,2 @@
+# Add the list of code owners here (using their GitHub username)
+* gatekeeper-PullAssigner @jiangliu @eryugey @sboeuf @slp
diff --git a/third_party/vmm_vhost/Cargo.toml b/third_party/vmm_vhost/Cargo.toml
new file mode 100644
index 000000000..b7ba3381c
--- /dev/null
+++ b/third_party/vmm_vhost/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "vmm_vhost"
+version = "0.1.0"
+keywords = ["vhost", "vhost-user", "virtio", "vdpa"]
+description = "a pure rust library for vdpa, vhost and vhost-user"
+authors = ["Liu Jiang <gerry@linux.alibaba.com>"]
+repository = "https://github.com/rust-vmm/vhost"
+documentation = "https://docs.rs/vhost"
+readme = "README.md"
+license = "Apache-2.0 or BSD-3-Clause"
+edition = "2021"
+
+[features]
+default = []
+vmm = []
+device = []
+vfio-device = []
+
+[dependencies]
+anyhow = "*"
+base = { path = "../../base" }
+bitflags = ">=1.0.1"
+cfg-if = "1.0.0"
+data_model = { path = "../../common/data_model" }
+libc = ">=0.2.39"
+remain = "*"
+tempfile = "*"
+thiserror = { version = "1.0.20" }
+
+[target.'cfg(windows)'.dependencies]
+serde = { version = "1", features = [ "derive" ] }
+serde_json = "*"
diff --git a/third_party/vmm_vhost/LICENSE b/third_party/vmm_vhost/LICENSE
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/third_party/vmm_vhost/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/third_party/vmm_vhost/LICENSE-BSD-3-Clause b/third_party/vmm_vhost/LICENSE-BSD-3-Clause
new file mode 100644
index 000000000..1ff0cd75e
--- /dev/null
+++ b/third_party/vmm_vhost/LICENSE-BSD-3-Clause
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Alibaba Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/third_party/vmm_vhost/LICENSE-BSD-Chromium b/third_party/vmm_vhost/LICENSE-BSD-Chromium
new file mode 100644
index 000000000..8bafca303
--- /dev/null
+++ b/third_party/vmm_vhost/LICENSE-BSD-Chromium
@@ -0,0 +1,27 @@
+// Copyright 2017 The Chromium OS Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/third_party/vmm_vhost/README.md b/third_party/vmm_vhost/README.md
new file mode 100644
index 000000000..4e8d13dee
--- /dev/null
+++ b/third_party/vmm_vhost/README.md
@@ -0,0 +1,17 @@
+# vHost
+
+A pure rust library for vhost-user. This is a fork of [rust-vmm/vhost](https://github.com/rust-vmm/vhost).
+
+![vhost Architecture](/docs/vhost_architecture.png)
+
+The [vhost-user protocol](https://qemu.readthedocs.io/en/latest/interop/vhost-user.html#communication) aims to implement vhost backend drivers in
+userspace, which complements the ioctl interface used to control the vhost
+implementation in the Linux kernel. It implements the control plane needed
+to establish virtqueue sharing with a user space process on the same host.
+It uses communication over a Unix domain socket to share file descriptors in
+the ancillary data of the message.
+
+The protocol defines two sides of the communication, master and slave.
+Master is the application that shares its virtqueues, slave is the consumer
+of the virtqueues. Master and slave can be either a client (i.e. connecting)
+or server (listening) in the socket communication.
diff --git a/third_party/vmm_vhost/cargo2android.json b/third_party/vmm_vhost/cargo2android.json
new file mode 100644
index 000000000..b5b6dd4d9
--- /dev/null
+++ b/third_party/vmm_vhost/cargo2android.json
@@ -0,0 +1,8 @@
+{
+ "add_workspace": true,
+ "device": true,
+ "features": "device,vfio-device,vmm",
+ "global_defaults": "crosvm_defaults",
+ "run": true,
+ "tests": true
+}
diff --git a/third_party/vmm_vhost/docs/vhost_architecture.drawio b/third_party/vmm_vhost/docs/vhost_architecture.drawio
new file mode 100644
index 000000000..5906cf17c
--- /dev/null
+++ b/third_party/vmm_vhost/docs/vhost_architecture.drawio
@@ -0,0 +1 @@
+<mxfile host="app.diagrams.net" modified="2021-11-19T18:45:35.343Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36" etag="8ey8CHBbcITXM9trhA1N" version="15.8.3" type="device"><diagram id="xCgrIAQPDQM0eynUYBOE" name="Page-1">7V1tc5s4EP41/pgbJAw2HxsnaWeumetc7nrtR2IrNheMXCEnzv36k0DCIIRfsAC/TWZStEKK4Hm0u1otas8ezVefib+YPeIJCnvQmqx69l0PQrs/hOwfLvlIJcC23FQyJcFEyNaCp+A/JISWkC6DCYoLN1KMQxosisIxjiI0pgWZTwh+L972gsPiX134U1QSPI39sCz9J5jQWSodOtZa/gUF05n8y8ASNXNf3iwE8cyf4PecyL7v2SOCMU2v5qsRCvnbk+8lbfdQUZsNjKCI7tKgL178mx8uxcP1oBuytrfPhF1N+dVVUpQkwNEPyQaCl9EE8Rdqser3WUDR08If89p3NgGYbEbnISsBdhn6zyi89cev06TZCIeYsKoIR+z+2/gV0fFM9PSCIyq4Dx1Rlvf3oP3wYFkPD1wehKHaDyX4FeVu7t/xn6xGstbJnidPGcGiN0QoWuVEgkKfEZ4jSj7YLbLWlXwWMxoO0uL7enb05eyY5WaG5wmhL2bkNOt7TVp2IXhbweGBhsMKRmyeLfjlDK38KY7YYy8QCdifQmQt/SZFcDuOL8EKSc3Ey0US7INyGT0V+Ikfz5KuQQlAdwMvSiTIagxAPvAUxO0y5AC6GswHjgnM+9sxv87LG1AESYuRbl66AwMYwUrTsiCoAJX7a8kNXvImb178eRCyAX/qcaXi+nOOlLjhX0SfiR9EMbelOMJqfQ+ORE2cIn2bmHfWa5wAxvsE7m/OQqj2rFmq2h/9mM3+P9GvL340CRGR42VPmgzZhAFI2CPabideJ3N7aBdp0x+UeZN5MHnaGJnZ9VijxTeF/l2MkFcOLCtXY5pqKp++z3BM/44ROZhY4FAN1g2R+kUiOV6ZSLbORrgGiOSWXieaMC9eFDGhM8xsvh/er6WKFV/f8xXjhXjNjBf0Q6h/f0lxEQS0CugP0Zxf/+TXvzmidLfKVd19yELEnuyH7IAXcq14cd0sKcl2rQEe4yUZi3fopCLqkymiBT3P3+5GUhAU+jR4K66hdAgnTT8R4n/kbljgIKJxrudvXJDzQeGwSLaBB4tLn20NHAsq/ErHsGZb9jA7EdA5OfvXwvgsdYjQ2jTK0hCfQv8NPcQjfzxDTJ9WKdKLtNtgAJziFHBbNNy6VdjZGe7HJb0Q2w1cxQt0HY3x9hoy3sNOjXdmsH/mavTGuxMjDMtGuN+IES4ZTc+pIIXsIh2maLXB+rqqtQZKR+nDlTqqYYflIvhqiI0b4q8BU49R9XrmMs1wtqTJ1s+wpDmBTnOaMMMAXNlunu0nMMRkQm4PMVwnat5fhoWJag81/jJoaqKeU6Tr2FVM5tAfNEvOzsV3ivx3NBsEtt2Qiy/Xqsfv42cBulxM7me+7pgCdNL+5xcHoKXVAYCWQii35vKg1JOr9mRwfVCdA3F6ijhRdJxzKJp0rsYsy/Nyu5tKjXgvB6sxW9mKBppdBv0upwktVjPs1bUt/B2RqC5NjHt/JXoMLf5TJlQmN0AbR4lvaZ2/poKlYHiatPk+WfhHQ5sqclTTyQBt4KBL2nhd0ObQlWkaRD86vrS0Bw475Ivs97T4kmiaJ/xS2485D02jJm+1SpxyFDFdMXNZQOivJb8KIvbr++NjCQqZbrkgeIzieDscz9mb/2NJwyCSsNRPq9ycpNcJgo5mB02LIIAmILR3hnDmk8m7z6fWFcddbP7QahXIcqCwCshX5tXzjy2OHsaj8PhbhvGI4l1ZiKvGnnZVNCzLXquIhtWYoYU9bzELCnEtgV/ju95qKkS/ZlhL7WigdmQuqtVt/iPo7Z7/uCvdentlRzYVXvU0NATt0FBNp/XUnIldaah2BKzmsi9kXtilqj2DDktBHdoaHg7b4WEfVtjRfXmoduSpHRmk4RFllJ0TDTW7TRLUpmkIFS02rJuLpnbkqR2Zo6G0/5dKQ+1GULJFtBvdLI3Wc1tyAoEpJxC05gTa4DzoVk2bukTczdkbdkc3YFmKkzaoyTd12Zu5ZA3wDZ4H3zpSb9IL74ZviloCKk12zt1wrOJnKDcDuzHCdfKR1QVkk95Hk0WKzzVhtDqz25EJdG1kdts185SuXN8yxEcUx/zEnyvVN1DdtdukuqmPCQtMULPyCpWmKXiC80D56vFYcqM1PLft9ISViplhgP+e8n0a1O3dNpUabV94sOpQN1oXo2/NjVZWW05dN9pRPuaGDUalymllVTvMBM0x5SW5l6wS9fj2mrtJ+1I+lG13r7l/MnGfuiriWA4/aWkHGgwVdwwM667O1Z5gc19W9O1ToWFLduzwvWa7LcL1bUOEYyYRbu7JIOFq5kebXjcwqO3+Jp9d3tDsyuGgYey3dviL9W8qWHSaKwZVQWc5FfkFc1MrBqecr5us5m6WMV/IcW+N4jEupwbu9dYLB5Hu4ZGVD7fM+WjM/BxwhqYQGsAPZO6YdNk0AGq/hgUmQh4ykliFIPv9xi+OFL8dcVJwzo4/NYGfeqSM7ryi5uArZ1vrEuOvcGVugXpGSgmsLC3DPFg1j17o2sg+UbIc738AmfGItCY+Y1mjUZkwmdwEYUpJi1ATkdZxxnH2pgwrrg/hTx3R9f9lYN//Dw==</diagram></mxfile> \ No newline at end of file
diff --git a/third_party/vmm_vhost/docs/vhost_architecture.png b/third_party/vmm_vhost/docs/vhost_architecture.png
new file mode 100644
index 000000000..5ae3f4829
--- /dev/null
+++ b/third_party/vmm_vhost/docs/vhost_architecture.png
Binary files differ
diff --git a/third_party/vmm_vhost/src/backend.rs b/third_party/vmm_vhost/src/backend.rs
new file mode 100644
index 000000000..dbfa3dda7
--- /dev/null
+++ b/third_party/vmm_vhost/src/backend.rs
@@ -0,0 +1,526 @@
+// Copyright (C) 2019-2021 Alibaba Cloud. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
+//
+// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+//
+// 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-Google file.
+
+//! Common traits and structs for vhost-user backend drivers.
+
+use base::{RawDescriptor, INVALID_DESCRIPTOR};
+use std::cell::RefCell;
+use std::sync::RwLock;
+
+use base::Event;
+
+use super::Result;
+
+/// Maximum number of memory regions supported.
+pub const VHOST_MAX_MEMORY_REGIONS: usize = 255;
+
+/// Vring configuration data.
+pub struct VringConfigData {
+ /// Maximum queue size supported by the driver.
+ pub queue_max_size: u16,
+ /// Actual queue size negotiated by the driver.
+ pub queue_size: u16,
+ /// Bitmask of vring flags.
+ pub flags: u32,
+ /// Descriptor table address.
+ pub desc_table_addr: u64,
+ /// Used ring buffer address.
+ pub used_ring_addr: u64,
+ /// Available ring buffer address.
+ pub avail_ring_addr: u64,
+ /// Optional address for logging.
+ pub log_addr: Option<u64>,
+}
+
+impl VringConfigData {
+ /// Check whether the log (flag, address) pair is valid.
+ pub fn is_log_addr_valid(&self) -> bool {
+ if self.flags & 0x1 != 0 && self.log_addr.is_none() {
+ return false;
+ }
+
+ true
+ }
+
+ /// Get the log address, default to zero if not available.
+ pub fn get_log_addr(&self) -> u64 {
+ if self.flags & 0x1 != 0 && self.log_addr.is_some() {
+ self.log_addr.unwrap()
+ } else {
+ 0
+ }
+ }
+}
+
+/// Memory region configuration data.
+#[derive(Clone, Copy)]
+pub struct VhostUserMemoryRegionInfo {
+ /// Guest physical address of the memory region.
+ pub guest_phys_addr: u64,
+ /// Size of the memory region.
+ pub memory_size: u64,
+ /// Virtual address in the current process.
+ pub userspace_addr: u64,
+ /// Optional offset where region starts in the mapped memory.
+ pub mmap_offset: u64,
+ /// Optional file descriptor for mmap.
+ pub mmap_handle: RawDescriptor,
+}
+
+// We cannot derive default because windows Handle does not implement a default.
+impl Default for VhostUserMemoryRegionInfo {
+ fn default() -> Self {
+ VhostUserMemoryRegionInfo {
+ guest_phys_addr: u64::default(),
+ memory_size: u64::default(),
+ userspace_addr: u64::default(),
+ mmap_offset: u64::default(),
+ mmap_handle: INVALID_DESCRIPTOR,
+ }
+ }
+}
+
+/// An interface for setting up vhost-based backend drivers with interior mutability.
+///
+/// Vhost devices are subset of virtio devices, which improve virtio device's performance by
+/// delegating data plane operations to dedicated IO service processes. Vhost devices use the
+/// same virtqueue layout as virtio devices to allow vhost devices to be mapped directly to
+/// virtio devices.
+///
+/// The purpose of vhost is to implement a subset of a virtio device's functionality outside the
+/// VMM process. Typically fast paths for IO operations are delegated to the dedicated IO service
+/// processes, and slow path for device configuration are still handled by the VMM process. It may
+/// also be used to control access permissions of virtio backend devices.
+pub trait VhostBackend: std::marker::Sized {
+ /// Get a bitmask of supported virtio/vhost features.
+ fn get_features(&self) -> Result<u64>;
+
+ /// Inform the vhost subsystem which features to enable.
+ /// This should be a subset of supported features from get_features().
+ ///
+ /// # Arguments
+ /// * `features` - Bitmask of features to set.
+ fn set_features(&self, features: u64) -> Result<()>;
+
+ /// Set the current process as the owner of the vhost backend.
+ /// This must be run before any other vhost commands.
+ fn set_owner(&self) -> Result<()>;
+
+ /// Used to be sent to request disabling all rings
+ /// This is no longer used.
+ fn reset_owner(&self) -> Result<()>;
+
+ /// Set the guest memory mappings for vhost to use.
+ fn set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()>;
+
+ /// Set base address for page modification logging.
+ fn set_log_base(&self, base: u64, fd: Option<RawDescriptor>) -> Result<()>;
+
+ /// Specify an event file descriptor to signal on log write.
+ fn set_log_fd(&self, fd: RawDescriptor) -> Result<()>;
+
+ /// Set the number of descriptors in the vring.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to set descriptor count for.
+ /// * `num` - Number of descriptors in the queue.
+ fn set_vring_num(&self, queue_index: usize, num: u16) -> Result<()>;
+
+ /// Set the addresses for a given vring.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to set addresses for.
+ /// * `config_data` - Configuration data for a vring.
+ fn set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()>;
+
+ /// Set the first index to look for available descriptors.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to modify.
+ /// * `num` - Index where available descriptors start.
+ fn set_vring_base(&self, queue_index: usize, base: u16) -> Result<()>;
+
+ /// Get the available vring base offset.
+ fn get_vring_base(&self, queue_index: usize) -> Result<u32>;
+
+ /// Set the event to trigger when buffers have been used by the host.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to modify.
+ /// * `event` - Event to trigger.
+ fn set_vring_call(&self, queue_index: usize, event: &Event) -> Result<()>;
+
+ /// Set the event that will be signaled by the guest when buffers are
+ /// available for the host to process.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to modify.
+ /// * `event` - Event that will be signaled from guest.
+ fn set_vring_kick(&self, queue_index: usize, event: &Event) -> Result<()>;
+
+ /// Set the event that will be signaled by the guest when error happens.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to modify.
+ /// * `event` - Event that will be signaled from guest.
+ fn set_vring_err(&self, queue_index: usize, event: &Event) -> Result<()>;
+}
+
+/// An interface for setting up vhost-based backend drivers.
+///
+/// Vhost devices are subset of virtio devices, which improve virtio device's performance by
+/// delegating data plane operations to dedicated IO service processes. Vhost devices use the
+/// same virtqueue layout as virtio devices to allow vhost devices to be mapped directly to
+/// virtio devices.
+///
+/// The purpose of vhost is to implement a subset of a virtio device's functionality outside the
+/// VMM process. Typically fast paths for IO operations are delegated to the dedicated IO service
+/// processes, and slow path for device configuration are still handled by the VMM process. It may
+/// also be used to control access permissions of virtio backend devices.
+pub trait VhostBackendMut: std::marker::Sized {
+ /// Get a bitmask of supported virtio/vhost features.
+ fn get_features(&mut self) -> Result<u64>;
+
+ /// Inform the vhost subsystem which features to enable.
+ /// This should be a subset of supported features from get_features().
+ ///
+ /// # Arguments
+ /// * `features` - Bitmask of features to set.
+ fn set_features(&mut self, features: u64) -> Result<()>;
+
+ /// Set the current process as the owner of the vhost backend.
+ /// This must be run before any other vhost commands.
+ fn set_owner(&mut self) -> Result<()>;
+
+ /// Used to be sent to request disabling all rings
+ /// This is no longer used.
+ fn reset_owner(&mut self) -> Result<()>;
+
+ /// Set the guest memory mappings for vhost to use.
+ fn set_mem_table(&mut self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()>;
+
+ /// Set base address for page modification logging.
+ fn set_log_base(&mut self, base: u64, fd: Option<RawDescriptor>) -> Result<()>;
+
+ /// Specify an event file descriptor to signal on log write.
+ fn set_log_fd(&mut self, fd: RawDescriptor) -> Result<()>;
+
+ /// Set the number of descriptors in the vring.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to set descriptor count for.
+ /// * `num` - Number of descriptors in the queue.
+ fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()>;
+
+ /// Set the addresses for a given vring.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to set addresses for.
+ /// * `config_data` - Configuration data for a vring.
+ fn set_vring_addr(&mut self, queue_index: usize, config_data: &VringConfigData) -> Result<()>;
+
+ /// Set the first index to look for available descriptors.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to modify.
+ /// * `num` - Index where available descriptors start.
+ fn set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()>;
+
+ /// Get the available vring base offset.
+ fn get_vring_base(&mut self, queue_index: usize) -> Result<u32>;
+
+ /// Set the event to trigger when buffers have been used by the host.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to modify.
+ /// * `event` - Event to trigger.
+ fn set_vring_call(&mut self, queue_index: usize, event: &Event) -> Result<()>;
+
+ /// Set the event that will be signaled by the guest when buffers are
+ /// available for the host to process.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to modify.
+ /// * `event` - Event that will be signaled from guest.
+ fn set_vring_kick(&mut self, queue_index: usize, event: &Event) -> Result<()>;
+
+ /// Set the event that will be signaled by the guest when error happens.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to modify.
+ /// * `event` - Event that will be signaled from guest.
+ fn set_vring_err(&mut self, queue_index: usize, event: &Event) -> Result<()>;
+}
+
+impl<T: VhostBackendMut> VhostBackend for RwLock<T> {
+ fn get_features(&self) -> Result<u64> {
+ self.write().unwrap().get_features()
+ }
+
+ fn set_features(&self, features: u64) -> Result<()> {
+ self.write().unwrap().set_features(features)
+ }
+
+ fn set_owner(&self) -> Result<()> {
+ self.write().unwrap().set_owner()
+ }
+
+ fn reset_owner(&self) -> Result<()> {
+ self.write().unwrap().reset_owner()
+ }
+
+ fn set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()> {
+ self.write().unwrap().set_mem_table(regions)
+ }
+
+ fn set_log_base(&self, base: u64, fd: Option<RawDescriptor>) -> Result<()> {
+ self.write().unwrap().set_log_base(base, fd)
+ }
+
+ fn set_log_fd(&self, fd: RawDescriptor) -> Result<()> {
+ self.write().unwrap().set_log_fd(fd)
+ }
+
+ fn set_vring_num(&self, queue_index: usize, num: u16) -> Result<()> {
+ self.write().unwrap().set_vring_num(queue_index, num)
+ }
+
+ fn set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()> {
+ self.write()
+ .unwrap()
+ .set_vring_addr(queue_index, config_data)
+ }
+
+ fn set_vring_base(&self, queue_index: usize, base: u16) -> Result<()> {
+ self.write().unwrap().set_vring_base(queue_index, base)
+ }
+
+ fn get_vring_base(&self, queue_index: usize) -> Result<u32> {
+ self.write().unwrap().get_vring_base(queue_index)
+ }
+
+ fn set_vring_call(&self, queue_index: usize, event: &Event) -> Result<()> {
+ self.write().unwrap().set_vring_call(queue_index, event)
+ }
+
+ fn set_vring_kick(&self, queue_index: usize, event: &Event) -> Result<()> {
+ self.write().unwrap().set_vring_kick(queue_index, event)
+ }
+
+ fn set_vring_err(&self, queue_index: usize, event: &Event) -> Result<()> {
+ self.write().unwrap().set_vring_err(queue_index, event)
+ }
+}
+
+impl<T: VhostBackendMut> VhostBackend for RefCell<T> {
+ fn get_features(&self) -> Result<u64> {
+ self.borrow_mut().get_features()
+ }
+
+ fn set_features(&self, features: u64) -> Result<()> {
+ self.borrow_mut().set_features(features)
+ }
+
+ fn set_owner(&self) -> Result<()> {
+ self.borrow_mut().set_owner()
+ }
+
+ fn reset_owner(&self) -> Result<()> {
+ self.borrow_mut().reset_owner()
+ }
+
+ fn set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()> {
+ self.borrow_mut().set_mem_table(regions)
+ }
+
+ fn set_log_base(&self, base: u64, fd: Option<RawDescriptor>) -> Result<()> {
+ self.borrow_mut().set_log_base(base, fd)
+ }
+
+ fn set_log_fd(&self, fd: RawDescriptor) -> Result<()> {
+ self.borrow_mut().set_log_fd(fd)
+ }
+
+ fn set_vring_num(&self, queue_index: usize, num: u16) -> Result<()> {
+ self.borrow_mut().set_vring_num(queue_index, num)
+ }
+
+ fn set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()> {
+ self.borrow_mut().set_vring_addr(queue_index, config_data)
+ }
+
+ fn set_vring_base(&self, queue_index: usize, base: u16) -> Result<()> {
+ self.borrow_mut().set_vring_base(queue_index, base)
+ }
+
+ fn get_vring_base(&self, queue_index: usize) -> Result<u32> {
+ self.borrow_mut().get_vring_base(queue_index)
+ }
+
+ fn set_vring_call(&self, queue_index: usize, event: &Event) -> Result<()> {
+ self.borrow_mut().set_vring_call(queue_index, event)
+ }
+
+ fn set_vring_kick(&self, queue_index: usize, event: &Event) -> Result<()> {
+ self.borrow_mut().set_vring_kick(queue_index, event)
+ }
+
+ fn set_vring_err(&self, queue_index: usize, event: &Event) -> Result<()> {
+ self.borrow_mut().set_vring_err(queue_index, event)
+ }
+}
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ struct MockBackend {}
+
+ impl VhostBackendMut for MockBackend {
+ fn get_features(&mut self) -> Result<u64> {
+ Ok(0x1)
+ }
+
+ fn set_features(&mut self, features: u64) -> Result<()> {
+ assert_eq!(features, 0x1);
+ Ok(())
+ }
+
+ fn set_owner(&mut self) -> Result<()> {
+ Ok(())
+ }
+
+ fn reset_owner(&mut self) -> Result<()> {
+ Ok(())
+ }
+
+ fn set_mem_table(&mut self, _regions: &[VhostUserMemoryRegionInfo]) -> Result<()> {
+ Ok(())
+ }
+
+ fn set_log_base(&mut self, base: u64, fd: Option<RawDescriptor>) -> Result<()> {
+ assert_eq!(base, 0x100);
+ #[allow(clippy::unnecessary_cast)]
+ let rd = 100 as RawDescriptor;
+ assert_eq!(fd, Some(rd));
+ Ok(())
+ }
+
+ fn set_log_fd(&mut self, fd: RawDescriptor) -> Result<()> {
+ #[allow(clippy::unnecessary_cast)]
+ let rd = 100 as RawDescriptor;
+ assert_eq!(fd, rd);
+ Ok(())
+ }
+
+ fn set_vring_num(&mut self, queue_index: usize, num: u16) -> Result<()> {
+ assert_eq!(queue_index, 1);
+ assert_eq!(num, 256);
+ Ok(())
+ }
+
+ fn set_vring_addr(
+ &mut self,
+ queue_index: usize,
+ _config_data: &VringConfigData,
+ ) -> Result<()> {
+ assert_eq!(queue_index, 1);
+ Ok(())
+ }
+
+ fn set_vring_base(&mut self, queue_index: usize, base: u16) -> Result<()> {
+ assert_eq!(queue_index, 1);
+ assert_eq!(base, 2);
+ Ok(())
+ }
+
+ fn get_vring_base(&mut self, queue_index: usize) -> Result<u32> {
+ assert_eq!(queue_index, 1);
+ Ok(2)
+ }
+
+ fn set_vring_call(&mut self, queue_index: usize, _event: &Event) -> Result<()> {
+ assert_eq!(queue_index, 1);
+ Ok(())
+ }
+
+ fn set_vring_kick(&mut self, queue_index: usize, _event: &Event) -> Result<()> {
+ assert_eq!(queue_index, 1);
+ Ok(())
+ }
+
+ fn set_vring_err(&mut self, queue_index: usize, _event: &Event) -> Result<()> {
+ assert_eq!(queue_index, 1);
+ Ok(())
+ }
+ }
+
+ #[test]
+ fn test_vring_backend_mut() {
+ let b = RwLock::new(MockBackend {});
+
+ assert_eq!(b.get_features().unwrap(), 0x1);
+ b.set_features(0x1).unwrap();
+ b.set_owner().unwrap();
+ b.reset_owner().unwrap();
+ b.set_mem_table(&[]).unwrap();
+
+ #[allow(clippy::unnecessary_cast)]
+ let rd = 100 as RawDescriptor;
+ b.set_log_base(0x100, Some(rd)).unwrap();
+ b.set_log_fd(rd).unwrap();
+ b.set_vring_num(1, 256).unwrap();
+
+ let config = VringConfigData {
+ queue_max_size: 0x1000,
+ queue_size: 0x2000,
+ flags: 0x0,
+ desc_table_addr: 0x4000,
+ used_ring_addr: 0x5000,
+ avail_ring_addr: 0x6000,
+ log_addr: None,
+ };
+ b.set_vring_addr(1, &config).unwrap();
+
+ b.set_vring_base(1, 2).unwrap();
+ assert_eq!(b.get_vring_base(1).unwrap(), 2);
+
+ let event = Event::new().unwrap();
+ b.set_vring_call(1, &event).unwrap();
+ b.set_vring_kick(1, &event).unwrap();
+ b.set_vring_err(1, &event).unwrap();
+ }
+
+ #[test]
+ fn test_vring_config_data() {
+ let mut config = VringConfigData {
+ queue_max_size: 0x1000,
+ queue_size: 0x2000,
+ flags: 0x0,
+ desc_table_addr: 0x4000,
+ used_ring_addr: 0x5000,
+ avail_ring_addr: 0x6000,
+ log_addr: None,
+ };
+
+ assert!(config.is_log_addr_valid());
+ assert_eq!(config.get_log_addr(), 0);
+
+ config.flags = 0x1;
+ assert!(!config.is_log_addr_valid());
+ assert_eq!(config.get_log_addr(), 0);
+
+ config.log_addr = Some(0x7000);
+ assert!(config.is_log_addr_valid());
+ assert_eq!(config.get_log_addr(), 0x7000);
+
+ config.flags = 0x0;
+ assert!(config.is_log_addr_valid());
+ assert_eq!(config.get_log_addr(), 0);
+ }
+}
diff --git a/third_party/vmm_vhost/src/connection.rs b/third_party/vmm_vhost/src/connection.rs
new file mode 100644
index 000000000..ef11cfc6d
--- /dev/null
+++ b/third_party/vmm_vhost/src/connection.rs
@@ -0,0 +1,475 @@
+// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+//! Common data structures for listener and endpoint.
+
+cfg_if::cfg_if! {
+ if #[cfg(unix)] {
+ pub mod socket;
+ #[cfg(feature = "vfio-device")]
+ pub mod vfio;
+ mod unix;
+ } else if #[cfg(windows)] {
+ mod tube;
+ pub use tube::{TubeEndpoint, TubeListener};
+ mod windows;
+ }
+}
+
+use base::RawDescriptor;
+use std::fs::File;
+use std::io::{IoSlice, IoSliceMut};
+use std::mem;
+use std::path::Path;
+
+use data_model::DataInit;
+
+use super::message::*;
+use super::{Error, Result};
+use crate::connection::Req;
+
+/// Listener for accepting connections.
+pub trait Listener: Sized {
+ /// Type of an object created when a connection is accepted.
+ type Connection;
+
+ /// Accept an incoming connection.
+ fn accept(&mut self) -> Result<Option<Self::Connection>>;
+
+ /// Change blocking status on the listener.
+ fn set_nonblocking(&self, block: bool) -> Result<()>;
+}
+
+/// Abstracts a vhost-user connection and related operations.
+pub trait Endpoint<R: Req>: Sized {
+ /// Type of an object that Endpoint is created from.
+ type Listener: Listener;
+
+ /// Create an endpoint from a stream object.
+ fn from_connection(sock: <Self::Listener as Listener>::Connection) -> Self;
+
+ /// Create a new stream by connecting to server at `str`.
+ fn connect<P: AsRef<Path>>(path: P) -> Result<Self>;
+
+ /// Sends bytes from scatter-gather vectors with optional attached file descriptors.
+ ///
+ /// # Return:
+ /// * - number of bytes sent on success
+ fn send_iovec(&mut self, iovs: &[IoSlice], fds: Option<&[RawDescriptor]>) -> Result<usize>;
+
+ /// Reads bytes into the given scatter/gather vectors with optional attached file.
+ ///
+ /// # Arguements
+ /// * `bufs` - A slice of buffers to store received data.
+ /// * `allow_fd` - Indicates whether we can receive FDs.
+ ///
+ /// # Return:
+ /// * - (number of bytes received, [received files]) on success.
+ /// * - `Error::Disconnect` if the client closed.
+ fn recv_into_bufs(
+ &mut self,
+ bufs: &mut [IoSliceMut],
+ allow_fd: bool,
+ ) -> Result<(usize, Option<Vec<File>>)>;
+}
+
+// Advance the internal cursor of the slices.
+// This is same with a nightly API `IoSlice::advance_slices` but for `&[u8]`.
+fn advance_slices(bufs: &mut &mut [&[u8]], mut count: usize) {
+ use std::mem::take;
+
+ let mut idx = 0;
+ for b in bufs.iter() {
+ if count < b.len() {
+ break;
+ }
+ count -= b.len();
+ idx += 1;
+ }
+ *bufs = &mut take(bufs)[idx..];
+ if !bufs.is_empty() {
+ bufs[0] = &bufs[0][count..];
+ }
+}
+
+// Advance the internal cursor of the slices.
+// This is same with a nightly API `IoSliceMut::advance_slices` but for `&mut [u8]`.
+fn advance_slices_mut(bufs: &mut &mut [&mut [u8]], mut count: usize) {
+ use std::mem::take;
+
+ let mut idx = 0;
+ for b in bufs.iter() {
+ if count < b.len() {
+ break;
+ }
+ count -= b.len();
+ idx += 1;
+ }
+ *bufs = &mut take(bufs)[idx..];
+ if !bufs.is_empty() {
+ let slice = take(&mut bufs[0]);
+ let (_, remaining) = slice.split_at_mut(count);
+ bufs[0] = remaining;
+ }
+}
+
+/// Abstracts VVU message parsing, sending and receiving.
+pub trait EndpointExt<R: Req>: Endpoint<R> {
+ /// Sends all bytes from scatter-gather vectors with optional attached file descriptors. Will
+ /// loop until all data has been transfered.
+ ///
+ /// # Return:
+ /// * - number of bytes sent on success
+ ///
+ /// # TODO
+ /// This function takes a slice of `&[u8]` instead of `IoSlice` because the internal
+ /// cursor needs to be moved by `advance_slices()`.
+ /// Once `IoSlice::advance_slices()` becomes stable, this should be updated.
+ /// <https://github.com/rust-lang/rust/issues/62726>.
+ fn send_iovec_all(
+ &mut self,
+ mut iovs: &mut [&[u8]],
+ mut fds: Option<&[RawDescriptor]>,
+ ) -> Result<usize> {
+ // Guarantee that `iovs` becomes empty if it doesn't contain any data.
+ advance_slices(&mut iovs, 0);
+
+ let mut data_sent = 0;
+ while !iovs.is_empty() {
+ let iovec: Vec<_> = iovs.iter_mut().map(|i| IoSlice::new(i)).collect();
+ match self.send_iovec(&iovec, fds) {
+ Ok(0) => {
+ break;
+ }
+ Ok(n) => {
+ data_sent += n;
+ fds = None;
+ advance_slices(&mut iovs, n);
+ }
+ Err(e) => match e {
+ Error::SocketRetry(_) => {}
+ _ => return Err(e),
+ },
+ }
+ }
+ Ok(data_sent)
+ }
+
+ /// Sends bytes from a slice with optional attached file descriptors.
+ ///
+ /// # Return:
+ /// * - number of bytes sent on success
+ #[cfg(test)]
+ fn send_slice(&mut self, data: IoSlice, fds: Option<&[RawDescriptor]>) -> Result<usize> {
+ self.send_iovec(&[data], fds)
+ }
+
+ /// Sends a header-only message with optional attached file descriptors.
+ ///
+ /// # Return:
+ /// * - number of bytes sent on success
+ /// * - PartialMessage: received a partial message.
+ /// * - backend specific errors
+ fn send_header(
+ &mut self,
+ hdr: &VhostUserMsgHeader<R>,
+ fds: Option<&[RawDescriptor]>,
+ ) -> Result<()> {
+ let mut iovs = [hdr.as_slice()];
+ let bytes = self.send_iovec_all(&mut iovs[..], fds)?;
+ if bytes != mem::size_of::<VhostUserMsgHeader<R>>() {
+ return Err(Error::PartialMessage);
+ }
+ Ok(())
+ }
+
+ /// Send a message with header and body. Optional file descriptors may be attached to
+ /// the message.
+ ///
+ /// # Return:
+ /// * - number of bytes sent on success
+ /// * - OversizedMsg: message size is too big.
+ /// * - PartialMessage: received a partial message.
+ /// * - backend specific errors
+ fn send_message<T: Sized + DataInit>(
+ &mut self,
+ hdr: &VhostUserMsgHeader<R>,
+ body: &T,
+ fds: Option<&[RawDescriptor]>,
+ ) -> Result<()> {
+ if mem::size_of::<T>() > MAX_MSG_SIZE {
+ return Err(Error::OversizedMsg);
+ }
+
+ // We send the header and the body separately here. This is necessary on Windows. Otherwise
+ // the recv side cannot read the header independently (the transport is message oriented).
+ let mut bytes = self.send_iovec_all(&mut [hdr.as_slice()], fds)?;
+ bytes += self.send_iovec_all(&mut [body.as_slice()], None)?;
+ if bytes != mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<T>() {
+ return Err(Error::PartialMessage);
+ }
+ Ok(())
+ }
+
+ /// Send a message with header, body and payload. Optional file descriptors
+ /// may also be attached to the message.
+ ///
+ /// # Return:
+ /// * - number of bytes sent on success
+ /// * - OversizedMsg: message size is too big.
+ /// * - PartialMessage: received a partial message.
+ /// * - IncorrectFds: wrong number of attached fds.
+ /// * - backend specific errors
+ fn send_message_with_payload<T: Sized + DataInit>(
+ &mut self,
+ hdr: &VhostUserMsgHeader<R>,
+ body: &T,
+ payload: &[u8],
+ fds: Option<&[RawDescriptor]>,
+ ) -> Result<()> {
+ let len = payload.len();
+ if mem::size_of::<T>() > MAX_MSG_SIZE {
+ return Err(Error::OversizedMsg);
+ }
+ if len > MAX_MSG_SIZE - mem::size_of::<T>() {
+ return Err(Error::OversizedMsg);
+ }
+ if let Some(fd_arr) = fds {
+ if fd_arr.len() > MAX_ATTACHED_FD_ENTRIES {
+ return Err(Error::IncorrectFds);
+ }
+ }
+
+ let total = mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<T>() + len;
+
+ // We send the header and the body separately here. This is necessary on Windows. Otherwise
+ // the recv side cannot read the header independently (the transport is message oriented).
+ let mut len = self.send_iovec_all(&mut [hdr.as_slice()], fds)?;
+ len += self.send_iovec_all(&mut [body.as_slice(), payload], None)?;
+
+ if len != total {
+ return Err(Error::PartialMessage);
+ }
+ Ok(())
+ }
+
+ /// Reads `len` bytes at most.
+ ///
+ /// # Return:
+ /// * - (number of bytes received, buf) on success
+ fn recv_data(&mut self, len: usize) -> Result<Vec<u8>> {
+ let mut buf = vec![0u8; len];
+ let (data_len, _) =
+ self.recv_into_bufs(&mut [IoSliceMut::new(&mut buf)], false /* allow_fd */)?;
+ buf.truncate(data_len);
+ Ok(buf)
+ }
+
+ /// Reads all bytes into the given scatter/gather vectors with optional attached files. Will
+ /// loop until all data has been transferred.
+ ///
+ /// # Return:
+ /// * - (number of bytes received, [received fds]) on success
+ /// * - `Disconnect` - client is closed
+ ///
+ /// # TODO
+ /// This function takes a slice of `&mut [u8]` instead of `IoSliceMut` because the internal
+ /// cursor needs to be moved by `advance_slices_mut()`.
+ /// Once `IoSliceMut::advance_slices()` becomes stable, this should be updated.
+ /// <https://github.com/rust-lang/rust/issues/62726>.
+ fn recv_into_bufs_all(
+ &mut self,
+ mut bufs: &mut [&mut [u8]],
+ ) -> Result<(usize, Option<Vec<File>>)> {
+ let buf_lens: Vec<usize> = bufs.iter().map(|b| b.len()).collect();
+ let data_total: usize = buf_lens.iter().sum();
+ let mut data_read = 0;
+ let mut rfds = None;
+
+ while (data_total - data_read) > 0 {
+ let mut slices: Vec<IoSliceMut> = bufs.iter_mut().map(|b| IoSliceMut::new(b)).collect();
+ let res = self.recv_into_bufs(&mut slices, true);
+ match res {
+ Ok((0, _)) => return Ok((data_read, rfds)),
+ Ok((n, fds)) => {
+ if data_read == 0 {
+ rfds = fds;
+ }
+ data_read += n;
+ advance_slices_mut(&mut bufs, n);
+ }
+ Err(e) => match e {
+ Error::SocketRetry(_) => {}
+ _ => return Err(e),
+ },
+ }
+ }
+ Ok((data_read, rfds))
+ }
+
+ /// Reads bytes into a new buffer with optional attached files. Received file descriptors are
+ /// set close-on-exec and converted to `File`.
+ ///
+ /// # Return:
+ /// * - (number of bytes received, buf, [received files]) on success.
+ /// * - backend specific errors
+ #[cfg(test)]
+ fn recv_into_buf(&mut self, buf_size: usize) -> Result<(usize, Vec<u8>, Option<Vec<File>>)> {
+ let mut buf = vec![0u8; buf_size];
+ let mut slices = [IoSliceMut::new(buf.as_mut_slice())];
+ let (bytes, files) = self.recv_into_bufs(&mut slices, true /* allow_fd */)?;
+ Ok((bytes, buf, files))
+ }
+
+ /// Receive a header-only message with optional attached files.
+ /// Note, only the first MAX_ATTACHED_FD_ENTRIES file descriptors will be
+ /// accepted and all other file descriptor will be discard silently.
+ ///
+ /// # Return:
+ /// * - (message header, [received files]) on success.
+ /// * - Disconnect: the client closed the connection.
+ /// * - PartialMessage: received a partial message.
+ /// * - InvalidMessage: received a invalid message.
+ /// * - backend specific errors
+ fn recv_header(&mut self) -> Result<(VhostUserMsgHeader<R>, Option<Vec<File>>)> {
+ let mut hdr = VhostUserMsgHeader::default();
+ let (bytes, files) = self.recv_into_bufs(
+ &mut [IoSliceMut::new(hdr.as_mut_slice())],
+ true, /* allow_fd */
+ )?;
+
+ if bytes != mem::size_of::<VhostUserMsgHeader<R>>() {
+ return Err(Error::PartialMessage);
+ } else if !hdr.is_valid() {
+ return Err(Error::InvalidMessage);
+ }
+
+ Ok((hdr, files))
+ }
+
+ /// Receive a message with optional attached file descriptors.
+ /// Note, only the first MAX_ATTACHED_FD_ENTRIES file descriptors will be
+ /// accepted and all other file descriptor will be discard silently.
+ ///
+ /// # Return:
+ /// * - (message header, message body, [received files]) on success.
+ /// * - PartialMessage: received a partial message.
+ /// * - InvalidMessage: received a invalid message.
+ /// * - backend specific errors
+ fn recv_body<T: Sized + DataInit + Default + VhostUserMsgValidator>(
+ &mut self,
+ ) -> Result<(VhostUserMsgHeader<R>, T, Option<Vec<File>>)> {
+ let mut hdr = VhostUserMsgHeader::default();
+ let mut body: T = Default::default();
+ let mut slices = [hdr.as_mut_slice(), body.as_mut_slice()];
+ let (bytes, files) = self.recv_into_bufs_all(&mut slices)?;
+
+ let total = mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<T>();
+ if bytes != total {
+ return Err(Error::PartialMessage);
+ } else if !hdr.is_valid() || !body.is_valid() {
+ return Err(Error::InvalidMessage);
+ }
+
+ Ok((hdr, body, files))
+ }
+
+ /// Receive a message with header and optional content. Callers need to
+ /// pre-allocate a big enough buffer to receive the message body and
+ /// optional payload. If there are attached file descriptor associated
+ /// with the message, the first MAX_ATTACHED_FD_ENTRIES file descriptors
+ /// will be accepted and all other file descriptor will be discard
+ /// silently.
+ ///
+ /// # Return:
+ /// * - (message header, message size, [received files]) on success.
+ /// * - PartialMessage: received a partial message.
+ /// * - InvalidMessage: received a invalid message.
+ /// * - backend specific errors
+ #[cfg(test)]
+ fn recv_body_into_buf(
+ &mut self,
+ buf: &mut [u8],
+ ) -> Result<(VhostUserMsgHeader<R>, usize, Option<Vec<File>>)> {
+ let mut hdr = VhostUserMsgHeader::default();
+ let mut slices = [hdr.as_mut_slice(), buf];
+ let (bytes, files) = self.recv_into_bufs_all(&mut slices)?;
+
+ if bytes < mem::size_of::<VhostUserMsgHeader<R>>() {
+ return Err(Error::PartialMessage);
+ } else if !hdr.is_valid() {
+ return Err(Error::InvalidMessage);
+ }
+
+ Ok((hdr, bytes - mem::size_of::<VhostUserMsgHeader<R>>(), files))
+ }
+
+ /// Receive a message with optional payload and attached file descriptors.
+ /// Note, only the first MAX_ATTACHED_FD_ENTRIES file descriptors will be
+ /// accepted and all other file descriptor will be discard silently.
+ ///
+ /// # Return:
+ /// * - (message header, message body, size of payload, [received files]) on success.
+ /// * - PartialMessage: received a partial message.
+ /// * - InvalidMessage: received a invalid message.
+ /// * - backend specific errors
+ #[cfg_attr(feature = "cargo-clippy", allow(clippy::type_complexity))]
+ fn recv_payload_into_buf<T: Sized + DataInit + Default + VhostUserMsgValidator>(
+ &mut self,
+ buf: &mut [u8],
+ ) -> Result<(VhostUserMsgHeader<R>, T, usize, Option<Vec<File>>)> {
+ let mut hdr = VhostUserMsgHeader::default();
+ let mut body: T = Default::default();
+ let mut slices = [hdr.as_mut_slice(), body.as_mut_slice(), buf];
+ let (bytes, files) = self.recv_into_bufs_all(&mut slices)?;
+
+ let total = mem::size_of::<VhostUserMsgHeader<R>>() + mem::size_of::<T>();
+ if bytes < total {
+ return Err(Error::PartialMessage);
+ } else if !hdr.is_valid() || !body.is_valid() {
+ return Err(Error::InvalidMessage);
+ }
+
+ Ok((hdr, body, bytes - total, files))
+ }
+}
+
+impl<R: Req, E: Endpoint<R>> EndpointExt<R> for E {}
+
+#[cfg(test)]
+pub(crate) mod tests {
+ use super::*;
+ cfg_if::cfg_if! {
+ if #[cfg(unix)] {
+ #[cfg(feature = "vmm")]
+ pub(crate) use super::unix::tests::*;
+ } else if #[cfg(windows)] {
+ #[cfg(feature = "vmm")]
+ pub(crate) use windows::tests::*;
+ }
+ }
+
+ #[test]
+ fn test_advance_slices() {
+ // Test case from https://doc.rust-lang.org/std/io/struct.IoSlice.html#method.advance_slices
+ let buf1 = [1; 8];
+ let buf2 = [2; 16];
+ let buf3 = [3; 8];
+ let mut bufs = &mut [&buf1[..], &buf2[..], &buf3[..]][..];
+ advance_slices(&mut bufs, 10);
+ assert_eq!(bufs[0], [2; 14].as_ref());
+ assert_eq!(bufs[1], [3; 8].as_ref());
+ }
+
+ #[test]
+ fn test_advance_slices_mut() {
+ // Test case from https://doc.rust-lang.org/std/io/struct.IoSliceMut.html#method.advance_slices
+ let mut buf1 = [1; 8];
+ let mut buf2 = [2; 16];
+ let mut buf3 = [3; 8];
+ let mut bufs = &mut [&mut buf1[..], &mut buf2[..], &mut buf3[..]][..];
+ advance_slices_mut(&mut bufs, 10);
+ assert_eq!(bufs[0], [2; 14].as_ref());
+ assert_eq!(bufs[1], [3; 8].as_ref());
+ }
+}
diff --git a/third_party/vmm_vhost/src/connection/socket.rs b/third_party/vmm_vhost/src/connection/socket.rs
new file mode 100644
index 000000000..ea0759d8f
--- /dev/null
+++ b/third_party/vmm_vhost/src/connection/socket.rs
@@ -0,0 +1,486 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+//! Structs for Unix Domain Socket listener and endpoint.
+
+use std::fs::File;
+use std::io::{ErrorKind, IoSlice, IoSliceMut};
+use std::marker::PhantomData;
+use std::path::{Path, PathBuf};
+
+use base::{AsRawDescriptor, FromRawDescriptor, RawDescriptor, ScmSocket};
+
+use super::{Error, Result};
+use crate::connection::{Endpoint as EndpointTrait, Listener as ListenerTrait, Req};
+use crate::message::*;
+use crate::{SystemListener, SystemStream};
+
+/// Unix domain socket listener for accepting incoming connections.
+pub struct Listener {
+ fd: SystemListener,
+ path: PathBuf,
+}
+
+impl Listener {
+ /// Create a unix domain socket listener.
+ ///
+ /// # Return:
+ /// * - the new Listener object on success.
+ /// * - SocketError: failed to create listener socket.
+ pub fn new<P: AsRef<Path>>(path: P, unlink: bool) -> Result<Self> {
+ if unlink {
+ let _ = std::fs::remove_file(&path);
+ }
+ let fd = SystemListener::bind(&path).map_err(Error::SocketError)?;
+ Ok(Listener {
+ fd,
+ path: path.as_ref().to_owned(),
+ })
+ }
+}
+
+impl ListenerTrait for Listener {
+ type Connection = SystemStream;
+
+ /// Accept an incoming connection.
+ ///
+ /// # Return:
+ /// * - Some(SystemListener): new SystemListener object if new incoming connection is available.
+ /// * - None: no incoming connection available.
+ /// * - SocketError: errors from accept().
+ fn accept(&mut self) -> Result<Option<Self::Connection>> {
+ loop {
+ match self.fd.accept() {
+ Ok((stream, _addr)) => return Ok(Some(stream)),
+ Err(e) => {
+ match e.kind() {
+ // No incoming connection available.
+ ErrorKind::WouldBlock => return Ok(None),
+ // New connection closed by peer.
+ ErrorKind::ConnectionAborted => return Ok(None),
+ // Interrupted by signals, retry
+ ErrorKind::Interrupted => continue,
+ _ => return Err(Error::SocketError(e)),
+ }
+ }
+ }
+ }
+ }
+
+ /// Change blocking status on the listener.
+ ///
+ /// # Return:
+ /// * - () on success.
+ /// * - SocketError: failure from set_nonblocking().
+ fn set_nonblocking(&self, block: bool) -> Result<()> {
+ self.fd.set_nonblocking(block).map_err(Error::SocketError)
+ }
+}
+
+impl AsRawDescriptor for Listener {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.fd.as_raw_descriptor()
+ }
+}
+
+impl Drop for Listener {
+ fn drop(&mut self) {
+ let _ = std::fs::remove_file(&self.path);
+ }
+}
+
+/// Unix domain socket endpoint for vhost-user connection.
+pub struct Endpoint<R: Req> {
+ sock: SystemStream,
+ _r: PhantomData<R>,
+}
+
+impl<R: Req> From<SystemStream> for Endpoint<R> {
+ fn from(sock: SystemStream) -> Self {
+ Self {
+ sock,
+ _r: PhantomData,
+ }
+ }
+}
+
+impl<R: Req> EndpointTrait<R> for Endpoint<R> {
+ type Listener = Listener;
+
+ /// Create an endpoint from a stream object.
+ fn from_connection(
+ sock: <<Self as EndpointTrait<R>>::Listener as ListenerTrait>::Connection,
+ ) -> Self {
+ Self {
+ sock,
+ _r: PhantomData,
+ }
+ }
+
+ /// Create a new stream by connecting to server at `str`.
+ ///
+ /// # Return:
+ /// * - the new Endpoint object on success.
+ /// * - SocketConnect: failed to connect to peer.
+ fn connect<P: AsRef<Path>>(path: P) -> Result<Self> {
+ let sock = SystemStream::connect(path).map_err(Error::SocketConnect)?;
+ Ok(Self::from(sock))
+ }
+
+ /// Sends bytes from scatter-gather vectors over the socket with optional attached file
+ /// descriptors.
+ ///
+ /// # Return:
+ /// * - number of bytes sent on success
+ /// * - SocketRetry: temporary error caused by signals or short of resources.
+ /// * - SocketBroken: the underline socket is broken.
+ /// * - SocketError: other socket related errors.
+ fn send_iovec(&mut self, iovs: &[IoSlice], fds: Option<&[RawDescriptor]>) -> Result<usize> {
+ let rfds = match fds {
+ Some(rfds) => rfds,
+ _ => &[],
+ };
+ self.sock.send_bufs_with_fds(iovs, rfds).map_err(Into::into)
+ }
+
+ /// Reads bytes from the socket into the given scatter/gather vectors with optional attached
+ /// file.
+ ///
+ /// The underlying communication channel is a Unix domain socket in STREAM mode. It's a little
+ /// tricky to pass file descriptors through such a communication channel. Let's assume that a
+ /// sender sending a message with some file descriptors attached. To successfully receive those
+ /// attached file descriptors, the receiver must obey following rules:
+ /// 1) file descriptors are attached to a message.
+ /// 2) message(packet) boundaries must be respected on the receive side.
+ /// In other words, recvmsg() operations must not cross the packet boundary, otherwise the
+ /// attached file descriptors will get lost.
+ /// Note that this function wraps received file descriptors as `File`.
+ ///
+ /// # Return:
+ /// * - (number of bytes received, [received files]) on success
+ /// * - Disconnect: the connection is closed.
+ /// * - SocketRetry: temporary error caused by signals or short of resources.
+ /// * - SocketBroken: the underline socket is broken.
+ /// * - SocketError: other socket related errors.
+ fn recv_into_bufs(
+ &mut self,
+ bufs: &mut [IoSliceMut],
+ allow_fd: bool,
+ ) -> Result<(usize, Option<Vec<File>>)> {
+ let mut fd_array = if allow_fd {
+ vec![0; MAX_ATTACHED_FD_ENTRIES]
+ } else {
+ vec![]
+ };
+ let mut iovs: Vec<_> = bufs.iter_mut().map(|s| IoSliceMut::new(s)).collect();
+ let (bytes, fds) = self.sock.recv_iovecs_with_fds(&mut iovs, &mut fd_array)?;
+
+ // 0-bytes indicates that the connection is closed.
+ if bytes == 0 {
+ return Err(Error::Disconnect);
+ }
+
+ let files = match fds {
+ 0 => None,
+ n => {
+ let files = fd_array
+ .iter()
+ .take(n)
+ .map(|fd| {
+ // Safe because we have the ownership of `fd`.
+ unsafe { File::from_raw_descriptor(*fd as RawDescriptor) }
+ })
+ .collect();
+ Some(files)
+ }
+ };
+
+ Ok((bytes, files))
+ }
+}
+
+impl<T: Req> AsRawDescriptor for Endpoint<T> {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.sock.as_raw_descriptor()
+ }
+}
+
+impl<T: Req> AsMut<SystemStream> for Endpoint<T> {
+ fn as_mut(&mut self) -> &mut SystemStream {
+ &mut self.sock
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::{Read, Seek, SeekFrom, Write};
+ use std::{mem, slice};
+ use tempfile::{tempfile, Builder, TempDir};
+
+ use crate::connection::EndpointExt;
+
+ fn temp_dir() -> TempDir {
+ Builder::new().prefix("/tmp/vhost_test").tempdir().unwrap()
+ }
+
+ #[test]
+ fn create_listener() {
+ let dir = temp_dir();
+ let mut path = dir.path().to_owned();
+ path.push("sock");
+ let listener = Listener::new(&path, true).unwrap();
+
+ assert!(listener.as_raw_descriptor() > 0);
+ }
+
+ #[test]
+ fn accept_connection() {
+ let dir = temp_dir();
+ let mut path = dir.path().to_owned();
+ path.push("sock");
+ let mut listener = Listener::new(&path, true).unwrap();
+ listener.set_nonblocking(true).unwrap();
+
+ // accept on a fd without incoming connection
+ let conn = listener.accept().unwrap();
+ assert!(conn.is_none());
+ }
+
+ #[test]
+ fn send_data() {
+ let dir = temp_dir();
+ let mut path = dir.path().to_owned();
+ path.push("sock");
+ let mut listener = Listener::new(&path, true).unwrap();
+ listener.set_nonblocking(true).unwrap();
+ let mut master = Endpoint::<MasterReq>::connect(&path).unwrap();
+ let sock = listener.accept().unwrap().unwrap();
+ let mut slave = Endpoint::<MasterReq>::from(sock);
+
+ let buf1 = vec![0x1, 0x2, 0x3, 0x4];
+ let mut len = master.send_slice(IoSlice::new(&buf1[..]), None).unwrap();
+ assert_eq!(len, 4);
+ let (bytes, buf2, _) = slave.recv_into_buf(0x1000).unwrap();
+ assert_eq!(bytes, 4);
+ assert_eq!(&buf1[..], &buf2[..bytes]);
+
+ len = master.send_slice(IoSlice::new(&buf1[..]), None).unwrap();
+ assert_eq!(len, 4);
+ let (bytes, buf2, _) = slave.recv_into_buf(0x2).unwrap();
+ assert_eq!(bytes, 2);
+ assert_eq!(&buf1[..2], &buf2[..]);
+ let (bytes, buf2, _) = slave.recv_into_buf(0x2).unwrap();
+ assert_eq!(bytes, 2);
+ assert_eq!(&buf1[2..], &buf2[..]);
+ }
+
+ #[test]
+ fn send_fd() {
+ let dir = temp_dir();
+ let mut path = dir.path().to_owned();
+ path.push("sock");
+ let mut listener = Listener::new(&path, true).unwrap();
+ listener.set_nonblocking(true).unwrap();
+ let mut master = Endpoint::<MasterReq>::connect(&path).unwrap();
+ let sock = listener.accept().unwrap().unwrap();
+ let mut slave = Endpoint::<MasterReq>::from(sock);
+
+ let mut fd = tempfile().unwrap();
+ write!(fd, "test").unwrap();
+
+ // Normal case for sending/receiving file descriptors
+ let buf1 = vec![0x1, 0x2, 0x3, 0x4];
+ let len = master
+ .send_slice(IoSlice::new(&buf1[..]), Some(&[fd.as_raw_descriptor()]))
+ .unwrap();
+ assert_eq!(len, 4);
+
+ let (bytes, buf2, files) = slave.recv_into_buf(4).unwrap();
+ assert_eq!(bytes, 4);
+ assert_eq!(&buf1[..], &buf2[..]);
+ assert!(files.is_some());
+ let files = files.unwrap();
+ {
+ assert_eq!(files.len(), 1);
+ let mut file = &files[0];
+ let mut content = String::new();
+ file.seek(SeekFrom::Start(0)).unwrap();
+ file.read_to_string(&mut content).unwrap();
+ assert_eq!(content, "test");
+ }
+
+ // Following communication pattern should work:
+ // Sending side: data(header, body) with fds
+ // Receiving side: data(header) with fds, data(body)
+ let len = master
+ .send_slice(
+ IoSlice::new(&buf1[..]),
+ Some(&[
+ fd.as_raw_descriptor(),
+ fd.as_raw_descriptor(),
+ fd.as_raw_descriptor(),
+ ]),
+ )
+ .unwrap();
+ assert_eq!(len, 4);
+
+ let (bytes, buf2, files) = slave.recv_into_buf(0x2).unwrap();
+ assert_eq!(bytes, 2);
+ assert_eq!(&buf1[..2], &buf2[..]);
+ assert!(files.is_some());
+ let files = files.unwrap();
+ {
+ assert_eq!(files.len(), 3);
+ let mut file = &files[1];
+ let mut content = String::new();
+ file.seek(SeekFrom::Start(0)).unwrap();
+ file.read_to_string(&mut content).unwrap();
+ assert_eq!(content, "test");
+ }
+ let (bytes, buf2, files) = slave.recv_into_buf(0x2).unwrap();
+ assert_eq!(bytes, 2);
+ assert_eq!(&buf1[2..], &buf2[..]);
+ assert!(files.is_none());
+
+ // Following communication pattern should not work:
+ // Sending side: data(header, body) with fds
+ // Receiving side: data(header), data(body) with fds
+ let len = master
+ .send_slice(
+ IoSlice::new(&buf1[..]),
+ Some(&[
+ fd.as_raw_descriptor(),
+ fd.as_raw_descriptor(),
+ fd.as_raw_descriptor(),
+ ]),
+ )
+ .unwrap();
+ assert_eq!(len, 4);
+
+ let buf4 = slave.recv_data(2).unwrap();
+ assert_eq!(buf4.len(), 2);
+ assert_eq!(&buf1[..2], &buf4[..]);
+ let (bytes, buf2, files) = slave.recv_into_buf(0x2).unwrap();
+ assert_eq!(bytes, 2);
+ assert_eq!(&buf1[2..], &buf2[..]);
+ assert!(files.is_none());
+
+ // Following communication pattern should work:
+ // Sending side: data, data with fds
+ // Receiving side: data, data with fds
+ let len = master.send_slice(IoSlice::new(&buf1[..]), None).unwrap();
+ assert_eq!(len, 4);
+ let len = master
+ .send_slice(
+ IoSlice::new(&buf1[..]),
+ Some(&[
+ fd.as_raw_descriptor(),
+ fd.as_raw_descriptor(),
+ fd.as_raw_descriptor(),
+ ]),
+ )
+ .unwrap();
+ assert_eq!(len, 4);
+
+ let (bytes, buf2, files) = slave.recv_into_buf(0x4).unwrap();
+ assert_eq!(bytes, 4);
+ assert_eq!(&buf1[..], &buf2[..]);
+ assert!(files.is_none());
+
+ let (bytes, buf2, files) = slave.recv_into_buf(0x2).unwrap();
+ assert_eq!(bytes, 2);
+ assert_eq!(&buf1[..2], &buf2[..]);
+ assert!(files.is_some());
+ let files = files.unwrap();
+ {
+ assert_eq!(files.len(), 3);
+ let mut file = &files[1];
+ let mut content = String::new();
+ file.seek(SeekFrom::Start(0)).unwrap();
+ file.read_to_string(&mut content).unwrap();
+ assert_eq!(content, "test");
+ }
+ let (bytes, buf2, files) = slave.recv_into_buf(0x2).unwrap();
+ assert_eq!(bytes, 2);
+ assert_eq!(&buf1[2..], &buf2[..]);
+ assert!(files.is_none());
+
+ // Following communication pattern should not work:
+ // Sending side: data1, data2 with fds
+ // Receiving side: data + partial of data2, left of data2 with fds
+ let len = master.send_slice(IoSlice::new(&buf1[..]), None).unwrap();
+ assert_eq!(len, 4);
+ let len = master
+ .send_slice(
+ IoSlice::new(&buf1[..]),
+ Some(&[
+ fd.as_raw_descriptor(),
+ fd.as_raw_descriptor(),
+ fd.as_raw_descriptor(),
+ ]),
+ )
+ .unwrap();
+ assert_eq!(len, 4);
+
+ let v = slave.recv_data(5).unwrap();
+ assert_eq!(v.len(), 5);
+
+ let (bytes, _, files) = slave.recv_into_buf(0x4).unwrap();
+ assert_eq!(bytes, 3);
+ assert!(files.is_none());
+
+ // If the target fd array is too small, extra file descriptors will get lost.
+ let len = master
+ .send_slice(
+ IoSlice::new(&buf1[..]),
+ Some(&[
+ fd.as_raw_descriptor(),
+ fd.as_raw_descriptor(),
+ fd.as_raw_descriptor(),
+ ]),
+ )
+ .unwrap();
+ assert_eq!(len, 4);
+
+ let (bytes, _, files) = slave.recv_into_buf(0x4).unwrap();
+ assert_eq!(bytes, 4);
+ assert!(files.is_some());
+ }
+
+ #[test]
+ fn send_recv() {
+ let dir = temp_dir();
+ let mut path = dir.path().to_owned();
+ path.push("sock");
+ let mut listener = Listener::new(&path, true).unwrap();
+ listener.set_nonblocking(true).unwrap();
+ let mut master = Endpoint::<MasterReq>::connect(&path).unwrap();
+ let sock = listener.accept().unwrap().unwrap();
+ let mut slave = Endpoint::<MasterReq>::from(sock);
+
+ let mut hdr1 =
+ VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0, mem::size_of::<u64>() as u32);
+ hdr1.set_need_reply(true);
+ let features1 = 0x1u64;
+ master.send_message(&hdr1, &features1, None).unwrap();
+
+ let mut features2 = 0u64;
+ let slice = unsafe {
+ slice::from_raw_parts_mut(
+ (&mut features2 as *mut u64) as *mut u8,
+ mem::size_of::<u64>(),
+ )
+ };
+ let (hdr2, bytes, files) = slave.recv_body_into_buf(slice).unwrap();
+ assert_eq!(hdr1, hdr2);
+ assert_eq!(bytes, 8);
+ assert_eq!(features1, features2);
+ assert!(files.is_none());
+
+ master.send_header(&hdr1, None).unwrap();
+ let (hdr2, files) = slave.recv_header().unwrap();
+ assert_eq!(hdr1, hdr2);
+ assert!(files.is_none());
+ }
+}
diff --git a/third_party/vmm_vhost/src/connection/tube.rs b/third_party/vmm_vhost/src/connection/tube.rs
new file mode 100644
index 000000000..1a31c1ff0
--- /dev/null
+++ b/third_party/vmm_vhost/src/connection/tube.rs
@@ -0,0 +1,319 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+//! Structs for Tube based endpoint. Listeners are not used with Tubes, since they are essentially
+//! fancy socket pairs.
+
+use std::io::{IoSlice, IoSliceMut};
+use std::path::Path;
+use std::ptr::copy_nonoverlapping;
+
+use base::{AsRawDescriptor, FromRawDescriptor, RawDescriptor, Tube};
+use serde::{Deserialize, Serialize};
+
+use super::{Error, Result};
+use crate::connection::{Endpoint, Listener, Req};
+use std::cmp::min;
+use std::fs::File;
+use std::marker::PhantomData;
+
+#[derive(Serialize, Deserialize)]
+struct RawDescriptorContainer {
+ #[serde(with = "base::with_raw_descriptor")]
+ rd: RawDescriptor,
+}
+
+#[derive(Serialize, Deserialize)]
+struct EndpointMessage {
+ rds: Vec<RawDescriptorContainer>,
+ data: Vec<u8>,
+}
+
+/// No-op for Tubes. Tubes are a socketpair() equivalent and cannot be listened for or connected.
+pub struct TubeListener;
+
+impl Listener for TubeListener {
+ type Connection = Tube;
+
+ fn accept(&mut self) -> Result<Option<Self::Connection>> {
+ unimplemented!("listeners for Tubes are not used")
+ }
+
+ fn set_nonblocking(&self, _block: bool) -> Result<()> {
+ unimplemented!("listeners for Tubes are not used")
+ }
+}
+
+/// Tube endpoint for vhost-user connection.
+pub struct TubeEndpoint<R: Req> {
+ tube: Tube,
+ _r: PhantomData<R>,
+}
+
+impl<R: Req> From<Tube> for TubeEndpoint<R> {
+ fn from(tube: Tube) -> Self {
+ Self {
+ tube,
+ _r: PhantomData,
+ }
+ }
+}
+
+impl<R: Req> Endpoint<R> for TubeEndpoint<R> {
+ type Listener = TubeListener;
+
+ fn from_connection(tube: <<Self as Endpoint<R>>::Listener as Listener>::Connection) -> Self {
+ Self {
+ tube,
+ _r: PhantomData,
+ }
+ }
+
+ fn connect<P: AsRef<Path>>(_path: P) -> Result<Self> {
+ unimplemented!("connections not supported on Tubes")
+ }
+
+ /// Sends bytes from scatter-gather vectors with optional attached file descriptors.
+ ///
+ /// # Return:
+ /// * - number of bytes sent on success
+ /// * - TubeError: tube related errors.
+ fn send_iovec(&mut self, iovs: &[IoSlice], rds: Option<&[RawDescriptor]>) -> Result<usize> {
+ // Gather the iovecs
+ let total_bytes = iovs.iter().map(|iov| iov.len()).sum();
+ let mut data = Vec::with_capacity(total_bytes);
+ for iov in iovs {
+ data.extend(iov.iter());
+ }
+
+ let mut msg = EndpointMessage {
+ data,
+ rds: Vec::with_capacity(rds.map_or(0, |rds| rds.len())),
+ };
+ if let Some(rds) = rds {
+ for rd in rds {
+ msg.rds.push(RawDescriptorContainer { rd: *rd });
+ }
+ }
+ self.tube.send(&msg).map_err(Error::TubeError)?;
+ Ok(total_bytes)
+ }
+
+ /// Reads bytes from the tube into the given scatter/gather vectors with optional attached
+ /// file.
+ ///
+ /// The underlying communication channel is a Tube. Providing too little recv buffer space will
+ /// cause data to get dropped (with an error). This is tricky to fix with Tube backing our
+ /// transport layer, and as far as we can tell, is not exercised in practice.
+ ///
+ /// # Return:
+ /// * - (number of bytes received, [received files]) on success
+ /// * - RecvBufferTooSmall: Input bufs is too small for the received buffer.
+ /// * - TubeError: tube related errors.
+ fn recv_into_bufs(
+ &mut self,
+ bufs: &mut [IoSliceMut],
+ _allow_rds: bool,
+ ) -> Result<(usize, Option<Vec<File>>)> {
+ // TODO(b/221882601): implement "allow_rds"
+
+ let msg: EndpointMessage = self.tube.recv().map_err(Error::TubeError)?;
+
+ let files = match msg.rds.len() {
+ 0 => None,
+ // Safe because we own r.rd and it is guaranteed valid.
+ _ => Some(
+ msg.rds
+ .iter()
+ .map(|r| unsafe { File::from_raw_descriptor(r.rd) })
+ .collect::<Vec<File>>(),
+ ),
+ };
+
+ let mut bytes_read = 0;
+ for dest_iov in bufs.iter_mut() {
+ if bytes_read >= msg.data.len() {
+ // We've read all the available data into the iovecs.
+ break;
+ }
+
+ let copy_count = min(dest_iov.len(), msg.data.len() - bytes_read);
+
+ // Safe because:
+ // 1) msg.data and dest_iov do not overlap.
+ // 2) copy_count is bounded by dest_iov's length and msg.data.len() so we can't
+ // overrun.
+ unsafe {
+ copy_nonoverlapping(
+ msg.data.as_ptr().add(bytes_read),
+ dest_iov.as_mut_ptr(),
+ copy_count,
+ )
+ };
+ bytes_read += copy_count;
+ }
+
+ if bytes_read != msg.data.len() {
+ // User didn't supply enough iov space.
+ return Err(Error::RecvBufferTooSmall {
+ got: bytes_read,
+ want: msg.data.len(),
+ });
+ }
+
+ Ok((bytes_read, files))
+ }
+}
+
+impl<R: Req> AsRawDescriptor for TubeEndpoint<R> {
+ /// WARNING: this function does not return a waitable descriptor! Use base::ReadNotifier
+ /// instead.
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.tube.as_raw_descriptor()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::connection::EndpointExt;
+ use crate::message::{MasterReq, VhostUserMsgHeader};
+ use base::{AsRawDescriptor, Tube};
+ use std::io::{IoSlice, Read, Seek, SeekFrom, Write};
+ use std::mem;
+ use tempfile::tempfile;
+
+ fn create_pair() -> (TubeEndpoint<MasterReq>, TubeEndpoint<MasterReq>) {
+ let (master_tube, slave_tube) = Tube::pair().unwrap();
+ (
+ TubeEndpoint::<MasterReq>::from_connection(master_tube),
+ TubeEndpoint::<MasterReq>::from_connection(slave_tube),
+ )
+ }
+
+ #[test]
+ fn send_data() {
+ let (mut master, mut slave) = create_pair();
+
+ let buf1 = vec![0x1, 0x2, 0x3, 0x4];
+ let len = master.send_slice(IoSlice::new(&buf1[..]), None).unwrap();
+ assert_eq!(len, 4);
+ let (bytes, buf2, _) = slave.recv_into_buf(0x1000).unwrap();
+ assert_eq!(bytes, 4);
+ assert_eq!(&buf1[..], &buf2[..bytes]);
+ }
+
+ #[test]
+ fn send_fd() {
+ let (mut master, mut slave) = create_pair();
+
+ let mut file = tempfile().unwrap();
+ write!(file, "test").unwrap();
+
+ // Normal case for sending/receiving file descriptors
+ let buf1 = vec![0x1, 0x2, 0x3, 0x4];
+ let len = master
+ .send_slice(IoSlice::new(&buf1[..]), Some(&[file.as_raw_descriptor()]))
+ .unwrap();
+ assert_eq!(len, 4);
+
+ let (bytes, buf2, files) = slave.recv_into_buf(4).unwrap();
+ assert_eq!(bytes, 4);
+ assert_eq!(&buf1[..], &buf2[..]);
+ assert!(files.is_some());
+ let files = files.unwrap();
+ {
+ assert_eq!(files.len(), 1);
+ let mut file = &files[0];
+ let mut content = String::new();
+ file.seek(SeekFrom::Start(0)).unwrap();
+ file.read_to_string(&mut content).unwrap();
+ assert_eq!(content, "test");
+ }
+
+ // Following communication pattern should work:
+ // Sending side: data, data with fds
+ // Receiving side: data, data with fds
+ let len = master.send_slice(IoSlice::new(&buf1[..]), None).unwrap();
+ assert_eq!(len, 4);
+ let len = master
+ .send_slice(
+ IoSlice::new(&buf1[..]),
+ Some(&[
+ file.as_raw_descriptor(),
+ file.as_raw_descriptor(),
+ file.as_raw_descriptor(),
+ ]),
+ )
+ .unwrap();
+ assert_eq!(len, 4);
+
+ let (bytes, buf2, files) = slave.recv_into_buf(0x4).unwrap();
+ assert_eq!(bytes, 4);
+ assert_eq!(&buf1[..], &buf2[..]);
+ assert!(files.is_none());
+
+ let (bytes, buf2, files) = slave.recv_into_buf(0x4).unwrap();
+ assert_eq!(bytes, 4);
+ assert_eq!(&buf1[..], &buf2[..]);
+ assert!(files.is_some());
+ let files = files.unwrap();
+ {
+ assert_eq!(files.len(), 3);
+ let mut file = &files[1];
+ let mut content = String::new();
+ file.seek(SeekFrom::Start(0)).unwrap();
+ file.read_to_string(&mut content).unwrap();
+ assert_eq!(content, "test");
+ }
+
+ // If the target fd array is too small, extra file descriptors will get lost.
+ //
+ // Porting note: no, they won't. The FD array is sized to whatever the header says it
+ // should be.
+ let len = master
+ .send_slice(
+ IoSlice::new(&buf1[..]),
+ Some(&[
+ file.as_raw_descriptor(),
+ file.as_raw_descriptor(),
+ file.as_raw_descriptor(),
+ ]),
+ )
+ .unwrap();
+ assert_eq!(len, 4);
+
+ let (bytes, _, files) = slave.recv_into_buf(0x4).unwrap();
+ assert_eq!(bytes, 4);
+ assert!(files.is_some());
+ }
+
+ #[test]
+ fn send_recv() {
+ let (mut master, mut slave) = create_pair();
+
+ let mut hdr1 =
+ VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0, mem::size_of::<u64>() as u32);
+ hdr1.set_need_reply(true);
+ let features1 = 0x1u64;
+ master.send_message(&hdr1, &features1, None).unwrap();
+
+ let mut features2 = 0u64;
+ let slice = unsafe {
+ std::slice::from_raw_parts_mut(
+ (&mut features2 as *mut u64) as *mut u8,
+ mem::size_of::<u64>(),
+ )
+ };
+ let (hdr2, bytes, files) = slave.recv_body_into_buf(slice).unwrap();
+ assert_eq!(hdr1, hdr2);
+ assert_eq!(bytes, 8);
+ assert_eq!(features1, features2);
+ assert!(files.is_none());
+
+ master.send_header(&hdr1, None).unwrap();
+ let (hdr2, files) = slave.recv_header().unwrap();
+ assert_eq!(hdr1, hdr2);
+ assert!(files.is_none());
+ }
+}
diff --git a/third_party/vmm_vhost/src/connection/unix.rs b/third_party/vmm_vhost/src/connection/unix.rs
new file mode 100644
index 000000000..e0d72721e
--- /dev/null
+++ b/third_party/vmm_vhost/src/connection/unix.rs
@@ -0,0 +1,74 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+//! Unix specific code that keeps rest of the code in the crate platform independent.
+
+#[cfg(all(test, feature = "vmm"))]
+pub(crate) mod tests {
+ use crate::connection::{
+ socket::Endpoint as SocketEndpoint, socket::Listener as SocketListener, Listener,
+ };
+ use crate::master::Master;
+ use crate::message::MasterReq;
+ use tempfile::{Builder, TempDir};
+ #[cfg(feature = "device")]
+ use {
+ crate::{
+ slave::SlaveListener,
+ slave_req_handler::{SlaveReqHandler, VhostUserSlaveReqHandler},
+ },
+ std::sync::Arc,
+ };
+
+ pub(crate) type TestMaster = Master<SocketEndpoint<MasterReq>>;
+ pub(crate) type TestEndpoint = SocketEndpoint<MasterReq>;
+ pub(crate) fn temp_dir() -> TempDir {
+ Builder::new().prefix("/tmp/vhost_test").tempdir().unwrap()
+ }
+
+ pub(crate) fn create_pair() -> (Master<SocketEndpoint<MasterReq>>, SocketEndpoint<MasterReq>) {
+ let dir = temp_dir();
+ let mut path = dir.path().to_owned();
+ path.push("sock");
+ let mut listener = SocketListener::new(&path, true).unwrap();
+ listener.set_nonblocking(true).unwrap();
+ let master = Master::connect(path, 2).unwrap();
+ let slave = listener.accept().unwrap().unwrap();
+ (master, SocketEndpoint::from(slave))
+ }
+
+ #[cfg(feature = "device")]
+ pub(crate) fn create_master_slave_pair<S>(
+ backend: Arc<S>,
+ ) -> (TestMaster, SlaveReqHandler<S, TestEndpoint>)
+ where
+ S: VhostUserSlaveReqHandler,
+ {
+ let dir = Builder::new().prefix("/tmp/vhost_test").tempdir().unwrap();
+ let mut path = dir.path().to_owned();
+ path.push("sock");
+ let listener = SocketListener::new(&path, true).unwrap();
+ let mut slave_listener = SlaveListener::new(listener, backend).unwrap();
+ let master = Master::connect(&path, 1).unwrap();
+ (master, slave_listener.accept().unwrap().unwrap())
+ }
+
+ // Create failures don't happen on using Tubes because there is no "connection". (The channel is
+ // already up when we invoke this library.)
+ #[test]
+ fn test_create_failure() {
+ let dir = temp_dir();
+ let mut path = dir.path().to_owned();
+ path.push("sock");
+ let _ = SocketListener::new(&path, true).unwrap();
+ let _ = SocketListener::new(&path, false).is_err();
+ assert!(Master::<SocketEndpoint<_>>::connect(&path, 1).is_err());
+
+ let mut listener = SocketListener::new(&path, true).unwrap();
+ assert!(SocketListener::new(&path, false).is_err());
+ listener.set_nonblocking(true).unwrap();
+
+ let _master = Master::<SocketEndpoint<_>>::connect(&path, 1).unwrap();
+ let _slave = listener.accept().unwrap().unwrap();
+ }
+}
diff --git a/third_party/vmm_vhost/src/connection/vfio.rs b/third_party/vmm_vhost/src/connection/vfio.rs
new file mode 100644
index 000000000..48eaea1bc
--- /dev/null
+++ b/third_party/vmm_vhost/src/connection/vfio.rs
@@ -0,0 +1,142 @@
+// Copyright 2021 The Chromium OS Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+//! Structs for VFIO listener and endpoint.
+
+use std::convert::From;
+use std::fs::File;
+use std::io::{IoSlice, IoSliceMut};
+use std::marker::PhantomData;
+use std::os::unix::io::RawFd;
+use std::path::Path;
+
+use base::{AsRawDescriptor, Event, RawDescriptor};
+use remain::sorted;
+use thiserror::Error as ThisError;
+
+use super::{Error, Result};
+use crate::connection::{Endpoint as EndpointTrait, Listener as ListenerTrait, Req};
+
+/// Errors for `Device::recv_into_bufs()`.
+#[sorted]
+#[derive(Debug, ThisError)]
+pub enum RecvIntoBufsError {
+ /// Connection is closed.
+ #[error("connection is closed")]
+ Disconnect,
+ /// Fatal error while receiving data.
+ #[error("failed to receive data via VFIO: {0:#}")]
+ Fatal(anyhow::Error),
+}
+
+impl From<RecvIntoBufsError> for Error {
+ fn from(e: RecvIntoBufsError) -> Self {
+ match e {
+ RecvIntoBufsError::Disconnect => Error::Disconnect,
+ RecvIntoBufsError::Fatal(e) => Error::VfioDeviceError(e),
+ }
+ }
+}
+
+/// VFIO device which can be used as virtio-vhost-user device backend.
+pub trait Device {
+ /// This event must be read before handle_request() is called.
+ fn event(&self) -> &Event;
+
+ /// Starts VFIO device.
+ fn start(&mut self) -> std::result::Result<(), anyhow::Error>;
+
+ /// Sends data in the given slice of slices.
+ fn send_bufs(
+ &mut self,
+ iovs: &[IoSlice],
+ fds: Option<&[RawFd]>,
+ ) -> std::result::Result<usize, anyhow::Error>;
+
+ /// Receives data into the given slice of slices and returns the size of the received data.
+ fn recv_into_bufs(
+ &mut self,
+ iovs: &mut [IoSliceMut],
+ ) -> std::result::Result<usize, RecvIntoBufsError>;
+}
+
+/// Listener for accepting incoming connections from virtio-vhost-user device through VFIO.
+pub struct Listener<D: Device> {
+ // device will be dropped when Listener::accept() is called.
+ device: Option<D>,
+}
+
+impl<D: Device> Listener<D> {
+ /// Creates a VFIO listener.
+ pub fn new(device: D) -> Result<Self> {
+ Ok(Self {
+ device: Some(device),
+ })
+ }
+}
+
+impl<D: Device> ListenerTrait for Listener<D> {
+ type Connection = D;
+
+ fn accept(&mut self) -> Result<Option<Self::Connection>> {
+ let mut device = self
+ .device
+ .take()
+ .expect("Listener isn't initialized correctly");
+ device.start().map_err(Error::VfioDeviceError)?;
+ Ok(Some(device))
+ }
+
+ fn set_nonblocking(&self, _block: bool) -> Result<()> {
+ unimplemented!("set_nonblocking");
+ }
+}
+
+/// Endpoint for vhost-user connection through VFIO.
+pub struct Endpoint<R: Req, D: Device> {
+ device: D,
+ _r: PhantomData<R>,
+}
+
+impl<R: Req, D: Device> EndpointTrait<R> for Endpoint<R, D> {
+ type Listener = Listener<D>;
+
+ /// Create an endpoint from a stream object.
+ fn from_connection(device: D) -> Self {
+ Self {
+ device,
+ _r: PhantomData,
+ }
+ }
+
+ fn connect<P: AsRef<Path>>(_path: P) -> Result<Self> {
+ // TODO: remove this method from Endpoint trait?
+ panic!("VfioEndpoint cannot create a connection from path");
+ }
+
+ fn send_iovec(&mut self, iovs: &[IoSlice], fds: Option<&[RawFd]>) -> Result<usize> {
+ self.device
+ .send_bufs(iovs, fds)
+ .map_err(Error::VfioDeviceError)
+ }
+
+ fn recv_into_bufs(
+ &mut self,
+ bufs: &mut [IoSliceMut],
+ _allow_fd: bool, /* ignore, as VFIO doesn't receive FDs */
+ ) -> Result<(usize, Option<Vec<File>>)> {
+ let size = self
+ .device
+ .recv_into_bufs(bufs)
+ .map_err::<Error, _>(From::<RecvIntoBufsError>::from)?;
+
+ // VFIO backend doesn't receive any files.
+ Ok((size, None))
+ }
+}
+
+impl<R: Req, D: Device> AsRawDescriptor for Endpoint<R, D> {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.device.event().as_raw_descriptor()
+ }
+}
diff --git a/third_party/vmm_vhost/src/connection/windows.rs b/third_party/vmm_vhost/src/connection/windows.rs
new file mode 100644
index 000000000..8357d590e
--- /dev/null
+++ b/third_party/vmm_vhost/src/connection/windows.rs
@@ -0,0 +1,42 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+//! Windows specific code that keeps rest of the code in the crate platform independent.
+
+#[cfg(all(test, feature = "vmm"))]
+pub(crate) mod tests {
+ use crate::connection::Endpoint;
+ use crate::connection::TubeEndpoint;
+ use crate::master::Master;
+ use crate::message::MasterReq;
+ #[cfg(feature = "device")]
+ use {
+ crate::slave_req_handler::{SlaveReqHandler, VhostUserSlaveReqHandler},
+ std::sync::Arc,
+ };
+
+ use crate::SystemStream;
+ pub(crate) type TestEndpoint = TubeEndpoint<MasterReq>;
+ pub(crate) type TestMaster = Master<TestEndpoint>;
+
+ pub(crate) fn create_pair() -> (TestMaster, TestEndpoint) {
+ let (master_tube, slave_tube) = SystemStream::pair().unwrap();
+ let master = Master::from_stream(master_tube, 2);
+ (master, TubeEndpoint::from_connection(slave_tube))
+ }
+
+ #[cfg(feature = "device")]
+ pub(crate) fn create_master_slave_pair<S>(
+ backend: Arc<S>,
+ ) -> (TestMaster, SlaveReqHandler<S, TestEndpoint>)
+ where
+ S: VhostUserSlaveReqHandler,
+ {
+ let (master_tube, slave_tube) = SystemStream::pair().unwrap();
+ let master = Master::from_stream(master_tube, 2);
+ (
+ master,
+ SlaveReqHandler::<S, TubeEndpoint<MasterReq>>::from_stream(slave_tube, backend),
+ )
+ }
+}
diff --git a/third_party/vmm_vhost/src/dummy_slave.rs b/third_party/vmm_vhost/src/dummy_slave.rs
new file mode 100644
index 000000000..658a1e704
--- /dev/null
+++ b/third_party/vmm_vhost/src/dummy_slave.rs
@@ -0,0 +1,273 @@
+// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+use std::fs::File;
+
+use super::message::*;
+use super::*;
+
+pub const MAX_QUEUE_NUM: usize = 2;
+pub const MAX_VRING_NUM: usize = 256;
+pub const MAX_MEM_SLOTS: usize = 32;
+pub const VIRTIO_FEATURES: u64 = 0x40000003;
+
+#[derive(Default)]
+pub struct DummySlaveReqHandler {
+ pub owned: bool,
+ pub features_acked: bool,
+ pub acked_features: u64,
+ pub acked_protocol_features: u64,
+ pub queue_num: usize,
+ pub vring_num: [u32; MAX_QUEUE_NUM],
+ pub vring_base: [u32; MAX_QUEUE_NUM],
+ pub call_fd: [Option<File>; MAX_QUEUE_NUM],
+ pub kick_fd: [Option<File>; MAX_QUEUE_NUM],
+ pub err_fd: [Option<File>; MAX_QUEUE_NUM],
+ pub vring_started: [bool; MAX_QUEUE_NUM],
+ pub vring_enabled: [bool; MAX_QUEUE_NUM],
+ pub inflight_file: Option<File>,
+}
+
+impl DummySlaveReqHandler {
+ pub fn new() -> Self {
+ DummySlaveReqHandler {
+ queue_num: MAX_QUEUE_NUM,
+ ..Default::default()
+ }
+ }
+}
+
+impl VhostUserSlaveReqHandlerMut for DummySlaveReqHandler {
+ fn protocol(&self) -> Protocol {
+ Protocol::Regular
+ }
+
+ fn set_owner(&mut self) -> Result<()> {
+ if self.owned {
+ return Err(Error::InvalidOperation);
+ }
+ self.owned = true;
+ Ok(())
+ }
+
+ fn reset_owner(&mut self) -> Result<()> {
+ self.owned = false;
+ self.features_acked = false;
+ self.acked_features = 0;
+ self.acked_protocol_features = 0;
+ Ok(())
+ }
+
+ fn get_features(&mut self) -> Result<u64> {
+ Ok(VIRTIO_FEATURES)
+ }
+
+ fn set_features(&mut self, features: u64) -> Result<()> {
+ if !self.owned || self.features_acked {
+ return Err(Error::InvalidOperation);
+ } else if (features & !VIRTIO_FEATURES) != 0 {
+ return Err(Error::InvalidParam);
+ }
+
+ self.acked_features = features;
+ self.features_acked = true;
+
+ // If VHOST_USER_F_PROTOCOL_FEATURES has not been negotiated,
+ // the ring is initialized in an enabled state.
+ // If VHOST_USER_F_PROTOCOL_FEATURES has been negotiated,
+ // the ring is initialized in a disabled state. Client must not
+ // pass data to/from the backend until ring is enabled by
+ // VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has
+ // been disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0.
+ let vring_enabled =
+ self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0;
+ for enabled in &mut self.vring_enabled {
+ *enabled = vring_enabled;
+ }
+
+ Ok(())
+ }
+
+ fn set_mem_table(&mut self, _ctx: &[VhostUserMemoryRegion], _files: Vec<File>) -> Result<()> {
+ Ok(())
+ }
+
+ fn set_vring_num(&mut self, index: u32, num: u32) -> Result<()> {
+ if index as usize >= self.queue_num || num == 0 || num as usize > MAX_VRING_NUM {
+ return Err(Error::InvalidParam);
+ }
+ self.vring_num[index as usize] = num;
+ Ok(())
+ }
+
+ fn set_vring_addr(
+ &mut self,
+ index: u32,
+ _flags: VhostUserVringAddrFlags,
+ _descriptor: u64,
+ _used: u64,
+ _available: u64,
+ _log: u64,
+ ) -> Result<()> {
+ if index as usize >= self.queue_num {
+ return Err(Error::InvalidParam);
+ }
+ Ok(())
+ }
+
+ fn set_vring_base(&mut self, index: u32, base: u32) -> Result<()> {
+ if index as usize >= self.queue_num || base as usize >= MAX_VRING_NUM {
+ return Err(Error::InvalidParam);
+ }
+ self.vring_base[index as usize] = base;
+ Ok(())
+ }
+
+ fn get_vring_base(&mut self, index: u32) -> Result<VhostUserVringState> {
+ if index as usize >= self.queue_num {
+ return Err(Error::InvalidParam);
+ }
+ // Quotation from vhost-user spec:
+ // Client must start ring upon receiving a kick (that is, detecting
+ // that file descriptor is readable) on the descriptor specified by
+ // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
+ // VHOST_USER_GET_VRING_BASE.
+ self.vring_started[index as usize] = false;
+ Ok(VhostUserVringState::new(
+ index,
+ self.vring_base[index as usize],
+ ))
+ }
+
+ fn set_vring_kick(&mut self, index: u8, fd: Option<File>) -> Result<()> {
+ if index as usize >= self.queue_num || index as usize > self.queue_num {
+ return Err(Error::InvalidParam);
+ }
+ self.kick_fd[index as usize] = fd;
+
+ // Quotation from vhost-user spec:
+ // Client must start ring upon receiving a kick (that is, detecting
+ // that file descriptor is readable) on the descriptor specified by
+ // VHOST_USER_SET_VRING_KICK, and stop ring upon receiving
+ // VHOST_USER_GET_VRING_BASE.
+ //
+ // So we should add fd to event monitor(select, poll, epoll) here.
+ self.vring_started[index as usize] = true;
+ Ok(())
+ }
+
+ fn set_vring_call(&mut self, index: u8, fd: Option<File>) -> Result<()> {
+ if index as usize >= self.queue_num || index as usize > self.queue_num {
+ return Err(Error::InvalidParam);
+ }
+ self.call_fd[index as usize] = fd;
+ Ok(())
+ }
+
+ fn set_vring_err(&mut self, index: u8, fd: Option<File>) -> Result<()> {
+ if index as usize >= self.queue_num || index as usize > self.queue_num {
+ return Err(Error::InvalidParam);
+ }
+ self.err_fd[index as usize] = fd;
+ Ok(())
+ }
+
+ fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures> {
+ Ok(VhostUserProtocolFeatures::all())
+ }
+
+ fn set_protocol_features(&mut self, features: u64) -> Result<()> {
+ // Note: slave that reported VHOST_USER_F_PROTOCOL_FEATURES must
+ // support this message even before VHOST_USER_SET_FEATURES was
+ // called.
+ // What happens if the master calls set_features() with
+ // VHOST_USER_F_PROTOCOL_FEATURES cleared after calling this
+ // interface?
+ self.acked_protocol_features = features;
+ Ok(())
+ }
+
+ fn get_queue_num(&mut self) -> Result<u64> {
+ Ok(MAX_QUEUE_NUM as u64)
+ }
+
+ fn set_vring_enable(&mut self, index: u32, enable: bool) -> Result<()> {
+ // This request should be handled only when VHOST_USER_F_PROTOCOL_FEATURES
+ // has been negotiated.
+ if self.acked_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 {
+ return Err(Error::InvalidOperation);
+ } else if index as usize >= self.queue_num || index as usize > self.queue_num {
+ return Err(Error::InvalidParam);
+ }
+
+ // Slave must not pass data to/from the backend until ring is
+ // enabled by VHOST_USER_SET_VRING_ENABLE with parameter 1,
+ // or after it has been disabled by VHOST_USER_SET_VRING_ENABLE
+ // with parameter 0.
+ self.vring_enabled[index as usize] = enable;
+ Ok(())
+ }
+
+ fn get_config(
+ &mut self,
+ offset: u32,
+ size: u32,
+ _flags: VhostUserConfigFlags,
+ ) -> Result<Vec<u8>> {
+ if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
+ return Err(Error::InvalidOperation);
+ } else if !(VHOST_USER_CONFIG_OFFSET..VHOST_USER_CONFIG_SIZE).contains(&offset)
+ || size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
+ || size + offset > VHOST_USER_CONFIG_SIZE
+ {
+ return Err(Error::InvalidParam);
+ }
+ Ok(vec![0xa5; size as usize])
+ }
+
+ fn set_config(&mut self, offset: u32, buf: &[u8], _flags: VhostUserConfigFlags) -> Result<()> {
+ let size = buf.len() as u32;
+ if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
+ return Err(Error::InvalidOperation);
+ } else if !(VHOST_USER_CONFIG_OFFSET..VHOST_USER_CONFIG_SIZE).contains(&offset)
+ || size > VHOST_USER_CONFIG_SIZE - VHOST_USER_CONFIG_OFFSET
+ || size + offset > VHOST_USER_CONFIG_SIZE
+ {
+ return Err(Error::InvalidParam);
+ }
+ Ok(())
+ }
+
+ fn get_inflight_fd(
+ &mut self,
+ inflight: &VhostUserInflight,
+ ) -> Result<(VhostUserInflight, File)> {
+ let file = tempfile::tempfile().unwrap();
+ self.inflight_file = Some(file.try_clone().unwrap());
+ Ok((
+ VhostUserInflight {
+ mmap_size: 0x1000,
+ mmap_offset: 0,
+ num_queues: inflight.num_queues,
+ queue_size: inflight.queue_size,
+ },
+ file,
+ ))
+ }
+
+ fn set_inflight_fd(&mut self, _inflight: &VhostUserInflight, _file: File) -> Result<()> {
+ Ok(())
+ }
+
+ fn get_max_mem_slots(&mut self) -> Result<u64> {
+ Ok(MAX_MEM_SLOTS as u64)
+ }
+
+ fn add_mem_region(&mut self, _region: &VhostUserSingleMemoryRegion, _fd: File) -> Result<()> {
+ Ok(())
+ }
+
+ fn remove_mem_region(&mut self, _region: &VhostUserSingleMemoryRegion) -> Result<()> {
+ Ok(())
+ }
+}
diff --git a/third_party/vmm_vhost/src/lib.rs b/third_party/vmm_vhost/src/lib.rs
new file mode 100644
index 000000000..8f78e982a
--- /dev/null
+++ b/third_party/vmm_vhost/src/lib.rs
@@ -0,0 +1,480 @@
+// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
+
+//! Virtio Vhost Backend Drivers
+//!
+//! Virtio devices use virtqueues to transport data efficiently. The first generation of virtqueue
+//! is a set of three different single-producer, single-consumer ring structures designed to store
+//! generic scatter-gather I/O. The virtio specification 1.1 introduces an alternative compact
+//! virtqueue layout named "Packed Virtqueue", which is more friendly to memory cache system and
+//! hardware implemented virtio devices. The packed virtqueue uses read-write memory, that means
+//! the memory will be both read and written by both host and guest. The new Packed Virtqueue is
+//! preferred for performance.
+//!
+//! Vhost is a mechanism to improve performance of Virtio devices by delegate data plane operations
+//! to dedicated IO service processes. Only the configuration, I/O submission notification, and I/O
+//! completion interruption are piped through the hypervisor.
+//! It uses the same virtqueue layout as Virtio to allow Vhost devices to be mapped directly to
+//! Virtio devices. This allows a Vhost device to be accessed directly by a guest OS inside a
+//! hypervisor process with an existing Virtio (PCI) driver.
+//!
+//! The initial vhost implementation is a part of the Linux kernel and uses ioctl interface to
+//! communicate with userspace applications. Dedicated kernel worker threads are created to handle
+//! IO requests from the guest.
+//!
+//! Later Vhost-user protocol is introduced to complement the ioctl interface used to control the
+//! vhost implementation in the Linux kernel. It implements the control plane needed to establish
+//! virtqueues sharing with a user space process on the same host. It uses communication over a
+//! Unix domain socket to share file descriptors in the ancillary data of the message.
+//! The protocol defines 2 sides of the communication, master and slave. Master is the application
+//! that shares its virtqueues. Slave is the consumer of the virtqueues. Master and slave can be
+//! either a client (i.e. connecting) or server (listening) in the socket communication.
+
+#![deny(missing_docs)]
+
+#[cfg(any(feature = "vmm", feature = "device"))]
+use std::fs::File;
+use std::io::Error as IOError;
+
+use remain::sorted;
+use thiserror::Error as ThisError;
+
+mod backend;
+pub use backend::*;
+
+pub mod message;
+
+pub mod connection;
+
+mod sys;
+pub use sys::{SystemStream, *};
+
+cfg_if::cfg_if! {
+ if #[cfg(feature = "vmm")] {
+ pub(crate) mod master;
+ pub use self::master::{Master, VhostUserMaster};
+ mod master_req_handler;
+ pub use self::master_req_handler::{VhostUserMasterReqHandler,
+ VhostUserMasterReqHandlerMut};
+ }
+}
+cfg_if::cfg_if! {
+ if #[cfg(feature = "device")] {
+ mod slave_req_handler;
+ mod slave_fs_cache;
+ pub use self::slave_req_handler::{
+ Protocol, SlaveReqHandler, SlaveReqHelper, VhostUserSlaveReqHandler,
+ VhostUserSlaveReqHandlerMut,
+ };
+ pub use self::slave_fs_cache::SlaveFsCacheReq;
+ }
+}
+cfg_if::cfg_if! {
+ if #[cfg(all(feature = "device", unix))] {
+ mod slave;
+ pub use self::slave::SlaveListener;
+ }
+}
+cfg_if::cfg_if! {
+ if #[cfg(all(feature = "vmm", unix))] {
+ pub use self::master_req_handler::MasterReqHandler;
+ }
+}
+
+/// Errors for vhost-user operations
+#[sorted]
+#[derive(Debug, ThisError)]
+pub enum Error {
+ /// client exited properly.
+ #[error("client exited properly")]
+ ClientExit,
+ /// client disconnected.
+ /// If connection is closed properly, use `ClientExit` instead.
+ #[error("client closed the connection")]
+ Disconnect,
+ /// Virtio/protocol features mismatch.
+ #[error("virtio features mismatch")]
+ FeatureMismatch,
+ /// Fd array in question is too big or too small
+ #[error("wrong number of attached fds")]
+ IncorrectFds,
+ /// Invalid message format, flag or content.
+ #[error("invalid message")]
+ InvalidMessage,
+ /// Unsupported operations due to that the protocol feature hasn't been negotiated.
+ #[error("invalid operation")]
+ InvalidOperation,
+ /// Invalid parameters.
+ #[error("invalid parameters")]
+ InvalidParam,
+ /// Failure from the master side.
+ #[error("master Internal error")]
+ MasterInternalError,
+ /// Message is too large
+ #[error("oversized message")]
+ OversizedMsg,
+ /// Only part of a message have been sent or received successfully
+ #[error("partial message")]
+ PartialMessage,
+ /// Provided recv buffer was too small, and data was dropped.
+ #[error("buffer for recv was too small, data was dropped: got size {got}, needed {want}")]
+ RecvBufferTooSmall {
+ /// The size of the buffer received.
+ got: usize,
+ /// The expected size of the buffer.
+ want: usize,
+ },
+ /// Error from request handler
+ #[error("handler failed to handle request: {0}")]
+ ReqHandlerError(IOError),
+ /// Failure from the slave side.
+ #[error("slave internal error")]
+ SlaveInternalError,
+ /// The socket is broken or has been closed.
+ #[error("socket is broken: {0}")]
+ SocketBroken(std::io::Error),
+ /// Can't connect to peer.
+ #[error("can't connect to peer: {0}")]
+ SocketConnect(std::io::Error),
+ /// Generic socket errors.
+ #[error("socket error: {0}")]
+ SocketError(std::io::Error),
+ /// Should retry the socket operation again.
+ #[error("temporary socket error: {0}")]
+ SocketRetry(std::io::Error),
+ /// Error from tx/rx on a Tube.
+ #[error("failed to read/write on Tube: {0}")]
+ TubeError(base::TubeError),
+ /// Error from VFIO device.
+ #[error("error occurred in VFIO device: {0}")]
+ VfioDeviceError(anyhow::Error),
+}
+
+impl std::convert::From<base::Error> for Error {
+ /// Convert raw socket errors into meaningful vhost-user errors.
+ ///
+ /// The base::Error is a simple wrapper over the raw errno, which doesn't means
+ /// much to the vhost-user connection manager. So convert it into meaningful errors to simplify
+ /// the connection manager logic.
+ ///
+ /// # Return:
+ /// * - Error::SocketRetry: temporary error caused by signals or short of resources.
+ /// * - Error::SocketBroken: the underline socket is broken.
+ /// * - Error::SocketError: other socket related errors.
+ #[allow(unreachable_patterns)] // EWOULDBLOCK equals to EGAIN on linux
+ fn from(err: base::Error) -> Self {
+ match err.errno() {
+ // Retry:
+ // * EAGAIN, EWOULDBLOCK: The socket is marked nonblocking and the requested operation
+ // would block.
+ // * EINTR: A signal occurred before any data was transmitted
+ // * ENOBUFS: The output queue for a network interface was full. This generally
+ // indicates that the interface has stopped sending, but may be caused by transient
+ // congestion.
+ // * ENOMEM: No memory available.
+ libc::EAGAIN | libc::EWOULDBLOCK | libc::EINTR | libc::ENOBUFS | libc::ENOMEM => {
+ Error::SocketRetry(err.into())
+ }
+ // Broken:
+ // * ECONNRESET: Connection reset by peer.
+ // * EPIPE: The local end has been shut down on a connection oriented socket. In this
+ // case the process will also receive a SIGPIPE unless MSG_NOSIGNAL is set.
+ libc::ECONNRESET | libc::EPIPE => Error::SocketBroken(err.into()),
+ // Write permission is denied on the destination socket file, or search permission is
+ // denied for one of the directories the path prefix.
+ libc::EACCES => Error::SocketConnect(IOError::from_raw_os_error(libc::EACCES)),
+ // Catch all other errors
+ e => Error::SocketError(IOError::from_raw_os_error(e)),
+ }
+ }
+}
+
+/// Result of vhost-user operations
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// Result of request handler.
+pub type HandlerResult<T> = std::result::Result<T, IOError>;
+
+/// Utility function to take the first element from option of a vector of files.
+/// Returns `None` if the vector contains no file or more than one file.
+#[cfg(any(feature = "vmm", feature = "device"))]
+pub(crate) fn take_single_file(files: Option<Vec<File>>) -> Option<File> {
+ let mut files = files?;
+ if files.len() != 1 {
+ return None;
+ }
+ Some(files.swap_remove(0))
+}
+
+#[cfg(all(test, feature = "device"))]
+mod dummy_slave;
+
+#[cfg(all(test, feature = "vmm", feature = "device"))]
+mod tests {
+ use base::AsRawDescriptor;
+ use std::sync::{Arc, Barrier, Mutex};
+ use std::thread;
+
+ use super::connection::tests::*;
+ use super::dummy_slave::{DummySlaveReqHandler, VIRTIO_FEATURES};
+ use super::message::*;
+ use super::*;
+ use crate::backend::VhostBackend;
+ use crate::{VhostUserMemoryRegionInfo, VringConfigData};
+ use tempfile::tempfile;
+
+ #[test]
+ fn create_dummy_slave() {
+ let slave = Arc::new(Mutex::new(DummySlaveReqHandler::new()));
+
+ slave.set_owner().unwrap();
+ assert!(slave.set_owner().is_err());
+ }
+
+ #[test]
+ fn test_set_owner() {
+ let slave_be = Arc::new(Mutex::new(DummySlaveReqHandler::new()));
+ let (master, mut slave) = create_master_slave_pair(slave_be.clone());
+
+ assert!(!slave_be.lock().unwrap().owned);
+ master.set_owner().unwrap();
+ slave.handle_request().unwrap();
+ assert!(slave_be.lock().unwrap().owned);
+ master.set_owner().unwrap();
+ assert!(slave.handle_request().is_err());
+ assert!(slave_be.lock().unwrap().owned);
+ }
+
+ #[test]
+ fn test_set_features() {
+ let mbar = Arc::new(Barrier::new(2));
+ let sbar = mbar.clone();
+ let slave_be = Arc::new(Mutex::new(DummySlaveReqHandler::new()));
+ let (mut master, mut slave) = create_master_slave_pair(slave_be.clone());
+
+ thread::spawn(move || {
+ slave.handle_request().unwrap();
+ assert!(slave_be.lock().unwrap().owned);
+
+ slave.handle_request().unwrap();
+ slave.handle_request().unwrap();
+ assert_eq!(
+ slave_be.lock().unwrap().acked_features,
+ VIRTIO_FEATURES & !0x1
+ );
+
+ slave.handle_request().unwrap();
+ slave.handle_request().unwrap();
+ assert_eq!(
+ slave_be.lock().unwrap().acked_protocol_features,
+ VhostUserProtocolFeatures::all().bits()
+ );
+
+ sbar.wait();
+ });
+
+ master.set_owner().unwrap();
+
+ // set virtio features
+ let features = master.get_features().unwrap();
+ assert_eq!(features, VIRTIO_FEATURES);
+ master.set_features(VIRTIO_FEATURES & !0x1).unwrap();
+
+ // set vhost protocol features
+ let features = master.get_protocol_features().unwrap();
+ assert_eq!(features.bits(), VhostUserProtocolFeatures::all().bits());
+ master.set_protocol_features(features).unwrap();
+
+ mbar.wait();
+ }
+
+ #[test]
+ fn test_master_slave_process() {
+ let mbar = Arc::new(Barrier::new(2));
+ let sbar = mbar.clone();
+ let slave_be = Arc::new(Mutex::new(DummySlaveReqHandler::new()));
+ let (mut master, mut slave) = create_master_slave_pair(slave_be.clone());
+
+ thread::spawn(move || {
+ // set_own()
+ slave.handle_request().unwrap();
+ assert!(slave_be.lock().unwrap().owned);
+
+ // get/set_features()
+ slave.handle_request().unwrap();
+ slave.handle_request().unwrap();
+ assert_eq!(
+ slave_be.lock().unwrap().acked_features,
+ VIRTIO_FEATURES & !0x1
+ );
+
+ slave.handle_request().unwrap();
+ slave.handle_request().unwrap();
+ assert_eq!(
+ slave_be.lock().unwrap().acked_protocol_features,
+ VhostUserProtocolFeatures::all().bits()
+ );
+
+ // get_inflight_fd()
+ slave.handle_request().unwrap();
+ // set_inflight_fd()
+ slave.handle_request().unwrap();
+
+ // get_queue_num()
+ slave.handle_request().unwrap();
+
+ // set_mem_table()
+ slave.handle_request().unwrap();
+
+ // get/set_config()
+ slave.handle_request().unwrap();
+ slave.handle_request().unwrap();
+
+ // set_slave_request_rd isn't implemented on Windows.
+ #[cfg(unix)]
+ {
+ // set_slave_request_fd
+ slave.handle_request().unwrap();
+ }
+
+ // set_vring_enable
+ slave.handle_request().unwrap();
+
+ // set_log_base,set_log_fd()
+ slave.handle_request().unwrap_err();
+ slave.handle_request().unwrap_err();
+
+ // set_vring_xxx
+ slave.handle_request().unwrap();
+ slave.handle_request().unwrap();
+ slave.handle_request().unwrap();
+ slave.handle_request().unwrap();
+ slave.handle_request().unwrap();
+ slave.handle_request().unwrap();
+
+ // get_max_mem_slots()
+ slave.handle_request().unwrap();
+
+ // add_mem_region()
+ slave.handle_request().unwrap();
+
+ // remove_mem_region()
+ slave.handle_request().unwrap();
+
+ sbar.wait();
+ });
+
+ master.set_owner().unwrap();
+
+ // set virtio features
+ let features = master.get_features().unwrap();
+ assert_eq!(features, VIRTIO_FEATURES);
+ master.set_features(VIRTIO_FEATURES & !0x1).unwrap();
+
+ // set vhost protocol features
+ let features = master.get_protocol_features().unwrap();
+ assert_eq!(features.bits(), VhostUserProtocolFeatures::all().bits());
+ master.set_protocol_features(features).unwrap();
+
+ // Retrieve inflight I/O tracking information
+ let (inflight_info, inflight_file) = master
+ .get_inflight_fd(&VhostUserInflight {
+ num_queues: 2,
+ queue_size: 256,
+ ..Default::default()
+ })
+ .unwrap();
+ // Set the buffer back to the backend
+ master
+ .set_inflight_fd(&inflight_info, inflight_file.as_raw_descriptor())
+ .unwrap();
+
+ let num = master.get_queue_num().unwrap();
+ assert_eq!(num, 2);
+
+ let event = base::Event::new().unwrap();
+ let mem = [VhostUserMemoryRegionInfo {
+ guest_phys_addr: 0,
+ memory_size: 0x10_0000,
+ userspace_addr: 0,
+ mmap_offset: 0,
+ mmap_handle: event.as_raw_descriptor(),
+ }];
+ master.set_mem_table(&mem).unwrap();
+
+ master
+ .set_config(0x100, VhostUserConfigFlags::WRITABLE, &[0xa5u8])
+ .unwrap();
+ let buf = [0x0u8; 4];
+ let (reply_body, reply_payload) = master
+ .get_config(0x100, 4, VhostUserConfigFlags::empty(), &buf)
+ .unwrap();
+ let offset = reply_body.offset;
+ assert_eq!(offset, 0x100);
+ assert_eq!(reply_payload[0], 0xa5);
+
+ // slave request rds are not implemented on Windows.
+ #[cfg(unix)]
+ {
+ master
+ .set_slave_request_fd(&event as &dyn AsRawDescriptor)
+ .unwrap();
+ }
+ master.set_vring_enable(0, true).unwrap();
+
+ // unimplemented yet
+ master
+ .set_log_base(0, Some(event.as_raw_descriptor()))
+ .unwrap();
+ master.set_log_fd(event.as_raw_descriptor()).unwrap();
+
+ master.set_vring_num(0, 256).unwrap();
+ master.set_vring_base(0, 0).unwrap();
+ let config = VringConfigData {
+ queue_max_size: 256,
+ queue_size: 128,
+ flags: VhostUserVringAddrFlags::VHOST_VRING_F_LOG.bits(),
+ desc_table_addr: 0x1000,
+ used_ring_addr: 0x2000,
+ avail_ring_addr: 0x3000,
+ log_addr: Some(0x4000),
+ };
+ master.set_vring_addr(0, &config).unwrap();
+ master.set_vring_call(0, &event).unwrap();
+ master.set_vring_kick(0, &event).unwrap();
+ master.set_vring_err(0, &event).unwrap();
+
+ let max_mem_slots = master.get_max_mem_slots().unwrap();
+ assert_eq!(max_mem_slots, 32);
+
+ let region_file = tempfile().unwrap();
+ let region = VhostUserMemoryRegionInfo {
+ guest_phys_addr: 0x10_0000,
+ memory_size: 0x10_0000,
+ userspace_addr: 0,
+ mmap_offset: 0,
+ mmap_handle: region_file.as_raw_descriptor(),
+ };
+ master.add_mem_region(&region).unwrap();
+
+ master.remove_mem_region(&region).unwrap();
+
+ mbar.wait();
+ }
+
+ #[test]
+ fn test_error_display() {
+ assert_eq!(format!("{}", Error::InvalidParam), "invalid parameters");
+ assert_eq!(format!("{}", Error::InvalidOperation), "invalid operation");
+ }
+
+ #[test]
+ fn test_error_from_base_error() {
+ let e: Error = base::Error::new(libc::EAGAIN).into();
+ if let Error::SocketRetry(e1) = e {
+ assert_eq!(e1.raw_os_error().unwrap(), libc::EAGAIN);
+ } else {
+ panic!("invalid error code conversion!");
+ }
+ }
+}
diff --git a/third_party/vmm_vhost/src/master.rs b/third_party/vmm_vhost/src/master.rs
new file mode 100644
index 000000000..f661187a1
--- /dev/null
+++ b/third_party/vmm_vhost/src/master.rs
@@ -0,0 +1,1100 @@
+// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+//! Traits and Struct for vhost-user master.
+
+use std::fs::File;
+use std::mem;
+use std::path::Path;
+use std::sync::{Arc, Mutex, MutexGuard};
+
+use base::{AsRawDescriptor, Event, RawDescriptor, INVALID_DESCRIPTOR};
+use data_model::DataInit;
+
+use super::connection::{Endpoint, EndpointExt};
+use super::message::*;
+use super::{take_single_file, Error as VhostUserError, Result as VhostUserResult};
+use crate::backend::{VhostBackend, VhostUserMemoryRegionInfo, VringConfigData};
+use crate::{Result, SystemStream};
+
+/// Trait for vhost-user master to provide extra methods not covered by the VhostBackend yet.
+pub trait VhostUserMaster: VhostBackend {
+ /// Get the protocol feature bitmask from the underlying vhost implementation.
+ fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures>;
+
+ /// Enable protocol features in the underlying vhost implementation.
+ fn set_protocol_features(&mut self, features: VhostUserProtocolFeatures) -> Result<()>;
+
+ /// Query how many queues the backend supports.
+ fn get_queue_num(&mut self) -> Result<u64>;
+
+ /// Signal slave to enable or disable corresponding vring.
+ ///
+ /// Slave must not pass data to/from the backend until ring is enabled by
+ /// VHOST_USER_SET_VRING_ENABLE with parameter 1, or after it has been
+ /// disabled by VHOST_USER_SET_VRING_ENABLE with parameter 0.
+ fn set_vring_enable(&mut self, queue_index: usize, enable: bool) -> Result<()>;
+
+ /// Fetch the contents of the virtio device configuration space.
+ fn get_config(
+ &mut self,
+ offset: u32,
+ size: u32,
+ flags: VhostUserConfigFlags,
+ buf: &[u8],
+ ) -> Result<(VhostUserConfig, VhostUserConfigPayload)>;
+
+ /// Change the virtio device configuration space. It also can be used for live migration on the
+ /// destination host to set readonly configuration space fields.
+ fn set_config(&mut self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()>;
+
+ /// Setup slave communication channel.
+ fn set_slave_request_fd(&mut self, fd: &dyn AsRawDescriptor) -> Result<()>;
+
+ /// Retrieve shared buffer for inflight I/O tracking.
+ fn get_inflight_fd(
+ &mut self,
+ inflight: &VhostUserInflight,
+ ) -> Result<(VhostUserInflight, File)>;
+
+ /// Set shared buffer for inflight I/O tracking.
+ fn set_inflight_fd(&mut self, inflight: &VhostUserInflight, fd: RawDescriptor) -> Result<()>;
+
+ /// Query the maximum amount of memory slots supported by the backend.
+ fn get_max_mem_slots(&mut self) -> Result<u64>;
+
+ /// Add a new guest memory mapping for vhost to use.
+ fn add_mem_region(&mut self, region: &VhostUserMemoryRegionInfo) -> Result<()>;
+
+ /// Remove a guest memory mapping from vhost.
+ fn remove_mem_region(&mut self, region: &VhostUserMemoryRegionInfo) -> Result<()>;
+}
+
+/// Struct for the vhost-user master endpoint.
+#[derive(Clone)]
+pub struct Master<E: Endpoint<MasterReq>> {
+ node: Arc<Mutex<MasterInternal<E>>>,
+}
+
+impl<E: Endpoint<MasterReq> + From<SystemStream>> Master<E> {
+ /// Create a new instance from a Unix stream socket.
+ pub fn from_stream(sock: SystemStream, max_queue_num: u64) -> Self {
+ Self::new(E::from(sock), max_queue_num)
+ }
+}
+
+impl<E: Endpoint<MasterReq>> Master<E> {
+ /// Create a new instance.
+ fn new(ep: E, max_queue_num: u64) -> Self {
+ Master {
+ node: Arc::new(Mutex::new(MasterInternal {
+ main_sock: ep,
+ virtio_features: 0,
+ acked_virtio_features: 0,
+ protocol_features: 0,
+ acked_protocol_features: 0,
+ protocol_features_ready: false,
+ max_queue_num,
+ error: None,
+ hdr_flags: VhostUserHeaderFlag::empty(),
+ })),
+ }
+ }
+
+ fn node(&self) -> MutexGuard<MasterInternal<E>> {
+ self.node.lock().unwrap()
+ }
+
+ /// Create a new vhost-user master endpoint.
+ ///
+ /// Will retry as the backend may not be ready to accept the connection.
+ ///
+ /// # Arguments
+ /// * `path` - path of Unix domain socket listener to connect to
+ pub fn connect<P: AsRef<Path>>(path: P, max_queue_num: u64) -> Result<Self> {
+ let mut retry_count = 5;
+ let endpoint = loop {
+ match E::connect(&path) {
+ Ok(endpoint) => break Ok(endpoint),
+ Err(e) => match &e {
+ VhostUserError::SocketConnect(why) => {
+ if why.kind() == std::io::ErrorKind::ConnectionRefused && retry_count > 0 {
+ std::thread::sleep(std::time::Duration::from_millis(100));
+ retry_count -= 1;
+ continue;
+ } else {
+ break Err(e);
+ }
+ }
+ _ => break Err(e),
+ },
+ }
+ }?;
+
+ Ok(Self::new(endpoint, max_queue_num))
+ }
+
+ /// Set the header flags that should be applied to all following messages.
+ pub fn set_hdr_flags(&self, flags: VhostUserHeaderFlag) {
+ let mut node = self.node();
+ node.hdr_flags = flags;
+ }
+}
+
+impl<E: Endpoint<MasterReq>> VhostBackend for Master<E> {
+ /// Get from the underlying vhost implementation the feature bitmask.
+ fn get_features(&self) -> Result<u64> {
+ let mut node = self.node();
+ let hdr = node.send_request_header(MasterReq::GET_FEATURES, None)?;
+ let val = node.recv_reply::<VhostUserU64>(&hdr)?;
+ node.virtio_features = val.value;
+ Ok(node.virtio_features)
+ }
+
+ /// Enable features in the underlying vhost implementation using a bitmask.
+ fn set_features(&self, features: u64) -> Result<()> {
+ let mut node = self.node();
+ let val = VhostUserU64::new(features);
+ let hdr = node.send_request_with_body(MasterReq::SET_FEATURES, &val, None)?;
+ node.acked_virtio_features = features & node.virtio_features;
+ node.wait_for_ack(&hdr)
+ }
+
+ /// Set the current Master as an owner of the session.
+ fn set_owner(&self) -> Result<()> {
+ // We unwrap() the return value to assert that we are not expecting threads to ever fail
+ // while holding the lock.
+ let mut node = self.node();
+ let hdr = node.send_request_header(MasterReq::SET_OWNER, None)?;
+ node.wait_for_ack(&hdr)
+ }
+
+ fn reset_owner(&self) -> Result<()> {
+ let mut node = self.node();
+ let hdr = node.send_request_header(MasterReq::RESET_OWNER, None)?;
+ node.wait_for_ack(&hdr)
+ }
+
+ /// Set the memory map regions on the slave so it can translate the vring
+ /// addresses. In the ancillary data there is an array of file descriptors
+ fn set_mem_table(&self, regions: &[VhostUserMemoryRegionInfo]) -> Result<()> {
+ if regions.is_empty() || regions.len() > MAX_ATTACHED_FD_ENTRIES {
+ return Err(VhostUserError::InvalidParam);
+ }
+
+ let mut ctx = VhostUserMemoryContext::new();
+ for region in regions.iter() {
+ // TODO(b/221882601): once mmap handle cross platform story exists, update this null
+ // check.
+ if region.memory_size == 0 || (region.mmap_handle as isize) < 0 {
+ return Err(VhostUserError::InvalidParam);
+ }
+ let reg = VhostUserMemoryRegion {
+ guest_phys_addr: region.guest_phys_addr,
+ memory_size: region.memory_size,
+ user_addr: region.userspace_addr,
+ mmap_offset: region.mmap_offset,
+ };
+ ctx.append(&reg, region.mmap_handle);
+ }
+
+ let mut node = self.node();
+ let body = VhostUserMemory::new(ctx.regions.len() as u32);
+ let (_, payload, _) = unsafe { ctx.regions.align_to::<u8>() };
+ let hdr = node.send_request_with_payload(
+ MasterReq::SET_MEM_TABLE,
+ &body,
+ payload,
+ Some(ctx.fds.as_slice()),
+ )?;
+ node.wait_for_ack(&hdr)
+ }
+
+ // Clippy doesn't seem to know that if let with && is still experimental
+ #[allow(clippy::unnecessary_unwrap)]
+ fn set_log_base(&self, base: u64, fd: Option<RawDescriptor>) -> Result<()> {
+ let mut node = self.node();
+ let val = VhostUserU64::new(base);
+
+ if node.acked_protocol_features & VhostUserProtocolFeatures::LOG_SHMFD.bits() != 0
+ && fd.is_some()
+ {
+ let fds = [fd.unwrap()];
+ let _ = node.send_request_with_body(MasterReq::SET_LOG_BASE, &val, Some(&fds))?;
+ } else {
+ let _ = node.send_request_with_body(MasterReq::SET_LOG_BASE, &val, None)?;
+ }
+ Ok(())
+ }
+
+ fn set_log_fd(&self, fd: RawDescriptor) -> Result<()> {
+ let mut node = self.node();
+ let fds = [fd];
+ let hdr = node.send_request_header(MasterReq::SET_LOG_FD, Some(&fds))?;
+ node.wait_for_ack(&hdr)
+ }
+
+ /// Set the size of the queue.
+ fn set_vring_num(&self, queue_index: usize, num: u16) -> Result<()> {
+ let mut node = self.node();
+ if queue_index as u64 >= node.max_queue_num {
+ return Err(VhostUserError::InvalidParam);
+ }
+
+ let val = VhostUserVringState::new(queue_index as u32, num.into());
+ let hdr = node.send_request_with_body(MasterReq::SET_VRING_NUM, &val, None)?;
+ node.wait_for_ack(&hdr)
+ }
+
+ /// Sets the addresses of the different aspects of the vring.
+ fn set_vring_addr(&self, queue_index: usize, config_data: &VringConfigData) -> Result<()> {
+ let mut node = self.node();
+ if queue_index as u64 >= node.max_queue_num
+ || config_data.flags & !(VhostUserVringAddrFlags::all().bits()) != 0
+ {
+ return Err(VhostUserError::InvalidParam);
+ }
+
+ let val = VhostUserVringAddr::from_config_data(queue_index as u32, config_data);
+ let hdr = node.send_request_with_body(MasterReq::SET_VRING_ADDR, &val, None)?;
+ node.wait_for_ack(&hdr)
+ }
+
+ /// Sets the base offset in the available vring.
+ fn set_vring_base(&self, queue_index: usize, base: u16) -> Result<()> {
+ let mut node = self.node();
+ if queue_index as u64 >= node.max_queue_num {
+ return Err(VhostUserError::InvalidParam);
+ }
+
+ let val = VhostUserVringState::new(queue_index as u32, base.into());
+ let hdr = node.send_request_with_body(MasterReq::SET_VRING_BASE, &val, None)?;
+ node.wait_for_ack(&hdr)
+ }
+
+ fn get_vring_base(&self, queue_index: usize) -> Result<u32> {
+ let mut node = self.node();
+ if queue_index as u64 >= node.max_queue_num {
+ return Err(VhostUserError::InvalidParam);
+ }
+
+ let req = VhostUserVringState::new(queue_index as u32, 0);
+ let hdr = node.send_request_with_body(MasterReq::GET_VRING_BASE, &req, None)?;
+ let reply = node.recv_reply::<VhostUserVringState>(&hdr)?;
+ Ok(reply.num)
+ }
+
+ /// Set the event file descriptor to signal when buffers are used.
+ /// Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag. This flag
+ /// is set when there is no file descriptor in the ancillary data. This signals that polling
+ /// will be used instead of waiting for the call.
+ fn set_vring_call(&self, queue_index: usize, event: &Event) -> Result<()> {
+ let mut node = self.node();
+ if queue_index as u64 >= node.max_queue_num {
+ return Err(VhostUserError::InvalidParam);
+ }
+ let hdr = node.send_fd_for_vring(
+ MasterReq::SET_VRING_CALL,
+ queue_index,
+ event.as_raw_descriptor(),
+ )?;
+ node.wait_for_ack(&hdr)
+ }
+
+ /// Set the event file descriptor for adding buffers to the vring.
+ /// Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag. This flag
+ /// is set when there is no file descriptor in the ancillary data. This signals that polling
+ /// should be used instead of waiting for a kick.
+ fn set_vring_kick(&self, queue_index: usize, event: &Event) -> Result<()> {
+ let mut node = self.node();
+ if queue_index as u64 >= node.max_queue_num {
+ return Err(VhostUserError::InvalidParam);
+ }
+ let hdr = node.send_fd_for_vring(
+ MasterReq::SET_VRING_KICK,
+ queue_index,
+ event.as_raw_descriptor(),
+ )?;
+ node.wait_for_ack(&hdr)
+ }
+
+ /// Set the event file descriptor to signal when error occurs.
+ /// Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag. This flag
+ /// is set when there is no file descriptor in the ancillary data.
+ fn set_vring_err(&self, queue_index: usize, event: &Event) -> Result<()> {
+ let mut node = self.node();
+ if queue_index as u64 >= node.max_queue_num {
+ return Err(VhostUserError::InvalidParam);
+ }
+ let hdr = node.send_fd_for_vring(
+ MasterReq::SET_VRING_ERR,
+ queue_index,
+ event.as_raw_descriptor(),
+ )?;
+ node.wait_for_ack(&hdr)
+ }
+}
+
+impl<E: Endpoint<MasterReq>> VhostUserMaster for Master<E> {
+ fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures> {
+ let mut node = self.node();
+ let flag = VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ if node.virtio_features & flag == 0 {
+ return Err(VhostUserError::InvalidOperation);
+ }
+ let hdr = node.send_request_header(MasterReq::GET_PROTOCOL_FEATURES, None)?;
+ let val = node.recv_reply::<VhostUserU64>(&hdr)?;
+ node.protocol_features = val.value;
+ // Should we support forward compatibility?
+ // If so just mask out unrecognized flags instead of return errors.
+ match VhostUserProtocolFeatures::from_bits(node.protocol_features) {
+ Some(val) => Ok(val),
+ None => Err(VhostUserError::InvalidMessage),
+ }
+ }
+
+ fn set_protocol_features(&mut self, features: VhostUserProtocolFeatures) -> Result<()> {
+ let mut node = self.node();
+ let flag = VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ if node.virtio_features & flag == 0 {
+ return Err(VhostUserError::InvalidOperation);
+ }
+ let val = VhostUserU64::new(features.bits());
+ let hdr = node.send_request_with_body(MasterReq::SET_PROTOCOL_FEATURES, &val, None)?;
+ // Don't wait for ACK here because the protocol feature negotiation process hasn't been
+ // completed yet.
+ node.acked_protocol_features = features.bits();
+ node.protocol_features_ready = true;
+ node.wait_for_ack(&hdr)
+ }
+
+ fn get_queue_num(&mut self) -> Result<u64> {
+ let mut node = self.node();
+ if !node.is_feature_mq_available() {
+ return Err(VhostUserError::InvalidOperation);
+ }
+
+ let hdr = node.send_request_header(MasterReq::GET_QUEUE_NUM, None)?;
+ let val = node.recv_reply::<VhostUserU64>(&hdr)?;
+ if val.value > VHOST_USER_MAX_VRINGS {
+ return Err(VhostUserError::InvalidMessage);
+ }
+ node.max_queue_num = val.value;
+ Ok(node.max_queue_num)
+ }
+
+ fn set_vring_enable(&mut self, queue_index: usize, enable: bool) -> Result<()> {
+ let mut node = self.node();
+ // set_vring_enable() is supported only when PROTOCOL_FEATURES has been enabled.
+ if node.acked_virtio_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits() == 0 {
+ return Err(VhostUserError::InvalidOperation);
+ } else if queue_index as u64 >= node.max_queue_num {
+ return Err(VhostUserError::InvalidParam);
+ }
+
+ let flag = if enable { 1 } else { 0 };
+ let val = VhostUserVringState::new(queue_index as u32, flag);
+ let hdr = node.send_request_with_body(MasterReq::SET_VRING_ENABLE, &val, None)?;
+ node.wait_for_ack(&hdr)
+ }
+
+ fn get_config(
+ &mut self,
+ offset: u32,
+ size: u32,
+ flags: VhostUserConfigFlags,
+ buf: &[u8],
+ ) -> Result<(VhostUserConfig, VhostUserConfigPayload)> {
+ let body = VhostUserConfig::new(offset, size, flags);
+ if !body.is_valid() {
+ return Err(VhostUserError::InvalidParam);
+ }
+
+ let mut node = self.node();
+ // depends on VhostUserProtocolFeatures::CONFIG
+ if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
+ return Err(VhostUserError::InvalidOperation);
+ }
+
+ // vhost-user spec states that:
+ // "Master payload: virtio device config space"
+ // "Slave payload: virtio device config space"
+ let hdr = node.send_request_with_payload(MasterReq::GET_CONFIG, &body, buf, None)?;
+ let (body_reply, buf_reply, rfds) =
+ node.recv_reply_with_payload::<VhostUserConfig>(&hdr)?;
+ if rfds.is_some() {
+ return Err(VhostUserError::InvalidMessage);
+ } else if body_reply.size == 0 {
+ return Err(VhostUserError::SlaveInternalError);
+ } else if body_reply.size != body.size
+ || body_reply.size as usize != buf.len()
+ || body_reply.offset != body.offset
+ {
+ return Err(VhostUserError::InvalidMessage);
+ }
+
+ Ok((body_reply, buf_reply))
+ }
+
+ fn set_config(&mut self, offset: u32, flags: VhostUserConfigFlags, buf: &[u8]) -> Result<()> {
+ if buf.len() > MAX_MSG_SIZE {
+ return Err(VhostUserError::InvalidParam);
+ }
+ let body = VhostUserConfig::new(offset, buf.len() as u32, flags);
+ if !body.is_valid() {
+ return Err(VhostUserError::InvalidParam);
+ }
+
+ let mut node = self.node();
+ // depends on VhostUserProtocolFeatures::CONFIG
+ if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
+ return Err(VhostUserError::InvalidOperation);
+ }
+
+ let hdr = node.send_request_with_payload(MasterReq::SET_CONFIG, &body, buf, None)?;
+ node.wait_for_ack(&hdr)
+ }
+
+ fn set_slave_request_fd(&mut self, fd: &dyn AsRawDescriptor) -> Result<()> {
+ let mut node = self.node();
+ if node.acked_protocol_features & VhostUserProtocolFeatures::SLAVE_REQ.bits() == 0 {
+ return Err(VhostUserError::InvalidOperation);
+ }
+ let fds = [fd.as_raw_descriptor()];
+ let hdr = node.send_request_header(MasterReq::SET_SLAVE_REQ_FD, Some(&fds))?;
+ node.wait_for_ack(&hdr)
+ }
+
+ fn get_inflight_fd(
+ &mut self,
+ inflight: &VhostUserInflight,
+ ) -> Result<(VhostUserInflight, File)> {
+ let mut node = self.node();
+ if node.acked_protocol_features & VhostUserProtocolFeatures::INFLIGHT_SHMFD.bits() == 0 {
+ return Err(VhostUserError::InvalidOperation);
+ }
+
+ let hdr = node.send_request_with_body(MasterReq::GET_INFLIGHT_FD, inflight, None)?;
+ let (inflight, files) = node.recv_reply_with_files::<VhostUserInflight>(&hdr)?;
+
+ match take_single_file(files) {
+ Some(file) => Ok((inflight, file)),
+ None => Err(VhostUserError::IncorrectFds),
+ }
+ }
+
+ fn set_inflight_fd(&mut self, inflight: &VhostUserInflight, fd: RawDescriptor) -> Result<()> {
+ let mut node = self.node();
+ if node.acked_protocol_features & VhostUserProtocolFeatures::INFLIGHT_SHMFD.bits() == 0 {
+ return Err(VhostUserError::InvalidOperation);
+ }
+
+ if inflight.mmap_size == 0
+ || inflight.num_queues == 0
+ || inflight.queue_size == 0
+ || fd == INVALID_DESCRIPTOR
+ {
+ return Err(VhostUserError::InvalidParam);
+ }
+
+ let hdr = node.send_request_with_body(MasterReq::SET_INFLIGHT_FD, inflight, Some(&[fd]))?;
+ node.wait_for_ack(&hdr)
+ }
+
+ fn get_max_mem_slots(&mut self) -> Result<u64> {
+ let mut node = self.node();
+ if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS.bits() == 0
+ {
+ return Err(VhostUserError::InvalidOperation);
+ }
+
+ let hdr = node.send_request_header(MasterReq::GET_MAX_MEM_SLOTS, None)?;
+ let val = node.recv_reply::<VhostUserU64>(&hdr)?;
+
+ Ok(val.value)
+ }
+
+ fn add_mem_region(&mut self, region: &VhostUserMemoryRegionInfo) -> Result<()> {
+ let mut node = self.node();
+ if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS.bits() == 0
+ {
+ return Err(VhostUserError::InvalidOperation);
+ }
+ // TODO(b/221882601): once mmap handle cross platform story exists, update this null check.
+ if region.memory_size == 0 || (region.mmap_handle as isize) < 0 {
+ return Err(VhostUserError::InvalidParam);
+ }
+
+ let body = VhostUserSingleMemoryRegion::new(
+ region.guest_phys_addr,
+ region.memory_size,
+ region.userspace_addr,
+ region.mmap_offset,
+ );
+ let fds = [region.mmap_handle];
+ let hdr = node.send_request_with_body(MasterReq::ADD_MEM_REG, &body, Some(&fds))?;
+ node.wait_for_ack(&hdr)
+ }
+
+ fn remove_mem_region(&mut self, region: &VhostUserMemoryRegionInfo) -> Result<()> {
+ let mut node = self.node();
+ if node.acked_protocol_features & VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS.bits() == 0
+ {
+ return Err(VhostUserError::InvalidOperation);
+ }
+ if region.memory_size == 0 {
+ return Err(VhostUserError::InvalidParam);
+ }
+
+ let body = VhostUserSingleMemoryRegion::new(
+ region.guest_phys_addr,
+ region.memory_size,
+ region.userspace_addr,
+ region.mmap_offset,
+ );
+ let hdr = node.send_request_with_body(MasterReq::REM_MEM_REG, &body, None)?;
+ node.wait_for_ack(&hdr)
+ }
+}
+
+impl<E: Endpoint<MasterReq> + AsRawDescriptor> AsRawDescriptor for Master<E> {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ let node = self.node();
+ // TODO(b/221882601): why is this here? The underlying Tube needs to use a read notifier
+ // if this is for polling.
+ node.main_sock.as_raw_descriptor()
+ }
+}
+
+// TODO(b/221882601): likely need pairs of RDs and/or SharedMemory to represent mmaps on Windows.
+/// Context object to pass guest memory configuration to VhostUserMaster::set_mem_table().
+struct VhostUserMemoryContext {
+ regions: VhostUserMemoryPayload,
+ fds: Vec<RawDescriptor>,
+}
+
+impl VhostUserMemoryContext {
+ /// Create a context object.
+ pub fn new() -> Self {
+ VhostUserMemoryContext {
+ regions: VhostUserMemoryPayload::new(),
+ fds: Vec::new(),
+ }
+ }
+
+ /// Append a user memory region and corresponding RawDescriptor into the context object.
+ pub fn append(&mut self, region: &VhostUserMemoryRegion, fd: RawDescriptor) {
+ self.regions.push(*region);
+ self.fds.push(fd);
+ }
+}
+
+struct MasterInternal<E: Endpoint<MasterReq>> {
+ // Used to send requests to the slave.
+ main_sock: E,
+ // Cached virtio features from the slave.
+ virtio_features: u64,
+ // Cached acked virtio features from the driver.
+ acked_virtio_features: u64,
+ // Cached vhost-user protocol features from the slave.
+ protocol_features: u64,
+ // Cached vhost-user protocol features.
+ acked_protocol_features: u64,
+ // Cached vhost-user protocol features are ready to use.
+ protocol_features_ready: bool,
+ // Cached maxinum number of queues supported from the slave.
+ max_queue_num: u64,
+ // Internal flag to mark failure state.
+ error: Option<i32>,
+ // List of header flags.
+ hdr_flags: VhostUserHeaderFlag,
+}
+
+impl<E: Endpoint<MasterReq>> MasterInternal<E> {
+ fn send_request_header(
+ &mut self,
+ code: MasterReq,
+ fds: Option<&[RawDescriptor]>,
+ ) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
+ self.check_state()?;
+ let hdr = self.new_request_header(code, 0);
+ self.main_sock.send_header(&hdr, fds)?;
+ Ok(hdr)
+ }
+
+ fn send_request_with_body<T: Sized + DataInit>(
+ &mut self,
+ code: MasterReq,
+ msg: &T,
+ fds: Option<&[RawDescriptor]>,
+ ) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
+ if mem::size_of::<T>() > MAX_MSG_SIZE {
+ return Err(VhostUserError::InvalidParam);
+ }
+ self.check_state()?;
+
+ let hdr = self.new_request_header(code, mem::size_of::<T>() as u32);
+ self.main_sock.send_message(&hdr, msg, fds)?;
+ Ok(hdr)
+ }
+
+ fn send_request_with_payload<T: Sized + DataInit>(
+ &mut self,
+ code: MasterReq,
+ msg: &T,
+ payload: &[u8],
+ fds: Option<&[RawDescriptor]>,
+ ) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
+ let len = mem::size_of::<T>() + payload.len();
+ if len > MAX_MSG_SIZE {
+ return Err(VhostUserError::InvalidParam);
+ }
+ if let Some(fd_arr) = fds {
+ if fd_arr.len() > MAX_ATTACHED_FD_ENTRIES {
+ return Err(VhostUserError::InvalidParam);
+ }
+ }
+ self.check_state()?;
+
+ let hdr = self.new_request_header(code, len as u32);
+ self.main_sock
+ .send_message_with_payload(&hdr, msg, payload, fds)?;
+ Ok(hdr)
+ }
+
+ fn send_fd_for_vring(
+ &mut self,
+ code: MasterReq,
+ queue_index: usize,
+ fd: RawDescriptor,
+ ) -> VhostUserResult<VhostUserMsgHeader<MasterReq>> {
+ if queue_index as u64 >= self.max_queue_num {
+ return Err(VhostUserError::InvalidParam);
+ }
+ self.check_state()?;
+
+ // Bits (0-7) of the payload contain the vring index. Bit 8 is the invalid FD flag.
+ // This flag is set when there is no file descriptor in the ancillary data. This signals
+ // that polling will be used instead of waiting for the call.
+ let msg = VhostUserU64::new(queue_index as u64);
+ let hdr = self.new_request_header(code, mem::size_of::<VhostUserU64>() as u32);
+ self.main_sock.send_message(&hdr, &msg, Some(&[fd]))?;
+ Ok(hdr)
+ }
+
+ fn recv_reply<T: Sized + DataInit + Default + VhostUserMsgValidator>(
+ &mut self,
+ hdr: &VhostUserMsgHeader<MasterReq>,
+ ) -> VhostUserResult<T> {
+ if mem::size_of::<T>() > MAX_MSG_SIZE || hdr.is_reply() {
+ return Err(VhostUserError::InvalidParam);
+ }
+ self.check_state()?;
+
+ let (reply, body, rfds) = self.main_sock.recv_body::<T>()?;
+ if !reply.is_reply_for(hdr) || rfds.is_some() || !body.is_valid() {
+ return Err(VhostUserError::InvalidMessage);
+ }
+ Ok(body)
+ }
+
+ fn recv_reply_with_files<T: Sized + DataInit + Default + VhostUserMsgValidator>(
+ &mut self,
+ hdr: &VhostUserMsgHeader<MasterReq>,
+ ) -> VhostUserResult<(T, Option<Vec<File>>)> {
+ if mem::size_of::<T>() > MAX_MSG_SIZE || hdr.is_reply() {
+ return Err(VhostUserError::InvalidParam);
+ }
+ self.check_state()?;
+
+ let (reply, body, files) = self.main_sock.recv_body::<T>()?;
+ if !reply.is_reply_for(hdr) || files.is_none() || !body.is_valid() {
+ return Err(VhostUserError::InvalidMessage);
+ }
+ Ok((body, files))
+ }
+
+ fn recv_reply_with_payload<T: Sized + DataInit + Default + VhostUserMsgValidator>(
+ &mut self,
+ hdr: &VhostUserMsgHeader<MasterReq>,
+ ) -> VhostUserResult<(T, Vec<u8>, Option<Vec<File>>)> {
+ if mem::size_of::<T>() > MAX_MSG_SIZE
+ || hdr.get_size() as usize <= mem::size_of::<T>()
+ || hdr.get_size() as usize > MAX_MSG_SIZE
+ || hdr.is_reply()
+ {
+ return Err(VhostUserError::InvalidParam);
+ }
+ self.check_state()?;
+
+ let mut buf: Vec<u8> = vec![0; hdr.get_size() as usize - mem::size_of::<T>()];
+ let (reply, body, bytes, files) = self.main_sock.recv_payload_into_buf::<T>(&mut buf)?;
+ if !reply.is_reply_for(hdr)
+ || reply.get_size() as usize != mem::size_of::<T>() + bytes
+ || files.is_some()
+ || !body.is_valid()
+ || bytes != buf.len()
+ {
+ return Err(VhostUserError::InvalidMessage);
+ }
+
+ Ok((body, buf, files))
+ }
+
+ fn wait_for_ack(&mut self, hdr: &VhostUserMsgHeader<MasterReq>) -> VhostUserResult<()> {
+ if self.acked_protocol_features & VhostUserProtocolFeatures::REPLY_ACK.bits() == 0
+ || !hdr.is_need_reply()
+ {
+ return Ok(());
+ }
+ self.check_state()?;
+
+ let (reply, body, rfds) = self.main_sock.recv_body::<VhostUserU64>()?;
+ if !reply.is_reply_for(hdr) || rfds.is_some() || !body.is_valid() {
+ return Err(VhostUserError::InvalidMessage);
+ }
+ if body.value != 0 {
+ return Err(VhostUserError::SlaveInternalError);
+ }
+ Ok(())
+ }
+
+ fn is_feature_mq_available(&self) -> bool {
+ self.acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() != 0
+ }
+
+ fn check_state(&self) -> VhostUserResult<()> {
+ match self.error {
+ Some(e) => Err(VhostUserError::SocketBroken(
+ std::io::Error::from_raw_os_error(e),
+ )),
+ None => Ok(()),
+ }
+ }
+
+ #[inline]
+ fn new_request_header(&self, request: MasterReq, size: u32) -> VhostUserMsgHeader<MasterReq> {
+ VhostUserMsgHeader::new(request, self.hdr_flags.bits() | 0x1, size)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::connection::tests::{create_pair, TestEndpoint, TestMaster};
+ use base::INVALID_DESCRIPTOR;
+
+ #[test]
+ fn create_master() {
+ let (master, mut slave) = create_pair();
+
+ assert!(master.as_raw_descriptor() != INVALID_DESCRIPTOR);
+ // Send two messages continuously
+ master.set_owner().unwrap();
+ master.reset_owner().unwrap();
+
+ let (hdr, rfds) = slave.recv_header().unwrap();
+ assert_eq!(hdr.get_code(), MasterReq::SET_OWNER);
+ assert_eq!(hdr.get_size(), 0);
+ assert_eq!(hdr.get_version(), 0x1);
+ assert!(rfds.is_none());
+
+ let (hdr, rfds) = slave.recv_header().unwrap();
+ assert_eq!(hdr.get_code(), MasterReq::RESET_OWNER);
+ assert_eq!(hdr.get_size(), 0);
+ assert_eq!(hdr.get_version(), 0x1);
+ assert!(rfds.is_none());
+ }
+
+ #[test]
+ fn test_features() {
+ let (master, mut peer) = create_pair();
+
+ master.set_owner().unwrap();
+ let (hdr, rfds) = peer.recv_header().unwrap();
+ assert_eq!(hdr.get_code(), MasterReq::SET_OWNER);
+ assert_eq!(hdr.get_size(), 0);
+ assert_eq!(hdr.get_version(), 0x1);
+ assert!(rfds.is_none());
+
+ let hdr = VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0x4, 8);
+ let msg = VhostUserU64::new(0x15);
+ peer.send_message(&hdr, &msg, None).unwrap();
+ let features = master.get_features().unwrap();
+ assert_eq!(features, 0x15u64);
+ let (_hdr, rfds) = peer.recv_header().unwrap();
+ assert!(rfds.is_none());
+
+ let hdr = VhostUserMsgHeader::new(MasterReq::SET_FEATURES, 0x4, 8);
+ let msg = VhostUserU64::new(0x15);
+ peer.send_message(&hdr, &msg, None).unwrap();
+ master.set_features(0x15).unwrap();
+ let (_hdr, msg, rfds) = peer.recv_body::<VhostUserU64>().unwrap();
+ assert!(rfds.is_none());
+ let val = msg.value;
+ assert_eq!(val, 0x15);
+
+ let hdr = VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0x4, 8);
+ let msg = 0x15u32;
+ peer.send_message(&hdr, &msg, None).unwrap();
+ assert!(master.get_features().is_err());
+ }
+
+ #[test]
+ fn test_protocol_features() {
+ let (mut master, mut peer) = create_pair();
+
+ master.set_owner().unwrap();
+ let (hdr, rfds) = peer.recv_header().unwrap();
+ assert_eq!(hdr.get_code(), MasterReq::SET_OWNER);
+ assert!(rfds.is_none());
+
+ assert!(master.get_protocol_features().is_err());
+ assert!(master
+ .set_protocol_features(VhostUserProtocolFeatures::all())
+ .is_err());
+
+ let vfeatures = 0x15 | VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let hdr = VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0x4, 8);
+ let msg = VhostUserU64::new(vfeatures);
+ peer.send_message(&hdr, &msg, None).unwrap();
+ let features = master.get_features().unwrap();
+ assert_eq!(features, vfeatures);
+ let (_hdr, rfds) = peer.recv_header().unwrap();
+ assert!(rfds.is_none());
+
+ master.set_features(vfeatures).unwrap();
+ let (_hdr, msg, rfds) = peer.recv_body::<VhostUserU64>().unwrap();
+ assert!(rfds.is_none());
+ let val = msg.value;
+ assert_eq!(val, vfeatures);
+
+ let pfeatures = VhostUserProtocolFeatures::all();
+ let hdr = VhostUserMsgHeader::new(MasterReq::GET_PROTOCOL_FEATURES, 0x4, 8);
+ let msg = VhostUserU64::new(pfeatures.bits());
+ peer.send_message(&hdr, &msg, None).unwrap();
+ let features = master.get_protocol_features().unwrap();
+ assert_eq!(features, pfeatures);
+ let (_hdr, rfds) = peer.recv_header().unwrap();
+ assert!(rfds.is_none());
+
+ master.set_protocol_features(pfeatures).unwrap();
+ let (_hdr, msg, rfds) = peer.recv_body::<VhostUserU64>().unwrap();
+ assert!(rfds.is_none());
+ let val = msg.value;
+ assert_eq!(val, pfeatures.bits());
+
+ let hdr = VhostUserMsgHeader::new(MasterReq::SET_PROTOCOL_FEATURES, 0x4, 8);
+ let msg = VhostUserU64::new(pfeatures.bits());
+ peer.send_message(&hdr, &msg, None).unwrap();
+ assert!(master.get_protocol_features().is_err());
+ }
+
+ #[test]
+ fn test_master_set_config_negative() {
+ let (mut master, _peer) = create_pair();
+ let buf = vec![0x0; MAX_MSG_SIZE + 1];
+
+ master
+ .set_config(0x100, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .unwrap_err();
+
+ {
+ let mut node = master.node();
+ node.virtio_features = 0xffff_ffff;
+ node.acked_virtio_features = 0xffff_ffff;
+ node.protocol_features = 0xffff_ffff;
+ node.acked_protocol_features = 0xffff_ffff;
+ }
+
+ master
+ .set_config(0, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .unwrap();
+ master
+ .set_config(
+ VHOST_USER_CONFIG_SIZE,
+ VhostUserConfigFlags::WRITABLE,
+ &buf[0..4],
+ )
+ .unwrap_err();
+ master
+ .set_config(0x1000, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .unwrap_err();
+ master
+ .set_config(
+ 0x100,
+ unsafe { VhostUserConfigFlags::from_bits_unchecked(0xffff_ffff) },
+ &buf[0..4],
+ )
+ .unwrap_err();
+ master
+ .set_config(VHOST_USER_CONFIG_SIZE, VhostUserConfigFlags::WRITABLE, &buf)
+ .unwrap_err();
+ master
+ .set_config(VHOST_USER_CONFIG_SIZE, VhostUserConfigFlags::WRITABLE, &[])
+ .unwrap_err();
+ }
+
+ fn create_pair2() -> (TestMaster, TestEndpoint) {
+ let (master, peer) = create_pair();
+ {
+ let mut node = master.node();
+ node.virtio_features = 0xffff_ffff;
+ node.acked_virtio_features = 0xffff_ffff;
+ node.protocol_features = 0xffff_ffff;
+ node.acked_protocol_features = 0xffff_ffff;
+ }
+
+ (master, peer)
+ }
+
+ #[test]
+ fn test_master_get_config_negative0() {
+ let (mut master, mut peer) = create_pair2();
+ let buf = vec![0x0; MAX_MSG_SIZE + 1];
+
+ let mut hdr = VhostUserMsgHeader::new(MasterReq::GET_CONFIG, 0x4, 16);
+ let msg = VhostUserConfig::new(0x100, 4, VhostUserConfigFlags::empty());
+ peer.send_message_with_payload(&hdr, &msg, &buf[0..4], None)
+ .unwrap();
+ assert!(master
+ .get_config(0x100, 4, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .is_ok());
+
+ hdr.set_code(MasterReq::GET_FEATURES);
+ peer.send_message_with_payload(&hdr, &msg, &buf[0..4], None)
+ .unwrap();
+ assert!(master
+ .get_config(0x100, 4, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .is_err());
+ hdr.set_code(MasterReq::GET_CONFIG);
+ }
+
+ #[test]
+ fn test_master_get_config_negative1() {
+ let (mut master, mut peer) = create_pair2();
+ let buf = vec![0x0; MAX_MSG_SIZE + 1];
+
+ let mut hdr = VhostUserMsgHeader::new(MasterReq::GET_CONFIG, 0x4, 16);
+ let msg = VhostUserConfig::new(0x100, 4, VhostUserConfigFlags::empty());
+ peer.send_message_with_payload(&hdr, &msg, &buf[0..4], None)
+ .unwrap();
+ assert!(master
+ .get_config(0x100, 4, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .is_ok());
+
+ hdr.set_reply(false);
+ peer.send_message_with_payload(&hdr, &msg, &buf[0..4], None)
+ .unwrap();
+ assert!(master
+ .get_config(0x100, 4, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .is_err());
+ }
+
+ #[test]
+ fn test_master_get_config_negative2() {
+ let (mut master, mut peer) = create_pair2();
+ let buf = vec![0x0; MAX_MSG_SIZE + 1];
+
+ let hdr = VhostUserMsgHeader::new(MasterReq::GET_CONFIG, 0x4, 16);
+ let msg = VhostUserConfig::new(0x100, 4, VhostUserConfigFlags::empty());
+ peer.send_message_with_payload(&hdr, &msg, &buf[0..4], None)
+ .unwrap();
+ assert!(master
+ .get_config(0x100, 4, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .is_ok());
+ }
+
+ #[test]
+ fn test_master_get_config_negative3() {
+ let (mut master, mut peer) = create_pair2();
+ let buf = vec![0x0; MAX_MSG_SIZE + 1];
+
+ let hdr = VhostUserMsgHeader::new(MasterReq::GET_CONFIG, 0x4, 16);
+ let mut msg = VhostUserConfig::new(0x100, 4, VhostUserConfigFlags::empty());
+ peer.send_message_with_payload(&hdr, &msg, &buf[0..4], None)
+ .unwrap();
+ assert!(master
+ .get_config(0x100, 4, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .is_ok());
+
+ msg.offset = 0;
+ peer.send_message_with_payload(&hdr, &msg, &buf[0..4], None)
+ .unwrap();
+ assert!(master
+ .get_config(0x100, 4, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .is_err());
+ }
+
+ #[test]
+ fn test_master_get_config_negative4() {
+ let (mut master, mut peer) = create_pair2();
+ let buf = vec![0x0; MAX_MSG_SIZE + 1];
+
+ let hdr = VhostUserMsgHeader::new(MasterReq::GET_CONFIG, 0x4, 16);
+ let mut msg = VhostUserConfig::new(0x100, 4, VhostUserConfigFlags::empty());
+ peer.send_message_with_payload(&hdr, &msg, &buf[0..4], None)
+ .unwrap();
+ assert!(master
+ .get_config(0x100, 4, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .is_ok());
+
+ msg.offset = 0x101;
+ peer.send_message_with_payload(&hdr, &msg, &buf[0..4], None)
+ .unwrap();
+ assert!(master
+ .get_config(0x100, 4, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .is_err());
+ }
+
+ #[test]
+ fn test_master_get_config_negative5() {
+ let (mut master, mut peer) = create_pair2();
+ let buf = vec![0x0; MAX_MSG_SIZE + 1];
+
+ let hdr = VhostUserMsgHeader::new(MasterReq::GET_CONFIG, 0x4, 16);
+ let mut msg = VhostUserConfig::new(0x100, 4, VhostUserConfigFlags::empty());
+ peer.send_message_with_payload(&hdr, &msg, &buf[0..4], None)
+ .unwrap();
+ assert!(master
+ .get_config(0x100, 4, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .is_ok());
+
+ msg.offset = (MAX_MSG_SIZE + 1) as u32;
+ peer.send_message_with_payload(&hdr, &msg, &buf[0..4], None)
+ .unwrap();
+ assert!(master
+ .get_config(0x100, 4, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .is_err());
+ }
+
+ #[test]
+ fn test_master_get_config_negative6() {
+ let (mut master, mut peer) = create_pair2();
+ let buf = vec![0x0; MAX_MSG_SIZE + 1];
+
+ let hdr = VhostUserMsgHeader::new(MasterReq::GET_CONFIG, 0x4, 16);
+ let mut msg = VhostUserConfig::new(0x100, 4, VhostUserConfigFlags::empty());
+ peer.send_message_with_payload(&hdr, &msg, &buf[0..4], None)
+ .unwrap();
+ assert!(master
+ .get_config(0x100, 4, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .is_ok());
+
+ msg.size = 6;
+ peer.send_message_with_payload(&hdr, &msg, &buf[0..6], None)
+ .unwrap();
+ assert!(master
+ .get_config(0x100, 4, VhostUserConfigFlags::WRITABLE, &buf[0..4])
+ .is_err());
+ }
+
+ #[test]
+ fn test_maset_set_mem_table_failure() {
+ let (master, _peer) = create_pair2();
+
+ master.set_mem_table(&[]).unwrap_err();
+ let tables = vec![VhostUserMemoryRegionInfo::default(); MAX_ATTACHED_FD_ENTRIES + 1];
+ master.set_mem_table(&tables).unwrap_err();
+ }
+}
diff --git a/third_party/vmm_vhost/src/master_req_handler.rs b/third_party/vmm_vhost/src/master_req_handler.rs
new file mode 100644
index 000000000..d05352e63
--- /dev/null
+++ b/third_party/vmm_vhost/src/master_req_handler.rs
@@ -0,0 +1,512 @@
+// Copyright (C) 2019-2021 Alibaba Cloud. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+#[cfg(unix)]
+use std::fs::File;
+#[cfg(unix)]
+use std::mem;
+#[cfg(unix)]
+use std::os::unix::io::AsRawFd;
+use std::sync::Mutex;
+
+#[cfg(unix)]
+use super::connection::{socket::Endpoint as SocketEndpoint, EndpointExt};
+use super::message::*;
+use super::HandlerResult;
+#[cfg(unix)]
+use super::{Error, Result};
+#[cfg(unix)]
+use crate::SystemStream;
+#[cfg(unix)]
+use std::sync::Arc;
+
+use base::AsRawDescriptor;
+#[cfg(unix)]
+use base::RawDescriptor;
+
+/// Define services provided by masters for the slave communication channel.
+///
+/// The vhost-user specification defines a slave communication channel, by which slaves could
+/// request services from masters. The [VhostUserMasterReqHandler] trait defines services provided
+/// by masters, and it's used both on the master side and slave side.
+/// - on the slave side, a stub forwarder implementing [VhostUserMasterReqHandler] will proxy
+/// service requests to masters. The [SlaveFsCacheReq] is an example stub forwarder.
+/// - on the master side, the [MasterReqHandler] will forward service requests to a handler
+/// implementing [VhostUserMasterReqHandler].
+///
+/// The [VhostUserMasterReqHandler] trait is design with interior mutability to improve performance
+/// for multi-threading.
+///
+/// [VhostUserMasterReqHandler]: trait.VhostUserMasterReqHandler.html
+/// [MasterReqHandler]: struct.MasterReqHandler.html
+/// [SlaveFsCacheReq]: struct.SlaveFsCacheReq.html
+pub trait VhostUserMasterReqHandler {
+ /// Handle device configuration change notifications.
+ fn handle_config_change(&self) -> HandlerResult<u64> {
+ Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+ }
+
+ /// Handle virtio-fs map file requests.
+ fn fs_slave_map(
+ &self,
+ _fs: &VhostUserFSSlaveMsg,
+ _fd: &dyn AsRawDescriptor,
+ ) -> HandlerResult<u64> {
+ Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+ }
+
+ /// Handle virtio-fs unmap file requests.
+ fn fs_slave_unmap(&self, _fs: &VhostUserFSSlaveMsg) -> HandlerResult<u64> {
+ Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+ }
+
+ /// Handle virtio-fs sync file requests.
+ fn fs_slave_sync(&self, _fs: &VhostUserFSSlaveMsg) -> HandlerResult<u64> {
+ Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+ }
+
+ /// Handle virtio-fs file IO requests.
+ fn fs_slave_io(
+ &self,
+ _fs: &VhostUserFSSlaveMsg,
+ _fd: &dyn AsRawDescriptor,
+ ) -> HandlerResult<u64> {
+ Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+ }
+
+ // fn handle_iotlb_msg(&mut self, iotlb: VhostUserIotlb);
+ // fn handle_vring_host_notifier(&mut self, area: VhostUserVringArea, fd: &dyn AsRawDescriptor);
+}
+
+/// A helper trait mirroring [VhostUserMasterReqHandler] but without interior mutability.
+///
+/// [VhostUserMasterReqHandler]: trait.VhostUserMasterReqHandler.html
+pub trait VhostUserMasterReqHandlerMut {
+ /// Handle device configuration change notifications.
+ fn handle_config_change(&mut self) -> HandlerResult<u64> {
+ Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+ }
+
+ /// Handle virtio-fs map file requests.
+ fn fs_slave_map(
+ &mut self,
+ _fs: &VhostUserFSSlaveMsg,
+ _fd: &dyn AsRawDescriptor,
+ ) -> HandlerResult<u64> {
+ Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+ }
+
+ /// Handle virtio-fs unmap file requests.
+ fn fs_slave_unmap(&mut self, _fs: &VhostUserFSSlaveMsg) -> HandlerResult<u64> {
+ Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+ }
+
+ /// Handle virtio-fs sync file requests.
+ fn fs_slave_sync(&mut self, _fs: &VhostUserFSSlaveMsg) -> HandlerResult<u64> {
+ Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+ }
+
+ /// Handle virtio-fs file IO requests.
+ fn fs_slave_io(
+ &mut self,
+ _fs: &VhostUserFSSlaveMsg,
+ _fd: &dyn AsRawDescriptor,
+ ) -> HandlerResult<u64> {
+ Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+ }
+
+ // fn handle_iotlb_msg(&mut self, iotlb: VhostUserIotlb);
+ // fn handle_vring_host_notifier(&mut self, area: VhostUserVringArea, fd: RawDescriptor);
+}
+
+impl<S: VhostUserMasterReqHandlerMut> VhostUserMasterReqHandler for Mutex<S> {
+ fn handle_config_change(&self) -> HandlerResult<u64> {
+ self.lock().unwrap().handle_config_change()
+ }
+
+ fn fs_slave_map(
+ &self,
+ fs: &VhostUserFSSlaveMsg,
+ fd: &dyn AsRawDescriptor,
+ ) -> HandlerResult<u64> {
+ self.lock().unwrap().fs_slave_map(fs, fd)
+ }
+
+ fn fs_slave_unmap(&self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<u64> {
+ self.lock().unwrap().fs_slave_unmap(fs)
+ }
+
+ fn fs_slave_sync(&self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<u64> {
+ self.lock().unwrap().fs_slave_sync(fs)
+ }
+
+ fn fs_slave_io(
+ &self,
+ fs: &VhostUserFSSlaveMsg,
+ fd: &dyn AsRawDescriptor,
+ ) -> HandlerResult<u64> {
+ self.lock().unwrap().fs_slave_io(fs, fd)
+ }
+}
+
+/// The [MasterReqHandler] acts as a server on the master side, to handle service requests from
+/// slaves on the slave communication channel. It's actually a proxy invoking the registered
+/// handler implementing [VhostUserMasterReqHandler] to do the real work.
+///
+/// [MasterReqHandler]: struct.MasterReqHandler.html
+/// [VhostUserMasterReqHandler]: trait.VhostUserMasterReqHandler.html
+///
+/// TODO(b/221882601): we can write a version of this for Windows by switching the socket for a Tube.
+/// The interfaces would need to change so that we fetch a full Tube (which is 2 rds on Windows)
+/// and send it to the device backend (slave) as a message on the master -> slave channel.
+/// (Currently the interface only supports sending a single rd.)
+///
+/// Note that handling requests from slaves is not needed for the initial devices we plan to
+/// support.
+///
+/// Server to handle service requests from slaves from the slave communication channel.
+#[cfg(unix)]
+pub struct MasterReqHandler<S: VhostUserMasterReqHandler> {
+ // underlying Unix domain socket for communication
+ sub_sock: SocketEndpoint<SlaveReq>,
+ tx_sock: SystemStream,
+ // Protocol feature VHOST_USER_PROTOCOL_F_REPLY_ACK has been negotiated.
+ reply_ack_negotiated: bool,
+ // the VirtIO backend device object
+ backend: Arc<S>,
+ // whether the endpoint has encountered any failure
+ error: Option<i32>,
+}
+
+#[cfg(unix)]
+impl<S: VhostUserMasterReqHandler> MasterReqHandler<S> {
+ /// Create a server to handle service requests from slaves on the slave communication channel.
+ ///
+ /// This opens a pair of connected anonymous sockets to form the slave communication channel.
+ /// The socket fd returned by [Self::get_tx_raw_fd()] should be sent to the slave by
+ /// [VhostUserMaster::set_slave_request_fd()].
+ ///
+ /// [Self::get_tx_raw_fd()]: struct.MasterReqHandler.html#method.get_tx_raw_fd
+ /// [VhostUserMaster::set_slave_request_fd()]: trait.VhostUserMaster.html#tymethod.set_slave_request_fd
+ pub fn new(backend: Arc<S>) -> Result<Self> {
+ let (tx, rx) = SystemStream::pair().map_err(Error::SocketError)?;
+
+ Ok(MasterReqHandler {
+ sub_sock: SocketEndpoint::<SlaveReq>::from(rx),
+ tx_sock: tx,
+ reply_ack_negotiated: false,
+ backend,
+ error: None,
+ })
+ }
+
+ /// Get the socket fd for the slave to communication with the master.
+ ///
+ /// The returned fd should be sent to the slave by [VhostUserMaster::set_slave_request_fd()].
+ ///
+ /// [VhostUserMaster::set_slave_request_fd()]: trait.VhostUserMaster.html#tymethod.set_slave_request_fd
+ pub fn get_tx_raw_fd(&self) -> RawDescriptor {
+ self.tx_sock.as_raw_fd()
+ }
+
+ /// Set the negotiation state of the `VHOST_USER_PROTOCOL_F_REPLY_ACK` protocol feature.
+ ///
+ /// When the `VHOST_USER_PROTOCOL_F_REPLY_ACK` protocol feature has been negotiated,
+ /// the "REPLY_ACK" flag will be set in the message header for every slave to master request
+ /// message.
+ pub fn set_reply_ack_flag(&mut self, enable: bool) {
+ self.reply_ack_negotiated = enable;
+ }
+
+ /// Mark endpoint as failed or in normal state.
+ pub fn set_failed(&mut self, error: i32) {
+ if error == 0 {
+ self.error = None;
+ } else {
+ self.error = Some(error);
+ }
+ }
+
+ /// Main entrance to server slave request from the slave communication channel.
+ ///
+ /// The caller needs to:
+ /// - serialize calls to this function
+ /// - decide what to do when errer happens
+ /// - optional recover from failure
+ pub fn handle_request(&mut self) -> Result<u64> {
+ // Return error if the endpoint is already in failed state.
+ self.check_state()?;
+
+ // The underlying communication channel is a Unix domain socket in
+ // stream mode, and recvmsg() is a little tricky here. To successfully
+ // receive attached file descriptors, we need to receive messages and
+ // corresponding attached file descriptors in this way:
+ // . recv messsage header and optional attached file
+ // . validate message header
+ // . recv optional message body and payload according size field in
+ // message header
+ // . validate message body and optional payload
+ let (hdr, files) = self.sub_sock.recv_header()?;
+ self.check_attached_files(&hdr, &files)?;
+ let buf = match hdr.get_size() {
+ 0 => vec![0u8; 0],
+ len => {
+ if len as usize > MAX_MSG_SIZE {
+ return Err(Error::InvalidMessage);
+ }
+ let rbuf = self.sub_sock.recv_data(len as usize)?;
+ if rbuf.len() != len as usize {
+ return Err(Error::InvalidMessage);
+ }
+ rbuf
+ }
+ };
+ let size = buf.len();
+
+ let res = match hdr.get_code() {
+ SlaveReq::CONFIG_CHANGE_MSG => {
+ self.check_msg_size(&hdr, size, 0)?;
+ self.backend
+ .handle_config_change()
+ .map_err(Error::ReqHandlerError)
+ }
+ SlaveReq::FS_MAP => {
+ let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
+ // check_attached_files() has validated files
+ self.backend
+ .fs_slave_map(&msg, &files.unwrap()[0])
+ .map_err(Error::ReqHandlerError)
+ }
+ SlaveReq::FS_UNMAP => {
+ let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
+ self.backend
+ .fs_slave_unmap(&msg)
+ .map_err(Error::ReqHandlerError)
+ }
+ SlaveReq::FS_SYNC => {
+ let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
+ self.backend
+ .fs_slave_sync(&msg)
+ .map_err(Error::ReqHandlerError)
+ }
+ SlaveReq::FS_IO => {
+ let msg = self.extract_msg_body::<VhostUserFSSlaveMsg>(&hdr, size, &buf)?;
+ // check_attached_files() has validated files
+ self.backend
+ .fs_slave_io(&msg, &files.unwrap()[0])
+ .map_err(Error::ReqHandlerError)
+ }
+ _ => Err(Error::InvalidMessage),
+ };
+
+ self.send_ack_message(&hdr, &res)?;
+
+ res
+ }
+
+ fn check_state(&self) -> Result<()> {
+ match self.error {
+ Some(e) => Err(Error::SocketBroken(std::io::Error::from_raw_os_error(e))),
+ None => Ok(()),
+ }
+ }
+
+ fn check_msg_size(
+ &self,
+ hdr: &VhostUserMsgHeader<SlaveReq>,
+ size: usize,
+ expected: usize,
+ ) -> Result<()> {
+ if hdr.get_size() as usize != expected
+ || hdr.is_reply()
+ || hdr.get_version() != 0x1
+ || size != expected
+ {
+ return Err(Error::InvalidMessage);
+ }
+ Ok(())
+ }
+
+ fn check_attached_files(
+ &self,
+ hdr: &VhostUserMsgHeader<SlaveReq>,
+ files: &Option<Vec<File>>,
+ ) -> Result<()> {
+ match hdr.get_code() {
+ SlaveReq::FS_MAP | SlaveReq::FS_IO => {
+ // Expect a single file is passed.
+ match files {
+ Some(files) if files.len() == 1 => Ok(()),
+ _ => Err(Error::InvalidMessage),
+ }
+ }
+ _ if files.is_some() => Err(Error::InvalidMessage),
+ _ => Ok(()),
+ }
+ }
+
+ fn extract_msg_body<T: Sized + VhostUserMsgValidator>(
+ &self,
+ hdr: &VhostUserMsgHeader<SlaveReq>,
+ size: usize,
+ buf: &[u8],
+ ) -> Result<T> {
+ self.check_msg_size(hdr, size, mem::size_of::<T>())?;
+ let msg = unsafe { std::ptr::read_unaligned(buf.as_ptr() as *const T) };
+ if !msg.is_valid() {
+ return Err(Error::InvalidMessage);
+ }
+ Ok(msg)
+ }
+
+ fn new_reply_header<T: Sized>(
+ &self,
+ req: &VhostUserMsgHeader<SlaveReq>,
+ ) -> Result<VhostUserMsgHeader<SlaveReq>> {
+ if mem::size_of::<T>() > MAX_MSG_SIZE {
+ return Err(Error::InvalidParam);
+ }
+ self.check_state()?;
+ Ok(VhostUserMsgHeader::new(
+ req.get_code(),
+ VhostUserHeaderFlag::REPLY.bits(),
+ mem::size_of::<T>() as u32,
+ ))
+ }
+
+ fn send_ack_message(
+ &mut self,
+ req: &VhostUserMsgHeader<SlaveReq>,
+ res: &Result<u64>,
+ ) -> Result<()> {
+ if self.reply_ack_negotiated && req.is_need_reply() {
+ let hdr = self.new_reply_header::<VhostUserU64>(req)?;
+ let def_err = libc::EINVAL;
+ let val = match res {
+ Ok(n) => *n,
+ Err(e) => match &*e {
+ Error::ReqHandlerError(ioerr) => match ioerr.raw_os_error() {
+ Some(rawerr) => -rawerr as u64,
+ None => -def_err as u64,
+ },
+ _ => -def_err as u64,
+ },
+ };
+ let msg = VhostUserU64::new(val);
+ self.sub_sock.send_message(&hdr, &msg, None)?;
+ }
+ Ok(())
+ }
+}
+
+#[cfg(unix)]
+impl<S: VhostUserMasterReqHandler> AsRawDescriptor for MasterReqHandler<S> {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ // TODO(b/221882601): figure out whether this is used for polling. If so, we need theTube's
+ // read notifier here instead.
+ self.sub_sock.as_raw_descriptor()
+ }
+}
+
+#[cfg(unix)]
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use base::{AsRawDescriptor, INVALID_DESCRIPTOR};
+ #[cfg(feature = "device")]
+ use base::{Descriptor, FromRawDescriptor};
+
+ #[cfg(feature = "device")]
+ use crate::SlaveFsCacheReq;
+
+ struct MockMasterReqHandler {}
+
+ impl VhostUserMasterReqHandlerMut for MockMasterReqHandler {
+ /// Handle virtio-fs map file requests from the slave.
+ fn fs_slave_map(
+ &mut self,
+ _fs: &VhostUserFSSlaveMsg,
+ _fd: &dyn AsRawDescriptor,
+ ) -> HandlerResult<u64> {
+ Ok(0)
+ }
+
+ /// Handle virtio-fs unmap file requests from the slave.
+ fn fs_slave_unmap(&mut self, _fs: &VhostUserFSSlaveMsg) -> HandlerResult<u64> {
+ Err(std::io::Error::from_raw_os_error(libc::ENOSYS))
+ }
+ }
+
+ #[test]
+ fn test_new_master_req_handler() {
+ let backend = Arc::new(Mutex::new(MockMasterReqHandler {}));
+ let mut handler = MasterReqHandler::new(backend).unwrap();
+
+ assert!(handler.get_tx_raw_fd() >= 0);
+ assert!(handler.as_raw_descriptor() != INVALID_DESCRIPTOR);
+ handler.check_state().unwrap();
+
+ assert_eq!(handler.error, None);
+ handler.set_failed(libc::EAGAIN);
+ assert_eq!(handler.error, Some(libc::EAGAIN));
+ handler.check_state().unwrap_err();
+ }
+
+ #[cfg(feature = "device")]
+ #[test]
+ fn test_master_slave_req_handler() {
+ let backend = Arc::new(Mutex::new(MockMasterReqHandler {}));
+ let mut handler = MasterReqHandler::new(backend).unwrap();
+
+ let fd = unsafe { libc::dup(handler.get_tx_raw_fd()) };
+ if fd < 0 {
+ panic!("failed to duplicated tx fd!");
+ }
+ let stream = unsafe { SystemStream::from_raw_descriptor(fd) };
+ let fs_cache = SlaveFsCacheReq::from_stream(stream);
+
+ std::thread::spawn(move || {
+ let res = handler.handle_request().unwrap();
+ assert_eq!(res, 0);
+ handler.handle_request().unwrap_err();
+ });
+
+ fs_cache
+ .fs_slave_map(&VhostUserFSSlaveMsg::default(), &Descriptor(fd))
+ .unwrap();
+ // When REPLY_ACK has not been negotiated, the master has no way to detect failure from
+ // slave side.
+ fs_cache
+ .fs_slave_unmap(&VhostUserFSSlaveMsg::default())
+ .unwrap();
+ }
+
+ #[cfg(feature = "device")]
+ #[test]
+ fn test_master_slave_req_handler_with_ack() {
+ let backend = Arc::new(Mutex::new(MockMasterReqHandler {}));
+ let mut handler = MasterReqHandler::new(backend).unwrap();
+ handler.set_reply_ack_flag(true);
+
+ let fd = unsafe { libc::dup(handler.get_tx_raw_fd()) };
+ if fd < 0 {
+ panic!("failed to duplicated tx fd!");
+ }
+ let stream = unsafe { SystemStream::from_raw_descriptor(fd) };
+ let fs_cache = SlaveFsCacheReq::from_stream(stream);
+
+ std::thread::spawn(move || {
+ let res = handler.handle_request().unwrap();
+ assert_eq!(res, 0);
+ handler.handle_request().unwrap_err();
+ });
+
+ fs_cache.set_reply_ack_flag(true);
+ fs_cache
+ .fs_slave_map(&VhostUserFSSlaveMsg::default(), &Descriptor(fd))
+ .unwrap();
+ fs_cache
+ .fs_slave_unmap(&VhostUserFSSlaveMsg::default())
+ .unwrap_err();
+ }
+}
diff --git a/third_party/vmm_vhost/src/message.rs b/third_party/vmm_vhost/src/message.rs
new file mode 100644
index 000000000..701107142
--- /dev/null
+++ b/third_party/vmm_vhost/src/message.rs
@@ -0,0 +1,1272 @@
+// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+//! Define communication messages for the vhost-user protocol.
+//!
+//! For message definition, please refer to the [vhost-user spec](https://github.com/qemu/qemu/blob/f7526eece29cd2e36a63b6703508b24453095eb8/docs/interop/vhost-user.txt).
+
+#![allow(dead_code)]
+#![allow(non_camel_case_types)]
+#![allow(clippy::upper_case_acronyms)]
+
+use std::convert::TryInto;
+use std::fmt::Debug;
+use std::marker::PhantomData;
+
+use bitflags::bitflags;
+use data_model::DataInit;
+
+use crate::VringConfigData;
+
+/// The vhost-user specification uses a field of u32 to store message length.
+/// On the other hand, preallocated buffers are needed to receive messages from the Unix domain
+/// socket. To preallocating a 4GB buffer for each vhost-user message is really just an overhead.
+/// Among all defined vhost-user messages, only the VhostUserConfig and VhostUserMemory has variable
+/// message size. For the VhostUserConfig, a maximum size of 4K is enough because the user
+/// configuration space for virtio devices is (4K - 0x100) bytes at most. For the VhostUserMemory,
+/// 4K should be enough too because it can support 255 memory regions at most.
+pub const MAX_MSG_SIZE: usize = 0x1000;
+
+/// The VhostUserMemory message has variable message size and variable number of attached file
+/// descriptors. Each user memory region entry in the message payload occupies 32 bytes,
+/// so setting maximum number of attached file descriptors based on the maximum message size.
+/// But rust only implements Default and AsMut traits for arrays with 0 - 32 entries, so further
+/// reduce the maximum number...
+// pub const MAX_ATTACHED_FD_ENTRIES: usize = (MAX_MSG_SIZE - 8) / 32;
+pub const MAX_ATTACHED_FD_ENTRIES: usize = 32;
+
+/// Starting position (inclusion) of the device configuration space in virtio devices.
+pub const VHOST_USER_CONFIG_OFFSET: u32 = 0x100;
+
+/// Ending position (exclusion) of the device configuration space in virtio devices.
+pub const VHOST_USER_CONFIG_SIZE: u32 = 0x1000;
+
+/// Maximum number of vrings supported.
+pub const VHOST_USER_MAX_VRINGS: u64 = 0x8000u64;
+
+/// Used for the payload in Vhost Master messages.
+pub trait Req:
+ Clone + Copy + Debug + PartialEq + Eq + PartialOrd + Ord + Into<u32> + Send + Sync
+{
+ /// Is the entity valid.
+ fn is_valid(&self) -> bool;
+}
+
+/// Type of requests sending from masters to slaves.
+#[repr(u32)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum MasterReq {
+ /// Null operation.
+ NOOP = 0,
+ /// Get from the underlying vhost implementation the features bit mask.
+ GET_FEATURES = 1,
+ /// Enable features in the underlying vhost implementation using a bit mask.
+ SET_FEATURES = 2,
+ /// Set the current Master as an owner of the session.
+ SET_OWNER = 3,
+ /// No longer used.
+ RESET_OWNER = 4,
+ /// Set the memory map regions on the slave so it can translate the vring addresses.
+ SET_MEM_TABLE = 5,
+ /// Set logging shared memory space.
+ SET_LOG_BASE = 6,
+ /// Set the logging file descriptor, which is passed as ancillary data.
+ SET_LOG_FD = 7,
+ /// Set the size of the queue.
+ SET_VRING_NUM = 8,
+ /// Set the addresses of the different aspects of the vring.
+ SET_VRING_ADDR = 9,
+ /// Set the base offset in the available vring.
+ SET_VRING_BASE = 10,
+ /// Get the available vring base offset.
+ GET_VRING_BASE = 11,
+ /// Set the event file descriptor for adding buffers to the vring.
+ SET_VRING_KICK = 12,
+ /// Set the event file descriptor to signal when buffers are used.
+ SET_VRING_CALL = 13,
+ /// Set the event file descriptor to signal when error occurs.
+ SET_VRING_ERR = 14,
+ /// Get the protocol feature bit mask from the underlying vhost implementation.
+ GET_PROTOCOL_FEATURES = 15,
+ /// Enable protocol features in the underlying vhost implementation.
+ SET_PROTOCOL_FEATURES = 16,
+ /// Query how many queues the backend supports.
+ GET_QUEUE_NUM = 17,
+ /// Signal slave to enable or disable corresponding vring.
+ SET_VRING_ENABLE = 18,
+ /// Ask vhost user backend to broadcast a fake RARP to notify the migration is terminated
+ /// for guest that does not support GUEST_ANNOUNCE.
+ SEND_RARP = 19,
+ /// Set host MTU value exposed to the guest.
+ NET_SET_MTU = 20,
+ /// Set the socket file descriptor for slave initiated requests.
+ SET_SLAVE_REQ_FD = 21,
+ /// Send IOTLB messages with struct vhost_iotlb_msg as payload.
+ IOTLB_MSG = 22,
+ /// Set the endianness of a VQ for legacy devices.
+ SET_VRING_ENDIAN = 23,
+ /// Fetch the contents of the virtio device configuration space.
+ GET_CONFIG = 24,
+ /// Change the contents of the virtio device configuration space.
+ SET_CONFIG = 25,
+ /// Create a session for crypto operation.
+ CREATE_CRYPTO_SESSION = 26,
+ /// Close a session for crypto operation.
+ CLOSE_CRYPTO_SESSION = 27,
+ /// Advise slave that a migration with postcopy enabled is underway.
+ POSTCOPY_ADVISE = 28,
+ /// Advise slave that a transition to postcopy mode has happened.
+ POSTCOPY_LISTEN = 29,
+ /// Advise that postcopy migration has now completed.
+ POSTCOPY_END = 30,
+ /// Get a shared buffer from slave.
+ GET_INFLIGHT_FD = 31,
+ /// Send the shared inflight buffer back to slave.
+ SET_INFLIGHT_FD = 32,
+ /// Sets the GPU protocol socket file descriptor.
+ GPU_SET_SOCKET = 33,
+ /// Ask the vhost user backend to disable all rings and reset all internal
+ /// device state to the initial state.
+ RESET_DEVICE = 34,
+ /// Indicate that a buffer was added to the vring instead of signalling it
+ /// using the vring’s kick file descriptor.
+ VRING_KICK = 35,
+ /// Return a u64 payload containing the maximum number of memory slots.
+ GET_MAX_MEM_SLOTS = 36,
+ /// Update the memory tables by adding the region described.
+ ADD_MEM_REG = 37,
+ /// Update the memory tables by removing the region described.
+ REM_MEM_REG = 38,
+ /// Notify the backend with updated device status as defined in the VIRTIO
+ /// specification.
+ SET_STATUS = 39,
+ /// Query the backend for its device status as defined in the VIRTIO
+ /// specification.
+ GET_STATUS = 40,
+ /// Upper bound of valid commands.
+ MAX_CMD = 41,
+}
+
+impl From<MasterReq> for u32 {
+ fn from(req: MasterReq) -> u32 {
+ req as u32
+ }
+}
+
+impl Req for MasterReq {
+ fn is_valid(&self) -> bool {
+ (*self > MasterReq::NOOP) && (*self < MasterReq::MAX_CMD)
+ }
+}
+
+/// Type of requests sending from slaves to masters.
+#[repr(u32)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub enum SlaveReq {
+ /// Null operation.
+ NOOP = 0,
+ /// Send IOTLB messages with struct vhost_iotlb_msg as payload.
+ IOTLB_MSG = 1,
+ /// Notify that the virtio device's configuration space has changed.
+ CONFIG_CHANGE_MSG = 2,
+ /// Set host notifier for a specified queue.
+ VRING_HOST_NOTIFIER_MSG = 3,
+ /// Indicate that a buffer was used from the vring.
+ VRING_CALL = 4,
+ /// Indicate that an error occurred on the specific vring.
+ VRING_ERR = 5,
+ /// Virtio-fs draft: map file content into the window.
+ FS_MAP = 6,
+ /// Virtio-fs draft: unmap file content from the window.
+ FS_UNMAP = 7,
+ /// Virtio-fs draft: sync file content.
+ FS_SYNC = 8,
+ /// Virtio-fs draft: perform a read/write from an fd directly to GPA.
+ FS_IO = 9,
+ /// Upper bound of valid commands.
+ MAX_CMD = 10,
+}
+
+impl From<SlaveReq> for u32 {
+ fn from(req: SlaveReq) -> u32 {
+ req as u32
+ }
+}
+
+impl Req for SlaveReq {
+ fn is_valid(&self) -> bool {
+ (*self > SlaveReq::NOOP) && (*self < SlaveReq::MAX_CMD)
+ }
+}
+
+/// Vhost message Validator.
+pub trait VhostUserMsgValidator {
+ /// Validate message syntax only.
+ /// It doesn't validate message semantics such as protocol version number and dependency
+ /// on feature flags etc.
+ fn is_valid(&self) -> bool {
+ true
+ }
+}
+
+// Bit mask for common message flags.
+bitflags! {
+ /// Common message flags for vhost-user requests and replies.
+ pub struct VhostUserHeaderFlag: u32 {
+ /// Bits[0..2] is message version number.
+ const VERSION = 0x3;
+ /// Mark message as reply.
+ const REPLY = 0x4;
+ /// Sender anticipates a reply message from the peer.
+ const NEED_REPLY = 0x8;
+ /// All valid bits.
+ const ALL_FLAGS = 0xc;
+ /// All reserved bits.
+ const RESERVED_BITS = !0xf;
+ }
+}
+
+/// Common message header for vhost-user requests and replies.
+/// A vhost-user message consists of 3 header fields and an optional payload. All numbers are in the
+/// machine native byte order.
+#[repr(packed)]
+#[derive(Copy)]
+pub struct VhostUserMsgHeader<R: Req> {
+ request: u32,
+ flags: u32,
+ size: u32,
+ _r: PhantomData<R>,
+}
+// Safe because it only has data and has no implicit padding.
+unsafe impl<R: Req> DataInit for VhostUserMsgHeader<R> {}
+
+impl<R: Req> Debug for VhostUserMsgHeader<R> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("Point")
+ .field("request", &{ self.request })
+ .field("flags", &{ self.flags })
+ .field("size", &{ self.size })
+ .finish()
+ }
+}
+
+impl<R: Req> Clone for VhostUserMsgHeader<R> {
+ fn clone(&self) -> VhostUserMsgHeader<R> {
+ *self
+ }
+}
+
+impl<R: Req> PartialEq for VhostUserMsgHeader<R> {
+ fn eq(&self, other: &Self) -> bool {
+ self.request == other.request && self.flags == other.flags && self.size == other.size
+ }
+}
+
+impl<R: Req> VhostUserMsgHeader<R> {
+ /// Create a new instance of `VhostUserMsgHeader`.
+ pub fn new(request: R, flags: u32, size: u32) -> Self {
+ // Default to protocol version 1
+ let fl = (flags & VhostUserHeaderFlag::ALL_FLAGS.bits()) | 0x1;
+ VhostUserMsgHeader {
+ request: request.into(),
+ flags: fl,
+ size,
+ _r: PhantomData,
+ }
+ }
+
+ /// Get message type.
+ pub fn get_code(&self) -> R {
+ // It's safe because R is marked as repr(u32).
+ unsafe { std::mem::transmute_copy::<u32, R>(&{ self.request }) }
+ }
+
+ /// Set message type.
+ pub fn set_code(&mut self, request: R) {
+ self.request = request.into();
+ }
+
+ /// Get message version number.
+ pub fn get_version(&self) -> u32 {
+ self.flags & 0x3
+ }
+
+ /// Set message version number.
+ pub fn set_version(&mut self, ver: u32) {
+ self.flags &= !0x3;
+ self.flags |= ver & 0x3;
+ }
+
+ /// Check whether it's a reply message.
+ pub fn is_reply(&self) -> bool {
+ (self.flags & VhostUserHeaderFlag::REPLY.bits()) != 0
+ }
+
+ /// Mark message as reply.
+ pub fn set_reply(&mut self, is_reply: bool) {
+ if is_reply {
+ self.flags |= VhostUserHeaderFlag::REPLY.bits();
+ } else {
+ self.flags &= !VhostUserHeaderFlag::REPLY.bits();
+ }
+ }
+
+ /// Check whether reply for this message is requested.
+ pub fn is_need_reply(&self) -> bool {
+ (self.flags & VhostUserHeaderFlag::NEED_REPLY.bits()) != 0
+ }
+
+ /// Mark that reply for this message is needed.
+ pub fn set_need_reply(&mut self, need_reply: bool) {
+ if need_reply {
+ self.flags |= VhostUserHeaderFlag::NEED_REPLY.bits();
+ } else {
+ self.flags &= !VhostUserHeaderFlag::NEED_REPLY.bits();
+ }
+ }
+
+ /// Check whether it's the reply message for the request `req`.
+ pub fn is_reply_for(&self, req: &VhostUserMsgHeader<R>) -> bool {
+ self.is_reply() && !req.is_reply() && self.get_code() == req.get_code()
+ }
+
+ /// Get message size.
+ pub fn get_size(&self) -> u32 {
+ self.size
+ }
+
+ /// Set message size.
+ pub fn set_size(&mut self, size: u32) {
+ self.size = size;
+ }
+}
+
+impl<R: Req> Default for VhostUserMsgHeader<R> {
+ fn default() -> Self {
+ VhostUserMsgHeader {
+ request: 0,
+ flags: 0x1,
+ size: 0,
+ _r: PhantomData,
+ }
+ }
+}
+
+impl From<[u8; 12]> for VhostUserMsgHeader<MasterReq> {
+ fn from(buf: [u8; 12]) -> Self {
+ // Convert 4-length slice into [u8; 4]. This must succeed.
+ let req = u32::from_le_bytes(buf[0..4].try_into().unwrap());
+ // Safe because `MasterReq` is defined with `#[repr(u32)]`.
+ let req = unsafe { std::mem::transmute_copy::<u32, MasterReq>(&req) };
+
+ let flags = u32::from_le_bytes(buf[4..8].try_into().unwrap());
+ let size = u32::from_le_bytes(buf[8..12].try_into().unwrap());
+ Self::new(req, flags, size)
+ }
+}
+
+impl<T: Req> VhostUserMsgValidator for VhostUserMsgHeader<T> {
+ #[allow(clippy::if_same_then_else)]
+ fn is_valid(&self) -> bool {
+ if !self.get_code().is_valid() {
+ return false;
+ } else if self.size as usize > MAX_MSG_SIZE {
+ return false;
+ } else if self.get_version() != 0x1 {
+ return false;
+ } else if (self.flags & VhostUserHeaderFlag::RESERVED_BITS.bits()) != 0 {
+ return false;
+ }
+ true
+ }
+}
+
+// Bit mask for transport specific flags in VirtIO feature set defined by vhost-user.
+bitflags! {
+ /// Transport specific flags in VirtIO feature set defined by vhost-user.
+ pub struct VhostUserVirtioFeatures: u64 {
+ /// Feature flag for the protocol feature.
+ const PROTOCOL_FEATURES = 0x4000_0000;
+ }
+}
+
+// Bit mask for vhost-user protocol feature flags.
+bitflags! {
+ /// Vhost-user protocol feature flags.
+ pub struct VhostUserProtocolFeatures: u64 {
+ /// Support multiple queues.
+ const MQ = 0x0000_0001;
+ /// Support logging through shared memory fd.
+ const LOG_SHMFD = 0x0000_0002;
+ /// Support broadcasting fake RARP packet.
+ const RARP = 0x0000_0004;
+ /// Support sending reply messages for requests with NEED_REPLY flag set.
+ const REPLY_ACK = 0x0000_0008;
+ /// Support setting MTU for virtio-net devices.
+ const MTU = 0x0000_0010;
+ /// Allow the slave to send requests to the master by an optional communication channel.
+ const SLAVE_REQ = 0x0000_0020;
+ /// Support setting slave endian by SET_VRING_ENDIAN.
+ const CROSS_ENDIAN = 0x0000_0040;
+ /// Support crypto operations.
+ const CRYPTO_SESSION = 0x0000_0080;
+ /// Support sending userfault_fd from slaves to masters.
+ const PAGEFAULT = 0x0000_0100;
+ /// Support Virtio device configuration.
+ const CONFIG = 0x0000_0200;
+ /// Allow the slave to send fds (at most 8 descriptors in each message) to the master.
+ const SLAVE_SEND_FD = 0x0000_0400;
+ /// Allow the slave to register a host notifier.
+ const HOST_NOTIFIER = 0x0000_0800;
+ /// Support inflight shmfd.
+ const INFLIGHT_SHMFD = 0x0000_1000;
+ /// Support resetting the device.
+ const RESET_DEVICE = 0x0000_2000;
+ /// Support inband notifications.
+ const INBAND_NOTIFICATIONS = 0x0000_4000;
+ /// Support configuring memory slots.
+ const CONFIGURE_MEM_SLOTS = 0x0000_8000;
+ /// Support reporting status.
+ const STATUS = 0x0001_0000;
+ }
+}
+
+/// A generic message to encapsulate a 64-bit value.
+#[repr(packed)]
+#[derive(Default, Clone, Copy)]
+pub struct VhostUserU64 {
+ /// The encapsulated 64-bit common value.
+ pub value: u64,
+}
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VhostUserU64 {}
+
+impl VhostUserU64 {
+ /// Create a new instance.
+ pub fn new(value: u64) -> Self {
+ VhostUserU64 { value }
+ }
+}
+
+impl VhostUserMsgValidator for VhostUserU64 {}
+
+/// Memory region descriptor for the SET_MEM_TABLE request.
+#[repr(packed)]
+#[derive(Default, Clone, Copy)]
+pub struct VhostUserMemory {
+ /// Number of memory regions in the payload.
+ pub num_regions: u32,
+ /// Padding for alignment.
+ pub padding1: u32,
+}
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VhostUserMemory {}
+
+impl VhostUserMemory {
+ /// Create a new instance.
+ pub fn new(cnt: u32) -> Self {
+ VhostUserMemory {
+ num_regions: cnt,
+ padding1: 0,
+ }
+ }
+}
+
+impl VhostUserMsgValidator for VhostUserMemory {
+ #[allow(clippy::if_same_then_else)]
+ fn is_valid(&self) -> bool {
+ if self.padding1 != 0 {
+ return false;
+ } else if self.num_regions == 0 || self.num_regions > MAX_ATTACHED_FD_ENTRIES as u32 {
+ return false;
+ }
+ true
+ }
+}
+
+/// Memory region descriptors as payload for the SET_MEM_TABLE request.
+#[repr(packed)]
+#[derive(Default, Clone, Copy)]
+pub struct VhostUserMemoryRegion {
+ /// Guest physical address of the memory region.
+ pub guest_phys_addr: u64,
+ /// Size of the memory region.
+ pub memory_size: u64,
+ /// Virtual address in the current process.
+ pub user_addr: u64,
+ /// Offset where region starts in the mapped memory.
+ pub mmap_offset: u64,
+}
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VhostUserMemoryRegion {}
+
+impl VhostUserMemoryRegion {
+ /// Create a new instance.
+ pub fn new(guest_phys_addr: u64, memory_size: u64, user_addr: u64, mmap_offset: u64) -> Self {
+ VhostUserMemoryRegion {
+ guest_phys_addr,
+ memory_size,
+ user_addr,
+ mmap_offset,
+ }
+ }
+}
+
+impl VhostUserMsgValidator for VhostUserMemoryRegion {
+ fn is_valid(&self) -> bool {
+ if self.memory_size == 0
+ || self.guest_phys_addr.checked_add(self.memory_size).is_none()
+ || self.user_addr.checked_add(self.memory_size).is_none()
+ || self.mmap_offset.checked_add(self.memory_size).is_none()
+ {
+ return false;
+ }
+ true
+ }
+}
+
+/// Payload of the VhostUserMemory message.
+pub type VhostUserMemoryPayload = Vec<VhostUserMemoryRegion>;
+
+/// Single memory region descriptor as payload for ADD_MEM_REG and REM_MEM_REG
+/// requests.
+#[repr(C)]
+#[derive(Default, Clone, Copy)]
+pub struct VhostUserSingleMemoryRegion {
+ /// Padding for correct alignment
+ padding: u64,
+ /// Guest physical address of the memory region.
+ pub guest_phys_addr: u64,
+ /// Size of the memory region.
+ pub memory_size: u64,
+ /// Virtual address in the current process.
+ pub user_addr: u64,
+ /// Offset where region starts in the mapped memory.
+ pub mmap_offset: u64,
+}
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VhostUserSingleMemoryRegion {}
+
+impl VhostUserSingleMemoryRegion {
+ /// Create a new instance.
+ pub fn new(guest_phys_addr: u64, memory_size: u64, user_addr: u64, mmap_offset: u64) -> Self {
+ VhostUserSingleMemoryRegion {
+ padding: 0,
+ guest_phys_addr,
+ memory_size,
+ user_addr,
+ mmap_offset,
+ }
+ }
+}
+
+impl VhostUserMsgValidator for VhostUserSingleMemoryRegion {
+ fn is_valid(&self) -> bool {
+ if self.memory_size == 0
+ || self.guest_phys_addr.checked_add(self.memory_size).is_none()
+ || self.user_addr.checked_add(self.memory_size).is_none()
+ || self.mmap_offset.checked_add(self.memory_size).is_none()
+ {
+ return false;
+ }
+ true
+ }
+}
+
+/// Vring state descriptor.
+#[repr(packed)]
+#[derive(Default, Clone, Copy)]
+pub struct VhostUserVringState {
+ /// Vring index.
+ pub index: u32,
+ /// A common 32bit value to encapsulate vring state etc.
+ pub num: u32,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VhostUserVringState {}
+
+impl VhostUserVringState {
+ /// Create a new instance.
+ pub fn new(index: u32, num: u32) -> Self {
+ VhostUserVringState { index, num }
+ }
+}
+
+impl VhostUserMsgValidator for VhostUserVringState {}
+
+// Bit mask for vring address flags.
+bitflags! {
+ /// Flags for vring address.
+ pub struct VhostUserVringAddrFlags: u32 {
+ /// Support log of vring operations.
+ /// Modifications to "used" vring should be logged.
+ const VHOST_VRING_F_LOG = 0x1;
+ }
+}
+
+/// Vring address descriptor.
+#[repr(packed)]
+#[derive(Default, Clone, Copy)]
+pub struct VhostUserVringAddr {
+ /// Vring index.
+ pub index: u32,
+ /// Vring flags defined by VhostUserVringAddrFlags.
+ pub flags: u32,
+ /// Ring address of the vring descriptor table.
+ pub descriptor: u64,
+ /// Ring address of the vring used ring.
+ pub used: u64,
+ /// Ring address of the vring available ring.
+ pub available: u64,
+ /// Guest address for logging.
+ pub log: u64,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VhostUserVringAddr {}
+
+impl VhostUserVringAddr {
+ /// Create a new instance.
+ pub fn new(
+ index: u32,
+ flags: VhostUserVringAddrFlags,
+ descriptor: u64,
+ used: u64,
+ available: u64,
+ log: u64,
+ ) -> Self {
+ VhostUserVringAddr {
+ index,
+ flags: flags.bits(),
+ descriptor,
+ used,
+ available,
+ log,
+ }
+ }
+
+ /// Create a new instance from `VringConfigData`.
+ #[cfg_attr(feature = "cargo-clippy", allow(clippy::identity_conversion))]
+ pub fn from_config_data(index: u32, config_data: &VringConfigData) -> Self {
+ let log_addr = config_data.log_addr.unwrap_or(0);
+ VhostUserVringAddr {
+ index,
+ flags: config_data.flags,
+ descriptor: config_data.desc_table_addr,
+ used: config_data.used_ring_addr,
+ available: config_data.avail_ring_addr,
+ log: log_addr,
+ }
+ }
+}
+
+impl VhostUserMsgValidator for VhostUserVringAddr {
+ #[allow(clippy::if_same_then_else)]
+ fn is_valid(&self) -> bool {
+ if (self.flags & !VhostUserVringAddrFlags::all().bits()) != 0 {
+ return false;
+ } else if self.descriptor & 0xf != 0 {
+ return false;
+ } else if self.available & 0x1 != 0 {
+ return false;
+ } else if self.used & 0x3 != 0 {
+ return false;
+ }
+ true
+ }
+}
+
+// Bit mask for the vhost-user device configuration message.
+bitflags! {
+ /// Flags for the device configuration message.
+ pub struct VhostUserConfigFlags: u32 {
+ /// Vhost master messages used for writeable fields.
+ const WRITABLE = 0x1;
+ /// Vhost master messages used for live migration.
+ const LIVE_MIGRATION = 0x2;
+ }
+}
+
+/// Message to read/write device configuration space.
+#[repr(packed)]
+#[derive(Default, Clone, Copy)]
+pub struct VhostUserConfig {
+ /// Offset of virtio device's configuration space.
+ pub offset: u32,
+ /// Configuration space access size in bytes.
+ pub size: u32,
+ /// Flags for the device configuration operation.
+ pub flags: u32,
+}
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VhostUserConfig {}
+
+impl VhostUserConfig {
+ /// Create a new instance.
+ pub fn new(offset: u32, size: u32, flags: VhostUserConfigFlags) -> Self {
+ VhostUserConfig {
+ offset,
+ size,
+ flags: flags.bits(),
+ }
+ }
+}
+
+impl VhostUserMsgValidator for VhostUserConfig {
+ #[allow(clippy::if_same_then_else)]
+ fn is_valid(&self) -> bool {
+ let end_addr = match self.size.checked_add(self.offset) {
+ Some(addr) => addr,
+ None => return false,
+ };
+ if (self.flags & !VhostUserConfigFlags::all().bits()) != 0 {
+ return false;
+ } else if self.size == 0 || end_addr > VHOST_USER_CONFIG_SIZE {
+ return false;
+ }
+ true
+ }
+}
+
+/// Payload for the VhostUserConfig message.
+pub type VhostUserConfigPayload = Vec<u8>;
+
+/// Single memory region descriptor as payload for ADD_MEM_REG and REM_MEM_REG
+/// requests.
+#[repr(C)]
+#[derive(Default, Clone, Copy)]
+pub struct VhostUserInflight {
+ /// Size of the area to track inflight I/O.
+ pub mmap_size: u64,
+ /// Offset of this area from the start of the supplied file descriptor.
+ pub mmap_offset: u64,
+ /// Number of virtqueues.
+ pub num_queues: u16,
+ /// Size of virtqueues.
+ pub queue_size: u16,
+}
+
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VhostUserInflight {}
+
+impl VhostUserInflight {
+ /// Create a new instance.
+ pub fn new(mmap_size: u64, mmap_offset: u64, num_queues: u16, queue_size: u16) -> Self {
+ VhostUserInflight {
+ mmap_size,
+ mmap_offset,
+ num_queues,
+ queue_size,
+ }
+ }
+}
+
+impl VhostUserMsgValidator for VhostUserInflight {
+ fn is_valid(&self) -> bool {
+ if self.num_queues == 0 || self.queue_size == 0 {
+ return false;
+ }
+ true
+ }
+}
+
+/*
+ * TODO: support dirty log, live migration and IOTLB operations.
+#[repr(packed)]
+pub struct VhostUserVringArea {
+ pub index: u32,
+ pub flags: u32,
+ pub size: u64,
+ pub offset: u64,
+}
+
+#[repr(packed)]
+pub struct VhostUserLog {
+ pub size: u64,
+ pub offset: u64,
+}
+
+#[repr(packed)]
+pub struct VhostUserIotlb {
+ pub iova: u64,
+ pub size: u64,
+ pub user_addr: u64,
+ pub permission: u8,
+ pub optype: u8,
+}
+*/
+
+// Bit mask for flags in virtio-fs slave messages
+bitflags! {
+ #[derive(Default)]
+ /// Flags for virtio-fs slave messages.
+ pub struct VhostUserFSSlaveMsgFlags: u64 {
+ /// Empty permission.
+ const EMPTY = 0x0;
+ /// Read permission.
+ const MAP_R = 0x1;
+ /// Write permission.
+ const MAP_W = 0x2;
+ }
+}
+
+/// Max entries in one virtio-fs slave request.
+pub const VHOST_USER_FS_SLAVE_ENTRIES: usize = 8;
+
+/// Slave request message to update the MMIO window.
+#[repr(packed)]
+#[derive(Default, Copy, Clone)]
+pub struct VhostUserFSSlaveMsg {
+ /// File offset.
+ pub fd_offset: [u64; VHOST_USER_FS_SLAVE_ENTRIES],
+ /// Offset into the DAX window.
+ pub cache_offset: [u64; VHOST_USER_FS_SLAVE_ENTRIES],
+ /// Size of region to map.
+ pub len: [u64; VHOST_USER_FS_SLAVE_ENTRIES],
+ /// Flags for the mmap operation
+ pub flags: [VhostUserFSSlaveMsgFlags; VHOST_USER_FS_SLAVE_ENTRIES],
+}
+// Safe because it only has data and has no implicit padding.
+unsafe impl DataInit for VhostUserFSSlaveMsg {}
+
+impl VhostUserMsgValidator for VhostUserFSSlaveMsg {
+ fn is_valid(&self) -> bool {
+ for i in 0..VHOST_USER_FS_SLAVE_ENTRIES {
+ if ({ self.flags[i] }.bits() & !VhostUserFSSlaveMsgFlags::all().bits()) != 0
+ || self.fd_offset[i].checked_add(self.len[i]).is_none()
+ || self.cache_offset[i].checked_add(self.len[i]).is_none()
+ {
+ return false;
+ }
+ }
+ true
+ }
+}
+
+/// Inflight I/O descriptor state for split virtqueues
+#[repr(packed)]
+#[derive(Clone, Copy, Default)]
+pub struct DescStateSplit {
+ /// Indicate whether this descriptor (only head) is inflight or not.
+ pub inflight: u8,
+ /// Padding
+ padding: [u8; 5],
+ /// List of last batch of used descriptors, only when batching is used for submitting
+ pub next: u16,
+ /// Preserve order of fetching available descriptors, only for head descriptor
+ pub counter: u64,
+}
+
+impl DescStateSplit {
+ /// New instance of DescStateSplit struct
+ pub fn new() -> Self {
+ Self::default()
+ }
+}
+
+/// Inflight I/O queue region for split virtqueues
+#[repr(packed)]
+pub struct QueueRegionSplit {
+ /// Features flags of this region
+ pub features: u64,
+ /// Version of this region
+ pub version: u16,
+ /// Number of DescStateSplit entries
+ pub desc_num: u16,
+ /// List to track last batch of used descriptors
+ pub last_batch_head: u16,
+ /// Idx value of used ring
+ pub used_idx: u16,
+ /// Pointer to an array of DescStateSplit entries
+ pub desc: u64,
+}
+
+impl QueueRegionSplit {
+ /// New instance of QueueRegionSplit struct
+ pub fn new(features: u64, queue_size: u16) -> Self {
+ QueueRegionSplit {
+ features,
+ version: 1,
+ desc_num: queue_size,
+ last_batch_head: 0,
+ used_idx: 0,
+ desc: 0,
+ }
+ }
+}
+
+/// Inflight I/O descriptor state for packed virtqueues
+#[repr(packed)]
+#[derive(Clone, Copy, Default)]
+pub struct DescStatePacked {
+ /// Indicate whether this descriptor (only head) is inflight or not.
+ pub inflight: u8,
+ /// Padding
+ padding: u8,
+ /// Link to next free entry
+ pub next: u16,
+ /// Link to last entry of descriptor list, only for head
+ pub last: u16,
+ /// Length of descriptor list, only for head
+ pub num: u16,
+ /// Preserve order of fetching avail descriptors, only for head
+ pub counter: u64,
+ /// Buffer ID
+ pub id: u16,
+ /// Descriptor flags
+ pub flags: u16,
+ /// Buffer length
+ pub len: u32,
+ /// Buffer address
+ pub addr: u64,
+}
+
+impl DescStatePacked {
+ /// New instance of DescStatePacked struct
+ pub fn new() -> Self {
+ Self::default()
+ }
+}
+
+/// Inflight I/O queue region for packed virtqueues
+#[repr(packed)]
+pub struct QueueRegionPacked {
+ /// Features flags of this region
+ pub features: u64,
+ /// version of this region
+ pub version: u16,
+ /// size of descriptor state array
+ pub desc_num: u16,
+ /// head of free DescStatePacked entry list
+ pub free_head: u16,
+ /// old head of free DescStatePacked entry list
+ pub old_free_head: u16,
+ /// used idx of descriptor ring
+ pub used_idx: u16,
+ /// old used idx of descriptor ring
+ pub old_used_idx: u16,
+ /// device ring wrap counter
+ pub used_wrap_counter: u8,
+ /// old device ring wrap counter
+ pub old_used_wrap_counter: u8,
+ /// Padding
+ padding: [u8; 7],
+ /// Pointer to array tracking state of each descriptor from descriptor ring
+ pub desc: u64,
+}
+
+impl QueueRegionPacked {
+ /// New instance of QueueRegionPacked struct
+ pub fn new(features: u64, queue_size: u16) -> Self {
+ QueueRegionPacked {
+ features,
+ version: 1,
+ desc_num: queue_size,
+ free_head: 0,
+ old_free_head: 0,
+ used_idx: 0,
+ old_used_idx: 0,
+ used_wrap_counter: 0,
+ old_used_wrap_counter: 0,
+ padding: [0; 7],
+ desc: 0,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::mem;
+
+ #[test]
+ fn check_master_request_code() {
+ let code = MasterReq::NOOP;
+ assert!(!code.is_valid());
+ let code = MasterReq::MAX_CMD;
+ assert!(!code.is_valid());
+ assert!(code > MasterReq::NOOP);
+ let code = MasterReq::GET_FEATURES;
+ assert!(code.is_valid());
+ assert_eq!(code, code.clone());
+ let code: MasterReq = unsafe { std::mem::transmute::<u32, MasterReq>(10000u32) };
+ assert!(!code.is_valid());
+ }
+
+ #[test]
+ fn check_slave_request_code() {
+ let code = SlaveReq::NOOP;
+ assert!(!code.is_valid());
+ let code = SlaveReq::MAX_CMD;
+ assert!(!code.is_valid());
+ assert!(code > SlaveReq::NOOP);
+ let code = SlaveReq::CONFIG_CHANGE_MSG;
+ assert!(code.is_valid());
+ assert_eq!(code, code.clone());
+ let code: SlaveReq = unsafe { std::mem::transmute::<u32, SlaveReq>(10000u32) };
+ assert!(!code.is_valid());
+ }
+
+ #[test]
+ fn msg_header_ops() {
+ let mut hdr = VhostUserMsgHeader::new(MasterReq::GET_FEATURES, 0, 0x100);
+ assert_eq!(hdr.get_code(), MasterReq::GET_FEATURES);
+ hdr.set_code(MasterReq::SET_FEATURES);
+ assert_eq!(hdr.get_code(), MasterReq::SET_FEATURES);
+
+ assert_eq!(hdr.get_version(), 0x1);
+
+ assert!(!hdr.is_reply());
+ hdr.set_reply(true);
+ assert!(hdr.is_reply());
+ hdr.set_reply(false);
+
+ assert!(!hdr.is_need_reply());
+ hdr.set_need_reply(true);
+ assert!(hdr.is_need_reply());
+ hdr.set_need_reply(false);
+
+ assert_eq!(hdr.get_size(), 0x100);
+ hdr.set_size(0x200);
+ assert_eq!(hdr.get_size(), 0x200);
+
+ assert!(!hdr.is_need_reply());
+ assert!(!hdr.is_reply());
+ assert_eq!(hdr.get_version(), 0x1);
+
+ // Check message length
+ assert!(hdr.is_valid());
+ hdr.set_size(0x2000);
+ assert!(!hdr.is_valid());
+ hdr.set_size(0x100);
+ assert_eq!(hdr.get_size(), 0x100);
+ assert!(hdr.is_valid());
+ hdr.set_size((MAX_MSG_SIZE - mem::size_of::<VhostUserMsgHeader<MasterReq>>()) as u32);
+ assert!(hdr.is_valid());
+ hdr.set_size(0x0);
+ assert!(hdr.is_valid());
+
+ // Check version
+ hdr.set_version(0x0);
+ assert!(!hdr.is_valid());
+ hdr.set_version(0x2);
+ assert!(!hdr.is_valid());
+ hdr.set_version(0x1);
+ assert!(hdr.is_valid());
+
+ // Test Debug, Clone, PartiaEq trait
+ assert_eq!(hdr, hdr.clone());
+ assert_eq!(hdr.clone().get_code(), hdr.get_code());
+ assert_eq!(format!("{:?}", hdr.clone()), format!("{:?}", hdr));
+ }
+
+ #[test]
+ fn test_vhost_user_message_u64() {
+ let val = VhostUserU64::default();
+ let val1 = VhostUserU64::new(0);
+
+ let a = val.value;
+ let b = val1.value;
+ assert_eq!(a, b);
+ let a = VhostUserU64::new(1).value;
+ assert_eq!(a, 1);
+ }
+
+ #[test]
+ fn check_user_memory() {
+ let mut msg = VhostUserMemory::new(1);
+ assert!(msg.is_valid());
+ msg.num_regions = MAX_ATTACHED_FD_ENTRIES as u32;
+ assert!(msg.is_valid());
+
+ msg.num_regions += 1;
+ assert!(!msg.is_valid());
+ msg.num_regions = 0xFFFFFFFF;
+ assert!(!msg.is_valid());
+ msg.num_regions = MAX_ATTACHED_FD_ENTRIES as u32;
+ msg.padding1 = 1;
+ assert!(!msg.is_valid());
+ }
+
+ #[test]
+ fn check_user_memory_region() {
+ let mut msg = VhostUserMemoryRegion {
+ guest_phys_addr: 0,
+ memory_size: 0x1000,
+ user_addr: 0,
+ mmap_offset: 0,
+ };
+ assert!(msg.is_valid());
+ msg.guest_phys_addr = 0xFFFFFFFFFFFFEFFF;
+ assert!(msg.is_valid());
+ msg.guest_phys_addr = 0xFFFFFFFFFFFFF000;
+ assert!(!msg.is_valid());
+ msg.guest_phys_addr = 0xFFFFFFFFFFFF0000;
+ msg.memory_size = 0;
+ assert!(!msg.is_valid());
+ let a = msg.guest_phys_addr;
+ let b = msg.guest_phys_addr;
+ assert_eq!(a, b);
+
+ let msg = VhostUserMemoryRegion::default();
+ let a = msg.guest_phys_addr;
+ assert_eq!(a, 0);
+ let a = msg.memory_size;
+ assert_eq!(a, 0);
+ let a = msg.user_addr;
+ assert_eq!(a, 0);
+ let a = msg.mmap_offset;
+ assert_eq!(a, 0);
+ }
+
+ #[test]
+ fn test_vhost_user_state() {
+ let state = VhostUserVringState::new(5, 8);
+
+ let a = state.index;
+ assert_eq!(a, 5);
+ let a = state.num;
+ assert_eq!(a, 8);
+ assert!(state.is_valid());
+
+ let state = VhostUserVringState::default();
+ let a = state.index;
+ assert_eq!(a, 0);
+ let a = state.num;
+ assert_eq!(a, 0);
+ assert!(state.is_valid());
+ }
+
+ #[test]
+ fn test_vhost_user_addr() {
+ let mut addr = VhostUserVringAddr::new(
+ 2,
+ VhostUserVringAddrFlags::VHOST_VRING_F_LOG,
+ 0x1000,
+ 0x2000,
+ 0x3000,
+ 0x4000,
+ );
+
+ let a = addr.index;
+ assert_eq!(a, 2);
+ let a = addr.flags;
+ assert_eq!(a, VhostUserVringAddrFlags::VHOST_VRING_F_LOG.bits());
+ let a = addr.descriptor;
+ assert_eq!(a, 0x1000);
+ let a = addr.used;
+ assert_eq!(a, 0x2000);
+ let a = addr.available;
+ assert_eq!(a, 0x3000);
+ let a = addr.log;
+ assert_eq!(a, 0x4000);
+ assert!(addr.is_valid());
+
+ addr.descriptor = 0x1001;
+ assert!(!addr.is_valid());
+ addr.descriptor = 0x1000;
+
+ addr.available = 0x3001;
+ assert!(!addr.is_valid());
+ addr.available = 0x3000;
+
+ addr.used = 0x2001;
+ assert!(!addr.is_valid());
+ addr.used = 0x2000;
+ assert!(addr.is_valid());
+ }
+
+ #[test]
+ fn test_vhost_user_state_from_config() {
+ let config = VringConfigData {
+ queue_max_size: 256,
+ queue_size: 128,
+ flags: VhostUserVringAddrFlags::VHOST_VRING_F_LOG.bits,
+ desc_table_addr: 0x1000,
+ used_ring_addr: 0x2000,
+ avail_ring_addr: 0x3000,
+ log_addr: Some(0x4000),
+ };
+ let addr = VhostUserVringAddr::from_config_data(2, &config);
+
+ let a = addr.index;
+ assert_eq!(a, 2);
+ let a = addr.flags;
+ assert_eq!(a, VhostUserVringAddrFlags::VHOST_VRING_F_LOG.bits());
+ let a = addr.descriptor;
+ assert_eq!(a, 0x1000);
+ let a = addr.used;
+ assert_eq!(a, 0x2000);
+ let a = addr.available;
+ assert_eq!(a, 0x3000);
+ let a = addr.log;
+ assert_eq!(a, 0x4000);
+ assert!(addr.is_valid());
+ }
+
+ #[test]
+ fn check_user_vring_addr() {
+ let mut msg =
+ VhostUserVringAddr::new(0, VhostUserVringAddrFlags::all(), 0x0, 0x0, 0x0, 0x0);
+ assert!(msg.is_valid());
+
+ msg.descriptor = 1;
+ assert!(!msg.is_valid());
+ msg.descriptor = 0;
+
+ msg.available = 1;
+ assert!(!msg.is_valid());
+ msg.available = 0;
+
+ msg.used = 1;
+ assert!(!msg.is_valid());
+ msg.used = 0;
+
+ msg.flags |= 0x80000000;
+ assert!(!msg.is_valid());
+ msg.flags &= !0x80000000;
+ }
+
+ #[test]
+ fn check_user_config_msg() {
+ let mut msg =
+ VhostUserConfig::new(0, VHOST_USER_CONFIG_SIZE, VhostUserConfigFlags::WRITABLE);
+
+ assert!(msg.is_valid());
+ msg.size = 0;
+ assert!(!msg.is_valid());
+ msg.size = 1;
+ assert!(msg.is_valid());
+ msg.offset = u32::MAX;
+ assert!(!msg.is_valid());
+ msg.offset = VHOST_USER_CONFIG_SIZE;
+ assert!(!msg.is_valid());
+ msg.offset = VHOST_USER_CONFIG_SIZE - 1;
+ assert!(msg.is_valid());
+ msg.size = 2;
+ assert!(!msg.is_valid());
+ msg.size = 1;
+ msg.flags |= VhostUserConfigFlags::LIVE_MIGRATION.bits();
+ assert!(msg.is_valid());
+ msg.flags |= 0x4;
+ assert!(!msg.is_valid());
+ }
+
+ #[test]
+ fn test_vhost_user_fs_slave() {
+ let mut fs_slave = VhostUserFSSlaveMsg::default();
+
+ assert!(fs_slave.is_valid());
+
+ fs_slave.fd_offset[0] = 0xffff_ffff_ffff_ffff;
+ fs_slave.len[0] = 0x1;
+ assert!(!fs_slave.is_valid());
+
+ assert_ne!(
+ VhostUserFSSlaveMsgFlags::MAP_R,
+ VhostUserFSSlaveMsgFlags::MAP_W
+ );
+ assert_eq!(VhostUserFSSlaveMsgFlags::EMPTY.bits(), 0);
+ }
+}
diff --git a/third_party/vmm_vhost/src/slave.rs b/third_party/vmm_vhost/src/slave.rs
new file mode 100644
index 000000000..3fd141217
--- /dev/null
+++ b/third_party/vmm_vhost/src/slave.rs
@@ -0,0 +1,90 @@
+// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+//! Traits and Structs for vhost-user slave.
+//!
+//! These are used on platforms where the slave has to listen for connections (e.g. POSIX only).
+
+use std::sync::Arc;
+
+use super::connection::{Endpoint, Listener};
+use super::message::*;
+use super::{Result, SlaveReqHandler, VhostUserSlaveReqHandler};
+
+/// Vhost-user slave side connection listener.
+pub struct SlaveListener<E: Endpoint<MasterReq>, S: VhostUserSlaveReqHandler> {
+ listener: E::Listener,
+ backend: Option<Arc<S>>,
+}
+
+/// Sets up a listener for incoming master connections, and handles construction
+/// of a Slave on success.
+impl<E: Endpoint<MasterReq>, S: VhostUserSlaveReqHandler> SlaveListener<E, S> {
+ /// Create a unix domain socket for incoming master connections.
+ pub fn new(listener: E::Listener, backend: Arc<S>) -> Result<Self> {
+ Ok(SlaveListener {
+ listener,
+ backend: Some(backend),
+ })
+ }
+
+ /// Accept an incoming connection from the master, returning Some(Slave) on
+ /// success, or None if the socket is nonblocking and no incoming connection
+ /// was detected
+ pub fn accept(&mut self) -> Result<Option<SlaveReqHandler<S, E>>> {
+ if let Some(fd) = self.listener.accept()? {
+ return Ok(Some(SlaveReqHandler::new(
+ E::from_connection(fd),
+ self.backend.take().unwrap(),
+ )));
+ }
+ Ok(None)
+ }
+
+ /// Change blocking status on the listener.
+ pub fn set_nonblocking(&self, block: bool) -> Result<()> {
+ self.listener.set_nonblocking(block)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::sync::Mutex;
+
+ use super::*;
+ use crate::connection::socket::{Endpoint, Listener};
+ use crate::dummy_slave::DummySlaveReqHandler;
+
+ #[test]
+ fn test_slave_listener_set_nonblocking() {
+ let backend = Arc::new(Mutex::new(DummySlaveReqHandler::new()));
+ let listener =
+ Listener::new("/tmp/vhost_user_lib_unit_test_slave_nonblocking", true).unwrap();
+ let slave_listener = SlaveListener::<Endpoint<_>, _>::new(listener, backend).unwrap();
+
+ slave_listener.set_nonblocking(true).unwrap();
+ slave_listener.set_nonblocking(false).unwrap();
+ slave_listener.set_nonblocking(false).unwrap();
+ slave_listener.set_nonblocking(true).unwrap();
+ slave_listener.set_nonblocking(true).unwrap();
+ }
+
+ #[cfg(feature = "vmm")]
+ #[test]
+ fn test_slave_listener_accept() {
+ use crate::connection::socket::Endpoint as SocketEndpoint;
+ use crate::Master;
+
+ let path = "/tmp/vhost_user_lib_unit_test_slave_accept";
+ let backend = Arc::new(Mutex::new(DummySlaveReqHandler::new()));
+ let listener = Listener::new(path, true).unwrap();
+ let mut slave_listener = SlaveListener::<Endpoint<_>, _>::new(listener, backend).unwrap();
+
+ slave_listener.set_nonblocking(true).unwrap();
+ assert!(slave_listener.accept().unwrap().is_none());
+ assert!(slave_listener.accept().unwrap().is_none());
+
+ let _master = Master::<SocketEndpoint<_>>::connect(path, 1).unwrap();
+ let _slave = slave_listener.accept().unwrap().unwrap();
+ }
+}
diff --git a/third_party/vmm_vhost/src/slave_fs_cache.rs b/third_party/vmm_vhost/src/slave_fs_cache.rs
new file mode 100644
index 000000000..3b5c64ada
--- /dev/null
+++ b/third_party/vmm_vhost/src/slave_fs_cache.rs
@@ -0,0 +1,221 @@
+// Copyright (C) 2020 Alibaba Cloud. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::{SlaveFsCacheReqSocket, SystemStream};
+use base::{AsRawDescriptor, RawDescriptor};
+use std::io;
+use std::mem;
+use std::sync::{Arc, Mutex, MutexGuard};
+
+use super::connection::EndpointExt;
+use super::message::*;
+use super::{Error, HandlerResult, Result, VhostUserMasterReqHandler};
+
+struct SlaveFsCacheReqInternal {
+ sock: SlaveFsCacheReqSocket,
+
+ // Protocol feature VHOST_USER_PROTOCOL_F_REPLY_ACK has been negotiated.
+ reply_ack_negotiated: bool,
+
+ // whether the endpoint has encountered any failure
+ error: Option<i32>,
+}
+
+impl SlaveFsCacheReqInternal {
+ fn check_state(&self) -> Result<u64> {
+ match self.error {
+ Some(e) => Err(Error::SocketBroken(std::io::Error::from_raw_os_error(e))),
+ None => Ok(0),
+ }
+ }
+
+ fn send_message(
+ &mut self,
+ request: SlaveReq,
+ fs: &VhostUserFSSlaveMsg,
+ fds: Option<&[RawDescriptor]>,
+ ) -> Result<u64> {
+ self.check_state()?;
+
+ let len = mem::size_of::<VhostUserFSSlaveMsg>();
+ let mut hdr = VhostUserMsgHeader::new(request, 0, len as u32);
+ if self.reply_ack_negotiated {
+ hdr.set_need_reply(true);
+ }
+ self.sock.send_message(&hdr, fs, fds)?;
+
+ self.wait_for_ack(&hdr)
+ }
+
+ fn wait_for_ack(&mut self, hdr: &VhostUserMsgHeader<SlaveReq>) -> Result<u64> {
+ self.check_state()?;
+ if !self.reply_ack_negotiated {
+ return Ok(0);
+ }
+
+ let (reply, body, rfds) = self.sock.recv_body::<VhostUserU64>()?;
+ if !reply.is_reply_for(hdr) || rfds.is_some() || !body.is_valid() {
+ return Err(Error::InvalidMessage);
+ }
+ if body.value != 0 {
+ return Err(Error::MasterInternalError);
+ }
+
+ Ok(body.value)
+ }
+}
+
+/// Request proxy to send vhost-user-fs slave requests to the master through the slave
+/// communication channel.
+///
+/// The [SlaveFsCacheReq] acts as a message proxy to forward vhost-user-fs slave requests to the
+/// master through the vhost-user slave communication channel. The forwarded messages will be
+/// handled by the [MasterReqHandler] server.
+///
+/// [SlaveFsCacheReq]: struct.SlaveFsCacheReq.html
+/// [MasterReqHandler]: struct.MasterReqHandler.html
+#[derive(Clone)]
+pub struct SlaveFsCacheReq {
+ // underlying socket for communication
+ node: Arc<Mutex<SlaveFsCacheReqInternal>>,
+}
+
+impl SlaveFsCacheReq {
+ fn new(ep: SlaveFsCacheReqSocket) -> Self {
+ SlaveFsCacheReq {
+ node: Arc::new(Mutex::new(SlaveFsCacheReqInternal {
+ sock: ep,
+ reply_ack_negotiated: false,
+ error: None,
+ })),
+ }
+ }
+
+ fn node(&self) -> MutexGuard<SlaveFsCacheReqInternal> {
+ self.node.lock().unwrap()
+ }
+
+ fn send_message(
+ &self,
+ request: SlaveReq,
+ fs: &VhostUserFSSlaveMsg,
+ fds: Option<&[RawDescriptor]>,
+ ) -> io::Result<u64> {
+ self.node()
+ .send_message(request, fs, fds)
+ .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{}", e)))
+ }
+
+ /// Create a new instance from a `SystemStream` object.
+ pub fn from_stream(sock: SystemStream) -> Self {
+ Self::new(SlaveFsCacheReqSocket::from(sock))
+ }
+
+ /// Set the negotiation state of the `VHOST_USER_PROTOCOL_F_REPLY_ACK` protocol feature.
+ ///
+ /// When the `VHOST_USER_PROTOCOL_F_REPLY_ACK` protocol feature has been negotiated,
+ /// the "REPLY_ACK" flag will be set in the message header for every slave to master request
+ /// message.
+ pub fn set_reply_ack_flag(&self, enable: bool) {
+ self.node().reply_ack_negotiated = enable;
+ }
+
+ /// Mark endpoint as failed with specified error code.
+ pub fn set_failed(&self, error: i32) {
+ self.node().error = Some(error);
+ }
+}
+
+impl VhostUserMasterReqHandler for SlaveFsCacheReq {
+ /// Forward vhost-user-fs map file requests to the slave.
+ fn fs_slave_map(
+ &self,
+ fs: &VhostUserFSSlaveMsg,
+ fd: &dyn AsRawDescriptor,
+ ) -> HandlerResult<u64> {
+ self.send_message(SlaveReq::FS_MAP, fs, Some(&[fd.as_raw_descriptor()]))
+ }
+
+ /// Forward vhost-user-fs unmap file requests to the master.
+ fn fs_slave_unmap(&self, fs: &VhostUserFSSlaveMsg) -> HandlerResult<u64> {
+ self.send_message(SlaveReq::FS_UNMAP, fs, None)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+ use crate::connection::Endpoint;
+
+ #[test]
+ fn test_slave_fs_cache_req_set_failed() {
+ let (p1, _p2) = SystemStream::pair().unwrap();
+ let fs_cache = SlaveFsCacheReq::from_stream(p1);
+
+ assert!(fs_cache.node().error.is_none());
+ fs_cache.set_failed(libc::EAGAIN);
+ assert_eq!(fs_cache.node().error, Some(libc::EAGAIN));
+ }
+
+ #[test]
+ fn test_slave_fs_cache_send_failure() {
+ let (p1, p2) = SystemStream::pair().unwrap();
+ let fs_cache = SlaveFsCacheReq::from_stream(p1);
+
+ fs_cache.set_failed(libc::ECONNRESET);
+ fs_cache
+ .fs_slave_map(&VhostUserFSSlaveMsg::default(), &p2)
+ .unwrap_err();
+ fs_cache
+ .fs_slave_unmap(&VhostUserFSSlaveMsg::default())
+ .unwrap_err();
+ fs_cache.node().error = None;
+ }
+
+ #[test]
+ fn test_slave_fs_cache_recv_negative() {
+ let (p1, p2) = SystemStream::pair().unwrap();
+ let fs_cache = SlaveFsCacheReq::from_stream(p1);
+ let mut master = SlaveFsCacheReqSocket::from_connection(p2);
+
+ let len = mem::size_of::<VhostUserFSSlaveMsg>();
+ let mut hdr = VhostUserMsgHeader::new(
+ SlaveReq::FS_MAP,
+ VhostUserHeaderFlag::REPLY.bits(),
+ len as u32,
+ );
+ let body = VhostUserU64::new(0);
+
+ master
+ .send_message(&hdr, &body, Some(&[master.as_raw_descriptor()]))
+ .unwrap();
+ fs_cache
+ .fs_slave_map(&VhostUserFSSlaveMsg::default(), &master)
+ .unwrap();
+
+ fs_cache.set_reply_ack_flag(true);
+ fs_cache
+ .fs_slave_map(&VhostUserFSSlaveMsg::default(), &master)
+ .unwrap_err();
+
+ hdr.set_code(SlaveReq::FS_UNMAP);
+ master.send_message(&hdr, &body, None).unwrap();
+ fs_cache
+ .fs_slave_map(&VhostUserFSSlaveMsg::default(), &master)
+ .unwrap_err();
+ hdr.set_code(SlaveReq::FS_MAP);
+
+ let body = VhostUserU64::new(1);
+ master.send_message(&hdr, &body, None).unwrap();
+ fs_cache
+ .fs_slave_map(&VhostUserFSSlaveMsg::default(), &master)
+ .unwrap_err();
+
+ let body = VhostUserU64::new(0);
+ master.send_message(&hdr, &body, None).unwrap();
+ fs_cache
+ .fs_slave_map(&VhostUserFSSlaveMsg::default(), &master)
+ .unwrap();
+ }
+}
diff --git a/third_party/vmm_vhost/src/slave_req_handler.rs b/third_party/vmm_vhost/src/slave_req_handler.rs
new file mode 100644
index 000000000..eec39836a
--- /dev/null
+++ b/third_party/vmm_vhost/src/slave_req_handler.rs
@@ -0,0 +1,957 @@
+// Copyright (C) 2019 Alibaba Cloud Computing. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+use base::{AsRawDescriptor, RawDescriptor};
+use std::fs::File;
+use std::mem;
+use std::slice;
+use std::sync::{Arc, Mutex};
+
+use data_model::DataInit;
+
+use super::connection::{Endpoint, EndpointExt};
+use super::message::*;
+use super::{take_single_file, Error, Result};
+use crate::{MasterReqEndpoint, SystemStream};
+
+#[derive(PartialEq, Eq, Debug)]
+/// Vhost-user protocol variants used for the communication.
+pub enum Protocol {
+ /// Use the regular vhost-user protocol.
+ Regular,
+ /// Use the virtio-vhost-user protocol, which is proxied through virtqueues.
+ /// The protocol is mostly same as the vhost-user protocol but no file transfer is allowed.
+ Virtio,
+}
+
+/// Services provided to the master by the slave with interior mutability.
+///
+/// The [VhostUserSlaveReqHandler] trait defines the services provided to the master by the slave.
+/// And the [VhostUserSlaveReqHandlerMut] trait is a helper mirroring [VhostUserSlaveReqHandler],
+/// but without interior mutability.
+/// The vhost-user specification defines a master communication channel, by which masters could
+/// request services from slaves. The [VhostUserSlaveReqHandler] trait defines services provided by
+/// slaves, and it's used both on the master side and slave side.
+///
+/// - on the master side, a stub forwarder implementing [VhostUserSlaveReqHandler] will proxy
+/// service requests to slaves.
+/// - on the slave side, the [SlaveReqHandler] will forward service requests to a handler
+/// implementing [VhostUserSlaveReqHandler].
+///
+/// The [VhostUserSlaveReqHandler] trait is design with interior mutability to improve performance
+/// for multi-threading.
+///
+/// [VhostUserSlaveReqHandler]: trait.VhostUserSlaveReqHandler.html
+/// [VhostUserSlaveReqHandlerMut]: trait.VhostUserSlaveReqHandlerMut.html
+/// [SlaveReqHandler]: struct.SlaveReqHandler.html
+#[allow(missing_docs)]
+pub trait VhostUserSlaveReqHandler {
+ /// Returns the type of vhost-user protocol that the handler support.
+ fn protocol(&self) -> Protocol;
+
+ fn set_owner(&self) -> Result<()>;
+ fn reset_owner(&self) -> Result<()>;
+ fn get_features(&self) -> Result<u64>;
+ fn set_features(&self, features: u64) -> Result<()>;
+ fn set_mem_table(&self, ctx: &[VhostUserMemoryRegion], files: Vec<File>) -> Result<()>;
+ fn set_vring_num(&self, index: u32, num: u32) -> Result<()>;
+ fn set_vring_addr(
+ &self,
+ index: u32,
+ flags: VhostUserVringAddrFlags,
+ descriptor: u64,
+ used: u64,
+ available: u64,
+ log: u64,
+ ) -> Result<()>;
+ fn set_vring_base(&self, index: u32, base: u32) -> Result<()>;
+ fn get_vring_base(&self, index: u32) -> Result<VhostUserVringState>;
+ fn set_vring_kick(&self, index: u8, fd: Option<File>) -> Result<()>;
+ fn set_vring_call(&self, index: u8, fd: Option<File>) -> Result<()>;
+ fn set_vring_err(&self, index: u8, fd: Option<File>) -> Result<()>;
+
+ fn get_protocol_features(&self) -> Result<VhostUserProtocolFeatures>;
+ fn set_protocol_features(&self, features: u64) -> Result<()>;
+ fn get_queue_num(&self) -> Result<u64>;
+ fn set_vring_enable(&self, index: u32, enable: bool) -> Result<()>;
+ fn get_config(&self, offset: u32, size: u32, flags: VhostUserConfigFlags) -> Result<Vec<u8>>;
+ fn set_config(&self, offset: u32, buf: &[u8], flags: VhostUserConfigFlags) -> Result<()>;
+ fn set_slave_req_fd(&self, _vu_req: File) {}
+ fn get_inflight_fd(&self, inflight: &VhostUserInflight) -> Result<(VhostUserInflight, File)>;
+ fn set_inflight_fd(&self, inflight: &VhostUserInflight, file: File) -> Result<()>;
+ fn get_max_mem_slots(&self) -> Result<u64>;
+ fn add_mem_region(&self, region: &VhostUserSingleMemoryRegion, fd: File) -> Result<()>;
+ fn remove_mem_region(&self, region: &VhostUserSingleMemoryRegion) -> Result<()>;
+}
+
+/// Services provided to the master by the slave without interior mutability.
+///
+/// This is a helper trait mirroring the [VhostUserSlaveReqHandler] trait.
+#[allow(missing_docs)]
+pub trait VhostUserSlaveReqHandlerMut {
+ /// Returns the type of vhost-user protocol that the handler support.
+ fn protocol(&self) -> Protocol;
+
+ fn set_owner(&mut self) -> Result<()>;
+ fn reset_owner(&mut self) -> Result<()>;
+ fn get_features(&mut self) -> Result<u64>;
+ fn set_features(&mut self, features: u64) -> Result<()>;
+ fn set_mem_table(&mut self, ctx: &[VhostUserMemoryRegion], files: Vec<File>) -> Result<()>;
+ fn set_vring_num(&mut self, index: u32, num: u32) -> Result<()>;
+ fn set_vring_addr(
+ &mut self,
+ index: u32,
+ flags: VhostUserVringAddrFlags,
+ descriptor: u64,
+ used: u64,
+ available: u64,
+ log: u64,
+ ) -> Result<()>;
+ fn set_vring_base(&mut self, index: u32, base: u32) -> Result<()>;
+ fn get_vring_base(&mut self, index: u32) -> Result<VhostUserVringState>;
+ fn set_vring_kick(&mut self, index: u8, fd: Option<File>) -> Result<()>;
+ fn set_vring_call(&mut self, index: u8, fd: Option<File>) -> Result<()>;
+ fn set_vring_err(&mut self, index: u8, fd: Option<File>) -> Result<()>;
+
+ fn get_protocol_features(&mut self) -> Result<VhostUserProtocolFeatures>;
+ fn set_protocol_features(&mut self, features: u64) -> Result<()>;
+ fn get_queue_num(&mut self) -> Result<u64>;
+ fn set_vring_enable(&mut self, index: u32, enable: bool) -> Result<()>;
+ fn get_config(
+ &mut self,
+ offset: u32,
+ size: u32,
+ flags: VhostUserConfigFlags,
+ ) -> Result<Vec<u8>>;
+ fn set_config(&mut self, offset: u32, buf: &[u8], flags: VhostUserConfigFlags) -> Result<()>;
+ fn set_slave_req_fd(&mut self, _vu_req: File) {}
+ fn get_inflight_fd(
+ &mut self,
+ inflight: &VhostUserInflight,
+ ) -> Result<(VhostUserInflight, File)>;
+ fn set_inflight_fd(&mut self, inflight: &VhostUserInflight, file: File) -> Result<()>;
+ fn get_max_mem_slots(&mut self) -> Result<u64>;
+ fn add_mem_region(&mut self, region: &VhostUserSingleMemoryRegion, fd: File) -> Result<()>;
+ fn remove_mem_region(&mut self, region: &VhostUserSingleMemoryRegion) -> Result<()>;
+}
+
+impl<T: VhostUserSlaveReqHandlerMut> VhostUserSlaveReqHandler for Mutex<T> {
+ fn protocol(&self) -> Protocol {
+ self.lock().unwrap().protocol()
+ }
+
+ fn set_owner(&self) -> Result<()> {
+ self.lock().unwrap().set_owner()
+ }
+
+ fn reset_owner(&self) -> Result<()> {
+ self.lock().unwrap().reset_owner()
+ }
+
+ fn get_features(&self) -> Result<u64> {
+ self.lock().unwrap().get_features()
+ }
+
+ fn set_features(&self, features: u64) -> Result<()> {
+ self.lock().unwrap().set_features(features)
+ }
+
+ fn set_mem_table(&self, ctx: &[VhostUserMemoryRegion], files: Vec<File>) -> Result<()> {
+ self.lock().unwrap().set_mem_table(ctx, files)
+ }
+
+ fn set_vring_num(&self, index: u32, num: u32) -> Result<()> {
+ self.lock().unwrap().set_vring_num(index, num)
+ }
+
+ fn set_vring_addr(
+ &self,
+ index: u32,
+ flags: VhostUserVringAddrFlags,
+ descriptor: u64,
+ used: u64,
+ available: u64,
+ log: u64,
+ ) -> Result<()> {
+ self.lock()
+ .unwrap()
+ .set_vring_addr(index, flags, descriptor, used, available, log)
+ }
+
+ fn set_vring_base(&self, index: u32, base: u32) -> Result<()> {
+ self.lock().unwrap().set_vring_base(index, base)
+ }
+
+ fn get_vring_base(&self, index: u32) -> Result<VhostUserVringState> {
+ self.lock().unwrap().get_vring_base(index)
+ }
+
+ fn set_vring_kick(&self, index: u8, fd: Option<File>) -> Result<()> {
+ self.lock().unwrap().set_vring_kick(index, fd)
+ }
+
+ fn set_vring_call(&self, index: u8, fd: Option<File>) -> Result<()> {
+ self.lock().unwrap().set_vring_call(index, fd)
+ }
+
+ fn set_vring_err(&self, index: u8, fd: Option<File>) -> Result<()> {
+ self.lock().unwrap().set_vring_err(index, fd)
+ }
+
+ fn get_protocol_features(&self) -> Result<VhostUserProtocolFeatures> {
+ self.lock().unwrap().get_protocol_features()
+ }
+
+ fn set_protocol_features(&self, features: u64) -> Result<()> {
+ self.lock().unwrap().set_protocol_features(features)
+ }
+
+ fn get_queue_num(&self) -> Result<u64> {
+ self.lock().unwrap().get_queue_num()
+ }
+
+ fn set_vring_enable(&self, index: u32, enable: bool) -> Result<()> {
+ self.lock().unwrap().set_vring_enable(index, enable)
+ }
+
+ fn get_config(&self, offset: u32, size: u32, flags: VhostUserConfigFlags) -> Result<Vec<u8>> {
+ self.lock().unwrap().get_config(offset, size, flags)
+ }
+
+ fn set_config(&self, offset: u32, buf: &[u8], flags: VhostUserConfigFlags) -> Result<()> {
+ self.lock().unwrap().set_config(offset, buf, flags)
+ }
+
+ fn set_slave_req_fd(&self, vu_req: File) {
+ self.lock().unwrap().set_slave_req_fd(vu_req)
+ }
+
+ fn get_inflight_fd(&self, inflight: &VhostUserInflight) -> Result<(VhostUserInflight, File)> {
+ self.lock().unwrap().get_inflight_fd(inflight)
+ }
+
+ fn set_inflight_fd(&self, inflight: &VhostUserInflight, file: File) -> Result<()> {
+ self.lock().unwrap().set_inflight_fd(inflight, file)
+ }
+
+ fn get_max_mem_slots(&self) -> Result<u64> {
+ self.lock().unwrap().get_max_mem_slots()
+ }
+
+ fn add_mem_region(&self, region: &VhostUserSingleMemoryRegion, fd: File) -> Result<()> {
+ self.lock().unwrap().add_mem_region(region, fd)
+ }
+
+ fn remove_mem_region(&self, region: &VhostUserSingleMemoryRegion) -> Result<()> {
+ self.lock().unwrap().remove_mem_region(region)
+ }
+}
+
+/// Abstracts |Endpoint| related operations for vhost-user slave implementations.
+pub struct SlaveReqHelper<E: Endpoint<MasterReq>> {
+ /// Underlying endpoint for communication.
+ endpoint: E,
+
+ /// Protocol used for the communication.
+ protocol: Protocol,
+
+ /// Sending ack for messages without payload.
+ reply_ack_enabled: bool,
+}
+
+impl<E: Endpoint<MasterReq>> SlaveReqHelper<E> {
+ /// Creates a new |SlaveReqHelper| instance with an |Endpoint| underneath it.
+ pub fn new(endpoint: E, protocol: Protocol) -> Self {
+ SlaveReqHelper {
+ endpoint,
+ protocol,
+ reply_ack_enabled: false,
+ }
+ }
+
+ fn new_reply_header<T: Sized>(
+ &self,
+ req: &VhostUserMsgHeader<MasterReq>,
+ payload_size: usize,
+ ) -> Result<VhostUserMsgHeader<MasterReq>> {
+ if mem::size_of::<T>() > MAX_MSG_SIZE
+ || payload_size > MAX_MSG_SIZE
+ || mem::size_of::<T>() + payload_size > MAX_MSG_SIZE
+ {
+ return Err(Error::InvalidParam);
+ }
+
+ Ok(VhostUserMsgHeader::new(
+ req.get_code(),
+ VhostUserHeaderFlag::REPLY.bits(),
+ (mem::size_of::<T>() + payload_size) as u32,
+ ))
+ }
+
+ /// Sends reply back to Vhost Master in response to a message.
+ pub fn send_ack_message(
+ &mut self,
+ req: &VhostUserMsgHeader<MasterReq>,
+ success: bool,
+ ) -> Result<()> {
+ if self.reply_ack_enabled && req.is_need_reply() {
+ let hdr = self.new_reply_header::<VhostUserU64>(req, 0)?;
+ let val = if success { 0 } else { 1 };
+ let msg = VhostUserU64::new(val);
+ self.endpoint.send_message(&hdr, &msg, None)?;
+ }
+ Ok(())
+ }
+
+ fn send_reply_message<T: Sized + DataInit>(
+ &mut self,
+ req: &VhostUserMsgHeader<MasterReq>,
+ msg: &T,
+ ) -> Result<()> {
+ let hdr = self.new_reply_header::<T>(req, 0)?;
+ self.endpoint.send_message(&hdr, msg, None)?;
+ Ok(())
+ }
+
+ fn send_reply_with_payload<T: Sized + DataInit>(
+ &mut self,
+ req: &VhostUserMsgHeader<MasterReq>,
+ msg: &T,
+ payload: &[u8],
+ ) -> Result<()> {
+ let hdr = self.new_reply_header::<T>(req, payload.len())?;
+ self.endpoint
+ .send_message_with_payload(&hdr, msg, payload, None)?;
+ Ok(())
+ }
+
+ /// Parses an incoming |SET_VRING_KICK| or |SET_VRING_CALL| message into a
+ /// Vring number and an fd.
+ pub fn handle_vring_fd_request(
+ &mut self,
+ buf: &[u8],
+ files: Option<Vec<File>>,
+ ) -> Result<(u8, Option<File>)> {
+ if buf.len() > MAX_MSG_SIZE || buf.len() < mem::size_of::<VhostUserU64>() {
+ return Err(Error::InvalidMessage);
+ }
+ let msg = unsafe { std::ptr::read_unaligned(buf.as_ptr() as *const VhostUserU64) };
+ if !msg.is_valid() {
+ return Err(Error::InvalidMessage);
+ }
+
+ // Virtio-vhost-user protocol doesn't send FDs.
+ if self.protocol == Protocol::Virtio {
+ return Ok((msg.value as u8, None));
+ }
+
+ // Bits (0-7) of the payload contain the vring index. Bit 8 is the
+ // invalid FD flag (VHOST_USER_VRING_NOFD_MASK).
+ // This bit is set when there is no file descriptor
+ // in the ancillary data. This signals that polling will be used
+ // instead of waiting for the call.
+ // If Bit 8 is unset, the data must contain a file descriptor.
+ let has_fd = (msg.value & 0x100u64) == 0;
+
+ let file = take_single_file(files);
+
+ if has_fd && file.is_none() || !has_fd && file.is_some() {
+ return Err(Error::InvalidMessage);
+ }
+
+ Ok((msg.value as u8, file))
+ }
+}
+
+impl<E: Endpoint<MasterReq>> AsRef<E> for SlaveReqHelper<E> {
+ fn as_ref(&self) -> &E {
+ &self.endpoint
+ }
+}
+
+impl<E: Endpoint<MasterReq>> AsMut<E> for SlaveReqHelper<E> {
+ fn as_mut(&mut self) -> &mut E {
+ &mut self.endpoint
+ }
+}
+
+impl<E: Endpoint<MasterReq> + AsRawDescriptor> AsRawDescriptor for SlaveReqHelper<E> {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ self.endpoint.as_raw_descriptor()
+ }
+}
+
+/// Server to handle service requests from masters from the master communication channel.
+///
+/// The [SlaveReqHandler] acts as a server on the slave side, to handle service requests from
+/// masters on the master communication channel. It's actually a proxy invoking the registered
+/// handler implementing [VhostUserSlaveReqHandler] to do the real work.
+///
+/// The lifetime of the SlaveReqHandler object should be the same as the underline Unix Domain
+/// Socket, so it gets simpler to recover from disconnect.
+///
+/// [VhostUserSlaveReqHandler]: trait.VhostUserSlaveReqHandler.html
+/// [SlaveReqHandler]: struct.SlaveReqHandler.html
+pub struct SlaveReqHandler<S: VhostUserSlaveReqHandler, E: Endpoint<MasterReq>> {
+ slave_req_helper: SlaveReqHelper<E>,
+ // the vhost-user backend device object
+ backend: Arc<S>,
+
+ virtio_features: u64,
+ acked_virtio_features: u64,
+ protocol_features: VhostUserProtocolFeatures,
+ acked_protocol_features: u64,
+
+ // whether the endpoint has encountered any failure
+ error: Option<i32>,
+}
+
+impl<S: VhostUserSlaveReqHandler> SlaveReqHandler<S, MasterReqEndpoint> {
+ /// Create a vhost-user slave endpoint from a connected socket.
+ pub fn from_stream(socket: SystemStream, backend: Arc<S>) -> Self {
+ Self::new(MasterReqEndpoint::from(socket), backend)
+ }
+}
+
+impl<S: VhostUserSlaveReqHandler, E: Endpoint<MasterReq>> SlaveReqHandler<S, E> {
+ /// Create a vhost-user slave endpoint.
+ pub(super) fn new(endpoint: E, backend: Arc<S>) -> Self {
+ SlaveReqHandler {
+ slave_req_helper: SlaveReqHelper::new(endpoint, backend.protocol()),
+ backend,
+ virtio_features: 0,
+ acked_virtio_features: 0,
+ protocol_features: VhostUserProtocolFeatures::empty(),
+ acked_protocol_features: 0,
+ error: None,
+ }
+ }
+
+ /// Create a new vhost-user slave endpoint.
+ ///
+ /// # Arguments
+ /// * - `path` - path of Unix domain socket listener to connect to
+ /// * - `backend` - handler for requests from the master to the slave
+ pub fn connect(path: &str, backend: Arc<S>) -> Result<Self> {
+ Ok(Self::new(Endpoint::<MasterReq>::connect(path)?, backend))
+ }
+
+ /// Mark endpoint as failed with specified error code.
+ pub fn set_failed(&mut self, error: i32) {
+ self.error = Some(error);
+ }
+
+ /// Main entrance to request from the communication channel.
+ ///
+ /// Receive and handle one incoming request message from the vmm. The caller needs to:
+ /// - serialize calls to this function
+ /// - decide what to do when error happens
+ /// - optional recover from failure
+ ///
+ /// # Return:
+ /// * - `Ok(())`: one request was successfully handled.
+ /// * - `Err(ClientExit)`: the vmm closed the connection properly. This isn't an actual failure.
+ /// * - `Err(Disconnect)`: the connection was closed unexpectedly.
+ /// * - `Err(InvalidMessage)`: the vmm sent a illegal message.
+ /// * - other errors: failed to handle a request.
+ pub fn handle_request(&mut self) -> Result<()> {
+ // Return error if the endpoint is already in failed state.
+ self.check_state()?;
+
+ // The underlying communication channel is a Unix domain socket in
+ // stream mode, and recvmsg() is a little tricky here. To successfully
+ // receive attached file descriptors, we need to receive messages and
+ // corresponding attached file descriptors in this way:
+ // . recv messsage header and optional attached file
+ // . validate message header
+ // . recv optional message body and payload according size field in
+ // message header
+ // . validate message body and optional payload
+ let (hdr, files) = match self.slave_req_helper.endpoint.recv_header() {
+ Ok((hdr, files)) => (hdr, files),
+ Err(Error::Disconnect) => {
+ // If the client closed the connection before sending a header, this should be
+ // handled as a legal exit.
+ return Err(Error::ClientExit);
+ }
+ Err(e) => {
+ return Err(e);
+ }
+ };
+
+ self.check_attached_files(&hdr, &files)?;
+
+ let buf = match hdr.get_size() {
+ 0 => vec![0u8; 0],
+ len => {
+ let rbuf = self.slave_req_helper.endpoint.recv_data(len as usize)?;
+ if rbuf.len() != len as usize {
+ return Err(Error::InvalidMessage);
+ }
+ rbuf
+ }
+ };
+ let size = buf.len();
+
+ match hdr.get_code() {
+ MasterReq::SET_OWNER => {
+ self.check_request_size(&hdr, size, 0)?;
+ let res = self.backend.set_owner();
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::RESET_OWNER => {
+ self.check_request_size(&hdr, size, 0)?;
+ let res = self.backend.reset_owner();
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::GET_FEATURES => {
+ self.check_request_size(&hdr, size, 0)?;
+ let features = self.backend.get_features()?;
+ let msg = VhostUserU64::new(features);
+ self.slave_req_helper.send_reply_message(&hdr, &msg)?;
+ self.virtio_features = features;
+ self.update_reply_ack_flag();
+ }
+ MasterReq::SET_FEATURES => {
+ let msg = self.extract_request_body::<VhostUserU64>(&hdr, size, &buf)?;
+ let res = self.backend.set_features(msg.value);
+ self.acked_virtio_features = msg.value;
+ self.update_reply_ack_flag();
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::SET_MEM_TABLE => {
+ let res = self.set_mem_table(&hdr, size, &buf, files);
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::SET_VRING_NUM => {
+ let msg = self.extract_request_body::<VhostUserVringState>(&hdr, size, &buf)?;
+ let res = self.backend.set_vring_num(msg.index, msg.num);
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::SET_VRING_ADDR => {
+ let msg = self.extract_request_body::<VhostUserVringAddr>(&hdr, size, &buf)?;
+ let flags = match VhostUserVringAddrFlags::from_bits(msg.flags) {
+ Some(val) => val,
+ None => return Err(Error::InvalidMessage),
+ };
+ let res = self.backend.set_vring_addr(
+ msg.index,
+ flags,
+ msg.descriptor,
+ msg.used,
+ msg.available,
+ msg.log,
+ );
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::SET_VRING_BASE => {
+ let msg = self.extract_request_body::<VhostUserVringState>(&hdr, size, &buf)?;
+ let res = self.backend.set_vring_base(msg.index, msg.num);
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::GET_VRING_BASE => {
+ let msg = self.extract_request_body::<VhostUserVringState>(&hdr, size, &buf)?;
+ let reply = self.backend.get_vring_base(msg.index)?;
+ self.slave_req_helper.send_reply_message(&hdr, &reply)?;
+ }
+ MasterReq::SET_VRING_CALL => {
+ self.check_request_size(&hdr, size, mem::size_of::<VhostUserU64>())?;
+ let (index, file) = self.handle_vring_fd_request(&buf, files)?;
+ let res = self.backend.set_vring_call(index, file);
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::SET_VRING_KICK => {
+ self.check_request_size(&hdr, size, mem::size_of::<VhostUserU64>())?;
+ let (index, file) = self.handle_vring_fd_request(&buf, files)?;
+ let res = self.backend.set_vring_kick(index, file);
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::SET_VRING_ERR => {
+ self.check_request_size(&hdr, size, mem::size_of::<VhostUserU64>())?;
+ let (index, file) = self.handle_vring_fd_request(&buf, files)?;
+ let res = self.backend.set_vring_err(index, file);
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::GET_PROTOCOL_FEATURES => {
+ self.check_request_size(&hdr, size, 0)?;
+ let features = self.backend.get_protocol_features()?;
+ let msg = VhostUserU64::new(features.bits());
+ self.slave_req_helper.send_reply_message(&hdr, &msg)?;
+ self.protocol_features = features;
+ self.update_reply_ack_flag();
+ }
+ MasterReq::SET_PROTOCOL_FEATURES => {
+ let msg = self.extract_request_body::<VhostUserU64>(&hdr, size, &buf)?;
+ let res = self.backend.set_protocol_features(msg.value);
+ self.acked_protocol_features = msg.value;
+ self.update_reply_ack_flag();
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::GET_QUEUE_NUM => {
+ if self.acked_protocol_features & VhostUserProtocolFeatures::MQ.bits() == 0 {
+ return Err(Error::InvalidOperation);
+ }
+ self.check_request_size(&hdr, size, 0)?;
+ let num = self.backend.get_queue_num()?;
+ let msg = VhostUserU64::new(num);
+ self.slave_req_helper.send_reply_message(&hdr, &msg)?;
+ }
+ MasterReq::SET_VRING_ENABLE => {
+ let msg = self.extract_request_body::<VhostUserVringState>(&hdr, size, &buf)?;
+ if self.acked_virtio_features & VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits()
+ == 0
+ {
+ return Err(Error::InvalidOperation);
+ }
+ let enable = match msg.num {
+ 1 => true,
+ 0 => false,
+ _ => return Err(Error::InvalidParam),
+ };
+
+ let res = self.backend.set_vring_enable(msg.index, enable);
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::GET_CONFIG => {
+ if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
+ return Err(Error::InvalidOperation);
+ }
+ self.check_request_size(&hdr, size, hdr.get_size() as usize)?;
+ self.get_config(&hdr, &buf)?;
+ }
+ MasterReq::SET_CONFIG => {
+ if self.acked_protocol_features & VhostUserProtocolFeatures::CONFIG.bits() == 0 {
+ return Err(Error::InvalidOperation);
+ }
+ self.check_request_size(&hdr, size, hdr.get_size() as usize)?;
+ let res = self.set_config(size, &buf);
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::SET_SLAVE_REQ_FD => {
+ if self.acked_protocol_features & VhostUserProtocolFeatures::SLAVE_REQ.bits() == 0 {
+ return Err(Error::InvalidOperation);
+ }
+ self.check_request_size(&hdr, size, hdr.get_size() as usize)?;
+ let res = self.set_slave_req_fd(files);
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::GET_INFLIGHT_FD => {
+ if self.acked_protocol_features & VhostUserProtocolFeatures::INFLIGHT_SHMFD.bits()
+ == 0
+ {
+ return Err(Error::InvalidOperation);
+ }
+
+ let msg = self.extract_request_body::<VhostUserInflight>(&hdr, size, &buf)?;
+ let (inflight, file) = self.backend.get_inflight_fd(&msg)?;
+ let reply_hdr = self
+ .slave_req_helper
+ .new_reply_header::<VhostUserInflight>(&hdr, 0)?;
+ self.slave_req_helper.endpoint.send_message(
+ &reply_hdr,
+ &inflight,
+ Some(&[file.as_raw_descriptor()]),
+ )?;
+ }
+ MasterReq::SET_INFLIGHT_FD => {
+ if self.acked_protocol_features & VhostUserProtocolFeatures::INFLIGHT_SHMFD.bits()
+ == 0
+ {
+ return Err(Error::InvalidOperation);
+ }
+ let file = take_single_file(files).ok_or(Error::IncorrectFds)?;
+ let msg = self.extract_request_body::<VhostUserInflight>(&hdr, size, &buf)?;
+ let res = self.backend.set_inflight_fd(&msg, file);
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::GET_MAX_MEM_SLOTS => {
+ if self.acked_protocol_features
+ & VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS.bits()
+ == 0
+ {
+ return Err(Error::InvalidOperation);
+ }
+ self.check_request_size(&hdr, size, 0)?;
+ let num = self.backend.get_max_mem_slots()?;
+ let msg = VhostUserU64::new(num);
+ self.slave_req_helper.send_reply_message(&hdr, &msg)?;
+ }
+ MasterReq::ADD_MEM_REG => {
+ if self.acked_protocol_features
+ & VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS.bits()
+ == 0
+ {
+ return Err(Error::InvalidOperation);
+ }
+ let mut files = files.ok_or(Error::InvalidParam)?;
+ if files.len() != 1 {
+ return Err(Error::InvalidParam);
+ }
+ let msg =
+ self.extract_request_body::<VhostUserSingleMemoryRegion>(&hdr, size, &buf)?;
+ let res = self.backend.add_mem_region(&msg, files.swap_remove(0));
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ MasterReq::REM_MEM_REG => {
+ if self.acked_protocol_features
+ & VhostUserProtocolFeatures::CONFIGURE_MEM_SLOTS.bits()
+ == 0
+ {
+ return Err(Error::InvalidOperation);
+ }
+
+ let msg =
+ self.extract_request_body::<VhostUserSingleMemoryRegion>(&hdr, size, &buf)?;
+ let res = self.backend.remove_mem_region(&msg);
+ self.slave_req_helper.send_ack_message(&hdr, res.is_ok())?;
+ res?;
+ }
+ _ => {
+ return Err(Error::InvalidMessage);
+ }
+ }
+ Ok(())
+ }
+
+ fn set_mem_table(
+ &mut self,
+ hdr: &VhostUserMsgHeader<MasterReq>,
+ size: usize,
+ buf: &[u8],
+ files: Option<Vec<File>>,
+ ) -> Result<()> {
+ self.check_request_size(hdr, size, hdr.get_size() as usize)?;
+
+ // check message size is consistent
+ let hdrsize = mem::size_of::<VhostUserMemory>();
+ if size < hdrsize {
+ return Err(Error::InvalidMessage);
+ }
+ let msg = unsafe { &*(buf.as_ptr() as *const VhostUserMemory) };
+ if !msg.is_valid() {
+ return Err(Error::InvalidMessage);
+ }
+ if size != hdrsize + msg.num_regions as usize * mem::size_of::<VhostUserMemoryRegion>() {
+ return Err(Error::InvalidMessage);
+ }
+
+ let files = match self.slave_req_helper.protocol {
+ Protocol::Regular => {
+ // validate number of fds matching number of memory regions
+ let files = files.ok_or(Error::InvalidMessage)?;
+ if files.len() != msg.num_regions as usize {
+ return Err(Error::InvalidMessage);
+ }
+ files
+ }
+ Protocol::Virtio => vec![],
+ };
+
+ // Validate memory regions
+ let regions = unsafe {
+ slice::from_raw_parts(
+ buf.as_ptr().add(hdrsize) as *const VhostUserMemoryRegion,
+ msg.num_regions as usize,
+ )
+ };
+ for region in regions.iter() {
+ if !region.is_valid() {
+ return Err(Error::InvalidMessage);
+ }
+ }
+
+ self.backend.set_mem_table(regions, files)
+ }
+
+ fn get_config(&mut self, hdr: &VhostUserMsgHeader<MasterReq>, buf: &[u8]) -> Result<()> {
+ let payload_offset = mem::size_of::<VhostUserConfig>();
+ if buf.len() > MAX_MSG_SIZE || buf.len() < payload_offset {
+ return Err(Error::InvalidMessage);
+ }
+ let msg = unsafe { std::ptr::read_unaligned(buf.as_ptr() as *const VhostUserConfig) };
+ if !msg.is_valid() {
+ return Err(Error::InvalidMessage);
+ }
+ if buf.len() - payload_offset != msg.size as usize {
+ return Err(Error::InvalidMessage);
+ }
+ let flags = match VhostUserConfigFlags::from_bits(msg.flags) {
+ Some(val) => val,
+ None => return Err(Error::InvalidMessage),
+ };
+ let res = self.backend.get_config(msg.offset, msg.size, flags);
+
+ // vhost-user slave's payload size MUST match master's request
+ // on success, uses zero length of payload to indicate an error
+ // to vhost-user master.
+ match res {
+ Ok(ref buf) if buf.len() == msg.size as usize => {
+ let reply = VhostUserConfig::new(msg.offset, buf.len() as u32, flags);
+ self.slave_req_helper
+ .send_reply_with_payload(hdr, &reply, buf.as_slice())?;
+ }
+ Ok(_) => {
+ let reply = VhostUserConfig::new(msg.offset, 0, flags);
+ self.slave_req_helper.send_reply_message(hdr, &reply)?;
+ }
+ Err(_) => {
+ let reply = VhostUserConfig::new(msg.offset, 0, flags);
+ self.slave_req_helper.send_reply_message(hdr, &reply)?;
+ }
+ }
+ Ok(())
+ }
+
+ fn set_config(&mut self, size: usize, buf: &[u8]) -> Result<()> {
+ if size > MAX_MSG_SIZE || size < mem::size_of::<VhostUserConfig>() {
+ return Err(Error::InvalidMessage);
+ }
+ let msg = unsafe { std::ptr::read_unaligned(buf.as_ptr() as *const VhostUserConfig) };
+ if !msg.is_valid() {
+ return Err(Error::InvalidMessage);
+ }
+ if size - mem::size_of::<VhostUserConfig>() != msg.size as usize {
+ return Err(Error::InvalidMessage);
+ }
+ let flags: VhostUserConfigFlags = match VhostUserConfigFlags::from_bits(msg.flags) {
+ Some(val) => val,
+ None => return Err(Error::InvalidMessage),
+ };
+
+ self.backend.set_config(msg.offset, buf, flags)
+ }
+
+ fn set_slave_req_fd(&mut self, files: Option<Vec<File>>) -> Result<()> {
+ if cfg!(windows) {
+ unimplemented!();
+ } else {
+ let file = take_single_file(files).ok_or(Error::InvalidMessage)?;
+ self.backend.set_slave_req_fd(file);
+ Ok(())
+ }
+ }
+
+ fn handle_vring_fd_request(
+ &mut self,
+ buf: &[u8],
+ files: Option<Vec<File>>,
+ ) -> Result<(u8, Option<File>)> {
+ self.slave_req_helper.handle_vring_fd_request(buf, files)
+ }
+
+ fn check_state(&self) -> Result<()> {
+ match self.error {
+ Some(e) => Err(Error::SocketBroken(std::io::Error::from_raw_os_error(e))),
+ None => Ok(()),
+ }
+ }
+
+ fn check_request_size(
+ &self,
+ hdr: &VhostUserMsgHeader<MasterReq>,
+ size: usize,
+ expected: usize,
+ ) -> Result<()> {
+ if hdr.get_size() as usize != expected
+ || hdr.is_reply()
+ || hdr.get_version() != 0x1
+ || size != expected
+ {
+ return Err(Error::InvalidMessage);
+ }
+ Ok(())
+ }
+
+ fn check_attached_files(
+ &self,
+ hdr: &VhostUserMsgHeader<MasterReq>,
+ files: &Option<Vec<File>>,
+ ) -> Result<()> {
+ match hdr.get_code() {
+ MasterReq::SET_MEM_TABLE
+ | MasterReq::SET_VRING_CALL
+ | MasterReq::SET_VRING_KICK
+ | MasterReq::SET_VRING_ERR
+ | MasterReq::SET_LOG_BASE
+ | MasterReq::SET_LOG_FD
+ | MasterReq::SET_SLAVE_REQ_FD
+ | MasterReq::SET_INFLIGHT_FD
+ | MasterReq::ADD_MEM_REG => Ok(()),
+ _ if files.is_some() => Err(Error::InvalidMessage),
+ _ => Ok(()),
+ }
+ }
+
+ fn extract_request_body<T: Sized + DataInit + VhostUserMsgValidator>(
+ &self,
+ hdr: &VhostUserMsgHeader<MasterReq>,
+ size: usize,
+ buf: &[u8],
+ ) -> Result<T> {
+ self.check_request_size(hdr, size, mem::size_of::<T>())?;
+ let msg = unsafe { std::ptr::read_unaligned(buf.as_ptr() as *const T) };
+ if !msg.is_valid() {
+ return Err(Error::InvalidMessage);
+ }
+ Ok(msg)
+ }
+
+ fn update_reply_ack_flag(&mut self) {
+ let vflag = VhostUserVirtioFeatures::PROTOCOL_FEATURES.bits();
+ let pflag = VhostUserProtocolFeatures::REPLY_ACK;
+ if (self.virtio_features & vflag) != 0
+ && self.protocol_features.contains(pflag)
+ && (self.acked_protocol_features & pflag.bits()) != 0
+ {
+ self.slave_req_helper.reply_ack_enabled = true;
+ } else {
+ self.slave_req_helper.reply_ack_enabled = false;
+ }
+ }
+}
+
+impl<S: VhostUserSlaveReqHandler, E: AsRawDescriptor + Endpoint<MasterReq>> AsRawDescriptor
+ for SlaveReqHandler<S, E>
+{
+ fn as_raw_descriptor(&self) -> RawDescriptor {
+ // TODO(b/221882601): figure out if this used for polling.
+ self.slave_req_helper.endpoint.as_raw_descriptor()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use base::INVALID_DESCRIPTOR;
+
+ use super::*;
+ use crate::{dummy_slave::DummySlaveReqHandler, MasterReqEndpoint, SystemStream};
+
+ #[test]
+ fn test_slave_req_handler_new() {
+ let (p1, _p2) = SystemStream::pair().unwrap();
+ let endpoint = MasterReqEndpoint::from(p1);
+ let backend = Arc::new(Mutex::new(DummySlaveReqHandler::new()));
+ let mut handler = SlaveReqHandler::new(endpoint, backend);
+
+ handler.check_state().unwrap();
+ handler.set_failed(libc::EAGAIN);
+ handler.check_state().unwrap_err();
+ assert!(handler.as_raw_descriptor() != INVALID_DESCRIPTOR);
+ }
+}
diff --git a/third_party/vmm_vhost/src/sys.rs b/third_party/vmm_vhost/src/sys.rs
new file mode 100644
index 000000000..c37943a53
--- /dev/null
+++ b/third_party/vmm_vhost/src/sys.rs
@@ -0,0 +1,16 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+//! A wrapper module for platform dependent code.
+
+cfg_if::cfg_if! {
+ if #[cfg(unix)] {
+ mod unix;
+ pub use unix::*;
+ } else if #[cfg(windows)] {
+ mod windows;
+ pub use windows::*;
+ } else {
+ compile_error!("Unsupported platform");
+ }
+}
diff --git a/third_party/vmm_vhost/src/sys/unix.rs b/third_party/vmm_vhost/src/sys/unix.rs
new file mode 100644
index 000000000..2b4ec9a3c
--- /dev/null
+++ b/third_party/vmm_vhost/src/sys/unix.rs
@@ -0,0 +1,35 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+//! Unix specific code that keeps rest of the code in the crate platform independent.
+
+use std::io::Result;
+use std::os::unix::net::{UnixListener, UnixStream};
+
+/// Alias to enable platform independent code.
+pub type SystemListener = UnixListener;
+
+/// Alias to enable platform independent code.
+pub type SystemStream = UnixStream;
+
+cfg_if::cfg_if! {
+ if #[cfg(feature = "device")] {
+ use crate::{connection::socket::Endpoint as SocketEndpoint, message::SlaveReq};
+ use crate::message::MasterReq;
+
+ pub(crate) type SlaveFsCacheReqSocket = SocketEndpoint<SlaveReq>;
+ pub(crate) type MasterReqEndpoint = SocketEndpoint<MasterReq>;
+ }
+}
+
+/// Collection of platform-specific methods that SystemListener provides.
+pub(crate) trait SystemListenerExt {
+ /// Accept a connection.
+ fn accept(&self) -> Result<SystemStream>;
+}
+
+impl SystemListenerExt for SystemListener {
+ fn accept(&self) -> Result<SystemStream> {
+ self.accept().map(|(socket, _address)| socket)
+ }
+}
diff --git a/third_party/vmm_vhost/src/sys/windows.rs b/third_party/vmm_vhost/src/sys/windows.rs
new file mode 100644
index 000000000..7e835ca84
--- /dev/null
+++ b/third_party/vmm_vhost/src/sys/windows.rs
@@ -0,0 +1,22 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+//! Windows specific code that keeps rest of the code in the crate platform independent.
+
+use base::Tube;
+
+/// Alias to enable platform independent code.
+pub type SystemStream = Tube;
+
+cfg_if::cfg_if! {
+ if #[cfg(feature = "device")] {
+ use crate::connection::TubeEndpoint;
+ use crate::message::{MasterReq, SlaveReq};
+
+ pub(crate) type SlaveFsCacheReqSocket = TubeEndpoint<SlaveReq>;
+ pub(crate) type MasterReqEndpoint = TubeEndpoint<MasterReq>;
+ }
+}
+
+/// Collection of platform-specific methods that SystemListener provides.
+pub(crate) trait SystemListenerExt {}
diff --git a/tools/aarch64vm b/tools/aarch64vm
new file mode 100755
index 000000000..676eb017b
--- /dev/null
+++ b/tools/aarch64vm
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+python3 $(dirname $0)/impl/testvm.py --arch=aarch64 "$@"
diff --git a/tools/bindgen-all-the-things b/tools/bindgen-all-the-things
new file mode 100755
index 000000000..edd7bdcbc
--- /dev/null
+++ b/tools/bindgen-all-the-things
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Regenerate all bindgen-generated Rust bindings in the crosvm tree.
+#
+# This script expects to be executed from a full Chrome OS checkout so it has access to the kernel
+# and other repositories.
+
+set -e
+cd "$(dirname "${BASH_SOURCE[0]}")/.."
+
+source tools/impl/bindgen-common.sh
+
+die() {
+ echo "$@"
+ exit 1
+}
+
+dirs=(
+ io_uring
+ devices/src/virtio/gpu
+ kernel_loader
+ kvm_sys
+ media/libvda
+ net_sys
+ vfio_sys
+ virtio_sys
+)
+
+for d in "${dirs[@]}"; do
+ echo "bindgen ${d}"
+ "${d}"/bindgen.sh || die "Failed to generate bindings for ${d}"
+done
+
+echo "Done!"
diff --git a/tools/cargo-doc b/tools/cargo-doc
new file mode 100755
index 000000000..49df6e550
--- /dev/null
+++ b/tools/cargo-doc
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -ex
+
+# Build cargo-doc
+# $ ./tools/cargo-doc --target-dir /path/to/dir
+
+echo "start cargo-doc"
+
+MANIFEST_PATH=$(dirname "$0")/../Cargo.toml
+
+echo "manifest = ${MANIFEST_PATH}"
+
+DISABLED_FEATURES=(
+ audio_cras
+ chromeos
+ libvda
+ power-monitor-powerd
+ video-decoder
+ video-encoder
+)
+
+ALL_FEATURES=$(
+ cargo metadata --manifest-path "${MANIFEST_PATH}" | \
+ jq -r '.packages[] |
+ select(.name == "crosvm") |
+ .features |
+ keys[]')
+
+features=""
+
+for f in $ALL_FEATURES; do
+ if [[ ! "${DISABLED_FEATURES[*]}" =~ $f ]]; then
+ features+=",${f}"
+ fi
+done
+
+cargo doc \
+ --manifest-path="${MANIFEST_PATH}" \
+ --features="${features}" "$@"
diff --git a/tools/chromeos/create_merge b/tools/chromeos/create_merge
new file mode 100755
index 000000000..17bf7dc7c
--- /dev/null
+++ b/tools/chromeos/create_merge
@@ -0,0 +1,112 @@
+#!/bin/bash
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Script to create a commit to merge cros/main into cros/chromeos with a useful
+# commit message.
+#
+# Basic usage to upload a merge to gerrit:
+#
+# $ repo start uprev .
+# $ ./tools/chromeos/create_merge
+# $ git push cros HEAD:refs/for/chromeos
+#
+# To merge with a specific commit, use: ./tools/chromeos/create_merge $SHA
+
+set -e
+
+if [ "$1" == "--dry-run-only" ]; then
+ DRY_RUN_ONLY=true
+ shift
+fi
+
+LOCAL_BRANCH=$(git branch --show-current)
+REMOTE_NAME=$(git config "branch.${LOCAL_BRANCH}.remote")
+URL=$(git remote get-url "${REMOTE_NAME}")
+
+DEFAULT_TARGET="${REMOTE_NAME}/main"
+MERGE_TARGET="${1:-${DEFAULT_TARGET}}"
+
+commit_list() {
+ git log --oneline --decorate=no --no-color "HEAD..${MERGE_TARGET}"
+}
+
+prerequisites() {
+ if [[ -e "${LOCAL_BRANCH}" ]] ||
+ [[ -e "${REMOTE_NAME}" ]] ||
+ [[ -e "${URL}" ]]; then
+ echo "This script requires the local repository to be on" \
+ "a tracking branch."
+ exit 1
+ fi
+
+ if [[ -n $(git status -s) ]]; then
+ echo "Working directory is not clean:"
+ git status -s
+ exit 1
+ fi
+
+ if [[ -z "$(commit_list)" ]]; then
+ echo "Nothing to merge."
+ exit 0
+ fi
+}
+
+cq_depends() {
+ git log --no-color "HEAD..${MERGE_TARGET}" --pretty=email |
+ grep ^Cq-Depend: |
+ sort -u
+}
+
+bug_references() {
+ git log --no-color "HEAD..${MERGE_TARGET}" --pretty=email |
+ grep ^BUG= |
+ grep -vi ^BUG=none |
+ sort -u
+}
+
+merge_message() {
+ local old=$(git rev-parse HEAD)
+ local new=$(git rev-parse "${MERGE_TARGET}")
+ local count=$(commit_list | wc -l)
+
+ local notes="$(date +%F)"
+ if [[ -n "$(cq_depends)" ]]; then
+ notes="${notes}, cq-depend"
+ fi
+
+ if [ "${DRY_RUN_ONLY}" = true ]; then
+ echo "Merge dry run (${notes})"
+ else
+ echo "Merge ${count} commits from ${MERGE_TARGET} (${notes})"
+ fi
+ echo ""
+ commit_list
+ echo ""
+ echo "${URL}/+log/${old}..${new}"
+ echo ""
+ if [ "${DRY_RUN_ONLY}" != true ]; then
+ bug_references
+ fi
+ echo "TEST=CQ"
+}
+
+merge_trailers() {
+ cq_depends
+ if [ "${DRY_RUN_ONLY}" = true ]; then
+ echo "Commit: false"
+ fi
+}
+
+main() {
+ prerequisites
+ # Note: trailers need to be added in a separate -m argument. Otherwise trailing whitespace may
+ # be trimmed which can confuse the gerrit preupload hook when it's trying to add the Commit-Id
+ # trailer.
+ git merge -X theirs --no-ff "${MERGE_TARGET}" -m "$(merge_message)" -m "$(merge_trailers)"
+
+ git --no-pager log -n 1
+}
+
+main
diff --git a/tools/chromeos/setup_cargo b/tools/chromeos/setup_cargo
new file mode 100755
index 000000000..a06388247
--- /dev/null
+++ b/tools/chromeos/setup_cargo
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# To build crosvm using cargo against libraries and crates provided by ChromeOS
+# use this script to update path references in Cargo.toml.
+
+set -e
+
+CARGO_PATH=$(dirname "$0")/../../Cargo.toml
+
+if ! git diff "${CARGO_PATH}"; then
+ echo "There is pending Cargo.toml changes, please clean first."
+ exit 1
+fi
+
+declare -A replacements=(
+ ["libcras_stub"]="../../third_party/adhd/cras/client/libcras"
+ ["system_api_stub"]="../../platform2/system_api"
+ ["third_party/minijail"]="../../aosp/external/minijail"
+)
+
+for crate in "${!replacements[@]}"; do
+ echo "Replacing '${crate}' with '${replacements[$crate]}'"
+ sed -i "s|path = \"${crate}|path = \"${replacements[$crate]}|g" \
+ "${CARGO_PATH}"
+done
+
+git commit "${CARGO_PATH}" -m 'crosvm: DO NOT SUBMIT: Cargo.toml changes.
+
+This is for local cargo {build,test} in your CrOS checkout.
+Please do not submit this change.
+
+BUG=None
+TEST=None
+
+Commit: false
+'
+
+echo "Modified Cargo.toml with new paths. Please do not submit the change."
diff --git a/tools/chromeos/uprev_ebuilds b/tools/chromeos/uprev_ebuilds
new file mode 100755
index 000000000..1eace904b
--- /dev/null
+++ b/tools/chromeos/uprev_ebuilds
@@ -0,0 +1,43 @@
+#!/bin/sh
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Uprev ebuild files of crosvm (and related packages) to the currently checked
+# out commit.
+# This uses the same process that PUpr is using when generating uprev CLs.
+
+cd $(dirname $0)
+
+CHROMITE_DIR=../../../../../chromite
+if ! [ -e "${CHROMITE_DIR}" ]; then
+ echo "Run from ChromeOS checkout."
+ exit 1
+fi
+
+IN=$(mktemp)
+OUT=$(mktemp)
+
+echo '{
+ "package_info": {
+ "category": "chromeos-base",
+ "package_name": "crosvm"
+ },
+ "versions": [
+ {
+ "repository": "dummy",
+ "ref": "dummy",
+ "revision": "dummy"
+ }
+ ]
+}' >> "${IN}"
+
+${CHROMITE_DIR}/bin/build_api \
+ --input-json "${IN}" \
+ --output-json "${OUT}" \
+ chromite.api.PackageService/UprevVersionedPackage
+
+cat "${OUT}"
+
+rm "${IN}"
+rm "${OUT}"
diff --git a/tools/clippy b/tools/clippy
new file mode 100755
index 000000000..d3ae380f6
--- /dev/null
+++ b/tools/clippy
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+# Copyright 2019 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# To check for violations:
+# $ ./tools/clippy
+#
+# To fix violations where possible:
+# $ ./tools/clippy --fix
+
+import os
+import sys
+
+sys.path.append(os.path.join(sys.path[0], "impl"))
+from impl.common import CROSVM_ROOT, cwd, run_main, cmd, chdir
+from impl.test_runner import get_workspace_excludes
+
+clippy = cmd("cargo clippy")
+
+
+if os.name == "posix":
+ EXCLUDED_CRATES: list[str] = []
+ EXCLUDED_CRATES_ARGS: list[str] = []
+ FEATURES: str = "--features=all-linux"
+
+elif os.name == "nt":
+ EXCLUDED_CRATES: list[str] = list(get_workspace_excludes("win64"))
+ EXCLUDED_CRATES_ARGS: list[str] = [f"--exclude={crate}" for crate in EXCLUDED_CRATES]
+ FEATURES: str = ""
+
+else:
+ raise Exception(f"Unsupported build target: {os.name}")
+
+
+def is_crate_excluded(crate: str) -> bool:
+ return crate in EXCLUDED_CRATES
+
+
+def main(fix: bool = False):
+ chdir(CROSVM_ROOT)
+
+ # Note: Clippy checks are configured in .cargo/config.toml
+ common_args = [
+ "--fix" if fix else None,
+ "--all-targets",
+ "--",
+ "-Dwarnings",
+ ]
+ print("Clippy crosvm workspace")
+ clippy("--workspace", FEATURES, *EXCLUDED_CRATES_ARGS, *common_args).fg()
+
+ for crate in CROSVM_ROOT.glob("common/*/Cargo.toml"):
+ if not is_crate_excluded(crate.parent.name):
+ print("Clippy", crate.parent.relative_to(CROSVM_ROOT))
+ with cwd(crate.parent):
+ clippy("--all-features", *common_args).fg()
+ else:
+ print("Skipping crate", crate.parent.relative_to(CROSVM_ROOT))
+
+
+run_main(main)
diff --git a/tools/contrib/cargo_refactor.py b/tools/contrib/cargo_refactor.py
new file mode 100644
index 000000000..cb08d8f6d
--- /dev/null
+++ b/tools/contrib/cargo_refactor.py
@@ -0,0 +1,209 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Refactoring tools for moving around crates and updating dependencies
+# in toml files.
+#
+# Contains the last run refactoring for reference. Don't run this script, it'll
+# fail, but use it as a foundation for other refactorings.
+
+from contextlib import contextmanager
+from pathlib import Path
+import os
+import re
+import shutil
+import subprocess
+from typing import Callable, List, Tuple, Union
+
+
+SearchPattern = Union[str, re.Pattern[str]]
+Replacement = Union[str, Callable[[re.Match[str]], str]]
+
+
+def append_to_file(file_path: Path, appendix: str):
+ contents = file_path.read_text()
+ file_path.write_text(contents.rstrip() + "\n" + appendix + "\n")
+
+
+def replace_in_file(file_path: Path, search: SearchPattern, replace: Replacement):
+ if not file_path.exists():
+ print(f"WARNING: Does not exist {file_path}")
+ return
+ if isinstance(search, str):
+ search = re.escape(search)
+ contents = file_path.read_text()
+ (contents, count) = re.subn(search, replace, contents)
+ if count > 0:
+ print(f"replacing '{search}' with '{replace}' in {file_path}")
+ file_path.write_text(contents)
+
+
+def replace_in_files(glob: str, replacements: List[Tuple[SearchPattern, Replacement]]):
+ for file in Path().glob(glob):
+ for (search, replace) in replacements:
+ replace_in_file(file, search, replace)
+
+
+def replace_path_in_all_cargo_toml(old_path: Path, new_path: Path):
+ "Replace path in all cargo.toml files, accounting for relative paths."
+ for toml in Path().glob("**/Cargo.toml"):
+ crate_dir = toml.parent
+ old_rel = os.path.relpath(old_path, crate_dir)
+ new_rel = os.path.relpath(new_path, crate_dir)
+ replace_in_file(toml, re.escape(f'path = "{old_rel}"'), f'path = "{new_rel}"')
+
+
+def update_path_deps(toml: Path, from_path: Path, to_path: Path):
+ "Update path deps in toml file after moving it"
+ contents = toml.read_text()
+ for old_dep in re.findall('{ path = "([^"]+)"', contents):
+ new_dep = os.path.relpath((from_path / old_dep).resolve(), to_path)
+ contents = contents.replace(f'path = "{old_dep}"', f'path = "{new_dep}"')
+ toml.write_text(contents)
+
+
+def move_crate(from_path: Path, to_path: Path):
+ "Move crate and update dependencies"
+ print(f"{from_path} -> {to_path}")
+ if to_path.exists():
+ shutil.rmtree(to_path)
+ shutil.copytree(str(from_path), str(to_path))
+ update_path_deps(to_path / "Cargo.toml", from_path, to_path)
+ replace_in_files("**/*/Cargo.toml", [(str(from_path), str(to_path))])
+ replace_in_file(Path("Cargo.toml"), str(from_path), str(to_path))
+
+
+def update_workspace_members():
+ members: list[str] = []
+ members.append("members = [")
+ for toml in sorted(Path().glob("*/Cargo.toml")):
+ members.append(f' "{toml.parent}",')
+ members.append(' "third_party/vmm_vhost",')
+
+ members.append("]")
+ replace_in_file(Path("Cargo.toml"), re.compile(r"members = \[[^\]]+\]"), "\n".join(members))
+
+ exclude: list[str] = []
+ exclude.append("exclude = [")
+ for toml in sorted(Path().glob("common/*/Cargo.toml")):
+ exclude.append(f' "{toml.parent}",')
+ exclude.append("]")
+ replace_in_file(Path("Cargo.toml"), re.compile(r"exclude = \[[^\]]+\]"), "\n".join(exclude))
+
+
+@contextmanager
+def chdir(path: Union[Path, str]):
+ origin = Path().absolute()
+ try:
+ os.chdir(path)
+ yield
+ finally:
+ os.chdir(origin)
+
+
+def copy_crate_src_to_module(source: str, destination: str):
+ shutil.rmtree(destination, ignore_errors=True)
+ shutil.copytree(source, destination)
+ with chdir(destination):
+ Path("lib.rs").rename("mod.rs")
+
+
+IMPORT = """pub mod unix;
+
+#[cfg(windows)]
+pub mod windows;
+"""
+
+BUILD_RS = """\
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+fn main() {
+ cc::Build::new()
+ .file("src/windows/stdio_fileno.c")
+ .compile("stdio_fileno");
+}
+"""
+
+
+def main():
+ os.chdir(Path(__file__).parent.parent.parent)
+
+ subprocess.check_call(["git", "checkout", "-f", "--", "base"])
+
+ # Move crates to base
+ move_crate(Path("common/win_util"), Path("win_util"))
+ copy_crate_src_to_module("common/win_sys_util/src", "base/src/windows")
+ Path("base/build.rs").write_text(BUILD_RS)
+
+ # Load the added module
+ replace_in_file(Path("base/src/lib.rs"), "pub mod unix;", IMPORT)
+
+ # Flatten all imports for easier replacements
+ subprocess.check_call(
+ ["rustfmt", "+nightly", "--config=imports_granularity=item", "base/src/lib.rs"]
+ )
+
+ # Update references to the above crates in base:
+ replace_in_files(
+ "base/src/**/*.rs",
+ [
+ ("sys_util_core::", "crate::common::"),
+ ("win_sys_util::", "crate::platform::"),
+ ("crate::unix::", "crate::platform::"),
+ ("use poll_token_derive::", "use base_poll_token_derive::"),
+ ],
+ )
+
+ # Fixup macros since they like to have special treatement.
+ macros = [
+ "debug",
+ "error",
+ "handle_eintr_errno",
+ "info",
+ "ioctl_io_nr",
+ "ioctl_ior_nr",
+ "ioctl_iow_nr",
+ "ioctl_iowr_nr",
+ "syscall",
+ "warn",
+ "volatile_at_impl",
+ "volatile_impl",
+ "generate_scoped_event",
+ "syslog_lock",
+ "CHRONO_TIMESTAMP_FIXED_FMT",
+ ]
+ for macro in macros:
+ # Update use statments. #[macro_export] exports them on the crate scoped
+ replace_in_files(
+ "base/src/windows/**/*.rs",
+ [
+ (f"crate::common::{macro}", f"crate::{macro}"),
+ (f"super::super::{macro}", f"crate::{macro}"),
+ (f"super::{macro}", f"crate::{macro}"),
+ ],
+ )
+
+ # Replace $crate:: with $crate::windows (unless it's a macro invocation..)
+ def replace_references_in_macros(match: re.Match[str]):
+ name = match.group(0)
+ if not name.endswith("!"):
+ return name.replace("$crate", f"$crate::platform")
+ return name
+
+ replace_in_files(
+ f"base/src/windows/**/*.rs",
+ [(re.compile(r"([\w\*\_\$]+\:\:)+([\w\*\_\!]+)"), replace_references_in_macros)],
+ )
+
+ # Unflatten imports again
+ subprocess.check_call(
+ ["rustfmt", "+nightly", "--config=imports_granularity=crate", "base/src/lib.rs"]
+ )
+
+ subprocess.check_call(["git", "rm", "-r", "common/win_sys_util", "common/win_util"])
+
+
+main()
diff --git a/tools/contrib/refactor_use_references.py b/tools/contrib/refactor_use_references.py
new file mode 100644
index 000000000..6aed8c711
--- /dev/null
+++ b/tools/contrib/refactor_use_references.py
@@ -0,0 +1,163 @@
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Tools for refactoring references in rust code.
+#
+# Contains the last run refactoring for reference. Don't run this script, it'll
+# fail, but use it as a foundation for other refactorings.
+
+from contextlib import contextmanager
+import os
+import re
+import subprocess
+from pathlib import Path
+from typing import Callable, NamedTuple, Union
+
+SearchPattern = Union[str, re.Pattern[str]]
+
+
+class Token(NamedTuple):
+ token: str
+ start: int
+ end: int
+
+
+def tokenize(source: str):
+ "Split source by whitespace with start/end indices annotated."
+ start = 0
+ for i in range(len(source)):
+ if source[i] in (" ", "\n", "\t") and i - start > 0:
+ token = source[start:i].strip()
+ if token:
+ yield Token(token, start, i)
+ start = i
+
+
+def parse_module_chunks(source: str):
+ """Terrible parser to split code by `mod foo { ... }` statements. Please don't judge me.
+
+ Returns the original source split with module names anntated as ('module name', 'source')
+ """
+ tokens = list(tokenize(source))
+ prev = 0
+ for i in range(len(tokens) - 2):
+ if tokens[i].token == "mod" and tokens[i + 2].token == "{":
+ brackets = 1
+ for j in range(i + 3, len(tokens)):
+ if "{" not in tokens[j].token or "}" not in tokens[j].token:
+ if "{" in tokens[j].token:
+ brackets += 1
+ elif "}" in tokens[j].token:
+ brackets -= 1
+ if brackets == 0:
+ start = tokens[i + 2].end
+ end = tokens[j].start
+ yield ("", source[prev:start])
+ yield (tokens[i + 1].token, source[start:end])
+ prev = end
+ break
+ if prev != len(source):
+ yield ("", source[prev:])
+
+
+def replace_use_references(file_path: Path, callback: Callable[[list[str], str], str]):
+ """Calls 'callback' for each foo::bar reference in `file_path`.
+
+ The callback is called with the reference as an argument and is expected to return the rewritten
+ reference.
+ Additionally, the absolute path in the module tree is provided, taking into account the file
+ path as well as modules defined in the source itself.
+
+ eg.
+ src/foo.rs:
+ ```
+ mod tests {
+ use crate::baz;
+ }
+ ```
+ will call `callback(['foo', 'tests'], 'crate::baz')`
+ """
+ module_parts = list(file_path.parts[:-1])
+ if file_path.stem not in ("mod", "lib"):
+ module_parts.append(file_path.stem)
+
+ with open(file_path, "r") as file:
+ contents = file.read()
+ chunks: list[str] = []
+ for module, source in parse_module_chunks(contents):
+ if module:
+ full_module_parts = module_parts + [module]
+ else:
+ full_module_parts = module_parts
+ chunks.append(
+ re.sub(
+ r"([\w\*\_\$]+\:\:)+[\w\*\_]+",
+ lambda m: callback(full_module_parts, m.group(0)),
+ source,
+ )
+ )
+ with open(file_path, "w") as file:
+ file.write("".join(chunks))
+
+
+@contextmanager
+def chdir(path: Union[Path, str]):
+ origin = Path().absolute()
+ try:
+ os.chdir(path)
+ yield
+ finally:
+ os.chdir(origin)
+
+
+def use_super_instead_of_crate(root: Path):
+ """Expects to be run directly on the src directory and assumes
+ that directory to be the module crate:: refers to."""
+
+ def replace(module: list[str], use: str):
+ # Patch up weird module structure...
+ if len(module) > 1 and module[0] == "win":
+ # Only the listed modules are actually in win::.
+ # The rest is in the top level.
+ if module[1] not in (
+ "file_traits",
+ "syslog",
+ "platform_timer_utils",
+ "file_util",
+ "shm",
+ "wait",
+ "mmap",
+ "stream_channel",
+ "timer",
+ ):
+ del module[0]
+ if len(module) > 0 and module[0] in ("punch_hole", "write_zeros"):
+ module = ["write_zeroes", module[0]]
+
+ if use.startswith("crate::"):
+ new_use = use.replace("crate::", "super::" * len(module))
+ print("::".join(module), use, "->", new_use)
+ return new_use
+ return use
+
+ with chdir(root):
+ for file in Path().glob("**/*.rs"):
+ replace_use_references(file, replace)
+
+
+def main():
+ path = Path("common") / "win_sys_util/src"
+ subprocess.check_call(["git", "checkout", "-f", str(path)])
+
+ # Use rustfmt to re-format use statements to be one per line.
+ subprocess.check_call(
+ ["rustfmt", "+nightly", "--config=imports_granularity=item", f"{path}/lib.rs"]
+ )
+ use_super_instead_of_crate(path)
+ subprocess.check_call(
+ ["rustfmt", "+nightly", "--config=imports_granularity=crate", f"{path}/lib.rs"]
+ )
+
+
+main()
diff --git a/tools/dev_container b/tools/dev_container
new file mode 100755
index 000000000..67a9cfe82
--- /dev/null
+++ b/tools/dev_container
@@ -0,0 +1,107 @@
+#!/usr/bin/env python3
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Usage:
+#
+# To get an interactive shell for development:
+# ./tools/dev_container
+#
+# To run a command in the container, e.g. to run presubmits:
+# ./tools/dev_container ./tools/presubmit
+#
+# The state of the container (including build artifacts) are preserved between
+# calls. To stop the container call:
+# ./tools/dev_container --stop
+#
+# The dev container can also be called with a fresh container for each call that
+# is cleaned up afterwards (e.g. when run by Kokoro):
+#
+# ./tools/dev_container --hermetic CMD
+
+import argparse
+import getpass
+from argh import arg
+from impl.common import CROSVM_ROOT, run_main, cmd, chdir, quoted
+import sys
+import os
+
+CONTAINER_NAME = f"crosvm_dev_{getpass.getuser()}"
+IMAGE_VERSION = (CROSVM_ROOT / "tools/impl/dev_container/version").read_text().strip()
+
+try:
+ docker = cmd(os.environ.get("DOCKER", "docker"))
+except ValueError:
+ docker = cmd("podman")
+
+is_podman = docker.executable.name == "podman"
+
+# Enable interactive mode when running in an interactive terminal.
+TTY_ARGS = "--interactive --tty" if sys.stdin.isatty() else None
+
+
+DOCKER_ARGS = [
+ TTY_ARGS,
+ # Podman will not share devices when `--privileged` is specified
+ "--privileged" if not is_podman else None,
+ # Share crosvm source
+ f"--volume {quoted(CROSVM_ROOT)}:/workspace:rw",
+ # Share devices and syslog
+ "--device /dev/kvm",
+ "--volume /dev/log:/dev/log",
+ "--device /dev/net/tun",
+ "--device /dev/vhost-net",
+ "--device /dev/vhost-vsock",
+ # Use tmpfs in the container for faster performance.
+ "--mount type=tmpfs,destination=/tmp",
+ # For plugin process jail
+ "--mount type=tmpfs,destination=/var/empty",
+ f"gcr.io/crosvm-packages/crosvm_dev:{IMAGE_VERSION}",
+]
+
+
+def container_revision(container_id: str):
+ image = docker("container inspect -f {{.Config.Image}}", container_id).stdout()
+ parts = image.split(":")
+ assert len(parts) == 2, f"Invalid image name {image}"
+ return parts[1]
+
+
+@arg("command", nargs=argparse.REMAINDER)
+def main(command: tuple[str, ...], stop: bool = False, hermetic: bool = False):
+ chdir(CROSVM_ROOT)
+ container_id = docker(f"ps -q -f name={CONTAINER_NAME}").stdout()
+
+ # Start an interactive shell by default
+ if not command:
+ command = ("/bin/bash",)
+
+ command = list(map(quoted, command))
+
+ if stop:
+ if container_id:
+ print(f"Stopping dev-container {container_id}.")
+ docker("rm -f", container_id).fg(quiet=True)
+ else:
+ print(f"Dev-container is not running.")
+ return
+
+ if hermetic:
+ docker(f"run --rm", *DOCKER_ARGS, *command).fg()
+ else:
+ if container_id and container_revision(container_id) != IMAGE_VERSION:
+ print(f"New image is available. Stopping old container ({container_id}).")
+ docker("rm -f", container_id).fg(quiet=True)
+ container_id = None
+
+ if not container_id:
+ container_id = docker(f"run --detach --name {CONTAINER_NAME}", *DOCKER_ARGS).stdout()
+ print(f"Started dev-container ({container_id}).")
+ else:
+ print(f"Using existing dev-container instance ({container_id}).")
+
+ docker("exec", TTY_ARGS, container_id, *command).fg()
+
+
+run_main(main)
diff --git a/tools/examples/.gitignore b/tools/examples/.gitignore
new file mode 100644
index 000000000..c291c0912
--- /dev/null
+++ b/tools/examples/.gitignore
@@ -0,0 +1 @@
+images
diff --git a/tools/examples/README.md b/tools/examples/README.md
new file mode 100644
index 000000000..e1476b413
--- /dev/null
+++ b/tools/examples/README.md
@@ -0,0 +1,6 @@
+# Crosvm on Linux Example
+
+This directory contains an examples for how to run a VM with crosvm on linux.
+
+See [Basic Usage](https://google.github.io/crosvm/running_crosvm/example_usage.html) for a tutorial
+on how to run the examples.
diff --git a/tools/examples/example_desktop b/tools/examples/example_desktop
new file mode 100755
index 000000000..f479d38a5
--- /dev/null
+++ b/tools/examples/example_desktop
@@ -0,0 +1,51 @@
+#!/bin/bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Example VM with a full desktop
+
+set -e
+
+SRC=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
+mkdir -p "$SRC/images/desktop" && cd "$_"
+
+if ! [ -f rootfs ]; then
+ # ANCHOR: build
+ builder_args=(
+ # Create user with no password.
+ --run-command "useradd -m -g sudo -p '' $USER ; chage -d 0 $USER"
+
+ # Configure network. See ./example_network
+ --hostname crosvm-test
+ --copy-in "$SRC/guest/01-netcfg.yaml:/etc/netplan/"
+
+ # Install a desktop environment to launch
+ --install xfce4
+
+ -o rootfs
+ )
+ virt-builder ubuntu-20.04 "${builder_args[@]}"
+ # ANCHOR_END: build
+
+ virt-builder --get-kernel ./rootfs -o .
+fi
+
+# ANCHOR: run
+# Enable the GPU and keyboard/mouse input. Since this will be a much heavier
+# system to run we also need to increase the cpu/memory given to the VM.
+# Note: GDM does not allow you to set your password on first login, you have to
+# log in on the command line first to set a password.
+cargo run --features=gpu,x,virgl_renderer -- run \
+ --cpus 4 \
+ --mem 4096 \
+ --disable-sandbox \
+ --gpu backend=virglrenderer,width=1920,height=1080 \
+ --display-window-keyboard \
+ --display-window-mouse \
+ --tap-name crosvm_tap \
+ --rwroot ./rootfs \
+ --initrd ./initrd.img-* \
+ -p "root=/dev/vda5" \
+ ./vmlinuz-*
+# ANCHOR_END: run
diff --git a/tools/examples/example_network b/tools/examples/example_network
new file mode 100755
index 000000000..a5b410ed2
--- /dev/null
+++ b/tools/examples/example_network
@@ -0,0 +1,44 @@
+#!/bin/bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Example VM with internet access and sshd
+
+set -e
+
+SRC=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
+mkdir -p "$SRC/images/network" && cd "$_"
+
+if ! [ -f rootfs ]; then
+ # ANCHOR: build
+ builder_args=(
+ # Create user with no password.
+ --run-command "useradd -m -g sudo -p '' $USER ; chage -d 0 $USER"
+
+ # Configure network via netplan config in 01-netcfg.yaml
+ --hostname crosvm-test
+ --copy-in "$SRC/guest/01-netcfg.yaml:/etc/netplan/"
+
+ # Install sshd and authorized key for the user.
+ --install openssh-server
+ --ssh-inject "$USER:file:$HOME/.ssh/id_rsa.pub"
+
+ -o rootfs
+ )
+ virt-builder ubuntu-20.04 "${builder_args[@]}"
+ # ANCHOR_END: build
+
+ virt-builder --get-kernel ./rootfs -o .
+fi
+
+# ANCHOR: run
+# Use the previously configured crosvm_tap device for networking.
+cargo run -- run \
+ --disable-sandbox \
+ --rwroot ./rootfs \
+ --initrd ./initrd.img-* \
+ --tap-name crosvm_tap \
+ -p "root=/dev/vda5" \
+ ./vmlinuz-*
+# ANCHOR_END: run
diff --git a/tools/examples/example_simple b/tools/examples/example_simple
new file mode 100755
index 000000000..da649f1a3
--- /dev/null
+++ b/tools/examples/example_simple
@@ -0,0 +1,36 @@
+#!/bin/bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Example VM with a simple ubuntu guest OS but no UI, audio or networking.
+
+set -e
+
+SRC=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
+mkdir -p "$SRC/images/simple" && cd "$_"
+
+if ! [ -f rootfs ]; then
+ # ANCHOR: build
+ # Build a simple ubuntu image and create a user with no password.
+ virt-builder ubuntu-20.04 \
+ --run-command "useradd -m -g sudo -p '' $USER ; chage -d 0 $USER" \
+ -o ./rootfs
+ # ANCHOR_END: build
+
+ # ANCHOR: kernel
+ virt-builder --get-kernel ./rootfs -o .
+ # ANCHOR_END: kernel
+fi
+
+# ANCHOR: run
+# Run crosvm without sandboxing.
+# The rootfs is an image of a partitioned hard drive, so we need to tell
+# the kernel which partition to use (vda5 in case of ubuntu-20.04).
+cargo run --features=all-linux -- run \
+ --disable-sandbox \
+ --rwroot ./rootfs \
+ --initrd ./initrd.img-* \
+ -p "root=/dev/vda5" \
+ ./vmlinuz-*
+# ANCHOR_END: run
diff --git a/tools/examples/guest/01-netcfg.yaml b/tools/examples/guest/01-netcfg.yaml
new file mode 100644
index 000000000..5643b6e5e
--- /dev/null
+++ b/tools/examples/guest/01-netcfg.yaml
@@ -0,0 +1,15 @@
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Configure network with static IP 192.168.10.2
+
+network:
+ version: 2
+ renderer: networkd
+ ethernets:
+ enp0s4:
+ addresses: [192.168.10.2/24]
+ nameservers:
+ addresses: [8.8.8.8]
+ gateway4: 192.168.10.1
diff --git a/tools/examples/setup_network b/tools/examples/setup_network
new file mode 100755
index 000000000..ac53b5388
--- /dev/null
+++ b/tools/examples/setup_network
@@ -0,0 +1,39 @@
+#!/bin/bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Set up networking on the host using a TAP device. This probably works on
+# many ubuntu or debian machines, but highly depends on the existing network
+# configuration.
+
+setup_network() {
+ # ANCHOR: setup_tap
+ sudo ip tuntap add mode tap user "$USER" vnet_hdr crosvm_tap
+ sudo ip addr add 192.168.10.1/24 dev crosvm_tap
+ sudo ip link set crosvm_tap up
+ # ANCHOR_END: setup_tap
+
+ # ANCHOR: setup_routing
+ sudo sysctl net.ipv4.ip_forward=1
+ # Network interface used to connect to the internet.
+ HOST_DEV=$(ip route get 8.8.8.8 | awk -- '{printf $5}')
+ sudo iptables -t nat -A POSTROUTING -o "${HOST_DEV}" -j MASQUERADE
+ sudo iptables -A FORWARD -i "${HOST_DEV}" -o crosvm_tap -m state --state RELATED,ESTABLISHED -j ACCEPT
+ sudo iptables -A FORWARD -i crosvm_tap -o "${HOST_DEV}" -j ACCEPT
+ # ANCHOR_END: setup_routing
+}
+
+echo "This will set up a tap device 'crosvm_tap'."
+echo
+echo "It will run the following commands:"
+echo
+type setup_network | sed '1,3d;$d'
+echo
+read -p "Continue [y/N]? " -r
+if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+ exit 0
+fi
+
+set -ex
+setup_network
diff --git a/tools/fmt b/tools/fmt
new file mode 100755
index 000000000..4d64d90ee
--- /dev/null
+++ b/tools/fmt
@@ -0,0 +1,49 @@
+#!/usr/bin/env python3
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Run `rustfmt` on all Rust code contained in the crosvm workspace, including
+# all commmon/* crates as well.
+#
+# Usage:
+#
+# $ bin/fmt
+#
+# To print a diff and exit 1 if code is not formatted, but without changing any
+# files, use:
+#
+# $ bin/fmt --check
+#
+
+from impl.common import CROSVM_ROOT, parallel, run_main, cmd, chdir
+from pathlib import Path
+
+mdformat = cmd("mdformat")
+rustfmt = cmd(cmd("rustup which rustfmt"))
+
+# How many files to check at once in each thread.
+BATCH_SIZE = 8
+
+
+def find_sources(extension: str):
+ for file in Path(".").glob(f"**/{extension}"):
+ if file.is_relative_to("third_party"):
+ continue
+ if "target" in file.parts:
+ continue
+ yield str(file)
+
+
+def main(check: bool = False):
+ chdir(CROSVM_ROOT)
+ check_arg = "--check" if check else None
+
+ print(f"{'Checking' if check else 'Formatting'}: Rust, Markdown")
+ parallel(
+ *rustfmt(check_arg).foreach(find_sources("*.rs"), batch_size=BATCH_SIZE),
+ *mdformat("--wrap 100", check_arg).foreach(find_sources("*.md"), batch_size=BATCH_SIZE),
+ ).fg()
+
+
+run_main(main)
diff --git a/tools/impl/bindgen-common.sh b/tools/impl/bindgen-common.sh
new file mode 100755
index 000000000..de2203d7a
--- /dev/null
+++ b/tools/impl/bindgen-common.sh
@@ -0,0 +1,74 @@
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Helper functions for bindgen scripts sourced by tools/bindgen-all-the-things.
+
+# virtio-video bindings need some types that are only in the 5.10-arcvm branch.
+# This should be switched to a vanilla kernel when these are upstream.
+export BINDGEN_LINUX="${PWD}/../../third_party/kernel/v5.10-arcvm"
+
+export BINDGEN_PLATFORM2="${PWD}/../../platform2"
+
+export BINDGEN_OPTS=(
+ '--disable-header-comment'
+ '--no-layout-tests'
+ '--no-doc-comments'
+ '--with-derive-default'
+ '--size_t-is-usize'
+)
+
+export BINDGEN_HEADER="/* automatically generated by tools/bindgen-all-the-things */
+
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+"
+
+# Delete definitions of types like __u32 and replace their uses with the equivalent native Rust
+# type, like u32. This ensures that the types are correctly sized on all platforms, unlike the
+# definitions from the system headers, which resolve to C types like short/int/long that may vary
+# across architectures.
+replace_linux_int_types() {
+ sed -E -e '/^pub type __(u|s)(8|16|32|64) =/d' -e 's/__u(8|16|32|64)/u\1/g' -e 's/__s(8|16|32|64)/i\1/g'
+ cat
+}
+
+# Delete definitions of types like __le32 and __be32 and replace their uses with the equivalent
+# data_model types.
+replace_linux_endian_types() {
+ sed -E -e '/^pub type __(l|b)e(16|32|64) =/d' -e 's/__le(16|32|64)/Le\1/g' -e 's/__be(16|32|64)/Be\1/g'
+}
+
+# Wrapper for bindgen used by the various bindgen.sh scripts.
+# Pass extra bindgen options and the .h filename as parameters.
+# Output is produced on stdout and should be redirected to a file.
+bindgen_generate() {
+ echo "${BINDGEN_HEADER}"
+ bindgen "${BINDGEN_OPTS[@]}" "$@"
+}
+
+bindgen_cleanup() {
+ rm -rf "${BINDGEN_LINUX_X86_HEADERS}" "${BINDGEN_LINUX_ARM64_HEADERS}"
+}
+
+# Install Linux kernel headers for x86 and arm into temporary locations. These are used for KVM bindings.
+
+if [[ -z "${BINDGEN_LINUX_X86_HEADERS+x}" || ! -d "${BINDGEN_LINUX_X86_HEADERS}" || \
+ -z "${BINDGEN_LINUX_ARM64_HEADERS+x}" || ! -d "${BINDGEN_LINUX_ARM64_HEADERS}" ]]; then
+ export BINDGEN_LINUX_X86_HEADERS='/tmp/bindgen_linux_x86_headers'
+ export BINDGEN_LINUX_ARM64_HEADERS='/tmp/bindgen_linux_arm64_headers'
+
+ trap bindgen_cleanup EXIT
+
+ echo -n "Installing Linux headers for x86 and arm64..."
+ (
+ cd "${BINDGEN_LINUX}"
+ nproc=$(nproc)
+ make -s headers_install ARCH=x86 INSTALL_HDR_PATH="${BINDGEN_LINUX_X86_HEADERS}" -j "${nproc}"
+ make -s headers_install ARCH=arm64 INSTALL_HDR_PATH="${BINDGEN_LINUX_ARM64_HEADERS}" -j "${nproc}"
+ make -s mrproper
+ )
+ echo " done."
+fi
diff --git a/tools/impl/check_code_hygiene.py b/tools/impl/check_code_hygiene.py
new file mode 100644
index 000000000..dccff160a
--- /dev/null
+++ b/tools/impl/check_code_hygiene.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+from pathlib import Path
+import re
+import subprocess
+import sys
+
+
+USAGE = """\
+Checks code hygiene of a given directory.
+
+The tool verifies that
+- code under given directory has no conditionally compiled platform specific code.
+- crates in current directory, excluding crates in ./common/, do not depend on
+ on sys_util, sys_util_core or on win_sys_util.
+
+To check
+
+ $ ./tools/impl/check_code_hygiene ./common/sys_util_core
+
+On finding platform specific code, the tool prints the file, line number and the
+line containing conditional compilation.
+On finding dependency on sys_util, sys_util_core or on win_sys_util, the tool prints
+the names of crates.
+"""
+
+
+def has_platform_dependent_code(rootdir: Path):
+ """Recursively searches for target os specific code in the given rootdir.
+ Returns false and relative file path if target specific code is found.
+ Returns false and rootdir if rootdir does not exists or is not a directory.
+ Otherwise returns true and empty string is returned.
+
+ Args:
+ rootdir: Base directory path to search for.
+ """
+
+ if not rootdir.is_dir():
+ return False, "'" + str(rootdir) + "' does not exists or is not a directory"
+
+ cfg_unix = "cfg.*unix"
+ cfg_linux = "cfg.*linux"
+ cfg_windows = "cfg.*windows"
+ cfg_android = "cfg.*android"
+ target_os = "target_os = "
+
+ target_os_pattern = re.compile(
+ "%s|%s|%s|%s|%s" % (cfg_android, cfg_linux, cfg_unix, cfg_windows, target_os)
+ )
+
+ for file_path in rootdir.rglob("**/*.rs"):
+ for line_number, line in enumerate(open(file_path, encoding="utf8")):
+ if re.search(target_os_pattern, line):
+ return False, str(file_path) + ":" + str(line_number) + ":" + line
+ return True, ""
+
+
+def is_sys_util_independent():
+ """Recursively searches for that depend on sys_util, sys_util_core or win_util.
+ Does not search crates in common/ as they are allowed to be platform specific.
+ Returns false and a list of crates that depend on those crates. Otherwise
+ returns true and am empty list.
+
+ """
+
+ crates: list[str] = []
+ sys_util_crates = re.compile("sys_util|sys_util_core|win_sys_util")
+ files: list[Path] = list(Path(".").glob("**/Cargo.toml"))
+ files.extend(Path("src").glob("**/*.rs"))
+
+ # Exclude common as it is allowed to depend on sys_util and exclude Cargo.toml
+ # from root directory as it contains workspace related entries for sys_util.
+ files[:] = [
+ file for file in files if not file.is_relative_to("common") and str(file) != "Cargo.toml"
+ ]
+
+ for file_path in files:
+ with open(file_path) as open_file:
+ for line in open_file:
+ if sys_util_crates.match(line):
+ crates.append(str(file_path))
+
+ return not crates, crates
+
+
+def has_crlf_line_endings():
+ """Searches for files with crlf(dos) line endings in a git repo. Returns
+ a list of files having crlf line endings.
+
+ """
+ process = subprocess.Popen(
+ "git ls-files --eol",
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ shell=True,
+ )
+
+ stdout, _ = process.communicate()
+ dos_files: list[str] = []
+
+ if process.returncode != 0:
+ return dos_files
+
+ crlf_re = re.compile("crlf|mixed")
+ assert process.stdout
+ for line in iter(stdout.splitlines()):
+ # A typical output of git ls-files --eol looks like below
+ # i/lf w/lf attr/ vhost/Cargo.toml
+ fields = line.split()
+ if fields and crlf_re.search(fields[0] + fields[1]):
+ dos_files.append(fields[3] + "\n")
+
+ return dos_files
+
+
+def main():
+ parser = argparse.ArgumentParser(usage=USAGE)
+ parser.add_argument("path", type=Path, help="Path of the directory to check.")
+ args = parser.parse_args()
+
+ hygiene, error = has_platform_dependent_code(args.path)
+ if not hygiene:
+ print("Error: Platform dependent code not allowed in sys_util_core crate.")
+ print("Offending line: " + error)
+ sys.exit(-1)
+
+ hygiene, crates = is_sys_util_independent()
+ if not hygiene:
+ print("Error: Following files depend on sys_util, sys_util_core or on win_sys_util")
+ print(crates)
+ sys.exit(-1)
+
+ crlf_endings = has_crlf_line_endings()
+ if crlf_endings:
+ print("Error: Following files have crlf(dos) line encodings")
+ print(*crlf_endings)
+ sys.exit(-1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/impl/common.py b/tools/impl/common.py
new file mode 100644
index 000000000..055ce4ec7
--- /dev/null
+++ b/tools/impl/common.py
@@ -0,0 +1,487 @@
+#!/usr/bin/env python3
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Provides helpers for writing shell-like scripts in Python.
+
+It provides tools to execute commands with similar flexibility to shell scripts and simplifies
+command line arguments using `argh` and provides common flags (e.g. -v and -vv) for all of
+our command line tools.
+
+Refer to the scripts in ./tools for example usage.
+"""
+from __future__ import annotations
+
+import argparse
+import contextlib
+import csv
+import os
+import re
+import shutil
+import subprocess
+import sys
+import traceback
+from io import StringIO
+from multiprocessing.pool import ThreadPool
+from pathlib import Path
+from subprocess import DEVNULL, PIPE, STDOUT # type: ignore
+from typing import Any, Callable, Iterable, NamedTuple, Optional, TypeVar, Union
+
+try:
+ import argh # type: ignore
+except ImportError as e:
+ print("Missing module:", e)
+ print("(Re-)Run ./tools/install-deps to install the required dependencies.")
+ sys.exit(1)
+
+"Root directory of crosvm"
+CROSVM_ROOT = Path(__file__).parent.parent.parent.resolve()
+
+"Cargo.toml file of crosvm"
+CROSVM_TOML = CROSVM_ROOT / "Cargo.toml"
+
+# Ensure that we really found the crosvm root directory
+assert 'name = "crosvm"' in CROSVM_TOML.read_text()
+
+
+PathLike = Union[Path, str]
+
+
+class CommandResult(NamedTuple):
+ """Results of a command execution as returned by Command.run()"""
+
+ stdout: str
+ stderr: str
+ returncode: int
+
+
+class Command(object):
+ """
+ Simplified subprocess handling for shell-like scripts.
+
+ ## Arguments
+
+ Arguments are provided as a list similar to subprocess.run():
+
+ >>> Command('cargo', 'build', '--workspace')
+ Command('cargo', 'build', '--workspace')
+
+ In contrast to subprocess.run, all strings are split by whitespaces similar to bash:
+
+ >>> Command('cargo build --workspace', '--features foo')
+ Command('cargo', 'build', '--workspace', '--features', 'foo')
+
+ In contrast to bash, globs are *not* evaluated, but can easily be provided using Path:
+
+ >>> Command('ls -l', *Path('.').glob('*.toml'))
+ Command('ls', '-l', ...)
+
+ None or False are ignored to make it easy to include conditional arguments:
+
+ >>> all = False
+ >>> Command('cargo build', '--workspace' if all else None)
+ Command('cargo', 'build')
+
+ Commands can be nested, similar to $() subshells in bash. The sub-commands will be executed
+ right away and their output will undergo the usual splitting:
+
+ >>> Command('printf "(%s)"', Command('echo foo bar')).stdout()
+ '(foo)(bar)'
+
+ Arguments can be explicitly quoted to prevent splitting, it applies to both sub-commands
+ as well as strings:
+
+ >>> Command('printf "(%s)"', quoted(Command('echo foo bar'))).stdout()
+ '(foo bar)'
+
+ Commands can also be piped into one another:
+
+ >>> wc = Command('wc')
+ >>> Command('echo "abcd"').pipe(wc('-c')).stdout()
+ '5'
+
+ ## Executing
+
+ Once built, commands can be executed using `Command.fg()`, to run the command in the
+ foreground, visible to the user, or `Command.stdout()` to capture the stdout.
+
+ By default, any non-zero exit code will trigger an Exception and stderr is always directed to
+ the user.
+
+ More complex use-cases are supported with the `Command.run()` or `Command.stream()` methods.
+ A Command instance can also be passed to the subprocess.run() for any use-cases unsupported by
+ this API.
+ """
+
+ def __init__(self, *args: Any, stdin_cmd: Optional[Command] = None):
+ self.args = Command.__parse_cmd(args)
+ self.stdin_cmd = stdin_cmd
+ if len(self.args) > 0:
+ executable = self.args[0]
+ path = shutil.which(executable)
+ if not path:
+ raise ValueError(f'Required program "{executable}" cannot be found in PATH.')
+ elif very_verbose():
+ print(f"Using {executable}: {path}")
+ self.executable = Path(path)
+
+ ### High level execution API
+
+ def fg(
+ self,
+ quiet: bool = False,
+ check: bool = True,
+ ) -> int:
+ """
+ Runs a program in the foreground with output streamed to the user.
+
+ >>> Command('true').fg()
+ 0
+
+ Non-zero exit codes will trigger an Exception
+ >>> Command('false').fg()
+ Traceback (most recent call last):
+ ...
+ subprocess.CalledProcessError: Command 'false' returned non-zero exit status 1.
+
+ But can be disabled:
+
+ >>> Command('false').fg(check=False)
+ 1
+
+ Arguments:
+ quiet: Do not show stdout unless the program failed.
+ check: Raise an exception if the program returned an error code.
+
+ Returns: The return code of the program.
+ """
+ self.__debug_print()
+ if quiet:
+ result = subprocess.run(
+ self.args,
+ stdout=PIPE,
+ stderr=STDOUT,
+ stdin=self.__stdin_stream(),
+ text=True,
+ )
+ else:
+ result = subprocess.run(
+ self.args,
+ stdin=self.__stdin_stream(),
+ )
+
+ if result.returncode != 0:
+ if quiet and result.stdout:
+ print(result.stdout)
+ if check:
+ raise subprocess.CalledProcessError(result.returncode, str(self), result.stdout)
+ return result.returncode
+
+ def stdout(self):
+ """
+ Runs a program and returns stdout. Stderr is still directed to the user.
+ """
+ return self.run(stderr=None).stdout.strip()
+
+ def write_to(self, filename: Path):
+ """
+ Writes all program output (stdout and stderr) to the provided file.
+ """
+ with open(filename, "w") as file:
+ file.write(self.run(stderr=STDOUT).stdout)
+
+ def append_to(self, filename: Path):
+ """
+ Appends all program output (stdout and stderr) to the provided file.
+ """
+ with open(filename, "a") as file:
+ file.write(self.run(stderr=STDOUT).stdout)
+
+ def pipe(self, *args: Any):
+ """
+ Pipes the output of this command into another process.
+
+ The target can either be another Command or the argument list to build a new command.
+ """
+ if len(args) == 1 and isinstance(args[0], Command):
+ cmd = Command(stdin_cmd=self)
+ cmd.args = args[0].args
+ return cmd
+ else:
+ return Command(*args, stdin_cmd=self)
+
+ ### Lower level execution API
+
+ def run(self, check: bool = True, stderr: Optional[int] = PIPE) -> CommandResult:
+ """
+ Runs a program with stdout, stderr and error code returned.
+
+ >>> Command('echo', 'Foo').run()
+ CommandResult(stdout='Foo\\n', stderr='', returncode=0)
+
+ Non-zero exit codes will trigger an Exception by default.
+
+ Arguments:
+ check: Raise an exception if the program returned an error code.
+
+ Returns: CommandResult(stdout, stderr, returncode)
+ """
+ self.__debug_print()
+ result = subprocess.run(
+ self.args,
+ stdout=subprocess.PIPE,
+ stderr=stderr,
+ stdin=self.__stdin_stream(),
+ check=check,
+ text=True,
+ )
+ return CommandResult(result.stdout, result.stderr, result.returncode)
+
+ def stream(self, stderr: Optional[int] = PIPE) -> subprocess.Popen[str]:
+ """
+ Runs a program and returns the Popen object of the running process.
+ """
+ self.__debug_print()
+ return subprocess.Popen(
+ self.args,
+ stdout=subprocess.PIPE,
+ stderr=stderr,
+ stdin=self.__stdin_stream(),
+ text=True,
+ )
+
+ def foreach(self, arguments: Iterable[Any], batch_size: int = 1):
+ """
+ Yields a new command for each entry in `arguments`.
+
+ The argument is appended to each command and is intended to be used in
+ conjunction with `parallel()` to execute a command on a list of arguments in
+ parallel.
+
+ >>> parallel(*cmd('echo').foreach((1, 2, 3))).stdout()
+ ['1', '2', '3']
+
+ Arguments can also be batched by setting batch_size > 1, which will append multiple
+ arguments to each command.
+
+ >>> parallel(*cmd('echo').foreach((1, 2, 3), batch_size=2)).stdout()
+ ['1 2', '3']
+
+ """
+ for batch in batched(arguments, batch_size):
+ yield self(*batch)
+
+ def __call__(self, *args: Any):
+ """Returns a new Command with added arguments.
+
+ >>> cargo = Command('cargo')
+ >>> cargo('clippy')
+ Command('cargo', 'clippy')
+ """
+ cmd = Command()
+ cmd.args = [*self.args, *Command.__parse_cmd(args)]
+ return cmd
+
+ def __iter__(self):
+ """Allows a `Command` to be treated like a list of arguments for subprocess.run()."""
+ return iter(self.args)
+
+ def __str__(self):
+ def fmt_arg(arg: str):
+ # Quote arguments containing spaces.
+ if re.search(r"\s", arg):
+ return f'"{arg}"'
+ return arg
+
+ stdin = ""
+ if self.stdin_cmd:
+ stdin = str(self.stdin_cmd) + " | "
+ return stdin + " ".join(fmt_arg(a) for a in self.args)
+
+ def __repr__(self):
+ stdin = ""
+ if self.stdin_cmd:
+ stdin = ", stdin_cmd=" + repr(self.stdin_cmd)
+ return f"Command({', '.join(repr(a) for a in self.args)}{stdin})"
+
+ ### Private utilities
+
+ def __stdin_stream(self):
+ if self.stdin_cmd:
+ return self.stdin_cmd.stream().stdout
+ return None
+
+ def __debug_print(self):
+ if verbose():
+ print("$", repr(self) if very_verbose() else str(self))
+
+ @staticmethod
+ def __shell_like_split(value: str):
+ """Splits a string by spaces, accounting for escape characters and quoting."""
+ # Re-use csv parses to split by spaces and new lines, while accounting for quoting.
+ for line in csv.reader(StringIO(value), delimiter=" ", quotechar='"'):
+ for arg in line:
+ if arg:
+ yield arg
+
+ @staticmethod
+ def __parse_cmd(args: Iterable[Any]) -> list[str]:
+ """Parses command line arguments for Command."""
+ res = [parsed for arg in args for parsed in Command.__parse_cmd_args(arg)]
+ return res
+
+ @staticmethod
+ def __parse_cmd_args(arg: Any) -> list[str]:
+ """Parses a mixed type command line argument into a list of strings."""
+ if isinstance(arg, Path):
+ return [str(arg)]
+ elif isinstance(arg, QuotedString):
+ return [arg.value]
+ elif isinstance(arg, Command):
+ return [*Command.__shell_like_split(arg.stdout())]
+ elif arg is None or arg is False:
+ return []
+ else:
+ return [*Command.__shell_like_split(str(arg))]
+
+
+class ParallelCommands(object):
+ """
+ Allows commands to be run in parallel.
+
+ >>> parallel(cmd('true'), cmd('false')).fg(check=False)
+ [0, 1]
+
+ >>> parallel(cmd('echo a'), cmd('echo b')).stdout()
+ ['a', 'b']
+ """
+
+ def __init__(self, *commands: Command):
+ self.commands = commands
+
+ def fg(self, quiet: bool = True, check: bool = True):
+ with ThreadPool(os.cpu_count()) as pool:
+ return pool.map(lambda command: command.fg(quiet=quiet, check=check), self.commands)
+
+ def stdout(self):
+ with ThreadPool(os.cpu_count()) as pool:
+ return pool.map(lambda command: command.stdout(), self.commands)
+
+
+@contextlib.contextmanager
+def cwd_context(path: PathLike):
+ """Context for temporarily changing the cwd.
+
+ >>> with cwd('/tmp'):
+ ... os.getcwd()
+ '/tmp'
+
+ """
+ cwd = os.getcwd()
+ try:
+ chdir(path)
+ yield
+ finally:
+ chdir(cwd)
+
+
+def chdir(path: PathLike):
+ if very_verbose():
+ print("cd", path)
+ os.chdir(path)
+
+
+class QuotedString(object):
+ """
+ Prevents the provided string from being split.
+
+ Commands will be executed and their stdout is quoted.
+ """
+
+ def __init__(self, value: Any):
+ if isinstance(value, Command):
+ self.value = value.stdout()
+ else:
+ self.value = str(value)
+
+ def __str__(self):
+ return f'"{self.value}"'
+
+
+T = TypeVar("T")
+
+
+def batched(source: Iterable[T], batch_size: int) -> Iterable[list[T]]:
+ """
+ Returns an iterator over batches of elements from source_list.
+
+ >>> list(batched([1, 2, 3, 4, 5], batch_size=2))
+ [[1, 2], [3, 4], [5]]
+ """
+ source_list = list(source)
+ for index in range(0, len(source_list), batch_size):
+ yield source_list[index : min(index + batch_size, len(source_list))]
+
+
+# Shorthands
+quoted = QuotedString
+cmd = Command
+cwd = cwd_context
+parallel = ParallelCommands
+
+
+def run_main(main_fn: Callable[..., Any]):
+ """
+ Runs the main function using argh to translate command line arguments into function arguments.
+ """
+ try:
+ # Add global verbose arguments
+ parser = argparse.ArgumentParser()
+ __add_verbose_args(parser)
+
+ # Register main method as argh command
+ argh.set_default_command(parser, main_fn) # type: ignore
+
+ # Call main method
+ argh.dispatch(parser) # type: ignore
+ except Exception as e:
+ if verbose():
+ traceback.print_exc()
+ else:
+ print(e)
+ sys.exit(1)
+
+
+def verbose():
+ return very_verbose() or "-v" in sys.argv or "--verbose" in sys.argv
+
+
+def very_verbose():
+ return "-vv" in sys.argv or "--very-verbose" in sys.argv
+
+
+def __add_verbose_args(parser: argparse.ArgumentParser):
+ # This just serves as documentation to argparse. The verbose variables are directly
+ # parsed from argv above to ensure they are accessible early.
+ parser.add_argument(
+ "--verbose",
+ "-v",
+ action="store_true",
+ default=False,
+ help="Print debug output",
+ )
+ parser.add_argument(
+ "--very-verbose",
+ "-vv",
+ action="store_true",
+ default=False,
+ help="Print more debug output",
+ )
+
+
+if __name__ == "__main__":
+ import doctest
+
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
diff --git a/tools/impl/dev_container/Dockerfile b/tools/impl/dev_container/Dockerfile
new file mode 100644
index 000000000..215cf04e8
--- /dev/null
+++ b/tools/impl/dev_container/Dockerfile
@@ -0,0 +1,48 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+ARG RUST_VERSION
+FROM docker.io/rust:${RUST_VERSION}-slim-bullseye
+
+# Use a dedicated target directory so we do not write into the source directory.
+RUN mkdir -p /scratch/cargo_target
+ENV CARGO_TARGET_DIR=/scratch/cargo_target
+
+# Prevent the container from writing root-owned __pycache__ files into the src.
+ENV PYTHONDONTWRITEBYTECODE=1
+
+# Add foreign architectures for cross-compilation.
+RUN dpkg --add-architecture arm64 \
+ && dpkg --add-architecture armhf
+
+# Install dependencies for native builds.
+COPY tools/install-deps /tools/
+RUN apt-get update \
+ && apt-get install --yes sudo \
+ && /tools/install-deps \
+ # Clear apt cache to save space in layer.
+ && rm -rf /var/lib/apt/lists/* \
+ # Delete build artifacts from 'cargo install' to save space in layer.
+ && rm -rf /scratch/cargo_target/*
+
+# Prepare wine64
+RUN ln -sf /usr/bin/wine64-stable /usr/bin/wine64 \
+ && wine64 wineboot
+
+# Install cross-compilation dependencies.
+COPY tools/install-aarch64-deps tools/install-armhf-deps /tools/
+
+RUN apt-get update \
+ && /tools/install-aarch64-deps \
+ && /tools/install-armhf-deps \
+ # Clear apt cache to save space in layer.
+ && rm -rf /var/lib/apt/lists/*
+
+# Prebuild aarch64 VM image for faster startup.
+COPY tools/aarch64vm /tools/
+COPY /tools/impl/testvm.py /tools/impl/
+COPY /tools/impl/testvm/version /tools/impl/testvm/
+RUN /tools/aarch64vm build
+
+VOLUME /workspace
+WORKDIR /workspace
diff --git a/tools/impl/dev_container/Makefile b/tools/impl/dev_container/Makefile
new file mode 100644
index 000000000..0b9084b08
--- /dev/null
+++ b/tools/impl/dev_container/Makefile
@@ -0,0 +1,36 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# To locally build the docker container for usage with dev_container:
+#
+# make -C tools/impl/dev_container crosvm_dev
+#
+# To upload a new version of the container, uprev the `version` file and run;
+#
+# make -C tools/impl/dev_container upload
+#
+# You need write permission to the crosvm-packages cloud registry to do so.
+
+export DOCKER_BUILDKIT=1
+
+TAG_BASE=gcr.io/crosvm-packages
+VERSION=$(shell cat version)
+RUST_VERSION=$(shell cat ../../../rust-toolchain | grep channel | grep -E -o "[0-9\.]+")
+BUILD_CONTEXT=$(shell realpath ../../../)
+
+DOCKER ?= docker
+
+all: crosvm_dev
+
+upload: all
+ $(DOCKER) push $(TAG_BASE)/crosvm_dev:$(VERSION)
+
+crosvm_dev:
+ $(DOCKER) build \
+ --build-arg RUST_VERSION=$(RUST_VERSION) \
+ -t $(TAG_BASE)/$@:$(VERSION) \
+ -f Dockerfile \
+ $(BUILD_CONTEXT)
+
+.PHONY: all crosvm_dev upload
diff --git a/tools/impl/dev_container/version b/tools/impl/dev_container/version
new file mode 100644
index 000000000..fa0363b79
--- /dev/null
+++ b/tools/impl/dev_container/version
@@ -0,0 +1 @@
+r0008
diff --git a/tools/impl/test_config.py b/tools/impl/test_config.py
new file mode 100755
index 000000000..b85107db0
--- /dev/null
+++ b/tools/impl/test_config.py
@@ -0,0 +1,151 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import enum
+import os
+
+
+class TestOption(enum.Enum):
+ # Do not build tests for all, or just some platforms.
+ DO_NOT_BUILD = "do_not_build"
+ DO_NOT_BUILD_AARCH64 = "do_not_build_aarch64"
+ DO_NOT_BUILD_ARMHF = "do_not_build_armhf"
+ DO_NOT_BUILD_X86_64 = "do_not_build_x86_64"
+ DO_NOT_BUILD_WIN64 = "do_not_build_win64"
+
+ # Build tests, but do not run for all, or just some platforms.
+ DO_NOT_RUN = "do_not_run"
+ DO_NOT_RUN_ARMHF = "do_not_run_armhf"
+ DO_NOT_RUN_AARCH64 = "do_not_run_aarch64"
+ DO_NOT_RUN_X86_64 = "do_not_run_x86_64"
+
+ # Do not run on foreign architecture kernel (e.g. running armhf on aarch64
+ # or running aarch64 on the host with user-space emulation)
+ # This option is expected on tests that use kernel APIs not supported in
+ # user space emulation or in armhf compatibility mode (most notably
+ # /dev/kvm usage)
+ DO_NOT_RUN_ON_FOREIGN_KERNEL = "do_not_run_on_foreign_kernel"
+
+ # Run tests single-threaded
+ SINGLE_THREADED = "single_threaded"
+
+ # This test needs longer than usual to run.
+ LARGE = "large"
+
+# Configuration to restrict how and where tests of a certain crate can
+# be build and run.
+#
+# Please add a bug number when restricting a tests.
+
+# This is just too big to keep in main list for now
+WIN64_DISABLED_CRATES = [
+ "aarch64",
+ "acpi_tables",
+ "arch",
+ "assertions",
+ "audio_streams",
+ "bit_field_derive",
+ "bit_field",
+ "cros_async",
+ "cros_asyncv2",
+ "cros-fuzz",
+ "crosvm_control",
+ "crosvm_plugin",
+ "crosvm-fuzz",
+ "crosvm",
+ "data_model",
+ "devices",
+ "disk",
+ "ffi",
+ "fuse",
+ "fuzz",
+ "gpu_display",
+ "hypervisor",
+ "integration_tests",
+ "io_uring",
+ "kernel_cmdline",
+ "kernel_loader",
+ "kvm_sys",
+ "kvm",
+ "libcras_stub",
+ "libvda",
+ "linux_input_sys",
+ "minijail-sys",
+ "minijail",
+ "net_sys",
+ "net_util",
+ "p9",
+ "power_monitor",
+ "protos",
+ "qcow_utils",
+ "resources",
+ "rutabaga_gfx",
+ "rutabaga_gralloc",
+ "sync",
+ "sys_util",
+ "system_api_stub",
+ "tpm2-sys",
+ "tpm2",
+ "usb_sys",
+ "usb_util",
+ "vfio_sys",
+ "vhost",
+ "virtio_sys",
+ "vm_control",
+ "vm_memory",
+ "vmm_vhost",
+ "wire_format_derive",
+ "x86_64",
+ ]
+
+CRATE_OPTIONS: dict[str, list[TestOption]] = {
+ "base": [TestOption.SINGLE_THREADED, TestOption.LARGE],
+ "cros_async": [TestOption.LARGE],
+ "crosvm": [TestOption.SINGLE_THREADED],
+ "crosvm_plugin": [
+ TestOption.DO_NOT_BUILD_AARCH64,
+ TestOption.DO_NOT_BUILD_ARMHF,
+ ],
+ "crosvm-fuzz": [TestOption.DO_NOT_BUILD], # b/194499769
+ "devices": [
+ TestOption.SINGLE_THREADED,
+ TestOption.LARGE,
+ TestOption.DO_NOT_RUN_ON_FOREIGN_KERNEL,
+ ],
+ "disk": [TestOption.DO_NOT_RUN_AARCH64, TestOption.DO_NOT_RUN_ARMHF], # b/202294155
+ "fuzz": [TestOption.DO_NOT_BUILD],
+ "hypervisor": [
+ TestOption.DO_NOT_RUN_AARCH64,
+ TestOption.DO_NOT_RUN_ON_FOREIGN_KERNEL,
+ ], # b/181672912
+ "integration_tests": [ # b/180196508
+ TestOption.SINGLE_THREADED,
+ TestOption.LARGE,
+ TestOption.DO_NOT_RUN_AARCH64,
+ TestOption.DO_NOT_RUN_ON_FOREIGN_KERNEL,
+ ],
+ "io_uring": [TestOption.DO_NOT_RUN], # b/202294403
+ "kvm_sys": [TestOption.DO_NOT_RUN_ON_FOREIGN_KERNEL],
+ "kvm": [
+ TestOption.DO_NOT_RUN_AARCH64,
+ TestOption.DO_NOT_RUN_ON_FOREIGN_KERNEL,
+ ], # b/181674144
+ "libcrosvm_control": [TestOption.DO_NOT_BUILD_ARMHF], # b/210015864
+ "libvda": [TestOption.DO_NOT_BUILD], # b/202293971
+ "rutabaga_gfx": [TestOption.DO_NOT_BUILD_ARMHF], # b/210015864
+ "sys_util": [TestOption.SINGLE_THREADED],
+ "sys_util_core": [TestOption.SINGLE_THREADED],
+ "vhost": [TestOption.DO_NOT_RUN_ON_FOREIGN_KERNEL],
+ "vm_control": [TestOption.DO_NOT_BUILD_ARMHF], # b/210015864
+}
+
+for name in WIN64_DISABLED_CRATES:
+ CRATE_OPTIONS[name] = CRATE_OPTIONS.get(name, []) + [TestOption.DO_NOT_BUILD_WIN64]
+
+BUILD_FEATURES: dict[str, str] = {
+ "x86_64": "linux-x86_64",
+ "aarch64": "linux-aarch64",
+ "armhf": "linux-armhf",
+ "win64": "win64",
+}
diff --git a/tools/impl/test_runner.py b/tools/impl/test_runner.py
new file mode 100644
index 000000000..6cadf54ee
--- /dev/null
+++ b/tools/impl/test_runner.py
@@ -0,0 +1,456 @@
+#!/usr/bin/env python3
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import argparse
+import functools
+import json
+import os
+import random
+import subprocess
+import sys
+from multiprocessing import Pool
+from pathlib import Path
+from typing import Dict, Iterable, List, NamedTuple
+import typing
+
+import test_target
+from test_target import TestTarget
+import testvm
+from test_config import CRATE_OPTIONS, TestOption, BUILD_FEATURES
+from check_code_hygiene import (
+ has_platform_dependent_code,
+ has_crlf_line_endings,
+)
+
+USAGE = """\
+Runs tests for crosvm locally, in a vm or on a remote device.
+
+To build and run all tests locally:
+
+ $ ./tools/run_tests --target=host
+
+To cross-compile tests for aarch64 and run them on a built-in VM:
+
+ $ ./tools/run_tests --target=vm:aarch64
+
+The VM will be automatically set up and booted. It will remain running between
+test runs and can be managed with `./tools/aarch64vm`.
+
+Tests can also be run on a remote device via SSH. However it is your
+responsiblity that runtime dependencies of crosvm are provided.
+
+ $ ./tools/run_tests --target=ssh:hostname
+
+The default test target can be managed with `./tools/set_test_target`
+
+To see full build and test output, add the `-v` or `--verbose` flag.
+"""
+
+Arch = test_target.Arch
+
+# Print debug info. Overriden by -v
+VERBOSE = False
+
+# Timeouts for tests to prevent them from running too long.
+TEST_TIMEOUT_SECS = 60
+LARGE_TEST_TIMEOUT_SECS = 120
+
+# Double the timeout if the test is running in an emulation environment, which will be
+# significantly slower than native environments.
+EMULATION_TIMEOUT_MULTIPLIER = 2
+
+# Number of parallel processes for executing tests.
+PARALLELISM = 4
+
+CROSVM_ROOT = Path(__file__).parent.parent.parent.resolve()
+COMMON_ROOT = CROSVM_ROOT / "common"
+
+
+class ExecutableResults(object):
+ """Container for results of a test executable."""
+
+ def __init__(self, name: str, success: bool, test_log: str):
+ self.name = name
+ self.success = success
+ self.test_log = test_log
+
+
+class Executable(NamedTuple):
+ """Container for info about an executable generated by cargo build/test."""
+
+ binary_path: Path
+ crate_name: str
+ cargo_target: str
+ kind: str
+ is_test: bool
+ is_fresh: bool
+ arch: Arch
+
+ @property
+ def name(self):
+ return f"{self.crate_name}:{self.cargo_target}"
+
+
+class Crate(NamedTuple):
+ """Container for info about crate."""
+
+ name: str
+ path: Path
+
+
+def get_workspace_excludes(target_arch: Arch):
+ for crate, options in CRATE_OPTIONS.items():
+ if TestOption.DO_NOT_BUILD in options:
+ yield crate
+ elif TestOption.DO_NOT_BUILD_X86_64 in options and target_arch == "x86_64":
+ yield crate
+ elif TestOption.DO_NOT_BUILD_AARCH64 in options and target_arch == "aarch64":
+ yield crate
+ elif TestOption.DO_NOT_BUILD_ARMHF in options and target_arch == "armhf":
+ yield crate
+ elif TestOption.DO_NOT_BUILD_WIN64 in options and target_arch == "win64":
+ yield crate
+
+
+def should_run_executable(executable: Executable, target_arch: Arch):
+ options = CRATE_OPTIONS.get(executable.crate_name, [])
+ if TestOption.DO_NOT_RUN in options:
+ return False
+ if TestOption.DO_NOT_RUN_X86_64 in options and target_arch == "x86_64":
+ return False
+ if TestOption.DO_NOT_RUN_AARCH64 in options and target_arch == "aarch64":
+ return False
+ if TestOption.DO_NOT_RUN_ARMHF in options and target_arch == "armhf":
+ return False
+ if TestOption.DO_NOT_RUN_ON_FOREIGN_KERNEL in options and target_arch != executable.arch:
+ return False
+ return True
+
+
+def list_common_crates(target_arch: Arch):
+ excluded_crates = list(get_workspace_excludes(target_arch))
+ for path in COMMON_ROOT.glob("**/Cargo.toml"):
+ if not path.parent.name in excluded_crates:
+ yield Crate(name=path.parent.name, path=path.parent)
+
+
+def exclude_crosvm(target_arch: Arch):
+ return "crosvm" in get_workspace_excludes(target_arch)
+
+
+def cargo(
+ cargo_command: str, cwd: Path, flags: list[str], env: dict[str, str], build_arch: Arch
+) -> Iterable[Executable]:
+ """
+ Executes a cargo command and returns the list of test binaries generated.
+
+ The build log will be hidden by default and only printed if the build
+ fails. In VERBOSE mode the output will be streamed directly.
+
+ Note: Exits the program if the build fails.
+ """
+ cmd = [
+ "cargo",
+ cargo_command,
+ "--message-format=json-diagnostic-rendered-ansi",
+ *flags,
+ ]
+ if VERBOSE:
+ print("$", " ".join(cmd))
+ process = subprocess.Popen(
+ cmd,
+ cwd=cwd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ env=env,
+ )
+
+ messages: List[str] = []
+
+ # Read messages as cargo is running.
+ assert process.stdout
+ for line in iter(process.stdout.readline, ""):
+ # any non-json line is a message to print
+ if not line.startswith("{"):
+ if VERBOSE:
+ print(line.rstrip())
+ messages.append(line.rstrip())
+ continue
+ json_line = json.loads(line)
+
+ # 'message' type lines will be printed
+ if json_line.get("message"):
+ message = json_line.get("message").get("rendered")
+ if VERBOSE:
+ print(message)
+ messages.append(message)
+
+ # Collect info about test executables produced
+ elif json_line.get("executable"):
+ yield Executable(
+ Path(json_line.get("executable")),
+ crate_name=json_line.get("package_id", "").split(" ")[0],
+ cargo_target=json_line.get("target").get("name"),
+ kind=json_line.get("target").get("kind")[0],
+ is_test=json_line.get("profile", {}).get("test", False),
+ is_fresh=json_line.get("fresh", False),
+ arch=build_arch,
+ )
+
+ if process.wait() != 0:
+ if not VERBOSE:
+ for message in messages:
+ print(message)
+ sys.exit(-1)
+
+
+def cargo_build_executables(
+ flags: list[str],
+ build_arch: Arch,
+ cwd: Path = Path("."),
+ env: Dict[str, str] = {},
+) -> Iterable[Executable]:
+ """Build all test binaries for the given list of crates."""
+ # Run build first, to make sure compiler errors of building non-test
+ # binaries are caught.
+ yield from cargo("build", cwd, flags, env, build_arch)
+
+ # Build all tests and return the collected executables
+ yield from cargo("test", cwd, ["--no-run", *flags], env, build_arch)
+
+
+def build_common_crate(build_env: dict[str, str], build_arch: Arch, crate: Crate):
+ print(f"Building tests for: common/{crate.name}")
+ return list(cargo_build_executables([], build_arch, env=build_env, cwd=crate.path))
+
+
+def build_all_binaries(target: TestTarget, build_arch: Arch):
+ """Discover all crates and build them."""
+ build_env = os.environ.copy()
+ build_env.update(test_target.get_cargo_env(target, build_arch))
+
+ print("Building crosvm workspace")
+ yield from cargo_build_executables(
+ [
+ "--features=" + BUILD_FEATURES[build_arch],
+ "--verbose",
+ "--workspace",
+ *[f"--exclude={crate}" for crate in get_workspace_excludes(build_arch)],
+ ],
+ build_arch,
+ cwd=CROSVM_ROOT,
+ env=build_env,
+ )
+
+ with Pool(PARALLELISM) as pool:
+ for executables in pool.imap(
+ functools.partial(build_common_crate, build_env, build_arch),
+ list_common_crates(build_arch),
+ ):
+ yield from executables
+
+
+def is_emulated(target: TestTarget, executable: Executable) -> bool:
+ if target.is_host:
+ # User-space emulation can run foreing-arch executables on the host.
+ return executable.arch != target.arch
+ elif target.vm:
+ return target.vm == "aarch64"
+ return False
+
+
+def get_test_timeout(target: TestTarget, executable: Executable):
+ large = TestOption.LARGE in CRATE_OPTIONS.get(executable.crate_name, [])
+ timeout = LARGE_TEST_TIMEOUT_SECS if large else TEST_TIMEOUT_SECS
+ if is_emulated(target, executable):
+ return timeout * EMULATION_TIMEOUT_MULTIPLIER
+ else:
+ return timeout
+
+
+def execute_test(target: TestTarget, executable: Executable):
+ """
+ Executes a single test on the given test targed
+
+ Note: This function is run in a multiprocessing.Pool.
+
+ Test output is hidden unless the test fails or VERBOSE mode is enabled.
+ """
+ options = CRATE_OPTIONS.get(executable.crate_name, [])
+ args: list[str] = []
+ if TestOption.SINGLE_THREADED in options:
+ args += ["--test-threads=1"]
+
+ binary_path = executable.binary_path
+
+ if executable.arch == "win64" and executable.kind != "proc-macro" and os.name != "nt":
+ args.insert(0, binary_path)
+ binary_path = "wine64"
+
+
+ # proc-macros and their tests are executed on the host.
+ if executable.kind == "proc-macro":
+ target = TestTarget("host")
+
+ if VERBOSE:
+ print(f"Running test {executable.name} on {target}...")
+ try:
+ # Pipe stdout/err to be printed in the main process if needed.
+ test_process = test_target.exec_file_on_target(
+ target,
+ binary_path,
+ args=args,
+ timeout=get_test_timeout(target, executable),
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ )
+ return ExecutableResults(
+ executable.name,
+ test_process.returncode == 0,
+ test_process.stdout,
+ )
+ except subprocess.TimeoutExpired as e:
+ # Append a note about the timeout to the stdout of the process.
+ msg = f"\n\nProcess timed out after {e.timeout}s\n"
+ return ExecutableResults(
+ executable.name,
+ False,
+ e.stdout.decode("utf-8") + msg,
+ )
+
+
+def execute_all(
+ executables: list[Executable],
+ target: test_target.TestTarget,
+ repeat: int,
+):
+ """Executes all tests in the `executables` list in parallel."""
+ executables = [e for e in executables if should_run_executable(e, target.arch)]
+ if repeat > 1:
+ executables = executables * repeat
+ random.shuffle(executables)
+
+ sys.stdout.write(f"Running {len(executables)} test binaries on {target}")
+ sys.stdout.flush()
+ with Pool(PARALLELISM) as pool:
+ for result in pool.imap(functools.partial(execute_test, target), executables):
+ if not result.success or VERBOSE:
+ msg = "passed" if result.success else "failed"
+ print()
+ print("--------------------------------")
+ print("-", result.name, msg)
+ print("--------------------------------")
+ print(result.test_log)
+ else:
+ sys.stdout.write(".")
+ sys.stdout.flush()
+ yield result
+ print()
+
+
+def find_crosvm_binary(executables: list[Executable]):
+ for executable in executables:
+ if not executable.is_test and executable.cargo_target == "crosvm":
+ return executable
+ raise Exception("Cannot find crosvm executable")
+
+
+def main():
+ parser = argparse.ArgumentParser(usage=USAGE)
+ parser.add_argument(
+ "--verbose",
+ "-v",
+ action="store_true",
+ default=False,
+ help="Print all test output.",
+ )
+ parser.add_argument(
+ "--target",
+ help="Execute tests on the selected target. See ./tools/set_test_target",
+ )
+ parser.add_argument(
+ "--arch",
+ choices=typing.get_args(Arch),
+ help="Target architecture to build for.",
+ )
+ parser.add_argument(
+ "--build-only",
+ action="store_true",
+ )
+ parser.add_argument(
+ "--repeat",
+ type=int,
+ default=1,
+ help="Repeat each test N times to check for flakes.",
+ )
+ args = parser.parse_args()
+
+ global VERBOSE
+ VERBOSE = args.verbose # type: ignore
+ os.environ["RUST_BACKTRACE"] = "1"
+
+ target = (
+ test_target.TestTarget(args.target) if args.target else test_target.TestTarget.default()
+ )
+ print("Test target:", target)
+
+ build_arch = args.arch or target.arch
+ print("Building for architecture:", build_arch)
+
+ # Start booting VM while we build
+ if target.vm:
+ testvm.build_if_needed(target.vm)
+ testvm.up(target.vm)
+
+ hygiene, error = has_platform_dependent_code(Path("common/sys_util_core"))
+ if not hygiene:
+ print("Error: Platform dependent code not allowed in sys_util_core crate.")
+ print("Offending line: " + error)
+ sys.exit(-1)
+
+ crlf_endings = has_crlf_line_endings()
+ if crlf_endings:
+ print("Error: Following files have crlf(dos) line encodings")
+ print(*crlf_endings)
+ sys.exit(-1)
+
+ executables = list(build_all_binaries(target, build_arch))
+
+ if args.build_only:
+ print("Not running tests as requested.")
+ sys.exit(0)
+
+ # Upload dependencies plus the main crosvm binary for integration tests if the
+ # crosvm binary is not excluded from testing.
+ extra_files = (
+ [find_crosvm_binary(executables).binary_path] if not exclude_crosvm(build_arch) else []
+ )
+
+ test_target.prepare_target(target, extra_files=extra_files)
+
+ # Execute all test binaries
+ test_executables = [e for e in executables if e.is_test]
+ all_results = list(execute_all(test_executables, target, repeat=args.repeat))
+
+ failed = [r for r in all_results if not r.success]
+ if len(failed) == 0:
+ print("All tests passed.")
+ sys.exit(0)
+ else:
+ print(f"{len(failed)} of {len(all_results)} tests failed:")
+ for result in failed:
+ print(f" {result.name}")
+ sys.exit(-1)
+
+
+if __name__ == "__main__":
+ try:
+ main()
+ except subprocess.CalledProcessError as e:
+ print("Command failed:", e.cmd)
+ print(e.stdout)
+ print(e.stderr)
+ sys.exit(-1)
diff --git a/tools/impl/test_target.py b/tools/impl/test_target.py
new file mode 100755
index 000000000..e6098c176
--- /dev/null
+++ b/tools/impl/test_target.py
@@ -0,0 +1,360 @@
+#!/usr/bin/env python3
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file
+import argparse
+import platform
+import subprocess
+from pathlib import Path
+from typing import Any, Literal, Optional, cast
+import typing
+import sys
+import testvm
+import os
+
+USAGE = """Choose to run tests locally, in a vm or on a remote machine.
+
+To set the default test target to run on one of the build-in VMs:
+
+ ./tools/set_test_target vm:aarch64 && source .envrc
+
+Then as usual run cargo or run_tests:
+
+ ./tools/run_tests
+ cargo test
+
+The command will prepare the VM for testing (e.g. upload required shared
+libraries for running rust tests) and set up run_tests as well as cargo
+to build for the test target and execute tests on it.
+
+Arbitrary SSH remotes can be used for running tests as well. e.g.
+
+ ./tools/set_test_target ssh:remotehost
+
+The `remotehost` needs to be properly configured for passwordless
+authentication.
+
+Tip: Use http://direnv.net to automatically load the envrc file instead of
+having to source it after each call.
+"""
+
+SCRIPT_PATH = Path(__file__).resolve()
+SCRIPT_DIR = SCRIPT_PATH.parent.resolve()
+TESTVM_DIR = SCRIPT_DIR.parent.joinpath("testvm")
+TARGET_DIR = testvm.cargo_target_dir().joinpath("crosvm_tools")
+ENVRC_PATH = SCRIPT_DIR.parent.parent.joinpath(".envrc")
+
+Arch = Literal["x86_64", "aarch64", "armhf", "win64"]
+
+# Enviroment variables needed for building with cargo
+BUILD_ENV = {
+ "PKG_CONFIG_aarch64_unknown_linux_gnu": "aarch64-linux-gnu-pkg-config",
+ "PKG_CONFIG_armv7_unknown_linux_gnueabihf": "arm-linux-gnueabihf-pkg-config",
+ "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER": "aarch64-linux-gnu-gcc",
+}
+
+
+class Ssh:
+ """Wrapper around subprocess to execute commands remotely via SSH."""
+
+ hostname: str
+ opts: list[str]
+
+ def __init__(self, hostname: str, opts: list[str] = []):
+ self.hostname = hostname
+ self.opts = opts
+
+ def run(self, cmd: str, **kwargs: Any):
+ """Equivalent of subprocess.run"""
+ return subprocess.run(
+ [
+ "ssh",
+ self.hostname,
+ *self.opts,
+ # Do not create a tty. This will mess with terminal output
+ # when running multiple subprocesses.
+ "-T",
+ # Tell sh to kill children on hangup.
+ f"shopt -s huponexit; {cmd}",
+ ],
+ **kwargs,
+ )
+
+ def check_output(self, cmd: str):
+ """Equivalent of subprocess.check_output"""
+ return subprocess.run(
+ ["ssh", self.hostname, *self.opts, "-T", cmd],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ text=True,
+ check=True,
+ ).stdout
+
+ def upload_files(self, files: list[Path], remote_dir: str = "", quiet: bool = False):
+ """Wrapper around SCP."""
+ flags: list[str] = []
+ if quiet:
+ flags.append("-q")
+ scp_cmd = [
+ "scp",
+ *flags,
+ *self.opts,
+ *files,
+ f"{self.hostname}:{remote_dir}",
+ ]
+ subprocess.run(scp_cmd, check=True)
+
+
+class TestTarget(object):
+ """A test target can be the local host, a VM or a remote devica via SSH."""
+
+ target_str: str
+ is_host: bool = False
+ vm: Optional[testvm.Arch] = None
+ ssh: Optional[Ssh] = None
+ __arch: Optional[Arch] = None
+
+ @classmethod
+ def default(cls):
+ return cls(os.environ.get("CROSVM_TEST_TARGET", "host"))
+
+ def __init__(self, target_str: str):
+ """target_str can be "vm:arch", "ssh:hostname" or just "host" """
+ self.target_str = target_str
+ parts = target_str.split(":")
+ if len(parts) == 2 and parts[0] == "vm":
+ arch: testvm.Arch = parts[1] # type: ignore
+ self.vm = arch
+ self.ssh = Ssh("localhost", testvm.ssh_cmd_args(arch))
+ elif len(parts) == 2 and parts[0] == "ssh":
+ self.ssh = Ssh(parts[1])
+ elif len(parts) == 1 and parts[0] == "host":
+ self.is_host = True
+ else:
+ raise Exception(f"Invalid target {target_str}")
+
+ @property
+ def arch(self) -> Arch:
+ if not self.__arch:
+ if self.vm:
+ self.__arch = self.vm
+ elif self.ssh:
+ self.__arch = cast(Arch, self.ssh.check_output("arch").strip())
+ else:
+ self.__arch = cast(Arch, platform.machine())
+ return self.__arch
+
+ def __str__(self):
+ return self.target_str
+
+
+def find_rust_lib_dir():
+ cargo_path = Path(subprocess.check_output(["rustup", "which", "cargo"], text=True))
+ if os.name == "posix":
+ return cargo_path.parent.parent.joinpath("lib")
+ elif os.name == "nt":
+ return cargo_path.parent
+ else:
+ raise Exception(f"Unsupported build target: {os.name}")
+
+
+def find_rust_libs():
+ lib_dir = find_rust_lib_dir()
+ yield from lib_dir.glob("libstd-*")
+ yield from lib_dir.glob("libtest-*")
+
+
+def prepare_remote(ssh: Ssh, extra_files: list[Path] = []):
+ print("Preparing remote")
+ ssh.upload_files(list(find_rust_libs()) + extra_files)
+ pass
+
+
+def prepare_target(target: TestTarget, extra_files: list[Path] = []):
+ if target.vm:
+ testvm.build_if_needed(target.vm)
+ testvm.wait(target.vm)
+ if target.ssh:
+ prepare_remote(target.ssh, extra_files)
+
+
+def get_cargo_build_target(arch: Arch):
+ if os.name == "posix":
+ if arch == "armhf":
+ return "armv7-unknown-linux-gnueabihf"
+ elif arch == "win64":
+ return "x86_64-pc-windows-gnu"
+ else:
+ return f"{arch}-unknown-linux-gnu"
+ elif os.name == "nt":
+ if arch == "win64":
+ return f"x86_64-pc-windows-msvc"
+ else:
+ return f"{arch}-pc-windows-msvc"
+ else:
+ raise Exception(f"Unsupported build target: {os.name}")
+
+
+def get_cargo_env(target: TestTarget, build_arch: Arch):
+ """Environment variables to make cargo use the test target."""
+ env: dict[str, str] = BUILD_ENV.copy()
+ cargo_target = get_cargo_build_target(build_arch)
+ upper_target = cargo_target.upper().replace("-", "_")
+ if build_arch != platform.machine():
+ env["CARGO_BUILD_TARGET"] = cargo_target
+ if not target.is_host:
+ env[f"CARGO_TARGET_{upper_target}_RUNNER"] = f"{SCRIPT_PATH} exec-file"
+ env["CROSVM_TEST_TARGET"] = str(target)
+ return env
+
+
+def write_envrc(values: dict[str, str]):
+ with open(ENVRC_PATH, "w") as file:
+ for key, value in values.items():
+ file.write(f'export {key}="{value}"\n')
+
+
+def set_target(target: TestTarget, build_arch: Optional[Arch]):
+ prepare_target(target)
+ if not build_arch:
+ build_arch = target.arch
+ write_envrc(get_cargo_env(target, build_arch))
+ print(f"Test target: {target}")
+ print(f"Target Architecture: {build_arch}")
+
+
+def exec_file_on_target(
+ target: TestTarget,
+ filepath: Path,
+ timeout: int,
+ args: list[str] = [],
+ extra_files: list[Path] = [],
+ **kwargs: Any,
+):
+ """Executes a file on the test target.
+
+ The file is uploaded to the target's home directory (if it's an ssh or vm
+ target) plus any additional extra files provided, then executed and
+ deleted afterwards.
+
+ If the test target is 'host', files will just be executed locally.
+
+ Timeouts will trigger a subprocess.TimeoutExpired exception, which contanins
+ any output produced by the subprocess until the timeout.
+ """
+ env = os.environ.copy()
+ if not target.ssh:
+ # Allow test binaries to find rust's test libs.
+ if os.name == "posix":
+ env["LD_LIBRARY_PATH"] = str(find_rust_lib_dir())
+ elif os.name == "nt":
+ if not env["PATH"]:
+ env["PATH"] = str(find_rust_lib_dir())
+ else:
+ env["PATH"] += ";" + str(find_rust_lib_dir())
+ else:
+ raise Exception(f"Unsupported build target: {os.name}")
+
+ cmd_line = [str(filepath), *args]
+ return subprocess.run(
+ cmd_line,
+ env=env,
+ timeout=timeout,
+ text=True,
+ **kwargs,
+ )
+ else:
+ filename = Path(filepath).name
+ target.ssh.upload_files([filepath] + extra_files, quiet=True)
+ try:
+ result = target.ssh.run(
+ f"chmod +x {filename} && sudo LD_LIBRARY_PATH=. ./{filename} {' '.join(args)}",
+ timeout=timeout,
+ text=True,
+ **kwargs,
+ )
+ finally:
+ # Remove uploaded files regardless of test result
+ all_filenames = [filename] + [f.name for f in extra_files]
+ target.ssh.check_output(f"sudo rm {' '.join(all_filenames)}")
+ return result
+
+
+def exec_file(
+ target: TestTarget,
+ filepath: Path,
+ args: list[str] = [],
+ timeout: int = 60,
+ extra_files: list[Path] = [],
+):
+ if not filepath.exists():
+ raise Exception(f"File does not exist: {filepath}")
+
+ print(f"Executing `{Path(filepath).name} {' '.join(args)}` on {target}")
+ try:
+ sys.exit(exec_file_on_target(target, filepath, timeout, args, extra_files).returncode)
+ except subprocess.TimeoutExpired as e:
+ print(f"Process timed out after {e.timeout}s")
+
+
+def main():
+ COMMANDS = [
+ "set",
+ "exec-file",
+ ]
+
+ parser = argparse.ArgumentParser(usage=USAGE)
+ parser.add_argument("command", choices=COMMANDS)
+ parser.add_argument("--target", type=str, help="Override default test target.")
+ parser.add_argument(
+ "--arch",
+ choices=typing.get_args(Arch),
+ help="Override target build architecture.",
+ )
+ parser.add_argument(
+ "--extra-files",
+ type=str,
+ nargs="*",
+ default=[],
+ help="Additional files required by the binary to execute.",
+ )
+ parser.add_argument(
+ "--timeout",
+ type=int,
+ default=60,
+ help="Kill the process after the specified timeout.",
+ )
+ parser.add_argument("remainder", nargs=argparse.REMAINDER)
+ args = parser.parse_args()
+
+ if args.command == "set":
+ if len(args.remainder) != 1:
+ parser.error("Need to specify a target.")
+ set_target(TestTarget(args.remainder[0]), args.arch)
+ return
+
+ if args.target:
+ target = TestTarget(args.target)
+ else:
+ target = TestTarget.default()
+
+ if args.command == "exec-file":
+ if len(args.remainder) < 1:
+ parser.error("Need to specify a file to execute.")
+ exec_file(
+ target,
+ Path(args.remainder[0]),
+ args=args.remainder[1:],
+ timeout=args.timeout,
+ extra_files=[Path(f) for f in args.extra_files],
+ )
+
+
+if __name__ == "__main__":
+ try:
+ main()
+ except subprocess.CalledProcessError as e:
+ print("Command failed:", e.cmd)
+ print(e.stdout)
+ print(e.stderr)
+ sys.exit(-1)
diff --git a/tools/impl/testvm.py b/tools/impl/testvm.py
new file mode 100755
index 000000000..4d66ffd47
--- /dev/null
+++ b/tools/impl/testvm.py
@@ -0,0 +1,438 @@
+#!/usr/bin/env python3
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+from pathlib import Path
+from typing import Iterable, Optional, Literal
+import argparse
+import itertools
+import os
+import shutil
+import subprocess
+import sys
+import time
+import typing
+import urllib.request as request
+import json
+
+USAGE = """%(prog)s {command} [options]
+
+Manages VMs for testing crosvm.
+
+Can run an x86_64 and an aarch64 vm via `./tools/x86vm` and `./tools/aarch64vm`.
+The VM image will be downloaded and initialized on first use.
+
+The easiest way to use the VM is:
+
+ $ ./tools/aarch64vm ssh
+
+Which will initialize and boot the VM, then wait for SSH to be available and
+opens an SSH session. The VM will stay alive between calls.
+
+Alternatively, you can set up an SSH config to connect to the VM. First ensure
+the VM ready:
+
+ $ ./tools/aarch64vm wait
+
+Then append the VMs ssh config to your SSH config:
+
+ $ ./tools/aarch64vm ssh_config >> ~/.ssh/config
+
+And connect as usual:
+
+ $ ssh crosvm_$ARCH
+
+Commands:
+
+ build: Download base image and create rootfs overlay.
+ up: Ensure that the VM is running in the background.
+ run: Run the VM in the foreground process for debugging.
+ wait: Boot the VM if it's offline and wait until it's available.
+ ssh: SSH into the VM. Boot and wait if it's not available.
+ ssh_config: Prints the ssh config needed to connnect to the VM.
+ stop: Gracefully shutdown the VM.
+ kill: Kill the QEMU process. Might damage the image file.
+ clean: Stop all VMs and delete all data.
+"""
+
+KVM_SUPPORT = os.access("/dev/kvm", os.W_OK)
+
+Arch = Literal["x86_64", "aarch64"]
+
+SCRIPT_DIR = Path(__file__).parent.resolve()
+SRC_DIR = SCRIPT_DIR.joinpath("testvm")
+ID_RSA = SRC_DIR.joinpath("id_rsa")
+BASE_IMG_VERSION = open(SRC_DIR.joinpath("version"), "r").read().strip()
+
+IMAGE_DIR_URL = "https://storage.googleapis.com/crosvm-testvm"
+
+
+def cargo_target_dir():
+ # Do not call cargo if we have the environment variable specified. This
+ # allows the script to be used when cargo is not available but the target
+ # dir is known.
+ env_target = os.environ.get("CARGO_TARGET_DIR")
+ if env_target:
+ return Path(env_target)
+ text = subprocess.run(
+ ["cargo", "metadata", "--no-deps", "--format-version=1"],
+ check=True,
+ capture_output=True,
+ text=True,
+ ).stdout
+ metadata = json.loads(text)
+ return Path(metadata["target_directory"])
+
+
+def data_dir(arch: Arch):
+ return cargo_target_dir().joinpath("crosvm_tools").joinpath(arch)
+
+
+def pid_path(arch: Arch):
+ return data_dir(arch).joinpath("pid")
+
+
+def base_img_name(arch: Arch):
+ return f"base-{arch}-{BASE_IMG_VERSION}.qcow2"
+
+
+def base_img_url(arch: Arch):
+ return f"{IMAGE_DIR_URL}/{base_img_name(arch)}"
+
+
+def base_img_path(arch: Arch):
+ return data_dir(arch).joinpath(base_img_name(arch))
+
+
+def rootfs_img_path(arch: Arch):
+ return data_dir(arch).joinpath(f"rootfs-{arch}-{BASE_IMG_VERSION}.qcow2")
+
+
+# List of ports to use for SSH for each architecture
+SSH_PORTS: dict[Arch, int] = {
+ "x86_64": 9000,
+ "aarch64": 9001,
+}
+
+# QEMU arguments shared by all architectures
+SHARED_ARGS: list[tuple[str, str]] = [
+ ("-display", "none"),
+ ("-device", "virtio-net-pci,netdev=net0"),
+ ("-smp", "8"),
+ ("-m", "4G"),
+]
+
+# Arguments to QEMU for each architecture
+ARCH_TO_QEMU: dict[Arch, tuple[str, list[Iterable[str]]]] = {
+ # arch: (qemu-binary, [(param, value), ...])
+ "x86_64": (
+ "qemu-system-x86_64",
+ [
+ ("-cpu", "Broadwell,vmx=on"),
+ ("-netdev", f"user,id=net0,hostfwd=tcp::{SSH_PORTS['x86_64']}-:22"),
+ *([("-enable-kvm",)] if KVM_SUPPORT else []),
+ *SHARED_ARGS,
+ ],
+ ),
+ "aarch64": (
+ "qemu-system-aarch64",
+ [
+ ("-M", "virt"),
+ ("-machine", "virt,virtualization=true,gic-version=3"),
+ ("-cpu", "cortex-a57"),
+ ("-bios", "/usr/share/qemu-efi-aarch64/QEMU_EFI.fd"),
+ (
+ "-netdev",
+ f"user,id=net0,hostfwd=tcp::{SSH_PORTS['aarch64']}-:22",
+ ),
+ *SHARED_ARGS,
+ ],
+ ),
+}
+
+
+def ssh_opts(arch: Arch) -> dict[str, str]:
+ return {
+ "Port": str(SSH_PORTS[arch]),
+ "User": "crosvm",
+ "StrictHostKeyChecking": "no",
+ "UserKnownHostsFile": "/dev/null",
+ "LogLevel": "ERROR",
+ "IdentityFile": str(ID_RSA),
+ }
+
+
+def ssh_cmd_args(arch: Arch):
+ return [f"-o{k}={v}" for k, v in ssh_opts(arch).items()]
+
+
+def ssh_exec(arch: Arch, cmd: Optional[str] = None):
+ subprocess.run(
+ [
+ "ssh",
+ "localhost",
+ *ssh_cmd_args(arch),
+ *(["-T", cmd] if cmd else []),
+ ],
+ ).check_returncode()
+
+
+def ping_vm(arch: Arch):
+ os.chmod(ID_RSA, 0o600)
+ return (
+ subprocess.run(
+ [
+ "ssh",
+ "localhost",
+ *ssh_cmd_args(arch),
+ "-oConnectTimeout=1",
+ "-T",
+ "exit",
+ ],
+ capture_output=True,
+ ).returncode
+ == 0
+ )
+
+
+def write_pid_file(arch: Arch, pid: int):
+ with open(pid_path(arch), "w") as pid_file:
+ pid_file.write(str(pid))
+
+
+def read_pid_file(arch: Arch):
+ if not pid_path(arch).exists():
+ return None
+
+ with open(pid_path(arch), "r") as pid_file:
+ return int(pid_file.read())
+
+
+def run_qemu(
+ arch: Arch,
+ hda: Path,
+ background: bool = False,
+):
+ (binary, arch_args) = ARCH_TO_QEMU[arch]
+ qemu_args = [*arch_args, ("-hda", str(hda))]
+ if background:
+ qemu_args.append(
+ ("-serial", f"file:{data_dir(arch).joinpath('vm_log')}")
+ )
+ else:
+ qemu_args.append(("-serial", "stdio"))
+
+ # Flatten list of tuples into flat list of arguments
+ qemu_cmd = [binary, *itertools.chain(*qemu_args)]
+ process = subprocess.Popen(qemu_cmd, start_new_session=background)
+ write_pid_file(arch, process.pid)
+ if not background:
+ process.wait()
+
+
+def run_vm(arch: Arch, background: bool = False):
+ run_qemu(
+ arch,
+ rootfs_img_path(arch),
+ background=background,
+ )
+
+
+def is_running(arch: Arch):
+ pid = read_pid_file(arch)
+ if pid is None:
+ return False
+
+ # Send signal 0 to check if the process is alive
+ try:
+ os.kill(pid, 0)
+ except OSError:
+ return False
+ return True
+
+
+def kill_vm(arch: Arch):
+ pid = read_pid_file(arch)
+ if pid:
+ os.kill(pid, 9)
+
+
+def build_if_needed(arch: Arch, reset: bool = False):
+ if reset and is_running(arch):
+ print("Killing existing VM...")
+ kill_vm(arch)
+ time.sleep(1)
+
+ data_dir(arch).mkdir(parents=True, exist_ok=True)
+
+ base_img = base_img_path(arch)
+ if not base_img.exists():
+ print(f"Downloading base image ({base_img_url(arch)})...")
+ request.urlretrieve(base_img_url(arch), base_img_path(arch))
+
+ rootfs_img = rootfs_img_path(arch)
+ if not rootfs_img.exists() or reset:
+ # The rootfs is backed by the base image generated above. So we can
+ # easily reset to a clean VM by rebuilding an empty rootfs image.
+ print("Creating rootfs overlay...")
+ subprocess.run(
+ [
+ "qemu-img",
+ "create",
+ "-f",
+ "qcow2",
+ "-F",
+ "qcow2",
+ "-b",
+ base_img,
+ rootfs_img,
+ "4G",
+ ]
+ ).check_returncode()
+
+
+def up(arch: Arch):
+ if is_running(arch):
+ return
+ print("Booting VM...")
+ run_qemu(
+ arch,
+ rootfs_img_path(arch),
+ background=True,
+ )
+
+
+def run(arch: Arch):
+ if is_running(arch):
+ raise Exception("VM is already running")
+ run_qemu(
+ arch,
+ rootfs_img_path(arch),
+ background=False,
+ )
+
+
+def wait(arch: Arch, timeout: int = 120):
+ if not is_running(arch):
+ up(arch)
+ elif ping_vm(arch):
+ return
+
+ print("Waiting for VM")
+ start_time = time.time()
+ while (time.time() - start_time) < timeout:
+ time.sleep(1)
+ sys.stdout.write(".")
+ sys.stdout.flush()
+ if ping_vm(arch):
+ print()
+ return
+ raise Exception("Timeout while waiting for VM")
+
+
+def ssh(arch: Arch, timeout: int):
+ wait(arch, timeout)
+ ssh_exec(arch)
+
+
+def ssh_config(arch: Arch):
+ print(f"Host crosvm_{arch}")
+ print(f" HostName localhost")
+ for opt, value in ssh_opts(arch).items():
+ print(f" {opt} {value}")
+
+
+def stop(arch: Arch):
+ if not is_running(arch):
+ print("VM is not running.")
+ return
+ ssh_exec(arch, "sudo poweroff")
+
+
+def kill(arch: Arch):
+ if not is_running(arch):
+ print("VM is not running.")
+ return
+ kill_vm(arch)
+
+
+def clean(arch: Arch):
+ if is_running(arch):
+ kill(arch)
+ shutil.rmtree(data_dir(arch))
+
+
+def main():
+ COMMANDS = [
+ "build",
+ "up",
+ "run",
+ "wait",
+ "ssh",
+ "ssh_config",
+ "stop",
+ "kill",
+ "clean",
+ ]
+ parser = argparse.ArgumentParser(usage=USAGE)
+ parser.add_argument("command", choices=COMMANDS)
+ parser.add_argument(
+ "--arch",
+ choices=typing.get_args(Arch),
+ help="Which architecture to run as the guest",
+ )
+ parser.add_argument(
+ "--reset",
+ action="store_true",
+ help="Reset VM image to a fresh state. Removes all user modifications.",
+ )
+ parser.add_argument(
+ "--timeout",
+ type=int,
+ default=60,
+ help="Timeout in seconds while waiting for the VM to come up.",
+ )
+ args = parser.parse_args()
+
+ if args.command == "clean":
+ if not args.arch:
+ clean("x86_64")
+ clean("aarch64")
+ else:
+ clean(args.arch)
+ return
+
+ if not args.arch:
+ print("--arch argument is required")
+ print("")
+ parser.print_help()
+ return
+
+ if args.command == "ssh_config":
+ ssh_config(args.arch)
+ return
+
+ # Ensure the images are built regardless of which command we execute.
+ build_if_needed(args.arch, reset=args.reset)
+
+ if args.command == "build":
+ return # Nothing left to do.
+ elif args.command == "run":
+ run(args.arch)
+ elif args.command == "up":
+ up(args.arch)
+ elif args.command == "ssh":
+ ssh(args.arch, args.timeout)
+ elif args.command == "stop":
+ stop(args.arch)
+ elif args.command == "kill":
+ kill(args.arch)
+ elif args.command == "wait":
+ wait(args.arch, args.timeout)
+ else:
+ print(f"Unknown command {args.command}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/impl/testvm/Makefile b/tools/impl/testvm/Makefile
new file mode 100644
index 000000000..2276ce0c6
--- /dev/null
+++ b/tools/impl/testvm/Makefile
@@ -0,0 +1,81 @@
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Builds the base image for test VMs used by ./tools/x86vm and
+# ./tools/aarch64vm.
+#
+# To build and upload a new base image, uprev the version in the `version` file
+# and run:
+#
+# make ARCH=x86_64 upload
+# make ARCH=aarch64 upload
+#
+# You need write access to the crosvm-testvm storage bucket which is part of the
+# crosvm-packages cloud project.
+#
+# Note: The locally built image is stored in the same place that testvm.py
+# expects. So the image can be tested directly:
+#
+# make -C tools/impl/testvm ARCH=x86_64
+# ./tools/x86vm run
+
+CARGO_TARGET=$(shell cargo metadata --no-deps --format-version 1 | \
+ jq -r ".target_directory")
+TARGET=$(CARGO_TARGET)/crosvm_tools/$(ARCH)
+$(shell mkdir -p $(TARGET))
+
+ifeq ($(ARCH), x86_64)
+ DEBIAN_ARCH=amd64
+ QEMU_CMD=qemu-system-x86_64 \
+ -cpu Broadwell \
+ -enable-kvm
+else ifeq ($(ARCH), aarch64)
+ DEBIAN_ARCH=arm64
+ QEMU_CMD=qemu-system-aarch64 \
+ -cpu cortex-a57 \
+ -M virt \
+ -bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd
+else
+ $(error Only x86_64 or aarch64 are supported)
+endif
+
+GS_PREFIX=gs://crosvm-testvm
+DEBIAN_URL=https://cloud.debian.org/images/cloud/bullseye/latest
+
+BASE_IMG_NAME=base-$(ARCH)-$(shell cat version).qcow2
+GS_URL=$(GS_PREFIX)/$(BASE_IMG_NAME)
+LOCAL_IMAGE=$(TARGET)/$(BASE_IMG_NAME)
+
+all: $(LOCAL_IMAGE)
+
+clean:
+ rm -rf $(TARGET)
+
+# Upload images to cloud storage. Do not overwrite existing images, uprev
+# `image_version` instead.
+upload: $(LOCAL_IMAGE)
+ gsutil cp -n $(LOCAL_IMAGE) $(GS_URL)
+
+# Download debian bullseye as base image for the testvm.
+$(TARGET)/debian.qcow2:
+ wget $(DEBIAN_URL)/debian-11-generic-$(DEBIAN_ARCH).qcow2 -O $@
+
+# The cloud init file contains instructions for how to set up the VM when it's
+# first booted.
+$(TARGET)/clould_init.img: cloud_init.yaml
+ cloud-localds -v $@ $<
+
+# Boot image to run through clould-init process.
+# cloud-init will shut down the VM after initialization. Compress the image
+# afterwards for distribution.
+$(LOCAL_IMAGE): $(TARGET)/clould_init.img $(TARGET)/debian.qcow2
+ cp -f $(TARGET)/debian.qcow2 $@
+ $(QEMU_CMD) \
+ -m 4G -smp 8 \
+ -display none \
+ -serial stdio \
+ -hda $@ \
+ -drive file=$(TARGET)/clould_init.img,format=raw,index=1,media=disk
+ qemu-img convert -O qcow2 -c $@ $@-compressed
+ mv -f $@-compressed $@
diff --git a/tools/impl/testvm/cloud_init.yaml b/tools/impl/testvm/cloud_init.yaml
new file mode 100644
index 000000000..e01f95760
--- /dev/null
+++ b/tools/impl/testvm/cloud_init.yaml
@@ -0,0 +1,74 @@
+## template: jinja
+#cloud-config
+#
+# This file is responsible for setting up the test VM when it is first booted.
+# See https://cloudinit.readthedocs.io/ for details.
+
+users:
+ - name: crosvm
+ sudo: ALL=(ALL) NOPASSWD:ALL
+ lock_passwd: False
+ shell: /bin/bash
+ # Hashed password is 'crosvm'
+ passwd: $6$rounds=4096$os6Q9Ok4Y9a8hKvG$EwQ1bbS0qd4IJyRP.bnRbyjPbSS8BwxEJh18PfhsyD0w7a4GhTwakrmYZ6KuBoyP.cSjYYSW9wYwko4oCPoJr.
+ # Pubkey for `id_rsa`
+ ssh_authorized_keys:
+ - ssh-rsa
+ AAAAB3NzaC1yc2EAAAADAQABAAABgQCYan8oXtUm6WTIClGMsfEf3hmJe+T8p08t9O8StuuDHyAtl1lC+8hOcuXTNvbc52/HNdZ5EO4ZpP3n+N6XftfXFWQanI8OrIHVpsMATMnofHE9RBHWcR/gH0V3xKnXcTvo3S0T3ennfCYxjtL7l7EvHDMdacX8NFOaARH92qJd/YdFp73mqykmc81OCZ4ToQ5s+20T7xvRzedksfSj/wIx3z8BJK9iovkQhNGFd1o557Vq1g3Bxk1PbcIUAvFPl0SwwlFfHBi2M9kZgCVa8UfokBzu77zvxWFud+MXVrfralwKV88k9Cy9FL5QGbtCiZ7RDP5pf69xapKBK+z2L+zuVlSkvaB1CcXuqqVDjD84LXEA+io0peXQcsqbAfbLo0666P7JsweCyQ07qc4AM8gv52SzFuQTIuHLciYxbPgkZTieKgmQLJ1EgfJelOG/+60XC24LbzPIAQxcO83erC/SQ3mTUizu6BueJt7LD1V6vXHcjLfE19FecIJ8U0XDaDU=
+ crosvm@localhost
+ groups: kvm, disk, tty
+
+preserve_hostname: true
+
+{% if v1.machine == 'aarch64' -%}
+# This needs to run as bootcmd to preceed processing `packages` section
+bootcmd:
+ - dpkg --add-architecture armhf
+{%- endif %}
+
+
+# Runtime dependencies of crosvm binaries.
+# Note: Keep in sync with ./install-[aarch64-]deps.sh
+packages:
+ - libcap2
+ - libdbus-1-3
+ - libdrm2
+ - libepoxy0
+ - libssl1.1
+ - libwayland-client0
+ - libx11-6
+ - libxext6
+ - rsync
+{% if v1.machine == 'aarch64' %}
+# Note: Keep in sync with the above
+ - libcap2:armhf
+ - libdbus-1-3:armhf
+ - libdrm2:armhf
+ - libepoxy0:armhf
+ - libssl1.1:armhf
+ - libwayland-client0:armhf
+ - libx11-6:armhf
+ - libxext6:armhf
+{%- endif %}
+
+# Commands to run once during setup
+runcmd:
+ # Append arch to hostname to make the VM easier to identify.
+ - echo "testvm-$(arch)" > /etc/hostname
+ - echo 127.0.0.1 testvm-$(arch) >> /etc/hosts
+
+ # Enable core dumps for debugging crashes
+ - echo "* soft core unlimited" > /etc/security/limits.conf
+
+ # Trim some fat
+ - [apt-get, remove, --yes, vim-runtime, iso-codes, perl, grub-common]
+ - [apt-get, autoremove, --yes]
+ - [apt-get, clean, --yes]
+ - [rm, -rf, /var/lib/apt/lists]
+
+ # Fill empty space with zeros, so the image can be sparsified.
+ - [dd, if=/dev/zero, of=/mytempfile]
+ - [rm, /mytempfile]
+
+ # And shut down after first boot setup is done.
+ - [poweroff]
diff --git a/ci/crosvm_test_vm/runtime/ssh/id_rsa b/tools/impl/testvm/id_rsa
index fae8a139c..fae8a139c 100644
--- a/ci/crosvm_test_vm/runtime/ssh/id_rsa
+++ b/tools/impl/testvm/id_rsa
diff --git a/tools/impl/testvm/version b/tools/impl/testvm/version
new file mode 100644
index 000000000..9742708e6
--- /dev/null
+++ b/tools/impl/testvm/version
@@ -0,0 +1 @@
+r0002
diff --git a/tools/install-aarch64-deps b/tools/install-aarch64-deps
new file mode 100755
index 000000000..7627482c9
--- /dev/null
+++ b/tools/install-aarch64-deps
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+set -ex
+
+sudo apt-get install --yes --no-install-recommends \
+ gcc-aarch64-linux-gnu \
+ ipxe-qemu \
+ libc-dev:arm64 \
+ libcap-dev:arm64 \
+ libdbus-1-dev:arm64 \
+ libdrm-dev:arm64 \
+ libepoxy-dev:arm64 \
+ libssl-dev:arm64 \
+ libwayland-dev:arm64 \
+ libxext-dev:arm64 \
+ qemu-efi-aarch64 \
+ qemu-system-aarch64 \
+ qemu-user-static
+
+rustup target add aarch64-unknown-linux-gnu
+
+# Generate a cross file for meson to compile for aarch64
+sudo mkdir -p -m 0755 /usr/local/share/meson/cross
+sudo tee /usr/local/share/meson/cross/aarch64 >/dev/null <<EOF
+[binaries]
+c = '/usr/bin/aarch64-linux-gnu-gcc'
+cpp = '/usr/bin/aarch64-linux-gnu-g++'
+ar = '/usr/bin/aarch64-linux-gnu-ar'
+strip = '/usr/bin/aarch64-linux-gnu-strip'
+objcopy = '/usr/bin/aarch64-linux-gnu-objcopy'
+ld= '/usr/bin/aarch64-linux-gnu-ld'
+pkgconfig = '/usr/bin/aarch64-linux-gnu-pkg-config'
+
+[properties]
+
+[host_machine]
+system = 'linux'
+cpu_family = 'aarch64'
+cpu = 'arm64'
+endian = 'little'
+EOF
diff --git a/tools/install-armhf-deps b/tools/install-armhf-deps
new file mode 100755
index 000000000..250fab1d9
--- /dev/null
+++ b/tools/install-armhf-deps
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+set -ex
+
+sudo apt-get install --yes --no-install-recommends \
+ gcc-arm-linux-gnueabihf \
+ libc-dev:armhf \
+ libcap-dev:armhf \
+ libdbus-1-dev:armhf \
+ libdrm-dev:armhf \
+ libepoxy-dev:armhf \
+ libssl-dev:armhf \
+ libwayland-dev:armhf \
+ libxext-dev:armhf
+
+rustup target add armv7-unknown-linux-gnueabihf
+
+# Generate a cross file for meson to compile for armhf
+sudo mkdir -p -m 0755 /usr/local/share/meson/cross
+sudo tee /usr/local/share/meson/cross/armhf >/dev/null <<EOF
+[binaries]
+c = '/usr/bin/arm-linux-gnueabihf-gcc'
+cpp = '/usr/bin/arm-linux-gnueabihf-g++'
+ar = '/usr/bin/arm-linux-gnueabihf-ar'
+strip = '/usr/bin/arm-linux-gnueabihf-strip'
+objcopy = '/usr/bin/arm-linux-gnueabihf-objcopy'
+ld= '/usr/bin/arm-linux-gnueabihf-ld'
+pkgconfig = '/usr/bin/arm-linux-gnueabihf-pkg-config'
+
+[properties]
+
+[host_machine]
+system = 'linux'
+cpu_family = 'arm'
+cpu = 'arm7hlf'
+endian = 'little'
+EOF
diff --git a/tools/install-deps b/tools/install-deps
new file mode 100755
index 000000000..23aa211a2
--- /dev/null
+++ b/tools/install-deps
@@ -0,0 +1,62 @@
+#!/usr/bin/env bash
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+set -ex
+
+sudo apt-get update
+sudo apt-get install --yes --no-install-recommends \
+ ca-certificates \
+ cloud-image-utils \
+ curl \
+ dpkg-dev \
+ expect \
+ gcc \
+ git \
+ jq \
+ libasound2-dev \
+ libcap-dev \
+ libclang-dev \
+ libdbus-1-dev \
+ libdrm-dev \
+ libepoxy-dev \
+ libssl-dev \
+ libwayland-dev \
+ libxext-dev \
+ make \
+ nasm \
+ ninja-build \
+ openssh-client \
+ pkg-config \
+ protobuf-compiler \
+ python3 \
+ python3-pip \
+ python3-setuptools \
+ qemu-system-x86 \
+ rsync \
+ screen \
+ wine64 \
+ gcc-mingw-w64-x86-64-win32 \
+ wayland-protocols
+
+# Install meson for rutabaga_gfx
+pip3 install meson
+
+# We use mdformat to keep the mdbook at docs/ formatted
+pip3 install mdformat
+
+# We use argh in our cli developer tools
+pip3 install argh
+
+rustup component add clippy
+rustup component add rustfmt
+
+rustup target add x86_64-pc-windows-gnu
+
+# The bindgen tool is required to build a crosvm dependency.
+cargo install bindgen
+
+# The mdbook and mdbook-mermaid tools are used to build the crosvm book.
+cargo install mdbook --no-default-features --version "^0.4.10"
+cargo install mdbook-mermaid --version "^0.8.3"
+cargo install mdbook-linkcheck --version "^0.7.6"
diff --git a/tools/presubmit b/tools/presubmit
new file mode 100755
index 000000000..91ab4a4db
--- /dev/null
+++ b/tools/presubmit
@@ -0,0 +1,118 @@
+#!/bin/bash
+# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+set -e
+
+cd "$(dirname $0)/.."
+
+HELP="This will run presubmit checks for crosvm.
+
+To run all checks just run
+
+ $ ./tools/presubmit
+
+The checks can be run in parallel for faster execution:
+
+ $ ./tools/presubmit --tmux
+
+This will open a tmux session to run all presubmit builds in parallel. It will
+create a nested tmux session if you are already using it.
+
+All tests are executed in the local development environment. If your host is not
+set up for aarch64 builds, it will use './tools/dev_container' to build run
+those.
+
+There are three levels of presubmit tests that can be run:
+
+ $ ./tools/presubmit --quick
+ $ ./tools/presubmit
+ $ ./tools/presubmit --all
+
+The quick mode will only cover x86 and does not require a dev_container. The
+default mode will add aarch64 tests, and the all mode will test everything that
+is also tested on Kokoro.
+"
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -q | --quick)
+ QUICK=true
+ shift
+ ;;
+ -a | --all)
+ ALL=true
+ shift
+ ;;
+ --tmux)
+ RUN_IN_TMUX=true
+ shift
+ ;;
+ -h | --help)
+ echo "$HELP"
+ exit 0
+ shift
+ ;;
+ *)
+ echo "unknown argument $1"
+ exit 1
+ ;;
+ esac
+done
+
+run_commands_in_tmux() {
+ local tmux_commands=(
+ set-option -g default-shell /bin/bash \;
+ new-session "$1; read -p 'Press enter to close.'" \;
+ )
+ for cmd in "${@:2}"; do
+ tmux_commands+=(
+ split-window -h "$cmd; read -p 'Press enter to close.'" \;
+ )
+ done
+ tmux_commands+=(
+ select-layout even-horizontal \;
+ )
+ TMUX="" tmux "${tmux_commands[@]}"
+}
+
+run_commands() {
+ for cmd in "$@"; do
+ echo "$ ${cmd}"
+ bash -c "$cmd"
+ echo
+ done
+}
+
+aarch64_wrapper() {
+ if ! (rustup target list --installed | grep -q aarch64 &&
+ dpkg --print-foreign-architectures | grep -q arm64); then
+ echo "./tools/dev_container"
+ fi
+}
+
+commands=(
+ "./tools/fmt --check && ./tools/clippy"
+ "./tools/run_tests --target=host"
+)
+
+if [ "$ALL" == true ]; then
+ commands+=(
+ "$(aarch64_wrapper) ./tools/run_tests --target=vm:aarch64"
+ "$(aarch64_wrapper) ./tools/run_tests --target=vm:aarch64 --arch=armhf"
+ "./tools/run_tests --target=host --arch=win64 --build-only"
+ "cargo build --verbose --no-default-features"
+ )
+elif [ "$QUICK" != true ]; then
+ commands+=(
+ # Test via user-space emulation for faster, but less complete results.
+ "$(aarch64_wrapper) ./tools/run_tests --target=host --arch=aarch64"
+ "./tools/run_tests --target=host --arch=win64 --build-only"
+ )
+fi
+
+if [ "$RUN_IN_TMUX" = true ]; then
+ run_commands_in_tmux "${commands[@]}"
+else
+ run_commands "${commands[@]}"
+fi
diff --git a/tools/run_tests b/tools/run_tests
new file mode 100755
index 000000000..727c4da6d
--- /dev/null
+++ b/tools/run_tests
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+python3 $(dirname $0)/impl/test_runner.py "$@"
diff --git a/tools/set_test_target b/tools/set_test_target
new file mode 100755
index 000000000..a98a674af
--- /dev/null
+++ b/tools/set_test_target
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+python3 $(dirname $0)/impl/test_target.py set "$@"
diff --git a/tools/windows/build_test b/tools/windows/build_test
new file mode 100644
index 000000000..25daf028a
--- /dev/null
+++ b/tools/windows/build_test
@@ -0,0 +1 @@
+python3 tools/windows/build_test.py
diff --git a/tools/windows/build_test.bat b/tools/windows/build_test.bat
new file mode 100644
index 000000000..6089e9e97
--- /dev/null
+++ b/tools/windows/build_test.bat
@@ -0,0 +1,5 @@
+if "%1"=="/copy" (
+ py tools/windows/build_test.py --copy True
+) else (
+ py tools/windows/build_test.py
+)
diff --git a/tools/windows/build_test.py b/tools/windows/build_test.py
new file mode 100644
index 000000000..11569703b
--- /dev/null
+++ b/tools/windows/build_test.py
@@ -0,0 +1,455 @@
+#!/usr/bin/env python3
+# Copyright 2017 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Builds crosvm in debug/release mode on all supported target architectures.
+
+A sysroot for each target architectures is required. The defaults are all generic boards' sysroots,
+but they can be changed with the command line arguments.
+
+To test changes more quickly, set the --noclean option. This prevents the target directories from
+being removed before building and testing.
+
+For easy binary size comparison, use the --size-only option to only do builds that will result in a
+binary size output, which are non-test release builds.
+
+This script automatically determines which packages will need to be tested based on the directory
+structure with Cargo.toml files. Only top-level crates are tested directly. To skip a top-level
+package, add an empty .build_test_skip file to the directory. Rarely, if a package needs to have its
+tests run single-threaded, add an empty .build_test_serial file to the directory.
+"""
+
+from __future__ import print_function
+import argparse
+import functools
+import multiprocessing.pool
+import os
+import shutil
+import subprocess
+import sys
+sys.path.append(os.path.join(sys.path[0], "script_utils"))
+from enabled_features import ENABLED_FEATURES, BUILD_FEATURES
+from files_to_include import DLLS, BINARIES
+from prepare_dlls import build_dlls, copy_dlls
+
+# Is Windows
+IS_WINDOWS = os.name == 'nt'
+
+ARM_TRIPLE = os.getenv('ARM_TRIPLE', 'armv7a-cros-linux-gnueabihf')
+AARCH64_TRIPLE = os.getenv('AARCH64_TRIPLE', 'aarch64-cros-linux-gnu')
+X86_64_TRIPLE = os.getenv('X86_64_TRIPLE', 'x86_64-unknown-linux-gnu')
+X86_64_WIN_MSVC_TRIPLE = os.getenv('X86_64_WIN_MSVC_TRIPLE', 'x86_64-pc-windows-msvc')
+SYMBOL_EXPORTS = ['NvOptimusEnablement', 'AmdPowerXpressRequestHighPerformance']
+
+LINUX_BUILD_ONLY_MODULES = [
+ 'io_jail',
+ 'poll_token_derive',
+ 'wire_format_derive',
+ 'bit_field_derive',
+ 'linux_input_sys',
+ 'vfio_sys',
+]
+
+# Bright green.
+PASS_COLOR = '\033[1;32m'
+# Bright red.
+FAIL_COLOR = '\033[1;31m'
+# Default color.
+END_COLOR = '\033[0m'
+
+def crosvm_binary_name():
+ return 'crosvm.exe' if IS_WINDOWS else 'crosvm'
+
+def get_target_path(triple, kind, test_it):
+ """Constructs a target path based on the configuration parameters.
+
+ Args:
+ triple: Target triple. Example: 'x86_64-unknown-linux-gnu'.
+ kind: 'debug' or 'release'.
+ test_it: If this target is tested.
+ """
+ target_path = os.path.abspath(os.path.join(os.sep, 'tmp', '{}_{}'.format(triple, kind)))
+ if test_it:
+ target_path += '_test'
+ return target_path
+
+def validate_symbols(triple, is_release):
+ kind = 'release' if is_release else 'debug'
+ target_path = get_target_path(triple, kind, False)
+ binary_path = os.path.join(target_path, triple, kind, crosvm_binary_name())
+ with open(binary_path, mode='rb') as f:
+ contents = f.read().decode('ascii', errors='ignore')
+ return all(symbol in contents for symbol in SYMBOL_EXPORTS)
+
+def build_target(
+ triple, is_release, env, only_build_targets, test_module_parallel, test_module_serial):
+ """Does a cargo build for the triple in release or debug mode.
+
+ Args:
+ triple: Target triple. Example: 'x86_64-unknown-linux-gnu'.
+ is_release: True to build a release version.
+ env: Enviroment variables to run cargo with.
+ only_build_targets: Only build packages that will be tested.
+ """
+ args = ['cargo', 'build', '--target=%s' % triple]
+
+ if is_release:
+ args.append('--release')
+
+ if only_build_targets:
+ test_modules = test_module_parallel + test_module_serial
+ if not IS_WINDOWS:
+ test_modules += LINUX_BUILD_ONLY_MODULES
+ for mod in test_modules:
+ args.append('-p')
+ args.append(mod)
+
+ args.append('--features')
+ args.append(','.join(BUILD_FEATURES))
+
+ if subprocess.Popen(args, env=env).wait() != 0:
+ return False, 'build error'
+ if IS_WINDOWS and not validate_symbols(triple, is_release):
+ return False, 'error validating discrete gpu symbols'
+
+ return True, 'pass'
+
+def test_target_modules(triple, is_release, env, no_run, modules, parallel):
+ """Does a cargo test on given modules for the triple and configuration.
+
+ Args:
+ triple: Target triple. Example: 'x86_64-unknown-linux-gnu'.
+ is_release: True to build a release version.
+ env: Enviroment variables to run cargo with.
+ no_run: True to pass --no-run flag to cargo test.
+ modules: List of module strings to test.
+ parallel: True to run the tests in parallel threads.
+ """
+ args = ['cargo', 'test', '--target=%s' % triple]
+
+ if is_release:
+ args.append('--release')
+
+ if no_run:
+ args.append('--no-run')
+
+ for mod in modules:
+ args.append('-p')
+ args.append(mod)
+
+ args.append('--features')
+ args.append(','.join(ENABLED_FEATURES))
+
+ if not parallel:
+ args.append('--')
+ args.append('--test-threads=1')
+ return subprocess.Popen(args, env=env).wait() == 0
+
+
+def test_target(triple, is_release, env, no_run, test_modules_parallel, test_modules_serial):
+ """Does a cargo test for the given triple and configuration.
+
+ Args:
+ triple: Target triple. Example: 'x86_64-unknown-linux-gnu'.
+ is_release: True to build a release version.
+ env: Enviroment variables to run cargo with.
+ no_run: True to pass --no-run flag to cargo test.
+ """
+
+ parallel_result = test_target_modules(
+ triple, is_release, env, no_run, test_modules_parallel, True)
+
+ serial_result = test_target_modules(
+ triple, is_release, env, no_run, test_modules_serial, False)
+
+ return parallel_result and serial_result
+
+
+def build_or_test(sysroot, triple, kind, skip_file_name, test_it=False, no_run=False, clean=False,
+ copy_output=False, copy_directory=None, only_build_targets=False):
+ """Runs relevant builds/tests for the given triple and configuration
+
+ Args:
+ sysroot: path to the target's sysroot directory.
+ triple: Target triple. Example: 'x86_64-unknown-linux-gnu'.
+ kind: 'debug' or 'release'.
+ skip_file_name: Skips building and testing a crate if this file is found in
+ crate's root directory.
+ test_it: True to test this triple and kind.
+ no_run: True to just compile and not run tests (only if test_it=True)
+ clean: True to skip cleaning the target path.
+ copy_output: True to copy build artifacts to external directory.
+ output_directory: Destination of copy of build artifacts.
+ only_build_targets: Only build packages that will be tested.
+ """
+ if not os.path.isdir(sysroot) and not IS_WINDOWS:
+ return False, 'sysroot missing'
+
+ target_path = get_target_path(triple, kind, test_it)
+
+ if clean:
+ shutil.rmtree(target_path, True)
+
+ is_release = kind == 'release'
+
+ env = os.environ.copy()
+ env['TARGET_CC'] = '%s-clang'%triple
+ env['SYSROOT'] = sysroot
+ env['CARGO_TARGET_DIR'] = target_path
+
+ if not IS_WINDOWS:
+ # The lib dir could be in either lib or lib64 depending on the target. Rather than checking to see
+ # which one is valid, just add both and let the dynamic linker and pkg-config search.
+ libdir = os.path.join(sysroot, 'usr', 'lib')
+ lib64dir = os.path.join(sysroot, 'usr', 'lib64')
+ libdir_pc = os.path.join(libdir, 'pkgconfig')
+ lib64dir_pc = os.path.join(lib64dir, 'pkgconfig')
+
+ # This line that changes the dynamic library path is needed for upstream, but breaks
+ # downstream's CrosVM linux kokoro presubmits.
+ # env['LD_LIBRARY_PATH'] = libdir + ':' + lib64dir
+ env['PKG_CONFIG_ALLOW_CROSS'] = '1'
+ env['PKG_CONFIG_LIBDIR'] = libdir_pc + ':' + lib64dir_pc
+ env['PKG_CONFIG_SYSROOT_DIR'] = sysroot
+ if 'KOKORO_JOB_NAME' not in os.environ:
+ env['RUSTFLAGS'] = '-C linker=' + env['TARGET_CC']
+ if is_release:
+ env['RUSTFLAGS'] += ' -Cembed-bitcode=yes -Clto'
+
+
+ if IS_WINDOWS and not test_it:
+ for symbol in SYMBOL_EXPORTS:
+ env['RUSTFLAGS'] = env.get('RUSTFLAGS', '') + ' -C link-args=/EXPORT:{}'.format(symbol)
+
+ deps_dir = os.path.join(target_path, triple, kind, 'deps')
+ if not os.path.exists(deps_dir):
+ os.makedirs(deps_dir)
+
+ target_dirs = [deps_dir]
+ if copy_output:
+ os.makedirs(os.path.join(copy_directory, kind), exist_ok=True)
+ if not test_it:
+ target_dirs.append(os.path.join(copy_directory, kind))
+
+ copy_dlls(os.getcwd(), target_dirs, kind)
+
+ (test_modules_parallel, test_modules_serial) = get_test_modules(skip_file_name)
+ print("modules to test in parallel:\n", test_modules_parallel)
+ print("modules to test serially:\n", test_modules_serial)
+
+ if not test_modules_parallel and not test_modules_serial:
+ print("All build and tests skipped.")
+ return True, 'pass'
+
+ if test_it:
+ if not test_target(triple, is_release, env, no_run, test_modules_parallel, test_modules_serial):
+ return False, 'test error'
+ else:
+ res, err = build_target(
+ triple, is_release, env, only_build_targets, test_modules_parallel, test_modules_serial)
+ if not res:
+ return res, err
+
+ # We only care about the non-test binaries, so only copy the output from cargo build.
+ if copy_output and not test_it:
+ binary_src = os.path.join(target_path, triple, kind, crosvm_binary_name())
+ pdb_src = binary_src.replace(".exe", "") + ".pdb"
+ binary_dst = os.path.join(copy_directory, kind)
+ shutil.copy(binary_src, binary_dst)
+ shutil.copy(pdb_src, binary_dst)
+
+ return True, 'pass'
+
+def get_test_modules(skip_file_name):
+ """ Returns a list of modules to test.
+ Args:
+ skip_file_name: Skips building and testing a crate if this file is found in
+ crate's root directory.
+ """
+ if IS_WINDOWS and not os.path.isfile(skip_file_name):
+ test_modules_parallel = ['crosvm']
+ else:
+ test_modules_parallel = []
+ test_modules_serial = []
+
+ file_in_crate = lambda file_name: os.path.isfile(os.path.join(crate.path, file_name))
+ serial_file_name = '{}build_test_serial'.format('.win_' if IS_WINDOWS else '.')
+ with os.scandir() as it:
+ for crate in it:
+ if file_in_crate('Cargo.toml'):
+ if file_in_crate(skip_file_name):
+ continue
+ if file_in_crate(serial_file_name):
+ test_modules_serial.append(crate.name)
+ else:
+ test_modules_parallel.append(crate.name)
+
+ test_modules_parallel.sort()
+ test_modules_serial.sort()
+
+ return (test_modules_parallel, test_modules_serial)
+
+def get_stripped_size(triple):
+ """Returns the formatted size of the given triple's release binary.
+
+ Args:
+ triple: Target triple. Example: 'x86_64-unknown-linux-gnu'.
+ """
+ target_path = get_target_path(triple, 'release', False)
+ bin_path = os.path.join(target_path, triple, 'release', crosvm_binary_name())
+ proc = subprocess.Popen(['%s-strip' % triple, bin_path])
+
+ if proc.wait() != 0:
+ return 'failed'
+
+ return '%dKiB' % (os.path.getsize(bin_path) / 1024)
+
+
+def get_parser():
+ """Gets the argument parser"""
+ parser = argparse.ArgumentParser(description=__doc__)
+ if IS_WINDOWS:
+ parser.add_argument('--x86_64-msvc-sysroot',
+ default='build/amd64-msvc',
+ help='x86_64 sysroot directory (default=%(default)s)')
+ else:
+ parser.add_argument('--arm-sysroot',
+ default='/build/arm-generic',
+ help='ARM sysroot directory (default=%(default)s)')
+ parser.add_argument('--aarch64-sysroot',
+ default='/build/arm64-generic',
+ help='AARCH64 sysroot directory (default=%(default)s)')
+ parser.add_argument('--x86_64-sysroot',
+ default='/build/amd64-generic',
+ help='x86_64 sysroot directory (default=%(default)s)')
+
+ parser.add_argument('--noclean', dest='clean', default=True,
+ action='store_false',
+ help='Keep the tempororary build directories.')
+ parser.add_argument('--copy', default=False,
+ help='Copies .exe files to an output directory for later use')
+ parser.add_argument('--copy-directory', default="/output",
+ help='Destination of .exe files when using --copy')
+ parser.add_argument('--serial', default=True,
+ action='store_false', dest='parallel',
+ help='Run cargo build serially rather than in parallel')
+ # TODO(b/154029826): Remove this option once all sysroots are available.
+ parser.add_argument('--x86_64-only', default=False, action='store_true',
+ help='Only runs tests on x86_64 sysroots')
+ parser.add_argument('--only-build-targets', default=False, action='store_true',
+ help='Builds only the tested modules. If false, builds the entire crate')
+ parser.add_argument('--size-only', dest='size_only', default=False,
+ action='store_true',
+ help='Only perform builds that output their binary size (i.e. release non-test).')
+ parser.add_argument('--job_type', default='local', choices=['kokoro', 'local'], help='Set to kokoro if this script is executed by a kokoro job, otherwise local')
+ parser.add_argument('--skip_file_name', default='.win_build_test_skip' if IS_WINDOWS else '.build_test_skip',
+ choices=['.build_test_skip', '.win_build_test_skip', '.windows_build_test_skip'],
+ help='Skips building and testing a crate if the crate contains specified file in its root directory.')
+ return parser
+
+
+def main(argv):
+ opts = get_parser().parse_args(argv)
+ os.environ["RUST_BACKTRACE"] = "1"
+ if IS_WINDOWS:
+ build_test_cases = [
+ #(sysroot path, target triple, debug/release, skip_file_name, should test?)
+ (opts.x86_64_msvc_sysroot, X86_64_WIN_MSVC_TRIPLE, "debug", opts.skip_file_name, True),
+ (opts.x86_64_msvc_sysroot, X86_64_WIN_MSVC_TRIPLE, "release", opts.skip_file_name, True),
+ (opts.x86_64_msvc_sysroot, X86_64_WIN_MSVC_TRIPLE, "release", opts.skip_file_name, False),
+ ]
+ else:
+ build_test_cases = [
+ #(sysroot path, target triple, debug/release, skip_file_name, should test?)
+ (opts.x86_64_sysroot, X86_64_TRIPLE, "debug", opts.skip_file_name, False),
+ (opts.x86_64_sysroot, X86_64_TRIPLE, "release", opts.skip_file_name, False),
+ (opts.x86_64_sysroot, X86_64_TRIPLE, "debug", opts.skip_file_name, True),
+ (opts.x86_64_sysroot, X86_64_TRIPLE, "release", opts.skip_file_name, True),
+ ]
+ if not opts.x86_64_only:
+ build_test_cases = [
+ #(sysroot path, target triple, debug/release, skip_file_name, should test?)
+ (opts.arm_sysroot, ARM_TRIPLE, "debug", opts.skip_file_name, False),
+ (opts.arm_sysroot, ARM_TRIPLE, "release", opts.skip_file_name, False),
+ (opts.aarch64_sysroot, AARCH64_TRIPLE, "debug", opts.skip_file_name, False),
+ (opts.aarch64_sysroot, AARCH64_TRIPLE, "release", opts.skip_file_name, False),
+ ] + build_test_cases
+ os.chdir(os.path.dirname(sys.argv[0]))
+
+ if opts.size_only:
+ # Only include non-test release builds
+ build_test_cases = [case for case in build_test_cases
+ if case[2] == 'release' and not case[4]]
+
+ # First we need to build necessary DLLs.
+ # Because build_or_test may be called by multithreads in parallel,
+ # we want to build the DLLs only once up front.
+ modes = set()
+ for case in build_test_cases:
+ modes.add(case[2])
+ for mode in modes:
+ build_dlls(os.getcwd(), mode, opts.job_type, BUILD_FEATURES)
+
+ # set keyword args to build_or_test based on opts
+ build_partial = functools.partial(
+ build_or_test, no_run=True, clean=opts.clean, copy_output=opts.copy,
+ copy_directory=opts.copy_directory,
+ only_build_targets=opts.only_build_targets)
+
+ if opts.parallel:
+ pool = multiprocessing.pool.Pool(len(build_test_cases))
+ results = pool.starmap(build_partial, build_test_cases, 1)
+ else:
+ results = [build_partial(*case) for case in build_test_cases]
+
+ print_summary("build", build_test_cases, results, opts)
+
+ # exit early if any builds failed
+ if not all([r[0] for r in results]):
+ return 1
+
+ # run tests for cases where should_test is True
+ test_cases = [case for case in build_test_cases if case[4]]
+
+ # Run tests serially. We set clean=False so it re-uses the results of the build phase.
+ results = [build_or_test(*case, no_run=False, clean=False,
+ copy_output=opts.copy,
+ copy_directory=opts.copy_directory,
+ only_build_targets=opts.only_build_targets) for case in test_cases]
+
+ print_summary("test", test_cases, results, opts)
+
+ if not all([r[0] for r in results]):
+ return 1
+
+ return 0
+
+
+def print_summary(title, cases, results, opts):
+ print('---')
+ print(f'{title} summary:')
+ for test_case, result in zip(cases, results):
+ _, triple, kind, _, test_it = test_case
+ title = '%s_%s' % (triple.split('-')[0], kind)
+ if test_it:
+ title += "_test"
+
+ success, result_msg = result
+
+ result_color = FAIL_COLOR
+ if success:
+ result_color = PASS_COLOR
+
+ display_size = ''
+ # Stripped binary isn't available when only certain packages are built, the tool is not available
+ # on Windows.
+ if success and kind == 'release' and not test_it and not opts.only_build_targets and not IS_WINDOWS:
+ display_size = get_stripped_size(triple) + ' stripped binary'
+
+ print('%20s: %s%15s%s %s' %
+ (title, result_color, result_msg, END_COLOR, display_size))
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))
diff --git a/tools/windows/enabled_features.py b/tools/windows/enabled_features.py
new file mode 100644
index 000000000..250ad0d64
--- /dev/null
+++ b/tools/windows/enabled_features.py
@@ -0,0 +1,7 @@
+# Following lists allow vendors to set/override default features during
+# presubmit/continuous builds.
+
+ENABLED_FEATURES = []
+BUILD_ONLY_FEATURES = []
+
+BUILD_FEATURES = ENABLED_FEATURES + BUILD_ONLY_FEATURES
diff --git a/tools/windows/files_to_include.py b/tools/windows/files_to_include.py
new file mode 100644
index 000000000..b8edfea4e
--- /dev/null
+++ b/tools/windows/files_to_include.py
@@ -0,0 +1,19 @@
+# The lists/dictionaries in this file let vendors build/link custom libraries
+
+# paths are relative to platform/crosvm dir
+DLLS = [
+]
+
+VS_PROJECTS_FROM_CMAKE = {
+ # Format of this dictionary is:
+ # "dll_path": { "src": "source_code_path", "cmake_flags": "flags", "cmake_flags_for_features": {"feature": "flags"}}
+}
+
+WINDOWS_BUILDABLE_DLLS = {
+ # Format of this dictionary is:
+ # dll_path: (proj_path/sln_path, build_flags)
+}
+
+BINARIES = [
+ # List of binaries to include.
+]
diff --git a/tools/windows/prepare_dlls.py b/tools/windows/prepare_dlls.py
new file mode 100644
index 000000000..4d9425f1c
--- /dev/null
+++ b/tools/windows/prepare_dlls.py
@@ -0,0 +1,12 @@
+# The functions in this file let the vendors build and copy tools/libraries
+# that they may find necessary for their products.
+
+from files_to_include import DLLS, WINDOWS_BUILDABLE_DLLS, BINARIES, VS_PROJECTS_FROM_CMAKE
+
+
+def build_dlls(crosvm_root, mode, job_type = "local", features=[]):
+ pass
+
+
+def copy_dlls(crosvm_root, target_paths, mode):
+ pass
diff --git a/tools/x86vm b/tools/x86vm
new file mode 100755
index 000000000..d4d7dae15
--- /dev/null
+++ b/tools/x86vm
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+python3 $(dirname $0)/impl/testvm.py --arch=x86_64 "$@"
diff --git a/tpm2-sys/Android.bp b/tpm2-sys/Android.bp
index f8d68d31d..25e7b3e3d 100644
--- a/tpm2-sys/Android.bp
+++ b/tpm2-sys/Android.bp
@@ -1,4 +1 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Do not modify this file as changes will be overridden on upgrade.
-
// Feature is disabled for Android
diff --git a/tpm2-sys/Cargo.toml b/tpm2-sys/Cargo.toml
index 12dc5ac76..59cfe2c01 100644
--- a/tpm2-sys/Cargo.toml
+++ b/tpm2-sys/Cargo.toml
@@ -2,9 +2,9 @@
name = "tpm2-sys"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
links = "tpm2"
[build-dependencies]
-num_cpus = "*"
pkg-config = "*"
+anyhow = "*"
diff --git a/tpm2-sys/TEST_MAPPING b/tpm2-sys/TEST_MAPPING
index 06e65a5e1..891cae010 100644
--- a/tpm2-sys/TEST_MAPPING
+++ b/tpm2-sys/TEST_MAPPING
@@ -4,10 +4,10 @@
// "presubmit": [
// {
// "host": true,
-// "name": "tpm2-sys_host_test_src_lib"
+// "name": "tpm2-sys_test_src_lib"
// },
// {
-// "name": "tpm2-sys_device_test_src_lib"
+// "name": "tpm2-sys_test_src_lib"
// }
// ]
}
diff --git a/tpm2-sys/build.rs b/tpm2-sys/build.rs
index 604d936cf..a138fb1aa 100644
--- a/tpm2-sys/build.rs
+++ b/tpm2-sys/build.rs
@@ -2,48 +2,75 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+use anyhow::{bail, Result};
use std::env;
-use std::io;
use std::path::Path;
-use std::process::{self, Command};
+use std::path::PathBuf;
+use std::process::Command;
-fn main() -> io::Result<()> {
- if pkg_config::Config::new()
- .statik(true)
- .probe("libtpm2")
- .is_ok()
- {
- // Use tpm2 package from the standard system location if available.
- return Ok(());
+/// Returns the target triplet prefix for gcc commands. No prefix is required
+/// for native builds.
+fn get_cross_compile_prefix() -> String {
+ let target = env::var("TARGET").unwrap();
+
+ if env::var("HOST").unwrap() == target {
+ return String::from("");
}
- // Build with `RUSTFLAGS='--cfg hermetic'` to disallow building our own
- // libtpm2 in a production build context. Building from the libtpm2
- // submodule is a convenience only intended for developer environments.
- if cfg!(hermetic) {
- eprintln!("libtpm2 not found; unable to perform hermetic build");
- process::exit(1);
+ let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
+ let os = env::var("CARGO_CFG_TARGET_OS").unwrap();
+ let env = if target.ends_with("-gnueabihf") {
+ String::from("gnueabihf")
+ } else {
+ env::var("CARGO_CFG_TARGET_ENV").unwrap()
+ };
+ return format!("{}-{}-{}-", arch, os, env);
+}
+
+fn build_libtpm2(out_dir: &Path) -> Result<()> {
+ let lib_path = out_dir.join("libtpm2.a");
+ if lib_path.exists() {
+ return Ok(());
}
if !Path::new("libtpm2/.git").exists() {
- Command::new("git")
- .args(&["submodule", "update", "--init"])
- .status()?;
+ bail!(
+ "tpm2-sys/libtpm2 source does not exist, did you forget to \
+ `git submodule update --init`?"
+ );
}
- if !Path::new("libtpm2/build/libtpm2.a").exists() {
- let ncpu = num_cpus::get();
- let status = Command::new("make")
- .arg(format!("-j{}", ncpu))
- .current_dir("libtpm2")
- .status()?;
- if !status.success() {
- process::exit(status.code().unwrap_or(1));
- }
+ let make_flags = env::var("CARGO_MAKEFLAGS").unwrap();
+ let prefix = get_cross_compile_prefix();
+ let status = Command::new("make")
+ .env("MAKEFLAGS", make_flags)
+ .arg(format!("AR={}ar", prefix))
+ .arg(format!("CC={}gcc", prefix))
+ .arg(format!("OBJCOPY={}objcopy", prefix))
+ .arg(format!("obj={}", out_dir.display()))
+ .current_dir("libtpm2")
+ .status()?;
+ if !status.success() {
+ bail!("make failed with status: {}", status);
}
+ Ok(())
+}
+
+fn main() -> Result<()> {
+ // Use tpm2 package from the standard system location if available.
+ if pkg_config::Config::new()
+ .statik(true)
+ .probe("libtpm2")
+ .is_ok()
+ {
+ return Ok(());
+ }
+
+ // Otherwise build from source
+ let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
+ build_libtpm2(&out_dir)?;
- let dir = env::var("CARGO_MANIFEST_DIR").unwrap();
- println!("cargo:rustc-link-search={}/libtpm2/build", dir);
+ println!("cargo:rustc-link-search={}", out_dir.display());
println!("cargo:rustc-link-lib=static=tpm2");
println!("cargo:rustc-link-lib=ssl");
println!("cargo:rustc-link-lib=crypto");
diff --git a/tpm2-sys/cargo2android.json b/tpm2-sys/cargo2android.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/tpm2-sys/cargo2android.json
@@ -0,0 +1 @@
+{} \ No newline at end of file
diff --git a/tpm2-sys/libtpm2 b/tpm2-sys/libtpm2
deleted file mode 160000
-Subproject 6ab308b4578233be5c65d3a334be48419ea677a
diff --git a/tpm2/Android.bp b/tpm2/Android.bp
index f8d68d31d..25e7b3e3d 100644
--- a/tpm2/Android.bp
+++ b/tpm2/Android.bp
@@ -1,4 +1 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// Do not modify this file as changes will be overridden on upgrade.
-
// Feature is disabled for Android
diff --git a/tpm2/Cargo.toml b/tpm2/Cargo.toml
index 56ee5fbc4..705be840a 100644
--- a/tpm2/Cargo.toml
+++ b/tpm2/Cargo.toml
@@ -2,7 +2,7 @@
name = "tpm2"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
tpm2-sys = { path = "../tpm2-sys" }
diff --git a/tpm2/TEST_MAPPING b/tpm2/TEST_MAPPING
index f2ef9cec0..513c40278 100644
--- a/tpm2/TEST_MAPPING
+++ b/tpm2/TEST_MAPPING
@@ -4,10 +4,10 @@
// "presubmit": [
// {
// "host": true,
-// "name": "tpm2_host_test_src_lib"
+// "name": "tpm2_test_src_lib"
// },
// {
-// "name": "tpm2_device_test_src_lib"
+// "name": "tpm2_test_src_lib"
// }
// ]
}
diff --git a/tpm2/cargo2android.json b/tpm2/cargo2android.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/tpm2/cargo2android.json
@@ -0,0 +1 @@
+{} \ No newline at end of file
diff --git a/unblocked_terms.txt b/unblocked_terms.txt
index bd18c8d54..69af13438 100644
--- a/unblocked_terms.txt
+++ b/unblocked_terms.txt
@@ -3,33 +3,8 @@
#
# See repohooks/README.md for more details.
-# black.?hat
-black.?list
-# build.?cop
-crazy
-# cripple
dummy
-# first.?class.?citizen
-# grandfathered
-# gr[ae]y.?hat
-# gr[ae]y.?list
-# \bhe\b
-# \bshe\b
-# \bhim\b
-# \bher\b
-# \bhis\b
-# \bhers\b
-# man.?in.?the.?middle
master
-# \bmitm(\b|\d)
\bnative
-# \bred.?line
-# rtfm
-# \b(in)?sane(\b|\d)
-sanity
slave
-# white.?glove
-# white.?hat
-# white.?label
white.?list
-\bwtf
diff --git a/usb_sys/Android.bp b/usb_sys/Android.bp
index a9c980029..790048e2b 100644
--- a/usb_sys/Android.bp
+++ b/usb_sys/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -15,75 +15,11 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "usb_sys",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
],
}
-
-rust_defaults {
- name: "usb_sys_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "usb_sys",
- srcs: ["src/lib.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libbase_rust",
- ],
-}
-
-rust_test_host {
- name: "usb_sys_host_test_src_lib",
- defaults: ["usb_sys_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "usb_sys_device_test_src_lib",
- defaults: ["usb_sys_defaults"],
-}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/usb_sys/Cargo.toml b/usb_sys/Cargo.toml
index 0e3cbc388..bb7d4deb0 100644
--- a/usb_sys/Cargo.toml
+++ b/usb_sys/Cargo.toml
@@ -2,7 +2,7 @@
name = "usb_sys"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
base = { path = "../base" }
diff --git a/usb_util/Android.bp b/usb_util/Android.bp
index 98ec8fa20..13b5d73ac 100644
--- a/usb_util/Android.bp
+++ b/usb_util/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -15,87 +15,42 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "usb_util",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libassertions",
"libbase_rust",
"libdata_model",
"liblibc",
+ "libthiserror",
"libusb_sys",
],
proc_macros: ["libremain"],
}
-rust_defaults {
- name: "usb_util_defaults",
+rust_test {
+ name: "usb_util_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "usb_util",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2021",
rustlibs: [
"libassertions",
"libbase_rust",
"libdata_model",
"liblibc",
+ "libthiserror",
"libusb_sys",
],
proc_macros: ["libremain"],
}
-
-rust_test_host {
- name: "usb_util_host_test_src_lib",
- defaults: ["usb_util_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "usb_util_device_test_src_lib",
- defaults: ["usb_util_defaults"],
-}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// ../usb_sys/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// remain-0.2.2
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/usb_util/Cargo.toml b/usb_util/Cargo.toml
index c31f67a6f..5281ea46e 100644
--- a/usb_util/Cargo.toml
+++ b/usb_util/Cargo.toml
@@ -2,12 +2,13 @@
name = "usb_util"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
-assertions = { path = "../assertions" }
-data_model = { path = "../data_model" }
+assertions = { path = "../common/assertions" }
+data_model = { path = "../common/data_model" }
libc = "*"
remain = "*"
+thiserror = "*"
base = { path = "../base" }
usb_sys = { path = "../usb_sys" }
diff --git a/usb_util/src/descriptor.rs b/usb_util/src/descriptor.rs
index 411fef94c..adf48d83a 100644
--- a/usb_util/src/descriptor.rs
+++ b/usb_util/src/descriptor.rs
@@ -7,19 +7,23 @@ use crate::{Error, Result};
use base::warn;
use data_model::DataInit;
use std::collections::BTreeMap;
-use std::io::{self, Read};
use std::mem::size_of;
use std::ops::Deref;
#[derive(Clone)]
pub struct DeviceDescriptorTree {
+ // Full descriptor tree in the original format returned by the device.
+ raw: Vec<u8>,
inner: types::DeviceDescriptor,
// Map of bConfigurationValue to ConfigDescriptor
config_descriptors: BTreeMap<u8, ConfigDescriptorTree>,
+ // Map of config index to bConfigurationValue.
+ config_values: BTreeMap<u8, u8>,
}
#[derive(Clone)]
pub struct ConfigDescriptorTree {
+ offset: usize,
inner: types::ConfigDescriptor,
// Map of (bInterfaceNumber, bAlternateSetting) to InterfaceDescriptor
interface_descriptors: BTreeMap<(u8, u8), InterfaceDescriptorTree>,
@@ -27,6 +31,7 @@ pub struct ConfigDescriptorTree {
#[derive(Clone)]
pub struct InterfaceDescriptorTree {
+ offset: usize,
inner: types::InterfaceDescriptor,
// Map of bEndpointAddress to EndpointDescriptor
endpoint_descriptors: BTreeMap<u8, EndpointDescriptor>,
@@ -36,6 +41,21 @@ impl DeviceDescriptorTree {
pub fn get_config_descriptor(&self, config_value: u8) -> Option<&ConfigDescriptorTree> {
self.config_descriptors.get(&config_value)
}
+
+ /// Retrieve the Nth configuration descriptor in the device descriptor.
+ /// `config_index`: 0-based index into the list of configuration descriptors.
+ pub fn get_config_descriptor_by_index(
+ &self,
+ config_index: u8,
+ ) -> Option<&ConfigDescriptorTree> {
+ self.config_descriptors
+ .get(self.config_values.get(&config_index)?)
+ }
+
+ /// Access the raw descriptor tree as a slice of bytes.
+ pub fn raw(&self) -> &[u8] {
+ &self.raw
+ }
}
impl Deref for DeviceDescriptorTree {
@@ -56,6 +76,11 @@ impl ConfigDescriptorTree {
self.interface_descriptors
.get(&(interface_num, alt_setting))
}
+
+ /// Get the offset of this configuration descriptor within the raw descriptor tree.
+ pub fn offset(&self) -> usize {
+ self.offset
+ }
}
impl Deref for ConfigDescriptorTree {
@@ -70,6 +95,11 @@ impl InterfaceDescriptorTree {
pub fn get_endpoint_descriptor(&self, ep_idx: u8) -> Option<&EndpointDescriptor> {
self.endpoint_descriptors.get(&ep_idx)
}
+
+ /// Get the offset of this interface descriptor within the raw descriptor tree.
+ pub fn offset(&self) -> usize {
+ self.offset
+ }
}
impl Deref for InterfaceDescriptorTree {
@@ -80,97 +110,124 @@ impl Deref for InterfaceDescriptorTree {
}
}
-/// Given a `reader` for a full set of descriptors as provided by the Linux kernel
+/// Given `data` containing a full set of descriptors as provided by the Linux kernel
/// usbdevfs `descriptors` file, parse the descriptors into a tree data structure.
-pub fn parse_usbfs_descriptors<R: Read>(mut reader: R) -> Result<DeviceDescriptorTree> {
- // Given a structure of length `struct_length`, of which `bytes_consumed` have
- // already been read, skip the remainder of the struct. If `bytes_consumed` is
- // more than `struct_length`, no additional bytes are skipped.
- fn skip<R: Read>(reader: R, bytes_consumed: usize, struct_length: u8) -> io::Result<u64> {
- let bytes_to_skip = u64::from(struct_length).saturating_sub(bytes_consumed as u64);
- io::copy(&mut reader.take(bytes_to_skip), &mut io::sink())
- }
+pub fn parse_usbfs_descriptors(data: &[u8]) -> Result<DeviceDescriptorTree> {
+ let mut offset = 0;
- // Find the next descriptor of type T and return it.
+ // Find the next descriptor of type T and return it and its offset.
// Any other descriptors encountered while searching for the expected type are skipped.
- fn next_descriptor<R: Read, T: Descriptor + DataInit>(mut reader: R) -> Result<T> {
+ // The `offset` parameter will be advanced to point to the next byte after the returned
+ // descriptor.
+ fn next_descriptor<T: Descriptor + DataInit>(
+ data: &[u8],
+ offset: &mut usize,
+ ) -> Result<(T, usize)> {
let desc_type = T::descriptor_type() as u8;
loop {
- let hdr = DescriptorHeader::from_reader(&mut reader).map_err(Error::DescriptorRead)?;
+ let hdr = DescriptorHeader::from_slice(
+ data.get(*offset..*offset + size_of::<DescriptorHeader>())
+ .ok_or(Error::DescriptorParse)?,
+ )
+ .ok_or(Error::DescriptorParse)?;
if hdr.bDescriptorType == desc_type {
if usize::from(hdr.bLength) < size_of::<DescriptorHeader>() + size_of::<T>() {
return Err(Error::DescriptorParse);
}
- let desc = T::from_reader(&mut reader).map_err(Error::DescriptorRead)?;
+ let desc_offset = *offset;
- // Skip any extra data beyond the standard descriptor length.
- skip(
- &mut reader,
- size_of::<DescriptorHeader>() + size_of::<T>(),
- hdr.bLength,
+ *offset += size_of::<DescriptorHeader>();
+ let desc = T::from_slice(
+ data.get(*offset..*offset + size_of::<T>())
+ .ok_or(Error::DescriptorParse)?,
)
- .map_err(Error::DescriptorRead)?;
- return Ok(desc);
- }
+ .ok_or(Error::DescriptorParse)?;
+ *offset += hdr.bLength as usize - size_of::<DescriptorHeader>();
+ return Ok((*desc, desc_offset));
+ } else {
+ // Finding a ConfigDescriptor while looking for InterfaceDescriptor means
+ // that we should advance to the next device configuration.
+ if desc_type == types::InterfaceDescriptor::descriptor_type() as u8
+ && hdr.bDescriptorType == types::ConfigDescriptor::descriptor_type() as u8
+ {
+ return Err(Error::DescriptorParse);
+ }
+
+ // Make sure we make forward progress.
+ if hdr.bLength == 0 {
+ return Err(Error::DescriptorParse);
+ }
- // Skip this entire descriptor, since it's not the right type.
- skip(&mut reader, size_of::<DescriptorHeader>(), hdr.bLength)
- .map_err(Error::DescriptorRead)?;
+ // Skip this entire descriptor, since it's not the right type.
+ *offset += hdr.bLength as usize;
+ }
}
}
- let raw_device_descriptor: types::DeviceDescriptor = next_descriptor(&mut reader)?;
+ let (raw_device_descriptor, _) = next_descriptor::<types::DeviceDescriptor>(data, &mut offset)?;
let mut device_descriptor = DeviceDescriptorTree {
+ raw: data.into(),
inner: raw_device_descriptor,
config_descriptors: BTreeMap::new(),
+ config_values: BTreeMap::new(),
};
for cfg_idx in 0..device_descriptor.bNumConfigurations {
- if let Ok(raw_config_descriptor) =
- next_descriptor::<_, types::ConfigDescriptor>(&mut reader)
+ if let Ok((raw_config_descriptor, config_offset)) =
+ next_descriptor::<types::ConfigDescriptor>(&device_descriptor.raw, &mut offset)
{
let mut config_descriptor = ConfigDescriptorTree {
+ offset: config_offset,
inner: raw_config_descriptor,
interface_descriptors: BTreeMap::new(),
};
- for intf_idx in 0..config_descriptor.bNumInterfaces {
- if let Ok(raw_interface_descriptor) =
- next_descriptor::<_, types::InterfaceDescriptor>(&mut reader)
- {
- let mut interface_descriptor = InterfaceDescriptorTree {
- inner: raw_interface_descriptor,
- endpoint_descriptors: BTreeMap::new(),
- };
-
- for ep_idx in 0..interface_descriptor.bNumEndpoints {
- if let Ok(endpoint_descriptor) =
- next_descriptor::<_, EndpointDescriptor>(&mut reader)
- {
- interface_descriptor
- .endpoint_descriptors
- .insert(ep_idx, endpoint_descriptor);
- } else {
- warn!("Could not read endpoint descriptor {}", ep_idx);
- break;
- }
+ while let Ok((raw_interface_descriptor, interface_offset)) =
+ next_descriptor::<types::InterfaceDescriptor>(&device_descriptor.raw, &mut offset)
+ {
+ let mut interface_descriptor = InterfaceDescriptorTree {
+ offset: interface_offset,
+ inner: raw_interface_descriptor,
+ endpoint_descriptors: BTreeMap::new(),
+ };
+
+ for ep_idx in 0..interface_descriptor.bNumEndpoints {
+ if let Ok((endpoint_descriptor, _)) =
+ next_descriptor::<EndpointDescriptor>(&device_descriptor.raw, &mut offset)
+ {
+ interface_descriptor
+ .endpoint_descriptors
+ .insert(ep_idx, endpoint_descriptor);
+ } else {
+ warn!("Could not read endpoint descriptor {}", ep_idx);
+ break;
}
+ }
- config_descriptor.interface_descriptors.insert(
- (
- interface_descriptor.bInterfaceNumber,
- interface_descriptor.bAlternateSetting,
- ),
- interface_descriptor,
- );
- } else {
- warn!("Could not read interface descriptor {}", intf_idx);
- break;
+ config_descriptor.interface_descriptors.insert(
+ (
+ interface_descriptor.bInterfaceNumber,
+ interface_descriptor.bAlternateSetting,
+ ),
+ interface_descriptor,
+ );
+ }
+
+ for intf_idx in 0..config_descriptor.bNumInterfaces {
+ if config_descriptor
+ .interface_descriptors
+ .get(&(intf_idx, 0))
+ .is_none()
+ {
+ warn!("device interface {} has no interface descriptors", intf_idx);
}
}
device_descriptor
+ .config_values
+ .insert(cfg_idx, config_descriptor.bConfigurationValue);
+ device_descriptor
.config_descriptors
.insert(config_descriptor.bConfigurationValue, config_descriptor);
} else {
@@ -183,6 +240,7 @@ pub fn parse_usbfs_descriptors<R: Read>(mut reader: R) -> Result<DeviceDescripto
}
#[cfg(test)]
+#[allow(clippy::useless_conversion)]
mod tests {
use super::*;
#[test]
@@ -496,4 +554,124 @@ mod tests {
assert_eq!(u16::from(e.wMaxPacketSize), 0x0200);
assert_eq!(e.bInterval, 0);
}
+
+ #[test]
+ fn parse_descriptors_multiple_altsettings() {
+ let data: &[u8] = &[
+ // DeviceDescriptor
+ 0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, 0x6d, 0x04, 0x43, 0x08, 0x13, 0x00,
+ 0x00, 0x02, 0x01, 0x01, // ConfigDescriptor
+ 0x09, 0x02, 0x0d, 0x0a, 0x03, 0x01, 0x00, 0x80, 0xfa,
+ // InterfaceDescriptor 0, 0
+ 0x09, 0x04, 0x00, 0x00, 0x01, 0x0e, 0x01, 0x00, 0x00, // EndpointDescriptor
+ 0x07, 0x05, 0x86, 0x03, 0x40, 0x00, 0x08, // InterfaceDescriptor 1, 0
+ 0x09, 0x04, 0x01, 0x00, 0x00, 0x0e, 0x02, 0x00, 0x00,
+ // InterfaceDescriptor 1, 1
+ 0x09, 0x04, 0x01, 0x01, 0x01, 0x0e, 0x02, 0x00, 0x00, // EndpointDescriptor
+ 0x07, 0x05, 0x81, 0x05, 0xc0, 0x00, 0x01, // InterfaceDescriptor 2, 0
+ 0x09, 0x04, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ ];
+
+ let d = parse_usbfs_descriptors(data).expect("parse_usbfs_descriptors failed");
+
+ // The seemingly-redundant u16::from() calls avoid borrows of packed fields.
+
+ assert_eq!(u16::from(d.bcdUSB), 0x02_00);
+ assert_eq!(d.bDeviceClass, 0xef);
+ assert_eq!(d.bDeviceSubClass, 0x02);
+ assert_eq!(d.bDeviceProtocol, 0x01);
+ assert_eq!(d.bMaxPacketSize0, 64);
+ assert_eq!(u16::from(d.idVendor), 0x046d);
+ assert_eq!(u16::from(d.idProduct), 0x0843);
+ assert_eq!(u16::from(d.bcdDevice), 0x00_13);
+ assert_eq!(d.iManufacturer, 0);
+ assert_eq!(d.iProduct, 2);
+ assert_eq!(d.iSerialNumber, 1);
+ assert_eq!(d.bNumConfigurations, 1);
+
+ let c = d
+ .get_config_descriptor(1)
+ .expect("could not get config descriptor 1");
+ assert_eq!(u16::from(c.wTotalLength), 2573);
+ assert_eq!(c.bNumInterfaces, 3);
+ assert_eq!(c.bConfigurationValue, 1);
+ assert_eq!(c.iConfiguration, 0);
+ assert_eq!(c.bmAttributes, 0x80);
+ assert_eq!(c.bMaxPower, 250);
+
+ let i = c
+ .get_interface_descriptor(0, 0)
+ .expect("could not get interface descriptor 0 alt setting 0");
+ assert_eq!(i.bInterfaceNumber, 0);
+ assert_eq!(i.bAlternateSetting, 0);
+ assert_eq!(i.bNumEndpoints, 1);
+ assert_eq!(i.bInterfaceClass, 0x0e);
+ assert_eq!(i.bInterfaceSubClass, 0x01);
+ assert_eq!(i.bInterfaceProtocol, 0x00);
+ assert_eq!(i.iInterface, 0x00);
+
+ let e = i
+ .get_endpoint_descriptor(0)
+ .expect("could not get endpoint 0 descriptor");
+ assert_eq!(e.bEndpointAddress, 0x86);
+ assert_eq!(e.bmAttributes, 0x03);
+ assert_eq!(u16::from(e.wMaxPacketSize), 0x40);
+ assert_eq!(e.bInterval, 0x08);
+
+ let i = c
+ .get_interface_descriptor(1, 0)
+ .expect("could not get interface descriptor 1 alt setting 0");
+ assert_eq!(i.bInterfaceNumber, 1);
+ assert_eq!(i.bAlternateSetting, 0);
+ assert_eq!(i.bNumEndpoints, 0);
+ assert_eq!(i.bInterfaceClass, 0x0e);
+ assert_eq!(i.bInterfaceSubClass, 0x02);
+ assert_eq!(i.bInterfaceProtocol, 0x00);
+ assert_eq!(i.iInterface, 0x00);
+
+ let i = c
+ .get_interface_descriptor(1, 1)
+ .expect("could not get interface descriptor 1 alt setting 1");
+ assert_eq!(i.bInterfaceNumber, 1);
+ assert_eq!(i.bAlternateSetting, 1);
+ assert_eq!(i.bNumEndpoints, 1);
+ assert_eq!(i.bInterfaceClass, 0x0e);
+ assert_eq!(i.bInterfaceSubClass, 0x02);
+ assert_eq!(i.bInterfaceProtocol, 0x00);
+ assert_eq!(i.iInterface, 0x00);
+
+ let e = i
+ .get_endpoint_descriptor(0)
+ .expect("could not get endpoint 0 descriptor");
+ assert_eq!(e.bEndpointAddress, 0x81);
+ assert_eq!(e.bmAttributes, 0x05);
+ assert_eq!(u16::from(e.wMaxPacketSize), 0xc0);
+ assert_eq!(e.bInterval, 0x01);
+
+ let i = c
+ .get_interface_descriptor(2, 0)
+ .expect("could not get interface descriptor 2 alt setting 0");
+ assert_eq!(i.bInterfaceNumber, 2);
+ assert_eq!(i.bAlternateSetting, 0);
+ assert_eq!(i.bNumEndpoints, 0);
+ assert_eq!(i.bInterfaceClass, 0x01);
+ assert_eq!(i.bInterfaceSubClass, 0x01);
+ assert_eq!(i.bInterfaceProtocol, 0x00);
+ assert_eq!(i.iInterface, 0x00);
+ }
+
+ #[test]
+ fn parse_descriptors_length_0() {
+ // Device descriptor followed by a bogus descriptor with bLength == 0.
+ // Note that this was generated by a fuzzer, so field values may not make sense.
+ let data: &[u8] = &[
+ 0x10, 0x00, 0x18, 0x25, 0x80, 0x80, 0xAC, 0x03, 0x22, 0x05, 0x00, 0x00, 0x00, 0x00,
+ 0xC3, 0x2A, 0x00, 0x32, 0x00,
+ ];
+
+ let d = parse_usbfs_descriptors(data);
+ if d.is_ok() {
+ panic!("parse_usbfs_descriptors should have failed");
+ }
+ }
}
diff --git a/usb_util/src/device.rs b/usb_util/src/device.rs
index 47cad2130..d07d3ccd9 100644
--- a/usb_util/src/device.rs
+++ b/usb_util/src/device.rs
@@ -12,7 +12,7 @@ use data_model::vec_with_array_field;
use libc::{EAGAIN, ENODEV, ENOENT};
use std::convert::TryInto;
use std::fs::File;
-use std::io::{Seek, SeekFrom};
+use std::io::{Read, Seek, SeekFrom};
use std::mem::size_of_val;
use std::os::raw::{c_int, c_uint, c_void};
use std::sync::Arc;
@@ -55,7 +55,10 @@ impl Device {
/// `fd` should be a file in usbdevfs (e.g. `/dev/bus/usb/001/002`).
pub fn new(mut fd: File) -> Result<Self> {
fd.seek(SeekFrom::Start(0)).map_err(Error::DescriptorRead)?;
- let device_descriptor_tree = descriptor::parse_usbfs_descriptors(&mut fd)?;
+ let mut descriptor_data = Vec::new();
+ fd.read_to_end(&mut descriptor_data)
+ .map_err(Error::DescriptorRead)?;
+ let device_descriptor_tree = descriptor::parse_usbfs_descriptors(&descriptor_data)?;
let device = Device {
fd: Arc::new(fd),
@@ -149,12 +152,8 @@ impl Device {
let result =
unsafe { self.ioctl_with_mut_ref(usb_sys::USBDEVFS_REAPURBNDELAY(), &mut urb_ptr) };
match result {
- Err(Error::IoctlFailed(_nr, e)) => {
- if e.errno() == EAGAIN {
- // No more completed transfers right now.
- break;
- }
- }
+ // EAGAIN indicates no more completed transfers right now.
+ Err(Error::IoctlFailed(_nr, e)) if e.errno() == EAGAIN => break,
Err(e) => return Err(e),
Ok(_) => {}
}
@@ -269,6 +268,10 @@ impl Device {
Ok(*self.device_descriptor_tree)
}
+ pub fn get_device_descriptor_tree(&self) -> &DeviceDescriptorTree {
+ &self.device_descriptor_tree
+ }
+
/// Get active config descriptor of this device.
pub fn get_config_descriptor(&self, config: u8) -> Result<ConfigDescriptorTree> {
match self.device_descriptor_tree.get_config_descriptor(config) {
@@ -277,8 +280,31 @@ impl Device {
}
}
+ /// Get a configuration descriptor by its index within the list of descriptors returned
+ /// by the device.
+ pub fn get_config_descriptor_by_index(&self, config_index: u8) -> Result<ConfigDescriptorTree> {
+ match self
+ .device_descriptor_tree
+ .get_config_descriptor_by_index(config_index)
+ {
+ Some(config_descriptor) => Ok(config_descriptor.clone()),
+ None => Err(Error::NoSuchDescriptor),
+ }
+ }
+
/// Get bConfigurationValue of the currently active configuration.
pub fn get_active_configuration(&self) -> Result<u8> {
+ // If the device only exposes a single configuration, bypass the control transfer below
+ // by looking up the configuration value from the descriptor.
+ if self.device_descriptor_tree.bNumConfigurations == 1 {
+ if let Some(config_descriptor) = self
+ .device_descriptor_tree
+ .get_config_descriptor_by_index(0)
+ {
+ return Ok(config_descriptor.bConfigurationValue);
+ }
+ }
+
// Send a synchronous control transfer to get the active configuration.
let mut active_config: u8 = 0;
let ctrl_transfer = usb_sys::usbdevfs_ctrltransfer {
diff --git a/usb_util/src/error.rs b/usb_util/src/error.rs
index 5b97c7e90..b24fccd36 100644
--- a/usb_util/src/error.rs
+++ b/usb_util/src/error.rs
@@ -4,48 +4,35 @@
use base::IoctlNr;
use remain::sorted;
-use std::fmt::{self, Display};
use std::io;
use std::num;
+use thiserror::Error;
#[sorted]
-#[derive(Debug)]
+#[derive(Error, Debug)]
pub enum Error {
+ #[error("parsing descriptors failed")]
DescriptorParse,
+ #[error("reading descriptors from device failed: {0}")]
DescriptorRead(io::Error),
+ #[error("File::try_clone() failed: {0}")]
FdCloneFailed(io::Error),
+ #[error("invalid actual_length in URB: {0}")]
InvalidActualLength(num::TryFromIntError),
+ #[error("invalid transfer buffer length: {0}")]
InvalidBufferLength(num::TryFromIntError),
+ #[error("USB ioctl 0x{0:x} failed: {1}")]
IoctlFailed(IoctlNr, base::Error),
+ #[error("Device has been removed")]
NoDevice,
+ #[error("Requested descriptor not found")]
NoSuchDescriptor,
+ #[error("Rc::get_mut failed")]
RcGetMutFailed,
+ #[error("Rc::try_unwrap failed")]
RcUnwrapFailed,
+ #[error("attempted to cancel already-completed transfer")]
TransferAlreadyCompleted,
}
-impl Display for Error {
- #[remain::check]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- #[sorted]
- match self {
- DescriptorParse => write!(f, "parsing descriptors failed"),
- DescriptorRead(e) => write!(f, "reading descriptors from device failed: {}", e),
- FdCloneFailed(e) => write!(f, "File::try_clone() failed: {}", e),
- InvalidActualLength(e) => write!(f, "invalid actual_length in URB: {}", e),
- InvalidBufferLength(e) => write!(f, "invalid transfer buffer length: {}", e),
- IoctlFailed(nr, e) => write!(f, "USB ioctl 0x{:x} failed: {}", nr, e),
- NoDevice => write!(f, "Device has been removed"),
- NoSuchDescriptor => write!(f, "Requested descriptor not found"),
- RcGetMutFailed => write!(f, "Rc::get_mut failed"),
- RcUnwrapFailed => write!(f, "Rc::try_unwrap failed"),
- TransferAlreadyCompleted => write!(f, "attempted to cancel already-completed transfer"),
- }
- }
-}
-
pub type Result<T> = std::result::Result<T, Error>;
-
-impl std::error::Error for Error {}
diff --git a/usb_util/src/lib.rs b/usb_util/src/lib.rs
index d793f6290..c9c620ff2 100644
--- a/usb_util/src/lib.rs
+++ b/usb_util/src/lib.rs
@@ -14,7 +14,7 @@ pub use self::device::{Device, Transfer, TransferStatus};
pub use self::error::{Error, Result};
pub use self::types::{
control_request_type, ConfigDescriptor, ControlRequestDataPhaseTransferDirection,
- ControlRequestRecipient, ControlRequestType, DeviceDescriptor, EndpointDescriptor,
- EndpointDirection, EndpointType, InterfaceDescriptor, StandardControlRequest, UsbRequestSetup,
- ENDPOINT_DIRECTION_OFFSET,
+ ControlRequestRecipient, ControlRequestType, DescriptorHeader, DescriptorType,
+ DeviceDescriptor, EndpointDescriptor, EndpointDirection, EndpointType, InterfaceDescriptor,
+ StandardControlRequest, UsbRequestSetup, ENDPOINT_DIRECTION_OFFSET,
};
diff --git a/usb_util/src/types.rs b/usb_util/src/types.rs
index d669dcdf7..7eb94ca2b 100644
--- a/usb_util/src/types.rs
+++ b/usb_util/src/types.rs
@@ -360,6 +360,7 @@ pub fn control_request_type(
}
#[cfg(test)]
+#[allow(clippy::unusual_byte_groupings)]
mod tests {
use super::*;
diff --git a/vfio_sys/Android.bp b/vfio_sys/Android.bp
index faead377f..46f97c491 100644
--- a/vfio_sys/Android.bp
+++ b/vfio_sys/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -15,75 +15,12 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "vfio_sys",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
+ "libdata_model",
],
}
-
-rust_defaults {
- name: "vfio_sys_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "vfio_sys",
- srcs: ["src/lib.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libbase_rust",
- ],
-}
-
-rust_test_host {
- name: "vfio_sys_host_test_src_lib",
- defaults: ["vfio_sys_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "vfio_sys_device_test_src_lib",
- defaults: ["vfio_sys_defaults"],
-}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/vfio_sys/Cargo.toml b/vfio_sys/Cargo.toml
index da53e5e8c..8890f55d0 100644
--- a/vfio_sys/Cargo.toml
+++ b/vfio_sys/Cargo.toml
@@ -2,7 +2,8 @@
name = "vfio_sys"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
base = { path = "../base" }
+data_model = { path = "../common/data_model" }
diff --git a/vfio_sys/bindgen.sh b/vfio_sys/bindgen.sh
new file mode 100755
index 000000000..12333801e
--- /dev/null
+++ b/vfio_sys/bindgen.sh
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Regenerate vfio_sys bindgen bindings.
+
+set -euo pipefail
+cd "$(dirname "${BASH_SOURCE[0]}")/.."
+
+source tools/impl/bindgen-common.sh
+
+# VFIO_TYPE is translated as a u8 since it is a char constant, but it needs to be u32 for use in
+# ioctl macros.
+fix_vfio_type() {
+ sed -E -e 's/^pub const VFIO_TYPE: u8 = (.*)u8;/pub const VFIO_TYPE: u32 = \1;/'
+}
+
+VFIO_EXTRA="// Added by vfio_sys/bindgen.sh
+use data_model::DataInit;
+
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct vfio_region_info_with_cap {
+ pub region_info: vfio_region_info,
+ pub cap_info: __IncompleteArrayField<u8>,
+}
+
+// vfio_iommu_type1_info_cap_iova_range minus the incomplete iova_ranges
+// array, so that Copy/DataInit can be implemented.
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vfio_iommu_type1_info_cap_iova_range_header {
+ pub header: vfio_info_cap_header,
+ pub nr_iovas: u32,
+ pub reserved: u32,
+}
+
+// Safe because it only has data and no implicit padding.
+unsafe impl DataInit for vfio_info_cap_header {}
+
+// Safe because it only has data and no implicit padding.
+unsafe impl DataInit for vfio_iommu_type1_info_cap_iova_range_header {}
+
+// Safe because it only has data and no implicit padding.
+unsafe impl DataInit for vfio_iova_range {}"
+
+bindgen_generate \
+ --raw-line "${VFIO_EXTRA}" \
+ --allowlist-var='VFIO_.*' \
+ --blocklist-item='VFIO_DEVICE_API_.*_STRING' \
+ --allowlist-type='vfio_.*' \
+ "${BINDGEN_LINUX}/include/uapi/linux/vfio.h" \
+ -- \
+ -D__user= \
+ | replace_linux_int_types | fix_vfio_type \
+ > vfio_sys/src/vfio.rs
diff --git a/vfio_sys/src/lib.rs b/vfio_sys/src/lib.rs
index b85233866..a8849a594 100644
--- a/vfio_sys/src/lib.rs
+++ b/vfio_sys/src/lib.rs
@@ -45,3 +45,15 @@ ioctl_io_nr!(
PLAT_IRQ_FORWARD_TYPE,
PLAT_IRQ_FORWARD_BASE
);
+
+ioctl_io_nr!(
+ PLAT_IRQ_WAKE_SET,
+ PLAT_IRQ_FORWARD_TYPE,
+ PLAT_IRQ_FORWARD_BASE + 1
+);
+
+ioctl_io_nr!(
+ ACPI_GPE_FORWARD_SET,
+ PLAT_IRQ_FORWARD_TYPE,
+ GPE_FORWARD_BASE
+);
diff --git a/vfio_sys/src/plat.rs b/vfio_sys/src/plat.rs
index 7a2e633d1..680eac9d7 100644
--- a/vfio_sys/src/plat.rs
+++ b/vfio_sys/src/plat.rs
@@ -62,9 +62,16 @@ pub const __FD_SETSIZE: u32 = 1024;
pub const PLAT_IRQ_FORWARD_API_VERSION: u32 = 0;
pub const PLAT_IRQ_FORWARD_TYPE: u32 = 59;
pub const PLAT_IRQ_FORWARD_BASE: u32 = 100;
+pub const GPE_FORWARD_BASE: u32 = 130;
pub const PLAT_IRQ_FORWARD_SET_LEVEL_TRIGGER_EVENTFD: u32 = 1;
pub const PLAT_IRQ_FORWARD_SET_LEVEL_UNMASK_EVENTFD: u32 = 2;
pub const PLAT_IRQ_FORWARD_SET_EDGE_TRIGGER: u32 = 4;
+pub const PLAT_IRQ_FORWARD_SET_LEVEL_SCI_FOR_GPE_TRIGGER_EVENTFD: u32 = 8;
+pub const PLAT_IRQ_FORWARD_SET_LEVEL_SCI_FOR_GPE_UNMASK_EVENTFD: u32 = 16;
+pub const PLAT_IRQ_WAKE_ENABLE: u32 = 1;
+pub const PLAT_IRQ_WAKE_DISABLE: u32 = 2;
+pub const ACPI_GPE_FORWARD_SET_TRIGGER: u32 = 1;
+pub const ACPI_GPE_FORWARD_CLEAR_TRIGGER: u32 = 2;
pub type __s8 = ::std::os::raw::c_schar;
pub type __u8 = ::std::os::raw::c_uchar;
pub type __s16 = ::std::os::raw::c_short;
@@ -131,3 +138,17 @@ pub struct plat_irq_forward_set {
pub count: __u32,
pub eventfd: __IncompleteArrayField<__u8>,
}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct plat_irq_wake_set {
+ pub argsz: __u32,
+ pub action_flags: __u32,
+ pub irq_number_host: __u32,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct gpe_forward_set {
+ pub argsz: __u32,
+ pub action_flags: __u32,
+ pub gpe_host_nr: __u32,
+}
diff --git a/vfio_sys/src/vfio.rs b/vfio_sys/src/vfio.rs
index 622b5db4b..401722f4c 100644
--- a/vfio_sys/src/vfio.rs
+++ b/vfio_sys/src/vfio.rs
@@ -1,9 +1,38 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+/* automatically generated by tools/bindgen-all-the-things */
-/* automatically generated by bindgen /usr/include/linux/vfio.h --constified-enum '*'
- * --with-derive-default --no-doc-comments --no-layout-tests */
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+
+// Added by vfio_sys/bindgen.sh
+use data_model::DataInit;
+
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct vfio_region_info_with_cap {
+ pub region_info: vfio_region_info,
+ pub cap_info: __IncompleteArrayField<u8>,
+}
+
+// vfio_iommu_type1_info_cap_iova_range minus the incomplete iova_ranges
+// array, so that Copy/DataInit can be implemented.
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vfio_iommu_type1_info_cap_iova_range_header {
+ pub header: vfio_info_cap_header,
+ pub nr_iovas: u32,
+ pub reserved: u32,
+}
+
+// Safe because it only has data and no implicit padding.
+unsafe impl DataInit for vfio_info_cap_header {}
+
+// Safe because it only has data and no implicit padding.
+unsafe impl DataInit for vfio_iommu_type1_info_cap_iova_range_header {}
+
+// Safe because it only has data and no implicit padding.
+unsafe impl DataInit for vfio_iova_range {}
#[repr(C)]
#[derive(Default)]
@@ -14,12 +43,12 @@ impl<T> __IncompleteArrayField<T> {
__IncompleteArrayField(::std::marker::PhantomData, [])
}
#[inline]
- pub unsafe fn as_ptr(&self) -> *const T {
- ::std::mem::transmute(self)
+ pub fn as_ptr(&self) -> *const T {
+ self as *const _ as *const T
}
#[inline]
- pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
- ::std::mem::transmute(self)
+ pub fn as_mut_ptr(&mut self) -> *mut T {
+ self as *mut _ as *mut T
}
#[inline]
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
@@ -35,34 +64,6 @@ impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
fmt.write_str("__IncompleteArrayField")
}
}
-impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
- #[inline]
- fn clone(&self) -> Self {
- Self::new()
- }
-}
-pub const __BITS_PER_LONG: u32 = 64;
-pub const __FD_SETSIZE: u32 = 1024;
-pub const _IOC_NRBITS: u32 = 8;
-pub const _IOC_TYPEBITS: u32 = 8;
-pub const _IOC_SIZEBITS: u32 = 14;
-pub const _IOC_DIRBITS: u32 = 2;
-pub const _IOC_NRMASK: u32 = 255;
-pub const _IOC_TYPEMASK: u32 = 255;
-pub const _IOC_SIZEMASK: u32 = 16383;
-pub const _IOC_DIRMASK: u32 = 3;
-pub const _IOC_NRSHIFT: u32 = 0;
-pub const _IOC_TYPESHIFT: u32 = 8;
-pub const _IOC_SIZESHIFT: u32 = 16;
-pub const _IOC_DIRSHIFT: u32 = 30;
-pub const _IOC_NONE: u32 = 0;
-pub const _IOC_WRITE: u32 = 1;
-pub const _IOC_READ: u32 = 2;
-pub const IOC_IN: u32 = 1073741824;
-pub const IOC_OUT: u32 = 2147483648;
-pub const IOC_INOUT: u32 = 3221225472;
-pub const IOCSIZE_MASK: u32 = 1073676288;
-pub const IOCSIZE_SHIFT: u32 = 16;
pub const VFIO_API_VERSION: u32 = 0;
pub const VFIO_TYPE1_IOMMU: u32 = 1;
pub const VFIO_SPAPR_TCE_IOMMU: u32 = 2;
@@ -81,10 +82,13 @@ pub const VFIO_DEVICE_FLAGS_PCI: u32 = 2;
pub const VFIO_DEVICE_FLAGS_PLATFORM: u32 = 4;
pub const VFIO_DEVICE_FLAGS_AMBA: u32 = 8;
pub const VFIO_DEVICE_FLAGS_CCW: u32 = 16;
-pub const VFIO_DEVICE_API_PCI_STRING: &[u8; 9usize] = b"vfio-pci\0";
-pub const VFIO_DEVICE_API_PLATFORM_STRING: &[u8; 14usize] = b"vfio-platform\0";
-pub const VFIO_DEVICE_API_AMBA_STRING: &[u8; 10usize] = b"vfio-amba\0";
-pub const VFIO_DEVICE_API_CCW_STRING: &[u8; 9usize] = b"vfio-ccw\0";
+pub const VFIO_DEVICE_FLAGS_AP: u32 = 32;
+pub const VFIO_DEVICE_FLAGS_FSL_MC: u32 = 64;
+pub const VFIO_DEVICE_FLAGS_CAPS: u32 = 128;
+pub const VFIO_DEVICE_INFO_CAP_ZPCI_BASE: u32 = 1;
+pub const VFIO_DEVICE_INFO_CAP_ZPCI_GROUP: u32 = 2;
+pub const VFIO_DEVICE_INFO_CAP_ZPCI_UTIL: u32 = 3;
+pub const VFIO_DEVICE_INFO_CAP_ZPCI_PFIP: u32 = 4;
pub const VFIO_REGION_INFO_FLAG_READ: u32 = 1;
pub const VFIO_REGION_INFO_FLAG_WRITE: u32 = 2;
pub const VFIO_REGION_INFO_FLAG_MMAP: u32 = 4;
@@ -93,10 +97,29 @@ pub const VFIO_REGION_INFO_CAP_SPARSE_MMAP: u32 = 1;
pub const VFIO_REGION_INFO_CAP_TYPE: u32 = 2;
pub const VFIO_REGION_TYPE_PCI_VENDOR_TYPE: u32 = 2147483648;
pub const VFIO_REGION_TYPE_PCI_VENDOR_MASK: u32 = 65535;
+pub const VFIO_REGION_TYPE_GFX: u32 = 1;
+pub const VFIO_REGION_TYPE_CCW: u32 = 2;
+pub const VFIO_REGION_TYPE_MIGRATION: u32 = 3;
pub const VFIO_REGION_SUBTYPE_INTEL_IGD_OPREGION: u32 = 1;
pub const VFIO_REGION_SUBTYPE_INTEL_IGD_HOST_CFG: u32 = 2;
pub const VFIO_REGION_SUBTYPE_INTEL_IGD_LPC_CFG: u32 = 3;
+pub const VFIO_REGION_SUBTYPE_NVIDIA_NVLINK2_RAM: u32 = 1;
+pub const VFIO_REGION_SUBTYPE_IBM_NVLINK2_ATSD: u32 = 1;
+pub const VFIO_REGION_SUBTYPE_GFX_EDID: u32 = 1;
+pub const VFIO_DEVICE_GFX_LINK_STATE_UP: u32 = 1;
+pub const VFIO_DEVICE_GFX_LINK_STATE_DOWN: u32 = 2;
+pub const VFIO_REGION_SUBTYPE_CCW_ASYNC_CMD: u32 = 1;
+pub const VFIO_REGION_SUBTYPE_CCW_SCHIB: u32 = 2;
+pub const VFIO_REGION_SUBTYPE_CCW_CRW: u32 = 3;
+pub const VFIO_REGION_SUBTYPE_MIGRATION: u32 = 1;
+pub const VFIO_DEVICE_STATE_STOP: u32 = 0;
+pub const VFIO_DEVICE_STATE_RUNNING: u32 = 1;
+pub const VFIO_DEVICE_STATE_SAVING: u32 = 2;
+pub const VFIO_DEVICE_STATE_RESUMING: u32 = 4;
+pub const VFIO_DEVICE_STATE_MASK: u32 = 7;
pub const VFIO_REGION_INFO_CAP_MSIX_MAPPABLE: u32 = 3;
+pub const VFIO_REGION_INFO_CAP_NVLINK2_SSATGT: u32 = 4;
+pub const VFIO_REGION_INFO_CAP_NVLINK2_LNKSPD: u32 = 5;
pub const VFIO_IRQ_INFO_EVENTFD: u32 = 1;
pub const VFIO_IRQ_INFO_MASKABLE: u32 = 2;
pub const VFIO_IRQ_INFO_AUTOMASKED: u32 = 4;
@@ -117,9 +140,22 @@ pub const VFIO_DEVICE_IOEVENTFD_16: u32 = 2;
pub const VFIO_DEVICE_IOEVENTFD_32: u32 = 4;
pub const VFIO_DEVICE_IOEVENTFD_64: u32 = 8;
pub const VFIO_DEVICE_IOEVENTFD_SIZE_MASK: u32 = 15;
+pub const VFIO_DEVICE_FEATURE_MASK: u32 = 65535;
+pub const VFIO_DEVICE_FEATURE_GET: u32 = 65536;
+pub const VFIO_DEVICE_FEATURE_SET: u32 = 131072;
+pub const VFIO_DEVICE_FEATURE_PROBE: u32 = 262144;
+pub const VFIO_DEVICE_FEATURE_PCI_VF_TOKEN: u32 = 0;
pub const VFIO_IOMMU_INFO_PGSIZES: u32 = 1;
+pub const VFIO_IOMMU_INFO_CAPS: u32 = 2;
+pub const VFIO_IOMMU_TYPE1_INFO_CAP_IOVA_RANGE: u32 = 1;
+pub const VFIO_IOMMU_TYPE1_INFO_CAP_MIGRATION: u32 = 2;
+pub const VFIO_IOMMU_TYPE1_INFO_DMA_AVAIL: u32 = 3;
pub const VFIO_DMA_MAP_FLAG_READ: u32 = 1;
pub const VFIO_DMA_MAP_FLAG_WRITE: u32 = 2;
+pub const VFIO_DMA_UNMAP_FLAG_GET_DIRTY_BITMAP: u32 = 1;
+pub const VFIO_IOMMU_DIRTY_PAGES_FLAG_START: u32 = 1;
+pub const VFIO_IOMMU_DIRTY_PAGES_FLAG_STOP: u32 = 2;
+pub const VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP: u32 = 4;
pub const VFIO_IOMMU_SPAPR_INFO_DDW: u32 = 1;
pub const VFIO_EEH_PE_DISABLE: u32 = 0;
pub const VFIO_EEH_PE_ENABLE: u32 = 1;
@@ -136,328 +172,388 @@ pub const VFIO_EEH_PE_RESET_HOT: u32 = 6;
pub const VFIO_EEH_PE_RESET_FUNDAMENTAL: u32 = 7;
pub const VFIO_EEH_PE_CONFIGURE: u32 = 8;
pub const VFIO_EEH_PE_INJECT_ERR: u32 = 9;
-pub type __s8 = ::std::os::raw::c_schar;
-pub type __u8 = ::std::os::raw::c_uchar;
-pub type __s16 = ::std::os::raw::c_short;
-pub type __u16 = ::std::os::raw::c_ushort;
-pub type __s32 = ::std::os::raw::c_int;
-pub type __u32 = ::std::os::raw::c_uint;
-pub type __s64 = ::std::os::raw::c_longlong;
-pub type __u64 = ::std::os::raw::c_ulonglong;
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct __kernel_fd_set {
- pub fds_bits: [::std::os::raw::c_ulong; 16usize],
-}
-pub type __kernel_sighandler_t =
- ::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
-pub type __kernel_key_t = ::std::os::raw::c_int;
-pub type __kernel_mqd_t = ::std::os::raw::c_int;
-pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
-pub type __kernel_long_t = ::std::os::raw::c_long;
-pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
-pub type __kernel_ino_t = __kernel_ulong_t;
-pub type __kernel_mode_t = ::std::os::raw::c_uint;
-pub type __kernel_pid_t = ::std::os::raw::c_int;
-pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
-pub type __kernel_uid_t = ::std::os::raw::c_uint;
-pub type __kernel_gid_t = ::std::os::raw::c_uint;
-pub type __kernel_suseconds_t = __kernel_long_t;
-pub type __kernel_daddr_t = ::std::os::raw::c_int;
-pub type __kernel_uid32_t = ::std::os::raw::c_uint;
-pub type __kernel_gid32_t = ::std::os::raw::c_uint;
-pub type __kernel_size_t = __kernel_ulong_t;
-pub type __kernel_ssize_t = __kernel_long_t;
-pub type __kernel_ptrdiff_t = __kernel_long_t;
-#[repr(C)]
-#[derive(Debug, Default, Copy, Clone)]
-pub struct __kernel_fsid_t {
- pub val: [::std::os::raw::c_int; 2usize],
-}
-pub type __kernel_off_t = __kernel_long_t;
-pub type __kernel_loff_t = ::std::os::raw::c_longlong;
-pub type __kernel_time_t = __kernel_long_t;
-pub type __kernel_clock_t = __kernel_long_t;
-pub type __kernel_timer_t = ::std::os::raw::c_int;
-pub type __kernel_clockid_t = ::std::os::raw::c_int;
-pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
-pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
-pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
-pub type __le16 = __u16;
-pub type __be16 = __u16;
-pub type __le32 = __u32;
-pub type __be32 = __u32;
-pub type __le64 = __u64;
-pub type __be64 = __u64;
-pub type __sum16 = __u16;
-pub type __wsum = __u32;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_info_cap_header {
- pub id: __u16,
- pub version: __u16,
- pub next: __u32,
+ pub id: u16,
+ pub version: u16,
+ pub next: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_group_status {
- pub argsz: __u32,
- pub flags: __u32,
+ pub argsz: u32,
+ pub flags: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_device_info {
- pub argsz: __u32,
- pub flags: __u32,
- pub num_regions: __u32,
- pub num_irqs: __u32,
+ pub argsz: u32,
+ pub flags: u32,
+ pub num_regions: u32,
+ pub num_irqs: u32,
+ pub cap_offset: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_region_info {
- pub argsz: __u32,
- pub flags: __u32,
- pub index: __u32,
- pub cap_offset: __u32,
- pub size: __u64,
- pub offset: __u64,
-}
-#[repr(C)]
-#[derive(Debug, Default)]
-pub struct vfio_region_info_with_cap {
- pub region_info: vfio_region_info,
- pub cap_info: __IncompleteArrayField<u8>,
+ pub argsz: u32,
+ pub flags: u32,
+ pub index: u32,
+ pub cap_offset: u32,
+ pub size: u64,
+ pub offset: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_region_sparse_mmap_area {
- pub offset: __u64,
- pub size: __u64,
+ pub offset: u64,
+ pub size: u64,
}
#[repr(C)]
-#[repr(align(8))]
#[derive(Debug, Default)]
pub struct vfio_region_info_cap_sparse_mmap {
pub header: vfio_info_cap_header,
- pub nr_areas: __u32,
- pub reserved: __u32,
+ pub nr_areas: u32,
+ pub reserved: u32,
pub areas: __IncompleteArrayField<vfio_region_sparse_mmap_area>,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_region_info_cap_type {
pub header: vfio_info_cap_header,
- pub type_: __u32,
- pub subtype: __u32,
+ pub type_: u32,
+ pub subtype: u32,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vfio_region_gfx_edid {
+ pub edid_offset: u32,
+ pub edid_max_size: u32,
+ pub edid_size: u32,
+ pub max_xres: u32,
+ pub max_yres: u32,
+ pub link_state: u32,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vfio_device_migration_info {
+ pub device_state: u32,
+ pub reserved: u32,
+ pub pending_bytes: u64,
+ pub data_offset: u64,
+ pub data_size: u64,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vfio_region_info_cap_nvlink2_ssatgt {
+ pub header: vfio_info_cap_header,
+ pub tgt: u64,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vfio_region_info_cap_nvlink2_lnkspd {
+ pub header: vfio_info_cap_header,
+ pub link_speed: u32,
+ pub __pad: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_irq_info {
- pub argsz: __u32,
- pub flags: __u32,
- pub index: __u32,
- pub count: __u32,
+ pub argsz: u32,
+ pub flags: u32,
+ pub index: u32,
+ pub count: u32,
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct vfio_irq_set {
- pub argsz: __u32,
- pub flags: __u32,
- pub index: __u32,
- pub start: __u32,
- pub count: __u32,
- pub data: __IncompleteArrayField<__u8>,
-}
-pub const VFIO_PCI_BAR0_REGION_INDEX: _bindgen_ty_1 = 0;
-pub const VFIO_PCI_BAR1_REGION_INDEX: _bindgen_ty_1 = 1;
-pub const VFIO_PCI_BAR2_REGION_INDEX: _bindgen_ty_1 = 2;
-pub const VFIO_PCI_BAR3_REGION_INDEX: _bindgen_ty_1 = 3;
-pub const VFIO_PCI_BAR4_REGION_INDEX: _bindgen_ty_1 = 4;
-pub const VFIO_PCI_BAR5_REGION_INDEX: _bindgen_ty_1 = 5;
-pub const VFIO_PCI_ROM_REGION_INDEX: _bindgen_ty_1 = 6;
-pub const VFIO_PCI_CONFIG_REGION_INDEX: _bindgen_ty_1 = 7;
-pub const VFIO_PCI_VGA_REGION_INDEX: _bindgen_ty_1 = 8;
-pub const VFIO_PCI_NUM_REGIONS: _bindgen_ty_1 = 9;
-pub type _bindgen_ty_1 = u32;
-pub const VFIO_PCI_INTX_IRQ_INDEX: _bindgen_ty_2 = 0;
-pub const VFIO_PCI_MSI_IRQ_INDEX: _bindgen_ty_2 = 1;
-pub const VFIO_PCI_MSIX_IRQ_INDEX: _bindgen_ty_2 = 2;
-pub const VFIO_PCI_ERR_IRQ_INDEX: _bindgen_ty_2 = 3;
-pub const VFIO_PCI_REQ_IRQ_INDEX: _bindgen_ty_2 = 4;
-pub const VFIO_PCI_NUM_IRQS: _bindgen_ty_2 = 5;
-pub type _bindgen_ty_2 = u32;
-pub const VFIO_CCW_CONFIG_REGION_INDEX: _bindgen_ty_3 = 0;
-pub const VFIO_CCW_NUM_REGIONS: _bindgen_ty_3 = 1;
-pub type _bindgen_ty_3 = u32;
-pub const VFIO_CCW_IO_IRQ_INDEX: _bindgen_ty_4 = 0;
-pub const VFIO_CCW_NUM_IRQS: _bindgen_ty_4 = 1;
-pub type _bindgen_ty_4 = u32;
+ pub argsz: u32,
+ pub flags: u32,
+ pub index: u32,
+ pub start: u32,
+ pub count: u32,
+ pub data: __IncompleteArrayField<u8>,
+}
+pub const VFIO_PCI_BAR0_REGION_INDEX: ::std::os::raw::c_uint = 0;
+pub const VFIO_PCI_BAR1_REGION_INDEX: ::std::os::raw::c_uint = 1;
+pub const VFIO_PCI_BAR2_REGION_INDEX: ::std::os::raw::c_uint = 2;
+pub const VFIO_PCI_BAR3_REGION_INDEX: ::std::os::raw::c_uint = 3;
+pub const VFIO_PCI_BAR4_REGION_INDEX: ::std::os::raw::c_uint = 4;
+pub const VFIO_PCI_BAR5_REGION_INDEX: ::std::os::raw::c_uint = 5;
+pub const VFIO_PCI_ROM_REGION_INDEX: ::std::os::raw::c_uint = 6;
+pub const VFIO_PCI_CONFIG_REGION_INDEX: ::std::os::raw::c_uint = 7;
+pub const VFIO_PCI_VGA_REGION_INDEX: ::std::os::raw::c_uint = 8;
+pub const VFIO_PCI_NUM_REGIONS: ::std::os::raw::c_uint = 9;
+pub type _bindgen_ty_1 = ::std::os::raw::c_uint;
+pub const VFIO_PCI_INTX_IRQ_INDEX: ::std::os::raw::c_uint = 0;
+pub const VFIO_PCI_MSI_IRQ_INDEX: ::std::os::raw::c_uint = 1;
+pub const VFIO_PCI_MSIX_IRQ_INDEX: ::std::os::raw::c_uint = 2;
+pub const VFIO_PCI_ERR_IRQ_INDEX: ::std::os::raw::c_uint = 3;
+pub const VFIO_PCI_REQ_IRQ_INDEX: ::std::os::raw::c_uint = 4;
+pub const VFIO_PCI_NUM_IRQS: ::std::os::raw::c_uint = 5;
+pub type _bindgen_ty_2 = ::std::os::raw::c_uint;
+pub const VFIO_CCW_CONFIG_REGION_INDEX: ::std::os::raw::c_uint = 0;
+pub const VFIO_CCW_NUM_REGIONS: ::std::os::raw::c_uint = 1;
+pub type _bindgen_ty_3 = ::std::os::raw::c_uint;
+pub const VFIO_CCW_IO_IRQ_INDEX: ::std::os::raw::c_uint = 0;
+pub const VFIO_CCW_CRW_IRQ_INDEX: ::std::os::raw::c_uint = 1;
+pub const VFIO_CCW_NUM_IRQS: ::std::os::raw::c_uint = 2;
+pub type _bindgen_ty_4 = ::std::os::raw::c_uint;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_pci_dependent_device {
- pub group_id: __u32,
- pub segment: __u16,
- pub bus: __u8,
- pub devfn: __u8,
+ pub group_id: u32,
+ pub segment: u16,
+ pub bus: u8,
+ pub devfn: u8,
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct vfio_pci_hot_reset_info {
- pub argsz: __u32,
- pub flags: __u32,
- pub count: __u32,
+ pub argsz: u32,
+ pub flags: u32,
+ pub count: u32,
pub devices: __IncompleteArrayField<vfio_pci_dependent_device>,
}
#[repr(C)]
#[derive(Debug, Default)]
pub struct vfio_pci_hot_reset {
- pub argsz: __u32,
- pub flags: __u32,
- pub count: __u32,
- pub group_fds: __IncompleteArrayField<__s32>,
+ pub argsz: u32,
+ pub flags: u32,
+ pub count: u32,
+ pub group_fds: __IncompleteArrayField<i32>,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct vfio_device_gfx_plane_info {
- pub argsz: __u32,
- pub flags: __u32,
- pub drm_plane_type: __u32,
- pub drm_format: __u32,
- pub drm_format_mod: __u64,
- pub width: __u32,
- pub height: __u32,
- pub stride: __u32,
- pub size: __u32,
- pub x_pos: __u32,
- pub y_pos: __u32,
- pub x_hot: __u32,
- pub y_hot: __u32,
+ pub argsz: u32,
+ pub flags: u32,
+ pub drm_plane_type: u32,
+ pub drm_format: u32,
+ pub drm_format_mod: u64,
+ pub width: u32,
+ pub height: u32,
+ pub stride: u32,
+ pub size: u32,
+ pub x_pos: u32,
+ pub y_pos: u32,
+ pub x_hot: u32,
+ pub y_hot: u32,
pub __bindgen_anon_1: vfio_device_gfx_plane_info__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union vfio_device_gfx_plane_info__bindgen_ty_1 {
- pub region_index: __u32,
- pub dmabuf_id: __u32,
- _bindgen_union_align: u32,
+ pub region_index: u32,
+ pub dmabuf_id: u32,
}
impl Default for vfio_device_gfx_plane_info__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
impl Default for vfio_device_gfx_plane_info {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_device_ioeventfd {
- pub argsz: __u32,
- pub flags: __u32,
- pub offset: __u64,
- pub data: __u64,
- pub fd: __s32,
+ pub argsz: u32,
+ pub flags: u32,
+ pub offset: u64,
+ pub data: u64,
+ pub fd: i32,
+}
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct vfio_device_feature {
+ pub argsz: u32,
+ pub flags: u32,
+ pub data: __IncompleteArrayField<u8>,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_iommu_type1_info {
- pub argsz: __u32,
- pub flags: __u32,
- pub iova_pgsizes: __u64,
+ pub argsz: u32,
+ pub flags: u32,
+ pub iova_pgsizes: u64,
+ pub cap_offset: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
-pub struct vfio_iommu_type1_dma_map {
- pub argsz: __u32,
- pub flags: __u32,
- pub vaddr: __u64,
- pub iova: __u64,
- pub size: __u64,
+pub struct vfio_iova_range {
+ pub start: u64,
+ pub end: u64,
+}
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct vfio_iommu_type1_info_cap_iova_range {
+ pub header: vfio_info_cap_header,
+ pub nr_iovas: u32,
+ pub reserved: u32,
+ pub iova_ranges: __IncompleteArrayField<vfio_iova_range>,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vfio_iommu_type1_info_cap_migration {
+ pub header: vfio_info_cap_header,
+ pub flags: u32,
+ pub pgsize_bitmap: u64,
+ pub max_dirty_bitmap_size: u64,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vfio_iommu_type1_info_dma_avail {
+ pub header: vfio_info_cap_header,
+ pub avail: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
+pub struct vfio_iommu_type1_dma_map {
+ pub argsz: u32,
+ pub flags: u32,
+ pub vaddr: u64,
+ pub iova: u64,
+ pub size: u64,
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vfio_bitmap {
+ pub pgsize: u64,
+ pub size: u64,
+ pub data: *mut u64,
+}
+impl Default for vfio_bitmap {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
+}
+#[repr(C)]
+#[derive(Debug, Default)]
pub struct vfio_iommu_type1_dma_unmap {
- pub argsz: __u32,
- pub flags: __u32,
- pub iova: __u64,
- pub size: __u64,
+ pub argsz: u32,
+ pub flags: u32,
+ pub iova: u64,
+ pub size: u64,
+ pub data: __IncompleteArrayField<u8>,
+}
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct vfio_iommu_type1_dirty_bitmap {
+ pub argsz: u32,
+ pub flags: u32,
+ pub data: __IncompleteArrayField<u8>,
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct vfio_iommu_type1_dirty_bitmap_get {
+ pub iova: u64,
+ pub size: u64,
+ pub bitmap: vfio_bitmap,
+}
+impl Default for vfio_iommu_type1_dirty_bitmap_get {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_iommu_spapr_tce_ddw_info {
- pub pgsizes: __u64,
- pub max_dynamic_windows_supported: __u32,
- pub levels: __u32,
+ pub pgsizes: u64,
+ pub max_dynamic_windows_supported: u32,
+ pub levels: u32,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_iommu_spapr_tce_info {
- pub argsz: __u32,
- pub flags: __u32,
- pub dma32_window_start: __u32,
- pub dma32_window_size: __u32,
+ pub argsz: u32,
+ pub flags: u32,
+ pub dma32_window_start: u32,
+ pub dma32_window_size: u32,
pub ddw: vfio_iommu_spapr_tce_ddw_info,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_eeh_pe_err {
- pub type_: __u32,
- pub func: __u32,
- pub addr: __u64,
- pub mask: __u64,
+ pub type_: u32,
+ pub func: u32,
+ pub addr: u64,
+ pub mask: u64,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct vfio_eeh_pe_op {
- pub argsz: __u32,
- pub flags: __u32,
- pub op: __u32,
+ pub argsz: u32,
+ pub flags: u32,
+ pub op: u32,
pub __bindgen_anon_1: vfio_eeh_pe_op__bindgen_ty_1,
}
#[repr(C)]
#[derive(Copy, Clone)]
pub union vfio_eeh_pe_op__bindgen_ty_1 {
pub err: vfio_eeh_pe_err,
- _bindgen_union_align: [u64; 3usize],
}
impl Default for vfio_eeh_pe_op__bindgen_ty_1 {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
impl Default for vfio_eeh_pe_op {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_iommu_spapr_register_memory {
- pub argsz: __u32,
- pub flags: __u32,
- pub vaddr: __u64,
- pub size: __u64,
+ pub argsz: u32,
+ pub flags: u32,
+ pub vaddr: u64,
+ pub size: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_iommu_spapr_tce_create {
- pub argsz: __u32,
- pub flags: __u32,
- pub page_shift: __u32,
- pub __resv1: __u32,
- pub window_size: __u64,
- pub levels: __u32,
- pub __resv2: __u32,
- pub start_addr: __u64,
+ pub argsz: u32,
+ pub flags: u32,
+ pub page_shift: u32,
+ pub __resv1: u32,
+ pub window_size: u64,
+ pub levels: u32,
+ pub __resv2: u32,
+ pub start_addr: u64,
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct vfio_iommu_spapr_tce_remove {
- pub argsz: __u32,
- pub flags: __u32,
- pub start_addr: __u64,
+ pub argsz: u32,
+ pub flags: u32,
+ pub start_addr: u64,
}
diff --git a/vhost/Android.bp b/vhost/Android.bp
index cc6c6a32c..966b202c9 100644
--- a/vhost/Android.bp
+++ b/vhost/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -15,90 +15,44 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "vhost",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libassertions",
"libbase_rust",
"liblibc",
"libnet_util",
+ "libthiserror",
"libvirtio_sys",
"libvm_memory",
],
+ proc_macros: ["libremain"],
}
-rust_defaults {
- name: "vhost_defaults",
+rust_test {
+ name: "vhost_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "vhost",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2021",
rustlibs: [
"libassertions",
"libbase_rust",
"liblibc",
"libnet_util",
+ "libthiserror",
"libvirtio_sys",
"libvm_memory",
],
+ proc_macros: ["libremain"],
}
-
-rust_test_host {
- name: "vhost_host_test_src_lib",
- defaults: ["vhost_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "vhost_device_test_src_lib",
- defaults: ["vhost_defaults"],
-}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../net_sys/src/lib.rs
-// ../net_util/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// ../virtio_sys/src/lib.rs
-// ../vm_memory/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/vhost/Cargo.toml b/vhost/Cargo.toml
index 1e2253f8d..5a945217a 100644
--- a/vhost/Cargo.toml
+++ b/vhost/Cargo.toml
@@ -2,12 +2,14 @@
name = "vhost"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
-assertions = { path = "../assertions" }
+assertions = { path = "../common/assertions" }
libc = "*"
net_util = { path = "../net_util" }
base = { path = "../base" }
+remain = "*"
+thiserror = "*"
virtio_sys = { path = "../virtio_sys" }
vm_memory = { path = "../vm_memory" }
diff --git a/vhost/src/lib.rs b/vhost/src/lib.rs
index 618c5ac47..4b947e3b1 100644
--- a/vhost/src/lib.rs
+++ b/vhost/src/lib.rs
@@ -2,58 +2,52 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#[cfg(unix)]
pub mod net;
mod vsock;
-pub use crate::net::Net;
-pub use crate::net::NetT;
+#[cfg(unix)]
+pub use crate::net::{Net, NetT};
pub use crate::vsock::Vsock;
use std::alloc::Layout;
-use std::fmt::{self, Display};
use std::io::Error as IoError;
-use std::mem;
use std::ptr::null;
use assertions::const_assert;
use base::{ioctl, ioctl_with_mut_ref, ioctl_with_ptr, ioctl_with_ref};
use base::{AsRawDescriptor, Event, LayoutAllocation};
+use remain::sorted;
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- /// Error opening vhost device.
- VhostOpen(IoError),
- /// Error while running ioctl.
- IoctlError(IoError),
- /// Invalid queue.
- InvalidQueue,
- /// Invalid descriptor table address.
- DescriptorTableAddress(GuestMemoryError),
- /// Invalid used address.
- UsedAddress(GuestMemoryError),
/// Invalid available address.
+ #[error("invalid available address: {0}")]
AvailAddress(GuestMemoryError),
+ /// Invalid descriptor table address.
+ #[error("invalid descriptor table address: {0}")]
+ DescriptorTableAddress(GuestMemoryError),
+ /// Invalid queue.
+ #[error("invalid queue")]
+ InvalidQueue,
+ /// Error while running ioctl.
+ #[error("failed to run ioctl: {0}")]
+ IoctlError(IoError),
/// Invalid log address.
+ #[error("invalid log address: {0}")]
LogAddress(GuestMemoryError),
+ /// Invalid used address.
+ #[error("invalid used address: {0}")]
+ UsedAddress(GuestMemoryError),
+ /// Error opening vhost device.
+ #[error("failed to open vhost device: {0}")]
+ VhostOpen(IoError),
}
-pub type Result<T> = std::result::Result<T, Error>;
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- VhostOpen(e) => write!(f, "failed to open vhost device: {}", e),
- IoctlError(e) => write!(f, "failed to run ioctl: {}", e),
- InvalidQueue => write!(f, "invalid queue"),
- DescriptorTableAddress(e) => write!(f, "invalid descriptor table address: {}", e),
- UsedAddress(e) => write!(f, "invalid used address: {}", e),
- AvailAddress(e) => write!(f, "invalid available address: {}", e),
- LogAddress(e) => write!(f, "invalid log address: {}", e),
- }
- }
-}
+pub type Result<T> = std::result::Result<T, Error>;
fn ioctl_result<T>() -> Result<T> {
Err(Error::IoctlError(IoError::last_os_error()))
@@ -64,13 +58,10 @@ fn ioctl_result<T>() -> Result<T> {
/// transfer. The device itself only needs to deal with setting up the kernel driver and
/// managing the control channel.
pub trait Vhost: AsRawDescriptor + std::marker::Sized {
- /// Get the guest memory mapping.
- fn mem(&self) -> &GuestMemory;
-
/// Set the current process as the owner of this file descriptor.
/// This must be run before any other vhost ioctls.
fn set_owner(&self) -> Result<()> {
- // This ioctl is called on a valid vhost_net fd and has its
+ // This ioctl is called on a valid vhost_net descriptor and has its
// return value checked.
let ret = unsafe { ioctl(self, virtio_sys::VHOST_SET_OWNER()) };
if ret < 0 {
@@ -79,10 +70,22 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
Ok(())
}
+ /// Give up ownership and reset the device to default values. Allows a subsequent call to
+ /// `set_owner` to succeed.
+ fn reset_owner(&self) -> Result<()> {
+ // This ioctl is called on a valid vhost fd and has its
+ // return value checked.
+ let ret = unsafe { ioctl(self, virtio_sys::VHOST_RESET_OWNER()) };
+ if ret < 0 {
+ return ioctl_result();
+ }
+ Ok(())
+ }
+
/// Get a bitmask of supported virtio/vhost features.
fn get_features(&self) -> Result<u64> {
let mut avail_features: u64 = 0;
- // This ioctl is called on a valid vhost_net fd and has its
+ // This ioctl is called on a valid vhost_net descriptor and has its
// return value checked.
let ret = unsafe {
ioctl_with_mut_ref(self, virtio_sys::VHOST_GET_FEATURES(), &mut avail_features)
@@ -99,7 +102,7 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
/// # Arguments
/// * `features` - Bitmask of features to set.
fn set_features(&self, features: u64) -> Result<()> {
- // This ioctl is called on a valid vhost_net fd and has its
+ // This ioctl is called on a valid vhost_net descriptor and has its
// return value checked.
let ret = unsafe { ioctl_with_ref(self, virtio_sys::VHOST_SET_FEATURES(), &features) };
if ret < 0 {
@@ -109,14 +112,14 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
}
/// Set the guest memory mappings for vhost to use.
- fn set_mem_table(&self) -> Result<()> {
- const SIZE_OF_MEMORY: usize = mem::size_of::<virtio_sys::vhost_memory>();
- const SIZE_OF_REGION: usize = mem::size_of::<virtio_sys::vhost_memory_region>();
- const ALIGN_OF_MEMORY: usize = mem::align_of::<virtio_sys::vhost_memory>();
- const ALIGN_OF_REGION: usize = mem::align_of::<virtio_sys::vhost_memory_region>();
+ fn set_mem_table(&self, mem: &GuestMemory) -> Result<()> {
+ const SIZE_OF_MEMORY: usize = std::mem::size_of::<virtio_sys::vhost_memory>();
+ const SIZE_OF_REGION: usize = std::mem::size_of::<virtio_sys::vhost_memory_region>();
+ const ALIGN_OF_MEMORY: usize = std::mem::align_of::<virtio_sys::vhost_memory>();
+ const ALIGN_OF_REGION: usize = std::mem::align_of::<virtio_sys::vhost_memory_region>();
const_assert!(ALIGN_OF_MEMORY >= ALIGN_OF_REGION);
- let num_regions = self.mem().num_regions() as usize;
+ let num_regions = mem.num_regions() as usize;
let size = SIZE_OF_MEMORY + num_regions * SIZE_OF_REGION;
let layout = Layout::from_size_align(size, ALIGN_OF_MEMORY).expect("impossible layout");
let mut allocation = LayoutAllocation::zeroed(layout);
@@ -130,17 +133,15 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
// we correctly specify the size to match the amount of backing memory.
let vhost_regions = unsafe { vhost_memory.regions.as_mut_slice(num_regions as usize) };
- let _ = self
- .mem()
- .with_regions::<_, ()>(|index, guest_addr, size, host_addr, _, _| {
- vhost_regions[index] = virtio_sys::vhost_memory_region {
- guest_phys_addr: guest_addr.offset() as u64,
- memory_size: size as u64,
- userspace_addr: host_addr as u64,
- flags_padding: 0u64,
- };
- Ok(())
- });
+ let _ = mem.with_regions::<_, ()>(|index, guest_addr, size, host_addr, _, _| {
+ vhost_regions[index] = virtio_sys::vhost_memory_region {
+ guest_phys_addr: guest_addr.offset() as u64,
+ memory_size: size as u64,
+ userspace_addr: host_addr as u64,
+ flags_padding: 0u64,
+ };
+ Ok(())
+ });
// This ioctl is called with a pointer that is valid for the lifetime
// of this function. The kernel will make its own copy of the memory
@@ -166,7 +167,7 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
num: num as u32,
};
- // This ioctl is called on a valid vhost_net fd and has its
+ // This ioctl is called on a valid vhost_net descriptor and has its
// return value checked.
let ret = unsafe { ioctl_with_ref(self, virtio_sys::VHOST_SET_VRING_NUM(), &vring_state) };
if ret < 0 {
@@ -179,6 +180,7 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
#[allow(clippy::if_same_then_else)]
fn is_valid(
&self,
+ mem: &GuestMemory,
queue_max_size: u16,
queue_size: u16,
desc_addr: GuestAddress,
@@ -192,17 +194,17 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
false
} else if desc_addr
.checked_add(desc_table_size as u64)
- .map_or(true, |v| !self.mem().address_in_range(v))
+ .map_or(true, |v| !mem.address_in_range(v))
{
false
} else if avail_addr
.checked_add(avail_ring_size as u64)
- .map_or(true, |v| !self.mem().address_in_range(v))
+ .map_or(true, |v| !mem.address_in_range(v))
{
false
} else if used_addr
.checked_add(used_ring_size as u64)
- .map_or(true, |v| !self.mem().address_in_range(v))
+ .map_or(true, |v| !mem.address_in_range(v))
{
false
} else {
@@ -223,6 +225,7 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
/// * `log_addr` - Optional address for logging.
fn set_vring_addr(
&self,
+ mem: &GuestMemory,
queue_max_size: u16,
queue_size: u16,
queue_index: usize,
@@ -234,25 +237,29 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
) -> Result<()> {
// TODO(smbarber): Refactor out virtio from crosvm so we can
// validate a Queue struct directly.
- if !self.is_valid(queue_max_size, queue_size, desc_addr, used_addr, avail_addr) {
+ if !self.is_valid(
+ mem,
+ queue_max_size,
+ queue_size,
+ desc_addr,
+ used_addr,
+ avail_addr,
+ ) {
return Err(Error::InvalidQueue);
}
- let desc_addr = self
- .mem()
+ let desc_addr = mem
.get_host_address(desc_addr)
.map_err(Error::DescriptorTableAddress)?;
- let used_addr = self
- .mem()
+ let used_addr = mem
.get_host_address(used_addr)
.map_err(Error::UsedAddress)?;
- let avail_addr = self
- .mem()
+ let avail_addr = mem
.get_host_address(avail_addr)
.map_err(Error::AvailAddress)?;
let log_addr = match log_addr {
None => null(),
- Some(a) => self.mem().get_host_address(a).map_err(Error::LogAddress)?,
+ Some(a) => mem.get_host_address(a).map_err(Error::LogAddress)?,
};
let vring_addr = virtio_sys::vhost_vring_addr {
@@ -264,7 +271,7 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
log_guest_addr: log_addr as u64,
};
- // This ioctl is called on a valid vhost_net fd and has its
+ // This ioctl is called on a valid vhost_net descriptor and has its
// return value checked.
let ret = unsafe { ioctl_with_ref(self, virtio_sys::VHOST_SET_VRING_ADDR(), &vring_addr) };
if ret < 0 {
@@ -284,7 +291,7 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
num: num as u32,
};
- // This ioctl is called on a valid vhost_net fd and has its
+ // This ioctl is called on a valid vhost_net descriptor and has its
// return value checked.
let ret = unsafe { ioctl_with_ref(self, virtio_sys::VHOST_SET_VRING_BASE(), &vring_state) };
if ret < 0 {
@@ -293,6 +300,27 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
Ok(())
}
+ /// Gets the index of the next available descriptor in the queue.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to query.
+ fn get_vring_base(&self, queue_index: usize) -> Result<u16> {
+ let mut vring_state = virtio_sys::vhost_vring_state {
+ index: queue_index as u32,
+ num: 0,
+ };
+
+ // Safe because this will only modify `vring_state` and we check the return value.
+ let ret = unsafe {
+ ioctl_with_mut_ref(self, virtio_sys::VHOST_GET_VRING_BASE(), &mut vring_state)
+ };
+ if ret < 0 {
+ return ioctl_result();
+ }
+
+ Ok(vring_state.num as u16)
+ }
+
/// Set the event to trigger when buffers have been used by the host.
///
/// # Arguments
@@ -301,10 +329,10 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
fn set_vring_call(&self, queue_index: usize, event: &Event) -> Result<()> {
let vring_file = virtio_sys::vhost_vring_file {
index: queue_index as u32,
- event: event.as_raw_descriptor(),
+ fd: event.as_raw_descriptor(),
};
- // This ioctl is called on a valid vhost_net fd and has its
+ // This ioctl is called on a valid vhost_net descriptor and has its
// return value checked.
let ret = unsafe { ioctl_with_ref(self, virtio_sys::VHOST_SET_VRING_CALL(), &vring_file) };
if ret < 0 {
@@ -313,19 +341,39 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
Ok(())
}
+ /// Set the event to trigger to signal an error.
+ ///
+ /// # Arguments
+ /// * `queue_index` - Index of the queue to modify.
+ /// * `event` - Event to trigger.
+ fn set_vring_err(&self, queue_index: usize, event: &Event) -> Result<()> {
+ let vring_file = virtio_sys::vhost_vring_file {
+ index: queue_index as u32,
+ fd: event.as_raw_descriptor(),
+ };
+
+ // This ioctl is called on a valid vhost_net fd and has its
+ // return value checked.
+ let ret = unsafe { ioctl_with_ref(self, virtio_sys::VHOST_SET_VRING_ERR(), &vring_file) };
+ if ret < 0 {
+ return ioctl_result();
+ }
+ Ok(())
+ }
+
/// Set the event that will be signaled by the guest when buffers are
/// available for the host to process.
///
/// # Arguments
/// * `queue_index` - Index of the queue to modify.
- /// * `fd` - Event that will be signaled from guest.
+ /// * `event` - Event that will be signaled from guest.
fn set_vring_kick(&self, queue_index: usize, event: &Event) -> Result<()> {
let vring_file = virtio_sys::vhost_vring_file {
index: queue_index as u32,
- event: event.as_raw_descriptor(),
+ fd: event.as_raw_descriptor(),
};
- // This ioctl is called on a valid vhost_net fd and has its
+ // This ioctl is called on a valid vhost_net descriptor and has its
// return value checked.
let ret = unsafe { ioctl_with_ref(self, virtio_sys::VHOST_SET_VRING_KICK(), &vring_file) };
if ret < 0 {
@@ -335,6 +383,8 @@ pub trait Vhost: AsRawDescriptor + std::marker::Sized {
}
}
+// TODO(225193541): Enable/add tests for windows.
+#[cfg(unix)]
#[cfg(test)]
mod tests {
use super::*;
@@ -347,7 +397,7 @@ mod tests {
fn create_guest_memory() -> result::Result<GuestMemory, GuestMemoryError> {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x1000);
- GuestMemory::new(&vec![(start_addr1, 0x1000), (start_addr2, 0x4000)])
+ GuestMemory::new(&[(start_addr1, 0x1000), (start_addr2, 0x4000)])
}
fn assert_ok_or_known_failure<T>(res: Result<T>) {
@@ -360,8 +410,7 @@ mod tests {
}
fn create_fake_vhost_net() -> FakeNet<FakeTap> {
- let gm = create_guest_memory().unwrap();
- FakeNet::<FakeTap>::new(&PathBuf::from(""), &gm).unwrap()
+ FakeNet::<FakeTap>::new(&PathBuf::from("")).unwrap()
}
#[test]
@@ -393,7 +442,8 @@ mod tests {
#[test]
fn set_mem_table() {
let vhost_net = create_fake_vhost_net();
- let res = vhost_net.set_mem_table();
+ let gm = create_guest_memory().unwrap();
+ let res = vhost_net.set_mem_table(&gm);
assert_ok_or_known_failure(res);
}
@@ -407,7 +457,9 @@ mod tests {
#[test]
fn set_vring_addr() {
let vhost_net = create_fake_vhost_net();
+ let gm = create_guest_memory().unwrap();
let res = vhost_net.set_vring_addr(
+ &gm,
1,
1,
0,
diff --git a/vhost/src/net.rs b/vhost/src/net.rs
index d0e57e710..47af74944 100644
--- a/vhost/src/net.rs
+++ b/vhost/src/net.rs
@@ -7,11 +7,10 @@ use std::marker::PhantomData;
use std::os::unix::fs::OpenOptionsExt;
use std::{
fs::{File, OpenOptions},
- path::PathBuf,
+ path::Path,
};
use base::{ioctl_with_ref, AsRawDescriptor, RawDescriptor};
-use vm_memory::GuestMemory;
use super::{ioctl_result, Error, Result, Vhost};
@@ -23,13 +22,12 @@ pub struct Net<T> {
// descriptor must be dropped first, which will stop and tear down the
// vhost-net worker before GuestMemory can potentially be unmapped.
descriptor: File,
- mem: GuestMemory,
phantom: PhantomData<T>,
}
pub trait NetT<T: TapT>: Vhost + AsRawDescriptor + Send + Sized {
/// Create a new NetT instance
- fn new(vhost_net_device_path: &PathBuf, mem: &GuestMemory) -> Result<Self>;
+ fn new(vhost_net_device_path: &Path) -> Result<Self>;
/// Set the tap file descriptor that will serve as the VHOST_NET backend.
/// This will start the vhost worker for the given queue.
@@ -48,7 +46,7 @@ where
///
/// # Arguments
/// * `mem` - Guest memory mapping.
- fn new(vhost_net_device_path: &PathBuf, mem: &GuestMemory) -> Result<Net<T>> {
+ fn new(vhost_net_device_path: &Path) -> Result<Net<T>> {
Ok(Net::<T> {
descriptor: OpenOptions::new()
.read(true)
@@ -56,7 +54,6 @@ where
.custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK)
.open(vhost_net_device_path)
.map_err(Error::VhostOpen)?,
- mem: mem.clone(),
phantom: PhantomData,
})
}
@@ -64,7 +61,7 @@ where
fn set_backend(&self, queue_index: usize, event: Option<&T>) -> Result<()> {
let vring_file = virtio_sys::vhost_vring_file {
index: queue_index as u32,
- event: event.map_or(-1, |event| event.as_raw_descriptor()),
+ fd: event.map_or(-1, |event| event.as_raw_descriptor()),
};
// This ioctl is called on a valid vhost_net descriptor and has its
@@ -83,11 +80,7 @@ where
}
}
-impl<T> Vhost for Net<T> {
- fn mem(&self) -> &GuestMemory {
- &self.mem
- }
-}
+impl<T> Vhost for Net<T> {}
impl<T> AsRawDescriptor for Net<T> {
fn as_raw_descriptor(&self) -> RawDescriptor {
@@ -104,7 +97,6 @@ pub mod fakes {
pub struct FakeNet<T> {
descriptor: File,
- mem: GuestMemory,
phantom: PhantomData<T>,
}
@@ -118,7 +110,7 @@ pub mod fakes {
where
T: TapT,
{
- fn new(_vhost_net_device_path: &PathBuf, mem: &GuestMemory) -> Result<FakeNet<T>> {
+ fn new(_vhost_net_device_path: &Path) -> Result<FakeNet<T>> {
Ok(FakeNet::<T> {
descriptor: OpenOptions::new()
.read(true)
@@ -126,7 +118,6 @@ pub mod fakes {
.create(true)
.open(TMP_FILE)
.unwrap(),
- mem: mem.clone(),
phantom: PhantomData,
})
}
@@ -136,11 +127,7 @@ pub mod fakes {
}
}
- impl<T> Vhost for FakeNet<T> {
- fn mem(&self) -> &GuestMemory {
- &self.mem
- }
- }
+ impl<T> Vhost for FakeNet<T> {}
impl<T> AsRawDescriptor for FakeNet<T> {
fn as_raw_descriptor(&self) -> RawDescriptor {
diff --git a/vhost/src/vsock.rs b/vhost/src/vsock.rs
index fb9795137..b690c54b3 100644
--- a/vhost/src/vsock.rs
+++ b/vhost/src/vsock.rs
@@ -2,36 +2,24 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::os::unix::fs::OpenOptionsExt;
-use std::{
- fs::{File, OpenOptions},
- path::PathBuf,
-};
+use std::fs::File;
use base::{ioctl_with_ref, AsRawDescriptor, RawDescriptor};
use virtio_sys::{VHOST_VSOCK_SET_GUEST_CID, VHOST_VSOCK_SET_RUNNING};
-use vm_memory::GuestMemory;
-use super::{ioctl_result, Error, Result, Vhost};
+use super::{ioctl_result, Result, Vhost};
/// Handle for running VHOST_VSOCK ioctls.
pub struct Vsock {
descriptor: File,
- mem: GuestMemory,
}
impl Vsock {
/// Open a handle to a new VHOST_VSOCK instance.
- pub fn new(vhost_vsock_device_path: &PathBuf, mem: &GuestMemory) -> Result<Vsock> {
- Ok(Vsock {
- descriptor: OpenOptions::new()
- .read(true)
- .write(true)
- .custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK)
- .open(vhost_vsock_device_path)
- .map_err(Error::VhostOpen)?,
- mem: mem.clone(),
- })
+ pub fn new(vhost_vsock_file: File) -> Vsock {
+ Vsock {
+ descriptor: vhost_vsock_file,
+ }
}
/// Set the CID for the guest. This number is used for routing all data destined for
@@ -69,11 +57,7 @@ impl Vsock {
}
}
-impl Vhost for Vsock {
- fn mem(&self) -> &GuestMemory {
- &self.mem
- }
-}
+impl Vhost for Vsock {}
impl AsRawDescriptor for Vsock {
fn as_raw_descriptor(&self) -> RawDescriptor {
diff --git a/virtio_sys/Android.bp b/virtio_sys/Android.bp
index fa7583489..6c726eff5 100644
--- a/virtio_sys/Android.bp
+++ b/virtio_sys/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -15,75 +15,11 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "virtio_sys",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
],
}
-
-rust_defaults {
- name: "virtio_sys_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "virtio_sys",
- srcs: ["src/lib.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
- rustlibs: [
- "libbase_rust",
- ],
-}
-
-rust_test_host {
- name: "virtio_sys_host_test_src_lib",
- defaults: ["virtio_sys_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "virtio_sys_device_test_src_lib",
- defaults: ["virtio_sys_defaults"],
-}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/virtio_sys/Cargo.toml b/virtio_sys/Cargo.toml
index 276a80448..0e3da484d 100644
--- a/virtio_sys/Cargo.toml
+++ b/virtio_sys/Cargo.toml
@@ -2,7 +2,7 @@
name = "virtio_sys"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[dependencies]
base = { path = "../base" }
diff --git a/virtio_sys/bindgen.sh b/virtio_sys/bindgen.sh
new file mode 100755
index 000000000..d22d82b3e
--- /dev/null
+++ b/virtio_sys/bindgen.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+# Copyright 2022 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Regenerate virtio_sys bindgen bindings.
+
+set -euo pipefail
+cd "$(dirname "${BASH_SOURCE[0]}")/.."
+
+source tools/impl/bindgen-common.sh
+
+bindgen_generate \
+ --allowlist-type='vhost_.*' \
+ --allowlist-var='VHOST_.*' \
+ --allowlist-var='VIRTIO_.*' \
+ "${BINDGEN_LINUX_X86_HEADERS}/include/linux/vhost.h" \
+ -- \
+ -isystem "${BINDGEN_LINUX_X86_HEADERS}/include" \
+ | replace_linux_int_types \
+ > virtio_sys/src/vhost.rs
+
+bindgen_generate \
+ --allowlist-var='VIRTIO_NET_.*' \
+ --allowlist-type='virtio_net_.*' \
+ --blocklist-type='virtio_net_ctrl_mac' \
+ "${BINDGEN_LINUX_X86_HEADERS}/include/linux/virtio_net.h" \
+ -- \
+ -isystem "${BINDGEN_LINUX_X86_HEADERS}/include" \
+ | replace_linux_int_types \
+ > virtio_sys/src/virtio_net.rs
+
+bindgen_generate \
+ --allowlist-var='VRING_.*' \
+ --allowlist-var='VIRTIO_RING_.*' \
+ --allowlist-type='vring.*' \
+ "${BINDGEN_LINUX_X86_HEADERS}/include/linux/virtio_ring.h" \
+ -- \
+ -isystem "${BINDGEN_LINUX_X86_HEADERS}/include" \
+ | replace_linux_int_types \
+ > virtio_sys/src/virtio_ring.rs
diff --git a/virtio_sys/src/vhost.rs b/virtio_sys/src/vhost.rs
index 020d43cec..3121cacea 100644
--- a/virtio_sys/src/vhost.rs
+++ b/virtio_sys/src/vhost.rs
@@ -1,24 +1,25 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+/* automatically generated by tools/bindgen-all-the-things */
-/* automatically generated by rust-bindgen */
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
#[repr(C)]
#[derive(Default)]
-pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>);
+pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
impl<T> __IncompleteArrayField<T> {
#[inline]
- pub fn new() -> Self {
- __IncompleteArrayField(::std::marker::PhantomData)
+ pub const fn new() -> Self {
+ __IncompleteArrayField(::std::marker::PhantomData, [])
}
#[inline]
- pub unsafe fn as_ptr(&self) -> *const T {
- ::std::mem::transmute(self)
+ pub fn as_ptr(&self) -> *const T {
+ self as *const _ as *const T
}
#[inline]
- pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
- ::std::mem::transmute(self)
+ pub fn as_mut_ptr(&mut self) -> *mut T {
+ self as *mut _ as *mut T
}
#[inline]
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
@@ -30,862 +31,184 @@ impl<T> __IncompleteArrayField<T> {
}
}
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
- fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
fmt.write_str("__IncompleteArrayField")
}
}
-impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
- #[inline]
- fn clone(&self) -> Self {
- Self::new()
- }
-}
-impl<T> ::std::marker::Copy for __IncompleteArrayField<T> {}
-pub const __BITS_PER_LONG: ::std::os::raw::c_uint = 64;
-pub const __FD_SETSIZE: ::std::os::raw::c_uint = 1024;
-pub const _IOC_NRBITS: ::std::os::raw::c_uint = 8;
-pub const _IOC_TYPEBITS: ::std::os::raw::c_uint = 8;
-pub const _IOC_SIZEBITS: ::std::os::raw::c_uint = 14;
-pub const _IOC_DIRBITS: ::std::os::raw::c_uint = 2;
-pub const _IOC_NRMASK: ::std::os::raw::c_uint = 255;
-pub const _IOC_TYPEMASK: ::std::os::raw::c_uint = 255;
-pub const _IOC_SIZEMASK: ::std::os::raw::c_uint = 16383;
-pub const _IOC_DIRMASK: ::std::os::raw::c_uint = 3;
-pub const _IOC_NRSHIFT: ::std::os::raw::c_uint = 0;
-pub const _IOC_TYPESHIFT: ::std::os::raw::c_uint = 8;
-pub const _IOC_SIZESHIFT: ::std::os::raw::c_uint = 16;
-pub const _IOC_DIRSHIFT: ::std::os::raw::c_uint = 30;
-pub const _IOC_NONE: ::std::os::raw::c_uint = 0;
-pub const _IOC_WRITE: ::std::os::raw::c_uint = 1;
-pub const _IOC_READ: ::std::os::raw::c_uint = 2;
-pub const IOC_IN: ::std::os::raw::c_uint = 1073741824;
-pub const IOC_OUT: ::std::os::raw::c_uint = 2147483648;
-pub const IOC_INOUT: ::std::os::raw::c_uint = 3221225472;
-pub const IOCSIZE_MASK: ::std::os::raw::c_uint = 1073676288;
-pub const IOCSIZE_SHIFT: ::std::os::raw::c_uint = 16;
-pub const VIRTIO_CONFIG_S_ACKNOWLEDGE: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_CONFIG_S_DRIVER: ::std::os::raw::c_uint = 2;
-pub const VIRTIO_CONFIG_S_DRIVER_OK: ::std::os::raw::c_uint = 4;
-pub const VIRTIO_CONFIG_S_FEATURES_OK: ::std::os::raw::c_uint = 8;
-pub const VIRTIO_CONFIG_S_FAILED: ::std::os::raw::c_uint = 128;
-pub const VIRTIO_TRANSPORT_F_START: ::std::os::raw::c_uint = 28;
-pub const VIRTIO_TRANSPORT_F_END: ::std::os::raw::c_uint = 33;
-pub const VIRTIO_F_NOTIFY_ON_EMPTY: ::std::os::raw::c_uint = 24;
-pub const VIRTIO_F_ANY_LAYOUT: ::std::os::raw::c_uint = 27;
-pub const VIRTIO_F_VERSION_1: ::std::os::raw::c_uint = 32;
-pub const _STDINT_H: ::std::os::raw::c_uint = 1;
-pub const _FEATURES_H: ::std::os::raw::c_uint = 1;
-pub const _DEFAULT_SOURCE: ::std::os::raw::c_uint = 1;
-pub const __USE_ISOC11: ::std::os::raw::c_uint = 1;
-pub const __USE_ISOC99: ::std::os::raw::c_uint = 1;
-pub const __USE_ISOC95: ::std::os::raw::c_uint = 1;
-pub const __USE_POSIX_IMPLICITLY: ::std::os::raw::c_uint = 1;
-pub const _POSIX_SOURCE: ::std::os::raw::c_uint = 1;
-pub const _POSIX_C_SOURCE: ::std::os::raw::c_uint = 200809;
-pub const __USE_POSIX: ::std::os::raw::c_uint = 1;
-pub const __USE_POSIX2: ::std::os::raw::c_uint = 1;
-pub const __USE_POSIX199309: ::std::os::raw::c_uint = 1;
-pub const __USE_POSIX199506: ::std::os::raw::c_uint = 1;
-pub const __USE_XOPEN2K: ::std::os::raw::c_uint = 1;
-pub const __USE_XOPEN2K8: ::std::os::raw::c_uint = 1;
-pub const _ATFILE_SOURCE: ::std::os::raw::c_uint = 1;
-pub const __USE_MISC: ::std::os::raw::c_uint = 1;
-pub const __USE_ATFILE: ::std::os::raw::c_uint = 1;
-pub const __USE_FORTIFY_LEVEL: ::std::os::raw::c_uint = 0;
-pub const _STDC_PREDEF_H: ::std::os::raw::c_uint = 1;
-pub const __STDC_IEC_559__: ::std::os::raw::c_uint = 1;
-pub const __STDC_IEC_559_COMPLEX__: ::std::os::raw::c_uint = 1;
-pub const __STDC_ISO_10646__: ::std::os::raw::c_uint = 201505;
-pub const __STDC_NO_THREADS__: ::std::os::raw::c_uint = 1;
-pub const __GNU_LIBRARY__: ::std::os::raw::c_uint = 6;
-pub const __GLIBC__: ::std::os::raw::c_uint = 2;
-pub const __GLIBC_MINOR__: ::std::os::raw::c_uint = 23;
-pub const _SYS_CDEFS_H: ::std::os::raw::c_uint = 1;
-pub const __WORDSIZE: ::std::os::raw::c_uint = 64;
-pub const __WORDSIZE_TIME64_COMPAT32: ::std::os::raw::c_uint = 1;
-pub const __SYSCALL_WORDSIZE: ::std::os::raw::c_uint = 64;
-pub const _BITS_WCHAR_H: ::std::os::raw::c_uint = 1;
-pub const INT8_MIN: ::std::os::raw::c_int = -128;
-pub const INT16_MIN: ::std::os::raw::c_int = -32768;
-pub const INT32_MIN: ::std::os::raw::c_int = -2147483648;
-pub const INT8_MAX: ::std::os::raw::c_uint = 127;
-pub const INT16_MAX: ::std::os::raw::c_uint = 32767;
-pub const INT32_MAX: ::std::os::raw::c_uint = 2147483647;
-pub const UINT8_MAX: ::std::os::raw::c_uint = 255;
-pub const UINT16_MAX: ::std::os::raw::c_uint = 65535;
-pub const UINT32_MAX: ::std::os::raw::c_uint = 4294967295;
-pub const INT_LEAST8_MIN: ::std::os::raw::c_int = -128;
-pub const INT_LEAST16_MIN: ::std::os::raw::c_int = -32768;
-pub const INT_LEAST32_MIN: ::std::os::raw::c_int = -2147483648;
-pub const INT_LEAST8_MAX: ::std::os::raw::c_uint = 127;
-pub const INT_LEAST16_MAX: ::std::os::raw::c_uint = 32767;
-pub const INT_LEAST32_MAX: ::std::os::raw::c_uint = 2147483647;
-pub const UINT_LEAST8_MAX: ::std::os::raw::c_uint = 255;
-pub const UINT_LEAST16_MAX: ::std::os::raw::c_uint = 65535;
-pub const UINT_LEAST32_MAX: ::std::os::raw::c_uint = 4294967295;
-pub const INT_FAST8_MIN: ::std::os::raw::c_int = -128;
-pub const INT_FAST16_MIN: ::std::os::raw::c_longlong = -9223372036854775808;
-pub const INT_FAST32_MIN: ::std::os::raw::c_longlong = -9223372036854775808;
-pub const INT_FAST8_MAX: ::std::os::raw::c_uint = 127;
-pub const INT_FAST16_MAX: ::std::os::raw::c_ulonglong = 9223372036854775807;
-pub const INT_FAST32_MAX: ::std::os::raw::c_ulonglong = 9223372036854775807;
-pub const UINT_FAST8_MAX: ::std::os::raw::c_uint = 255;
-pub const UINT_FAST16_MAX: ::std::os::raw::c_int = -1;
-pub const UINT_FAST32_MAX: ::std::os::raw::c_int = -1;
-pub const INTPTR_MIN: ::std::os::raw::c_longlong = -9223372036854775808;
-pub const INTPTR_MAX: ::std::os::raw::c_ulonglong = 9223372036854775807;
-pub const UINTPTR_MAX: ::std::os::raw::c_int = -1;
-pub const PTRDIFF_MIN: ::std::os::raw::c_longlong = -9223372036854775808;
-pub const PTRDIFF_MAX: ::std::os::raw::c_ulonglong = 9223372036854775807;
-pub const SIG_ATOMIC_MIN: ::std::os::raw::c_int = -2147483648;
-pub const SIG_ATOMIC_MAX: ::std::os::raw::c_uint = 2147483647;
-pub const SIZE_MAX: ::std::os::raw::c_int = -1;
-pub const WINT_MIN: ::std::os::raw::c_uint = 0;
-pub const WINT_MAX: ::std::os::raw::c_uint = 4294967295;
-pub const VRING_DESC_F_NEXT: ::std::os::raw::c_uint = 1;
-pub const VRING_DESC_F_WRITE: ::std::os::raw::c_uint = 2;
-pub const VRING_DESC_F_INDIRECT: ::std::os::raw::c_uint = 4;
-pub const VRING_USED_F_NO_NOTIFY: ::std::os::raw::c_uint = 1;
-pub const VRING_AVAIL_F_NO_INTERRUPT: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_RING_F_INDIRECT_DESC: ::std::os::raw::c_uint = 28;
-pub const VIRTIO_RING_F_EVENT_IDX: ::std::os::raw::c_uint = 29;
-pub const VRING_AVAIL_ALIGN_SIZE: ::std::os::raw::c_uint = 2;
-pub const VRING_USED_ALIGN_SIZE: ::std::os::raw::c_uint = 4;
-pub const VRING_DESC_ALIGN_SIZE: ::std::os::raw::c_uint = 16;
-pub const VHOST_VRING_F_LOG: ::std::os::raw::c_uint = 0;
-pub const VHOST_PAGE_SIZE: ::std::os::raw::c_uint = 4096;
-pub const VHOST_VIRTIO: ::std::os::raw::c_uint = 175;
-pub const VHOST_VRING_LITTLE_ENDIAN: ::std::os::raw::c_uint = 0;
-pub const VHOST_VRING_BIG_ENDIAN: ::std::os::raw::c_uint = 1;
-pub const VHOST_F_LOG_ALL: ::std::os::raw::c_uint = 26;
-pub const VHOST_NET_F_VIRTIO_NET_HDR: ::std::os::raw::c_uint = 27;
-pub const VHOST_SCSI_ABI_VERSION: ::std::os::raw::c_uint = 1;
-pub type __s8 = ::std::os::raw::c_schar;
-pub type __u8 = ::std::os::raw::c_uchar;
-pub type __s16 = ::std::os::raw::c_short;
-pub type __u16 = ::std::os::raw::c_ushort;
-pub type __s32 = ::std::os::raw::c_int;
-pub type __u32 = ::std::os::raw::c_uint;
-pub type __s64 = ::std::os::raw::c_longlong;
-pub type __u64 = ::std::os::raw::c_ulonglong;
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct __kernel_fd_set {
- pub fds_bits: [::std::os::raw::c_ulong; 16usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_fd_set() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_fd_set>(),
- 128usize,
- concat!("Size of: ", stringify!(__kernel_fd_set))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_fd_set>(),
- 8usize,
- concat!("Alignment of ", stringify!(__kernel_fd_set))
- );
- assert_eq!(
- unsafe { &(*(0 as *const __kernel_fd_set)).fds_bits as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(__kernel_fd_set),
- "::",
- stringify!(fds_bits)
- )
- );
-}
-impl Clone for __kernel_fd_set {
- fn clone(&self) -> Self {
- *self
- }
-}
-pub type __kernel_sighandler_t =
- ::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
-pub type __kernel_key_t = ::std::os::raw::c_int;
-pub type __kernel_mqd_t = ::std::os::raw::c_int;
-pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
-pub type __kernel_long_t = ::std::os::raw::c_long;
-pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
-pub type __kernel_ino_t = __kernel_ulong_t;
-pub type __kernel_mode_t = ::std::os::raw::c_uint;
-pub type __kernel_pid_t = ::std::os::raw::c_int;
-pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
-pub type __kernel_uid_t = ::std::os::raw::c_uint;
-pub type __kernel_gid_t = ::std::os::raw::c_uint;
-pub type __kernel_suseconds_t = __kernel_long_t;
-pub type __kernel_daddr_t = ::std::os::raw::c_int;
-pub type __kernel_uid32_t = ::std::os::raw::c_uint;
-pub type __kernel_gid32_t = ::std::os::raw::c_uint;
-pub type __kernel_size_t = __kernel_ulong_t;
-pub type __kernel_ssize_t = __kernel_long_t;
-pub type __kernel_ptrdiff_t = __kernel_long_t;
+pub const VIRTIO_CONFIG_S_ACKNOWLEDGE: u32 = 1;
+pub const VIRTIO_CONFIG_S_DRIVER: u32 = 2;
+pub const VIRTIO_CONFIG_S_DRIVER_OK: u32 = 4;
+pub const VIRTIO_CONFIG_S_FEATURES_OK: u32 = 8;
+pub const VIRTIO_CONFIG_S_NEEDS_RESET: u32 = 64;
+pub const VIRTIO_CONFIG_S_FAILED: u32 = 128;
+pub const VIRTIO_TRANSPORT_F_START: u32 = 28;
+pub const VIRTIO_TRANSPORT_F_END: u32 = 38;
+pub const VIRTIO_F_NOTIFY_ON_EMPTY: u32 = 24;
+pub const VIRTIO_F_ANY_LAYOUT: u32 = 27;
+pub const VIRTIO_F_VERSION_1: u32 = 32;
+pub const VIRTIO_F_ACCESS_PLATFORM: u32 = 33;
+pub const VIRTIO_F_IOMMU_PLATFORM: u32 = 33;
+pub const VIRTIO_F_RING_PACKED: u32 = 34;
+pub const VIRTIO_F_ORDER_PLATFORM: u32 = 36;
+pub const VIRTIO_F_SR_IOV: u32 = 37;
+pub const VIRTIO_RING_F_INDIRECT_DESC: u32 = 28;
+pub const VIRTIO_RING_F_EVENT_IDX: u32 = 29;
+pub const VHOST_VRING_F_LOG: u32 = 0;
+pub const VHOST_ACCESS_RO: u32 = 1;
+pub const VHOST_ACCESS_WO: u32 = 2;
+pub const VHOST_ACCESS_RW: u32 = 3;
+pub const VHOST_IOTLB_MISS: u32 = 1;
+pub const VHOST_IOTLB_UPDATE: u32 = 2;
+pub const VHOST_IOTLB_INVALIDATE: u32 = 3;
+pub const VHOST_IOTLB_ACCESS_FAIL: u32 = 4;
+pub const VHOST_IOTLB_BATCH_BEGIN: u32 = 5;
+pub const VHOST_IOTLB_BATCH_END: u32 = 6;
+pub const VHOST_IOTLB_MSG: u32 = 1;
+pub const VHOST_IOTLB_MSG_V2: u32 = 2;
+pub const VHOST_PAGE_SIZE: u32 = 4096;
+pub const VHOST_SCSI_ABI_VERSION: u32 = 1;
+pub const VHOST_F_LOG_ALL: u32 = 26;
+pub const VHOST_NET_F_VIRTIO_NET_HDR: u32 = 27;
+pub const VHOST_FILE_UNBIND: i32 = -1;
+pub const VHOST_VIRTIO: u32 = 175;
+pub const VHOST_VRING_LITTLE_ENDIAN: u32 = 0;
+pub const VHOST_VRING_BIG_ENDIAN: u32 = 1;
+pub const VHOST_BACKEND_F_IOTLB_MSG_V2: u32 = 1;
+pub const VHOST_BACKEND_F_IOTLB_BATCH: u32 = 2;
#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct __kernel_fsid_t {
- pub val: [::std::os::raw::c_int; 2usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_fsid_t() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_fsid_t>(),
- 8usize,
- concat!("Size of: ", stringify!(__kernel_fsid_t))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_fsid_t>(),
- 4usize,
- concat!("Alignment of ", stringify!(__kernel_fsid_t))
- );
- assert_eq!(
- unsafe { &(*(0 as *const __kernel_fsid_t)).val as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(__kernel_fsid_t),
- "::",
- stringify!(val)
- )
- );
-}
-impl Clone for __kernel_fsid_t {
- fn clone(&self) -> Self {
- *self
- }
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vhost_vring_state {
+ pub index: ::std::os::raw::c_uint,
+ pub num: ::std::os::raw::c_uint,
}
-pub type __kernel_off_t = __kernel_long_t;
-pub type __kernel_loff_t = ::std::os::raw::c_longlong;
-pub type __kernel_time_t = __kernel_long_t;
-pub type __kernel_clock_t = __kernel_long_t;
-pub type __kernel_timer_t = ::std::os::raw::c_int;
-pub type __kernel_clockid_t = ::std::os::raw::c_int;
-pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
-pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
-pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
-pub type __le16 = __u16;
-pub type __be16 = __u16;
-pub type __le32 = __u32;
-pub type __be32 = __u32;
-pub type __le64 = __u64;
-pub type __be64 = __u64;
-pub type __sum16 = __u16;
-pub type __wsum = __u32;
-pub type int_least8_t = ::std::os::raw::c_schar;
-pub type int_least16_t = ::std::os::raw::c_short;
-pub type int_least32_t = ::std::os::raw::c_int;
-pub type int_least64_t = ::std::os::raw::c_long;
-pub type uint_least8_t = ::std::os::raw::c_uchar;
-pub type uint_least16_t = ::std::os::raw::c_ushort;
-pub type uint_least32_t = ::std::os::raw::c_uint;
-pub type uint_least64_t = ::std::os::raw::c_ulong;
-pub type int_fast8_t = ::std::os::raw::c_schar;
-pub type int_fast16_t = ::std::os::raw::c_long;
-pub type int_fast32_t = ::std::os::raw::c_long;
-pub type int_fast64_t = ::std::os::raw::c_long;
-pub type uint_fast8_t = ::std::os::raw::c_uchar;
-pub type uint_fast16_t = ::std::os::raw::c_ulong;
-pub type uint_fast32_t = ::std::os::raw::c_ulong;
-pub type uint_fast64_t = ::std::os::raw::c_ulong;
-pub type intmax_t = ::std::os::raw::c_long;
-pub type uintmax_t = ::std::os::raw::c_ulong;
-pub type __virtio16 = __u16;
-pub type __virtio32 = __u32;
-pub type __virtio64 = __u64;
#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct vring_desc {
- pub addr: __virtio64,
- pub len: __virtio32,
- pub flags: __virtio16,
- pub next: __virtio16,
-}
-#[test]
-fn bindgen_test_layout_vring_desc() {
- assert_eq!(
- ::std::mem::size_of::<vring_desc>(),
- 16usize,
- concat!("Size of: ", stringify!(vring_desc))
- );
- assert_eq!(
- ::std::mem::align_of::<vring_desc>(),
- 8usize,
- concat!("Alignment of ", stringify!(vring_desc))
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring_desc)).addr as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring_desc),
- "::",
- stringify!(addr)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring_desc)).len as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring_desc),
- "::",
- stringify!(len)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring_desc)).flags as *const _ as usize },
- 12usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring_desc),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring_desc)).next as *const _ as usize },
- 14usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring_desc),
- "::",
- stringify!(next)
- )
- );
-}
-impl Clone for vring_desc {
- fn clone(&self) -> Self {
- *self
- }
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vhost_vring_file {
+ pub index: ::std::os::raw::c_uint,
+ pub fd: ::std::os::raw::c_int,
}
#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct vring_avail {
- pub flags: __virtio16,
- pub idx: __virtio16,
- pub ring: __IncompleteArrayField<__virtio16>,
-}
-#[test]
-fn bindgen_test_layout_vring_avail() {
- assert_eq!(
- ::std::mem::size_of::<vring_avail>(),
- 4usize,
- concat!("Size of: ", stringify!(vring_avail))
- );
- assert_eq!(
- ::std::mem::align_of::<vring_avail>(),
- 2usize,
- concat!("Alignment of ", stringify!(vring_avail))
- );
-}
-impl Clone for vring_avail {
- fn clone(&self) -> Self {
- *self
- }
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vhost_vring_addr {
+ pub index: ::std::os::raw::c_uint,
+ pub flags: ::std::os::raw::c_uint,
+ pub desc_user_addr: u64,
+ pub used_user_addr: u64,
+ pub avail_user_addr: u64,
+ pub log_guest_addr: u64,
}
#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct vring_used_elem {
- pub id: __virtio32,
- pub len: __virtio32,
-}
-#[test]
-fn bindgen_test_layout_vring_used_elem() {
- assert_eq!(
- ::std::mem::size_of::<vring_used_elem>(),
- 8usize,
- concat!("Size of: ", stringify!(vring_used_elem))
- );
- assert_eq!(
- ::std::mem::align_of::<vring_used_elem>(),
- 4usize,
- concat!("Alignment of ", stringify!(vring_used_elem))
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring_used_elem)).id as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring_used_elem),
- "::",
- stringify!(id)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring_used_elem)).len as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring_used_elem),
- "::",
- stringify!(len)
- )
- );
-}
-impl Clone for vring_used_elem {
- fn clone(&self) -> Self {
- *self
- }
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vhost_iotlb_msg {
+ pub iova: u64,
+ pub size: u64,
+ pub uaddr: u64,
+ pub perm: u8,
+ pub type_: u8,
}
#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct vring_used {
- pub flags: __virtio16,
- pub idx: __virtio16,
- pub ring: __IncompleteArrayField<vring_used_elem>,
- pub __bindgen_align: [u32; 0usize],
-}
-#[test]
-fn bindgen_test_layout_vring_used() {
- assert_eq!(
- ::std::mem::size_of::<vring_used>(),
- 4usize,
- concat!("Size of: ", stringify!(vring_used))
- );
- assert_eq!(
- ::std::mem::align_of::<vring_used>(),
- 4usize,
- concat!("Alignment of ", stringify!(vring_used))
- );
-}
-impl Clone for vring_used {
- fn clone(&self) -> Self {
- *self
- }
+#[derive(Copy, Clone)]
+pub struct vhost_msg {
+ pub type_: ::std::os::raw::c_int,
+ pub __bindgen_anon_1: vhost_msg__bindgen_ty_1,
}
#[repr(C)]
-#[derive(Debug, Copy)]
-pub struct vring {
- pub num: ::std::os::raw::c_uint,
- pub desc: *mut vring_desc,
- pub avail: *mut vring_avail,
- pub used: *mut vring_used,
-}
-#[test]
-fn bindgen_test_layout_vring() {
- assert_eq!(
- ::std::mem::size_of::<vring>(),
- 32usize,
- concat!("Size of: ", stringify!(vring))
- );
- assert_eq!(
- ::std::mem::align_of::<vring>(),
- 8usize,
- concat!("Alignment of ", stringify!(vring))
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring)).num as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring),
- "::",
- stringify!(num)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring)).desc as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring),
- "::",
- stringify!(desc)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring)).avail as *const _ as usize },
- 16usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring),
- "::",
- stringify!(avail)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring)).used as *const _ as usize },
- 24usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring),
- "::",
- stringify!(used)
- )
- );
+#[derive(Copy, Clone)]
+pub union vhost_msg__bindgen_ty_1 {
+ pub iotlb: vhost_iotlb_msg,
+ pub padding: [u8; 64usize],
}
-impl Clone for vring {
- fn clone(&self) -> Self {
- *self
+impl Default for vhost_msg__bindgen_ty_1 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-impl Default for vring {
+impl Default for vhost_msg {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct vhost_vring_state {
- pub index: ::std::os::raw::c_uint,
- pub num: ::std::os::raw::c_uint,
-}
-#[test]
-fn bindgen_test_layout_vhost_vring_state() {
- assert_eq!(
- ::std::mem::size_of::<vhost_vring_state>(),
- 8usize,
- concat!("Size of: ", stringify!(vhost_vring_state))
- );
- assert_eq!(
- ::std::mem::align_of::<vhost_vring_state>(),
- 4usize,
- concat!("Alignment of ", stringify!(vhost_vring_state))
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_vring_state)).index as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_vring_state),
- "::",
- stringify!(index)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_vring_state)).num as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_vring_state),
- "::",
- stringify!(num)
- )
- );
-}
-impl Clone for vhost_vring_state {
- fn clone(&self) -> Self {
- *self
- }
+#[derive(Copy, Clone)]
+pub struct vhost_msg_v2 {
+ pub type_: u32,
+ pub reserved: u32,
+ pub __bindgen_anon_1: vhost_msg_v2__bindgen_ty_1,
}
#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct vhost_vring_file {
- pub index: ::std::os::raw::c_uint,
- pub event: ::std::os::raw::c_int,
-}
-#[test]
-fn bindgen_test_layout_vhost_vring_file() {
- assert_eq!(
- ::std::mem::size_of::<vhost_vring_file>(),
- 8usize,
- concat!("Size of: ", stringify!(vhost_vring_file))
- );
- assert_eq!(
- ::std::mem::align_of::<vhost_vring_file>(),
- 4usize,
- concat!("Alignment of ", stringify!(vhost_vring_file))
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_vring_file)).index as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_vring_file),
- "::",
- stringify!(index)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_vring_file)).event as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_vring_file),
- "::",
- stringify!(fd)
- )
- );
+#[derive(Copy, Clone)]
+pub union vhost_msg_v2__bindgen_ty_1 {
+ pub iotlb: vhost_iotlb_msg,
+ pub padding: [u8; 64usize],
}
-impl Clone for vhost_vring_file {
- fn clone(&self) -> Self {
- *self
+impl Default for vhost_msg_v2__bindgen_ty_1 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[repr(C)]
-#[derive(Debug, Default, Copy)]
-pub struct vhost_vring_addr {
- pub index: ::std::os::raw::c_uint,
- pub flags: ::std::os::raw::c_uint,
- pub desc_user_addr: __u64,
- pub used_user_addr: __u64,
- pub avail_user_addr: __u64,
- pub log_guest_addr: __u64,
-}
-#[test]
-fn bindgen_test_layout_vhost_vring_addr() {
- assert_eq!(
- ::std::mem::size_of::<vhost_vring_addr>(),
- 40usize,
- concat!("Size of: ", stringify!(vhost_vring_addr))
- );
- assert_eq!(
- ::std::mem::align_of::<vhost_vring_addr>(),
- 8usize,
- concat!("Alignment of ", stringify!(vhost_vring_addr))
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_vring_addr)).index as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_vring_addr),
- "::",
- stringify!(index)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_vring_addr)).flags as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_vring_addr),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_vring_addr)).desc_user_addr as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_vring_addr),
- "::",
- stringify!(desc_user_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_vring_addr)).used_user_addr as *const _ as usize },
- 16usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_vring_addr),
- "::",
- stringify!(used_user_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_vring_addr)).avail_user_addr as *const _ as usize },
- 24usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_vring_addr),
- "::",
- stringify!(avail_user_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_vring_addr)).log_guest_addr as *const _ as usize },
- 32usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_vring_addr),
- "::",
- stringify!(log_guest_addr)
- )
- );
-}
-impl Clone for vhost_vring_addr {
- fn clone(&self) -> Self {
- *self
+impl Default for vhost_msg_v2 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
-#[derive(Debug, Default, Copy)]
+#[derive(Debug, Default, Copy, Clone)]
pub struct vhost_memory_region {
- pub guest_phys_addr: __u64,
- pub memory_size: __u64,
- pub userspace_addr: __u64,
- pub flags_padding: __u64,
-}
-#[test]
-fn bindgen_test_layout_vhost_memory_region() {
- assert_eq!(
- ::std::mem::size_of::<vhost_memory_region>(),
- 32usize,
- concat!("Size of: ", stringify!(vhost_memory_region))
- );
- assert_eq!(
- ::std::mem::align_of::<vhost_memory_region>(),
- 8usize,
- concat!("Alignment of ", stringify!(vhost_memory_region))
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_memory_region)).guest_phys_addr as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_memory_region),
- "::",
- stringify!(guest_phys_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_memory_region)).memory_size as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_memory_region),
- "::",
- stringify!(memory_size)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_memory_region)).userspace_addr as *const _ as usize },
- 16usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_memory_region),
- "::",
- stringify!(userspace_addr)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_memory_region)).flags_padding as *const _ as usize },
- 24usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_memory_region),
- "::",
- stringify!(flags_padding)
- )
- );
-}
-impl Clone for vhost_memory_region {
- fn clone(&self) -> Self {
- *self
- }
+ pub guest_phys_addr: u64,
+ pub memory_size: u64,
+ pub userspace_addr: u64,
+ pub flags_padding: u64,
}
#[repr(C)]
-#[derive(Debug, Default, Copy)]
+#[derive(Debug, Default)]
pub struct vhost_memory {
- pub nregions: __u32,
- pub padding: __u32,
+ pub nregions: u32,
+ pub padding: u32,
pub regions: __IncompleteArrayField<vhost_memory_region>,
- pub __force_alignment: [u64; 0],
-}
-#[test]
-fn bindgen_test_layout_vhost_memory() {
- assert_eq!(
- ::std::mem::size_of::<vhost_memory>(),
- 8usize,
- concat!("Size of: ", stringify!(vhost_memory))
- );
- assert_eq!(
- ::std::mem::align_of::<vhost_memory>(),
- 8usize,
- concat!("Alignment of ", stringify!(vhost_memory))
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_memory)).nregions as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_memory),
- "::",
- stringify!(nregions)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_memory)).padding as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_memory),
- "::",
- stringify!(padding)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_memory)).regions as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_memory),
- "::",
- stringify!(regions)
- )
- );
-}
-impl Clone for vhost_memory {
- fn clone(&self) -> Self {
- *self
- }
}
#[repr(C)]
+#[derive(Debug, Copy, Clone)]
pub struct vhost_scsi_target {
pub abi_version: ::std::os::raw::c_int,
pub vhost_wwpn: [::std::os::raw::c_char; 224usize],
pub vhost_tpgt: ::std::os::raw::c_ushort,
pub reserved: ::std::os::raw::c_ushort,
}
-#[test]
-fn bindgen_test_layout_vhost_scsi_target() {
- assert_eq!(
- ::std::mem::size_of::<vhost_scsi_target>(),
- 232usize,
- concat!("Size of: ", stringify!(vhost_scsi_target))
- );
- assert_eq!(
- ::std::mem::align_of::<vhost_scsi_target>(),
- 4usize,
- concat!("Alignment of ", stringify!(vhost_scsi_target))
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_scsi_target)).abi_version as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_scsi_target),
- "::",
- stringify!(abi_version)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_scsi_target)).vhost_wwpn as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_scsi_target),
- "::",
- stringify!(vhost_wwpn)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_scsi_target)).vhost_tpgt as *const _ as usize },
- 228usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_scsi_target),
- "::",
- stringify!(vhost_tpgt)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vhost_scsi_target)).reserved as *const _ as usize },
- 230usize,
- concat!(
- "Alignment of field: ",
- stringify!(vhost_scsi_target),
- "::",
- stringify!(reserved)
- )
- );
-}
impl Default for vhost_scsi_target {
fn default() -> Self {
- unsafe { ::std::mem::zeroed() }
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct vhost_vdpa_config {
+ pub off: u32,
+ pub len: u32,
+ pub buf: __IncompleteArrayField<u8>,
+}
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vhost_vdpa_iova_range {
+ pub first: u64,
+ pub last: u64,
+}
diff --git a/virtio_sys/src/virtio_net.rs b/virtio_sys/src/virtio_net.rs
index 5cb370ede..0c879cd5d 100644
--- a/virtio_sys/src/virtio_net.rs
+++ b/virtio_sys/src/virtio_net.rs
@@ -1,24 +1,25 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+/* automatically generated by tools/bindgen-all-the-things */
-/* automatically generated by rust-bindgen */
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
#[repr(C)]
#[derive(Default)]
-pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>);
+pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
impl<T> __IncompleteArrayField<T> {
#[inline]
- pub fn new() -> Self {
- __IncompleteArrayField(::std::marker::PhantomData)
+ pub const fn new() -> Self {
+ __IncompleteArrayField(::std::marker::PhantomData, [])
}
#[inline]
- pub unsafe fn as_ptr(&self) -> *const T {
- ::std::mem::transmute(self)
+ pub fn as_ptr(&self) -> *const T {
+ self as *const _ as *const T
}
#[inline]
- pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
- ::std::mem::transmute(self)
+ pub fn as_mut_ptr(&mut self) -> *mut T {
+ self as *mut _ as *mut T
}
#[inline]
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
@@ -30,750 +31,223 @@ impl<T> __IncompleteArrayField<T> {
}
}
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
- fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
fmt.write_str("__IncompleteArrayField")
}
}
-impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
- #[inline]
- fn clone(&self) -> Self {
- Self::new()
- }
+pub const VIRTIO_NET_F_CSUM: u32 = 0;
+pub const VIRTIO_NET_F_GUEST_CSUM: u32 = 1;
+pub const VIRTIO_NET_F_CTRL_GUEST_OFFLOADS: u32 = 2;
+pub const VIRTIO_NET_F_MTU: u32 = 3;
+pub const VIRTIO_NET_F_MAC: u32 = 5;
+pub const VIRTIO_NET_F_GUEST_TSO4: u32 = 7;
+pub const VIRTIO_NET_F_GUEST_TSO6: u32 = 8;
+pub const VIRTIO_NET_F_GUEST_ECN: u32 = 9;
+pub const VIRTIO_NET_F_GUEST_UFO: u32 = 10;
+pub const VIRTIO_NET_F_HOST_TSO4: u32 = 11;
+pub const VIRTIO_NET_F_HOST_TSO6: u32 = 12;
+pub const VIRTIO_NET_F_HOST_ECN: u32 = 13;
+pub const VIRTIO_NET_F_HOST_UFO: u32 = 14;
+pub const VIRTIO_NET_F_MRG_RXBUF: u32 = 15;
+pub const VIRTIO_NET_F_STATUS: u32 = 16;
+pub const VIRTIO_NET_F_CTRL_VQ: u32 = 17;
+pub const VIRTIO_NET_F_CTRL_RX: u32 = 18;
+pub const VIRTIO_NET_F_CTRL_VLAN: u32 = 19;
+pub const VIRTIO_NET_F_CTRL_RX_EXTRA: u32 = 20;
+pub const VIRTIO_NET_F_GUEST_ANNOUNCE: u32 = 21;
+pub const VIRTIO_NET_F_MQ: u32 = 22;
+pub const VIRTIO_NET_F_CTRL_MAC_ADDR: u32 = 23;
+pub const VIRTIO_NET_F_HASH_REPORT: u32 = 57;
+pub const VIRTIO_NET_F_RSS: u32 = 60;
+pub const VIRTIO_NET_F_RSC_EXT: u32 = 61;
+pub const VIRTIO_NET_F_STANDBY: u32 = 62;
+pub const VIRTIO_NET_F_SPEED_DUPLEX: u32 = 63;
+pub const VIRTIO_NET_F_GSO: u32 = 6;
+pub const VIRTIO_NET_S_LINK_UP: u32 = 1;
+pub const VIRTIO_NET_S_ANNOUNCE: u32 = 2;
+pub const VIRTIO_NET_RSS_HASH_TYPE_IPv4: u32 = 1;
+pub const VIRTIO_NET_RSS_HASH_TYPE_TCPv4: u32 = 2;
+pub const VIRTIO_NET_RSS_HASH_TYPE_UDPv4: u32 = 4;
+pub const VIRTIO_NET_RSS_HASH_TYPE_IPv6: u32 = 8;
+pub const VIRTIO_NET_RSS_HASH_TYPE_TCPv6: u32 = 16;
+pub const VIRTIO_NET_RSS_HASH_TYPE_UDPv6: u32 = 32;
+pub const VIRTIO_NET_RSS_HASH_TYPE_IP_EX: u32 = 64;
+pub const VIRTIO_NET_RSS_HASH_TYPE_TCP_EX: u32 = 128;
+pub const VIRTIO_NET_RSS_HASH_TYPE_UDP_EX: u32 = 256;
+pub const VIRTIO_NET_HDR_F_NEEDS_CSUM: u32 = 1;
+pub const VIRTIO_NET_HDR_F_DATA_VALID: u32 = 2;
+pub const VIRTIO_NET_HDR_F_RSC_INFO: u32 = 4;
+pub const VIRTIO_NET_HDR_GSO_NONE: u32 = 0;
+pub const VIRTIO_NET_HDR_GSO_TCPV4: u32 = 1;
+pub const VIRTIO_NET_HDR_GSO_UDP: u32 = 3;
+pub const VIRTIO_NET_HDR_GSO_TCPV6: u32 = 4;
+pub const VIRTIO_NET_HDR_GSO_ECN: u32 = 128;
+pub const VIRTIO_NET_HASH_REPORT_NONE: u32 = 0;
+pub const VIRTIO_NET_HASH_REPORT_IPv4: u32 = 1;
+pub const VIRTIO_NET_HASH_REPORT_TCPv4: u32 = 2;
+pub const VIRTIO_NET_HASH_REPORT_UDPv4: u32 = 3;
+pub const VIRTIO_NET_HASH_REPORT_IPv6: u32 = 4;
+pub const VIRTIO_NET_HASH_REPORT_TCPv6: u32 = 5;
+pub const VIRTIO_NET_HASH_REPORT_UDPv6: u32 = 6;
+pub const VIRTIO_NET_HASH_REPORT_IPv6_EX: u32 = 7;
+pub const VIRTIO_NET_HASH_REPORT_TCPv6_EX: u32 = 8;
+pub const VIRTIO_NET_HASH_REPORT_UDPv6_EX: u32 = 9;
+pub const VIRTIO_NET_OK: u32 = 0;
+pub const VIRTIO_NET_ERR: u32 = 1;
+pub const VIRTIO_NET_CTRL_RX: u32 = 0;
+pub const VIRTIO_NET_CTRL_RX_PROMISC: u32 = 0;
+pub const VIRTIO_NET_CTRL_RX_ALLMULTI: u32 = 1;
+pub const VIRTIO_NET_CTRL_RX_ALLUNI: u32 = 2;
+pub const VIRTIO_NET_CTRL_RX_NOMULTI: u32 = 3;
+pub const VIRTIO_NET_CTRL_RX_NOUNI: u32 = 4;
+pub const VIRTIO_NET_CTRL_RX_NOBCAST: u32 = 5;
+pub const VIRTIO_NET_CTRL_MAC: u32 = 1;
+pub const VIRTIO_NET_CTRL_MAC_TABLE_SET: u32 = 0;
+pub const VIRTIO_NET_CTRL_MAC_ADDR_SET: u32 = 1;
+pub const VIRTIO_NET_CTRL_VLAN: u32 = 2;
+pub const VIRTIO_NET_CTRL_VLAN_ADD: u32 = 0;
+pub const VIRTIO_NET_CTRL_VLAN_DEL: u32 = 1;
+pub const VIRTIO_NET_CTRL_ANNOUNCE: u32 = 3;
+pub const VIRTIO_NET_CTRL_ANNOUNCE_ACK: u32 = 0;
+pub const VIRTIO_NET_CTRL_MQ: u32 = 4;
+pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET: u32 = 0;
+pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN: u32 = 1;
+pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX: u32 = 32768;
+pub const VIRTIO_NET_CTRL_MQ_RSS_CONFIG: u32 = 1;
+pub const VIRTIO_NET_CTRL_MQ_HASH_CONFIG: u32 = 2;
+pub const VIRTIO_NET_CTRL_GUEST_OFFLOADS: u32 = 5;
+pub const VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET: u32 = 0;
+pub type __le16 = u16;
+pub type __le32 = u32;
+pub type __virtio16 = u16;
+pub type __virtio32 = u32;
+#[repr(C, packed)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_net_config {
+ pub mac: [u8; 6usize],
+ pub status: __virtio16,
+ pub max_virtqueue_pairs: __virtio16,
+ pub mtu: __virtio16,
+ pub speed: __le32,
+ pub duplex: u8,
+ pub rss_max_key_size: u8,
+ pub rss_max_indirection_table_length: __le16,
+ pub supported_hash_types: __le32,
}
-impl<T> ::std::marker::Copy for __IncompleteArrayField<T> {}
-pub const __BITS_PER_LONG: ::std::os::raw::c_uint = 64;
-pub const __FD_SETSIZE: ::std::os::raw::c_uint = 1024;
-pub const VIRTIO_ID_NET: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_ID_BLOCK: ::std::os::raw::c_uint = 2;
-pub const VIRTIO_ID_CONSOLE: ::std::os::raw::c_uint = 3;
-pub const VIRTIO_ID_RNG: ::std::os::raw::c_uint = 4;
-pub const VIRTIO_ID_BALLOON: ::std::os::raw::c_uint = 5;
-pub const VIRTIO_ID_RPMSG: ::std::os::raw::c_uint = 7;
-pub const VIRTIO_ID_SCSI: ::std::os::raw::c_uint = 8;
-pub const VIRTIO_ID_9P: ::std::os::raw::c_uint = 9;
-pub const VIRTIO_ID_RPROC_SERIAL: ::std::os::raw::c_uint = 11;
-pub const VIRTIO_ID_CAIF: ::std::os::raw::c_uint = 12;
-pub const VIRTIO_ID_GPU: ::std::os::raw::c_uint = 16;
-pub const VIRTIO_ID_INPUT: ::std::os::raw::c_uint = 18;
-pub const VIRTIO_CONFIG_S_ACKNOWLEDGE: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_CONFIG_S_DRIVER: ::std::os::raw::c_uint = 2;
-pub const VIRTIO_CONFIG_S_DRIVER_OK: ::std::os::raw::c_uint = 4;
-pub const VIRTIO_CONFIG_S_FEATURES_OK: ::std::os::raw::c_uint = 8;
-pub const VIRTIO_CONFIG_S_FAILED: ::std::os::raw::c_uint = 128;
-pub const VIRTIO_TRANSPORT_F_START: ::std::os::raw::c_uint = 28;
-pub const VIRTIO_TRANSPORT_F_END: ::std::os::raw::c_uint = 33;
-pub const VIRTIO_F_NOTIFY_ON_EMPTY: ::std::os::raw::c_uint = 24;
-pub const VIRTIO_F_ANY_LAYOUT: ::std::os::raw::c_uint = 27;
-pub const VIRTIO_F_VERSION_1: ::std::os::raw::c_uint = 32;
-pub const ETH_ALEN: ::std::os::raw::c_uint = 6;
-pub const ETH_HLEN: ::std::os::raw::c_uint = 14;
-pub const ETH_ZLEN: ::std::os::raw::c_uint = 60;
-pub const ETH_DATA_LEN: ::std::os::raw::c_uint = 1500;
-pub const ETH_FRAME_LEN: ::std::os::raw::c_uint = 1514;
-pub const ETH_FCS_LEN: ::std::os::raw::c_uint = 4;
-pub const ETH_P_LOOP: ::std::os::raw::c_uint = 96;
-pub const ETH_P_PUP: ::std::os::raw::c_uint = 512;
-pub const ETH_P_PUPAT: ::std::os::raw::c_uint = 513;
-pub const ETH_P_TSN: ::std::os::raw::c_uint = 8944;
-pub const ETH_P_IP: ::std::os::raw::c_uint = 2048;
-pub const ETH_P_X25: ::std::os::raw::c_uint = 2053;
-pub const ETH_P_ARP: ::std::os::raw::c_uint = 2054;
-pub const ETH_P_BPQ: ::std::os::raw::c_uint = 2303;
-pub const ETH_P_IEEEPUP: ::std::os::raw::c_uint = 2560;
-pub const ETH_P_IEEEPUPAT: ::std::os::raw::c_uint = 2561;
-pub const ETH_P_BATMAN: ::std::os::raw::c_uint = 17157;
-pub const ETH_P_DEC: ::std::os::raw::c_uint = 24576;
-pub const ETH_P_DNA_DL: ::std::os::raw::c_uint = 24577;
-pub const ETH_P_DNA_RC: ::std::os::raw::c_uint = 24578;
-pub const ETH_P_DNA_RT: ::std::os::raw::c_uint = 24579;
-pub const ETH_P_LAT: ::std::os::raw::c_uint = 24580;
-pub const ETH_P_DIAG: ::std::os::raw::c_uint = 24581;
-pub const ETH_P_CUST: ::std::os::raw::c_uint = 24582;
-pub const ETH_P_SCA: ::std::os::raw::c_uint = 24583;
-pub const ETH_P_TEB: ::std::os::raw::c_uint = 25944;
-pub const ETH_P_RARP: ::std::os::raw::c_uint = 32821;
-pub const ETH_P_ATALK: ::std::os::raw::c_uint = 32923;
-pub const ETH_P_AARP: ::std::os::raw::c_uint = 33011;
-pub const ETH_P_8021Q: ::std::os::raw::c_uint = 33024;
-pub const ETH_P_IPX: ::std::os::raw::c_uint = 33079;
-pub const ETH_P_IPV6: ::std::os::raw::c_uint = 34525;
-pub const ETH_P_PAUSE: ::std::os::raw::c_uint = 34824;
-pub const ETH_P_SLOW: ::std::os::raw::c_uint = 34825;
-pub const ETH_P_WCCP: ::std::os::raw::c_uint = 34878;
-pub const ETH_P_MPLS_UC: ::std::os::raw::c_uint = 34887;
-pub const ETH_P_MPLS_MC: ::std::os::raw::c_uint = 34888;
-pub const ETH_P_ATMMPOA: ::std::os::raw::c_uint = 34892;
-pub const ETH_P_PPP_DISC: ::std::os::raw::c_uint = 34915;
-pub const ETH_P_PPP_SES: ::std::os::raw::c_uint = 34916;
-pub const ETH_P_LINK_CTL: ::std::os::raw::c_uint = 34924;
-pub const ETH_P_ATMFATE: ::std::os::raw::c_uint = 34948;
-pub const ETH_P_PAE: ::std::os::raw::c_uint = 34958;
-pub const ETH_P_AOE: ::std::os::raw::c_uint = 34978;
-pub const ETH_P_8021AD: ::std::os::raw::c_uint = 34984;
-pub const ETH_P_802_EX1: ::std::os::raw::c_uint = 34997;
-pub const ETH_P_TIPC: ::std::os::raw::c_uint = 35018;
-pub const ETH_P_8021AH: ::std::os::raw::c_uint = 35047;
-pub const ETH_P_MVRP: ::std::os::raw::c_uint = 35061;
-pub const ETH_P_1588: ::std::os::raw::c_uint = 35063;
-pub const ETH_P_PRP: ::std::os::raw::c_uint = 35067;
-pub const ETH_P_FCOE: ::std::os::raw::c_uint = 35078;
-pub const ETH_P_TDLS: ::std::os::raw::c_uint = 35085;
-pub const ETH_P_FIP: ::std::os::raw::c_uint = 35092;
-pub const ETH_P_80221: ::std::os::raw::c_uint = 35095;
-pub const ETH_P_LOOPBACK: ::std::os::raw::c_uint = 36864;
-pub const ETH_P_QINQ1: ::std::os::raw::c_uint = 37120;
-pub const ETH_P_QINQ2: ::std::os::raw::c_uint = 37376;
-pub const ETH_P_QINQ3: ::std::os::raw::c_uint = 37632;
-pub const ETH_P_EDSA: ::std::os::raw::c_uint = 56026;
-pub const ETH_P_AF_IUCV: ::std::os::raw::c_uint = 64507;
-pub const ETH_P_802_3_MIN: ::std::os::raw::c_uint = 1536;
-pub const ETH_P_802_3: ::std::os::raw::c_uint = 1;
-pub const ETH_P_AX25: ::std::os::raw::c_uint = 2;
-pub const ETH_P_ALL: ::std::os::raw::c_uint = 3;
-pub const ETH_P_802_2: ::std::os::raw::c_uint = 4;
-pub const ETH_P_SNAP: ::std::os::raw::c_uint = 5;
-pub const ETH_P_DDCMP: ::std::os::raw::c_uint = 6;
-pub const ETH_P_WAN_PPP: ::std::os::raw::c_uint = 7;
-pub const ETH_P_PPP_MP: ::std::os::raw::c_uint = 8;
-pub const ETH_P_LOCALTALK: ::std::os::raw::c_uint = 9;
-pub const ETH_P_CAN: ::std::os::raw::c_uint = 12;
-pub const ETH_P_CANFD: ::std::os::raw::c_uint = 13;
-pub const ETH_P_PPPTALK: ::std::os::raw::c_uint = 16;
-pub const ETH_P_TR_802_2: ::std::os::raw::c_uint = 17;
-pub const ETH_P_MOBITEX: ::std::os::raw::c_uint = 21;
-pub const ETH_P_CONTROL: ::std::os::raw::c_uint = 22;
-pub const ETH_P_IRDA: ::std::os::raw::c_uint = 23;
-pub const ETH_P_ECONET: ::std::os::raw::c_uint = 24;
-pub const ETH_P_HDLC: ::std::os::raw::c_uint = 25;
-pub const ETH_P_ARCNET: ::std::os::raw::c_uint = 26;
-pub const ETH_P_DSA: ::std::os::raw::c_uint = 27;
-pub const ETH_P_TRAILER: ::std::os::raw::c_uint = 28;
-pub const ETH_P_PHONET: ::std::os::raw::c_uint = 245;
-pub const ETH_P_IEEE802154: ::std::os::raw::c_uint = 246;
-pub const ETH_P_CAIF: ::std::os::raw::c_uint = 247;
-pub const ETH_P_XDSA: ::std::os::raw::c_uint = 248;
-pub const VIRTIO_NET_F_CSUM: ::std::os::raw::c_uint = 0;
-pub const VIRTIO_NET_F_GUEST_CSUM: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_NET_F_CTRL_GUEST_OFFLOADS: ::std::os::raw::c_uint = 2;
-pub const VIRTIO_NET_F_MTU: ::std::os::raw::c_uint = 3;
-pub const VIRTIO_NET_F_MAC: ::std::os::raw::c_uint = 5;
-pub const VIRTIO_NET_F_GUEST_TSO4: ::std::os::raw::c_uint = 7;
-pub const VIRTIO_NET_F_GUEST_TSO6: ::std::os::raw::c_uint = 8;
-pub const VIRTIO_NET_F_GUEST_ECN: ::std::os::raw::c_uint = 9;
-pub const VIRTIO_NET_F_GUEST_UFO: ::std::os::raw::c_uint = 10;
-pub const VIRTIO_NET_F_HOST_TSO4: ::std::os::raw::c_uint = 11;
-pub const VIRTIO_NET_F_HOST_TSO6: ::std::os::raw::c_uint = 12;
-pub const VIRTIO_NET_F_HOST_ECN: ::std::os::raw::c_uint = 13;
-pub const VIRTIO_NET_F_HOST_UFO: ::std::os::raw::c_uint = 14;
-pub const VIRTIO_NET_F_MRG_RXBUF: ::std::os::raw::c_uint = 15;
-pub const VIRTIO_NET_F_STATUS: ::std::os::raw::c_uint = 16;
-pub const VIRTIO_NET_F_CTRL_VQ: ::std::os::raw::c_uint = 17;
-pub const VIRTIO_NET_F_CTRL_RX: ::std::os::raw::c_uint = 18;
-pub const VIRTIO_NET_F_CTRL_VLAN: ::std::os::raw::c_uint = 19;
-pub const VIRTIO_NET_F_CTRL_RX_EXTRA: ::std::os::raw::c_uint = 20;
-pub const VIRTIO_NET_F_GUEST_ANNOUNCE: ::std::os::raw::c_uint = 21;
-pub const VIRTIO_NET_F_MQ: ::std::os::raw::c_uint = 22;
-pub const VIRTIO_NET_F_CTRL_MAC_ADDR: ::std::os::raw::c_uint = 23;
-pub const VIRTIO_NET_F_GSO: ::std::os::raw::c_uint = 6;
-pub const VIRTIO_NET_S_LINK_UP: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_NET_S_ANNOUNCE: ::std::os::raw::c_uint = 2;
-pub const VIRTIO_NET_HDR_F_NEEDS_CSUM: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_NET_HDR_F_DATA_VALID: ::std::os::raw::c_uint = 2;
-pub const VIRTIO_NET_HDR_GSO_NONE: ::std::os::raw::c_uint = 0;
-pub const VIRTIO_NET_HDR_GSO_TCPV4: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_NET_HDR_GSO_UDP: ::std::os::raw::c_uint = 3;
-pub const VIRTIO_NET_HDR_GSO_TCPV6: ::std::os::raw::c_uint = 4;
-pub const VIRTIO_NET_HDR_GSO_ECN: ::std::os::raw::c_uint = 128;
-pub const VIRTIO_NET_OK: ::std::os::raw::c_uint = 0;
-pub const VIRTIO_NET_ERR: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_NET_CTRL_RX: ::std::os::raw::c_uint = 0;
-pub const VIRTIO_NET_CTRL_RX_PROMISC: ::std::os::raw::c_uint = 0;
-pub const VIRTIO_NET_CTRL_RX_ALLMULTI: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_NET_CTRL_RX_ALLUNI: ::std::os::raw::c_uint = 2;
-pub const VIRTIO_NET_CTRL_RX_NOMULTI: ::std::os::raw::c_uint = 3;
-pub const VIRTIO_NET_CTRL_RX_NOUNI: ::std::os::raw::c_uint = 4;
-pub const VIRTIO_NET_CTRL_RX_NOBCAST: ::std::os::raw::c_uint = 5;
-pub const VIRTIO_NET_CTRL_MAC: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_NET_CTRL_MAC_TABLE_SET: ::std::os::raw::c_uint = 0;
-pub const VIRTIO_NET_CTRL_MAC_ADDR_SET: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_NET_CTRL_VLAN: ::std::os::raw::c_uint = 2;
-pub const VIRTIO_NET_CTRL_VLAN_ADD: ::std::os::raw::c_uint = 0;
-pub const VIRTIO_NET_CTRL_VLAN_DEL: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_NET_CTRL_ANNOUNCE: ::std::os::raw::c_uint = 3;
-pub const VIRTIO_NET_CTRL_ANNOUNCE_ACK: ::std::os::raw::c_uint = 0;
-pub const VIRTIO_NET_CTRL_MQ: ::std::os::raw::c_uint = 4;
-pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET: ::std::os::raw::c_uint = 0;
-pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX: ::std::os::raw::c_uint = 32768;
-pub const VIRTIO_NET_CTRL_GUEST_OFFLOADS: ::std::os::raw::c_uint = 5;
-pub const VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET: ::std::os::raw::c_uint = 0;
-pub type __s8 = ::std::os::raw::c_schar;
-pub type __u8 = ::std::os::raw::c_uchar;
-pub type __s16 = ::std::os::raw::c_short;
-pub type __u16 = ::std::os::raw::c_ushort;
-pub type __s32 = ::std::os::raw::c_int;
-pub type __u32 = ::std::os::raw::c_uint;
-pub type __s64 = ::std::os::raw::c_longlong;
-pub type __u64 = ::std::os::raw::c_ulonglong;
#[repr(C)]
-#[derive(Debug, Copy)]
-pub struct __kernel_fd_set {
- pub fds_bits: [::std::os::raw::c_ulong; 16usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_fd_set() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_fd_set>(),
- 128usize,
- concat!("Size of: ", stringify!(__kernel_fd_set))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_fd_set>(),
- 8usize,
- concat!("Alignment of ", stringify!(__kernel_fd_set))
- );
- assert_eq!(
- unsafe { &(*(0 as *const __kernel_fd_set)).fds_bits as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(__kernel_fd_set),
- "::",
- stringify!(fds_bits)
- )
- );
-}
-impl Clone for __kernel_fd_set {
- fn clone(&self) -> Self {
- *self
- }
+#[derive(Copy, Clone)]
+pub struct virtio_net_hdr_v1 {
+ pub flags: u8,
+ pub gso_type: u8,
+ pub hdr_len: __virtio16,
+ pub gso_size: __virtio16,
+ pub __bindgen_anon_1: virtio_net_hdr_v1__bindgen_ty_1,
+ pub num_buffers: __virtio16,
}
-pub type __kernel_sighandler_t =
- ::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
-pub type __kernel_key_t = ::std::os::raw::c_int;
-pub type __kernel_mqd_t = ::std::os::raw::c_int;
-pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
-pub type __kernel_long_t = ::std::os::raw::c_long;
-pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
-pub type __kernel_ino_t = __kernel_ulong_t;
-pub type __kernel_mode_t = ::std::os::raw::c_uint;
-pub type __kernel_pid_t = ::std::os::raw::c_int;
-pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
-pub type __kernel_uid_t = ::std::os::raw::c_uint;
-pub type __kernel_gid_t = ::std::os::raw::c_uint;
-pub type __kernel_suseconds_t = __kernel_long_t;
-pub type __kernel_daddr_t = ::std::os::raw::c_int;
-pub type __kernel_uid32_t = ::std::os::raw::c_uint;
-pub type __kernel_gid32_t = ::std::os::raw::c_uint;
-pub type __kernel_size_t = __kernel_ulong_t;
-pub type __kernel_ssize_t = __kernel_long_t;
-pub type __kernel_ptrdiff_t = __kernel_long_t;
#[repr(C)]
-#[derive(Debug, Copy)]
-pub struct __kernel_fsid_t {
- pub val: [::std::os::raw::c_int; 2usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_fsid_t() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_fsid_t>(),
- 8usize,
- concat!("Size of: ", stringify!(__kernel_fsid_t))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_fsid_t>(),
- 4usize,
- concat!("Alignment of ", stringify!(__kernel_fsid_t))
- );
- assert_eq!(
- unsafe { &(*(0 as *const __kernel_fsid_t)).val as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(__kernel_fsid_t),
- "::",
- stringify!(val)
- )
- );
+#[derive(Copy, Clone)]
+pub union virtio_net_hdr_v1__bindgen_ty_1 {
+ pub __bindgen_anon_1: virtio_net_hdr_v1__bindgen_ty_1__bindgen_ty_1,
+ pub csum: virtio_net_hdr_v1__bindgen_ty_1__bindgen_ty_2,
+ pub rsc: virtio_net_hdr_v1__bindgen_ty_1__bindgen_ty_3,
}
-impl Clone for __kernel_fsid_t {
- fn clone(&self) -> Self {
- *self
- }
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_net_hdr_v1__bindgen_ty_1__bindgen_ty_1 {
+ pub csum_start: __virtio16,
+ pub csum_offset: __virtio16,
}
-pub type __kernel_off_t = __kernel_long_t;
-pub type __kernel_loff_t = ::std::os::raw::c_longlong;
-pub type __kernel_time_t = __kernel_long_t;
-pub type __kernel_clock_t = __kernel_long_t;
-pub type __kernel_timer_t = ::std::os::raw::c_int;
-pub type __kernel_clockid_t = ::std::os::raw::c_int;
-pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
-pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
-pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
-pub type __le16 = __u16;
-pub type __be16 = __u16;
-pub type __le32 = __u32;
-pub type __be32 = __u32;
-pub type __le64 = __u64;
-pub type __be64 = __u64;
-pub type __sum16 = __u16;
-pub type __wsum = __u32;
-pub type __virtio16 = __u16;
-pub type __virtio32 = __u32;
-pub type __virtio64 = __u64;
-#[repr(C, packed)]
-#[derive(Debug, Copy)]
-pub struct ethhdr {
- pub h_dest: [::std::os::raw::c_uchar; 6usize],
- pub h_source: [::std::os::raw::c_uchar; 6usize],
- pub h_proto: __be16,
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_net_hdr_v1__bindgen_ty_1__bindgen_ty_2 {
+ pub start: __virtio16,
+ pub offset: __virtio16,
}
-#[test]
-fn bindgen_test_layout_ethhdr() {
- assert_eq!(
- ::std::mem::size_of::<ethhdr>(),
- 14usize,
- concat!("Size of: ", stringify!(ethhdr))
- );
- assert_eq!(
- ::std::mem::align_of::<ethhdr>(),
- 1usize,
- concat!("Alignment of ", stringify!(ethhdr))
- );
- assert_eq!(
- unsafe { &(*(0 as *const ethhdr)).h_dest as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(ethhdr),
- "::",
- stringify!(h_dest)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const ethhdr)).h_source as *const _ as usize },
- 6usize,
- concat!(
- "Alignment of field: ",
- stringify!(ethhdr),
- "::",
- stringify!(h_source)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const ethhdr)).h_proto as *const _ as usize },
- 12usize,
- concat!(
- "Alignment of field: ",
- stringify!(ethhdr),
- "::",
- stringify!(h_proto)
- )
- );
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct virtio_net_hdr_v1__bindgen_ty_1__bindgen_ty_3 {
+ pub segments: __le16,
+ pub dup_acks: __le16,
}
-impl Clone for ethhdr {
- fn clone(&self) -> Self {
- *self
+impl Default for virtio_net_hdr_v1__bindgen_ty_1 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
-#[repr(C, packed)]
-#[derive(Debug, Copy)]
-pub struct virtio_net_config {
- pub mac: [__u8; 6usize],
- pub status: __u16,
- pub max_virtqueue_pairs: __u16,
- pub mtu: __u16,
-}
-#[test]
-fn bindgen_test_layout_virtio_net_config() {
- assert_eq!(
- ::std::mem::size_of::<virtio_net_config>(),
- 12usize,
- concat!("Size of: ", stringify!(virtio_net_config))
- );
- assert_eq!(
- ::std::mem::align_of::<virtio_net_config>(),
- 1usize,
- concat!("Alignment of ", stringify!(virtio_net_config))
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_config)).mac as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_config),
- "::",
- stringify!(mac)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_config)).status as *const _ as usize },
- 6usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_config),
- "::",
- stringify!(status)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_config)).max_virtqueue_pairs as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_config),
- "::",
- stringify!(max_virtqueue_pairs)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_config)).mtu as *const _ as usize },
- 10usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_config),
- "::",
- stringify!(mtu)
- )
- );
-}
-impl Clone for virtio_net_config {
- fn clone(&self) -> Self {
- *self
+impl Default for virtio_net_hdr_v1 {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
-#[derive(Debug, Copy)]
-pub struct virtio_net_hdr_v1 {
- pub flags: __u8,
- pub gso_type: __u8,
- pub hdr_len: __virtio16,
- pub gso_size: __virtio16,
- pub csum_start: __virtio16,
- pub csum_offset: __virtio16,
- pub num_buffers: __virtio16,
-}
-#[test]
-fn bindgen_test_layout_virtio_net_hdr_v1() {
- assert_eq!(
- ::std::mem::size_of::<virtio_net_hdr_v1>(),
- 12usize,
- concat!("Size of: ", stringify!(virtio_net_hdr_v1))
- );
- assert_eq!(
- ::std::mem::align_of::<virtio_net_hdr_v1>(),
- 2usize,
- concat!("Alignment of ", stringify!(virtio_net_hdr_v1))
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr_v1)).flags as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr_v1),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr_v1)).gso_type as *const _ as usize },
- 1usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr_v1),
- "::",
- stringify!(gso_type)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr_v1)).hdr_len as *const _ as usize },
- 2usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr_v1),
- "::",
- stringify!(hdr_len)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr_v1)).gso_size as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr_v1),
- "::",
- stringify!(gso_size)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr_v1)).csum_start as *const _ as usize },
- 6usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr_v1),
- "::",
- stringify!(csum_start)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr_v1)).csum_offset as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr_v1),
- "::",
- stringify!(csum_offset)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr_v1)).num_buffers as *const _ as usize },
- 10usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr_v1),
- "::",
- stringify!(num_buffers)
- )
- );
-}
-impl Clone for virtio_net_hdr_v1 {
- fn clone(&self) -> Self {
- *self
+#[derive(Copy, Clone)]
+pub struct virtio_net_hdr_v1_hash {
+ pub hdr: virtio_net_hdr_v1,
+ pub hash_value: __le32,
+ pub hash_report: __le16,
+ pub padding: __le16,
+}
+impl Default for virtio_net_hdr_v1_hash {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
}
}
#[repr(C)]
-#[derive(Debug, Copy)]
+#[derive(Debug, Default, Copy, Clone)]
pub struct virtio_net_hdr {
- pub flags: __u8,
- pub gso_type: __u8,
+ pub flags: u8,
+ pub gso_type: u8,
pub hdr_len: __virtio16,
pub gso_size: __virtio16,
pub csum_start: __virtio16,
pub csum_offset: __virtio16,
}
-#[test]
-fn bindgen_test_layout_virtio_net_hdr() {
- assert_eq!(
- ::std::mem::size_of::<virtio_net_hdr>(),
- 10usize,
- concat!("Size of: ", stringify!(virtio_net_hdr))
- );
- assert_eq!(
- ::std::mem::align_of::<virtio_net_hdr>(),
- 2usize,
- concat!("Alignment of ", stringify!(virtio_net_hdr))
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr)).flags as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr)).gso_type as *const _ as usize },
- 1usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr),
- "::",
- stringify!(gso_type)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr)).hdr_len as *const _ as usize },
- 2usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr),
- "::",
- stringify!(hdr_len)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr)).gso_size as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr),
- "::",
- stringify!(gso_size)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr)).csum_start as *const _ as usize },
- 6usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr),
- "::",
- stringify!(csum_start)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr)).csum_offset as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr),
- "::",
- stringify!(csum_offset)
- )
- );
-}
-impl Clone for virtio_net_hdr {
- fn clone(&self) -> Self {
- *self
- }
-}
#[repr(C)]
-#[derive(Debug, Copy)]
+#[derive(Debug, Default, Copy, Clone)]
pub struct virtio_net_hdr_mrg_rxbuf {
pub hdr: virtio_net_hdr,
pub num_buffers: __virtio16,
}
-#[test]
-fn bindgen_test_layout_virtio_net_hdr_mrg_rxbuf() {
- assert_eq!(
- ::std::mem::size_of::<virtio_net_hdr_mrg_rxbuf>(),
- 12usize,
- concat!("Size of: ", stringify!(virtio_net_hdr_mrg_rxbuf))
- );
- assert_eq!(
- ::std::mem::align_of::<virtio_net_hdr_mrg_rxbuf>(),
- 2usize,
- concat!("Alignment of ", stringify!(virtio_net_hdr_mrg_rxbuf))
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr_mrg_rxbuf)).hdr as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr_mrg_rxbuf),
- "::",
- stringify!(hdr)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_hdr_mrg_rxbuf)).num_buffers as *const _ as usize },
- 10usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_hdr_mrg_rxbuf),
- "::",
- stringify!(num_buffers)
- )
- );
-}
-impl Clone for virtio_net_hdr_mrg_rxbuf {
- fn clone(&self) -> Self {
- *self
- }
-}
#[repr(C, packed)]
-#[derive(Debug, Copy)]
+#[derive(Debug, Default, Copy, Clone)]
pub struct virtio_net_ctrl_hdr {
- pub class: __u8,
- pub cmd: __u8,
-}
-#[test]
-fn bindgen_test_layout_virtio_net_ctrl_hdr() {
- assert_eq!(
- ::std::mem::size_of::<virtio_net_ctrl_hdr>(),
- 2usize,
- concat!("Size of: ", stringify!(virtio_net_ctrl_hdr))
- );
- assert_eq!(
- ::std::mem::align_of::<virtio_net_ctrl_hdr>(),
- 1usize,
- concat!("Alignment of ", stringify!(virtio_net_ctrl_hdr))
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_ctrl_hdr)).class as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_ctrl_hdr),
- "::",
- stringify!(class)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_ctrl_hdr)).cmd as *const _ as usize },
- 1usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_ctrl_hdr),
- "::",
- stringify!(cmd)
- )
- );
-}
-impl Clone for virtio_net_ctrl_hdr {
- fn clone(&self) -> Self {
- *self
- }
-}
-pub type virtio_net_ctrl_ack = __u8;
-#[repr(C, packed)]
-#[derive(Debug, Copy)]
-pub struct virtio_net_ctrl_mac {
- pub entries: __virtio32,
- pub macs: __IncompleteArrayField<[__u8; 6usize]>,
-}
-#[test]
-fn bindgen_test_layout_virtio_net_ctrl_mac() {
- assert_eq!(
- ::std::mem::size_of::<virtio_net_ctrl_mac>(),
- 4usize,
- concat!("Size of: ", stringify!(virtio_net_ctrl_mac))
- );
- assert_eq!(
- ::std::mem::align_of::<virtio_net_ctrl_mac>(),
- 1usize,
- concat!("Alignment of ", stringify!(virtio_net_ctrl_mac))
- );
-}
-impl Clone for virtio_net_ctrl_mac {
- fn clone(&self) -> Self {
- *self
- }
+ pub class: u8,
+ pub cmd: u8,
}
+pub type virtio_net_ctrl_ack = u8;
#[repr(C)]
-#[derive(Debug, Copy)]
+#[derive(Debug, Default, Copy, Clone)]
pub struct virtio_net_ctrl_mq {
pub virtqueue_pairs: __virtio16,
}
-#[test]
-fn bindgen_test_layout_virtio_net_ctrl_mq() {
- assert_eq!(
- ::std::mem::size_of::<virtio_net_ctrl_mq>(),
- 2usize,
- concat!("Size of: ", stringify!(virtio_net_ctrl_mq))
- );
- assert_eq!(
- ::std::mem::align_of::<virtio_net_ctrl_mq>(),
- 2usize,
- concat!("Alignment of ", stringify!(virtio_net_ctrl_mq))
- );
- assert_eq!(
- unsafe { &(*(0 as *const virtio_net_ctrl_mq)).virtqueue_pairs as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(virtio_net_ctrl_mq),
- "::",
- stringify!(virtqueue_pairs)
- )
- );
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct virtio_net_rss_config {
+ pub hash_types: __le32,
+ pub indirection_table_mask: __le16,
+ pub unclassified_queue: __le16,
+ pub indirection_table: [__le16; 1usize],
+ pub max_tx_vq: __le16,
+ pub hash_key_length: u8,
+ pub hash_key_data: __IncompleteArrayField<u8>,
}
-impl Clone for virtio_net_ctrl_mq {
- fn clone(&self) -> Self {
- *self
- }
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct virtio_net_hash_config {
+ pub hash_types: __le32,
+ pub reserved: [__le16; 4usize],
+ pub hash_key_length: u8,
+ pub hash_key_data: __IncompleteArrayField<u8>,
}
diff --git a/virtio_sys/src/virtio_ring.rs b/virtio_sys/src/virtio_ring.rs
index c8628517f..e90b33e7e 100644
--- a/virtio_sys/src/virtio_ring.rs
+++ b/virtio_sys/src/virtio_ring.rs
@@ -1,24 +1,25 @@
-// Copyright 2019 The Chromium OS Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
+/* automatically generated by tools/bindgen-all-the-things */
-/* automatically generated by rust-bindgen */
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
#[repr(C)]
#[derive(Default)]
-pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>);
+pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>, [T; 0]);
impl<T> __IncompleteArrayField<T> {
#[inline]
- pub fn new() -> Self {
- __IncompleteArrayField(::std::marker::PhantomData)
+ pub const fn new() -> Self {
+ __IncompleteArrayField(::std::marker::PhantomData, [])
}
#[inline]
- pub unsafe fn as_ptr(&self) -> *const T {
- ::std::mem::transmute(self)
+ pub fn as_ptr(&self) -> *const T {
+ self as *const _ as *const T
}
#[inline]
- pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
- ::std::mem::transmute(self)
+ pub fn as_mut_ptr(&mut self) -> *mut T {
+ self as *mut _ as *mut T
}
#[inline]
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
@@ -30,456 +31,92 @@ impl<T> __IncompleteArrayField<T> {
}
}
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
- fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
fmt.write_str("__IncompleteArrayField")
}
}
-impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
- #[inline]
- fn clone(&self) -> Self {
- Self::new()
- }
-}
-impl<T> ::std::marker::Copy for __IncompleteArrayField<T> {}
-pub const _STDINT_H: ::std::os::raw::c_uint = 1;
-pub const _FEATURES_H: ::std::os::raw::c_uint = 1;
-pub const _DEFAULT_SOURCE: ::std::os::raw::c_uint = 1;
-pub const __USE_ISOC11: ::std::os::raw::c_uint = 1;
-pub const __USE_ISOC99: ::std::os::raw::c_uint = 1;
-pub const __USE_ISOC95: ::std::os::raw::c_uint = 1;
-pub const __USE_POSIX_IMPLICITLY: ::std::os::raw::c_uint = 1;
-pub const _POSIX_SOURCE: ::std::os::raw::c_uint = 1;
-pub const _POSIX_C_SOURCE: ::std::os::raw::c_uint = 200809;
-pub const __USE_POSIX: ::std::os::raw::c_uint = 1;
-pub const __USE_POSIX2: ::std::os::raw::c_uint = 1;
-pub const __USE_POSIX199309: ::std::os::raw::c_uint = 1;
-pub const __USE_POSIX199506: ::std::os::raw::c_uint = 1;
-pub const __USE_XOPEN2K: ::std::os::raw::c_uint = 1;
-pub const __USE_XOPEN2K8: ::std::os::raw::c_uint = 1;
-pub const _ATFILE_SOURCE: ::std::os::raw::c_uint = 1;
-pub const __USE_MISC: ::std::os::raw::c_uint = 1;
-pub const __USE_ATFILE: ::std::os::raw::c_uint = 1;
-pub const __USE_FORTIFY_LEVEL: ::std::os::raw::c_uint = 0;
-pub const _STDC_PREDEF_H: ::std::os::raw::c_uint = 1;
-pub const __STDC_IEC_559__: ::std::os::raw::c_uint = 1;
-pub const __STDC_IEC_559_COMPLEX__: ::std::os::raw::c_uint = 1;
-pub const __STDC_ISO_10646__: ::std::os::raw::c_uint = 201505;
-pub const __STDC_NO_THREADS__: ::std::os::raw::c_uint = 1;
-pub const __GNU_LIBRARY__: ::std::os::raw::c_uint = 6;
-pub const __GLIBC__: ::std::os::raw::c_uint = 2;
-pub const __GLIBC_MINOR__: ::std::os::raw::c_uint = 23;
-pub const _SYS_CDEFS_H: ::std::os::raw::c_uint = 1;
-pub const __WORDSIZE: ::std::os::raw::c_uint = 64;
-pub const __WORDSIZE_TIME64_COMPAT32: ::std::os::raw::c_uint = 1;
-pub const __SYSCALL_WORDSIZE: ::std::os::raw::c_uint = 64;
-pub const _BITS_WCHAR_H: ::std::os::raw::c_uint = 1;
-pub const INT8_MIN: ::std::os::raw::c_int = -128;
-pub const INT16_MIN: ::std::os::raw::c_int = -32768;
-pub const INT32_MIN: ::std::os::raw::c_int = -2147483648;
-pub const INT8_MAX: ::std::os::raw::c_uint = 127;
-pub const INT16_MAX: ::std::os::raw::c_uint = 32767;
-pub const INT32_MAX: ::std::os::raw::c_uint = 2147483647;
-pub const UINT8_MAX: ::std::os::raw::c_uint = 255;
-pub const UINT16_MAX: ::std::os::raw::c_uint = 65535;
-pub const UINT32_MAX: ::std::os::raw::c_uint = 4294967295;
-pub const INT_LEAST8_MIN: ::std::os::raw::c_int = -128;
-pub const INT_LEAST16_MIN: ::std::os::raw::c_int = -32768;
-pub const INT_LEAST32_MIN: ::std::os::raw::c_int = -2147483648;
-pub const INT_LEAST8_MAX: ::std::os::raw::c_uint = 127;
-pub const INT_LEAST16_MAX: ::std::os::raw::c_uint = 32767;
-pub const INT_LEAST32_MAX: ::std::os::raw::c_uint = 2147483647;
-pub const UINT_LEAST8_MAX: ::std::os::raw::c_uint = 255;
-pub const UINT_LEAST16_MAX: ::std::os::raw::c_uint = 65535;
-pub const UINT_LEAST32_MAX: ::std::os::raw::c_uint = 4294967295;
-pub const INT_FAST8_MIN: ::std::os::raw::c_int = -128;
-pub const INT_FAST16_MIN: ::std::os::raw::c_longlong = -9223372036854775808;
-pub const INT_FAST32_MIN: ::std::os::raw::c_longlong = -9223372036854775808;
-pub const INT_FAST8_MAX: ::std::os::raw::c_uint = 127;
-pub const INT_FAST16_MAX: ::std::os::raw::c_ulonglong = 9223372036854775807;
-pub const INT_FAST32_MAX: ::std::os::raw::c_ulonglong = 9223372036854775807;
-pub const UINT_FAST8_MAX: ::std::os::raw::c_uint = 255;
-pub const UINT_FAST16_MAX: ::std::os::raw::c_int = -1;
-pub const UINT_FAST32_MAX: ::std::os::raw::c_int = -1;
-pub const INTPTR_MIN: ::std::os::raw::c_longlong = -9223372036854775808;
-pub const INTPTR_MAX: ::std::os::raw::c_ulonglong = 9223372036854775807;
-pub const UINTPTR_MAX: ::std::os::raw::c_int = -1;
-pub const PTRDIFF_MIN: ::std::os::raw::c_longlong = -9223372036854775808;
-pub const PTRDIFF_MAX: ::std::os::raw::c_ulonglong = 9223372036854775807;
-pub const SIG_ATOMIC_MIN: ::std::os::raw::c_int = -2147483648;
-pub const SIG_ATOMIC_MAX: ::std::os::raw::c_uint = 2147483647;
-pub const SIZE_MAX: ::std::os::raw::c_int = -1;
-pub const WINT_MIN: ::std::os::raw::c_uint = 0;
-pub const WINT_MAX: ::std::os::raw::c_uint = 4294967295;
-pub const __BITS_PER_LONG: ::std::os::raw::c_uint = 64;
-pub const __FD_SETSIZE: ::std::os::raw::c_uint = 1024;
-pub const VRING_DESC_F_NEXT: ::std::os::raw::c_uint = 1;
-pub const VRING_DESC_F_WRITE: ::std::os::raw::c_uint = 2;
-pub const VRING_DESC_F_INDIRECT: ::std::os::raw::c_uint = 4;
-pub const VRING_USED_F_NO_NOTIFY: ::std::os::raw::c_uint = 1;
-pub const VRING_AVAIL_F_NO_INTERRUPT: ::std::os::raw::c_uint = 1;
-pub const VIRTIO_RING_F_INDIRECT_DESC: ::std::os::raw::c_uint = 28;
-pub const VIRTIO_RING_F_EVENT_IDX: ::std::os::raw::c_uint = 29;
-pub const VRING_AVAIL_ALIGN_SIZE: ::std::os::raw::c_uint = 2;
-pub const VRING_USED_ALIGN_SIZE: ::std::os::raw::c_uint = 4;
-pub const VRING_DESC_ALIGN_SIZE: ::std::os::raw::c_uint = 16;
-pub type int_least8_t = ::std::os::raw::c_schar;
-pub type int_least16_t = ::std::os::raw::c_short;
-pub type int_least32_t = ::std::os::raw::c_int;
-pub type int_least64_t = ::std::os::raw::c_long;
-pub type uint_least8_t = ::std::os::raw::c_uchar;
-pub type uint_least16_t = ::std::os::raw::c_ushort;
-pub type uint_least32_t = ::std::os::raw::c_uint;
-pub type uint_least64_t = ::std::os::raw::c_ulong;
-pub type int_fast8_t = ::std::os::raw::c_schar;
-pub type int_fast16_t = ::std::os::raw::c_long;
-pub type int_fast32_t = ::std::os::raw::c_long;
-pub type int_fast64_t = ::std::os::raw::c_long;
-pub type uint_fast8_t = ::std::os::raw::c_uchar;
-pub type uint_fast16_t = ::std::os::raw::c_ulong;
-pub type uint_fast32_t = ::std::os::raw::c_ulong;
-pub type uint_fast64_t = ::std::os::raw::c_ulong;
-pub type intmax_t = ::std::os::raw::c_long;
-pub type uintmax_t = ::std::os::raw::c_ulong;
-pub type __s8 = ::std::os::raw::c_schar;
-pub type __u8 = ::std::os::raw::c_uchar;
-pub type __s16 = ::std::os::raw::c_short;
-pub type __u16 = ::std::os::raw::c_ushort;
-pub type __s32 = ::std::os::raw::c_int;
-pub type __u32 = ::std::os::raw::c_uint;
-pub type __s64 = ::std::os::raw::c_longlong;
-pub type __u64 = ::std::os::raw::c_ulonglong;
-#[repr(C)]
-#[derive(Debug, Copy)]
-pub struct __kernel_fd_set {
- pub fds_bits: [::std::os::raw::c_ulong; 16usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_fd_set() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_fd_set>(),
- 128usize,
- concat!("Size of: ", stringify!(__kernel_fd_set))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_fd_set>(),
- 8usize,
- concat!("Alignment of ", stringify!(__kernel_fd_set))
- );
- assert_eq!(
- unsafe { &(*(0 as *const __kernel_fd_set)).fds_bits as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(__kernel_fd_set),
- "::",
- stringify!(fds_bits)
- )
- );
-}
-impl Clone for __kernel_fd_set {
- fn clone(&self) -> Self {
- *self
- }
-}
-pub type __kernel_sighandler_t =
- ::std::option::Option<unsafe extern "C" fn(arg1: ::std::os::raw::c_int)>;
-pub type __kernel_key_t = ::std::os::raw::c_int;
-pub type __kernel_mqd_t = ::std::os::raw::c_int;
-pub type __kernel_old_uid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_gid_t = ::std::os::raw::c_ushort;
-pub type __kernel_old_dev_t = ::std::os::raw::c_ulong;
-pub type __kernel_long_t = ::std::os::raw::c_long;
-pub type __kernel_ulong_t = ::std::os::raw::c_ulong;
-pub type __kernel_ino_t = __kernel_ulong_t;
-pub type __kernel_mode_t = ::std::os::raw::c_uint;
-pub type __kernel_pid_t = ::std::os::raw::c_int;
-pub type __kernel_ipc_pid_t = ::std::os::raw::c_int;
-pub type __kernel_uid_t = ::std::os::raw::c_uint;
-pub type __kernel_gid_t = ::std::os::raw::c_uint;
-pub type __kernel_suseconds_t = __kernel_long_t;
-pub type __kernel_daddr_t = ::std::os::raw::c_int;
-pub type __kernel_uid32_t = ::std::os::raw::c_uint;
-pub type __kernel_gid32_t = ::std::os::raw::c_uint;
-pub type __kernel_size_t = __kernel_ulong_t;
-pub type __kernel_ssize_t = __kernel_long_t;
-pub type __kernel_ptrdiff_t = __kernel_long_t;
+pub const VRING_DESC_F_NEXT: u32 = 1;
+pub const VRING_DESC_F_WRITE: u32 = 2;
+pub const VRING_DESC_F_INDIRECT: u32 = 4;
+pub const VRING_PACKED_DESC_F_AVAIL: u32 = 7;
+pub const VRING_PACKED_DESC_F_USED: u32 = 15;
+pub const VRING_USED_F_NO_NOTIFY: u32 = 1;
+pub const VRING_AVAIL_F_NO_INTERRUPT: u32 = 1;
+pub const VRING_PACKED_EVENT_FLAG_ENABLE: u32 = 0;
+pub const VRING_PACKED_EVENT_FLAG_DISABLE: u32 = 1;
+pub const VRING_PACKED_EVENT_FLAG_DESC: u32 = 2;
+pub const VRING_PACKED_EVENT_F_WRAP_CTR: u32 = 15;
+pub const VIRTIO_RING_F_INDIRECT_DESC: u32 = 28;
+pub const VIRTIO_RING_F_EVENT_IDX: u32 = 29;
+pub const VRING_AVAIL_ALIGN_SIZE: u32 = 2;
+pub const VRING_USED_ALIGN_SIZE: u32 = 4;
+pub const VRING_DESC_ALIGN_SIZE: u32 = 16;
+pub type __le16 = u16;
+pub type __le32 = u32;
+pub type __le64 = u64;
+pub type __virtio16 = u16;
+pub type __virtio32 = u32;
+pub type __virtio64 = u64;
#[repr(C)]
-#[derive(Debug, Copy)]
-pub struct __kernel_fsid_t {
- pub val: [::std::os::raw::c_int; 2usize],
-}
-#[test]
-fn bindgen_test_layout___kernel_fsid_t() {
- assert_eq!(
- ::std::mem::size_of::<__kernel_fsid_t>(),
- 8usize,
- concat!("Size of: ", stringify!(__kernel_fsid_t))
- );
- assert_eq!(
- ::std::mem::align_of::<__kernel_fsid_t>(),
- 4usize,
- concat!("Alignment of ", stringify!(__kernel_fsid_t))
- );
- assert_eq!(
- unsafe { &(*(0 as *const __kernel_fsid_t)).val as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(__kernel_fsid_t),
- "::",
- stringify!(val)
- )
- );
-}
-impl Clone for __kernel_fsid_t {
- fn clone(&self) -> Self {
- *self
- }
-}
-pub type __kernel_off_t = __kernel_long_t;
-pub type __kernel_loff_t = ::std::os::raw::c_longlong;
-pub type __kernel_time_t = __kernel_long_t;
-pub type __kernel_clock_t = __kernel_long_t;
-pub type __kernel_timer_t = ::std::os::raw::c_int;
-pub type __kernel_clockid_t = ::std::os::raw::c_int;
-pub type __kernel_caddr_t = *mut ::std::os::raw::c_char;
-pub type __kernel_uid16_t = ::std::os::raw::c_ushort;
-pub type __kernel_gid16_t = ::std::os::raw::c_ushort;
-pub type __le16 = __u16;
-pub type __be16 = __u16;
-pub type __le32 = __u32;
-pub type __be32 = __u32;
-pub type __le64 = __u64;
-pub type __be64 = __u64;
-pub type __sum16 = __u16;
-pub type __wsum = __u32;
-pub type __virtio16 = __u16;
-pub type __virtio32 = __u32;
-pub type __virtio64 = __u64;
-#[repr(C)]
-#[derive(Debug, Copy)]
+#[derive(Debug, Default, Copy, Clone)]
pub struct vring_desc {
pub addr: __virtio64,
pub len: __virtio32,
pub flags: __virtio16,
pub next: __virtio16,
}
-#[test]
-fn bindgen_test_layout_vring_desc() {
- assert_eq!(
- ::std::mem::size_of::<vring_desc>(),
- 16usize,
- concat!("Size of: ", stringify!(vring_desc))
- );
- assert_eq!(
- ::std::mem::align_of::<vring_desc>(),
- 8usize,
- concat!("Alignment of ", stringify!(vring_desc))
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring_desc)).addr as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring_desc),
- "::",
- stringify!(addr)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring_desc)).len as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring_desc),
- "::",
- stringify!(len)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring_desc)).flags as *const _ as usize },
- 12usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring_desc),
- "::",
- stringify!(flags)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring_desc)).next as *const _ as usize },
- 14usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring_desc),
- "::",
- stringify!(next)
- )
- );
-}
-impl Clone for vring_desc {
- fn clone(&self) -> Self {
- *self
- }
-}
#[repr(C)]
-#[derive(Debug, Copy)]
+#[derive(Debug, Default)]
pub struct vring_avail {
pub flags: __virtio16,
pub idx: __virtio16,
pub ring: __IncompleteArrayField<__virtio16>,
}
-#[test]
-fn bindgen_test_layout_vring_avail() {
- assert_eq!(
- ::std::mem::size_of::<vring_avail>(),
- 4usize,
- concat!("Size of: ", stringify!(vring_avail))
- );
- assert_eq!(
- ::std::mem::align_of::<vring_avail>(),
- 2usize,
- concat!("Alignment of ", stringify!(vring_avail))
- );
-}
-impl Clone for vring_avail {
- fn clone(&self) -> Self {
- *self
- }
-}
#[repr(C)]
-#[derive(Debug, Copy)]
+#[derive(Debug, Default, Copy, Clone)]
pub struct vring_used_elem {
pub id: __virtio32,
pub len: __virtio32,
}
-#[test]
-fn bindgen_test_layout_vring_used_elem() {
- assert_eq!(
- ::std::mem::size_of::<vring_used_elem>(),
- 8usize,
- concat!("Size of: ", stringify!(vring_used_elem))
- );
- assert_eq!(
- ::std::mem::align_of::<vring_used_elem>(),
- 4usize,
- concat!("Alignment of ", stringify!(vring_used_elem))
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring_used_elem)).id as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring_used_elem),
- "::",
- stringify!(id)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring_used_elem)).len as *const _ as usize },
- 4usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring_used_elem),
- "::",
- stringify!(len)
- )
- );
-}
-impl Clone for vring_used_elem {
- fn clone(&self) -> Self {
- *self
- }
-}
+pub type vring_used_elem_t = vring_used_elem;
#[repr(C)]
-#[derive(Debug, Copy)]
+#[derive(Debug, Default)]
pub struct vring_used {
pub flags: __virtio16,
pub idx: __virtio16,
- pub ring: __IncompleteArrayField<vring_used_elem>,
- pub __bindgen_align: [u32; 0usize],
-}
-#[test]
-fn bindgen_test_layout_vring_used() {
- assert_eq!(
- ::std::mem::size_of::<vring_used>(),
- 4usize,
- concat!("Size of: ", stringify!(vring_used))
- );
- assert_eq!(
- ::std::mem::align_of::<vring_used>(),
- 4usize,
- concat!("Alignment of ", stringify!(vring_used))
- );
-}
-impl Clone for vring_used {
- fn clone(&self) -> Self {
- *self
- }
+ pub ring: __IncompleteArrayField<vring_used_elem_t>,
}
+pub type vring_desc_t = vring_desc;
+pub type vring_avail_t = vring_avail;
+pub type vring_used_t = vring_used;
#[repr(C)]
-#[derive(Debug, Copy)]
+#[derive(Debug, Copy, Clone)]
pub struct vring {
pub num: ::std::os::raw::c_uint,
- pub desc: *mut vring_desc,
- pub avail: *mut vring_avail,
- pub used: *mut vring_used,
+ pub desc: *mut vring_desc_t,
+ pub avail: *mut vring_avail_t,
+ pub used: *mut vring_used_t,
+}
+impl Default for vring {
+ fn default() -> Self {
+ let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
+ unsafe {
+ ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
+ s.assume_init()
+ }
+ }
}
-#[test]
-fn bindgen_test_layout_vring() {
- assert_eq!(
- ::std::mem::size_of::<vring>(),
- 32usize,
- concat!("Size of: ", stringify!(vring))
- );
- assert_eq!(
- ::std::mem::align_of::<vring>(),
- 8usize,
- concat!("Alignment of ", stringify!(vring))
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring)).num as *const _ as usize },
- 0usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring),
- "::",
- stringify!(num)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring)).desc as *const _ as usize },
- 8usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring),
- "::",
- stringify!(desc)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring)).avail as *const _ as usize },
- 16usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring),
- "::",
- stringify!(avail)
- )
- );
- assert_eq!(
- unsafe { &(*(0 as *const vring)).used as *const _ as usize },
- 24usize,
- concat!(
- "Alignment of field: ",
- stringify!(vring),
- "::",
- stringify!(used)
- )
- );
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vring_packed_desc_event {
+ pub off_wrap: __le16,
+ pub flags: __le16,
}
-impl Clone for vring {
- fn clone(&self) -> Self {
- *self
- }
+#[repr(C)]
+#[derive(Debug, Default, Copy, Clone)]
+pub struct vring_packed_desc {
+ pub addr: __le64,
+ pub len: __le32,
+ pub id: __le16,
+ pub flags: __le16,
}
diff --git a/vm_control/Android.bp b/vm_control/Android.bp
index 623f43c2a..97e844c3e 100644
--- a/vm_control/Android.bp
+++ b/vm_control/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
-// NOTE: The --features=gdb should be applied only to the host (not the device) and there are inline changes to achieve this
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
package {
// See: http://go/android-license-faq
@@ -15,20 +15,12 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "vm_control",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
- target: {
- linux_glibc_x86_64: {
- features: [
- "gdb",
- "gdbstub",
- ],
- rustlibs: [
- "libgdbstub",
- ],
- },
- },
+ edition: "2021",
rustlibs: [
+ "libballoon_control",
"libbase_rust",
"libdata_model",
"libhypervisor",
@@ -36,103 +28,21 @@ rust_library {
"libresources",
"librutabaga_gfx",
"libserde",
+ "libserde_json",
"libsync_rust",
+ "libthiserror",
"libvm_memory",
],
-}
-
-rust_defaults {
- name: "vm_control_defaults",
- defaults: ["crosvm_defaults"],
- crate_name: "vm_control",
- srcs: ["src/lib.rs"],
- test_suites: ["general-tests"],
- auto_gen_config: true,
- edition: "2018",
+ proc_macros: ["libremain"],
target: {
- linux_glibc_x86_64: {
+ host_linux: {
features: [
"gdb",
- "gdbstub",
],
rustlibs: [
- "libgdbstub",
+ "libgdbstub_arch",
],
},
},
- rustlibs: [
- "libbase_rust",
- "libdata_model",
- "libhypervisor",
- "liblibc",
- "libresources",
- "librutabaga_gfx",
- "libserde",
- "libsync_rust",
- "libvm_memory",
- ],
-}
-rust_test_host {
- name: "vm_control_host_test_src_lib",
- defaults: ["vm_control_defaults"],
- test_options: {
- unit_test: true,
- },
}
-
-rust_test {
- name: "vm_control_device_test_src_lib",
- defaults: ["vm_control_defaults"],
-}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../bit_field/bit_field_derive/bit_field_derive.rs
-// ../bit_field/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../enumn/src/lib.rs
-// ../hypervisor/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../kvm/src/lib.rs
-// ../kvm_sys/src/lib.rs
-// ../resources/src/lib.rs
-// ../rutabaga_gfx/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// ../vm_memory/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// downcast-rs-1.2.0 "default,std"
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/vm_control/Cargo.toml b/vm_control/Cargo.toml
index 27b1066b1..5135d29c5 100644
--- a/vm_control/Cargo.toml
+++ b/vm_control/Cargo.toml
@@ -2,19 +2,23 @@
name = "vm_control"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[features]
-gdb = ["gdbstub"]
+gdb = ["gdbstub_arch"]
[dependencies]
+balloon_control = { path = "../common/balloon_control" }
base = { path = "../base" }
-data_model = { path = "../data_model" }
-gdbstub = { version = "0.4.0", optional = true }
+data_model = { path = "../common/data_model" }
+gdbstub_arch = { version = "0.1.0", optional = true }
hypervisor = { path = "../hypervisor" }
libc = "*"
+remain = "*"
resources = { path = "../resources" }
rutabaga_gfx = { path = "../rutabaga_gfx"}
serde = { version = "1", features = [ "derive" ] }
-sync = { path = "../sync" }
+serde_json = "*"
+sync = { path = "../common/sync" }
+thiserror = "*"
vm_memory = { path = "../vm_memory" }
diff --git a/vm_control/cargo2android.json b/vm_control/cargo2android.json
new file mode 100644
index 000000000..2ae887908
--- /dev/null
+++ b/vm_control/cargo2android.json
@@ -0,0 +1,8 @@
+{
+ "add-module-block": "cargo2android_gdb.bp",
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/vm_control/cargo2android_gdb.bp b/vm_control/cargo2android_gdb.bp
new file mode 100644
index 000000000..49b21a132
--- /dev/null
+++ b/vm_control/cargo2android_gdb.bp
@@ -0,0 +1,10 @@
+target: {
+ host_linux: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+}
diff --git a/vm_control/src/client.rs b/vm_control/src/client.rs
index 3fff3e32f..863b843dc 100644
--- a/vm_control/src/client.rs
+++ b/vm_control/src/client.rs
@@ -2,62 +2,44 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::*;
-use base::{info, net::UnixSeqpacket, validate_raw_descriptor, RawDescriptor, Tube};
+use base::{info, validate_raw_descriptor, RawDescriptor, Tube, UnixSeqpacket};
+use remain::sorted;
+use thiserror::Error;
use std::fs::OpenOptions;
use std::num::ParseIntError;
use std::path::{Path, PathBuf};
+#[sorted]
+#[derive(Error, Debug)]
enum ModifyBatError {
+ #[error("{0}")]
BatControlErr(BatControlResult),
}
-impl fmt::Display for ModifyBatError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::ModifyBatError::*;
-
- match self {
- BatControlErr(e) => write!(f, "{}", e),
- }
- }
-}
-
+#[sorted]
+#[derive(Error, Debug)]
pub enum ModifyUsbError {
+ #[error("argument missing: {0}")]
ArgMissing(&'static str),
+ #[error("failed to parse argument {0} value `{1}`")]
ArgParse(&'static str, String),
+ #[error("failed to parse integer argument {0} value `{1}`: {2}")]
ArgParseInt(&'static str, String, ParseIntError),
+ #[error("failed to validate file descriptor: {0}")]
FailedDescriptorValidate(base::Error),
+ #[error("path `{0}` does not exist")]
PathDoesNotExist(PathBuf),
+ #[error("socket failed")]
SocketFailed,
+ #[error("unexpected response: {0}")]
UnexpectedResponse(VmResponse),
+ #[error("unknown command: `{0}`")]
UnknownCommand(String),
+ #[error("{0}")]
UsbControl(UsbControlResult),
}
-impl std::fmt::Display for ModifyUsbError {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- use self::ModifyUsbError::*;
-
- match self {
- ArgMissing(a) => write!(f, "argument missing: {}", a),
- ArgParse(name, value) => {
- write!(f, "failed to parse argument {} value `{}`", name, value)
- }
- ArgParseInt(name, value, e) => write!(
- f,
- "failed to parse integer argument {} value `{}`: {}",
- name, value, e
- ),
- FailedDescriptorValidate(e) => write!(f, "failed to validate file descriptor: {}", e),
- PathDoesNotExist(p) => write!(f, "path `{}` does not exist", p.display()),
- SocketFailed => write!(f, "socket failed"),
- UnexpectedResponse(r) => write!(f, "unexpected response: {}", r),
- UnknownCommand(c) => write!(f, "unknown command: `{}`", c),
- UsbControl(e) => write!(f, "{}", e),
- }
- }
-}
-
pub type ModifyUsbResult<T> = std::result::Result<T, ModifyUsbError>;
fn raw_descriptor_from_path(path: &Path) -> ModifyUsbResult<RawDescriptor> {
@@ -100,12 +82,12 @@ pub fn do_usb_attach(
let usb_file: File = if dev_path.parent() == Some(Path::new("/proc/self/fd")) {
// Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
// Safe because we will validate |raw_fd|.
- unsafe { File::from_raw_descriptor(raw_descriptor_from_path(&dev_path)?) }
+ unsafe { File::from_raw_descriptor(raw_descriptor_from_path(dev_path)?) }
} else {
OpenOptions::new()
.read(true)
.write(true)
- .open(&dev_path)
+ .open(dev_path)
.map_err(|_| ModifyUsbError::UsbControl(UsbControlResult::FailedToOpenDevice))?
};
diff --git a/vm_control/src/gdb.rs b/vm_control/src/gdb.rs
index cf1cf6dd7..003f8a867 100644
--- a/vm_control/src/gdb.rs
+++ b/vm_control/src/gdb.rs
@@ -3,11 +3,11 @@
// found in the LICENSE file.
#[cfg(target_arch = "x86_64")]
-use gdbstub::arch::x86::reg::X86_64CoreRegs as CoreRegs;
+use gdbstub_arch::x86::reg::X86_64CoreRegs as CoreRegs;
use vm_memory::GuestAddress;
/// Messages that can be sent to a vCPU to set/get its state from the debugger.
-#[derive(Debug)]
+#[derive(Clone, Debug)]
pub enum VcpuDebug {
ReadMem(GuestAddress, usize),
ReadRegs,
@@ -18,6 +18,7 @@ pub enum VcpuDebug {
}
/// Messages that can be sent from a vCPU to update the state to the debugger.
+#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum VcpuDebugStatus {
RegValues(CoreRegs),
diff --git a/vm_control/src/lib.rs b/vm_control/src/lib.rs
index 3222edf09..b8499d6ba 100644
--- a/vm_control/src/lib.rs
+++ b/vm_control/src/lib.rs
@@ -15,20 +15,31 @@ pub mod gdb;
pub mod client;
+use std::convert::TryInto;
use std::fmt::{self, Display};
use std::fs::File;
use std::os::raw::c_int;
+use std::path::PathBuf;
use std::result::Result as StdResult;
use std::str::FromStr;
-use std::sync::Arc;
+use std::sync::{mpsc, Arc};
-use libc::{EINVAL, EIO, ENODEV};
+use std::thread::JoinHandle;
+
+use remain::sorted;
+use thiserror::Error;
+
+use libc::{EINVAL, EIO, ENODEV, ENOTSUP, ERANGE};
use serde::{Deserialize, Serialize};
+pub use balloon_control::BalloonStats;
+use balloon_control::{BalloonTubeCommand, BalloonTubeResult};
+
use base::{
- error, with_as_descriptor, AsRawDescriptor, Error as SysError, Event, ExternalMapping, Fd,
- FromRawDescriptor, IntoRawDescriptor, MappedRegion, MemoryMappingArena, MemoryMappingBuilder,
- MemoryMappingBuilderUnix, MmapError, Protection, Result, SafeDescriptor, SharedMemory, Tube,
+ error, with_as_descriptor, AsRawDescriptor, Error as SysError, Event, ExternalMapping,
+ FromRawDescriptor, IntoRawDescriptor, Killable, MappedRegion, MemoryMappingArena,
+ MemoryMappingBuilder, MemoryMappingBuilderUnix, MmapError, Protection, Result, SafeDescriptor,
+ SharedMemory, Tube, SIGRTMIN,
};
use hypervisor::{IrqRoute, IrqSource, Vm};
use resources::{Alloc, MmioType, SystemAllocator};
@@ -57,11 +68,12 @@ pub use crate::gdb::*;
pub use hypervisor::MemSlot;
/// Control the state of a particular VM CPU.
-#[derive(Debug)]
+#[derive(Clone, Debug)]
pub enum VcpuControl {
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
Debug(VcpuDebug),
RunState(VmRunMode),
+ MakeRT,
}
/// Mode of execution for the VM.
@@ -96,6 +108,17 @@ impl Default for VmRunMode {
}
}
+// Trait for devices that get notification on specific GPE trigger
+pub trait GpeNotify: Send {
+ fn notify(&mut self) {}
+}
+
+pub trait PmResource {
+ fn pwrbtn_evt(&mut self) {}
+ fn gpe_evt(&mut self, _gpe: u32) {}
+ fn register_gpe_notify_dev(&mut self, _gpe: u32, _notify_dev: Arc<Mutex<dyn GpeNotify>>) {}
+}
+
/// The maximum number of devices that can be listed in one `UsbControlCommand`.
///
/// This value was set to be equal to `xhci_regs::MAX_PORTS` for convenience, but it is not
@@ -103,6 +126,7 @@ impl Default for VmRunMode {
/// require adding a big dependency for a single const.
pub const USB_CONTROL_MAX_PORTS: usize = 16;
+// Balloon commands that are sent on the crosvm control socket.
#[derive(Serialize, Deserialize, Debug)]
pub enum BalloonControlCommand {
/// Set the size of the VM's balloon.
@@ -112,58 +136,7 @@ pub enum BalloonControlCommand {
Stats,
}
-// BalloonStats holds stats returned from the stats_queue.
-#[derive(Default, Serialize, Deserialize, Debug)]
-pub struct BalloonStats {
- pub swap_in: Option<u64>,
- pub swap_out: Option<u64>,
- pub major_faults: Option<u64>,
- pub minor_faults: Option<u64>,
- pub free_memory: Option<u64>,
- pub total_memory: Option<u64>,
- pub available_memory: Option<u64>,
- pub disk_caches: Option<u64>,
- pub hugetlb_allocations: Option<u64>,
- pub hugetlb_failures: Option<u64>,
-}
-
-impl Display for BalloonStats {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "{{")?;
- if let Some(swap_in) = self.swap_in {
- write!(f, "\n swap_in: {}", swap_in)?;
- }
- if let Some(swap_out) = self.swap_out {
- write!(f, "\n swap_out: {}", swap_out)?;
- }
- if let Some(major_faults) = self.major_faults {
- write!(f, "\n major_faults: {}", major_faults)?;
- }
- if let Some(minor_faults) = self.minor_faults {
- write!(f, "\n minor_faults: {}", minor_faults)?;
- }
- if let Some(free_memory) = self.free_memory {
- write!(f, "\n free_memory: {}", free_memory)?;
- }
- if let Some(total_memory) = self.total_memory {
- write!(f, "\n total_memory: {}", total_memory)?;
- }
- if let Some(available_memory) = self.available_memory {
- write!(f, "\n available_memory: {}", available_memory)?;
- }
- if let Some(disk_caches) = self.disk_caches {
- write!(f, "\n disk_caches: {}", disk_caches)?;
- }
- if let Some(hugetlb_allocations) = self.hugetlb_allocations {
- write!(f, "\n hugetlb_allocations: {}", hugetlb_allocations)?;
- }
- if let Some(hugetlb_failures) = self.hugetlb_failures {
- write!(f, "\n hugetlb_failures: {}", hugetlb_failures)?;
- }
- write!(f, "\n}}")
- }
-}
-
+// BalloonControlResult holds results for BalloonControlCommand defined above.
#[derive(Serialize, Deserialize, Debug)]
pub enum BalloonControlResult {
Stats {
@@ -188,7 +161,7 @@ impl Display for DiskControlCommand {
}
}
-#[derive(Serialize, Deserialize, Debug)]
+#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum DiskControlResult {
Ok,
Err(SysError),
@@ -233,6 +206,7 @@ pub enum UsbControlResult {
NoSuchPort,
FailedToOpenDevice,
Devices([UsbControlAttachedDevice; USB_CONTROL_MAX_PORTS]),
+ FailedToInitHostDevice,
}
impl Display for UsbControlResult {
@@ -252,47 +226,147 @@ impl Display for UsbControlResult {
}
std::result::Result::Ok(())
}
+ FailedToInitHostDevice => write!(f, "failed_to_init_host_device"),
}
}
}
+/// Source of a `VmMemoryRequest::RegisterMemory` mapping.
#[derive(Serialize, Deserialize)]
-pub enum VmMemoryRequest {
- /// Register shared memory represented by the given descriptor into guest address space.
- /// The response variant is `VmResponse::RegisterMemory`.
- RegisterMemory(SharedMemory),
- /// Similiar to `VmMemoryRequest::RegisterMemory`, but doesn't allocate new address space.
- /// Useful for cases where the address space is already allocated (PCI regions).
- RegisterFdAtPciBarOffset(Alloc, SafeDescriptor, usize, u64),
- /// Similar to RegisterFdAtPciBarOffset, but is for buffers in the current address space.
- RegisterHostPointerAtPciBarOffset(Alloc, u64),
- /// Similiar to `RegisterFdAtPciBarOffset`, but uses Vulkano to map the resource instead of
- /// the mmap system call.
- RegisterVulkanMemoryAtPciBarOffset {
- alloc: Alloc,
+pub enum VmMemorySource {
+ /// Register shared memory represented by the given descriptor.
+ SharedMemory(SharedMemory),
+ /// Register a file mapping from the given descriptor.
+ Descriptor {
+ /// File descriptor to map.
+ descriptor: SafeDescriptor,
+ /// Offset within the file in bytes.
+ offset: u64,
+ /// Size of the mapping in bytes.
+ size: u64,
+ },
+ /// Register memory mapped by Vulkano.
+ Vulkan {
descriptor: SafeDescriptor,
handle_type: u32,
memory_idx: u32,
physical_device_idx: u32,
- offset: u64,
size: u64,
},
- /// Unregister the given memory slot that was previously registered with `RegisterMemory*`.
- UnregisterMemory(MemSlot),
+ /// Register the current rutabaga external mapping.
+ ExternalMapping { size: u64 },
+}
+
+impl VmMemorySource {
+ /// Map the resource and return its mapping and size in bytes.
+ pub fn map(
+ self,
+ map_request: Arc<Mutex<Option<ExternalMapping>>>,
+ gralloc: &mut RutabagaGralloc,
+ read_only: bool,
+ ) -> Result<(Box<dyn MappedRegion>, u64)> {
+ let (mem_region, size) = match self {
+ VmMemorySource::Descriptor {
+ descriptor,
+ offset,
+ size,
+ } => (map_descriptor(&descriptor, offset, size, read_only)?, size),
+ VmMemorySource::SharedMemory(shm) => {
+ (map_descriptor(&shm, 0, shm.size(), read_only)?, shm.size())
+ }
+ VmMemorySource::Vulkan {
+ descriptor,
+ handle_type,
+ memory_idx,
+ physical_device_idx,
+ size,
+ } => {
+ let mapped_region = match gralloc.import_and_map(
+ RutabagaHandle {
+ os_handle: descriptor,
+ handle_type,
+ },
+ VulkanInfo {
+ memory_idx,
+ physical_device_idx,
+ },
+ size,
+ ) {
+ Ok(mapped_region) => mapped_region,
+ Err(e) => {
+ error!("gralloc failed to import and map: {}", e);
+ return Err(SysError::new(EINVAL));
+ }
+ };
+ (mapped_region, size)
+ }
+ VmMemorySource::ExternalMapping { size } => {
+ let mem = map_request
+ .lock()
+ .take()
+ .ok_or_else(|| VmMemoryResponse::Err(SysError::new(EINVAL)))
+ .unwrap();
+ let mapped_region: Box<dyn MappedRegion> = Box::new(mem);
+ (mapped_region, size)
+ }
+ };
+ Ok((mem_region, size))
+ }
+}
+
+/// Destination of a `VmMemoryRequest::RegisterMemory` mapping in guest address space.
+#[derive(Serialize, Deserialize)]
+pub enum VmMemoryDestination {
+ /// Map at an offset within an existing PCI BAR allocation.
+ ExistingAllocation { allocation: Alloc, offset: u64 },
+ /// Create a new anonymous allocation in MMIO space.
+ NewAllocation,
+ /// Map at the specified guest physical address.
+ GuestPhysicalAddress(u64),
+}
+
+impl VmMemoryDestination {
+ /// Allocate and return the guest address of a memory mapping destination.
+ pub fn allocate(self, allocator: &mut SystemAllocator, size: u64) -> Result<GuestAddress> {
+ let addr = match self {
+ VmMemoryDestination::ExistingAllocation { allocation, offset } => allocator
+ .mmio_allocator(MmioType::High)
+ .address_from_pci_offset(allocation, offset, size)
+ .map_err(|_e| SysError::new(EINVAL))?,
+ VmMemoryDestination::NewAllocation => {
+ let alloc = allocator.get_anon_alloc();
+ allocator
+ .mmio_allocator(MmioType::High)
+ .allocate(size, alloc, "vmcontrol_register_memory".to_string())
+ .map_err(|_e| SysError::new(EINVAL))?
+ }
+ VmMemoryDestination::GuestPhysicalAddress(gpa) => gpa,
+ };
+ Ok(GuestAddress(addr))
+ }
+}
+
+#[derive(Serialize, Deserialize)]
+pub enum VmMemoryRequest {
+ RegisterMemory {
+ /// Source of the memory to register (mapped file descriptor, shared memory region, etc.)
+ source: VmMemorySource,
+ /// Where to map the memory in the guest.
+ dest: VmMemoryDestination,
+ /// Whether to map the memory read only (true) or read-write (false).
+ read_only: bool,
+ },
/// Allocate GPU buffer of a given size/format and register the memory into guest address space.
/// The response variant is `VmResponse::AllocateAndRegisterGpuMemory`
AllocateAndRegisterGpuMemory {
width: u32,
height: u32,
format: u32,
+ /// Where to map the memory in the guest.
+ dest: VmMemoryDestination,
},
- /// Register mmaped memory into the hypervisor's EPT.
- RegisterMmapMemory {
- descriptor: SafeDescriptor,
- size: usize,
- offset: u64,
- gpa: u64,
- },
+ /// Unregister the given memory slot that was previously registered with `RegisterMemory`.
+ UnregisterMemory(MemSlot),
}
impl VmMemoryRequest {
@@ -314,150 +388,115 @@ impl VmMemoryRequest {
) -> VmMemoryResponse {
use self::VmMemoryRequest::*;
match self {
- RegisterMemory(ref shm) => {
- match register_memory(vm, sys_allocator, shm, shm.size() as usize, None) {
- Ok((pfn, slot)) => VmMemoryResponse::RegisterMemory { pfn, slot },
- Err(e) => VmMemoryResponse::Err(e),
- }
- }
- RegisterFdAtPciBarOffset(alloc, ref descriptor, size, offset) => {
- match register_memory(vm, sys_allocator, descriptor, size, Some((alloc, offset))) {
- Ok((pfn, slot)) => VmMemoryResponse::RegisterMemory { pfn, slot },
- Err(e) => VmMemoryResponse::Err(e),
- }
+ RegisterMemory {
+ source,
+ dest,
+ read_only,
+ } => {
+ let (mapped_region, size) = match source.map(map_request, gralloc, read_only) {
+ Ok((region, size)) => (region, size),
+ Err(e) => return VmMemoryResponse::Err(e),
+ };
+
+ let guest_addr = match dest.allocate(sys_allocator, size) {
+ Ok(addr) => addr,
+ Err(e) => return VmMemoryResponse::Err(e),
+ };
+
+ let slot = match vm.add_memory_region(guest_addr, mapped_region, read_only, false) {
+ Ok(slot) => slot,
+ Err(e) => return VmMemoryResponse::Err(e),
+ };
+ let pfn = guest_addr.0 >> 12;
+ VmMemoryResponse::RegisterMemory { pfn, slot }
}
UnregisterMemory(slot) => match vm.remove_memory_region(slot) {
Ok(_) => VmMemoryResponse::Ok,
Err(e) => VmMemoryResponse::Err(e),
},
- RegisterHostPointerAtPciBarOffset(alloc, offset) => {
- let mem = map_request
- .lock()
- .take()
- .ok_or_else(|| VmMemoryResponse::Err(SysError::new(EINVAL)))
- .unwrap();
-
- match register_host_pointer(vm, sys_allocator, Box::new(mem), (alloc, offset)) {
- Ok((pfn, slot)) => VmMemoryResponse::RegisterMemory { pfn, slot },
- Err(e) => VmMemoryResponse::Err(e),
- }
- }
- RegisterVulkanMemoryAtPciBarOffset {
- alloc,
- descriptor,
- handle_type,
- memory_idx,
- physical_device_idx,
- offset,
- size,
- } => {
- let mapped_region = match gralloc.import_and_map(
- RutabagaHandle {
- os_handle: descriptor,
- handle_type,
- },
- VulkanInfo {
- memory_idx,
- physical_device_idx,
- },
- size,
- ) {
- Ok(mapped_region) => mapped_region,
- Err(e) => {
- error!("gralloc failed to import and map: {}", e);
- return VmMemoryResponse::Err(SysError::new(EINVAL));
- }
- };
-
- match register_host_pointer(vm, sys_allocator, mapped_region, (alloc, offset)) {
- Ok((pfn, slot)) => VmMemoryResponse::RegisterMemory { pfn, slot },
- Err(e) => VmMemoryResponse::Err(e),
- }
- }
AllocateAndRegisterGpuMemory {
width,
height,
format,
+ dest,
} => {
- let img = ImageAllocationInfo {
- width,
- height,
- drm_format: DrmFormat::from(format),
- // Linear layout is a requirement as virtio wayland guest expects
- // this for CPU access to the buffer. Scanout and texturing are
- // optional as the consumer (wayland compositor) is expected to
- // fall-back to a less efficient meachnisms for presentation if
- // neccesary. In practice, linear buffers for commonly used formats
- // will also support scanout and texturing.
- flags: RutabagaGrallocFlags::empty().use_linear(true),
- };
-
- let reqs = match gralloc.get_image_memory_requirements(img) {
- Ok(reqs) => reqs,
- Err(e) => {
- error!("gralloc failed to get image requirements: {}", e);
- return VmMemoryResponse::Err(SysError::new(EINVAL));
- }
+ let (mapped_region, size, descriptor, gpu_desc) =
+ match Self::allocate_gpu_memory(gralloc, width, height, format) {
+ Ok(v) => v,
+ Err(e) => return VmMemoryResponse::Err(e),
+ };
+
+ let guest_addr = match dest.allocate(sys_allocator, size) {
+ Ok(addr) => addr,
+ Err(e) => return VmMemoryResponse::Err(e),
};
- let handle = match gralloc.allocate_memory(reqs) {
- Ok(handle) => handle,
- Err(e) => {
- error!("gralloc failed to allocate memory: {}", e);
- return VmMemoryResponse::Err(SysError::new(EINVAL));
- }
+ let slot = match vm.add_memory_region(guest_addr, mapped_region, false, false) {
+ Ok(slot) => slot,
+ Err(e) => return VmMemoryResponse::Err(e),
};
+ let pfn = guest_addr.0 >> 12;
- let mut desc = GpuMemoryDesc::default();
- for i in 0..3 {
- desc.planes[i] = GpuMemoryPlaneDesc {
- stride: reqs.strides[i],
- offset: reqs.offsets[i],
- }
+ VmMemoryResponse::AllocateAndRegisterGpuMemory {
+ descriptor,
+ pfn,
+ slot,
+ desc: gpu_desc,
}
+ }
+ }
+ }
- match register_memory(
- vm,
- sys_allocator,
- &handle.os_handle,
- reqs.size as usize,
- None,
- ) {
- Ok((pfn, slot)) => VmMemoryResponse::AllocateAndRegisterGpuMemory {
- // Safe because ownership is transferred to SafeDescriptor via
- // into_raw_descriptor
- descriptor: unsafe {
- SafeDescriptor::from_raw_descriptor(
- handle.os_handle.into_raw_descriptor(),
- )
- },
- pfn,
- slot,
- desc,
- },
- Err(e) => VmMemoryResponse::Err(e),
- }
+ fn allocate_gpu_memory(
+ gralloc: &mut RutabagaGralloc,
+ width: u32,
+ height: u32,
+ format: u32,
+ ) -> Result<(Box<dyn MappedRegion>, u64, SafeDescriptor, GpuMemoryDesc)> {
+ let img = ImageAllocationInfo {
+ width,
+ height,
+ drm_format: DrmFormat::from(format),
+ // Linear layout is a requirement as virtio wayland guest expects
+ // this for CPU access to the buffer. Scanout and texturing are
+ // optional as the consumer (wayland compositor) is expected to
+ // fall-back to a less efficient meachnisms for presentation if
+ // neccesary. In practice, linear buffers for commonly used formats
+ // will also support scanout and texturing.
+ flags: RutabagaGrallocFlags::empty().use_linear(true),
+ };
+
+ let reqs = match gralloc.get_image_memory_requirements(img) {
+ Ok(reqs) => reqs,
+ Err(e) => {
+ error!("gralloc failed to get image requirements: {}", e);
+ return Err(SysError::new(EINVAL));
}
- RegisterMmapMemory {
- ref descriptor,
- size,
- offset,
- gpa,
- } => {
- let mmap = match MemoryMappingBuilder::new(size)
- .from_descriptor(descriptor)
- .offset(offset as u64)
- .build()
- {
- Ok(v) => v,
- Err(_e) => return VmMemoryResponse::Err(SysError::new(EINVAL)),
- };
- match vm.add_memory_region(GuestAddress(gpa), Box::new(mmap), false, false) {
- Ok(_) => VmMemoryResponse::Ok,
- Err(e) => VmMemoryResponse::Err(e),
- }
+ };
+
+ let handle = match gralloc.allocate_memory(reqs) {
+ Ok(handle) => handle,
+ Err(e) => {
+ error!("gralloc failed to allocate memory: {}", e);
+ return Err(SysError::new(EINVAL));
+ }
+ };
+
+ let mut desc = GpuMemoryDesc::default();
+ for i in 0..3 {
+ desc.planes[i] = GpuMemoryPlaneDesc {
+ stride: reqs.strides[i],
+ offset: reqs.offsets[i],
}
}
+
+ // Safe because ownership is transferred to SafeDescriptor via
+ // into_raw_descriptor
+ let descriptor =
+ unsafe { SafeDescriptor::from_raw_descriptor(handle.os_handle.into_raw_descriptor()) };
+
+ let mapped_region = map_descriptor(&descriptor, 0, reqs.size, false)?;
+ Ok((mapped_region, reqs.size, descriptor, desc))
}
}
@@ -484,21 +523,32 @@ pub enum VmMemoryResponse {
#[derive(Serialize, Deserialize, Debug)]
pub enum VmIrqRequest {
/// Allocate one gsi, and associate gsi to irqfd with register_irqfd()
- AllocateOneMsi { irqfd: Event },
+ AllocateOneMsi {
+ irqfd: Event,
+ device_id: u32,
+ queue_id: usize,
+ device_name: String,
+ },
/// Add one msi route entry into the IRQ chip.
AddMsiRoute {
gsi: u32,
msi_address: u64,
msi_data: u32,
},
+ // unregister_irqfs() and release gsi
+ ReleaseOneIrq {
+ gsi: u32,
+ irqfd: Event,
+ },
}
/// Data to set up an IRQ event or IRQ route on the IRQ chip.
/// VmIrqRequest::execute can't take an `IrqChip` argument, because of a dependency cycle between
/// devices and vm_control, so it takes a Fn that processes an `IrqSetup`.
pub enum IrqSetup<'a> {
- Event(u32, &'a Event),
+ Event(u32, &'a Event, u32, usize, String),
Route(IrqRoute),
+ UnRegister(u32, &'a Event),
}
impl VmIrqRequest {
@@ -516,9 +566,20 @@ impl VmIrqRequest {
{
use self::VmIrqRequest::*;
match *self {
- AllocateOneMsi { ref irqfd } => {
+ AllocateOneMsi {
+ ref irqfd,
+ device_id,
+ queue_id,
+ ref device_name,
+ } => {
if let Some(irq_num) = sys_allocator.allocate_irq() {
- match set_up_irq(IrqSetup::Event(irq_num, &irqfd)) {
+ match set_up_irq(IrqSetup::Event(
+ irq_num,
+ irqfd,
+ device_id,
+ queue_id,
+ device_name.clone(),
+ )) {
Ok(_) => VmIrqResponse::AllocateOneMsi { gsi: irq_num },
Err(e) => VmIrqResponse::Err(e),
}
@@ -543,6 +604,11 @@ impl VmIrqRequest {
Err(e) => VmIrqResponse::Err(e),
}
}
+ ReleaseOneIrq { gsi, ref irqfd } => {
+ let _ = set_up_irq(IrqSetup::UnRegister(gsi, irqfd));
+ sys_allocator.release_irq(gsi);
+ VmIrqResponse::Ok
+ }
}
}
}
@@ -870,13 +936,11 @@ impl FsMappingRequest {
prot,
mem_offset,
} => {
- let raw_fd: Fd = Fd(fd.as_raw_descriptor());
-
match vm.add_fd_mapping(
slot,
mem_offset,
size,
- &raw_fd,
+ fd,
file_offset,
Protection::from(prot as c_int & (libc::PROT_READ | libc::PROT_WRITE)),
) {
@@ -901,10 +965,16 @@ impl FsMappingRequest {
pub enum VmRequest {
/// Break the VM's run loop and exit.
Exit,
+ /// Trigger a power button event in the guest.
+ Powerbtn,
/// Suspend the VM's VCPUs until resume.
Suspend,
/// Resume the VM's VCPUs that were previously suspended.
Resume,
+ /// Inject a general-purpose event.
+ Gpe(u32),
+ /// Make the VM's RT VCPU real-time.
+ MakeRT,
/// Command for balloon driver.
BalloonCommand(BalloonControlCommand),
/// Send a command to a disk chosen by `disk_index`.
@@ -917,56 +987,32 @@ pub enum VmRequest {
UsbCommand(UsbControlCommand),
/// Command to set battery.
BatCommand(BatteryType, BatControlCommand),
+ /// Command to add/remove vfio pci device
+ VfioCommand { vfio_path: PathBuf, add: bool },
}
-fn register_memory(
- vm: &mut impl Vm,
- allocator: &mut SystemAllocator,
+fn map_descriptor(
descriptor: &dyn AsRawDescriptor,
- size: usize,
- pci_allocation: Option<(Alloc, u64)>,
-) -> Result<(u64, MemSlot)> {
- let mmap = match MemoryMappingBuilder::new(size)
+ offset: u64,
+ size: u64,
+ read_only: bool,
+) -> Result<Box<dyn MappedRegion>> {
+ let size: usize = size.try_into().map_err(|_e| SysError::new(ERANGE))?;
+ let prot = if read_only {
+ Protection::read()
+ } else {
+ Protection::read_write()
+ };
+ match MemoryMappingBuilder::new(size)
.from_descriptor(descriptor)
+ .offset(offset)
+ .protection(prot)
.build()
{
- Ok(v) => v,
- Err(MmapError::SystemCallFailed(e)) => return Err(e),
- _ => return Err(SysError::new(EINVAL)),
- };
-
- let addr = match pci_allocation {
- Some(pci_allocation) => allocator
- .mmio_allocator_any()
- .address_from_pci_offset(pci_allocation.0, pci_allocation.1, size as u64)
- .map_err(|_e| SysError::new(EINVAL))?,
- None => {
- let alloc = allocator.get_anon_alloc();
- allocator
- .mmio_allocator_any()
- .allocate(size as u64, alloc, "vmcontrol_register_memory".to_string())
- .map_err(|_e| SysError::new(EINVAL))?
- }
- };
-
- let slot = vm.add_memory_region(GuestAddress(addr), Box::new(mmap), false, false)?;
-
- Ok((addr >> 12, slot))
-}
-
-fn register_host_pointer(
- vm: &mut impl Vm,
- allocator: &mut SystemAllocator,
- mem: Box<dyn MappedRegion>,
- pci_allocation: (Alloc, u64),
-) -> Result<(u64, MemSlot)> {
- let addr = allocator
- .mmio_allocator_any()
- .address_from_pci_offset(pci_allocation.0, pci_allocation.1, mem.size() as u64)
- .map_err(|_e| SysError::new(EINVAL))?;
-
- let slot = vm.add_memory_region(GuestAddress(addr), mem, false, false)?;
- Ok((addr >> 12, slot))
+ Ok(mmap) => Ok(Box::new(mmap)),
+ Err(MmapError::SystemCallFailed(e)) => Err(e),
+ _ => Err(SysError::new(EINVAL)),
+ }
}
impl VmRequest {
@@ -978,16 +1024,28 @@ impl VmRequest {
pub fn execute(
&self,
run_mode: &mut Option<VmRunMode>,
- balloon_host_tube: &Tube,
+ balloon_host_tube: Option<&Tube>,
+ balloon_stats_id: &mut u64,
disk_host_tubes: &[Tube],
- usb_control_tube: &Tube,
+ pm: &mut Option<Arc<Mutex<dyn PmResource>>>,
+ usb_control_tube: Option<&Tube>,
bat_control: &mut Option<BatControl>,
+ vcpu_handles: &[(JoinHandle<()>, mpsc::Sender<VcpuControl>)],
) -> VmResponse {
match *self {
VmRequest::Exit => {
*run_mode = Some(VmRunMode::Exiting);
VmResponse::Ok
}
+ VmRequest::Powerbtn => {
+ if pm.is_some() {
+ pm.as_ref().unwrap().lock().pwrbtn_evt();
+ VmResponse::Ok
+ } else {
+ error!("{:#?} not supported", *self);
+ VmResponse::Err(SysError::new(ENOTSUP))
+ }
+ }
VmRequest::Suspend => {
*run_mode = Some(VmRunMode::Suspending);
VmResponse::Ok
@@ -996,28 +1054,84 @@ impl VmRequest {
*run_mode = Some(VmRunMode::Running);
VmResponse::Ok
}
+ VmRequest::Gpe(gpe) => {
+ if pm.is_some() {
+ pm.as_ref().unwrap().lock().gpe_evt(gpe);
+ VmResponse::Ok
+ } else {
+ error!("{:#?} not supported", *self);
+ VmResponse::Err(SysError::new(ENOTSUP))
+ }
+ }
+ VmRequest::MakeRT => {
+ for (handle, channel) in vcpu_handles {
+ if let Err(e) = channel.send(VcpuControl::MakeRT) {
+ error!("failed to send MakeRT: {}", e);
+ }
+ let _ = handle.kill(SIGRTMIN() + 0);
+ }
+ VmResponse::Ok
+ }
VmRequest::BalloonCommand(BalloonControlCommand::Adjust { num_bytes }) => {
- match balloon_host_tube.send(&BalloonControlCommand::Adjust { num_bytes }) {
- Ok(_) => VmResponse::Ok,
- Err(_) => VmResponse::Err(SysError::last()),
+ if let Some(balloon_host_tube) = balloon_host_tube {
+ match balloon_host_tube.send(&BalloonTubeCommand::Adjust {
+ num_bytes,
+ allow_failure: false,
+ }) {
+ Ok(_) => VmResponse::Ok,
+ Err(_) => VmResponse::Err(SysError::last()),
+ }
+ } else {
+ VmResponse::Err(SysError::new(ENOTSUP))
}
}
VmRequest::BalloonCommand(BalloonControlCommand::Stats) => {
- match balloon_host_tube.send(&BalloonControlCommand::Stats {}) {
- Ok(_) => match balloon_host_tube.recv() {
- Ok(BalloonControlResult::Stats {
- stats,
- balloon_actual,
- }) => VmResponse::BalloonStats {
- stats,
- balloon_actual,
- },
- Err(e) => {
- error!("balloon socket recv failed: {}", e);
- VmResponse::Err(SysError::last())
+ if let Some(balloon_host_tube) = balloon_host_tube {
+ // NB: There are a few reasons stale balloon stats could be left
+ // in balloon_host_tube:
+ // - the send succeeds, but the recv fails because the device
+ // is not ready yet. So when the device is ready, there are
+ // extra stats requests queued.
+ // - the send succeed, but the recv times out. When the device
+ // does return the stats, there will be no consumer.
+ //
+ // To guard against this, add an `id` to the stats request. If
+ // the id returned to us doesn't match, we keep trying to read
+ // until it does.
+ *balloon_stats_id = (*balloon_stats_id).wrapping_add(1);
+ let sent_id = *balloon_stats_id;
+ match balloon_host_tube.send(&BalloonTubeCommand::Stats { id: sent_id }) {
+ Ok(_) => {
+ loop {
+ match balloon_host_tube.recv() {
+ Ok(BalloonTubeResult::Stats {
+ stats,
+ balloon_actual,
+ id,
+ }) => {
+ if sent_id != id {
+ // Keep trying to get the fresh stats.
+ continue;
+ }
+ break VmResponse::BalloonStats {
+ stats,
+ balloon_actual,
+ };
+ }
+ Err(e) => {
+ error!("balloon socket recv failed: {}", e);
+ break VmResponse::Err(SysError::last());
+ }
+ Ok(BalloonTubeResult::Adjusted { .. }) => {
+ unreachable!("unexpected adjusted response")
+ }
+ }
+ }
}
- },
- Err(_) => VmResponse::Err(SysError::last()),
+ Err(_) => VmResponse::Err(SysError::last()),
+ }
+ } else {
+ VmResponse::Err(SysError::new(ENOTSUP))
}
}
VmRequest::DiskCommand {
@@ -1044,6 +1158,13 @@ impl VmRequest {
}
}
VmRequest::UsbCommand(ref cmd) => {
+ let usb_control_tube = match usb_control_tube {
+ Some(t) => t,
+ None => {
+ error!("attempted to execute USB request without control tube");
+ return VmResponse::Err(SysError::new(ENODEV));
+ }
+ };
let res = usb_control_tube.send(cmd);
if let Err(e) = res {
error!("fail to send command to usb control socket: {}", e);
@@ -1082,6 +1203,10 @@ impl VmRequest {
None => VmResponse::BatResponse(BatControlResult::NoBatDevice),
}
}
+ VmRequest::VfioCommand {
+ vfio_path: _,
+ add: _,
+ } => VmResponse::Ok,
}
}
}
@@ -1137,13 +1262,138 @@ impl Display for VmResponse {
VmResponse::BalloonStats {
stats,
balloon_actual,
- } => write!(
- f,
- "balloon size: {}\nballoon stats: {}",
- balloon_actual, stats
- ),
+ } => {
+ write!(
+ f,
+ "stats: {}\nballoon_actual: {}",
+ serde_json::to_string_pretty(&stats)
+ .unwrap_or_else(|_| "invalid_response".to_string()),
+ balloon_actual
+ )
+ }
UsbResponse(result) => write!(f, "usb control request get result {:?}", result),
BatResponse(result) => write!(f, "{}", result),
}
}
}
+
+#[sorted]
+#[derive(Error, Debug)]
+pub enum VirtioIOMMUVfioError {
+ #[error("socket failed")]
+ SocketFailed,
+ #[error("unexpected response: {0}")]
+ UnexpectedResponse(VirtioIOMMUResponse),
+ #[error("unknown command: `{0}`")]
+ UnknownCommand(String),
+ #[error("{0}")]
+ VfioControl(VirtioIOMMUVfioResult),
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub enum VirtioIOMMUVfioCommand {
+ // Add the vfio device attached to virtio-iommu.
+ VfioDeviceAdd {
+ endpoint_addr: u32,
+ #[serde(with = "with_as_descriptor")]
+ container: File,
+ },
+ // Delete the vfio device attached to virtio-iommu.
+ VfioDeviceDel {
+ endpoint_addr: u32,
+ },
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub enum VirtioIOMMUVfioResult {
+ Ok,
+ NotInPCIRanges,
+ NoAvailableContainer,
+ NoSuchDevice,
+}
+
+impl Display for VirtioIOMMUVfioResult {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::VirtioIOMMUVfioResult::*;
+
+ match self {
+ Ok => write!(f, "successfully"),
+ NotInPCIRanges => write!(f, "not in the pci ranges of virtio-iommu"),
+ NoAvailableContainer => write!(f, "no available vfio container"),
+ NoSuchDevice => write!(f, "no such a vfio device"),
+ }
+ }
+}
+
+/// A request to the virtio-iommu process to perform some operations.
+///
+/// Unless otherwise noted, each request should expect a `VirtioIOMMUResponse::Ok` to be received on
+/// success.
+#[derive(Serialize, Deserialize, Debug)]
+pub enum VirtioIOMMURequest {
+ /// Command for vfio related operations.
+ VfioCommand(VirtioIOMMUVfioCommand),
+}
+
+/// Indication of success or failure of a `VirtioIOMMURequest`.
+///
+/// Success is usually indicated `VirtioIOMMUResponse::Ok` unless there is data associated with the
+/// response.
+#[derive(Serialize, Deserialize, Debug)]
+pub enum VirtioIOMMUResponse {
+ /// Indicates the request was executed successfully.
+ Ok,
+ /// Indicates the request encountered some error during execution.
+ Err(SysError),
+ /// Results for Vfio commands.
+ VfioResponse(VirtioIOMMUVfioResult),
+}
+
+impl Display for VirtioIOMMUResponse {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::VirtioIOMMUResponse::*;
+ match self {
+ Ok => write!(f, "ok"),
+ Err(e) => write!(f, "error: {}", e),
+ VfioResponse(result) => write!(
+ f,
+ "The vfio-related virtio-iommu request got result: {:?}",
+ result
+ ),
+ }
+ }
+}
+
+/// Send VirtioIOMMURequest without waiting for the response
+pub fn virtio_iommu_request_async(
+ iommu_control_tube: &Tube,
+ req: &VirtioIOMMURequest,
+) -> VirtioIOMMUResponse {
+ match iommu_control_tube.send(&req) {
+ Ok(_) => VirtioIOMMUResponse::Ok,
+ Err(e) => {
+ error!("virtio-iommu socket send failed: {:?}", e);
+ VirtioIOMMUResponse::Err(SysError::last())
+ }
+ }
+}
+
+pub type VirtioIOMMURequestResult = std::result::Result<VirtioIOMMUResponse, ()>;
+
+/// Send VirtioIOMMURequest and wait to get the response
+pub fn virtio_iommu_request(
+ iommu_control_tube: &Tube,
+ req: &VirtioIOMMURequest,
+) -> VirtioIOMMURequestResult {
+ let response = match virtio_iommu_request_async(iommu_control_tube, req) {
+ VirtioIOMMUResponse::Ok => match iommu_control_tube.recv() {
+ Ok(response) => response,
+ Err(e) => {
+ error!("virtio-iommu socket recv failed: {:?}", e);
+ VirtioIOMMUResponse::Err(SysError::last())
+ }
+ },
+ resp => resp,
+ };
+ Ok(response)
+}
diff --git a/vm_memory/Android.bp b/vm_memory/Android.bp
index 9634de9ff..58f4c07f8 100644
--- a/vm_memory/Android.bp
+++ b/vm_memory/Android.bp
@@ -1,4 +1,4 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace.
+// This file is generated by cargo2android.py --run --device --tests --global_defaults=crosvm_defaults --add_workspace.
// Do not modify this file as changes will be overridden on upgrade.
package {
@@ -15,84 +15,44 @@ rust_library {
defaults: ["crosvm_defaults"],
host_supported: true,
crate_name: "vm_memory",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
+ edition: "2021",
rustlibs: [
"libbase_rust",
"libbitflags",
"libcros_async",
"libdata_model",
"liblibc",
+ "libserde",
+ "libthiserror",
],
+ proc_macros: ["libremain"],
}
-rust_defaults {
- name: "vm_memory_defaults",
+rust_test {
+ name: "vm_memory_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "vm_memory",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
+ test_options: {
+ unit_test: true,
+ },
+ edition: "2021",
rustlibs: [
"libbase_rust",
"libbitflags",
"libcros_async",
"libdata_model",
"liblibc",
+ "libserde",
+ "libthiserror",
],
+ proc_macros: ["libremain"],
}
-
-rust_test_host {
- name: "vm_memory_host_test_src_lib",
- defaults: ["vm_memory_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-
-rust_test {
- name: "vm_memory_device_test_src_lib",
- defaults: ["vm_memory_defaults"],
-}
-
-// dependent_library ["feature_list"]
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../tempfile/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.50
-// autocfg-1.0.1
-// bitflags-1.2.1 "default"
-// futures-0.3.14 "alloc"
-// futures-channel-0.3.14 "alloc,futures-sink,sink"
-// futures-core-0.3.14 "alloc"
-// futures-io-0.3.14
-// futures-sink-0.3.14 "alloc"
-// futures-task-0.3.14 "alloc"
-// futures-util-0.3.14 "alloc,futures-sink,sink"
-// intrusive-collections-0.9.0 "alloc,default"
-// itoa-0.4.7
-// libc-0.2.93 "default,std"
-// memoffset-0.5.6 "default"
-// paste-1.0.5
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// proc-macro2-1.0.26 "default,proc-macro"
-// quote-1.0.9 "default,proc-macro"
-// ryu-1.0.5
-// serde-1.0.125 "default,derive,serde_derive,std"
-// serde_derive-1.0.125 "default"
-// serde_json-1.0.64 "default,std"
-// slab-0.4.3 "default,std"
-// smallvec-1.6.1
-// syn-1.0.70 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/vm_memory/Cargo.toml b/vm_memory/Cargo.toml
index 631ad9b84..fc32d6726 100644
--- a/vm_memory/Cargo.toml
+++ b/vm_memory/Cargo.toml
@@ -2,14 +2,17 @@
name = "vm_memory"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
include = ["src/**/*", "Cargo.toml"]
[dependencies]
-cros_async = { path = "../cros_async" } # provided by ebuild
-data_model = { path = "../data_model" } # provided by ebuild
+cros_async = { path = "../cros_async" }
+data_model = { path = "../common/data_model" }
libc = "*"
-base = { path = "../base" } # provided by ebuild
+base = { path = "../base" }
bitflags = "1"
+remain = "*"
+serde = { version = "1", features = [ "derive" ] }
+thiserror = "*"
+
-[workspace]
diff --git a/vm_memory/src/guest_address.rs b/vm_memory/src/guest_address.rs
index 075cf9f8c..a3af75cc3 100644
--- a/vm_memory/src/guest_address.rs
+++ b/vm_memory/src/guest_address.rs
@@ -8,8 +8,10 @@ use std::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd};
use std::fmt::{self, Display};
use std::ops::{BitAnd, BitOr};
+use serde::{Deserialize, Serialize};
+
/// Represents an Address in the guest's memory.
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct GuestAddress(pub u64);
impl GuestAddress {
@@ -112,12 +114,15 @@ mod tests {
}
#[test]
+ #[allow(clippy::eq_op)]
+ #[allow(clippy::nonminimal_bool)]
fn cmp() {
let a = GuestAddress(0x300);
let b = GuestAddress(0x301);
assert!(a < b);
assert!(b > a);
assert!(!(a < a));
+ assert!(a >= a);
}
#[test]
diff --git a/vm_memory/src/guest_memory.rs b/vm_memory/src/guest_memory.rs
index 62ba134e7..47c3ba4fd 100644
--- a/vm_memory/src/guest_memory.rs
+++ b/vm_memory/src/guest_memory.rs
@@ -4,103 +4,148 @@
//! Track memory regions that are mapped to the guest VM.
-use std::convert::AsRef;
-use std::convert::TryFrom;
-use std::fmt::{self, Display};
+use std::convert::{AsRef, TryFrom};
+use std::fs::File;
+use std::marker::{Send, Sync};
use std::mem::size_of;
use std::result;
use std::sync::Arc;
-use crate::guest_address::GuestAddress;
use base::{pagesize, Error as SysError};
use base::{
AsRawDescriptor, AsRawDescriptors, MappedRegion, MemfdSeals, MemoryMapping,
MemoryMappingBuilder, MemoryMappingUnix, MmapError, RawDescriptor, SharedMemory,
SharedMemoryUnix,
};
+use bitflags::bitflags;
use cros_async::{mem, BackingMemory};
use data_model::volatile_memory::*;
use data_model::DataInit;
+use remain::sorted;
+use thiserror::Error;
-use bitflags::bitflags;
+use crate::guest_address::GuestAddress;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- DescriptorChainOverflow,
+ #[error("invalid guest address {0}")]
InvalidGuestAddress(GuestAddress),
+ #[error("invalid offset {0}")]
InvalidOffset(u64),
+ #[error("size {0} must not be zero")]
+ InvalidSize(usize),
+ #[error("invalid guest memory access at addr={0}: {1}")]
MemoryAccess(GuestAddress, MmapError),
+ #[error("failed to set seals on shm region: {0}")]
+ MemoryAddSealsFailed(SysError),
+ #[error("failed to create shm region")]
+ MemoryCreationFailed(SysError),
+ #[error("failed to map guest memory: {0}")]
MemoryMappingFailed(MmapError),
- MemoryRegionOverlap,
- MemoryRegionTooLarge(u64),
+ #[error("shm regions must be page aligned")]
MemoryNotAligned,
- MemoryCreationFailed(SysError),
- MemoryAddSealsFailed(SysError),
- ShortWrite { expected: usize, completed: usize },
+ #[error("memory regions overlap")]
+ MemoryRegionOverlap,
+ #[error("memory region size {0} is too large")]
+ MemoryRegionTooLarge(u128),
+ #[error("incomplete read of {completed} instead of {expected} bytes")]
ShortRead { expected: usize, completed: usize },
+ #[error("incomplete write of {completed} instead of {expected} bytes")]
+ ShortWrite { expected: usize, completed: usize },
+ #[error("DescriptorChain split is out of bounds: {0}")]
SplitOutOfBounds(usize),
+ #[error("{0}")]
VolatileMemoryAccess(VolatileMemoryError),
}
+
pub type Result<T> = result::Result<T, Error>;
-impl std::error::Error for Error {}
+bitflags! {
+ pub struct MemoryPolicy: u32 {
+ const USE_HUGEPAGES = 1;
+ }
+}
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
+/// A file-like object backing `MemoryRegion`.
+#[derive(Clone)]
+pub enum BackingObject {
+ Shm(Arc<SharedMemory>),
+ File(Arc<File>),
+}
+impl AsRawDescriptor for BackingObject {
+ fn as_raw_descriptor(&self) -> RawDescriptor {
match self {
- DescriptorChainOverflow => write!(
- f,
- "the combined length of all the buffers in a DescriptorChain is too large"
- ),
- InvalidGuestAddress(addr) => write!(f, "invalid guest address {}", addr),
- InvalidOffset(addr) => write!(f, "invalid offset {}", addr),
- MemoryAccess(addr, e) => {
- write!(f, "invalid guest memory access at addr={}: {}", addr, e)
- }
- MemoryMappingFailed(e) => write!(f, "failed to map guest memory: {}", e),
- MemoryRegionOverlap => write!(f, "memory regions overlap"),
- MemoryRegionTooLarge(size) => write!(f, "memory region size {} is too large", size),
- MemoryNotAligned => write!(f, "shm regions must be page aligned"),
- MemoryCreationFailed(_) => write!(f, "failed to create shm region"),
- MemoryAddSealsFailed(e) => write!(f, "failed to set seals on shm region: {}", e),
- ShortWrite {
- expected,
- completed,
- } => write!(
- f,
- "incomplete write of {} instead of {} bytes",
- completed, expected,
- ),
- ShortRead {
- expected,
- completed,
- } => write!(
- f,
- "incomplete read of {} instead of {} bytes",
- completed, expected,
- ),
- SplitOutOfBounds(off) => write!(f, "DescriptorChain split is out of bounds: {}", off),
- VolatileMemoryAccess(e) => e.fmt(f),
+ Self::Shm(shm) => shm.as_raw_descriptor(),
+ Self::File(f) => f.as_raw_descriptor(),
}
}
}
-bitflags! {
- pub struct MemoryPolicy: u32 {
- const USE_HUGEPAGES = 1;
+impl AsRef<dyn AsRawDescriptor + Sync + Send> for BackingObject {
+ fn as_ref(&self) -> &(dyn AsRawDescriptor + Sync + Send + 'static) {
+ match self {
+ BackingObject::Shm(shm) => shm.as_ref(),
+ BackingObject::File(f) => f.as_ref(),
+ }
}
}
-struct MemoryRegion {
+/// A regions of memory mapped memory.
+/// Holds the memory mapping with its offset in guest memory.
+/// Also holds the backing object for the mapping and the offset in that object of the mapping.
+pub struct MemoryRegion {
mapping: MemoryMapping,
guest_base: GuestAddress,
- shm_offset: u64,
- shm: Arc<SharedMemory>,
+
+ shared_obj: BackingObject,
+ obj_offset: u64,
}
impl MemoryRegion {
+ /// Creates a new MemoryRegion using the given SharedMemory object to later be attached to a VM
+ /// at `guest_base` address in the guest.
+ pub fn new_from_shm(
+ size: u64,
+ guest_base: GuestAddress,
+ offset: u64,
+ shm: Arc<SharedMemory>,
+ ) -> Result<Self> {
+ let mapping = MemoryMappingBuilder::new(size as usize)
+ .from_shared_memory(shm.as_ref())
+ .offset(offset)
+ .build()
+ .map_err(Error::MemoryMappingFailed)?;
+ Ok(MemoryRegion {
+ mapping,
+ guest_base,
+ shared_obj: BackingObject::Shm(shm),
+ obj_offset: offset,
+ })
+ }
+
+ /// Creates a new MemoryRegion using the given file to get available later at `guest_base`
+ /// address in the guest.
+ pub fn new_from_file(
+ size: u64,
+ guest_base: GuestAddress,
+ offset: u64,
+ file: Arc<File>,
+ ) -> Result<Self> {
+ let mapping = MemoryMappingBuilder::new(size as usize)
+ .from_file(&file)
+ .offset(offset)
+ .build()
+ .map_err(Error::MemoryMappingFailed)?;
+ Ok(MemoryRegion {
+ mapping,
+ guest_base,
+ shared_obj: BackingObject::File(file),
+ obj_offset: offset,
+ })
+ }
+
fn start(&self) -> GuestAddress {
self.guest_base
}
@@ -115,8 +160,8 @@ impl MemoryRegion {
}
}
-/// Tracks a memory region and where it is mapped in the guest, along with a shm
-/// fd of the underlying memory regions.
+/// Tracks memory regions and where they are mapped in the guest, along with shm
+/// fds of the underlying memory regions.
#[derive(Clone)]
pub struct GuestMemory {
regions: Arc<[MemoryRegion]>,
@@ -126,7 +171,7 @@ impl AsRawDescriptors for GuestMemory {
fn as_raw_descriptors(&self) -> Vec<RawDescriptor> {
self.regions
.iter()
- .map(|r| r.shm.as_raw_descriptor())
+ .map(|r| r.shared_obj.as_raw_descriptor())
.collect()
}
}
@@ -178,8 +223,8 @@ impl GuestMemory {
}
}
- let size =
- usize::try_from(range.1).map_err(|_| Error::MemoryRegionTooLarge(range.1))?;
+ let size = usize::try_from(range.1)
+ .map_err(|_| Error::MemoryRegionTooLarge(range.1 as u128))?;
let mapping = MemoryMappingBuilder::new(size)
.from_shared_memory(shm.as_ref())
.offset(offset)
@@ -188,8 +233,8 @@ impl GuestMemory {
regions.push(MemoryRegion {
mapping,
guest_base: range.0,
- shm_offset: offset,
- shm: Arc::clone(&shm),
+ shared_obj: BackingObject::Shm(shm.clone()),
+ obj_offset: offset,
});
offset += size as u64;
@@ -200,6 +245,34 @@ impl GuestMemory {
})
}
+ /// Creates a `GuestMemory` from a collection of MemoryRegions.
+ pub fn from_regions(mut regions: Vec<MemoryRegion>) -> Result<Self> {
+ // Sort the regions and ensure non overlap.
+ regions.sort_by(|a, b| a.guest_base.cmp(&b.guest_base));
+
+ if regions.len() > 1 {
+ let mut prev_end = regions[0]
+ .guest_base
+ .checked_add(regions[0].mapping.size() as u64)
+ .ok_or(Error::MemoryRegionOverlap)?;
+ for region in &regions[1..] {
+ if prev_end > region.guest_base {
+ return Err(Error::MemoryRegionOverlap);
+ }
+ prev_end = region
+ .guest_base
+ .checked_add(region.mapping.size() as u64)
+ .ok_or(Error::MemoryRegionTooLarge(
+ region.guest_base.0 as u128 + region.mapping.size() as u128,
+ ))?;
+ }
+ }
+
+ Ok(GuestMemory {
+ regions: Arc::from(regions),
+ })
+ }
+
/// Returns the end address of memory.
///
/// # Examples
@@ -273,9 +346,8 @@ impl GuestMemory {
for (_, region) in self.regions.iter().enumerate() {
let ret = region.mapping.use_hugepages();
- match ret {
- Err(err) => println!("Failed to enable HUGEPAGE for mapping {}", err),
- Ok(_) => (),
+ if let Err(err) = ret {
+ println!("Failed to enable HUGEPAGE for mapping {}", err);
}
}
}
@@ -288,11 +360,11 @@ impl GuestMemory {
/// * guest_addr : GuestAddress
/// * size: usize
/// * host_addr: usize
- /// * shm: SharedMemory backing for the given region
+ /// * shm: Descriptor of the backing memory region
/// * shm_offset: usize
pub fn with_regions<F, E>(&self, mut cb: F) -> result::Result<(), E>
where
- F: FnMut(usize, GuestAddress, usize, usize, &SharedMemory, u64) -> result::Result<(), E>,
+ F: FnMut(usize, GuestAddress, usize, usize, &BackingObject, u64) -> result::Result<(), E>,
{
for (index, region) in self.regions.iter().enumerate() {
cb(
@@ -300,8 +372,8 @@ impl GuestMemory {
region.start(),
region.mapping.size(),
region.mapping.as_ptr() as usize,
- region.shm.as_ref(),
- region.shm_offset,
+ &region.shared_obj,
+ region.obj_offset,
)?;
}
Ok(())
@@ -640,17 +712,66 @@ impl GuestMemory {
})
}
- /// Returns a reference to the SharedMemory region that backs the given address.
- pub fn shm_region(&self, guest_addr: GuestAddress) -> Result<&SharedMemory> {
+ /// Convert a GuestAddress into a pointer in the address space of this
+ /// process, and verify that the provided size define a valid range within
+ /// a single memory region. Similar to get_host_address(), this should only
+ /// be used for giving addresses to the kernel.
+ ///
+ /// # Arguments
+ /// * `guest_addr` - Guest address to convert.
+ /// * `size` - Size of the address range to be converted.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// # use vm_memory::{GuestAddress, GuestMemory};
+ /// # fn test_host_addr() -> Result<(), ()> {
+ /// let start_addr = GuestAddress(0x1000);
+ /// let mut gm = GuestMemory::new(&vec![(start_addr, 0x500)]).map_err(|_| ())?;
+ /// let addr = gm.get_host_address_range(GuestAddress(0x1200), 0x200).unwrap();
+ /// println!("Host address is {:p}", addr);
+ /// Ok(())
+ /// # }
+ /// ```
+ pub fn get_host_address_range(
+ &self,
+ guest_addr: GuestAddress,
+ size: usize,
+ ) -> Result<*const u8> {
+ if size == 0 {
+ return Err(Error::InvalidSize(size));
+ }
+
+ // Assume no overlap among regions
+ self.do_in_region(guest_addr, |mapping, offset, _| {
+ if mapping
+ .size()
+ .checked_sub(offset)
+ .map_or(true, |v| v < size)
+ {
+ return Err(Error::InvalidGuestAddress(guest_addr));
+ }
+
+ // This is safe; `do_in_region` already checks that offset is in
+ // bounds.
+ Ok(unsafe { mapping.as_ptr().add(offset) } as *const u8)
+ })
+ }
+
+ /// Returns a reference to the region that backs the given address.
+ pub fn shm_region(
+ &self,
+ guest_addr: GuestAddress,
+ ) -> Result<&(dyn AsRawDescriptor + Send + Sync)> {
self.regions
.iter()
.find(|region| region.contains(guest_addr))
.ok_or(Error::InvalidGuestAddress(guest_addr))
- .map(|region| region.shm.as_ref())
+ .map(|region| region.shared_obj.as_ref())
}
/// Returns the region that contains the memory at `offset` from the base of guest memory.
- pub fn offset_region(&self, offset: u64) -> Result<&SharedMemory> {
+ pub fn offset_region(&self, offset: u64) -> Result<&(dyn AsRawDescriptor + Send + Sync)> {
self.shm_region(
self.checked_offset(self.regions[0].guest_base, offset)
.ok_or(Error::InvalidOffset(offset))?,
@@ -678,7 +799,7 @@ impl GuestMemory {
cb(
&region.mapping,
guest_addr.offset_from(region.start()) as usize,
- region.shm_offset,
+ region.obj_offset,
)
})
}
@@ -711,7 +832,7 @@ impl GuestMemory {
.iter()
.find(|region| region.contains(guest_addr))
.ok_or(Error::InvalidGuestAddress(guest_addr))
- .map(|region| region.shm_offset + guest_addr.offset_from(region.start()))
+ .map(|region| region.obj_offset + guest_addr.offset_from(region.start()))
}
}
@@ -759,23 +880,14 @@ mod tests {
let start_addr1 = GuestAddress(0x0);
let start_addr2 = GuestAddress(0x4000);
let gm = GuestMemory::new(&[(start_addr1, 0x2000), (start_addr2, 0x2000)]).unwrap();
- assert_eq!(gm.address_in_range(GuestAddress(0x1000)), true);
- assert_eq!(gm.address_in_range(GuestAddress(0x3000)), false);
- assert_eq!(gm.address_in_range(GuestAddress(0x5000)), true);
- assert_eq!(gm.address_in_range(GuestAddress(0x6000)), false);
- assert_eq!(gm.address_in_range(GuestAddress(0x6000)), false);
- assert_eq!(
- gm.range_overlap(GuestAddress(0x1000), GuestAddress(0x3000)),
- true
- );
- assert_eq!(
- gm.range_overlap(GuestAddress(0x3000), GuestAddress(0x4000)),
- false
- );
- assert_eq!(
- gm.range_overlap(GuestAddress(0x3000), GuestAddress(0x7000)),
- true
- );
+ assert!(gm.address_in_range(GuestAddress(0x1000)));
+ assert!(!gm.address_in_range(GuestAddress(0x3000)));
+ assert!(gm.address_in_range(GuestAddress(0x5000)));
+ assert!(!gm.address_in_range(GuestAddress(0x6000)));
+ assert!(!gm.address_in_range(GuestAddress(0x6000)));
+ assert!(gm.range_overlap(GuestAddress(0x1000), GuestAddress(0x3000)));
+ assert!(!gm.range_overlap(GuestAddress(0x3000), GuestAddress(0x4000)));
+ assert!(gm.range_overlap(GuestAddress(0x3000), GuestAddress(0x7000)));
assert!(gm.checked_offset(GuestAddress(0x1000), 0x1000).is_none());
assert!(gm.checked_offset(GuestAddress(0x5000), 0x800).is_some());
assert!(gm.checked_offset(GuestAddress(0x5000), 0x1000).is_none());
@@ -874,6 +986,31 @@ mod tests {
}
#[test]
+ fn guest_to_host_range() {
+ let start_addr1 = GuestAddress(0x0);
+ let start_addr2 = GuestAddress(0x1000);
+ let mem = GuestMemory::new(&[(start_addr1, 0x1000), (start_addr2, 0x4000)]).unwrap();
+
+ // Verify the host addresses match what we expect from the mappings.
+ let addr1_base = get_mapping(&mem, start_addr1).unwrap();
+ let addr2_base = get_mapping(&mem, start_addr2).unwrap();
+ let host_addr1 = mem.get_host_address_range(start_addr1, 0x1000).unwrap();
+ let host_addr2 = mem.get_host_address_range(start_addr2, 0x1000).unwrap();
+ assert_eq!(host_addr1, addr1_base);
+ assert_eq!(host_addr2, addr2_base);
+
+ let host_addr3 = mem.get_host_address_range(start_addr2, 0x2000).unwrap();
+ assert_eq!(host_addr3, addr2_base);
+
+ // Check that a valid guest address with an invalid size returns an error.
+ assert!(mem.get_host_address_range(start_addr1, 0x2000).is_err());
+
+ // Check that a bad address returns an error.
+ let bad_addr = GuestAddress(0x123456);
+ assert!(mem.get_host_address_range(bad_addr, 0x1000).is_err());
+ }
+
+ #[test]
fn shm_offset() {
if !kernel_has_memfd() {
return;
@@ -890,10 +1027,16 @@ mod tests {
gm.write_obj_at_addr(0x0420u16, GuestAddress(0x10000))
.unwrap();
- let _ = gm.with_regions::<_, ()>(|index, _, size, _, shm, shm_offset| {
+ let _ = gm.with_regions::<_, ()>(|index, _, size, _, obj, offset| {
+ let shm = match obj {
+ BackingObject::Shm(s) => s,
+ _ => {
+ panic!("backing object isn't SharedMemory");
+ }
+ };
let mmap = MemoryMappingBuilder::new(size)
.from_shared_memory(shm)
- .offset(shm_offset)
+ .offset(offset)
.build()
.unwrap();
diff --git a/win_util/Cargo.toml b/win_util/Cargo.toml
new file mode 100644
index 000000000..70b412e42
--- /dev/null
+++ b/win_util/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "win_util"
+version = "0.1.0"
+authors = ["The Chromium OS Authors"]
+edition = "2018"
+
+[dependencies]
+anyhow = "*"
+winapi = { version = "*", features = ["everything", "std", "impl-default"] }
+libc = "*"
+windows = "0.10.0"
+lazy_static = "*"
+
+[build-dependencies]
+windows = "0.10.0"
+
+[workspace]
diff --git a/win_util/build.rs b/win_util/build.rs
new file mode 100644
index 000000000..ac6e8dd9d
--- /dev/null
+++ b/win_util/build.rs
@@ -0,0 +1,13 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Do nothing on unix as the library is windows only.
+#[cfg(not(windows))]
+fn main() {}
+
+#[cfg(windows)]
+fn main() {
+ #[cfg(target_env = "msvc")]
+ windows::build!(Windows::Win32::Globalization::ImmDisableIME)
+}
diff --git a/win_util/cargo2android.json b/win_util/cargo2android.json
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/win_util/cargo2android.json
@@ -0,0 +1 @@
+{} \ No newline at end of file
diff --git a/win_util/src/large_integer.rs b/win_util/src/large_integer.rs
new file mode 100644
index 000000000..3126b806e
--- /dev/null
+++ b/win_util/src/large_integer.rs
@@ -0,0 +1,32 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::ops::Deref;
+use winapi::um::winnt::LARGE_INTEGER;
+
+pub struct LargeInteger {
+ large_integer: LARGE_INTEGER,
+}
+
+impl LargeInteger {
+ pub fn new(value: i64) -> LargeInteger {
+ // Safe because we are zero-initializing a struct with only primitive member fields.
+ let mut large_integer_val: LARGE_INTEGER = unsafe { std::mem::zeroed() };
+ // Safe because we uniquely own this variable
+ let large_integer_val_mut: &mut i64 = unsafe { large_integer_val.QuadPart_mut() };
+ *large_integer_val_mut = value;
+
+ LargeInteger {
+ large_integer: large_integer_val,
+ }
+ }
+}
+
+impl Deref for LargeInteger {
+ type Target = LARGE_INTEGER;
+
+ fn deref(&self) -> &Self::Target {
+ &self.large_integer
+ }
+}
diff --git a/win_util/src/lib.rs b/win_util/src/lib.rs
new file mode 100644
index 000000000..40ba486ca
--- /dev/null
+++ b/win_util/src/lib.rs
@@ -0,0 +1,317 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Library for common Windows-specfic utilities
+//!
+//! TODO(b/223723424) win_util should be merged into win_sys_util or part of the
+//! base.
+
+// Do nothing on unix as win_util is windows only.
+#![cfg(windows)]
+
+mod large_integer;
+pub use crate::large_integer::*;
+
+mod security_attributes;
+pub use crate::security_attributes::*;
+
+use libc::c_ulong;
+use std::ffi::{CString, OsStr};
+use std::iter::once;
+use std::mem::MaybeUninit;
+use std::os::windows::ffi::OsStrExt;
+use std::os::windows::io::RawHandle;
+use std::slice;
+use std::sync::Once;
+use std::{io, ptr};
+use winapi::shared::minwindef::{DWORD, FALSE, TRUE};
+use winapi::um::handleapi::{
+ CloseHandle, DuplicateHandle, SetHandleInformation, INVALID_HANDLE_VALUE,
+};
+use winapi::um::minwinbase::STILL_ACTIVE;
+use winapi::um::processthreadsapi::{
+ GetCurrentProcess, GetExitCodeProcess, OpenProcess, ResumeThread,
+};
+use winapi::um::sysinfoapi::{GetNativeSystemInfo, SYSTEM_INFO};
+use winapi::um::winbase::{CreateFileMappingA, HANDLE_FLAG_INHERIT};
+use winapi::um::winnt::{DUPLICATE_SAME_ACCESS, HRESULT, PROCESS_DUP_HANDLE};
+
+#[macro_export]
+macro_rules! syscall_bail {
+ ($details:expr) => {
+ ::anyhow::bail!(
+ "{} (Error code {})",
+ $details,
+ ::winapi::um::errhandlingapi::GetLastError()
+ )
+ };
+}
+
+/// Returns the lower 32 bits of a u64 as a u32 (c_ulong/DWORD)
+pub fn get_low_order(number: u64) -> c_ulong {
+ (number & (u32::max_value() as u64)) as c_ulong
+}
+
+/// Returns the upper 32 bits of a u64 as a u32 (c_ulong/DWORD)
+pub fn get_high_order(number: u64) -> c_ulong {
+ (number >> 32) as c_ulong
+}
+
+static INIT_NATIVE_SYSTEM_INFO: Once = Once::new();
+static mut NATIVE_SYSTEM_INFO: MaybeUninit<SYSTEM_INFO> = MaybeUninit::uninit();
+
+pub fn pagesize() -> usize {
+ get_native_system_info().dwPageSize as usize
+}
+
+pub fn allocation_granularity() -> u64 {
+ get_native_system_info().dwAllocationGranularity as u64
+}
+
+pub fn number_of_processors() -> usize {
+ get_native_system_info().dwNumberOfProcessors as usize
+}
+
+fn get_native_system_info() -> SYSTEM_INFO {
+ INIT_NATIVE_SYSTEM_INFO.call_once(|| unsafe {
+ // Safe because this is a universally available call on modern Windows systems.
+ GetNativeSystemInfo(NATIVE_SYSTEM_INFO.as_mut_ptr());
+ });
+ // Safe because it is guaranteed to be initialized by GetNativeSystemInfo above.
+ unsafe { NATIVE_SYSTEM_INFO.assume_init() }
+}
+
+pub fn win32_string(value: &str) -> CString {
+ CString::new(value).unwrap()
+}
+
+pub fn win32_wide_string(value: &str) -> Vec<u16> {
+ OsStr::new(value).encode_wide().chain(once(0)).collect()
+}
+
+/// Returns the length, in u16 words (*not* UTF-16 chars), of a null-terminated u16 string.
+/// Safe when `wide` is non-null and points to a u16 string terminated by a null character.
+unsafe fn strlen_ptr_u16(wide: *const u16) -> usize {
+ assert!(!wide.is_null());
+ for i in 0.. {
+ if *wide.offset(i) == 0 {
+ return i as usize;
+ }
+ }
+ unreachable!()
+}
+
+/// Converts a UTF-16 null-terminated string to an owned `String`. Any invalid code points are
+/// converted to `std::char::REPLACEMENT_CHARACTER`.
+/// Safe when `wide` is non-null and points to a u16 string terminated by a null character.
+pub unsafe fn from_ptr_win32_wide_string(wide: *const u16) -> String {
+ assert!(!wide.is_null());
+ let len = strlen_ptr_u16(wide);
+ let slice = slice::from_raw_parts(wide, len);
+ String::from_utf16_lossy(slice)
+}
+
+pub fn duplicate_handle_with_target_pid(hndl: RawHandle, target_pid: u32) -> io::Result<RawHandle> {
+ // Safe because caller will guarentee `hndl` and `target_pid` are valid and won't be dropped.
+ unsafe {
+ let target_process_handle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, target_pid);
+ if target_process_handle.is_null() {
+ return Err(io::Error::last_os_error());
+ }
+ let result = duplicate_handle_with_target_handle(hndl, target_process_handle);
+ CloseHandle(target_process_handle);
+ result
+ }
+}
+
+pub fn duplicate_handle_from_source_process(
+ source_process_handle: RawHandle,
+ hndl: RawHandle,
+ target_process_handle: RawHandle,
+) -> io::Result<RawHandle> {
+ // Safe because:
+ // 1. We are checking the return code
+ // 2. new_handle_ptr points to a valid location on the stack
+ // 3. Caller guarantees hndl is a real valid handle.
+ unsafe {
+ let mut new_handle: RawHandle = ptr::null_mut();
+ let success_flag = DuplicateHandle(
+ /* hSourceProcessHandle= */ source_process_handle,
+ /* hSourceHandle= */ hndl,
+ /* hTargetProcessHandle= */ target_process_handle,
+ /* lpTargetHandle= */ &mut new_handle,
+ /* dwDesiredAccess= */ 0,
+ /* bInheritHandle= */ TRUE,
+ /* dwOptions= */ DUPLICATE_SAME_ACCESS,
+ );
+
+ if success_flag == FALSE {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(new_handle)
+ }
+ }
+}
+
+fn duplicate_handle_with_target_handle(
+ hndl: RawHandle,
+ target_process_handle: RawHandle,
+) -> io::Result<RawHandle> {
+ // Safe because `GetCurrentProcess` just gets the current process handle.
+ duplicate_handle_from_source_process(
+ unsafe { GetCurrentProcess() },
+ hndl,
+ target_process_handle,
+ )
+}
+
+pub fn duplicate_handle(hndl: RawHandle) -> io::Result<RawHandle> {
+ // Safe because `GetCurrentProcess` just gets the current process handle.
+ duplicate_handle_with_target_handle(hndl, unsafe { GetCurrentProcess() })
+}
+
+/// Sets whether a handle is inheritable. Note that this only works on some types of handles,
+/// such as files, pipes, etc. See
+/// https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-sethandleinformation#parameters
+/// for further details.
+pub fn set_handle_inheritance(hndl: RawHandle, inheritable: bool) -> io::Result<()> {
+ // Safe because even if hndl is invalid, no unsafe memory access will result.
+ let res = unsafe {
+ SetHandleInformation(
+ hndl,
+ HANDLE_FLAG_INHERIT,
+ if inheritable { HANDLE_FLAG_INHERIT } else { 0 },
+ )
+ };
+ if res == 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+}
+
+/// Rusty version of CreateFileMappingA.
+///
+/// # Safety
+/// If provided, the caller must ensure hndl is valid.
+pub unsafe fn create_file_mapping(
+ handle: Option<RawHandle>,
+ size: u64,
+ protection: DWORD,
+ name: Option<&str>,
+) -> io::Result<RawHandle> {
+ let name_cstr = name.map(|s| CString::new(s).unwrap());
+ let name = name_cstr.map(|n| n.as_ptr()).unwrap_or(ptr::null_mut());
+
+ // Safe because:
+ // 1. The caller guarantees handle is valid (if provided).
+ // 2. The C string is guaranteed valid.
+ // 3. We check the results of the call.
+ let mapping_handle = CreateFileMappingA(
+ match handle {
+ Some(h) => h,
+ None => INVALID_HANDLE_VALUE,
+ },
+ SecurityAttributes::new_with_security_descriptor(
+ SelfRelativeSecurityDescriptor::get_singleton(),
+ /* inherit= */ true,
+ )
+ .as_mut(),
+ protection,
+ get_high_order(size),
+ get_low_order(size),
+ name,
+ );
+
+ if mapping_handle.is_null() {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(mapping_handle)
+ }
+}
+
+#[derive(PartialEq)]
+pub enum ThreadState {
+ // The specified thread was not suspended.
+ NotSuspended,
+ // The specified thread was suspended, but was restarted.
+ Restarted,
+ // The specified thread is still suspended.
+ StillSuspended,
+}
+
+/// Decrements a thread's suspend count. When the suspend count reaches 0, the
+/// thread is resumed. Returned `ThreadState` indicates whether the thread was
+/// resumed.
+pub fn resume_thread(handle: RawHandle) -> io::Result<ThreadState> {
+ // Safe as even an invalid handle should cause no adverse effects.
+ match unsafe { ResumeThread(handle) } {
+ u32::MAX => Err(io::Error::last_os_error()),
+ 0 => Ok(ThreadState::NotSuspended),
+ 1 => Ok(ThreadState::Restarted),
+ _ => Ok(ThreadState::StillSuspended),
+ }
+}
+
+/// Retrieves the termination status of the specified process.
+pub fn get_exit_code_process(handle: RawHandle) -> io::Result<Option<DWORD>> {
+ let mut exit_code: DWORD = 0;
+ // Safe as even an invalid handle should cause no adverse effects.
+ match unsafe { GetExitCodeProcess(handle, &mut exit_code) } {
+ 0 => Err(io::Error::last_os_error()),
+ _ => {
+ if exit_code == STILL_ACTIVE {
+ Ok(None)
+ } else {
+ Ok(Some(exit_code))
+ }
+ }
+ }
+}
+
+pub type HResult<T> = Result<T, HRESULT>;
+
+// windows-rs bindings
+#[cfg(target_env = "msvc")]
+mod bindings {
+ ::windows::include_bindings!();
+}
+#[cfg(target_env = "msvc")]
+pub use bindings::Windows::Win32::Globalization::ImmDisableIME;
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn high_low_order_utilities() {
+ let some_number: u64 = 0xA3200500FFB40123;
+ let high_order: u64 = get_high_order(some_number).into();
+ let low_order: u64 = get_low_order(some_number).into();
+ assert_eq!(some_number, (high_order << 32) + low_order);
+ }
+
+ #[test]
+ fn strlen() {
+ let u16s = [0];
+ assert_eq!(unsafe { strlen_ptr_u16((&u16s).as_ptr()) }, 0);
+ let u16s = [
+ 0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0xDD1E, 0x0069, 0x0063, 0xD834, 0,
+ ];
+ assert_eq!(unsafe { strlen_ptr_u16((&u16s).as_ptr()) }, 9);
+ }
+
+ #[test]
+ fn from_win32_wide_string() {
+ let u16s = [0];
+ assert_eq!(unsafe { from_ptr_win32_wide_string((&u16s).as_ptr()) }, "");
+ let u16s = [
+ 0xD834, 0xDD1E, 0x006d, 0x0075, 0x0073, 0xDD1E, 0x0069, 0x0063, 0xD834, 0,
+ ];
+ assert_eq!(
+ unsafe { from_ptr_win32_wide_string((&u16s).as_ptr()) },
+ "𝄞mus�ic�"
+ );
+ }
+}
diff --git a/win_util/src/security_attributes.rs b/win_util/src/security_attributes.rs
new file mode 100644
index 000000000..9003ff90c
--- /dev/null
+++ b/win_util/src/security_attributes.rs
@@ -0,0 +1,458 @@
+// Copyright 2022 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::alloc::{alloc_zeroed, dealloc, handle_alloc_error, Layout};
+use std::convert::{TryFrom, TryInto};
+use std::mem::size_of;
+use std::os::windows::io::RawHandle;
+use std::{io, ptr};
+use winapi::shared::minwindef::{FALSE, HLOCAL, LPDWORD, LPVOID, TRUE};
+use winapi::shared::winerror::{ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS};
+use winapi::um::accctrl::{
+ EXPLICIT_ACCESS_A, NO_INHERITANCE, NO_MULTIPLE_TRUSTEE, PEXPLICIT_ACCESSA, SET_ACCESS,
+ TRUSTEE_A, TRUSTEE_IS_SID, TRUSTEE_IS_USER,
+};
+use winapi::um::aclapi::SetEntriesInAclA;
+use winapi::um::handleapi::CloseHandle;
+use winapi::um::minwinbase::SECURITY_ATTRIBUTES;
+use winapi::um::processthreadsapi::{GetCurrentProcess, OpenProcessToken};
+use winapi::um::securitybaseapi::{
+ GetTokenInformation, InitializeSecurityDescriptor, MakeSelfRelativeSD,
+ SetSecurityDescriptorDacl,
+};
+use winapi::um::winbase::LocalFree;
+use winapi::um::winnt::{
+ TokenUser, ACL, GENERIC_ALL, PACL, PSECURITY_DESCRIPTOR, SECURITY_DESCRIPTOR,
+ SECURITY_DESCRIPTOR_REVISION, TOKEN_ALL_ACCESS, TOKEN_INFORMATION_CLASS, TOKEN_USER,
+};
+
+use lazy_static::lazy_static;
+
+/// Struct for wrapping `SECURITY_ATTRIBUTES` and `SECURITY_DESCRIPTOR`.
+pub struct SecurityAttributes<T: SecurityDescriptor> {
+ // The security descriptor shouldn't move, as it will be referenced by the
+ // security attributes. We already limit what can be done with the security
+ // attributes by only providing a reference to it, but we also want to
+ // ensure the security descriptor pointer remains valid even if this struct
+ // moves around.
+ _security_descriptor: T,
+ security_attributes: SECURITY_ATTRIBUTES,
+}
+
+impl<T: SecurityDescriptor> SecurityAttributes<T> {
+ /// Create a new `SecurityAttributes` struct with the provided `security_descriptor`.
+ pub fn new_with_security_descriptor(security_descriptor: T, inherit: bool) -> Self {
+ let sd = security_descriptor.security_descriptor();
+ let security_attributes = SECURITY_ATTRIBUTES {
+ nLength: size_of::<SECURITY_ATTRIBUTES>() as u32,
+ lpSecurityDescriptor: sd as PSECURITY_DESCRIPTOR,
+ bInheritHandle: if inherit { TRUE } else { FALSE },
+ };
+ SecurityAttributes {
+ _security_descriptor: security_descriptor,
+ security_attributes,
+ }
+ }
+}
+
+impl SecurityAttributes<SelfRelativeSecurityDescriptor> {
+ /// Create a new `SecurityAttributes` struct. This struct will have a
+ /// `SECURITY_DESCRIPTOR` that allows full access (`GENERIC_ALL`) to only
+ /// the current user.
+ pub fn new(inherit: bool) -> io::Result<Self> {
+ Ok(Self::new_with_security_descriptor(
+ SelfRelativeSecurityDescriptor::new()?,
+ inherit,
+ ))
+ }
+}
+
+impl<T: SecurityDescriptor> AsRef<SECURITY_ATTRIBUTES> for SecurityAttributes<T> {
+ fn as_ref(&self) -> &SECURITY_ATTRIBUTES {
+ &self.security_attributes
+ }
+}
+
+impl<T: SecurityDescriptor> AsMut<SECURITY_ATTRIBUTES> for SecurityAttributes<T> {
+ fn as_mut(&mut self) -> &mut SECURITY_ATTRIBUTES {
+ &mut self.security_attributes
+ }
+}
+
+trait TokenClass {
+ fn class() -> TOKEN_INFORMATION_CLASS;
+}
+
+impl TokenClass for TOKEN_USER {
+ fn class() -> TOKEN_INFORMATION_CLASS {
+ TokenUser
+ }
+}
+
+struct TokenInformation<T> {
+ token_info: *mut T,
+ layout: Layout,
+}
+
+impl<T: TokenClass> TokenInformation<T> {
+ fn new(mut token: ProcessToken) -> io::Result<Self> {
+ let token_handle = token.get();
+ // Retrieve the size of the struct.
+ let mut size: u32 = 0;
+ // Safe because size is valid, and TokenInformation is optional and allowed to be null.
+ if unsafe {
+ // The idiomatic usage of GetTokenInformation() requires two calls
+ // to the function: the first to get the length of the data that the
+ // function would return, and the second to fetch the data.
+ GetTokenInformation(
+ /* TokenHandle= */ token_handle,
+ /* TokenInformationClass= */ T::class(),
+ /* TokenInformation= */ ptr::null_mut(),
+ /* TokenInformationLength= */ 0,
+ /* ReturnLength= */ &mut size,
+ ) == 0
+ } {
+ const INSUFFICIENT_BUFFER: i32 = ERROR_INSUFFICIENT_BUFFER as i32;
+ match io::Error::last_os_error().raw_os_error() {
+ Some(INSUFFICIENT_BUFFER) => {
+ // Despite returning failure, the function will fill in the
+ // expected buffer length into the ReturnLength parameter.
+ // It may fail in other ways (e.g. if an invalid TokenHandle
+ // is provided), so we check that we receive the expected
+ // error code before assuming that we received a valid
+ // ReturnLength. In this case, we can ignore the error.
+ }
+ _ => return Err(io::Error::last_os_error()),
+ };
+ }
+
+ // size must be > 0. 0-sized layouts break alloc()'s assumptions.
+ assert!(size > 0, "Unable to get size of token information");
+
+ // Since we don't statically know the full size of the struct, we
+ // allocate memory for it based on the previous call, aligned to pointer
+ // size.
+ let layout = Layout::from_size_align(size as usize, size_of::<LPVOID>())
+ .expect("Failed to create layout");
+ assert!(layout.size() > 0, "Failed to create valid layout");
+ // Safe as we assert that layout's size is non-zero.
+ let token_info = unsafe { alloc_zeroed(layout) } as *mut T;
+ if token_info.is_null() {
+ handle_alloc_error(layout);
+ }
+
+ let token_info = TokenInformation::<T> { token_info, layout };
+
+ // Safe because token_user and size are valid.
+ if unsafe {
+ GetTokenInformation(
+ /* TokenHandle= */ token_handle,
+ /* TokenInformationClass= */ T::class(),
+ /* TokenInformation= */ token_info.token_info as LPVOID,
+ /* TokenInformationLength= */ size,
+ /* ReturnLength= */ &mut size,
+ ) == 0
+ } {
+ return Err(io::Error::last_os_error());
+ }
+
+ Ok(token_info)
+ }
+}
+
+impl<T> AsRef<T> for TokenInformation<T> {
+ fn as_ref(&self) -> &T {
+ // Safe because the underlying pointer is guaranteed to be properly
+ // aligned, dereferenceable, and point to a valid T. The underlying
+ // value will not be modified through the pointer and can only be
+ // accessed through these returned references.
+ unsafe { &*self.token_info }
+ }
+}
+
+impl<T> AsMut<T> for TokenInformation<T> {
+ fn as_mut(&mut self) -> &mut T {
+ // Safe because the underlying pointer is guaranteed to be properly
+ // aligned, dereferenceable, and point to a valid T. The underlying
+ // value will not be modified through the pointer and can only be
+ // accessed through these returned references.
+ unsafe { &mut *self.token_info }
+ }
+}
+
+impl<T> Drop for TokenInformation<T> {
+ fn drop(&mut self) {
+ // Safe because we ensure the pointer is valid in the constructor, and
+ // we are using the same layout struct as during the allocation.
+ unsafe { dealloc(self.token_info as *mut u8, self.layout) }
+ }
+}
+
+struct ProcessToken {
+ token: RawHandle,
+}
+
+impl ProcessToken {
+ fn new() -> io::Result<Self> {
+ let mut token: RawHandle = ptr::null_mut();
+
+ // Safe because token is valid.
+ if unsafe {
+ OpenProcessToken(
+ /* ProcessHandle= */ GetCurrentProcess(),
+ /* DesiredAccess= */ TOKEN_ALL_ACCESS,
+ /* TokenHandle= */ &mut token,
+ ) == 0
+ } {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(ProcessToken { token })
+ }
+
+ fn get(&mut self) -> RawHandle {
+ self.token
+ }
+}
+
+impl Drop for ProcessToken {
+ fn drop(&mut self) {
+ // Safe as token is valid, but the call should be safe regardless.
+ unsafe {
+ CloseHandle(self.token);
+ }
+ }
+}
+
+pub trait SecurityDescriptor {
+ fn security_descriptor(&self) -> *const SECURITY_DESCRIPTOR;
+}
+
+pub struct AbsoluteSecurityDescriptor {
+ descriptor: SECURITY_DESCRIPTOR,
+ acl: *mut ACL,
+}
+
+impl AbsoluteSecurityDescriptor {
+ /// Creates a `SECURITY_DESCRIPTOR` struct which gives full access rights
+ /// (`GENERIC_ALL`) to only the current user.
+ fn new() -> io::Result<AbsoluteSecurityDescriptor> {
+ let token = ProcessToken::new()?;
+ let token_user = TokenInformation::<TOKEN_USER>::new(token)?;
+ let sid = token_user.as_ref().User.Sid;
+
+ let mut ea = EXPLICIT_ACCESS_A {
+ grfAccessPermissions: GENERIC_ALL,
+ grfAccessMode: SET_ACCESS,
+ grfInheritance: NO_INHERITANCE,
+ Trustee: TRUSTEE_A {
+ TrusteeForm: TRUSTEE_IS_SID,
+ TrusteeType: TRUSTEE_IS_USER,
+ ptstrName: sid as *mut i8,
+ pMultipleTrustee: ptr::null_mut(),
+ MultipleTrusteeOperation: NO_MULTIPLE_TRUSTEE,
+ },
+ };
+
+ let mut security_descriptor: std::mem::MaybeUninit<AbsoluteSecurityDescriptor> =
+ std::mem::MaybeUninit::uninit();
+
+ let ptr = security_descriptor.as_mut_ptr();
+
+ // Safe because security_descriptor is valid but uninitialized, and
+ // InitializeSecurityDescriptor will initialize it.
+ if unsafe {
+ InitializeSecurityDescriptor(
+ /* pSecurityDescriptor= */
+ ptr::addr_of_mut!((*ptr).descriptor) as PSECURITY_DESCRIPTOR,
+ /* dwRevision= */ SECURITY_DESCRIPTOR_REVISION,
+ ) == 0
+ } {
+ return Err(io::Error::last_os_error());
+ }
+
+ // Safe because ea and acl are valid and OldAcl is allowed to be null.
+ if unsafe {
+ SetEntriesInAclA(
+ /* cCountOfExplicitEntries= */ 1,
+ /* pListOfExplicitEntries= */ &mut ea as PEXPLICIT_ACCESSA,
+ /* OldAcl= */ ptr::null_mut(),
+ /* NewAcl= */
+ ptr::addr_of_mut!((*ptr).acl) as *mut PACL,
+ )
+ } != ERROR_SUCCESS
+ {
+ return Err(io::Error::last_os_error());
+ }
+
+ // Safe because security_descriptor is valid and initialized after
+ // InitializeSecurityDescriptor() and SetEntriesInAclA().
+ let mut security_descriptor = unsafe { security_descriptor.assume_init() };
+ let sd = &mut security_descriptor.descriptor as *mut SECURITY_DESCRIPTOR;
+
+ // Safe because the descriptor is valid, and acl is valid after SetEntriesInAclA()
+ if unsafe {
+ SetSecurityDescriptorDacl(
+ /* pSecurityDescriptor= */ sd as PSECURITY_DESCRIPTOR,
+ /* bDaclPresent= */ TRUE,
+ /* pDacl= */ security_descriptor.acl,
+ /* bDaclDefaulted= */ FALSE,
+ ) == 0
+ } {
+ return Err(io::Error::last_os_error());
+ }
+
+ Ok(security_descriptor)
+ }
+}
+
+impl SecurityDescriptor for AbsoluteSecurityDescriptor {
+ fn security_descriptor(&self) -> *const SECURITY_DESCRIPTOR {
+ &self.descriptor as *const SECURITY_DESCRIPTOR
+ }
+}
+
+impl Drop for AbsoluteSecurityDescriptor {
+ fn drop(&mut self) {
+ // Safe because we guarantee that on creation acl is initialized to a
+ // pointer that can be freed.
+ unsafe { LocalFree(self.acl as HLOCAL) };
+ }
+}
+
+pub struct SelfRelativeSecurityDescriptor {
+ descriptor: *mut SECURITY_DESCRIPTOR,
+ layout: Layout,
+}
+
+impl Drop for SelfRelativeSecurityDescriptor {
+ fn drop(&mut self) {
+ unsafe { dealloc(self.descriptor as *mut u8, self.layout) }
+ }
+}
+
+impl Clone for SelfRelativeSecurityDescriptor {
+ fn clone(&self) -> Self {
+ // Safe because we know that the layout's size is non-zero.
+ let descriptor = unsafe { alloc_zeroed(self.layout) } as *mut SECURITY_DESCRIPTOR;
+ if descriptor.is_null() {
+ handle_alloc_error(self.layout);
+ }
+ let sd = SelfRelativeSecurityDescriptor {
+ descriptor,
+ layout: self.layout,
+ };
+ // Safe because:
+ // * `src` is at least `count` bytes, as it was allocated using the above layout.
+ // * `dst` is at least `count` bytes, as we just allocated it using the above layout.
+ // * `src` and `dst` are aligned according to the layout, and we are copying byte-wise.
+ // * `src` and `dst` do not overlap, as we just allocated new memory for `dst`.
+ unsafe {
+ std::ptr::copy_nonoverlapping::<u8>(
+ /* src= */ self.descriptor as *const u8,
+ /* dst= */ sd.descriptor as *mut u8,
+ /* count= */ self.layout.size(),
+ )
+ };
+ sd
+ }
+}
+
+impl TryFrom<AbsoluteSecurityDescriptor> for SelfRelativeSecurityDescriptor {
+ type Error = io::Error;
+
+ fn try_from(sd: AbsoluteSecurityDescriptor) -> io::Result<Self> {
+ let mut size: u32 = 0;
+ let descriptor = &sd.descriptor as *const SECURITY_DESCRIPTOR;
+
+ // Safe because descriptor and size are valid, and pSelfRelativeSD is
+ // optional and allowed to be null.
+ if unsafe {
+ MakeSelfRelativeSD(
+ /* pAbsoluteSD= */ descriptor as PSECURITY_DESCRIPTOR,
+ /* pSelfRelativeSD= */ ptr::null_mut(),
+ /* lpdwBufferLength= */ &mut size as LPDWORD,
+ )
+ } == 0
+ {
+ const INSUFFICIENT_BUFFER: i32 = ERROR_INSUFFICIENT_BUFFER as i32;
+ match io::Error::last_os_error().raw_os_error() {
+ Some(INSUFFICIENT_BUFFER) => {
+ // Despite returning failure, the function will fill in the
+ // expected buffer length into the lpdwBufferLength parameter.
+ // It may fail in other ways (e.g. if an invalid pAbsoluteSD
+ // is provided), so we check that we receive the expected
+ // error code before assuming that we received a valid
+ // lpdwBufferLength. In this case, we can ignore the error.
+ }
+ _ => return Err(io::Error::last_os_error()),
+ }
+ }
+ // size must be > 0. 0-sized layouts break alloc()'s assumptions.
+ assert!(size > 0, "Unable to get size of self-relative SD");
+
+ // Since we don't statically know the full size of the struct, we
+ // allocate memory for it based on the previous call, aligned to pointer
+ // size.
+ let layout = Layout::from_size_align(size as usize, size_of::<LPVOID>())
+ .expect("Failed to create layout");
+ assert!(layout.size() > 0, "Failed to create valid layout");
+ // Safe as we assert that layout's size is non-zero.
+ let self_relative_sd = unsafe { alloc_zeroed(layout) } as *mut SECURITY_DESCRIPTOR;
+ if self_relative_sd.is_null() {
+ handle_alloc_error(layout);
+ }
+
+ let self_relative_sd = SelfRelativeSecurityDescriptor {
+ descriptor: self_relative_sd,
+ layout,
+ };
+
+ // Safe because descriptor is valid, the newly allocated
+ // self_relative_sd descriptor is valid, and size is valid.
+ if unsafe {
+ MakeSelfRelativeSD(
+ /* pAbsoluteSD= */ descriptor as PSECURITY_DESCRIPTOR,
+ /* pSelfRelativeSD= */ self_relative_sd.descriptor as PSECURITY_DESCRIPTOR,
+ /* lpdwBufferLength= */ &mut size as LPDWORD,
+ )
+ } == 0
+ {
+ return Err(io::Error::last_os_error());
+ }
+
+ Ok(self_relative_sd)
+ }
+}
+
+lazy_static! {
+ static ref DEFAULT_SECURITY_DESCRIPTOR: SelfRelativeSecurityDescriptor =
+ SelfRelativeSecurityDescriptor::new().expect("Failed to create security descriptor");
+}
+
+impl SelfRelativeSecurityDescriptor {
+ /// Creates a `SECURITY_DESCRIPTOR` struct which gives full access rights
+ /// (`GENERIC_ALL`) to only the current user.
+ fn new() -> io::Result<Self> {
+ AbsoluteSecurityDescriptor::new()?.try_into()
+ }
+
+ /// Gets a copy of a singleton `SelfRelativeSecurityDescriptor`.
+ pub fn get_singleton() -> SelfRelativeSecurityDescriptor {
+ DEFAULT_SECURITY_DESCRIPTOR.clone()
+ }
+}
+
+impl SecurityDescriptor for SelfRelativeSecurityDescriptor {
+ fn security_descriptor(&self) -> *const SECURITY_DESCRIPTOR {
+ self.descriptor
+ }
+}
+
+// Safe because the descriptor and ACLs are treated as immutable by consuming
+// functions and can be safely shared between threads.
+unsafe impl Send for SelfRelativeSecurityDescriptor {}
+
+// Safe because the descriptor and ACLs are treated as immutable by consuming
+// functions.
+unsafe impl Sync for SelfRelativeSecurityDescriptor {}
diff --git a/x86_64/Android.bp b/x86_64/Android.bp
index c1658ffb9..c57d76c06 100644
--- a/x86_64/Android.bp
+++ b/x86_64/Android.bp
@@ -1,5 +1,5 @@
-// This file is generated by cargo2android.py --run --device --tests --dependencies --global_defaults=crosvm_defaults --add_workspace --features=gdb.
-// NOTE: The --features=gdb should be applied only to the host (not the device) and there are inline changes to achieve this
+// This file is generated by cargo2android.py --config cargo2android.json.
+// Do not modify this file as changes will be overridden on upgrade.
package {
// See: http://go/android-license-faq
@@ -16,19 +16,10 @@ rust_library {
stem: "libx86_64",
host_supported: true,
crate_name: "x86_64",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
- edition: "2018",
- target: {
- linux_glibc_x86_64: {
- features: [
- "gdb",
- "gdbstub",
- ],
- rustlibs: [
- "libgdbstub",
- ],
- },
- },
+ edition: "2021",
rustlibs: [
"libacpi_tables",
"libarch",
@@ -43,10 +34,21 @@ rust_library {
"libminijail_rust",
"libresources",
"libsync_rust",
+ "libthiserror",
"libvm_control",
"libvm_memory",
],
proc_macros: ["libremain"],
+ target: {
+ host_linux: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+ },
// Exclude arm family manually
arch: {
arm: {
@@ -56,27 +58,23 @@ rust_library {
enabled: false,
},
},
+
}
-rust_defaults {
- name: "x86_64_defaults",
+rust_test {
+ name: "x86_64_test_src_lib",
defaults: ["crosvm_defaults"],
+ host_supported: true,
crate_name: "x86_64",
+ cargo_env_compat: true,
+ cargo_pkg_version: "0.1.0",
srcs: ["src/lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
- edition: "2018",
- target: {
- linux_glibc_x86_64: {
- features: [
- "gdb",
- "gdbstub",
- ],
- rustlibs: [
- "libgdbstub",
- ],
- },
+ test_options: {
+ unit_test: false,
},
+ edition: "2021",
rustlibs: [
"libacpi_tables",
"libarch",
@@ -91,10 +89,21 @@ rust_defaults {
"libminijail_rust",
"libresources",
"libsync_rust",
+ "libthiserror",
"libvm_control",
"libvm_memory",
],
proc_macros: ["libremain"],
+ target: {
+ host_linux: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+ },
// Exclude arm family manually
arch: {
arm: {
@@ -104,113 +113,5 @@ rust_defaults {
enabled: false,
},
},
-}
-
-rust_test_host {
- name: "x86_64_host_test_src_lib",
- defaults: ["x86_64_defaults"],
- test_options: {
- unit_test: true,
- },
-}
-rust_test {
- name: "x86_64_device_test_src_lib",
- defaults: ["x86_64_defaults"],
}
-
-// dependent_library ["feature_list"]
-// ../../adhd/audio_streams/src/audio_streams.rs
-// ../../adhd/cras/client/cras-sys/src/lib.rs
-// ../../adhd/cras/client/libcras/src/libcras.rs
-// ../../libchromeos-rs/src/lib.rs
-// ../../minijail/rust/minijail-sys/lib.rs
-// ../../minijail/rust/minijail/src/lib.rs
-// ../../vm_tools/p9/src/lib.rs
-// ../../vm_tools/p9/wire_format_derive/wire_format_derive.rs
-// ../acpi_tables/src/lib.rs
-// ../arch/src/lib.rs "gdb,gdbstub"
-// ../assertions/src/lib.rs
-// ../base/src/lib.rs
-// ../bit_field/bit_field_derive/bit_field_derive.rs
-// ../bit_field/src/lib.rs
-// ../cros_async/src/lib.rs
-// ../data_model/src/lib.rs
-// ../devices/src/lib.rs
-// ../disk/src/disk.rs
-// ../enumn/src/lib.rs
-// ../fuse/src/lib.rs
-// ../hypervisor/src/lib.rs
-// ../io_uring/src/lib.rs
-// ../kernel_cmdline/src/kernel_cmdline.rs
-// ../kernel_loader/src/lib.rs
-// ../kvm/src/lib.rs
-// ../kvm_sys/src/lib.rs
-// ../linux_input_sys/src/lib.rs
-// ../net_sys/src/lib.rs
-// ../net_util/src/lib.rs
-// ../power_monitor/src/lib.rs
-// ../rand_ish/src/lib.rs
-// ../resources/src/lib.rs
-// ../rutabaga_gfx/src/lib.rs
-// ../sync/src/lib.rs
-// ../sys_util/poll_token_derive/poll_token_derive.rs
-// ../sys_util/src/lib.rs
-// ../syscall_defines/src/lib.rs
-// ../tempfile/src/lib.rs
-// ../usb_sys/src/lib.rs
-// ../usb_util/src/lib.rs
-// ../vfio_sys/src/lib.rs
-// ../vhost/src/lib.rs
-// ../virtio_sys/src/lib.rs
-// ../vm_control/src/lib.rs
-// ../vm_memory/src/lib.rs
-// async-task-4.0.3 "default,std"
-// async-trait-0.1.48
-// autocfg-1.0.1
-// base-0.1.0
-// bitflags-1.2.1 "default"
-// cfg-if-0.1.10
-// cfg-if-1.0.0
-// downcast-rs-1.2.0 "default,std"
-// futures-0.3.13 "alloc,async-await,default,executor,futures-executor,std"
-// futures-channel-0.3.13 "alloc,futures-sink,sink,std"
-// futures-core-0.3.13 "alloc,std"
-// futures-executor-0.3.13 "std"
-// futures-io-0.3.13 "std"
-// futures-macro-0.3.13
-// futures-sink-0.3.13 "alloc,std"
-// futures-task-0.3.13 "alloc,std"
-// futures-util-0.3.13 "alloc,async-await,async-await-macro,channel,futures-channel,futures-io,futures-macro,futures-sink,io,memchr,proc-macro-hack,proc-macro-nested,sink,slab,std"
-// gdbstub-0.4.4 "alloc,default,std"
-// getrandom-0.2.2 "std"
-// intrusive-collections-0.9.0 "alloc,default"
-// libc-0.2.88 "default,std"
-// log-0.4.14
-// managed-0.8.0 "alloc"
-// memchr-2.3.4 "default,std"
-// memoffset-0.5.6 "default"
-// num-traits-0.2.14
-// paste-1.0.4
-// pin-project-lite-0.2.6
-// pin-utils-0.1.0
-// pkg-config-0.3.19
-// ppv-lite86-0.2.10 "simd,std"
-// proc-macro-hack-0.5.19
-// proc-macro-nested-0.1.7
-// proc-macro2-1.0.24 "default,proc-macro"
-// protobuf-2.22.0
-// quote-1.0.9 "default,proc-macro"
-// rand-0.8.3 "alloc,default,getrandom,libc,rand_chacha,rand_hc,std,std_rng"
-// rand_chacha-0.3.0 "std"
-// rand_core-0.6.2 "alloc,getrandom,std"
-// remain-0.2.2
-// remove_dir_all-0.5.3
-// serde-1.0.124 "default,derive,serde_derive,std"
-// serde_derive-1.0.124 "default"
-// slab-0.4.2
-// syn-1.0.63 "clone-impls,default,derive,full,parsing,printing,proc-macro,quote,visit-mut"
-// tempfile-3.2.0
-// thiserror-1.0.24
-// thiserror-impl-1.0.24
-// unicode-xid-0.2.1 "default"
diff --git a/x86_64/Cargo.toml b/x86_64/Cargo.toml
index 07fac9dc9..2b6d94fa6 100644
--- a/x86_64/Cargo.toml
+++ b/x86_64/Cargo.toml
@@ -2,17 +2,18 @@
name = "x86_64"
version = "0.1.0"
authors = ["The Chromium OS Authors"]
-edition = "2018"
+edition = "2021"
[features]
-gdb = ["gdbstub", "arch/gdb"]
+gdb = ["gdbstub_arch", "arch/gdb"]
+direct = []
[dependencies]
arch = { path = "../arch" }
-assertions = { path = "../assertions" }
-data_model = { path = "../data_model" }
+assertions = { path = "../common/assertions" }
+data_model = { path = "../common/data_model" }
devices = { path = "../devices" }
-gdbstub = { version = "0.4.0", optional = true }
+gdbstub_arch = { version = "0.1.0", optional = true }
hypervisor = { path = "../hypervisor" }
kernel_cmdline = { path = "../kernel_cmdline" }
kernel_loader = { path = "../kernel_loader" }
@@ -20,7 +21,8 @@ libc = "*"
minijail = { path = "../../minijail/rust/minijail" } # ignored by ebuild
remain = "*"
resources = { path = "../resources" }
-sync = { path = "../sync" }
+sync = { path = "../common/sync" }
+thiserror = "*"
base = { path = "../base" }
acpi_tables = {path = "../acpi_tables" }
vm_control = { path = "../vm_control" }
diff --git a/x86_64/TEST_MAPPING b/x86_64/TEST_MAPPING
index bdb501b94..d05c3b567 100644
--- a/x86_64/TEST_MAPPING
+++ b/x86_64/TEST_MAPPING
@@ -4,10 +4,10 @@
// "presubmit": [
// {
// "host": true,
-// "name": "x86_64_host_test_src_lib"
+// "name": "x86_64_test_src_lib"
// },
// {
-// "name": "x86_64_device_test_src_lib"
+// "name": "x86_64_test_src_lib"
// }
// ]
}
diff --git a/x86_64/cargo2android.json b/x86_64/cargo2android.json
new file mode 100644
index 000000000..af79f6250
--- /dev/null
+++ b/x86_64/cargo2android.json
@@ -0,0 +1,9 @@
+{
+ "add-module-block": "cargo2android_gdb.bp",
+ "add_workspace": true,
+ "device": true,
+ "global_defaults": "crosvm_defaults",
+ "no_presubmit": true,
+ "run": true,
+ "tests": true
+} \ No newline at end of file
diff --git a/x86_64/cargo2android_gdb.bp b/x86_64/cargo2android_gdb.bp
new file mode 100644
index 000000000..2ac656a5d
--- /dev/null
+++ b/x86_64/cargo2android_gdb.bp
@@ -0,0 +1,19 @@
+target: {
+ host_linux: {
+ features: [
+ "gdb",
+ ],
+ rustlibs: [
+ "libgdbstub_arch",
+ ],
+ },
+},
+// Exclude arm family manually
+arch: {
+ arm: {
+ enabled: false,
+ },
+ arm64: {
+ enabled: false,
+ },
+}
diff --git a/x86_64/src/acpi.rs b/x86_64/src/acpi.rs
index f07a1f01e..aaac1b3c2 100644
--- a/x86_64/src/acpi.rs
+++ b/x86_64/src/acpi.rs
@@ -1,20 +1,43 @@
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use acpi_tables::{rsdp::RSDP, sdt::SDT};
+
+use std::arch::x86_64::{CpuidResult, __cpuid, __cpuid_count};
+use std::collections::BTreeMap;
+
+use acpi_tables::{facs::FACS, rsdp::RSDP, sdt::SDT};
+use arch::VcpuAffinity;
+use base::{error, warn};
use data_model::DataInit;
+use devices::{ACPIPMResource, PciAddress, PciInterruptPin};
+use std::sync::Arc;
+use sync::Mutex;
use vm_memory::{GuestAddress, GuestMemory};
-pub struct ACPIDevResource {
+pub struct AcpiDevResource {
pub amls: Vec<u8>,
pub pm_iobase: u64,
+ pub pm: Arc<Mutex<ACPIPMResource>>,
/// Additional system descriptor tables.
pub sdts: Vec<SDT>,
}
+#[repr(C, packed)]
+#[derive(Clone, Copy, Default)]
+struct GenericAddress {
+ _space_id: u8,
+ _bit_width: u8,
+ _bit_offset: u8,
+ _access_width: u8,
+ _address: u64,
+}
+
+// Safe as GenericAddress structure only contains raw data
+unsafe impl DataInit for GenericAddress {}
+
#[repr(C)]
#[derive(Clone, Copy, Default)]
-struct LocalAPIC {
+struct LocalApic {
_type: u8,
_length: u8,
_processor_id: u8,
@@ -23,11 +46,11 @@ struct LocalAPIC {
}
// Safe as LocalAPIC structure only contains raw data
-unsafe impl DataInit for LocalAPIC {}
+unsafe impl DataInit for LocalApic {}
#[repr(C)]
#[derive(Clone, Copy, Default)]
-struct IOAPIC {
+struct Ioapic {
_type: u8,
_length: u8,
_ioapic_id: u8,
@@ -37,7 +60,38 @@ struct IOAPIC {
}
// Safe as IOAPIC structure only contains raw data
-unsafe impl DataInit for IOAPIC {}
+unsafe impl DataInit for Ioapic {}
+
+#[repr(C)]
+#[derive(Clone, Copy, Default)]
+struct IoapicInterruptSourceOverride {
+ _type: u8,
+ _length: u8,
+ _bus: u8,
+ _source: u8,
+ _gsi: u32,
+ _flags: u16,
+}
+
+// Safe as IoapicInterruptSourceOverride structure only contains raw data
+unsafe impl DataInit for IoapicInterruptSourceOverride {}
+
+#[repr(C)]
+#[derive(Clone, Copy, Default)]
+struct Localx2Apic {
+ _type: u8,
+ _length: u8,
+ _reserved: u16,
+ _x2apic_id: u32,
+ _flags: u32,
+ _processor_id: u32,
+}
+
+// Safe as LocalAPIC structure only contains raw data
+unsafe impl DataInit for Localx2Apic {}
+
+// Space ID for GenericAddress
+const ADR_SPACE_SYSTEM_IO: u8 = 1;
const OEM_REVISION: u32 = 1;
//DSDT
@@ -49,30 +103,78 @@ const FADT_MINOR_REVISION: u8 = 3;
// FADT flags
const FADT_POWER_BUTTON: u32 = 1 << 4;
const FADT_SLEEP_BUTTON: u32 = 1 << 5;
+const FADT_RESET_REGISTER: u32 = 1 << 10;
+const FADT_LOW_POWER_S2IDLE: u32 = 1 << 21;
// FADT fields offset
-const FADT_FIELD_SCI_INTERRUPT: usize = 46;
+const FADT_FIELD_FACS_ADDR32: usize = 36;
+const FADT_FIELD_DSDT_ADDR32: usize = 40;
+pub const FADT_FIELD_SCI_INTERRUPT: usize = 46;
+const FADT_FIELD_SMI_COMMAND: usize = 48;
const FADT_FIELD_PM1A_EVENT_BLK_ADDR: usize = 56;
+const FADT_FIELD_PM1B_EVENT_BLK_ADDR: usize = 60;
const FADT_FIELD_PM1A_CONTROL_BLK_ADDR: usize = 64;
+const FADT_FIELD_PM1B_CONTROL_BLK_ADDR: usize = 68;
+const FADT_FIELD_PM2_CONTROL_BLK_ADDR: usize = 72;
+const FADT_FIELD_PM_TMR_BLK_ADDR: usize = 76;
+const FADT_FIELD_GPE0_BLK_ADDR: usize = 80;
+const FADT_FIELD_GPE1_BLK_ADDR: usize = 84;
const FADT_FIELD_PM1A_EVENT_BLK_LEN: usize = 88;
const FADT_FIELD_PM1A_CONTROL_BLK_LEN: usize = 89;
+const FADT_FIELD_PM2_CONTROL_BLK_LEN: usize = 90;
+const FADT_FIELD_PM_TMR_LEN: usize = 91;
+const FADT_FIELD_GPE0_BLK_LEN: usize = 92;
+const FADT_FIELD_GPE1_BLK_LEN: usize = 93;
+const FADT_FIELD_GPE1_BASE: usize = 94;
const FADT_FIELD_FLAGS: usize = 112;
+const FADT_FIELD_RESET_REGISTER: usize = 116;
+const FADT_FIELD_RESET_VALUE: usize = 128;
const FADT_FIELD_MINOR_REVISION: usize = 131;
+const FADT_FIELD_FACS_ADDR: usize = 132;
const FADT_FIELD_DSDT_ADDR: usize = 140;
+const FADT_FIELD_X_PM1A_EVENT_BLK_ADDR: usize = 148;
+const FADT_FIELD_X_PM1B_EVENT_BLK_ADDR: usize = 160;
+const FADT_FIELD_X_PM1A_CONTROL_BLK_ADDR: usize = 172;
+const FADT_FIELD_X_PM1B_CONTROL_BLK_ADDR: usize = 184;
+const FADT_FIELD_X_PM2_CONTROL_BLK_ADDR: usize = 196;
+const FADT_FIELD_X_PM_TMR_BLK_ADDR: usize = 208;
+const FADT_FIELD_X_GPE0_BLK_ADDR: usize = 220;
+const FADT_FIELD_X_GPE1_BLK_ADDR: usize = 232;
const FADT_FIELD_HYPERVISOR_ID: usize = 268;
// MADT
const MADT_LEN: u32 = 44;
const MADT_REVISION: u8 = 5;
// MADT fields offset
const MADT_FIELD_LAPIC_ADDR: usize = 36;
+const MADT_FIELD_FLAGS: usize = 40;
+// MADT flags
+const MADT_FLAG_PCAT_COMPAT: u32 = 1 << 0;
+// MADT structure offsets
+const MADT_STRUCTURE_TYPE: usize = 0;
+const MADT_STRUCTURE_LEN: usize = 1;
// MADT types
const MADT_TYPE_LOCAL_APIC: u8 = 0;
const MADT_TYPE_IO_APIC: u8 = 1;
+const MADT_TYPE_INTERRUPT_SOURCE_OVERRIDE: u8 = 2;
+const MADT_TYPE_LOCAL_X2APIC: u8 = 9;
// MADT flags
const MADT_ENABLED: u32 = 1;
+const MADT_INT_POLARITY_ACTIVE_LOW: u16 = 0b11;
+const MADT_INT_TRIGGER_LEVEL: u16 = 0b11 << 2;
+// MADT compatibility
+const MADT_MIN_LOCAL_APIC_ID: u32 = 255;
// XSDT
const XSDT_REVISION: u8 = 1;
-fn create_dsdt_table(amls: Vec<u8>) -> SDT {
+const CPUID_LEAF0_EBX_CPUID_SHIFT: u32 = 24; // Offset of initial apic id.
+
+// MCFG
+const MCFG_LEN: u32 = 60;
+const MCFG_REVISION: u8 = 1;
+const MCFG_FIELD_BASE_ADDRESS: usize = 44;
+const MCFG_FIELD_START_BUS_NUMBER: usize = 54;
+const MCFG_FIELD_END_BUS_NUMBER: usize = 55;
+
+fn create_dsdt_table(amls: &[u8]) -> SDT {
let mut dsdt = SDT::new(
*b"DSDT",
acpi_tables::HEADER_LEN,
@@ -83,12 +185,295 @@ fn create_dsdt_table(amls: Vec<u8>) -> SDT {
);
if !amls.is_empty() {
- dsdt.append_slice(amls.as_slice());
+ dsdt.append_slice(amls);
}
dsdt
}
+fn create_facp_table(sci_irq: u16, force_s2idle: bool) -> SDT {
+ let mut facp = SDT::new(
+ *b"FACP",
+ FADT_LEN,
+ FADT_REVISION,
+ *b"CROSVM",
+ *b"CROSVMDT",
+ OEM_REVISION,
+ );
+
+ let mut fadt_flags: u32 = FADT_SLEEP_BUTTON; // mask SLEEP BUTTON
+
+ if force_s2idle {
+ fadt_flags |= FADT_LOW_POWER_S2IDLE;
+ }
+
+ facp.write(FADT_FIELD_FLAGS, fadt_flags);
+
+ // SCI Interrupt
+ facp.write(FADT_FIELD_SCI_INTERRUPT, sci_irq);
+
+ facp.write(FADT_FIELD_MINOR_REVISION, FADT_MINOR_REVISION); // FADT minor version
+ facp.write(FADT_FIELD_HYPERVISOR_ID, *b"CROSVM"); // Hypervisor Vendor Identity
+
+ facp
+}
+
+// Write virtualized FADT fields
+fn write_facp_overrides(
+ facp: &mut SDT,
+ facs_offset: GuestAddress,
+ dsdt_offset: GuestAddress,
+ pm_iobase: u32,
+ reset_port: u32,
+ reset_value: u8,
+) {
+ let fadt_flags: u32 = facp.read(FADT_FIELD_FLAGS);
+ // indicate we support FADT RESET_REG
+ facp.write(FADT_FIELD_FLAGS, fadt_flags | FADT_RESET_REGISTER);
+
+ facp.write(FADT_FIELD_SMI_COMMAND, 0u32);
+ facp.write(FADT_FIELD_FACS_ADDR32, 0u32);
+ facp.write(FADT_FIELD_DSDT_ADDR32, 0u32);
+ facp.write(FADT_FIELD_FACS_ADDR, facs_offset.0 as u64);
+ facp.write(FADT_FIELD_DSDT_ADDR, dsdt_offset.0 as u64);
+
+ // PM1A Event Block Address
+ facp.write(FADT_FIELD_PM1A_EVENT_BLK_ADDR, pm_iobase);
+
+ // PM1B Event Block Address (not supported)
+ facp.write(FADT_FIELD_PM1B_EVENT_BLK_ADDR, 0u32);
+
+ // PM1A Control Block Address
+ facp.write(
+ FADT_FIELD_PM1A_CONTROL_BLK_ADDR,
+ pm_iobase + devices::acpi::ACPIPM_RESOURCE_EVENTBLK_LEN as u32,
+ );
+
+ // PM1B Control Block Address (not supported)
+ facp.write(FADT_FIELD_PM1B_CONTROL_BLK_ADDR, 0u32);
+
+ // PM2 Control Block Address (not supported)
+ facp.write(FADT_FIELD_PM2_CONTROL_BLK_ADDR, 0u32);
+
+ // PM Timer Control Block Address (not supported)
+ facp.write(FADT_FIELD_PM_TMR_BLK_ADDR, 0u32);
+
+ // GPE0 Block Address
+ facp.write(
+ FADT_FIELD_GPE0_BLK_ADDR,
+ pm_iobase + devices::acpi::ACPIPM_RESOURCE_EVENTBLK_LEN as u32 + 4,
+ );
+
+ // GPE1 Block Address (not supported)
+ facp.write(FADT_FIELD_GPE1_BLK_ADDR, 0u32);
+
+ // PM1 Event Block Length
+ facp.write(
+ FADT_FIELD_PM1A_EVENT_BLK_LEN,
+ devices::acpi::ACPIPM_RESOURCE_EVENTBLK_LEN,
+ );
+
+ // PM1 Control Block Length
+ facp.write(
+ FADT_FIELD_PM1A_CONTROL_BLK_LEN,
+ devices::acpi::ACPIPM_RESOURCE_CONTROLBLK_LEN,
+ );
+
+ // PM2 Control Block Length (not supported)
+ facp.write(FADT_FIELD_PM2_CONTROL_BLK_LEN, 0u8);
+
+ // PM Timer Control Block Length (not supported)
+ facp.write(FADT_FIELD_PM_TMR_LEN, 0u8);
+
+ // GPE0 Block Length
+ facp.write(
+ FADT_FIELD_GPE0_BLK_LEN,
+ devices::acpi::ACPIPM_RESOURCE_GPE0_BLK_LEN,
+ );
+
+ // GPE1 Block Length (not supported)
+ facp.write(FADT_FIELD_GPE1_BLK_LEN, 0u8);
+
+ // GPE1 Base (not supported)
+ facp.write(FADT_FIELD_GPE1_BASE, 0u8);
+
+ // PM1A Extended Event Block Address (not supported)
+ facp.write(
+ FADT_FIELD_X_PM1A_EVENT_BLK_ADDR,
+ GenericAddress {
+ ..Default::default()
+ },
+ );
+
+ // PM1B Extended Event Block Address (not supported)
+ facp.write(
+ FADT_FIELD_X_PM1B_EVENT_BLK_ADDR,
+ GenericAddress {
+ ..Default::default()
+ },
+ );
+
+ // PM1A Extended Control Block Address (not supported)
+ facp.write(
+ FADT_FIELD_X_PM1A_CONTROL_BLK_ADDR,
+ GenericAddress {
+ ..Default::default()
+ },
+ );
+
+ // PM1B Extended Control Block Address (not supported)
+ facp.write(
+ FADT_FIELD_X_PM1B_CONTROL_BLK_ADDR,
+ GenericAddress {
+ ..Default::default()
+ },
+ );
+
+ // PM2 Extended Control Block Address (not supported)
+ facp.write(
+ FADT_FIELD_X_PM2_CONTROL_BLK_ADDR,
+ GenericAddress {
+ ..Default::default()
+ },
+ );
+
+ // PM Timer Extended Control Block Address (not supported)
+ facp.write(
+ FADT_FIELD_X_PM_TMR_BLK_ADDR,
+ GenericAddress {
+ ..Default::default()
+ },
+ );
+
+ // GPE0 Extended Address (not supported)
+ facp.write(
+ FADT_FIELD_X_GPE0_BLK_ADDR,
+ GenericAddress {
+ ..Default::default()
+ },
+ );
+
+ // GPE1 Extended Address (not supported)
+ facp.write(
+ FADT_FIELD_X_GPE1_BLK_ADDR,
+ GenericAddress {
+ ..Default::default()
+ },
+ );
+
+ // Reset register
+ facp.write(
+ FADT_FIELD_RESET_REGISTER,
+ GenericAddress {
+ _space_id: ADR_SPACE_SYSTEM_IO,
+ _bit_width: 8,
+ _bit_offset: 0,
+ _access_width: 8,
+ _address: reset_port.into(),
+ },
+ );
+ facp.write(FADT_FIELD_RESET_VALUE, reset_value);
+}
+
+fn next_offset(offset: GuestAddress, len: u64) -> Option<GuestAddress> {
+ // Enforce 64-byte allocation alignment.
+ match len % 64 {
+ 0 => offset.checked_add(len),
+ x => offset.checked_add(len.checked_add(64 - x)?),
+ }
+}
+
+fn sync_acpi_id_from_cpuid(
+ madt: &mut SDT,
+ cpus: BTreeMap<usize, Vec<usize>>,
+ apic_ids: &mut Vec<usize>,
+) -> base::Result<()> {
+ let cpu_set = match base::get_cpu_affinity() {
+ Err(e) => {
+ error!("Failed to get CPU affinity: {} when create MADT", e);
+ return Err(e);
+ }
+ Ok(c) => c,
+ };
+
+ for (vcpu, pcpu) in cpus {
+ let mut has_leafb = false;
+ let mut get_apic_id = false;
+ let mut apic_id: u8 = 0;
+
+ if let Err(e) = base::set_cpu_affinity(pcpu) {
+ error!("Failed to set CPU affinity: {} when create MADT", e);
+ return Err(e);
+ }
+
+ // Safe because we pass 0 and 0 for this call and the host supports the
+ // `cpuid` instruction
+ let mut cpuid_entry: CpuidResult = unsafe { __cpuid_count(0, 0) };
+
+ if cpuid_entry.eax >= 0xB {
+ // Safe because we pass 0xB and 0 for this call and the host supports the
+ // `cpuid` instruction
+ cpuid_entry = unsafe { __cpuid_count(0xB, 0) };
+
+ if cpuid_entry.ebx != 0 {
+ // MADT compatibility: (ACPI Spec v6.4) On some legacy OSes,
+ // Logical processors with APIC ID values less than 255 (whether in
+ // XAPIC or X2APIC mode) must use the Processor Local APIC structure.
+ if cpuid_entry.edx < MADT_MIN_LOCAL_APIC_ID {
+ apic_id = cpuid_entry.edx as u8;
+ get_apic_id = true;
+ } else {
+ // (ACPI Spec v6.4) When using the X2APIC, logical processors are
+ // required to have a processor device object in the DSDT and must
+ // convey the processor’s APIC information to OSPM using the Processor
+ // Local X2APIC structure.
+ // Now vCPUs use the DSDT passthrougt from host and the same APIC ID as
+ // the physical CPUs. Both of them should meet ACPI specifications on
+ // the host.
+ has_leafb = true;
+
+ let x2apic = Localx2Apic {
+ _type: MADT_TYPE_LOCAL_X2APIC,
+ _length: std::mem::size_of::<Localx2Apic>() as u8,
+ _x2apic_id: cpuid_entry.edx,
+ _flags: MADT_ENABLED,
+ _processor_id: (vcpu + 1) as u32,
+ ..Default::default()
+ };
+ madt.append(x2apic);
+ apic_ids.push(cpuid_entry.edx as usize);
+ }
+ }
+ }
+
+ if !has_leafb {
+ if !get_apic_id {
+ // Safe because we pass 1 for this call and the host supports the
+ // `cpuid` instruction
+ cpuid_entry = unsafe { __cpuid(1) };
+ apic_id = (cpuid_entry.ebx >> CPUID_LEAF0_EBX_CPUID_SHIFT & 0xff) as u8;
+ }
+
+ let apic = LocalApic {
+ _type: MADT_TYPE_LOCAL_APIC,
+ _length: std::mem::size_of::<LocalApic>() as u8,
+ _processor_id: vcpu as u8,
+ _apic_id: apic_id,
+ _flags: MADT_ENABLED,
+ };
+ madt.append(apic);
+ apic_ids.push(apic_id as usize);
+ }
+ }
+
+ if let Err(e) = base::set_cpu_affinity(cpu_set) {
+ error!("Failed to reset CPU affinity: {} when create MADT", e);
+ return Err(e);
+ }
+
+ Ok(())
+}
+
/// Create ACPI tables and return the RSDP.
/// The basic tables DSDT/FACP/MADT/XSDT are constructed in this function.
/// # Arguments
@@ -98,91 +483,103 @@ fn create_dsdt_table(amls: Vec<u8>) -> SDT {
/// * `sci_irq` - Used to fill the FACP SCI_INTERRUPT field, which
/// is going to be used by the ACPI drivers to register
/// sci handler.
-/// * `acpi_dev_resource` - resouces needed by the ACPI devices for creating tables
+/// * `acpi_dev_resource` - resouces needed by the ACPI devices for creating tables.
+/// * `host_cpus` - The CPU affinity per CPU used to get corresponding CPUs' apic
+/// id and set these apic id in MADT if `--host-cpu-topology`
+/// option is set.
+/// * `apic_ids` - The apic id for vCPU will be sent to KVM by KVM_CREATE_VCPU ioctl.
+/// * `pci_rqs` - PCI device to IRQ number assignments as returned by
+/// `arch::generate_pci_root()` (device address, IRQ number, and PCI
+/// interrupt pin assignment).
+/// * `pcie_cfg_mmio` - Base address for the pcie enhanced configuration access mechanism
+/// * `max_bus` - Max bus number in MCFG table
+///
+
pub fn create_acpi_tables(
guest_mem: &GuestMemory,
num_cpus: u8,
sci_irq: u32,
- acpi_dev_resource: ACPIDevResource,
+ reset_port: u32,
+ reset_value: u8,
+ acpi_dev_resource: &AcpiDevResource,
+ host_cpus: Option<VcpuAffinity>,
+ apic_ids: &mut Vec<usize>,
+ pci_irqs: &[(PciAddress, u32, PciInterruptPin)],
+ pcie_cfg_mmio: u64,
+ max_bus: u8,
+ force_s2idle: bool,
) -> Option<GuestAddress> {
// RSDP is at the HI RSDP WINDOW
let rsdp_offset = GuestAddress(super::ACPI_HI_RSDP_WINDOW_BASE);
- let mut offset = rsdp_offset.checked_add(RSDP::len() as u64)?;
- let mut tables: Vec<u64> = Vec::new();
+ let facs_offset = next_offset(rsdp_offset, RSDP::len() as u64)?;
+ let mut offset = next_offset(facs_offset, FACS::len() as u64)?;
let mut dsdt_offset: Option<GuestAddress> = None;
+ let mut tables: Vec<u64> = Vec::new();
+ let mut facp: Option<SDT> = None;
+ let mut host_madt: Option<SDT> = None;
// User supplied System Description Tables, e.g. SSDT.
for sdt in acpi_dev_resource.sdts.iter() {
+ if sdt.is_signature(b"FACP") {
+ facp = Some(sdt.clone());
+ continue;
+ }
+ if sdt.is_signature(b"APIC") {
+ host_madt = Some(sdt.clone());
+ continue;
+ }
guest_mem.write_at_addr(sdt.as_slice(), offset).ok()?;
if sdt.is_signature(b"DSDT") {
dsdt_offset = Some(offset);
} else {
tables.push(offset.0);
}
- offset = offset.checked_add(sdt.len() as u64)?;
+ offset = next_offset(offset, sdt.len() as u64)?;
}
+ // FACS
+ let facs = FACS::new();
+ guest_mem.write_at_addr(facs.as_slice(), facs_offset).ok()?;
+
// DSDT
let dsdt_offset = match dsdt_offset {
Some(dsdt_offset) => dsdt_offset,
None => {
let dsdt_offset = offset;
- let dsdt = create_dsdt_table(acpi_dev_resource.amls);
+ let dsdt = create_dsdt_table(&acpi_dev_resource.amls);
guest_mem.write_at_addr(dsdt.as_slice(), offset).ok()?;
- offset = offset.checked_add(dsdt.len() as u64)?;
+ offset = next_offset(offset, dsdt.len() as u64)?;
dsdt_offset
}
};
// FACP aka FADT
- // Revision 6 of the ACPI FADT table is 276 bytes long
- let mut facp = SDT::new(
- *b"FACP",
- FADT_LEN,
- FADT_REVISION,
- *b"CROSVM",
- *b"CROSVMDT",
- OEM_REVISION,
+ let mut facp = facp.map_or_else(
+ || create_facp_table(sci_irq as u16, force_s2idle),
+ |facp| {
+ let fadt_flags: u32 = facp.read(FADT_FIELD_FLAGS);
+ if fadt_flags & FADT_POWER_BUTTON != 0 {
+ warn!(
+ "Control Method Power Button is not supported. FADT flags = 0x{:x}",
+ fadt_flags
+ );
+ }
+ facp
+ },
);
- let fadt_flags: u32 = FADT_POWER_BUTTON | FADT_SLEEP_BUTTON; // mask POWER and SLEEP BUTTON
- facp.write(FADT_FIELD_FLAGS, fadt_flags);
-
- // SCI Interrupt
- facp.write(FADT_FIELD_SCI_INTERRUPT, sci_irq as u16);
-
- // PM1A Event Block Address
- facp.write(
- FADT_FIELD_PM1A_EVENT_BLK_ADDR,
+ write_facp_overrides(
+ &mut facp,
+ facs_offset,
+ dsdt_offset,
acpi_dev_resource.pm_iobase as u32,
+ reset_port,
+ reset_value,
);
- // PM1A Control Block Address
- facp.write(
- FADT_FIELD_PM1A_CONTROL_BLK_ADDR,
- acpi_dev_resource.pm_iobase as u32 + devices::acpi::ACPIPM_RESOURCE_EVENTBLK_LEN as u32,
- );
-
- // PM1 Event Block Length
- facp.write(
- FADT_FIELD_PM1A_EVENT_BLK_LEN,
- devices::acpi::ACPIPM_RESOURCE_EVENTBLK_LEN as u8,
- );
-
- // PM1 Control Block Length
- facp.write(
- FADT_FIELD_PM1A_CONTROL_BLK_LEN,
- devices::acpi::ACPIPM_RESOURCE_CONTROLBLK_LEN as u8,
- );
-
- facp.write(FADT_FIELD_MINOR_REVISION, FADT_MINOR_REVISION); // FADT minor version
- facp.write(FADT_FIELD_DSDT_ADDR, dsdt_offset.0 as u64); // X_DSDT
-
- facp.write(FADT_FIELD_HYPERVISOR_ID, *b"CROSVM"); // Hypervisor Vendor Identity
-
guest_mem.write_at_addr(facp.as_slice(), offset).ok()?;
tables.push(offset.0);
- offset = offset.checked_add(facp.len() as u64)?;
+ offset = next_offset(offset, facp.len() as u64)?;
// MADT
let mut madt = SDT::new(
@@ -197,28 +594,89 @@ pub fn create_acpi_tables(
MADT_FIELD_LAPIC_ADDR,
super::mptable::APIC_DEFAULT_PHYS_BASE as u32,
);
+ // Our IrqChip implementations (the KVM in-kernel irqchip and the split irqchip) expose a pair
+ // of PC-compatible 8259 PICs.
+ madt.write(MADT_FIELD_FLAGS, MADT_FLAG_PCAT_COMPAT);
- for cpu in 0..num_cpus {
- let lapic = LocalAPIC {
- _type: MADT_TYPE_LOCAL_APIC,
- _length: std::mem::size_of::<LocalAPIC>() as u8,
- _processor_id: cpu,
- _apic_id: cpu,
- _flags: MADT_ENABLED,
- };
- madt.append(lapic);
+ match host_cpus {
+ Some(VcpuAffinity::PerVcpu(cpus)) => {
+ sync_acpi_id_from_cpuid(&mut madt, cpus, apic_ids).ok()?;
+ }
+ _ => {
+ for cpu in 0..num_cpus {
+ let apic = LocalApic {
+ _type: MADT_TYPE_LOCAL_APIC,
+ _length: std::mem::size_of::<LocalApic>() as u8,
+ _processor_id: cpu,
+ _apic_id: cpu,
+ _flags: MADT_ENABLED,
+ };
+ madt.append(apic);
+ apic_ids.push(cpu as usize);
+ }
+ }
}
- madt.append(IOAPIC {
+ madt.append(Ioapic {
_type: MADT_TYPE_IO_APIC,
- _length: std::mem::size_of::<IOAPIC>() as u8,
+ _length: std::mem::size_of::<Ioapic>() as u8,
_apic_address: super::mptable::IO_APIC_DEFAULT_PHYS_BASE,
..Default::default()
});
+ // Add interrupt overrides for the PCI IRQs so that they are reported as level triggered, as
+ // required by the PCI bus. The source and system GSI are identical, so this does not actually
+ // override the mapping; we just use it to set the level-triggered flag.
+ let mut unique_pci_irqs: Vec<u32> = pci_irqs.iter().map(|(_, irq_num, _)| *irq_num).collect();
+ unique_pci_irqs.sort_unstable();
+ unique_pci_irqs.dedup();
+ for irq_num in unique_pci_irqs {
+ madt.append(IoapicInterruptSourceOverride {
+ _type: MADT_TYPE_INTERRUPT_SOURCE_OVERRIDE,
+ _length: std::mem::size_of::<IoapicInterruptSourceOverride>() as u8,
+ _bus: 0, // ISA
+ _source: irq_num as u8,
+ _gsi: irq_num,
+ _flags: MADT_INT_POLARITY_ACTIVE_LOW | MADT_INT_TRIGGER_LEVEL,
+ });
+ }
+
+ if let Some(host_madt) = host_madt {
+ let mut idx = MADT_LEN as usize;
+ while idx + MADT_STRUCTURE_LEN < host_madt.len() {
+ let struct_type = host_madt.as_slice()[idx + MADT_STRUCTURE_TYPE];
+ let struct_len = host_madt.as_slice()[idx + MADT_STRUCTURE_LEN] as usize;
+ if struct_type == MADT_TYPE_INTERRUPT_SOURCE_OVERRIDE {
+ if idx + struct_len <= host_madt.len() {
+ madt.append_slice(&host_madt.as_slice()[idx..(idx + struct_len)]);
+ } else {
+ error!("Malformed host MADT");
+ }
+ }
+ idx += struct_len;
+ }
+ }
+
guest_mem.write_at_addr(madt.as_slice(), offset).ok()?;
tables.push(offset.0);
- offset = offset.checked_add(madt.len() as u64)?;
+ offset = next_offset(offset, madt.len() as u64)?;
+
+ // MCFG
+ let mut mcfg = SDT::new(
+ *b"MCFG",
+ MCFG_LEN,
+ MCFG_REVISION,
+ *b"CROSVM",
+ *b"CROSVMDT",
+ OEM_REVISION,
+ );
+ mcfg.write(MCFG_FIELD_BASE_ADDRESS, pcie_cfg_mmio);
+ mcfg.write(MCFG_FIELD_START_BUS_NUMBER, 0_u8);
+ mcfg.write(MCFG_FIELD_END_BUS_NUMBER, max_bus);
+
+ guest_mem.write_at_addr(mcfg.as_slice(), offset).ok()?;
+ tables.push(offset.0);
+ offset = next_offset(offset, madt.len() as u64)?;
// XSDT
let mut xsdt = SDT::new(
diff --git a/x86_64/src/bootparam.rs b/x86_64/src/bootparam.rs
index fd8e1d5a5..df7b03792 100644
--- a/x86_64/src/bootparam.rs
+++ b/x86_64/src/bootparam.rs
@@ -7,7 +7,7 @@
* From chromeos-linux v4.19
* $ bindgen \
* --no-layout-tests --with-derive-default --no-doc-comments \
- * --whitelist-type boot_params --whitelist-type setup_data \
+ * --allowlist-type boot_params --allowlist-type setup_data \
* arch/x86/include/uapi/asm/bootparam.h
*/
diff --git a/x86_64/src/bzimage.rs b/x86_64/src/bzimage.rs
index 7dd3041e3..db9bd8965 100644
--- a/x86_64/src/bzimage.rs
+++ b/x86_64/src/bzimage.rs
@@ -5,45 +5,36 @@
// Loader for bzImage-format Linux kernels as described in
// https://www.kernel.org/doc/Documentation/x86/boot.txt
-use std::fmt::{self, Display};
use std::io::{Read, Seek, SeekFrom};
use base::AsRawDescriptor;
+use data_model::DataInit;
+use remain::sorted;
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory};
use crate::bootparam::boot_params;
-#[derive(Debug, PartialEq)]
+#[sorted]
+#[derive(Error, Debug, PartialEq)]
pub enum Error {
+ #[error("bad kernel header signature")]
BadSignature,
+ #[error("invalid setup_sects value")]
InvalidSetupSects,
+ #[error("invalid syssize value")]
InvalidSysSize,
+ #[error("unable to read boot_params")]
ReadBootParams,
+ #[error("unable to read kernel image")]
ReadKernelImage,
+ #[error("unable to seek to boot_params")]
SeekBootParams,
+ #[error("unable to seek to kernel start")]
SeekKernelStart,
}
-pub type Result<T> = std::result::Result<T, Error>;
-
-impl std::error::Error for Error {}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- let description = match self {
- BadSignature => "bad kernel header signature",
- InvalidSetupSects => "invalid setup_sects value",
- InvalidSysSize => "invalid syssize value",
- ReadBootParams => "unable to read boot_params",
- ReadKernelImage => "unable to read kernel image",
- SeekBootParams => "unable to seek to boot_params",
- SeekKernelStart => "unable to seek to kernel start",
- };
- write!(f, "bzImage loader: {}", description)
- }
-}
+pub type Result<T> = std::result::Result<T, Error>;
/// Loads a kernel from a bzImage to a slice
///
@@ -55,19 +46,15 @@ impl Display for Error {
pub fn load_bzimage<F>(
guest_mem: &GuestMemory,
kernel_start: GuestAddress,
- kernel_image: &mut F,
+ mut kernel_image: &mut F,
) -> Result<(boot_params, u64)>
where
F: Read + Seek + AsRawDescriptor,
{
- let mut params: boot_params = Default::default();
kernel_image
.seek(SeekFrom::Start(0))
.map_err(|_| Error::SeekBootParams)?;
- unsafe {
- // read_struct is safe when reading a POD struct. It can be used and dropped without issue.
- base::read_struct(kernel_image, &mut params).map_err(|_| Error::ReadBootParams)?;
- }
+ let params = boot_params::from_reader(&mut kernel_image).map_err(|_| Error::ReadBootParams)?;
// bzImage header signature "HdrS"
if params.hdr.header != 0x53726448 {
diff --git a/x86_64/src/cpuid.rs b/x86_64/src/cpuid.rs
index 6b31d6012..7f3d0f14f 100644
--- a/x86_64/src/cpuid.rs
+++ b/x86_64/src/cpuid.rs
@@ -3,31 +3,23 @@
// found in the LICENSE file.
use std::arch::x86_64::{__cpuid, __cpuid_count};
-use std::fmt::{self, Display};
use std::result;
use devices::{IrqChipCap, IrqChipX86_64};
use hypervisor::{HypervisorX86_64, VcpuX86_64};
+use remain::sorted;
+use thiserror::Error;
-#[derive(Debug, PartialEq)]
+#[sorted]
+#[derive(Error, Debug, PartialEq)]
pub enum Error {
+ #[error("GetSupportedCpus ioctl failed: {0}")]
GetSupportedCpusFailed(base::Error),
+ #[error("SetSupportedCpus ioctl failed: {0}")]
SetSupportedCpusFailed(base::Error),
}
-pub type Result<T> = result::Result<T, Error>;
-
-impl std::error::Error for Error {}
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- GetSupportedCpusFailed(e) => write!(f, "GetSupportedCpus ioctl failed: {}", e),
- SetSupportedCpusFailed(e) => write!(f, "SetSupportedCpus ioctl failed: {}", e),
- }
- }
-}
+pub type Result<T> = result::Result<T, Error>;
// CPUID bits in ebx, ecx, and edx.
const EBX_CLFLUSH_CACHELINE: u32 = 8; // Flush a cache line size.
@@ -43,6 +35,7 @@ const ECX_TOPO_TYPE_SHIFT: u32 = 8; // Topology Level type.
const ECX_TOPO_SMT_TYPE: u32 = 1; // SMT type.
const ECX_TOPO_CORE_TYPE: u32 = 2; // CORE type.
const EAX_CPU_CORES_SHIFT: u32 = 26; // Index of cpu cores in the same physical package.
+const EDX_HYBRID_CPU_SHIFT: u32 = 15; // Hybrid. The processor is identified as a hybrid part.
fn filter_cpuid(
vcpu_id: usize,
@@ -50,6 +43,7 @@ fn filter_cpuid(
cpuid: &mut hypervisor::CpuId,
irq_chip: &dyn IrqChipX86_64,
no_smt: bool,
+ host_cpu_topology: bool,
) {
let entries = &mut cpuid.cpu_id_entries;
@@ -68,14 +62,31 @@ fn filter_cpuid(
if irq_chip.check_capability(IrqChipCap::TscDeadlineTimer) {
entry.ecx |= 1 << ECX_TSC_DEADLINE_TIMER_SHIFT;
}
+
+ if host_cpu_topology {
+ entry.ebx |= EBX_CLFLUSH_CACHELINE << EBX_CLFLUSH_SIZE_SHIFT;
+
+ // Expose HT flag to Guest.
+ let result = unsafe { __cpuid(entry.function) };
+ entry.edx |= result.edx & (1 << EDX_HTT_SHIFT);
+ continue;
+ }
+
entry.ebx = (vcpu_id << EBX_CPUID_SHIFT) as u32
| (EBX_CLFLUSH_CACHELINE << EBX_CLFLUSH_SIZE_SHIFT);
if cpu_count > 1 {
+ // This field is only valid if CPUID.1.EDX.HTT[bit 28]= 1.
entry.ebx |= (cpu_count as u32) << EBX_CPU_COUNT_SHIFT;
+ // A value of 0 for HTT indicates there is only a single logical
+ // processor in the package and software should assume only a
+ // single APIC ID is reserved.
entry.edx |= 1 << EDX_HTT_SHIFT;
}
}
- 2 | 0x80000005 | 0x80000006 => unsafe {
+ 2 | // Cache and TLB Descriptor information
+ 0x80000002 | 0x80000003 | 0x80000004 | // Processor Brand String
+ 0x80000005 | 0x80000006 // L1 and L2 cache information
+ => unsafe {
let result = __cpuid(entry.function);
entry.eax = result.eax;
entry.ebx = result.ebx;
@@ -90,6 +101,11 @@ fn filter_cpuid(
entry.ecx = result.ecx;
entry.edx = result.edx;
}
+
+ if host_cpu_topology {
+ continue;
+ }
+
entry.eax &= !0xFC000000;
if cpu_count > 1 {
let cpu_cores = if no_smt {
@@ -106,7 +122,30 @@ fn filter_cpuid(
// Clear X86 EPB feature. No frequency selection in the hypervisor.
entry.ecx &= !(1 << ECX_EPB_SHIFT);
}
+ 7 => {
+ if host_cpu_topology && entry.index == 0 {
+ // Safe because we pass 7 and 0 for this call and the host supports the
+ // `cpuid` instruction
+ let result = unsafe { __cpuid_count(entry.function, entry.index) };
+ entry.edx |= result.edx & (1 << EDX_HYBRID_CPU_SHIFT);
+ }
+ }
+ 0x1A => {
+ // Hybrid information leaf.
+ if host_cpu_topology {
+ // Safe because we pass 0x1A for this call and the host supports the
+ // `cpuid` instruction
+ let result = unsafe { __cpuid(entry.function) };
+ entry.eax = result.eax;
+ entry.ebx = result.ebx;
+ entry.ecx = result.ecx;
+ entry.edx = result.edx;
+ }
+ }
0xB | 0x1F => {
+ if host_cpu_topology {
+ continue;
+ }
// Extended topology enumeration / V2 Extended topology enumeration
// NOTE: these will need to be split if any of the fields that differ between
// the two versions are to be set.
@@ -153,6 +192,8 @@ fn filter_cpuid(
/// * `vcpu` - `VcpuX86_64` for setting CPU ID.
/// * `vcpu_id` - The vcpu index of `vcpu`.
/// * `nrcpus` - The number of vcpus being used by this VM.
+/// * `no_smt` - The flag indicates whether vCPUs supports SMT.
+/// * `host_cpu_topology` - The flag indicates whether vCPUs use mirror CPU topology.
pub fn setup_cpuid(
hypervisor: &dyn HypervisorX86_64,
irq_chip: &dyn IrqChipX86_64,
@@ -160,34 +201,29 @@ pub fn setup_cpuid(
vcpu_id: usize,
nrcpus: usize,
no_smt: bool,
+ host_cpu_topology: bool,
) -> Result<()> {
let mut cpuid = hypervisor
.get_supported_cpuid()
.map_err(Error::GetSupportedCpusFailed)?;
- filter_cpuid(vcpu_id, nrcpus, &mut cpuid, irq_chip, no_smt);
+ filter_cpuid(
+ vcpu_id,
+ nrcpus,
+ &mut cpuid,
+ irq_chip,
+ no_smt,
+ host_cpu_topology,
+ );
vcpu.set_cpuid(&cpuid)
.map_err(Error::SetSupportedCpusFailed)
}
-/// get host cpu max physical address bits
-pub fn phy_max_address_bits() -> u32 {
- let mut phys_bits: u32 = 36;
-
- let highest_ext_function = unsafe { __cpuid(0x80000000) };
- if highest_ext_function.eax >= 0x80000008 {
- let addr_size = unsafe { __cpuid(0x80000008) };
- phys_bits = addr_size.eax & 0xff;
- }
-
- phys_bits
-}
-
#[cfg(test)]
mod tests {
use super::*;
- use hypervisor::CpuIdEntry;
+ use hypervisor::{CpuIdEntry, ProtectionType};
#[test]
fn feature_and_vendor_name() {
@@ -195,7 +231,7 @@ mod tests {
let guest_mem =
vm_memory::GuestMemory::new(&[(vm_memory::GuestAddress(0), 0x10000)]).unwrap();
let kvm = hypervisor::kvm::Kvm::new().unwrap();
- let vm = hypervisor::kvm::KvmVm::new(&kvm, guest_mem).unwrap();
+ let vm = hypervisor::kvm::KvmVm::new(&kvm, guest_mem, ProtectionType::Unprotected).unwrap();
let irq_chip = devices::KvmKernelIrqChip::new(vm, 1).unwrap();
let entries = &mut cpuid.cpu_id_entries;
@@ -209,7 +245,7 @@ mod tests {
edx: 0,
..Default::default()
});
- filter_cpuid(1, 2, &mut cpuid, &irq_chip, false);
+ filter_cpuid(1, 2, &mut cpuid, &irq_chip, false, false);
let entries = &mut cpuid.cpu_id_entries;
assert_eq!(entries[0].function, 0);
diff --git a/x86_64/src/interrupts.rs b/x86_64/src/interrupts.rs
index 6346d2a15..909af1a55 100644
--- a/x86_64/src/interrupts.rs
+++ b/x86_64/src/interrupts.rs
@@ -2,30 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fmt::{self, Display};
use std::result;
use devices::IrqChipX86_64;
+use remain::sorted;
+use thiserror::Error;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
+ #[error("GetLapic ioctl failed: {0}")]
GetLapic(base::Error),
+ #[error("SetLapic ioctl failed: {0}")]
SetLapic(base::Error),
}
-pub type Result<T> = result::Result<T, Error>;
-
-impl std::error::Error for Error {}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
- match self {
- GetLapic(e) => write!(f, "GetLapic ioctl failed: {}", e),
- SetLapic(e) => write!(f, "SetLapic ioctl failed: {}", e),
- }
- }
-}
+pub type Result<T> = result::Result<T, Error>;
// Defines poached from apicdef.h kernel header.
diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs
index 2de945ae8..577b47c8f 100644
--- a/x86_64/src/lib.rs
+++ b/x86_64/src/lib.rs
@@ -2,11 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#![cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+
mod fdt;
-const E820_RAM: u32 = 1;
const SETUP_DTB: u32 = 2;
-const X86_64_FDT_MAX_SIZE: u64 = 0x200000;
+const X86_64_FDT_MAX_SIZE: u64 = 0x20_0000;
#[allow(dead_code)]
#[allow(non_upper_case_globals)]
@@ -45,172 +46,189 @@ mod regs;
mod smbios;
use std::collections::BTreeMap;
-use std::error::Error as StdError;
+use std::convert::TryFrom;
use std::ffi::{CStr, CString};
-use std::fmt::{self, Display};
use std::fs::File;
use std::io::{self, Seek};
use std::mem;
use std::sync::Arc;
use crate::bootparam::boot_params;
-use acpi_tables::aml::Aml;
use acpi_tables::sdt::SDT;
-use arch::{
- get_serial_cmdline, GetSerialCmdlineError, RunnableLinuxVm, SerialHardware, SerialParameters,
- VmComponents, VmImage,
+use acpi_tables::{aml, aml::Aml};
+use arch::{get_serial_cmdline, GetSerialCmdlineError, RunnableLinuxVm, VmComponents, VmImage};
+use base::{warn, Event};
+use devices::serial_device::{SerialHardware, SerialParameters};
+use devices::{
+ BusDeviceObj, BusResumeDevice, IrqChip, IrqChipX86_64, PciAddress, PciConfigIo, PciConfigMmio,
+ PciDevice, PciVirtualConfigMmio,
};
-use base::Event;
-use devices::{IrqChip, IrqChipX86_64, PciConfigIo, PciDevice, ProtectionType};
-use hypervisor::{HypervisorX86_64, VcpuX86_64, VmX86_64};
+use hypervisor::{HypervisorX86_64, ProtectionType, VcpuX86_64, Vm, VmX86_64};
use minijail::Minijail;
use remain::sorted;
-use resources::SystemAllocator;
+use resources::{MemRegion, SystemAllocator, SystemAllocatorConfig};
use sync::Mutex;
+use thiserror::Error;
use vm_control::{BatControl, BatteryType};
use vm_memory::{GuestAddress, GuestMemory, GuestMemoryError};
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
use {
- gdbstub::arch::x86::reg::X86_64CoreRegs,
+ gdbstub_arch::x86::reg::{X86SegmentRegs, X86_64CoreRegs},
hypervisor::x86_64::{Regs, Sregs},
};
#[sorted]
-#[derive(Debug)]
+#[derive(Error, Debug)]
pub enum Error {
+ #[error("error allocating IO resource: {0}")]
AllocateIOResouce(resources::Error),
+ #[error("error allocating a single irq")]
AllocateIrq,
+ #[error("unable to clone an Event: {0}")]
CloneEvent(base::Error),
+ #[error("failed to clone IRQ chip: {0}")]
+ CloneIrqChip(base::Error),
+ #[error("the given kernel command line was invalid: {0}")]
Cmdline(kernel_cmdline::Error),
+ #[error("failed to configure hotplugged pci device: {0}")]
+ ConfigurePciDevice(arch::DeviceRegistrationError),
+ #[error("error configuring the system")]
ConfigureSystem,
+ #[error("unable to create ACPI tables")]
+ CreateAcpi,
+ #[error("unable to create battery devices: {0}")]
CreateBatDevices(arch::DeviceRegistrationError),
- CreateDevices(Box<dyn StdError>),
+ #[error("unable to make an Event: {0}")]
CreateEvent(base::Error),
+ #[error("failed to create fdt: {0}")]
CreateFdt(arch::fdt::Error),
+ #[cfg(feature = "direct")]
+ #[error("failed to enable GPE forwarding: {0}")]
+ CreateGpe(devices::DirectIrqError),
+ #[error("failed to create IOAPIC device: {0}")]
CreateIoapicDevice(base::Error),
- CreateIrqChip(Box<dyn StdError>),
+ #[error("failed to create a PCI root hub: {0}")]
CreatePciRoot(arch::DeviceRegistrationError),
+ #[error("unable to create PIT: {0}")]
CreatePit(base::Error),
+ #[error("unable to make PIT device: {0}")]
CreatePitDevice(devices::PitError),
+ #[error("unable to create serial devices: {0}")]
CreateSerialDevices(arch::DeviceRegistrationError),
+ #[error("failed to create socket: {0}")]
CreateSocket(io::Error),
+ #[error("failed to create VCPU: {0}")]
CreateVcpu(base::Error),
- CreateVm(Box<dyn StdError>),
+ #[error("invalid e820 setup params")]
E820Configuration,
+ #[error("failed to enable singlestep execution: {0}")]
EnableSinglestep(base::Error),
+ #[error("failed to enable split irqchip: {0}")]
EnableSplitIrqchip(base::Error),
+ #[error("failed to get serial cmdline: {0}")]
GetSerialCmdline(GetSerialCmdlineError),
+ #[error("the kernel extends past the end of RAM")]
KernelOffsetPastEnd,
+ #[error("error loading bios: {0}")]
LoadBios(io::Error),
+ #[error("error loading kernel bzImage: {0}")]
LoadBzImage(bzimage::Error),
+ #[error("error loading command line: {0}")]
LoadCmdline(kernel_loader::Error),
+ #[error("error loading initrd: {0}")]
LoadInitrd(arch::LoadImageError),
+ #[error("error loading Kernel: {0}")]
LoadKernel(kernel_loader::Error),
+ #[error("error translating address: Page not present")]
PageNotPresent,
- Pstore(arch::pstore::Error),
+ #[error("error reading guest memory {0}")]
ReadingGuestMemory(vm_memory::GuestMemoryError),
+ #[error("error reading CPU registers {0}")]
ReadRegs(base::Error),
+ #[error("error registering an IrqFd: {0}")]
RegisterIrqfd(base::Error),
+ #[error("error registering virtual socket device: {0}")]
RegisterVsock(arch::DeviceRegistrationError),
+ #[error("failed to set a hardware breakpoint: {0}")]
SetHwBreakpoint(base::Error),
+ #[error("failed to set interrupts: {0}")]
SetLint(interrupts::Error),
+ #[error("failed to set tss addr: {0}")]
SetTssAddr(base::Error),
+ #[error("failed to set up cpuid: {0}")]
SetupCpuid(cpuid::Error),
+ #[error("failed to set up FPU: {0}")]
SetupFpu(regs::Error),
+ #[error("failed to set up guest memory: {0}")]
SetupGuestMemory(GuestMemoryError),
+ #[error("failed to set up mptable: {0}")]
SetupMptable(mptable::Error),
+ #[error("failed to set up MSRs: {0}")]
SetupMsrs(regs::Error),
+ #[error("failed to set up registers: {0}")]
SetupRegs(regs::Error),
+ #[error("failed to set up SMBIOS: {0}")]
SetupSmbios(smbios::Error),
+ #[error("failed to set up sregs: {0}")]
SetupSregs(regs::Error),
+ #[error("failed to translate virtual address")]
TranslatingVirtAddr,
+ #[error("protected VMs not supported on x86_64")]
UnsupportedProtectionType,
+ #[error("error writing CPU registers {0}")]
WriteRegs(base::Error),
+ #[error("error writing guest memory {0}")]
WritingGuestMemory(GuestMemoryError),
+ #[error("the zero page extends past the end of guest_mem")]
ZeroPagePastRamEnd,
+ #[error("error writing the zero page of guest memory")]
ZeroPageSetup,
}
-impl Display for Error {
- #[remain::check]
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- #[sorted]
- match self {
- AllocateIOResouce(e) => write!(f, "error allocating IO resource: {}", e),
- AllocateIrq => write!(f, "error allocating a single irq"),
- CloneEvent(e) => write!(f, "unable to clone an Event: {}", e),
- Cmdline(e) => write!(f, "the given kernel command line was invalid: {}", e),
- ConfigureSystem => write!(f, "error configuring the system"),
- CreateBatDevices(e) => write!(f, "unable to create battery devices: {}", e),
- CreateDevices(e) => write!(f, "error creating devices: {}", e),
- CreateEvent(e) => write!(f, "unable to make an Event: {}", e),
- CreateFdt(e) => write!(f, "failed to create fdt: {}", e),
- CreateIoapicDevice(e) => write!(f, "failed to create IOAPIC device: {}", e),
- CreateIrqChip(e) => write!(f, "failed to create IRQ chip: {}", e),
- CreatePciRoot(e) => write!(f, "failed to create a PCI root hub: {}", e),
- CreatePit(e) => write!(f, "unable to create PIT: {}", e),
- CreatePitDevice(e) => write!(f, "unable to make PIT device: {}", e),
- CreateSerialDevices(e) => write!(f, "unable to create serial devices: {}", e),
- CreateSocket(e) => write!(f, "failed to create socket: {}", e),
- CreateVcpu(e) => write!(f, "failed to create VCPU: {}", e),
- CreateVm(e) => write!(f, "failed to create VM: {}", e),
- E820Configuration => write!(f, "invalid e820 setup params"),
- EnableSinglestep(e) => write!(f, "failed to enable singlestep execution: {}", e),
- EnableSplitIrqchip(e) => write!(f, "failed to enable split irqchip: {}", e),
- GetSerialCmdline(e) => write!(f, "failed to get serial cmdline: {}", e),
- KernelOffsetPastEnd => write!(f, "the kernel extends past the end of RAM"),
- LoadBios(e) => write!(f, "error loading bios: {}", e),
- LoadBzImage(e) => write!(f, "error loading kernel bzImage: {}", e),
- LoadCmdline(e) => write!(f, "error loading command line: {}", e),
- LoadInitrd(e) => write!(f, "error loading initrd: {}", e),
- LoadKernel(e) => write!(f, "error loading Kernel: {}", e),
- PageNotPresent => write!(f, "error translating address: Page not present"),
- Pstore(e) => write!(f, "failed to allocate pstore region: {}", e),
- ReadingGuestMemory(e) => write!(f, "error reading guest memory {}", e),
- ReadRegs(e) => write!(f, "error reading CPU registers {}", e),
- RegisterIrqfd(e) => write!(f, "error registering an IrqFd: {}", e),
- RegisterVsock(e) => write!(f, "error registering virtual socket device: {}", e),
- SetHwBreakpoint(e) => write!(f, "failed to set a hardware breakpoint: {}", e),
- SetLint(e) => write!(f, "failed to set interrupts: {}", e),
- SetTssAddr(e) => write!(f, "failed to set tss addr: {}", e),
- SetupCpuid(e) => write!(f, "failed to set up cpuid: {}", e),
- SetupFpu(e) => write!(f, "failed to set up FPU: {}", e),
- SetupGuestMemory(e) => write!(f, "failed to set up guest memory: {}", e),
- SetupMptable(e) => write!(f, "failed to set up mptable: {}", e),
- SetupMsrs(e) => write!(f, "failed to set up MSRs: {}", e),
- SetupRegs(e) => write!(f, "failed to set up registers: {}", e),
- SetupSmbios(e) => write!(f, "failed to set up SMBIOS: {}", e),
- SetupSregs(e) => write!(f, "failed to set up sregs: {}", e),
- TranslatingVirtAddr => write!(f, "failed to translate virtual address"),
- UnsupportedProtectionType => write!(f, "protected VMs not supported on x86_64"),
- WriteRegs(e) => write!(f, "error writing CPU registers {}", e),
- WritingGuestMemory(e) => write!(f, "error writing guest memory {}", e),
- ZeroPagePastRamEnd => write!(f, "the zero page extends past the end of guest_mem"),
- ZeroPageSetup => write!(f, "error writing the zero page of guest memory"),
- }
- }
-}
-
pub type Result<T> = std::result::Result<T, Error>;
-impl std::error::Error for Error {}
-
pub struct X8664arch;
+enum E820Type {
+ Ram = 0x01,
+ Reserved = 0x2,
+}
+
+const MB: u64 = 1 << 20;
+const GB: u64 = 1 << 30;
+
const BOOT_STACK_POINTER: u64 = 0x8000;
// Make sure it align to 256MB for MTRR convenient
-const MEM_32BIT_GAP_SIZE: u64 = 768 << 20;
+const MEM_32BIT_GAP_SIZE: u64 = if cfg!(feature = "direct") {
+ // Allow space for identity mapping coreboot memory regions on the host
+ // which is found at around 7a00_0000 (little bit before 2GB)
+ //
+ // TODO(b/188011323): stop hardcoding sizes and addresses here and instead
+ // determine the memory map from how the VM has been configured via the
+ // command line.
+ 2560 * MB
+} else {
+ 768 * MB
+};
+const START_OF_RAM_32BITS: u64 = if cfg!(feature = "direct") { 0x1000 } else { 0 };
const FIRST_ADDR_PAST_32BITS: u64 = 1 << 32;
+// Reserved memory for nand_bios/LAPIC/IOAPIC/HPET/.....
+const RESERVED_MEM_SIZE: u64 = 0x800_0000;
+// Reserve 64MB for pcie enhanced configuration
+const PCIE_CFG_MMIO_SIZE: u64 = 0x400_0000;
+const PCIE_CFG_MMIO_START: u64 = FIRST_ADDR_PAST_32BITS - RESERVED_MEM_SIZE - PCIE_CFG_MMIO_SIZE;
+// Reserve memory region for pcie virtual configuration
+const PCIE_VCFG_MMIO_SIZE: u64 = PCIE_CFG_MMIO_SIZE;
const END_ADDR_BEFORE_32BITS: u64 = FIRST_ADDR_PAST_32BITS - MEM_32BIT_GAP_SIZE;
-const MMIO_SIZE: u64 = MEM_32BIT_GAP_SIZE - 0x8000000;
+const PCI_MMIO_SIZE: u64 = MEM_32BIT_GAP_SIZE - RESERVED_MEM_SIZE - PCIE_CFG_MMIO_SIZE;
+// Linux (with 4-level paging) has a physical memory limit of 46 bits (64 TiB).
+const HIGH_MMIO_MAX_END: u64 = 1u64 << 46;
const KERNEL_64BIT_ENTRY_OFFSET: u64 = 0x200;
const ZERO_PAGE_OFFSET: u64 = 0x7000;
-const TSS_ADDR: u64 = 0xfffbd000;
+const TSS_ADDR: u64 = 0xfffb_d000;
-const KERNEL_START_OFFSET: u64 = 0x200000;
-const CMDLINE_OFFSET: u64 = 0x20000;
+const KERNEL_START_OFFSET: u64 = 0x20_0000;
+const CMDLINE_OFFSET: u64 = 0x2_0000;
const CMDLINE_MAX_SIZE: u64 = KERNEL_START_OFFSET - CMDLINE_OFFSET;
const X86_64_SERIAL_1_3_IRQ: u32 = 4;
const X86_64_SERIAL_2_4_IRQ: u32 = 3;
@@ -223,18 +241,17 @@ const X86_64_SERIAL_2_4_IRQ: u32 = 3;
pub const X86_64_SCI_IRQ: u32 = 5;
// The CMOS RTC uses IRQ 8; start allocating IRQs at 9.
pub const X86_64_IRQ_BASE: u32 = 9;
-const ACPI_HI_RSDP_WINDOW_BASE: u64 = 0x000E0000;
+const ACPI_HI_RSDP_WINDOW_BASE: u64 = 0x000E_0000;
/// The x86 reset vector for i386+ and x86_64 puts the processor into an "unreal mode" where it
/// can access the last 1 MB of the 32-bit address space in 16-bit mode, and starts the instruction
-/// pointer at the effective physical address 0xFFFFFFF0.
+/// pointer at the effective physical address 0xFFFF_FFF0.
fn bios_start(bios_size: u64) -> GuestAddress {
GuestAddress(FIRST_ADDR_PAST_32BITS - bios_size)
}
fn configure_system(
guest_mem: &GuestMemory,
- _mem_size: u64,
kernel_addr: GuestAddress,
cmdline_addr: GuestAddress,
cmdline_size: usize,
@@ -242,11 +259,11 @@ fn configure_system(
initrd: Option<(GuestAddress, usize)>,
mut params: boot_params,
) -> Result<()> {
- const EBDA_START: u64 = 0x0009fc00;
+ const EBDA_START: u64 = 0x0009_fc00;
const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55;
- const KERNEL_HDR_MAGIC: u32 = 0x53726448;
+ const KERNEL_HDR_MAGIC: u32 = 0x5372_6448;
const KERNEL_LOADER_OTHER: u8 = 0xff;
- const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; // Must be non-zero.
+ const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x100_0000; // Must be non-zero.
let first_addr_past_32bits = GuestAddress(FIRST_ADDR_PAST_32BITS);
let end_32bit_gap_start = GuestAddress(END_ADDR_BEFORE_32BITS);
@@ -264,7 +281,12 @@ fn configure_system(
params.hdr.ramdisk_size = initrd_size as u32;
}
- add_e820_entry(&mut params, 0, EBDA_START, E820_RAM)?;
+ add_e820_entry(
+ &mut params,
+ START_OF_RAM_32BITS,
+ EBDA_START - START_OF_RAM_32BITS,
+ E820Type::Ram,
+ )?;
let mem_end = guest_mem.end_addr();
if mem_end < end_32bit_gap_start {
@@ -272,25 +294,39 @@ fn configure_system(
&mut params,
kernel_addr.offset() as u64,
mem_end.offset_from(kernel_addr) as u64,
- E820_RAM,
+ E820Type::Ram,
)?;
} else {
add_e820_entry(
&mut params,
kernel_addr.offset() as u64,
end_32bit_gap_start.offset_from(kernel_addr) as u64,
- E820_RAM,
+ E820Type::Ram,
)?;
if mem_end > first_addr_past_32bits {
add_e820_entry(
&mut params,
first_addr_past_32bits.offset() as u64,
mem_end.offset_from(first_addr_past_32bits) as u64,
- E820_RAM,
+ E820Type::Ram,
)?;
}
}
+ add_e820_entry(
+ &mut params,
+ PCIE_CFG_MMIO_START,
+ PCIE_CFG_MMIO_SIZE,
+ E820Type::Reserved,
+ )?;
+
+ add_e820_entry(
+ &mut params,
+ X8664arch::get_pcie_vcfg_mmio_base(guest_mem),
+ PCIE_VCFG_MMIO_SIZE,
+ E820Type::Reserved,
+ )?;
+
let zero_page_addr = GuestAddress(ZERO_PAGE_OFFSET);
guest_mem
.checked_offset(zero_page_addr, mem::size_of::<boot_params>() as u64)
@@ -304,14 +340,19 @@ fn configure_system(
/// Add an e820 region to the e820 map.
/// Returns Ok(()) if successful, or an error if there is no space left in the map.
-fn add_e820_entry(params: &mut boot_params, addr: u64, size: u64, mem_type: u32) -> Result<()> {
+fn add_e820_entry(
+ params: &mut boot_params,
+ addr: u64,
+ size: u64,
+ mem_type: E820Type,
+) -> Result<()> {
if params.e820_entries >= params.e820_table.len() as u8 {
return Err(Error::E820Configuration);
}
params.e820_table[params.e820_entries as usize].addr = addr;
params.e820_table[params.e820_entries as usize].size = size;
- params.e820_table[params.e820_entries as usize].type_ = mem_type;
+ params.e820_table[params.e820_entries as usize].type_ = mem_type as u32;
params.e820_entries += 1;
Ok(())
@@ -322,18 +363,21 @@ fn add_e820_entry(params: &mut boot_params, addr: u64, size: u64, mem_type: u32)
/// For x86_64 all addresses are valid from the start of the kernel except a
/// carve out at the end of 32bit address space.
fn arch_memory_regions(size: u64, bios_size: Option<u64>) -> Vec<(GuestAddress, u64)> {
- let mem_end = GuestAddress(size);
+ let mem_start = START_OF_RAM_32BITS;
+ let mem_end = GuestAddress(size + mem_start);
let first_addr_past_32bits = GuestAddress(FIRST_ADDR_PAST_32BITS);
let end_32bit_gap_start = GuestAddress(END_ADDR_BEFORE_32BITS);
-
let mut regions = Vec::new();
if mem_end <= end_32bit_gap_start {
- regions.push((GuestAddress(0), size));
+ regions.push((GuestAddress(mem_start), size));
if let Some(bios_size) = bios_size {
regions.push((bios_start(bios_size), bios_size));
}
} else {
- regions.push((GuestAddress(0), end_32bit_gap_start.offset()));
+ regions.push((
+ GuestAddress(mem_start),
+ end_32bit_gap_start.offset() - mem_start,
+ ));
if let Some(bios_size) = bios_size {
regions.push((bios_start(bios_size), bios_size));
}
@@ -359,99 +403,164 @@ impl arch::LinuxArch for X8664arch {
Ok(arch_memory_regions(components.memory_size, bios_size))
}
- fn build_vm<V, Vcpu, I, FD, FI, E1, E2>(
+ fn get_system_allocator_config<V: Vm>(vm: &V) -> SystemAllocatorConfig {
+ let guest_mem = vm.get_memory();
+ let high_mmio_start = Self::get_high_mmio_base(guest_mem);
+ let high_mmio_size = Self::get_high_mmio_size(vm);
+ SystemAllocatorConfig {
+ io: Some(MemRegion {
+ base: 0xc000,
+ size: 0x4000,
+ }),
+ low_mmio: MemRegion {
+ base: END_ADDR_BEFORE_32BITS,
+ size: PCI_MMIO_SIZE,
+ },
+ high_mmio: MemRegion {
+ base: high_mmio_start,
+ size: high_mmio_size,
+ },
+ platform_mmio: None,
+ first_irq: X86_64_IRQ_BASE,
+ }
+ }
+
+ fn build_vm<V, Vcpu>(
mut components: VmComponents,
+ exit_evt: &Event,
+ reset_evt: &Event,
+ system_allocator: &mut SystemAllocator,
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
serial_jail: Option<Minijail>,
battery: (&Option<BatteryType>, Option<Minijail>),
mut vm: V,
- create_devices: FD,
- create_irq_chip: FI,
- ) -> std::result::Result<RunnableLinuxVm<V, Vcpu, I>, Self::Error>
+ ramoops_region: Option<arch::pstore::RamoopsRegion>,
+ devs: Vec<(Box<dyn BusDeviceObj>, Option<Minijail>)>,
+ irq_chip: &mut dyn IrqChipX86_64,
+ kvm_vcpu_ids: &mut Vec<usize>,
+ ) -> std::result::Result<RunnableLinuxVm<V, Vcpu>, Self::Error>
where
V: VmX86_64,
Vcpu: VcpuX86_64,
- I: IrqChipX86_64,
- FD: FnOnce(
- &GuestMemory,
- &mut V,
- &mut SystemAllocator,
- &Event,
- ) -> std::result::Result<Vec<(Box<dyn PciDevice>, Option<Minijail>)>, E1>,
- FI: FnOnce(&V, /* vcpu_count: */ usize) -> std::result::Result<I, E2>,
- E1: StdError + 'static,
- E2: StdError + 'static,
{
if components.protected_vm != ProtectionType::Unprotected {
return Err(Error::UnsupportedProtectionType);
}
let mem = vm.get_memory().clone();
- let mut resources = Self::get_resource_allocator(&mem);
let vcpu_count = components.vcpu_count;
- let mut irq_chip =
- create_irq_chip(&vm, vcpu_count).map_err(|e| Error::CreateIrqChip(Box::new(e)))?;
let tss_addr = GuestAddress(TSS_ADDR);
vm.set_tss_addr(tss_addr).map_err(Error::SetTssAddr)?;
- let mut mmio_bus = devices::Bus::new();
+ // Use IRQ info in ACPI if provided by the user.
+ let mut noirq = true;
+ let mut mptable = true;
+ let mut sci_irq = X86_64_SCI_IRQ;
+
+ for sdt in components.acpi_sdts.iter() {
+ if sdt.is_signature(b"DSDT") || sdt.is_signature(b"APIC") {
+ noirq = false;
+ } else if sdt.is_signature(b"FACP") {
+ mptable = false;
+ let sci_irq_fadt: u16 = sdt.read(acpi::FADT_FIELD_SCI_INTERRUPT);
+ sci_irq = sci_irq_fadt.into();
+ if !system_allocator.reserve_irq(sci_irq) {
+ warn!("sci irq {} already reserved.", sci_irq);
+ }
+ }
+ }
+
+ let mmio_bus = Arc::new(devices::Bus::new());
+ let io_bus = Arc::new(devices::Bus::new());
- let exit_evt = Event::new().map_err(Error::CreateEvent)?;
+ let (pci_devices, _others): (Vec<_>, Vec<_>) = devs
+ .into_iter()
+ .partition(|(dev, _)| dev.as_pci_device().is_some());
+
+ let pci_devices = pci_devices
+ .into_iter()
+ .map(|(dev, jail_orig)| (dev.into_pci_device().unwrap(), jail_orig))
+ .collect();
- let pci_devices = create_devices(&mem, &mut vm, &mut resources, &exit_evt)
- .map_err(|e| Error::CreateDevices(Box::new(e)))?;
let (pci, pci_irqs, pid_debug_label_map) = arch::generate_pci_root(
pci_devices,
- &mut irq_chip,
- &mut mmio_bus,
- &mut resources,
+ irq_chip.as_irq_chip_mut(),
+ mmio_bus.clone(),
+ io_bus.clone(),
+ system_allocator,
&mut vm,
4, // Share the four pin interrupts (INTx#)
)
.map_err(Error::CreatePciRoot)?;
- let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(pci)));
+
+ let pci = Arc::new(Mutex::new(pci));
+ pci.lock().enable_pcie_cfg_mmio(PCIE_CFG_MMIO_START);
+ let pci_cfg = PciConfigIo::new(
+ pci.clone(),
+ reset_evt.try_clone().map_err(Error::CloneEvent)?,
+ );
+ let pci_bus = Arc::new(Mutex::new(pci_cfg));
+ io_bus.insert(pci_bus, 0xcf8, 0x8).unwrap();
+
+ let pcie_cfg_mmio = Arc::new(Mutex::new(PciConfigMmio::new(pci.clone(), 12)));
+ mmio_bus
+ .insert(pcie_cfg_mmio, PCIE_CFG_MMIO_START, PCIE_CFG_MMIO_SIZE)
+ .unwrap();
+
+ let pcie_vcfg_mmio = Arc::new(Mutex::new(PciVirtualConfigMmio::new(pci.clone(), 12)));
+ mmio_bus
+ .insert(
+ pcie_vcfg_mmio,
+ Self::get_pcie_vcfg_mmio_base(&mem),
+ PCIE_VCFG_MMIO_SIZE,
+ )
+ .unwrap();
// Event used to notify crosvm that guest OS is trying to suspend.
let suspend_evt = Event::new().map_err(Error::CreateEvent)?;
- let mut io_bus = Self::setup_io_bus(
- irq_chip.pit_uses_speaker_port(),
- exit_evt.try_clone().map_err(Error::CloneEvent)?,
- Some(pci_bus),
- components.memory_size,
- )?;
-
+ if !components.no_legacy {
+ Self::setup_legacy_devices(
+ &io_bus,
+ irq_chip.pit_uses_speaker_port(),
+ reset_evt.try_clone().map_err(Error::CloneEvent)?,
+ components.memory_size,
+ )?;
+ }
Self::setup_serial_devices(
components.protected_vm,
- &mut irq_chip,
- &mut io_bus,
+ irq_chip.as_irq_chip_mut(),
+ &io_bus,
serial_parameters,
serial_jail,
)?;
+ let mut resume_notify_devices = Vec::new();
+
+ // each bus occupy 1MB mmio for pcie enhanced configuration
+ let max_bus = ((PCIE_CFG_MMIO_SIZE / 0x100000) - 1) as u8;
+
let (acpi_dev_resource, bat_control) = Self::setup_acpi_devices(
- &mut io_bus,
- &mut resources,
+ &mem,
+ &io_bus,
+ system_allocator,
suspend_evt.try_clone().map_err(Error::CloneEvent)?,
exit_evt.try_clone().map_err(Error::CloneEvent)?,
components.acpi_sdts,
- &mut irq_chip,
+ #[cfg(feature = "direct")]
+ &components.direct_gpe,
+ irq_chip.as_irq_chip_mut(),
+ sci_irq,
battery,
- &mut mmio_bus,
+ &mmio_bus,
+ max_bus,
+ &mut resume_notify_devices,
)?;
- let ramoops_region = match components.pstore {
- Some(pstore) => Some(
- arch::pstore::create_memory_region(&mut vm, &mut resources, &pstore)
- .map_err(Error::Pstore)?,
- ),
- None => None,
- };
-
irq_chip
- .finalize_devices(&mut resources, &mut io_bus, &mut mmio_bus)
+ .finalize_devices(system_allocator, &io_bus, &mmio_bus)
.map_err(Error::RegisterIrqfd)?;
// All of these bios generated tables are set manually for the benefit of the kernel boot
@@ -462,50 +571,72 @@ impl arch::LinuxArch for X8664arch {
// If another guest does need a way to pass these tables down to it's BIOS, this approach
// should be rethought.
- // Note that this puts the mptable at 0x9FC00 in guest physical memory.
- mptable::setup_mptable(&mem, vcpu_count as u8, pci_irqs).map_err(Error::SetupMptable)?;
+ if mptable {
+ // Note that this puts the mptable at 0x9FC00 in guest physical memory.
+ mptable::setup_mptable(&mem, vcpu_count as u8, &pci_irqs)
+ .map_err(Error::SetupMptable)?;
+ }
smbios::setup_smbios(&mem, components.dmi_path).map_err(Error::SetupSmbios)?;
+ let host_cpus = if components.host_cpu_topology {
+ components.vcpu_affinity.clone()
+ } else {
+ None
+ };
+
// TODO (tjeznach) Write RSDP to bootconfig before writing to memory
- acpi::create_acpi_tables(&mem, vcpu_count as u8, X86_64_SCI_IRQ, acpi_dev_resource);
+ acpi::create_acpi_tables(
+ &mem,
+ vcpu_count as u8,
+ sci_irq,
+ 0xcf9,
+ 6, // RST_CPU|SYS_RST
+ &acpi_dev_resource,
+ host_cpus,
+ kvm_vcpu_ids,
+ &pci_irqs,
+ PCIE_CFG_MMIO_START,
+ max_bus,
+ components.force_s2idle,
+ )
+ .ok_or(Error::CreateAcpi)?;
- match components.vm_image {
- VmImage::Bios(ref mut bios) => Self::load_bios(&mem, bios)?,
- VmImage::Kernel(ref mut kernel_image) => {
- let mut cmdline = Self::get_base_linux_cmdline();
+ let mut cmdline = Self::get_base_linux_cmdline();
- get_serial_cmdline(&mut cmdline, serial_parameters, "io")
- .map_err(Error::GetSerialCmdline)?;
+ if noirq {
+ cmdline.insert_str("acpi=noirq").unwrap();
+ }
- for param in components.extra_kernel_params {
- cmdline.insert_str(&param).map_err(Error::Cmdline)?;
- }
- // It seems that default record_size is only 4096 byte even if crosvm allocates
- // more memory. It means that one crash can only 4096 byte.
- // Set record_size and console_size to 1/4 of allocated memory size.
- // This configulation is same as the host.
- if let Some(ramoops_region) = ramoops_region {
- let ramoops_opts = [
- ("mem_address", ramoops_region.address),
- ("mem_size", ramoops_region.size as u64),
- ("console_size", (ramoops_region.size / 4) as u64),
- ("record_size", (ramoops_region.size / 4) as u64),
- ("dump_oops", 1_u64),
- ];
- for (name, val) in &ramoops_opts {
- cmdline
- .insert_str(format!("ramoops.{}={:#x}", name, val))
- .map_err(Error::Cmdline)?;
- }
- }
+ get_serial_cmdline(&mut cmdline, serial_parameters, "io")
+ .map_err(Error::GetSerialCmdline)?;
+ for param in components.extra_kernel_params {
+ cmdline.insert_str(&param).map_err(Error::Cmdline)?;
+ }
+
+ if let Some(ramoops_region) = ramoops_region {
+ arch::pstore::add_ramoops_kernel_cmdline(&mut cmdline, &ramoops_region)
+ .map_err(Error::Cmdline)?;
+ }
+
+ match components.vm_image {
+ VmImage::Bios(ref mut bios) => {
+ // Allow a bios to hardcode CMDLINE_OFFSET and read the kernel command line from it.
+ kernel_loader::load_cmdline(
+ &mem,
+ GuestAddress(CMDLINE_OFFSET),
+ &CString::new(cmdline).unwrap(),
+ )
+ .map_err(Error::LoadCmdline)?;
+ Self::load_bios(&mem, bios)?
+ }
+ VmImage::Kernel(ref mut kernel_image) => {
// separate out load_kernel from other setup to get a specific error for
// kernel loading
let (params, kernel_end) = Self::load_kernel(&mem, kernel_image)?;
Self::setup_system_memory(
&mem,
- components.memory_size,
&CString::new(cmdline).unwrap(),
components.initrd_image,
components.android_fstab,
@@ -517,27 +648,30 @@ impl arch::LinuxArch for X8664arch {
Ok(RunnableLinuxVm {
vm,
- resources,
- exit_evt,
vcpu_count,
vcpus: None,
vcpu_affinity: components.vcpu_affinity,
no_smt: components.no_smt,
- irq_chip,
+ irq_chip: irq_chip.try_box_clone().map_err(Error::CloneIrqChip)?,
has_bios: matches!(components.vm_image, VmImage::Bios(_)),
io_bus,
mmio_bus,
pid_debug_label_map,
suspend_evt,
+ resume_notify_devices,
rt_cpus: components.rt_cpus,
+ delay_rt: components.delay_rt,
bat_control,
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
gdb: components.gdb,
+ pm: Some(acpi_dev_resource.pm),
+ root_config: pci,
+ hotplug_bus: Vec::new(),
})
}
- fn configure_vcpu(
- guest_mem: &GuestMemory,
+ fn configure_vcpu<V: Vm>(
+ vm: &V,
hypervisor: &dyn HypervisorX86_64,
irq_chip: &mut dyn IrqChipX86_64,
vcpu: &mut dyn VcpuX86_64,
@@ -545,16 +679,26 @@ impl arch::LinuxArch for X8664arch {
num_cpus: usize,
has_bios: bool,
no_smt: bool,
+ host_cpu_topology: bool,
) -> Result<()> {
- cpuid::setup_cpuid(hypervisor, irq_chip, vcpu, vcpu_id, num_cpus, no_smt)
- .map_err(Error::SetupCpuid)?;
+ cpuid::setup_cpuid(
+ hypervisor,
+ irq_chip,
+ vcpu,
+ vcpu_id,
+ num_cpus,
+ no_smt,
+ host_cpu_topology,
+ )
+ .map_err(Error::SetupCpuid)?;
if has_bios {
return Ok(());
}
+ let guest_mem = vm.get_memory();
let kernel_load_addr = GuestAddress(KERNEL_START_OFFSET);
- regs::setup_msrs(vcpu, END_ADDR_BEFORE_32BITS).map_err(Error::SetupMsrs)?;
+ regs::setup_msrs(vm, vcpu, END_ADDR_BEFORE_32BITS).map_err(Error::SetupMsrs)?;
let kernel_end = guest_mem
.checked_offset(kernel_load_addr, KERNEL_64BIT_ENTRY_OFFSET)
.ok_or(Error::KernelOffsetPastEnd)?;
@@ -572,6 +716,18 @@ impl arch::LinuxArch for X8664arch {
Ok(())
}
+ fn register_pci_device<V: VmX86_64, Vcpu: VcpuX86_64>(
+ linux: &mut RunnableLinuxVm<V, Vcpu>,
+ device: Box<dyn PciDevice>,
+ minijail: Option<Minijail>,
+ resources: &mut SystemAllocator,
+ ) -> Result<PciAddress> {
+ let pci_address = arch::configure_pci_device(linux, device, minijail, resources)
+ .map_err(Error::ConfigurePciDevice)?;
+
+ Ok(pci_address)
+ }
+
#[cfg(all(target_arch = "x86_64", feature = "gdb"))]
fn debug_read_registers<T: VcpuX86_64>(vcpu: &T) -> Result<X86_64CoreRegs> {
// General registers: RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, r8-r15
@@ -588,12 +744,14 @@ impl arch::LinuxArch for X8664arch {
// Segment registers: CS, SS, DS, ES, FS, GS
let sregs = vcpu.get_sregs().map_err(Error::ReadRegs)?;
- let sgs = [sregs.cs, sregs.ss, sregs.ds, sregs.es, sregs.fs, sregs.gs];
- let mut segments = [0u32; 6];
- // GDB uses only the selectors.
- for i in 0..sgs.len() {
- segments[i] = sgs[i].selector as u32;
- }
+ let segments = X86SegmentRegs {
+ cs: sregs.cs.selector as u32,
+ ss: sregs.ss.selector as u32,
+ ds: sregs.ds.selector as u32,
+ es: sregs.es.selector as u32,
+ fs: sregs.fs.selector as u32,
+ gs: sregs.gs.selector as u32,
+ };
// TODO(keiichiw): Other registers such as FPU, xmm and mxcsr.
@@ -636,12 +794,12 @@ impl arch::LinuxArch for X8664arch {
// Segment registers: CS, SS, DS, ES, FS, GS
// Since GDB care only selectors, we call get_sregs() first.
let mut sregs = vcpu.get_sregs().map_err(Error::ReadRegs)?;
- sregs.cs.selector = regs.segments[0] as u16;
- sregs.ss.selector = regs.segments[1] as u16;
- sregs.ds.selector = regs.segments[2] as u16;
- sregs.es.selector = regs.segments[3] as u16;
- sregs.fs.selector = regs.segments[4] as u16;
- sregs.gs.selector = regs.segments[5] as u16;
+ sregs.cs.selector = regs.segments.cs as u16;
+ sregs.ss.selector = regs.segments.ss as u16;
+ sregs.ds.selector = regs.segments.ds as u16;
+ sregs.es.selector = regs.segments.es as u16;
+ sregs.fs.selector = regs.segments.fs as u16;
+ sregs.gs.selector = regs.segments.gs as u16;
vcpu.set_sregs(&sregs).map_err(Error::WriteRegs)?;
@@ -713,7 +871,7 @@ impl arch::LinuxArch for X8664arch {
vcpu: &T,
breakpoints: &[GuestAddress],
) -> Result<()> {
- vcpu.set_guest_debug(&breakpoints, false /* enable_singlestep */)
+ vcpu.set_guest_debug(breakpoints, false /* enable_singlestep */)
.map_err(Error::SetHwBreakpoint)
}
}
@@ -801,6 +959,89 @@ fn phys_addr(mem: &GuestMemory, vaddr: u64, sregs: &Sregs) -> Result<(u64, u64)>
Err(Error::TranslatingVirtAddr)
}
+// OSC returned status register in CDW1
+const OSC_STATUS_UNSUPPORT_UUID: u32 = 0x4;
+// pci host bridge OSC returned control register in CDW3
+#[allow(dead_code)]
+const PCI_HB_OSC_CONTROL_PCIE_HP: u32 = 0x1;
+const PCI_HB_OSC_CONTROL_SHPC_HP: u32 = 0x2;
+const PCI_HB_OSC_CONTROL_PCIE_PME: u32 = 0x4;
+const PCI_HB_OSC_CONTROL_PCIE_AER: u32 = 0x8;
+#[allow(dead_code)]
+const PCI_HB_OSC_CONTROL_PCIE_CAP: u32 = 0x10;
+
+struct PciRootOSC {}
+
+// Method (_OSC, 4, NotSerialized) // _OSC: Operating System Capabilities
+// {
+// CreateDWordField (Arg3, Zero, CDW1) // flag and return value
+// If (Arg0 == ToUUID ("33db4d5b-1ff7-401c-9657-7441c03dd766"))
+// {
+// CreateDWordField (Arg3, 8, CDW3) // control field
+// if ( 0 == (CDW1 & 0x01)) // Query flag ?
+// {
+// CDW3 &= !(SHPC_HP | PME | AER)
+// }
+// } Else {
+// CDW1 |= UNSUPPORT_UUID
+// }
+// Return (Arg3)
+// }
+impl Aml for PciRootOSC {
+ fn to_aml_bytes(&self, aml: &mut Vec<u8>) {
+ let osc_uuid = "33DB4D5B-1FF7-401C-9657-7441C03DD766";
+ // virtual pcie root port supports hotplug and pcie cap register only, clear all
+ // the other bits.
+ let mask = !(PCI_HB_OSC_CONTROL_SHPC_HP
+ | PCI_HB_OSC_CONTROL_PCIE_PME
+ | PCI_HB_OSC_CONTROL_PCIE_AER);
+ aml::Method::new(
+ "_OSC".into(),
+ 4,
+ false,
+ vec![
+ &aml::CreateDWordField::new(
+ &aml::Name::new_field_name("CDW1"),
+ &aml::Arg(3),
+ &aml::ZERO,
+ ),
+ &aml::If::new(
+ &aml::Equal::new(&aml::Arg(0), &aml::Uuid::new(osc_uuid)),
+ vec![
+ &aml::CreateDWordField::new(
+ &aml::Name::new_field_name("CDW3"),
+ &aml::Arg(3),
+ &(8_u8),
+ ),
+ &aml::If::new(
+ &aml::Equal::new(
+ &aml::ZERO,
+ &aml::And::new(
+ &aml::Local(0),
+ &aml::Name::new_field_name("CDW1"),
+ &aml::ONE,
+ ),
+ ),
+ vec![&aml::And::new(
+ &aml::Name::new_field_name("CDW3"),
+ &mask,
+ &aml::Name::new_field_name("CDW3"),
+ )],
+ ),
+ ],
+ ),
+ &aml::Else::new(vec![&aml::Or::new(
+ &aml::Name::new_field_name("CDW1"),
+ &OSC_STATUS_UNSUPPORT_UUID,
+ &aml::Name::new_field_name("CDW1"),
+ )]),
+ &aml::Return::new(&aml::Arg(3)),
+ ],
+ )
+ .to_aml_bytes(aml)
+ }
+}
+
impl X8664arch {
/// Loads the bios from an open file.
///
@@ -861,7 +1102,6 @@ impl X8664arch {
/// * `initrd_file` - an initial ramdisk image
fn setup_system_memory(
mem: &GuestMemory,
- mem_size: u64,
cmdline: &CStr,
initrd_file: Option<File>,
android_fstab: Option<File>,
@@ -919,7 +1159,6 @@ impl X8664arch {
configure_system(
mem,
- mem_size,
GuestAddress(KERNEL_START_OFFSET),
GuestAddress(CMDLINE_OFFSET),
cmdline.to_bytes().len() + 1,
@@ -930,51 +1169,54 @@ impl X8664arch {
Ok(())
}
+ fn get_pcie_vcfg_mmio_base(mem: &GuestMemory) -> u64 {
+ // Put PCIe VCFG region at a 2MB boundary after physical memory or 4gb, whichever is greater.
+ let ram_end_round_2mb = (mem.end_addr().offset() + 2 * MB - 1) / (2 * MB) * (2 * MB);
+ std::cmp::max(ram_end_round_2mb, 4 * GB)
+ }
+
/// This returns the start address of high mmio
///
/// # Arguments
///
/// * mem: The memory to be used by the guest
fn get_high_mmio_base(mem: &GuestMemory) -> u64 {
- // Put device memory at a 2MB boundary after physical memory or 4gb, whichever is greater.
- const MB: u64 = 1 << 20;
- const GB: u64 = 1 << 30;
- let ram_end_round_2mb = (mem.end_addr().offset() + 2 * MB - 1) / (2 * MB) * (2 * MB);
- std::cmp::max(ram_end_round_2mb, 4 * GB)
+ Self::get_pcie_vcfg_mmio_base(mem) + PCIE_VCFG_MMIO_SIZE
+ }
+
+ /// This returns the size of high mmio
+ ///
+ /// # Arguments
+ ///
+ /// * `vm`: The virtual machine
+ fn get_high_mmio_size<V: Vm>(vm: &V) -> u64 {
+ let phys_mem_end = 1u64 << vm.get_guest_phys_addr_bits();
+ let high_mmio_end = std::cmp::min(phys_mem_end, HIGH_MMIO_MAX_END);
+ high_mmio_end - Self::get_high_mmio_base(vm.get_memory())
}
/// This returns a minimal kernel command for this architecture
fn get_base_linux_cmdline() -> kernel_cmdline::Cmdline {
let mut cmdline = kernel_cmdline::Cmdline::new(CMDLINE_MAX_SIZE as usize);
- cmdline.insert_str("pci=noacpi reboot=k panic=-1").unwrap();
+ cmdline.insert_str("panic=-1").unwrap();
cmdline
}
- /// Returns a system resource allocator.
- fn get_resource_allocator(mem: &GuestMemory) -> SystemAllocator {
- let high_mmio_start = Self::get_high_mmio_base(mem);
- SystemAllocator::builder()
- .add_io_addresses(0xc000, 0x10000)
- .add_low_mmio_addresses(END_ADDR_BEFORE_32BITS, MMIO_SIZE)
- .add_high_mmio_addresses(high_mmio_start, u64::max_value() - high_mmio_start)
- .create_allocator(X86_64_IRQ_BASE)
- .unwrap()
- }
-
- /// Sets up the IO bus for this platform
+ /// Sets up the legacy x86 IO platform devices
///
/// # Arguments
///
+ /// * - `io_bus` - the IO bus object
/// * - `pit_uses_speaker_port` - does the PIT use port 0x61 for the PC speaker
- /// * - `exit_evt` - the event object which should receive exit events
+ /// * - `reset_evt` - the event object which should receive exit events
/// * - `mem_size` - the size in bytes of physical ram for the guest
- fn setup_io_bus(
+ fn setup_legacy_devices(
+ io_bus: &devices::Bus,
pit_uses_speaker_port: bool,
- exit_evt: Event,
- pci: Option<Arc<Mutex<devices::PciConfigIo>>>,
+ reset_evt: Event,
mem_size: u64,
- ) -> Result<devices::Bus> {
+ ) -> Result<()> {
struct NoDevice;
impl devices::BusDevice for NoDevice {
fn debug_label(&self) -> String {
@@ -982,8 +1224,6 @@ impl X8664arch {
}
}
- let mut io_bus = devices::Bus::new();
-
let mem_regions = arch_memory_regions(mem_size, None);
let mem_below_4g = mem_regions
@@ -1008,7 +1248,7 @@ impl X8664arch {
let nul_device = Arc::new(Mutex::new(NoDevice));
let i8042 = Arc::new(Mutex::new(devices::I8042Device::new(
- exit_evt.try_clone().map_err(Error::CloneEvent)?,
+ reset_evt.try_clone().map_err(Error::CloneEvent)?,
)));
if pit_uses_speaker_port {
@@ -1018,16 +1258,9 @@ impl X8664arch {
}
io_bus.insert(nul_device.clone(), 0x0ed, 0x1).unwrap(); // most likely this one does nothing
- io_bus.insert(nul_device.clone(), 0x0f0, 0x2).unwrap(); // ignore fpu
-
- if let Some(pci_root) = pci {
- io_bus.insert(pci_root, 0xcf8, 0x8).unwrap();
- } else {
- // ignore pci.
- io_bus.insert(nul_device, 0xcf8, 0x8).unwrap();
- }
+ io_bus.insert(nul_device, 0x0f0, 0x2).unwrap(); // ignore fpu
- Ok(io_bus)
+ Ok(())
}
/// Sets up the acpi devices for this platform and
@@ -1044,18 +1277,40 @@ impl X8664arch {
/// * - `battery` indicate whether to create the battery
/// * - `mmio_bus` the MMIO bus to add the devices to
fn setup_acpi_devices(
- io_bus: &mut devices::Bus,
+ mem: &GuestMemory,
+ io_bus: &devices::Bus,
resources: &mut SystemAllocator,
suspend_evt: Event,
exit_evt: Event,
sdts: Vec<SDT>,
- irq_chip: &mut impl IrqChip,
+ #[cfg(feature = "direct")] direct_gpe: &[u32],
+ irq_chip: &mut dyn IrqChip,
+ sci_irq: u32,
battery: (&Option<BatteryType>, Option<Minijail>),
- mmio_bus: &mut devices::Bus,
- ) -> Result<(acpi::ACPIDevResource, Option<BatControl>)> {
+ mmio_bus: &devices::Bus,
+ max_bus: u8,
+ resume_notify_devices: &mut Vec<Arc<Mutex<dyn BusResumeDevice>>>,
+ ) -> Result<(acpi::AcpiDevResource, Option<BatControl>)> {
// The AML data for the acpi devices
let mut amls = Vec::new();
+ let bat_control = if let Some(battery_type) = battery.0 {
+ match battery_type {
+ BatteryType::Goldfish => {
+ let control_tube = arch::add_goldfish_battery(
+ &mut amls, battery.1, mmio_bus, irq_chip, sci_irq, resources,
+ )
+ .map_err(Error::CreateBatDevices)?;
+ Some(BatControl {
+ type_: BatteryType::Goldfish,
+ control_tube,
+ })
+ }
+ }
+ } else {
+ None
+ };
+
let pm_alloc = resources.get_anon_alloc();
let pm_iobase = match resources.io_allocator() {
Some(io) => io
@@ -1063,14 +1318,95 @@ impl X8664arch {
devices::acpi::ACPIPM_RESOURCE_LEN as u64,
pm_alloc,
"ACPIPM".to_string(),
- devices::acpi::ACPIPM_RESOURCE_LEN as u64,
+ 4, // must be 32-bit aligned
)
.map_err(Error::AllocateIOResouce)?,
None => 0x600,
};
- let pmresource = devices::ACPIPMResource::new(suspend_evt, exit_evt);
- Aml::to_aml_bytes(&pmresource, &mut amls);
+ let pcie_vcfg = aml::Name::new("VCFG".into(), &Self::get_pcie_vcfg_mmio_base(mem));
+ pcie_vcfg.to_aml_bytes(&mut amls);
+
+ let pm_sci_evt = devices::IrqLevelEvent::new().map_err(Error::CreateEvent)?;
+ irq_chip
+ .register_level_irq_event(sci_irq, &pm_sci_evt)
+ .map_err(Error::RegisterIrqfd)?;
+
+ #[cfg(feature = "direct")]
+ let direct_gpe_info = if direct_gpe.is_empty() {
+ None
+ } else {
+ let direct_sci_evt = devices::IrqLevelEvent::new().map_err(Error::CreateEvent)?;
+ let mut sci_devirq =
+ devices::DirectIrq::new_level(&direct_sci_evt).map_err(Error::CreateGpe)?;
+
+ sci_devirq.sci_irq_prepare().map_err(Error::CreateGpe)?;
+
+ for gpe in direct_gpe {
+ sci_devirq
+ .gpe_enable_forwarding(*gpe)
+ .map_err(Error::CreateGpe)?;
+ }
+
+ Some((direct_sci_evt, direct_gpe))
+ };
+
+ let mut pmresource = devices::ACPIPMResource::new(
+ pm_sci_evt,
+ #[cfg(feature = "direct")]
+ direct_gpe_info,
+ suspend_evt,
+ exit_evt,
+ );
+ pmresource.to_aml_bytes(&mut amls);
+ pmresource.start();
+
+ let mut crs_entries: Vec<Box<dyn Aml>> = vec![
+ Box::new(aml::AddressSpace::new_bus_number(0x0u16, max_bus as u16)),
+ Box::new(aml::IO::new(0xcf8, 0xcf8, 1, 0x8)),
+ ];
+ for r in resources.mmio_pools() {
+ let entry: Box<dyn Aml> = match (u32::try_from(*r.start()), u32::try_from(*r.end())) {
+ (Ok(start), Ok(end)) => Box::new(aml::AddressSpace::new_memory(
+ aml::AddressSpaceCachable::NotCacheable,
+ true,
+ start,
+ end,
+ )),
+ _ => Box::new(aml::AddressSpace::new_memory(
+ aml::AddressSpaceCachable::NotCacheable,
+ true,
+ *r.start(),
+ *r.end(),
+ )),
+ };
+ crs_entries.push(entry);
+ }
+
+ let mut pci_dsdt_inner_data: Vec<&dyn aml::Aml> = Vec::new();
+ let hid = aml::Name::new("_HID".into(), &aml::EISAName::new("PNP0A08"));
+ pci_dsdt_inner_data.push(&hid);
+ let cid = aml::Name::new("_CID".into(), &aml::EISAName::new("PNP0A03"));
+ pci_dsdt_inner_data.push(&cid);
+ let adr = aml::Name::new("_ADR".into(), &aml::ZERO);
+ pci_dsdt_inner_data.push(&adr);
+ let seg = aml::Name::new("_SEG".into(), &aml::ZERO);
+ pci_dsdt_inner_data.push(&seg);
+ let uid = aml::Name::new("_UID".into(), &aml::ZERO);
+ pci_dsdt_inner_data.push(&uid);
+ let supp = aml::Name::new("SUPP".into(), &aml::ZERO);
+ pci_dsdt_inner_data.push(&supp);
+ let crs = aml::Name::new(
+ "_CRS".into(),
+ &aml::ResourceTemplate::new(crs_entries.iter().map(|b| b.as_ref()).collect()),
+ );
+ pci_dsdt_inner_data.push(&crs);
+
+ let pci_root_osc = PciRootOSC {};
+ pci_dsdt_inner_data.push(&pci_root_osc);
+
+ aml::Device::new("_SB_.PCI0".into(), pci_dsdt_inner_data).to_aml_bytes(&mut amls);
+
let pm = Arc::new(Mutex::new(pmresource));
io_bus
.insert(
@@ -1079,34 +1415,13 @@ impl X8664arch {
devices::acpi::ACPIPM_RESOURCE_LEN as u64,
)
.unwrap();
- io_bus.notify_on_resume(pm);
-
- let bat_control = if let Some(battery_type) = battery.0 {
- match battery_type {
- BatteryType::Goldfish => {
- let control_tube = arch::add_goldfish_battery(
- &mut amls,
- battery.1,
- mmio_bus,
- irq_chip,
- X86_64_SCI_IRQ,
- resources,
- )
- .map_err(Error::CreateBatDevices)?;
- Some(BatControl {
- type_: BatteryType::Goldfish,
- control_tube,
- })
- }
- }
- } else {
- None
- };
+ resume_notify_devices.push(pm.clone());
Ok((
- acpi::ACPIDevResource {
+ acpi::AcpiDevResource {
amls,
pm_iobase,
+ pm,
sdts,
},
bat_control,
@@ -1123,29 +1438,29 @@ impl X8664arch {
/// * - `serial_parmaters` - definitions for how the serial devices should be configured
fn setup_serial_devices(
protected_vm: ProtectionType,
- irq_chip: &mut impl IrqChip,
- io_bus: &mut devices::Bus,
+ irq_chip: &mut dyn IrqChip,
+ io_bus: &devices::Bus,
serial_parameters: &BTreeMap<(SerialHardware, u8), SerialParameters>,
serial_jail: Option<Minijail>,
) -> Result<()> {
- let com_evt_1_3 = Event::new().map_err(Error::CreateEvent)?;
- let com_evt_2_4 = Event::new().map_err(Error::CreateEvent)?;
+ let com_evt_1_3 = devices::IrqEdgeEvent::new().map_err(Error::CreateEvent)?;
+ let com_evt_2_4 = devices::IrqEdgeEvent::new().map_err(Error::CreateEvent)?;
arch::add_serial_devices(
protected_vm,
io_bus,
- &com_evt_1_3,
- &com_evt_2_4,
- &serial_parameters,
+ com_evt_1_3.get_trigger(),
+ com_evt_2_4.get_trigger(),
+ serial_parameters,
serial_jail,
)
.map_err(Error::CreateSerialDevices)?;
irq_chip
- .register_irq_event(X86_64_SERIAL_1_3_IRQ, &com_evt_1_3, None)
+ .register_edge_irq_event(X86_64_SERIAL_1_3_IRQ, &com_evt_1_3)
.map_err(Error::RegisterIrqfd)?;
irq_chip
- .register_irq_event(X86_64_SERIAL_2_4_IRQ, &com_evt_2_4, None)
+ .register_edge_irq_event(X86_64_SERIAL_2_4_IRQ, &com_evt_2_4)
.map_err(Error::RegisterIrqfd)?;
Ok(())
@@ -1161,27 +1476,29 @@ mod tests {
#[test]
fn regions_lt_4gb_nobios() {
- let regions = arch_memory_regions(1u64 << 29, /* bios_size */ None);
+ let regions = arch_memory_regions(512 * MB, /* bios_size */ None);
assert_eq!(1, regions.len());
- assert_eq!(GuestAddress(0), regions[0].0);
+ assert_eq!(GuestAddress(START_OF_RAM_32BITS), regions[0].0);
assert_eq!(1u64 << 29, regions[0].1);
}
#[test]
fn regions_gt_4gb_nobios() {
- let regions = arch_memory_regions((1u64 << 32) + 0x8000, /* bios_size */ None);
+ let size = 4 * GB + 0x8000;
+ let regions = arch_memory_regions(size, /* bios_size */ None);
assert_eq!(2, regions.len());
- assert_eq!(GuestAddress(0), regions[0].0);
- assert_eq!(GuestAddress(1u64 << 32), regions[1].0);
+ assert_eq!(GuestAddress(START_OF_RAM_32BITS), regions[0].0);
+ assert_eq!(GuestAddress(4 * GB), regions[1].0);
+ assert_eq!(4 * GB + 0x8000, regions[0].1 + regions[1].1);
}
#[test]
fn regions_lt_4gb_bios() {
- let bios_len = 1 << 20;
- let regions = arch_memory_regions(1u64 << 29, Some(bios_len));
+ let bios_len = 1 * MB;
+ let regions = arch_memory_regions(512 * MB, Some(bios_len));
assert_eq!(2, regions.len());
- assert_eq!(GuestAddress(0), regions[0].0);
- assert_eq!(1u64 << 29, regions[0].1);
+ assert_eq!(GuestAddress(START_OF_RAM_32BITS), regions[0].0);
+ assert_eq!(512 * MB, regions[0].1);
assert_eq!(
GuestAddress(FIRST_ADDR_PAST_32BITS - bios_len),
regions[1].0
@@ -1191,39 +1508,72 @@ mod tests {
#[test]
fn regions_gt_4gb_bios() {
- let bios_len = 1 << 20;
- let regions = arch_memory_regions((1u64 << 32) + 0x8000, Some(bios_len));
+ let bios_len = 1 * MB;
+ let regions = arch_memory_regions(4 * GB + 0x8000, Some(bios_len));
assert_eq!(3, regions.len());
- assert_eq!(GuestAddress(0), regions[0].0);
+ assert_eq!(GuestAddress(START_OF_RAM_32BITS), regions[0].0);
assert_eq!(
GuestAddress(FIRST_ADDR_PAST_32BITS - bios_len),
regions[1].0
);
assert_eq!(bios_len, regions[1].1);
- assert_eq!(GuestAddress(1u64 << 32), regions[2].0);
+ assert_eq!(GuestAddress(4 * GB), regions[2].0);
}
#[test]
fn regions_eq_4gb_nobios() {
- // Test with size = 3328, which is exactly 4 GiB minus the size of the gap (768 MiB).
- let regions = arch_memory_regions(3328 << 20, /* bios_size */ None);
+ // Test with exact size of 4GB - the overhead.
+ let regions = arch_memory_regions(
+ 4 * GB - MEM_32BIT_GAP_SIZE - START_OF_RAM_32BITS,
+ /* bios_size */ None,
+ );
+ dbg!(&regions);
assert_eq!(1, regions.len());
- assert_eq!(GuestAddress(0), regions[0].0);
- assert_eq!(3328 << 20, regions[0].1);
+ assert_eq!(GuestAddress(START_OF_RAM_32BITS), regions[0].0);
+ assert_eq!(
+ 4 * GB - MEM_32BIT_GAP_SIZE - START_OF_RAM_32BITS,
+ regions[0].1
+ );
}
#[test]
fn regions_eq_4gb_bios() {
- // Test with size = 3328, which is exactly 4 GiB minus the size of the gap (768 MiB).
- let bios_len = 1 << 20;
- let regions = arch_memory_regions(3328 << 20, Some(bios_len));
+ // Test with exact size of 4GB - the overhead.
+ let bios_len = 1 * MB;
+ let regions = arch_memory_regions(
+ 4 * GB - MEM_32BIT_GAP_SIZE - START_OF_RAM_32BITS,
+ Some(bios_len),
+ );
assert_eq!(2, regions.len());
- assert_eq!(GuestAddress(0), regions[0].0);
- assert_eq!(3328 << 20, regions[0].1);
+ assert_eq!(GuestAddress(START_OF_RAM_32BITS), regions[0].0);
+ assert_eq!(
+ 4 * GB - MEM_32BIT_GAP_SIZE - START_OF_RAM_32BITS,
+ regions[0].1
+ );
assert_eq!(
GuestAddress(FIRST_ADDR_PAST_32BITS - bios_len),
regions[1].0
);
assert_eq!(bios_len, regions[1].1);
}
+
+ #[test]
+ #[cfg(feature = "direct")]
+ fn end_addr_before_32bits() {
+ // On volteer, type16 (coreboot) region is at 0x00000000769f3000-0x0000000076ffffff.
+ // On brya, type16 region is at 0x0000000076876000-0x00000000803fffff
+ let brya_type16_address = 0x7687_6000;
+ assert!(
+ END_ADDR_BEFORE_32BITS < brya_type16_address,
+ "{} < {}",
+ END_ADDR_BEFORE_32BITS,
+ brya_type16_address
+ );
+ }
+
+ #[test]
+ fn check_32bit_gap_size_alignment() {
+ // 32bit gap memory is 256 MB aligned to be friendly for MTRR mappings.
+ assert_eq!(MEM_32BIT_GAP_SIZE % (256 * MB), 0);
+ }
}
diff --git a/x86_64/src/mpspec.rs b/x86_64/src/mpspec.rs
index 5340d9e24..8d12edfd9 100644
--- a/x86_64/src/mpspec.rs
+++ b/x86_64/src/mpspec.rs
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#![allow(warnings)]
+
/* automatically generated by rust-bindgen */
pub const MPC_SIGNATURE: &'static [u8; 5usize] = b"PCMP\x00";
diff --git a/x86_64/src/mptable.rs b/x86_64/src/mptable.rs
index 7d6701ae2..2b392d456 100644
--- a/x86_64/src/mptable.rs
+++ b/x86_64/src/mptable.rs
@@ -3,63 +3,52 @@
// found in the LICENSE file.
use std::convert::TryFrom;
-use std::fmt::{self, Display};
use std::mem;
use std::result;
use std::slice;
use libc::c_char;
+use remain::sorted;
+use thiserror::Error;
use devices::{PciAddress, PciInterruptPin};
use vm_memory::{GuestAddress, GuestMemory};
use crate::mpspec::*;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- /// There was too little guest memory to store the entire MP table.
- NotEnoughMemory,
/// The MP table has too little address space to be stored.
+ #[error("The MP table has too little address space to be stored")]
AddressOverflow,
/// Failure while zeroing out the memory for the MP table.
+ #[error("Failure while zeroing out the memory for the MP table")]
Clear,
- /// Failure to write the MP floating pointer.
- WriteMpfIntel,
- /// Failure to write MP CPU entry.
- WriteMpcCpu,
- /// Failure to write MP ioapic entry.
- WriteMpcIoapic,
+ /// There was too little guest memory to store the entire MP table.
+ #[error("There was too little guest memory to store the MP table")]
+ NotEnoughMemory,
/// Failure to write MP bus entry.
+ #[error("Failure to write MP bus entry")]
WriteMpcBus,
+ /// Failure to write MP CPU entry.
+ #[error("Failure to write MP CPU entry")]
+ WriteMpcCpu,
/// Failure to write MP interrupt source entry.
+ #[error("Failure to write MP interrupt source entry")]
WriteMpcIntsrc,
+ /// Failure to write MP ioapic entry.
+ #[error("Failure to write MP ioapic entry")]
+ WriteMpcIoapic,
/// Failure to write MP local interrupt source entry.
+ #[error("Failure to write MP local interrupt source entry")]
WriteMpcLintsrc,
/// Failure to write MP table header.
+ #[error("Failure to write MP table header")]
WriteMpcTable,
-}
-
-impl std::error::Error for Error {}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- let description = match self {
- NotEnoughMemory => "There was too little guest memory to store the MP table",
- AddressOverflow => "The MP table has too little address space to be stored",
- Clear => "Failure while zeroing out the memory for the MP table",
- WriteMpfIntel => "Failure to write the MP floating pointer",
- WriteMpcCpu => "Failure to write MP CPU entry",
- WriteMpcIoapic => "Failure to write MP ioapic entry",
- WriteMpcBus => "Failure to write MP bus entry",
- WriteMpcIntsrc => "Failure to write MP interrupt source entry",
- WriteMpcLintsrc => "Failure to write MP local interrupt source entry",
- WriteMpcTable => "Failure to write MP table header",
- };
-
- write!(f, "MPTable error: {}", description)
- }
+ /// Failure to write the MP floating pointer.
+ #[error("Failure to write the MP floating pointer")]
+ WriteMpfIntel,
}
pub type Result<T> = result::Result<T, Error>;
@@ -117,7 +106,7 @@ fn compute_mp_size(num_cpus: u8) -> usize {
pub fn setup_mptable(
mem: &GuestMemory,
num_cpus: u8,
- pci_irqs: Vec<(PciAddress, u32, PciInterruptPin)>,
+ pci_irqs: &[(PciAddress, u32, PciInterruptPin)],
) -> Result<()> {
// Used to keep track of the next base pointer into the MP table.
let mut base_mp = GuestAddress(MPTABLE_START);
@@ -226,7 +215,7 @@ pub fn setup_mptable(
{
let size = mem::size_of::<mpc_intsrc>();
let mpc_intsrc = mpc_intsrc {
- type_: MP_INTSRC as u8,
+ type_: MP_LINTSRC as u8,
irqtype: mp_irq_source_types_mp_INT as u8,
irqflag: MP_IRQDIR_DEFAULT as u16,
srcbus: isa_bus_id,
@@ -296,7 +285,7 @@ pub fn setup_mptable(
}
let starting_isa_irq_num = pci_irqs
- .into_iter()
+ .iter()
.map(|(_, irq_num, _)| irq_num + 1)
.fold(super::X86_64_IRQ_BASE, u32::max) as u8;
@@ -403,7 +392,7 @@ mod tests {
)])
.unwrap();
- setup_mptable(&mem, num_cpus, Vec::new()).unwrap();
+ setup_mptable(&mem, num_cpus, &[]).unwrap();
}
#[test]
@@ -411,7 +400,7 @@ mod tests {
let num_cpus = 255;
let mem = GuestMemory::new(&[(GuestAddress(MPTABLE_START), 0x1000)]).unwrap();
- assert!(setup_mptable(&mem, num_cpus, Vec::new()).is_err());
+ assert!(setup_mptable(&mem, num_cpus, &[]).is_err());
}
#[test]
@@ -423,7 +412,7 @@ mod tests {
)])
.unwrap();
- setup_mptable(&mem, num_cpus, Vec::new()).unwrap();
+ setup_mptable(&mem, num_cpus, &[]).unwrap();
let mpf_intel = mem.read_obj_from_addr(GuestAddress(MPTABLE_START)).unwrap();
@@ -439,7 +428,7 @@ mod tests {
)])
.unwrap();
- setup_mptable(&mem, num_cpus, Vec::new()).unwrap();
+ setup_mptable(&mem, num_cpus, &[]).unwrap();
let mpf_intel: mpf_intel = mem.read_obj_from_addr(GuestAddress(MPTABLE_START)).unwrap();
let mpc_offset = GuestAddress(mpf_intel.physptr as u64);
@@ -465,7 +454,7 @@ mod tests {
.unwrap();
for i in 0..MAX_CPUS {
- setup_mptable(&mem, i, Vec::new()).unwrap();
+ setup_mptable(&mem, i, &[]).unwrap();
let mpf_intel: mpf_intel = mem.read_obj_from_addr(GuestAddress(MPTABLE_START)).unwrap();
let mpc_offset = GuestAddress(mpf_intel.physptr as u64);
diff --git a/x86_64/src/regs.rs b/x86_64/src/regs.rs
index 03369643b..82df5d99c 100644
--- a/x86_64/src/regs.rs
+++ b/x86_64/src/regs.rs
@@ -2,62 +2,52 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fmt::{self, Display};
use std::{mem, result};
use base::{self, warn};
-use hypervisor::{Fpu, Register, Regs, Sregs, VcpuX86_64};
+use hypervisor::{Fpu, Register, Regs, Sregs, VcpuX86_64, Vm};
+use remain::sorted;
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory};
use crate::gdt;
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- /// Setting up msrs failed.
- MsrIoctlFailed(base::Error),
/// Failed to configure the FPU.
+ #[error("failed to configure the FPU: {0}")]
FpuIoctlFailed(base::Error),
/// Failed to get sregs for this cpu.
+ #[error("failed to get sregs for this cpu: {0}")]
GetSRegsIoctlFailed(base::Error),
- /// Failed to set base registers for this cpu.
- SettingRegistersIoctl(base::Error),
+ /// Setting up msrs failed.
+ #[error("setting up msrs failed: {0}")]
+ MsrIoctlFailed(base::Error),
/// Failed to set sregs for this cpu.
+ #[error("failed to set sregs for this cpu: {0}")]
SetSRegsIoctlFailed(base::Error),
+ /// Failed to set base registers for this cpu.
+ #[error("failed to set base registers for this cpu: {0}")]
+ SettingRegistersIoctl(base::Error),
/// Writing the GDT to RAM failed.
+ #[error("writing the GDT to RAM failed")]
WriteGDTFailure,
/// Writing the IDT to RAM failed.
+ #[error("writing the IDT to RAM failed")]
WriteIDTFailure,
- /// Writing PML4 to RAM failed.
- WritePML4Address,
- /// Writing PDPTE to RAM failed.
- WritePDPTEAddress,
/// Writing PDE to RAM failed.
+ #[error("writing PDE to RAM failed")]
WritePDEAddress,
+ /// Writing PDPTE to RAM failed.
+ #[error("writing PDPTE to RAM failed")]
+ WritePDPTEAddress,
+ /// Writing PML4 to RAM failed.
+ #[error("writing PML4 to RAM failed")]
+ WritePML4Address,
}
-pub type Result<T> = result::Result<T, Error>;
-impl std::error::Error for Error {}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- match self {
- MsrIoctlFailed(e) => write!(f, "setting up msrs failed: {}", e),
- FpuIoctlFailed(e) => write!(f, "failed to configure the FPU: {}", e),
- GetSRegsIoctlFailed(e) => write!(f, "failed to get sregs for this cpu: {}", e),
- SettingRegistersIoctl(e) => {
- write!(f, "failed to set base registers for this cpu: {}", e)
- }
- SetSRegsIoctlFailed(e) => write!(f, "failed to set sregs for this cpu: {}", e),
- WriteGDTFailure => write!(f, "writing the GDT to RAM failed"),
- WriteIDTFailure => write!(f, "writing the IDT to RAM failed"),
- WritePML4Address => write!(f, "writing PML4 to RAM failed"),
- WritePDPTEAddress => write!(f, "writing PDPTE to RAM failed"),
- WritePDEAddress => write!(f, "writing PDE to RAM failed"),
- }
- }
-}
+pub type Result<T> = result::Result<T, Error>;
const MTRR_MEMTYPE_UC: u8 = 0x0;
const MTRR_MEMTYPE_WB: u8 = 0x6;
@@ -103,7 +93,12 @@ fn get_mtrr_pairs(base: u64, len: u64) -> Vec<(u64, u64)> {
vecs
}
-fn append_mtrr_entries(vpu: &dyn VcpuX86_64, pci_start: u64, entries: &mut Vec<Register>) {
+fn append_mtrr_entries(
+ vm: &dyn Vm,
+ vpu: &dyn VcpuX86_64,
+ pci_start: u64,
+ entries: &mut Vec<Register>,
+) {
// Get VAR MTRR num from MSR_MTRRcap
let mut msrs = vec![Register {
id: crate::msr_index::MSR_MTRRcap,
@@ -127,7 +122,7 @@ fn append_mtrr_entries(vpu: &dyn VcpuX86_64, pci_start: u64, entries: &mut Vec<R
return;
}
- let phys_mask: u64 = (1 << crate::cpuid::phy_max_address_bits()) - 1;
+ let phys_mask: u64 = (1 << vm.get_guest_phys_addr_bits()) - 1;
for (idx, (base, len)) in vecs.iter().enumerate() {
let reg_idx = idx as u32 * 2;
entries.push(Register {
@@ -147,7 +142,7 @@ fn append_mtrr_entries(vpu: &dyn VcpuX86_64, pci_start: u64, entries: &mut Vec<R
});
}
-fn create_msr_entries(vcpu: &dyn VcpuX86_64, pci_start: u64) -> Vec<Register> {
+fn create_msr_entries(vm: &dyn Vm, vcpu: &dyn VcpuX86_64, pci_start: u64) -> Vec<Register> {
let mut entries = vec![
Register {
id: crate::msr_index::MSR_IA32_SYSENTER_CS,
@@ -192,7 +187,7 @@ fn create_msr_entries(vcpu: &dyn VcpuX86_64, pci_start: u64) -> Vec<Register> {
value: crate::msr_index::MSR_IA32_MISC_ENABLE_FAST_STRING as u64,
},
];
- append_mtrr_entries(vcpu, pci_start, &mut entries);
+ append_mtrr_entries(vm, vcpu, pci_start, &mut entries);
entries
}
@@ -201,8 +196,8 @@ fn create_msr_entries(vcpu: &dyn VcpuX86_64, pci_start: u64) -> Vec<Register> {
/// # Arguments
///
/// * `vcpu` - Structure for the vcpu that holds the vcpu fd.
-pub fn setup_msrs(vcpu: &dyn VcpuX86_64, pci_start: u64) -> Result<()> {
- let msrs = create_msr_entries(vcpu, pci_start);
+pub fn setup_msrs(vm: &dyn Vm, vcpu: &dyn VcpuX86_64, pci_start: u64) -> Result<()> {
+ let msrs = create_msr_entries(vm, vcpu, pci_start);
vcpu.set_msrs(&msrs).map_err(Error::MsrIoctlFailed)
}
@@ -249,8 +244,8 @@ const X86_CR4_PAE: u64 = 0x20;
const EFER_LME: u64 = 0x100;
const EFER_LMA: u64 = 0x400;
-const BOOT_GDT_OFFSET: u64 = 0x500;
-const BOOT_IDT_OFFSET: u64 = 0x520;
+const BOOT_GDT_OFFSET: u64 = 0x1500;
+const BOOT_IDT_OFFSET: u64 = 0x1520;
const BOOT_GDT_MAX: usize = 4;
diff --git a/x86_64/src/smbios.rs b/x86_64/src/smbios.rs
index 6fd8774e1..09fd5a2c5 100644
--- a/x86_64/src/smbios.rs
+++ b/x86_64/src/smbios.rs
@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-use std::fmt::{self, Display};
use std::mem;
use std::result;
use std::slice;
@@ -12,47 +11,37 @@ use std::io::prelude::*;
use std::path::{Path, PathBuf};
use data_model::DataInit;
+use remain::sorted;
+use thiserror::Error;
use vm_memory::{GuestAddress, GuestMemory};
-#[derive(Debug)]
+#[sorted]
+#[derive(Error, Debug)]
pub enum Error {
- /// There was too little guest memory to store the entire SMBIOS table.
- NotEnoughMemory,
/// The SMBIOS table has too little address space to be stored.
+ #[error("The SMBIOS table has too little address space to be stored")]
AddressOverflow,
/// Failure while zeroing out the memory for the SMBIOS table.
+ #[error("Failure while zeroing out the memory for the SMBIOS table")]
Clear,
- /// Failure to write SMBIOS entrypoint structure
- WriteSmbiosEp,
- /// Failure to write additional data to memory
- WriteData,
- /// Failure while reading SMBIOS data file
- IoFailed,
- /// Incorrect or not readable host SMBIOS data
- InvalidInput,
/// Invalid table entry point checksum
+ #[error("Failure to verify host SMBIOS entry checksum")]
InvalidChecksum,
-}
-
-impl std::error::Error for Error {}
-
-impl Display for Error {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Error::*;
-
- let description = match self {
- NotEnoughMemory => "There was too little guest memory to store the SMBIOS table",
- AddressOverflow => "The SMBIOS table has too little address space to be stored",
- Clear => "Failure while zeroing out the memory for the SMBIOS table",
- WriteSmbiosEp => "Failure to write SMBIOS entrypoint structure",
- WriteData => "Failure to write additional data to memory",
- IoFailed => "Failure while reading SMBIOS data file",
- InvalidInput => "Failure to read host SMBIOS data",
- InvalidChecksum => "Failure to verify host SMBIOS entry checksum",
- };
-
- write!(f, "SMBIOS error: {}", description)
- }
+ /// Incorrect or not readable host SMBIOS data
+ #[error("Failure to read host SMBIOS data")]
+ InvalidInput,
+ /// Failure while reading SMBIOS data file
+ #[error("Failure while reading SMBIOS data file")]
+ IoFailed,
+ /// There was too little guest memory to store the entire SMBIOS table.
+ #[error("There was too little guest memory to store the SMBIOS table")]
+ NotEnoughMemory,
+ /// Failure to write additional data to memory
+ #[error("Failure to write additional data to memory")]
+ WriteData,
+ /// Failure to write SMBIOS entrypoint structure
+ #[error("Failure to write SMBIOS entrypoint structure")]
+ WriteSmbiosEp,
}
pub type Result<T> = result::Result<T, Error>;
diff --git a/x86_64/src/test_integration.rs b/x86_64/src/test_integration.rs
index 6ee54e1a3..01db29aed 100644
--- a/x86_64/src/test_integration.rs
+++ b/x86_64/src/test_integration.rs
@@ -4,8 +4,10 @@
#![cfg(any(target_arch = "x86", target_arch = "x86_64"))]
-use devices::{IrqChipX86_64, ProtectionType};
-use hypervisor::{HypervisorX86_64, VcpuExit, VcpuX86_64, VmX86_64};
+use arch::LinuxArch;
+use devices::IrqChipX86_64;
+use hypervisor::{HypervisorX86_64, ProtectionType, VcpuExit, VcpuX86_64, VmX86_64};
+use resources::SystemAllocator;
use vm_memory::{GuestAddress, GuestMemory};
use super::cpuid::setup_cpuid;
@@ -15,7 +17,7 @@ use super::X8664arch;
use super::{acpi, arch_memory_regions, bootparam, mptable, smbios};
use super::{
BOOT_STACK_POINTER, END_ADDR_BEFORE_32BITS, KERNEL_64BIT_ENTRY_OFFSET, KERNEL_START_OFFSET,
- X86_64_SCI_IRQ, ZERO_PAGE_OFFSET,
+ PCIE_CFG_MMIO_SIZE, PCIE_CFG_MMIO_START, X86_64_SCI_IRQ, ZERO_PAGE_OFFSET,
};
use base::{Event, Tube};
@@ -40,7 +42,8 @@ fn simple_kvm_kernel_irqchip_test() {
simple_vm_test::<_, _, KvmVcpu, _, _, _>(
|guest_mem| {
let kvm = Kvm::new().expect("failed to create kvm");
- let vm = KvmVm::new(&kvm, guest_mem).expect("failed to create kvm vm");
+ let vm = KvmVm::new(&kvm, guest_mem, ProtectionType::Unprotected)
+ .expect("failed to create kvm vm");
(kvm, vm)
},
|vm, vcpu_count, _| {
@@ -56,7 +59,8 @@ fn simple_kvm_split_irqchip_test() {
simple_vm_test::<_, _, KvmVcpu, _, _, _>(
|guest_mem| {
let kvm = Kvm::new().expect("failed to create kvm");
- let vm = KvmVm::new(&kvm, guest_mem).expect("failed to create kvm vm");
+ let vm = KvmVm::new(&kvm, guest_mem, ProtectionType::Unprotected)
+ .expect("failed to create kvm vm");
(kvm, vm)
},
|vm, vcpu_count, device_tube| {
@@ -98,14 +102,16 @@ where
let arch_mem_regions = arch_memory_regions(memory_size, None);
let guest_mem = GuestMemory::new(&arch_mem_regions).unwrap();
- let mut resources = X8664arch::get_resource_allocator(&guest_mem);
-
let (hyp, mut vm) = create_vm(guest_mem.clone());
+ let mut resources =
+ SystemAllocator::new(X8664arch::get_system_allocator_config(&vm), None, &[])
+ .expect("failed to create system allocator");
let (irqchip_tube, device_tube) = Tube::pair().expect("failed to create irq tube");
let mut irq_chip = create_irq_chip(vm.try_clone().expect("failed to clone vm"), 1, device_tube);
- let mut mmio_bus = devices::Bus::new();
+ let mmio_bus = Arc::new(devices::Bus::new());
+ let io_bus = Arc::new(devices::Bus::new());
let exit_evt = Event::new().unwrap();
let mut control_tubes = vec![TaggedControlTube::VmIrq(irqchip_tube)];
@@ -127,36 +133,39 @@ where
let (pci, pci_irqs, _pid_debug_label_map) = arch::generate_pci_root(
devices,
&mut irq_chip,
- &mut mmio_bus,
+ mmio_bus.clone(),
+ io_bus.clone(),
&mut resources,
&mut vm,
4,
)
.unwrap();
- let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(pci)));
+ let pci = Arc::new(Mutex::new(pci));
+ let pci_bus = Arc::new(Mutex::new(PciConfigIo::new(pci, Event::new().unwrap())));
+ io_bus.insert(pci_bus, 0xcf8, 0x8).unwrap();
- let mut io_bus = X8664arch::setup_io_bus(
+ X8664arch::setup_legacy_devices(
+ &io_bus,
irq_chip.pit_uses_speaker_port(),
exit_evt.try_clone().unwrap(),
- Some(pci_bus),
memory_size,
)
.unwrap();
let mut serial_params = BTreeMap::new();
- arch::set_default_serial_parameters(&mut serial_params);
+ arch::set_default_serial_parameters(&mut serial_params, false);
X8664arch::setup_serial_devices(
ProtectionType::Unprotected,
&mut irq_chip,
- &mut io_bus,
+ &io_bus,
&serial_params,
None,
)
.unwrap();
- let param_args = "nokaslr";
+ let param_args = "nokaslr acpi=noirq";
let mut cmdline = X8664arch::get_base_linux_cmdline();
@@ -173,9 +182,12 @@ where
// let mut kernel_image = File::open("/mnt/host/source/src/avd/vmlinux.uncompressed").expect("failed to open kernel");
// let (params, kernel_end) = X8664arch::load_kernel(&guest_mem, &mut kernel_image).expect("failed to load kernel");
+ let max_bus = (PCIE_CFG_MMIO_SIZE / 0x100000 - 1) as u8;
let suspend_evt = Event::new().unwrap();
+ let mut resume_notify_devices = Vec::new();
let acpi_dev_resource = X8664arch::setup_acpi_devices(
- &mut io_bus,
+ &guest_mem,
+ &io_bus,
&mut resources,
suspend_evt
.try_clone()
@@ -183,14 +195,16 @@ where
exit_evt.try_clone().expect("unable to clone exit_evt"),
Default::default(),
&mut irq_chip,
+ X86_64_SCI_IRQ,
(&None, None),
- &mut mmio_bus,
+ &mmio_bus,
+ max_bus,
+ &mut resume_notify_devices,
)
.unwrap();
X8664arch::setup_system_memory(
&guest_mem,
- memory_size,
&CString::new(cmdline).expect("failed to create cmdline"),
initrd_image,
None,
@@ -200,10 +214,24 @@ where
.expect("failed to setup system_memory");
// Note that this puts the mptable at 0x9FC00 in guest physical memory.
- mptable::setup_mptable(&guest_mem, 1, pci_irqs).expect("failed to setup mptable");
+ mptable::setup_mptable(&guest_mem, 1, &pci_irqs).expect("failed to setup mptable");
smbios::setup_smbios(&guest_mem, None).expect("failed to setup smbios");
- acpi::create_acpi_tables(&guest_mem, 1, X86_64_SCI_IRQ, acpi_dev_resource.0);
+ let mut apic_ids = Vec::new();
+ acpi::create_acpi_tables(
+ &guest_mem,
+ 1,
+ X86_64_SCI_IRQ,
+ 0xcf9,
+ 6,
+ &acpi_dev_resource.0,
+ None,
+ &mut apic_ids,
+ &pci_irqs,
+ PCIE_CFG_MMIO_START,
+ max_bus,
+ false,
+ );
let guest_mem2 = guest_mem.clone();
@@ -221,8 +249,8 @@ where
.add_vcpu(0, &vcpu)
.expect("failed to add vcpu to irqchip");
- setup_cpuid(&hyp, &irq_chip, &vcpu, 0, 1, false).unwrap();
- setup_msrs(&vcpu, END_ADDR_BEFORE_32BITS).unwrap();
+ setup_cpuid(&hyp, &irq_chip, &vcpu, 0, 1, false, false).unwrap();
+ setup_msrs(&vm, &vcpu, END_ADDR_BEFORE_32BITS).unwrap();
setup_regs(
&vcpu,